diff options
| author | Ultrabug <ultrabug@gentoo.org> | 2015-02-10 22:01:30 +0100 |
|---|---|---|
| committer | Ultrabug <ultrabug@gentoo.org> | 2015-02-10 22:01:30 +0100 |
| commit | 5c6f434f98550a5a6b350ad8303d0d6dd7aa9829 (patch) | |
| tree | e3efeb0bffd31cad2afdc90cddec9f0ee43d0752 /py3status/__init__.py | |
| parent | 232b6d4ec721ecc52a1525afe5a1ca9762ca70bb (diff) | |
refactor the main loop for instant i3bar update on module or i3status output change wrt issue #59, handle correctly all and multiple time and tztime instances while updating them only on py3status interval
Diffstat (limited to 'py3status/__init__.py')
| -rwxr-xr-x | py3status/__init__.py | 276 |
1 files changed, 176 insertions, 100 deletions
diff --git a/py3status/__init__.py b/py3status/__init__.py index 25b994b..0f107fb 100755 --- a/py3status/__init__.py +++ b/py3status/__init__.py @@ -7,7 +7,7 @@ import sys from contextlib import contextmanager from copy import deepcopy -from datetime import datetime +from datetime import datetime, timedelta from json import dumps, loads from signal import signal from signal import SIGTERM, SIGUSR1 @@ -262,14 +262,6 @@ class I3status(Thread): on_c[section_name] = on_c.get(section_name, {}) on_c[section_name][button] = value - # store time and tztime formats - if ( - section_name.split(' ')[0] in ['time', 'tztime'] - and key == 'format' - ): - key_name = section_name.split(' ')[0] + '_format' - setattr(self, key_name, value) - if line.endswith('}'): in_section = False section_name = '' @@ -290,38 +282,114 @@ class I3status(Thread): return config - def adjust_time(self, delta): + def set_responses(self, json_list): + """ + Set the given i3status responses on their respective configuration. """ - Adjust the 'time' object so that it's updated at interval seconds + for index, item in enumerate(self.json_list): + conf_name = self.config['i3s_modules'][index] + self.config[conf_name]['response'] = item + + def set_time_modules(self): + """ + This method is executed only once after the first i3status output. + + We parse all the i3status time and tztime modules and generate + a datetime for each of them while preserving (or defaulting) their + configured time format. + + We also calculate a timedelta for each of them representing their + timezone offset. This is this delta that we'll be using from now on as + any future time or tztime update from i3status will be overwritten + thanks to our pre-parsed date here. """ - json_list = deepcopy(self.json_list) default_time_format = '%Y-%m-%d %H:%M:%S' - try: - for item in json_list: - if item.get('name') in ['time', 'tztime']: - key_name = item.get('name') + '_format' - time_format = getattr(self, key_name, default_time_format) - i3status_time = item['full_text'].encode('UTF-8', 'replace') - try: - # python3 compatibility code - i3status_time = i3status_time.decode() - except: - pass + default_tztime_format = '%Y-%m-%d %H:%M:%S %Z' + utcnow = self.last_output_ts + # + for index, item in enumerate(self.json_list): + if item.get('name') in ['time', 'tztime']: + conf_name = self.config['i3s_modules'][index] + time_name = item.get('name') + + # time and tztime have different defaults + if time_name == 'time': + time_format = self.config.get( + conf_name, + {} + ).get('format', default_time_format) + else: + time_format = self.config.get( + conf_name, + {} + ).get('format', default_tztime_format) + + # parse i3status date + i3s_time = item['full_text'].encode('UTF-8', 'replace') + try: + # python3 compatibility code + i3s_time = i3s_time.decode() + except: + pass + + try: # add mendatory items in i3status time format wrt issue #18 time_fmt = time_format for fmt in ['%Y', '%m', '%d']: if not fmt in time_format: - time_fmt = '{} {}'.format(time_format, fmt) - i3status_time = '{} {}'.format( - i3status_time, datetime.now().strftime(fmt) + time_fmt = '{} {}'.format(time_fmt, fmt) + i3s_time = '{} {}'.format( + i3s_time, datetime.now().strftime(fmt) ) - date = datetime.strptime(i3status_time, time_fmt) - date += delta - item['full_text'] = date.strftime(time_format) - item['transformed'] = True - except Exception: - err = sys.exc_info()[1] - syslog(LOG_ERR, "i3status adjust_time failed ({})".format(err)) + + # get a datetime from the parsed string date + date = datetime.strptime(i3s_time, time_fmt) + + # i3status output delay adjustment + date += timedelta(seconds=1) + except Exception: + err = sys.exc_info()[1] + syslog( + LOG_ERR, + 'i3status set_time_modules {} failed ({})'.format( + conf_name, + err + ) + ) + date = datetime.now() + finally: + self.config[conf_name]['date'] = date + self.config[conf_name]['delta'] = date - utcnow + self.config[conf_name]['time_format'] = time_format + + def tick_time_modules(self, json_list, force): + """ + Adjust the 'time' and 'tztime' objects from the given json_list so that + they are updated only at py3status interval seconds. + + This method is used to overwrite any i3status time or tztime output + with respect to their parsed and timezone offset detected on start. + """ + utcnow = datetime.utcnow() + # + for index, item in enumerate(json_list): + if item.get('name') in ['time', 'tztime']: + conf_name = self.config['i3s_modules'][index] + time_module = self.config[conf_name] + if force: + date = utcnow + time_module['delta'] + time_module['date'] = date + else: + date = time_module['date'] + time_format = self.config[conf_name].get('time_format') + + # set the full_text date on the json_list to be returned + item['full_text'] = date.strftime(time_format) + json_list[index] = item + + # reset the full_text date on the config object for next + # iteration to be consistent with this one + time_module['response']['full_text'] = item['full_text'] return json_list def update_json_list(self): @@ -347,9 +415,7 @@ class I3status(Thread): for method in py3_modules[module].methods.values(): ordered.append(method['last_output']) else: - for m, j in zip(self.config['i3s_modules'], json_list): - if m == module: - ordered.append(j) + ordered.append(self.config[module]['response']) return ordered @staticmethod @@ -426,6 +492,10 @@ class I3status(Thread): self.last_output_ts = datetime.utcnow() self.last_prefix = ',' self.update_json_list() + self.set_responses(json_list) + # on first i3status output, we parse + # the time and tztime modules + self.set_time_modules() print_line(line) elif not line.startswith(','): if 'version' in line: @@ -439,6 +509,8 @@ class I3status(Thread): self.last_output = json_list self.last_output_ts = datetime.utcnow() self.last_prefix = prefix + self.update_json_list() + self.set_responses(json_list) else: err = self.poller_err.readline(timeout) code = i3status_pipe.poll() @@ -1412,83 +1484,87 @@ class Py3statusWrapper(): signal(SIGUSR1, self.sig_handler) signal(SIGTERM, self.terminate) - # initialize an empty json list output + # initialize usage variables + delta = 0 + last_delta = -1 previous_json_list = [] # main loop while True: - try: - # check i3status thread - if not self.i3status_thread.is_alive(): - err = self.i3status_thread.error - if not err: - err = 'i3status died horribly' - self.i3_nagbar(err) - break + # check i3status thread + if not self.i3status_thread.is_alive(): + err = self.i3status_thread.error + if not err: + err = 'i3status died horribly' + self.i3_nagbar(err) + break - # check events thread - if not self.events_thread.is_alive(): - # don't spam the user with i3-nagbar warnings - if not hasattr(self.events_thread, 'i3_nagbar'): - self.events_thread.i3_nagbar = True - err = 'events thread died, click events are disabled' - self.i3_nagbar(err, level='warning') + # check events thread + if not self.events_thread.is_alive(): + # don't spam the user with i3-nagbar warnings + if not hasattr(self.events_thread, 'i3_nagbar'): + self.events_thread.i3_nagbar = True + err = 'events thread died, click events are disabled' + self.i3_nagbar(err, level='warning') - # get output from i3status - prefix = self.i3status_thread.last_prefix - json_list = self.i3status_thread.json_list + # check that every module thread is alive + for module in self.modules.values(): + if not module.is_alive(): + # don't spam the user with i3-nagbar warnings + if not hasattr(module, 'i3_nagbar'): + module.i3_nagbar = True + msg = 'output frozen for dead module(s) {}'.format( + ','.join(module.methods.keys()) + ) + self.i3_nagbar(msg, level='warning') - # transform output from i3status - delta = datetime.utcnow() - self.i3status_thread.json_list_ts - if delta.seconds > 0: - json_list = self.i3status_thread.adjust_time(delta) + # get output from i3status + prefix = self.i3status_thread.last_prefix + json_list = deepcopy(self.i3status_thread.json_list) - # check that every module thread is alive - for module in self.modules.values(): - if not module.is_alive(): - # don't spam the user with i3-nagbar warnings - if not hasattr(module, 'i3_nagbar'): - module.i3_nagbar = True - msg = 'output frozen for dead module(s) {}'.format( - ','.join(module.methods.keys()) - ) - self.i3_nagbar(msg, level='warning') + # transform time and tztime outputs from i3status + # every configured interval seconds + if ( + int(delta) % self.config['interval'] == 0 + and int(last_delta) != int(delta) + ): + delta = 0 + last_delta = 0 + json_list = self.i3status_thread.tick_time_modules( + json_list, + force=True + ) + else: + json_list = self.i3status_thread.tick_time_modules( + json_list, + force=False + ) - # construct the global output, modules first - if self.modules: - if self.py3_modules: - # new style i3status configured ordering - json_list = self.i3status_thread.get_modules_output( - json_list, - self.modules - ) - else: - # old style ordering - json_list = self.get_modules_output(json_list) + # construct the global output + if self.modules: + if self.py3_modules: + # new style i3status configured ordering + json_list = self.i3status_thread.get_modules_output( + json_list, + self.modules + ) + else: + # old style ordering + json_list = self.get_modules_output(json_list) - # dump the line to stdout on change - if json_list != previous_json_list: - print_line('{}{}'.format(prefix, dumps(json_list))) + # dump the line to stdout only on change + if json_list != previous_json_list: + print_line('{}{}'.format(prefix, dumps(json_list))) - # update i3status output - self.i3status_thread.update_json_list() + # remember the last json list output + previous_json_list = deepcopy(json_list) - # remember the last json list output - previous_json_list = json_list + # reset i3status json_list and json_list_ts + self.i3status_thread.update_json_list() - # sleep a bit before doing this again to avoid killing the CPU - sleep(0.1) - except UserWarning: - # SIGUSR1 was received, we also force i3status to refresh by - # sending it a SIGUSR1 as well then we refresh the bar asap - err = sys.exc_info()[1] - syslog(LOG_INFO, str(err)) - try: - call(["killall", "-s", "USR1", "i3status"]) - self.clear_modules_cache() - except Exception: - err = sys.exc_info()[1] - self.i3_nagbar('SIGUSR1 ({})'.format(err), level='warning') + # sleep a bit before doing this again to avoid killing the CPU + delta += 0.1 + sleep(0.1) def main(): |
