lemonbar: Handle restarts

This commit is contained in:
kuben
2019-07-28 12:02:50 +02:00
parent 162c3a208f
commit 96d944d2ef
3 changed files with 105 additions and 65 deletions

View File

@@ -6,6 +6,10 @@ import i3_lemonbar_config as config
p_screen = None p_screen = None
kill_on_unfocus = [] kill_on_unfocus = []
# Loggers, initialized in main function
logger = None
health_logger = None
class bar_mode(Enum): class bar_mode(Enum):
power, normal, control = range(-1,2) # Don't cycle through power power, normal, control = range(-1,2) # Don't cycle through power

View File

@@ -35,37 +35,37 @@ def assert_only_instance():
with open(config.pid_file, 'r') as fp: with open(config.pid_file, 'r') as fp:
pid = int(fp.read()) pid = int(fp.read())
except IOError: except IOError:
logger.debug('Could not open PID file. Assuming non existent') common.logger.debug('Could not open PID file. Assuming non existent')
except ValueError: except ValueError:
logger.debug('''PID file contents broken''') common.logger.debug('''PID file contents broken''')
try: try:
os.remove(config.pid_file) os.remove(config.pid_file)
logger.debug('''Deleted old PID file, continuing as usual''') common.logger.debug('''Deleted old PID file, continuing as usual''')
except OSError: except OSError:
logger.debug('''Failed deleting old PID file.''') common.logger.debug('''Failed deleting old PID file.''')
os._exit(1) os._exit(1)
if pid is not None: if pid is not None:
try: try:
logger.debug('''Found old PID file. Looking for owner''') common.logger.debug('''Found old PID file. Looking for owner''')
os.kill(pid, 0) os.kill(pid, 0)
logger.debug('''Owner exists''') common.logger.debug('''Owner exists''')
logger.debug('''Failed, another instance of the launcher is running common.logger.debug('''Failed, another instance of the launcher is running
(PID {})'''.format(pid)) (PID {})'''.format(pid))
os._exit(1) os._exit(1)
except ProcessLookupError: except ProcessLookupError:
logger.debug('''Owner does not exist''') common.logger.debug('''Owner does not exist''')
try: try:
os.remove(config.pid_file) os.remove(config.pid_file)
logger.debug('''Deleted old PID file, continuing as usual''') common.logger.debug('''Deleted old PID file, continuing as usual''')
except OSError: except OSError:
logger.debug('''Failed deleting old PID file.''') common.logger.debug('''Failed deleting old PID file.''')
os._exit(1) os._exit(1)
with open(config.pid_file, 'w+') as fp: with open(config.pid_file, 'w+') as fp:
fp.write('{:d}'.format(os.getpid())) fp.write('{:d}'.format(os.getpid()))
logger.debug('''Created and wrote to PID file''') common.logger.debug('''Created and wrote to PID file''')
create_new_fifo(config.fifo_file_status) create_new_fifo(config.fifo_file_status)
create_new_fifo(config.fifo_file_executor) create_new_fifo(config.fifo_file_executor)
@@ -76,22 +76,22 @@ def create_new_fifo(fifo_file):
""" """
try: try:
os.remove(fifo_file) os.remove(fifo_file)
logger.debug('''Removed old fifo file''') common.logger.debug('''Removed old fifo file''')
except OSError: except OSError:
logger.debug('''No old fifo file, good''') common.logger.debug('''No old fifo file, good''')
try: try:
os.mkfifo(fifo_file) os.mkfifo(fifo_file)
except OSError: except OSError:
logger.error('''Failed, couldn't create fifo file''') common.logger.error('''Failed, couldn't create fifo file''')
os.remove(config.pid_file) # Clean up own PID file os.remove(config.pid_file) # Clean up own PID file
sys.exit(0) sys.exit(0)
def handle_exit(signum, frame): def handle_exit(signum, frame):
logger.info('Signal handler called with signal {}'.format(signum)) common.logger.info('Signal handler called with signal {}'.format(signum))
logger.info('Calling os._exit(0)') common.logger.info('Calling os._exit(0)')
os._exit(0) os._exit(0)
# Terminates process p nicely # Terminates process p nicely
@@ -104,7 +104,7 @@ def nice_delete(f):
os.remove(f) os.remove(f)
def clean_up(): def clean_up():
logger.debug('Cleaning up') common.logger.debug('Cleaning up')
nice_delete(config.pid_file) nice_delete(config.pid_file)
nice_delete(config.fifo_file_status) nice_delete(config.fifo_file_status)
nice_delete(config.fifo_file_executor) nice_delete(config.fifo_file_executor)
@@ -120,11 +120,12 @@ def write_sys_status():
with open(config.fifo_file_status, 'w') as fifo: with open(config.fifo_file_status, 'w') as fifo:
p_conky_slow = subprocess.Popen(['conky', '-c', config.path+'conky_slow'], # Use communicate p_conky_slow = subprocess.Popen(['conky', '-c', config.path+'conky_slow'], # Use communicate
stdout=fifo, stderr=fifo) stdout=fifo, stderr=fifo)
logger.debug('Started conky slow') common.logger.debug('Started conky slow')
p_conky_fast = subprocess.Popen(['conky', '-c', config.path+'conky_fast'], p_conky_fast = subprocess.Popen(['conky', '-c', config.path+'conky_fast'],
stdout=fifo, stderr=fifo) stdout=fifo, stderr=fifo)
logger.debug('Started conky fast') common.logger.debug('Started conky fast')
i3_ws_obj = wspaces.i3ws(fifo_file=config.fifo_file_status) i3_ws_obj = wspaces.i3ws(fifo_file=config.fifo_file_status)
i3_ws_obj.work()
def parse_status(): def parse_status():
global p_lemonbar global p_lemonbar
@@ -133,13 +134,13 @@ def parse_status():
,stdin=subprocess.PIPE, stdout=fifo_write, text=True) ,stdin=subprocess.PIPE, stdout=fifo_write, text=True)
with open(config.fifo_file_status, 'r', buffering=1) as fifo_read: with open(config.fifo_file_status, 'r', buffering=1) as fifo_read:
# Let parser read from fifo # Let parser read from fifo
logger.debug("FIFO {} opened for reading".format(config.fifo_file_status)) common.logger.debug("FIFO {} opened for reading".format(config.fifo_file_status))
lemonparser.parse_line('WSPINA1___main INA2___web FOC5___terms INA6___stats ') # TODO modular lemonparser.parse_line('WSPINA1___main INA2___web FOC5___terms INA6___stats ') # TODO modular
while True: while True:
try: try:
data = fifo_read.readline() # Blocking read data = fifo_read.readline() # Blocking read
if len(data) == 0: if len(data) == 0:
logger.debug("Writer closed") common.logger.debug("Writer closed")
break break
psd = lemonparser.parse_line(data) psd = lemonparser.parse_line(data)
p_lemonbar.stdin.write(lemonparser.parse_line(data) + '\n') p_lemonbar.stdin.write(lemonparser.parse_line(data) + '\n')
@@ -147,21 +148,21 @@ def parse_status():
#logger.debug('Read: "{0}"'.format(data)) #logger.debug('Read: "{0}"'.format(data))
#logger.debug('Parsed "{0}"'.format(psd)) #logger.debug('Parsed "{0}"'.format(psd))
except BrokenPipeError: except BrokenPipeError:
logger.debug('Broken pipe in parse status thread, exiting') common.logger.debug('Broken pipe in parse status thread, exiting')
health_logger.info('Broken pipe in parse status thread, exiting') common.health_logger.info('Broken pipe in parse status thread, exiting')
clean_up() clean_up()
except: except:
logger.debug('Unknown exception in parse status thread, exiting') common.logger.debug('Unknown exception in parse status thread, exiting')
health_logger.info('Unknown exception in parse status thread, exiting') common.health_logger.info('Unknown exception in parse status thread, exiting')
clean_up() clean_up()
def exec_commands(): def exec_commands():
with open(config.fifo_file_executor, 'r', buffering=1) as fifo_read: with open(config.fifo_file_executor, 'r', buffering=1) as fifo_read:
logger.debug("FIFO {} opened for reading".format(config.fifo_file_executor)) common.logger.debug("FIFO {} opened for reading".format(config.fifo_file_executor))
while True: while True:
try: try:
data = fifo_read.readline() data = fifo_read.readline()
logger.debug('Trying reading: "{0}"'.format(data.strip('\n'))) common.logger.debug('Trying reading: "{0}"'.format(data.strip('\n')))
try: try:
for key,func in common.commands_dict.items(): for key,func in common.commands_dict.items():
l = len(key) l = len(key)
@@ -170,17 +171,17 @@ def exec_commands():
break break
except: except:
logger.debug('Exception occured executing command\n Line in: {}\n Data: {}'.format(line_in, line_in[l:].split())) common.logger.debug('Exception occured executing command\n Line in: {}\n Data: {}'.format(line_in, line_in[l:].split()))
if len(data) == 0: if len(data) == 0:
logger.debug("Lemonbar output closed") common.logger.debug("Lemonbar output closed")
break break
logger.debug('Read: "{0}"'.format(data.strip('\n'))) common.logger.debug('Read: "{0}"'.format(data.strip('\n')))
except BrokenPipeError: except BrokenPipeError:
logger.debug('Broken pipe in exec commands thread, exiting') common.logger.debug('Broken pipe in exec commands thread, exiting')
health_logger.info('Broken pipe in exec commands thread, exiting') common.health_logger.info('Broken pipe in exec commands thread, exiting')
clean_up() clean_up()
except: except:
health_logger.info('Unknown exception in exec commands thread, exiting') common.health_logger.info('Unknown exception in exec commands thread, exiting')
raise raise
clean_up() clean_up()
def strip_ansi_unicode(s): def strip_ansi_unicode(s):
@@ -196,7 +197,7 @@ def user_screen():
,stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True) ,stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
for line in common.p_screen.stdout: for line in common.p_screen.stdout:
#try: #try:
logger.debug('Screen read line {}'.format(line.strip('\n'))) common.logger.debug('Screen read line {}'.format(line.strip('\n')))
# with open(config.fifo_file_status, 'w') as fifo: # with open(config.fifo_file_status, 'w') as fifo:
# clean_line = strip_ansi_unicode(line) # clean_line = strip_ansi_unicode(line)
# if (not clean_line.startswith('[bluetooth]') # if (not clean_line.startswith('[bluetooth]')
@@ -215,13 +216,13 @@ class i3_thread:
self.desc = desc self.desc = desc
self.all_threads.append(self) self.all_threads.append(self)
health_logger.info('"%s" starting', desc) common.health_logger.info('"%s" starting', desc)
self.thread.start() self.thread.start()
# Wrapper around the target # Wrapper around the target
def run(target, desc): def run(target, desc):
target() target()
health_logger.info('"%s" reached end', desc) common.health_logger.info('"%s" reached end', desc)
def join_threads(): def join_threads():
for i3_th in i3_thread.all_threads: for i3_th in i3_thread.all_threads:
@@ -241,19 +242,19 @@ if __name__ == "__main__":
handler = logging.StreamHandler(sys.stdout) handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter) handler.setFormatter(formatter)
logger = logging.getLogger('Normal logger') common.logger = logging.getLogger('Normal logger')
logger.setLevel(debuglvl) common.logger.setLevel(debuglvl)
logger.addHandler(handler) common.logger.addHandler(handler)
# Setup health logger to file in tmp # Setup health logger to file in tmp
formatter = logging.Formatter('%(asctime)s %(message)s') formatter = logging.Formatter('%(asctime)s %(message)s')
handler = logging.FileHandler(config.health_file) handler = logging.FileHandler(config.health_file)
handler.setFormatter(formatter) handler.setFormatter(formatter)
health_logger = logging.getLogger('Health logger') common.health_logger = logging.getLogger('Health logger')
health_logger.addHandler(handler) common.health_logger.addHandler(handler)
# logger.basicConfig(stream=sys.stdout, level=debuglvl) # common.logger.basicConfig(stream=sys.stdout, level=debuglvl)
assert_only_instance() # Creates pid file and fifo file assert_only_instance() # Creates pid file and fifo file
atexit.register(clean_up) atexit.register(clean_up)
signal.signal(signal.SIGTERM, handle_exit) signal.signal(signal.SIGTERM, handle_exit)
@@ -266,7 +267,7 @@ if __name__ == "__main__":
i3_thread(target = write_sys_status, desc='Write sys status thread') i3_thread(target = write_sys_status, desc='Write sys status thread')
i3_thread(target = user_screen, desc='Screen thread for user commands') i3_thread(target = user_screen, desc='Screen thread for user commands')
logger.debug('Threads started') common.logger.debug('Threads started')
i3_thread.join_threads() i3_thread.join_threads()
logger.debug('Reached end') common.logger.debug('Reached end')

View File

@@ -56,11 +56,15 @@ class State(object):
return self.inactive return self.inactive
# Create the i3ws object, then call locking function work()
# Another implementation would be to create a thread in __init__
# and implement a locking join() function
class i3ws(object): class i3ws(object):
ws_format = '%s%s ' ws_format = '%s%s '
end_format = 'WSP%s' end_format = 'WSP%s'
state = State() state = State()
backgrounds = {} backgrounds = {}
running = True
fifo = None fifo = None
@@ -69,10 +73,19 @@ class i3ws(object):
self.state = state self.state = state
if fifo_file: if fifo_file:
self.fifo = open(fifo_file, 'w') self.fifo = open(fifo_file, 'w')
def work(self):
# While loop to restart connection as long as there is an i3 ipc socket
while self.running:
self.resetConn()
common.logger.debug('Started i3 workspaces manager')
self.enterMain()
time.sleep(0.5) # TODO max number of attempts + catch exception instead
def resetConn(self):
# conn # conn
self.conn = i3ipc.Connection() self.conn = i3ipc.Connection()
# Run call backs once # Run call backs once
self.change(self.conn, None) self.change(self.conn, None)
self.win_focused(self.conn, None) self.win_focused(self.conn, None)
@@ -81,13 +94,19 @@ class i3ws(object):
self.conn.on('workspace::init' , self.change) self.conn.on('workspace::init' , self.change)
self.conn.on('workspace::empty' , self.change) self.conn.on('workspace::empty' , self.change)
self.conn.on('window::focus' , self.win_focused) self.conn.on('window::focus' , self.win_focused)
logging.debug('Started i3 workspaces manager') self.conn.on('shutdown' , self.shutdown)
def enterMain(self):
try: try:
# Locking call
self.conn.main() self.conn.main()
except BrokenPipeError: except BrokenPipeError:
logging.debug('Broken pipe in i3 workspaces thread, exiting') common.logger.debug('Broken pipe in i3 workspaces thread, exiting')
except: except:
logging.debug('Unknown exception in i3 workspaces thread, exiting') common.logger.debug('Unknown exception in i3 workspaces thread, exiting')
def shutdown(self, i3, e):
common.logger.debug('Shut down i3ipc')
def change(self, i3, e): def change(self, i3, e):
# Receives event and workspace data # Receives event and workspace data
@@ -121,16 +140,16 @@ class i3ws(object):
try: try:
os.kill(pid, signal.SIGTERM) os.kill(pid, signal.SIGTERM)
except ProcessLookupError: except ProcessLookupError:
logging.debug('Tried killing process {} but it doesn\'t exist'.format(pid)) common.logger.debug('Tried killing process {} but it doesn\'t exist'.format(pid))
common.kill_on_unfocus = [] common.kill_on_unfocus = []
# Set correct keymap # Set correct keymap
if wclass in common.keymaps: if wclass in common.keymaps:
new_km = common.keymaps[wclass] new_km = common.keymaps[wclass]
logging.debug('Setting {} as keymap for {}'.format(new_km, wclass)) common.logger.debug('Setting {} as keymap for {}'.format(new_km, wclass))
else: else:
new_km = common.def_keymap new_km = common.def_keymap
logging.debug('Setting default keymap {} for {}'.format(common.def_keymap, wclass)) common.logger.debug('Setting default keymap {} for {}'.format(common.def_keymap, wclass))
call(['/home/kuba/.i3/scripts/lang.sh', 'qset', new_km]) call(['/home/kuba/.i3/scripts/lang.sh', 'qset', new_km])
@@ -169,18 +188,34 @@ class i3ws(object):
call(['sh', '/home/kuba/.i3/lemonbar/set_bg.sh', args]) call(['sh', '/home/kuba/.i3/lemonbar/set_bg.sh', args])
def quit(self): def quit(self):
common.logger.debug('Quitting i3 workspace script')
self.running = False
self.conn.main_quit()
if self.fifo is not None: if self.fifo is not None:
try: try:
self.fifo.close() self.fifo.close()
except BrokenPipeError: except BrokenPipeError:
pass pass
def handle_exit(signal, frame):
global ws
print("Recieved Keyboard Interrupt from user")
ws.quit()
sys.exit(1)
if __name__ == '__main__': if __name__ == '__main__':
# Setup logger to stdout with debug log level
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter)
common.logger = logging.getLogger('Normal logger')
common.logger.setLevel(logging.DEBUG)
common.logger.addHandler(handler)
# Capture Keyboard Interrupt (i3ipc captures this internally)
signal.signal(signal.SIGINT, handle_exit)
# Start main
ws = i3ws() ws = i3ws()
try: ws.work()
while True:
time.sleep(1)
except KeyboardInterrupt:
print('') # force new line
# finally:
# ws.quit()