diff options
| -rw-r--r-- | modules/whoami.py | 26 | ||||
| -rwxr-xr-x | py3status/__init__.py | 548 | ||||
| -rw-r--r-- | py3status/modules/__init__.py (renamed from modules/__init__.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/battery-level.py (renamed from modules/battery-level.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/clementine.py (renamed from modules/clementine.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/dpms.py (renamed from modules/dpms.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/empty_class.py (renamed from modules/empty_class.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/glpi.py (renamed from modules/glpi.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/i3bar_click_events.py (renamed from modules/i3bar_click_events.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/imap.py (renamed from modules/imap.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/keyboard-layout.py (renamed from modules/keyboard-layout.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/mpd_status.py (renamed from modules/mpd_status.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/net_rate.py (renamed from modules/net_rate.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/netdata.py (renamed from modules/netdata.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/ns_checker.py (renamed from modules/ns_checker.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/pingdom.py (renamed from modules/pingdom.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/pomodoro.py (renamed from modules/pomodoro.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/scratchpad-counter.py (renamed from modules/scratchpad-counter.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/sysdata.py (renamed from modules/sysdata.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/vnstat.py (renamed from modules/vnstat.py) | 0 | ||||
| -rw-r--r-- | py3status/modules/weather_yahoo.py (renamed from modules/weather_yahoo.py) | 57 | ||||
| -rw-r--r-- | py3status/modules/whoami.py | 29 | ||||
| -rw-r--r-- | py3status/modules/window-title.py (renamed from modules/window-title.py) | 26 |
23 files changed, 491 insertions, 195 deletions
diff --git a/modules/whoami.py b/modules/whoami.py deleted file mode 100644 index f930991..0000000 --- a/modules/whoami.py +++ /dev/null @@ -1,26 +0,0 @@ -from getpass import getuser -from time import time - - -class Py3status: - """ - Simply output the currently logged in user in i3bar. - - Inspired by i3 FAQ: - https://faq.i3wm.org/question/1618/add-user-name-to-status-bar/ - """ - def whoami(self, i3status_output_json, i3status_config): - """ - We use the getpass module to get the current user. - """ - # the current user doesnt change so much, cache it good - CACHE_TIMEOUT = 600 - - # here you can change the format of the output - # default is just to show the username - username = '{}'.format(getuser()) - - # set, cache and return the output - response = {'full_text': username, 'name': 'whoami'} - response['cached_until'] = time() + CACHE_TIMEOUT - return (0, response) diff --git a/py3status/__init__.py b/py3status/__init__.py index 4182b7c..49bd97e 100755 --- a/py3status/__init__.py +++ b/py3status/__init__.py @@ -14,6 +14,7 @@ from signal import SIGUSR1 from subprocess import Popen from subprocess import PIPE from subprocess import call +from tempfile import NamedTemporaryFile from threading import Event, Thread from time import sleep, time from syslog import syslog, LOG_ERR, LOG_INFO, LOG_WARNING @@ -81,69 +82,147 @@ class I3status(Thread): Our output will be read asynchronously from 'last_output'. """ Thread.__init__(self) - self.i3status_config_path = i3status_config_path - self.standalone = standalone - self.config = self.i3status_config_reader() self.error = None + self.i3status_module_names = [ + 'battery', + 'cpu_temperature', + 'cpu_usage', + 'ddate', + 'disk', + 'ethernet', + 'ipv6', + 'load', + 'path_exists', + 'run_watch', + 'time', + 'tztime', + 'volume', + 'wireless' + ] + self.json_list = None + self.json_list_ts = None self.last_output = None self.last_output_ts = None self.last_prefix = None self.lock = lock - self.json_list = None - self.json_list_ts = None + self.standalone = standalone + self.time_format = '%Y-%m-%d %H:%M:%S' + # + self.config = self.i3status_config_reader(i3status_config_path) - def i3status_config_reader(self): + def valid_config_param(self, param_name): + """ + Check if a given section name is a valid parameter for i3status. + """ + valid_config_params = self.i3status_module_names + ['general', 'order'] + return param_name.split(' ')[0] in valid_config_params + + def i3status_config_reader(self, i3status_config_path): """ Parse i3status.conf so we can adapt our code to the i3status config. """ - in_time = False - in_general = False config = { - 'colors': False, - 'color_good': '#00FF00', - 'color_bad': '#FF0000', - 'color_degraded': '#FFFF00', - 'color_separator': '#333333', - 'interval': 5, - 'output_format': None, - 'time_format': '%Y-%m-%d %H:%M:%S' + 'general': { + 'color_bad': '#FF0000', + 'color_degraded': '#FFFF00', + 'color_good': '#00FF00', + 'color_separator': '#333333', + 'colors': False, + 'interval': 5, + 'output_format': 'i3bar' + }, + 'i3s_modules': [], + 'on_click': {}, + 'order': [], + 'py3_modules': [] } # some ugly parsing - if os.path.isfile(self.i3status_config_path): - for line in open(self.i3status_config_path, 'r'): - line = line.strip(' \t\n\r') - if line.startswith('general'): - in_general = True - elif line.startswith('time') or line.startswith('tztime'): - in_time = True - elif line.startswith('}'): - in_general = False - in_time = False - if in_general and '=' in line: - key = line.split('=')[0].strip() - value = line.split('=')[1].strip() - if key in config: - if value in ['true', 'false']: - value = 'True' if value == 'true' else 'False' + in_section = False + section_name = '' + + for line in open(i3status_config_path, 'r'): + line = line.strip(' \t\n\r') + + if not line or line.startswith('#'): + continue + + if line.startswith('order'): + in_section = True + section_name = 'order' + + if not in_section: + section_name = line.split('{')[0].strip() + if section_name not in config: + config[section_name] = {} + + if '{' in line: + in_section = True + + if section_name and '=' in line: + line = line.split('}')[0].strip() + + key = line.split('=')[0].strip() + value = line.split('=')[1].strip() + try: + e_value = eval(value) + if isinstance(e_value, str) or isinstance(e_value, int): + value = e_value + else: + raise ValueError() + except NameError: + pass + except ValueError: + pass + + if section_name == 'order': + config[section_name].append(value) + line = '}' + + # detect internal modules to be loaded dynamically + if not self.valid_config_param(value): + config['py3_modules'].append(value) + else: + config['i3s_modules'].append(value) + else: + if not key.startswith('on_click'): + config[section_name][key] = value + else: + # on_click special parameters try: - config[key] = eval(value) - except NameError: - config[key] = value - if in_time and '=' in line: - key = line.split('=')[0].strip() - value = line.split('=')[1].strip() - if 'time_' + key in config: - config['time_' + key] = eval(value) + button = int(key.split()[1]) + if button not in range(1, 6): + raise ValueError( + 'should be 1, 2, 3, 4 or 5' + ) + except IndexError as e: + raise IndexError( + 'missing "button id" for "on_click" ' + 'parameter in section {}'.format(section_name) + ) + except ValueError as e: + raise ValueError( + 'invalid "button id" ' + 'for "on_click" parameter ' + 'in section {} ({})'.format(section_name, e) + ) + on_c = config['on_click'] + on_c[section_name] = on_c.get(section_name, {}) + on_c[section_name][button] = value - # force output format on standalone mode - if self.standalone: - config['output_format'] = 'i3bar' + # override time format + if section_name in ['time', 'tztime'] and key == 'format': + self.time_format = value - # py3status uses only the i3bar protocol - assert config['output_format'] == 'i3bar', \ - 'i3status output_format should be set to "i3bar" on {}'.format( - self.i3status_config_path + if '}' in line: + in_section = False + section_name = '' + + # py3status only uses the i3bar protocol because it needs JSON output + if config['general']['output_format'] != 'i3bar': + raise RuntimeError( + 'i3status output_format should ' + 'be set to "i3bar" on {}'.format(i3status_config_path) ) return config @@ -154,7 +233,7 @@ class I3status(Thread): """ json_list = deepcopy(self.json_list) try: - time_format = self.config['time_format'] + time_format = self.time_format for item in json_list: if 'name' in item and item['name'] in ['time', 'tztime']: i3status_time = item['full_text'].encode('UTF-8', 'replace') @@ -172,9 +251,7 @@ class I3status(Thread): ) date = datetime.strptime(i3status_time, time_format) date += delta - item['full_text'] = date.strftime( - self.config['time_format'] - ) + item['full_text'] = date.strftime(self.time_format) item['transformed'] = True except Exception: err = sys.exc_info()[1] @@ -191,60 +268,106 @@ class I3status(Thread): self.json_list = deepcopy(self.last_output) self.json_list_ts = deepcopy(self.last_output_ts) + def get_modules_output(self, json_list, py3_modules): + """ + Return the final json list to be displayed on the i3bar by taking + into account every py3status configured module and i3status'. + Simply put, this method honors the initial 'order' configured by + the user in his i3status.conf. + """ + ordered = [] + for module in self.config['order']: + if module in py3_modules: + 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) + return ordered + + def write_tmp_i3status_config(self, tmpfile): + """ + Given a temporary file descriptor, write a valid i3status config file + based on the parsed one from 'i3status_config_path'. + """ + for section_name, conf in self.config.items(): + if section_name in ['i3s_modules', 'py3_modules']: + continue + elif section_name == 'order': + for module_name in conf: + if self.valid_config_param(module_name): + tmpfile.write('order += "%s"\n' % module_name) + tmpfile.write('\n') + elif self.valid_config_param(section_name): + tmpfile.write('%s {\n' % section_name) + for key, value in conf.items(): + tmpfile.write(' %s = "%s"\n' % (key, value)) + tmpfile.write('}\n\n') + tmpfile.flush() + def run(self): """ - Spawn i3status and poll its output. + Spawn i3status using a self generated config file and poll its output. """ - i3status_pipe = Popen( - ['i3status', '-c', self.i3status_config_path], - stdout=PIPE, - stderr=PIPE, - ) - self.poller_inp = IOPoller(i3status_pipe.stdout) - self.poller_err = IOPoller(i3status_pipe.stderr) + with NamedTemporaryFile(prefix='py3status_') as tmpfile: + self.write_tmp_i3status_config(tmpfile) + syslog( + LOG_INFO, + 'i3status spawned using config file {}'.format(tmpfile.name) + ) - try: - # at first, poll very quickly to avoid delay in first i3bar display - timeout = 0.001 + i3status_pipe = Popen( + ['i3status', '-c', tmpfile.name], + stdout=PIPE, + stderr=PIPE, + ) + self.poller_inp = IOPoller(i3status_pipe.stdout) + self.poller_err = IOPoller(i3status_pipe.stderr) - # loop on i3status output - while self.lock.is_set(): - line = self.poller_inp.readline(timeout) - if line: - if line.startswith('[{'): - with jsonify(line) as (prefix, json_list): - self.last_output = json_list - self.last_output_ts = datetime.utcnow() - self.last_prefix = ',' - self.update_json_list() - print_line(line) - elif not line.startswith(','): - if 'version' in line: - header = loads(line) - header.update({'click_events': True}) - line = dumps(header) - print_line(line) - else: - timeout = 0.5 - with jsonify(line) as (prefix, json_list): - self.last_output = json_list - self.last_output_ts = datetime.utcnow() - self.last_prefix = prefix - else: - err = self.poller_err.readline(timeout) - code = i3status_pipe.poll() - if code is not None: - if err: - msg = 'i3status died and said: {}'.format(err) + try: + # at first, poll very quickly + # to avoid delay in first i3bar display + timeout = 0.001 + + # loop on i3status output + while self.lock.is_set(): + line = self.poller_inp.readline(timeout) + if line: + if line.startswith('[{'): + with jsonify(line) as (prefix, json_list): + self.last_output = json_list + self.last_output_ts = datetime.utcnow() + self.last_prefix = ',' + self.update_json_list() + print_line(line) + elif not line.startswith(','): + if 'version' in line: + header = loads(line) + header.update({'click_events': True}) + line = dumps(header) + print_line(line) else: - msg = 'i3status died with code {}'.format(code) - raise IOError(msg) + timeout = 0.5 + with jsonify(line) as (prefix, json_list): + self.last_output = json_list + self.last_output_ts = datetime.utcnow() + self.last_prefix = prefix else: - # poll is CPU intensive, breath a bit - sleep(timeout) - except IOError: - err = sys.exc_info()[1] - self.error = err + err = self.poller_err.readline(timeout) + code = i3status_pipe.poll() + if code is not None: + if err: + msg = 'i3status died and said: {}'.format(err) + else: + msg = 'i3status died with code {}'.format(code) + raise IOError(msg) + else: + # poll is CPU intensive, breath a bit + sleep(timeout) + except IOError: + err = sys.exc_info()[1] + self.error = err def mock(self): """ @@ -273,7 +396,7 @@ class Events(Thread): """ This class is responsible for dispatching event JSONs sent by the i3bar. """ - def __init__(self, lock, config, modules): + def __init__(self, lock, config, modules, on_click): """ We need to poll stdin to receive i3bar messages. """ @@ -282,6 +405,7 @@ class Events(Thread): self.lock = lock self.modules = modules self.poller_inp = IOPoller(sys.stdin) + self.on_click = on_click def dispatch(self, module, obj, event): """ @@ -306,7 +430,7 @@ class Events(Thread): it will dispatch i3status click events to this module so you can catch them and trigger any function call based on the event. """ - for module in self.modules: + for module in self.modules.values(): if not module.click_events: continue if module.module_name == 'i3bar_click_events.py': @@ -314,6 +438,57 @@ class Events(Thread): else: return False + def refresh(self, module_name): + """ + Force a cache expiration for all the methods of the given module. + """ + module = self.modules.get(module_name) + if module is not None: + if self.config['debug']: + syslog(LOG_INFO, 'refresh module {}'.format(module_name)) + for obj in module.methods.values(): + obj['cached_until'] = time() + + @staticmethod + def refresh_all(module_name): + """ + Force a full refresh of py3status and i3status modules by sending + a SIGUSR1 signal to py3status. + """ + Popen(['killall', '-USR1', 'py3status']) + + def on_click_dispatcher(self, module_name, command): + """ + Dispatch on_click config parameters to either: + - Our own methods for special py3status commands (listed below) + - The i3-msg program which is part of i3wm + """ + py3_commands = ['refresh', 'refresh_all'] + if command is None: + return + elif command in py3_commands: + # this is a py3status command handled by this class + method = getattr(self, command) + method(module_name) + else: + # this is a i3 message + self.i3_msg(module_name, command) + + @staticmethod + def i3_msg(module_name, command): + """ + Execute the given i3 message and log its output. + """ + i3_msg_pipe = Popen(['i3-msg', command], stdout=PIPE) + syslog( + LOG_INFO, + 'i3-msg module={} command="{}" stdout={}'.format( + module_name, + command, + i3_msg_pipe.stdout.read() + ) + ) + def run(self): """ Wait for an i3bar JSON event, then find the right module to dispatch @@ -336,13 +511,28 @@ class Events(Thread): if self.config['debug']: syslog(LOG_INFO, 'received event {}'.format(event)) - # setup default action on button 2 press + # usage variables + button = event.get('button', 0) default_event = False dispatched = False - if 'button' in event and event['button'] == 2: + instance = event.get('instance', '') + name = event.get('name', '') + + # guess the module config name + module_name = '{} {}'.format(name, instance).strip() + + # execute any configured i3-msg command + if self.on_click.get(module_name, {}).get(button): + self.on_click_dispatcher( + module_name, + self.on_click[module_name].get(button) + ) + dispatched = True + # otherwise setup default action on button 2 press + elif button == 2: default_event = True - for module in self.modules: + for module in self.modules.values(): # skip modules not supporting click_events # unless we have a default_event set if not module.click_events and not default_event: @@ -350,9 +540,9 @@ class Events(Thread): # check for the method name/instance for obj in module.methods.values(): - if event['name'] == obj['name']: - if 'instance' in event: - if event['instance'] == obj['instance']: + if name == obj['name']: + if instance: + if instance == obj['instance']: self.dispatch(module, obj, event) dispatched = True break @@ -413,6 +603,20 @@ class Module(Thread): inst = py_mod.Py3status() return (mod_name, inst) + @staticmethod + def load_from_namespace(module_name): + """ + Load a py3status bundled module. + """ + inst = None + name = 'py3status.modules.{}'.format(module_name.split(' ')[0]) + py_mod = __import__(name) + components = name.split('.') + for comp in components[1:]: + py_mod = getattr(py_mod, comp) + inst = py_mod.Py3status() + return (module_name, inst) + def clear_cache(self): """ Reset the cache for all methods of this module. @@ -431,9 +635,20 @@ class Module(Thread): - 'on_click' methods as they'll be called upon a click_event - 'kill' methods as they'll be called upon this thread's exit """ - module, class_inst = self.load_from_file(include_path + f_name) + if include_path is not None: + module, class_inst = self.load_from_file(include_path + f_name) + else: + module, class_inst = self.load_from_namespace(f_name) + if module and class_inst: self.module_class = class_inst + + # apply module configuration from i3status config + mod_config = self.i3status_thread.config.get(self.module_name, {}) + for config, value in mod_config.items(): + setattr(self.module_class, config, value) + + # get the available methods for execution for method in sorted(dir(class_inst)): if method.startswith('_'): continue @@ -480,7 +695,7 @@ class Module(Thread): click_method = getattr(self.module_class, 'on_click') click_method( self.i3status_thread.json_list, - self.i3status_thread.config, + self.i3status_thread.config['general'], event ) except Exception: @@ -511,18 +726,31 @@ class Module(Thread): try: # execute method and get its output method = getattr(self.module_class, meth) - position, result = method( + response = method( self.i3status_thread.json_list, - self.i3status_thread.config + self.i3status_thread.config['general'] ) - # validate the result - assert isinstance(result, dict), "should return a dict" - assert 'full_text' in result, "missing 'full_text' key" - assert 'name' in result, "missing 'name' key" + if isinstance(response, dict): + # this is a shiny new module giving a dict response + position, result = None, response + result['name'] = self.module_name.split(' ')[0] + result['instance'] = ''.join( + self.module_name.split(' ')[1:] + ) + else: + # this is an old school module reporting its position + position, result = response + if not isinstance(position, int): + raise TypeError('position is not an int') + if not isinstance(result, dict): + raise TypeError('response should be a dict') + if 'name' not in result: + raise KeyError('missing "name" key in response') - # validate the position - assert isinstance(position, int), "position is not an int" + # validate the response + if not 'full_text' in result: + raise KeyError('missing "full_text" key in response') # initialize method object if my_method['name'] is None: @@ -568,7 +796,7 @@ class Module(Thread): kill_method = getattr(self.module_class, 'kill') kill_method( self.i3status_thread.json_list, - self.i3status_thread.config + self.i3status_thread.config['general'] ) except Exception: # this would be stupid to die on exit @@ -583,8 +811,9 @@ class Py3statusWrapper(): """ Useful variables we'll need. """ - self.modules = [] + self.modules = {} self.lock = Event() + self.py3_modules = [] def get_config(self): """ @@ -666,14 +895,27 @@ class Py3statusWrapper(): def list_modules(self): """ - Search import directories and files through given include paths. + Search import directories and files through given include paths with + respect to i3status.conf configured py3status modules. + + User provided modules take precedence over py3status generic modules + but if none has been configured then we'll load every file present + as this is the legacy behavior. + This method is a generator and loves to yield. """ for include_path in sorted(self.config['include_paths']): include_path = os.path.abspath(include_path) + '/' - if os.path.isdir(include_path): - for f_name in sorted(os.listdir(include_path)): - if f_name.endswith('.py'): + if not os.path.isdir(include_path): + continue + + for f_name in sorted(os.listdir(include_path)): + if f_name.endswith('.py'): + if self.py3_modules: + mod_name = f_name.rstrip('.py') + if mod_name in self.py3_modules: + yield (include_path, f_name) + else: yield (include_path, f_name) def setup(self): @@ -718,7 +960,12 @@ class Py3statusWrapper(): ) # setup input events thread - self.events_thread = Events(self.lock, self.config, self.modules) + self.events_thread = Events( + self.lock, + self.config, + self.modules, + self.i3status_thread.config['on_click'] + ) self.events_thread.start() if self.config['debug']: syslog(LOG_INFO, 'events thread started') @@ -728,8 +975,13 @@ class Py3statusWrapper(): sys.stdout = open('/dev/null', 'w') sys.stderr = open('/dev/null', 'w') - # load and spawn modules threads + # get the list of py3status configured modules + self.py3_modules = self.i3status_thread.config['py3_modules'] + + # load and spawn external modules threads + # based on inclusion folder for include_path, f_name in self.list_modules(): + module_name = f_name.rstrip('.py') try: my_m = Module( self.lock, @@ -741,7 +993,7 @@ class Py3statusWrapper(): # only start and handle modules with available methods if my_m.methods: my_m.start() - self.modules.append(my_m) + self.modules[module_name] = my_m elif self.config['debug']: syslog( LOG_INFO, @@ -752,6 +1004,33 @@ class Py3statusWrapper(): msg = 'loading {} failed ({})'.format(f_name, err) self.i3_nagbar(msg, level='warning') + # load and spawn i3status.conf configured modules threads + for module_name in self.py3_modules: + # ignore if the user already provided this module + if module_name in self.modules: + continue + try: + my_m = Module( + self.lock, + self.config, + None, + module_name, + self.i3status_thread + ) + # only start and handle modules with available methods + if my_m.methods: + my_m.start() + self.modules[module_name] = my_m + elif self.config['debug']: + syslog( + LOG_INFO, + 'ignoring {} (no methods found)'.format(module_name) + ) + except Exception: + err = sys.exc_info()[1] + msg = 'loading {} failed ({})'.format(module_name, err) + self.i3_nagbar(msg, level='warning') + def i3_nagbar(self, msg, level='error'): """ Make use of i3-nagbar to display errors and warnings to the user. @@ -791,7 +1070,7 @@ class Py3statusWrapper(): """ For every module, reset the 'cached_until' of all its methods. """ - for module in self.modules: + for module in self.modules.values(): module.clear_cache() def get_modules_output(self, json_list): @@ -804,7 +1083,8 @@ class Py3statusWrapper(): # prepopulate the list so that every usable index exists, thx @Lujeni m_list = [ '' for value in range( - sum([len(x.methods) for x in self.modules]) + len(json_list) + sum([len(x.methods) for x in self.modules.values()]) + + len(json_list) ) ] @@ -817,7 +1097,7 @@ class Py3statusWrapper(): # run through modules/methods output and insert them in reverse order debug_msg = '' - for m in reversed(self.modules): + for m in reversed(self.modules.values()): for meth in m.methods: position = m.methods[meth]['position'] last_output = m.methods[meth]['last_output'] @@ -925,7 +1205,7 @@ class Py3statusWrapper(): json_list = self.i3status_thread.adjust_time(delta) # check that every module thread is alive - for module in self.modules: + 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'): @@ -937,7 +1217,15 @@ class Py3statusWrapper(): # construct the global output, modules first if self.modules: - json_list = self.get_modules_output(json_list) + 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 print_line('{}{}'.format(prefix, dumps(json_list))) diff --git a/modules/__init__.py b/py3status/modules/__init__.py index e69de29..e69de29 100644 --- a/modules/__init__.py +++ b/py3status/modules/__init__.py diff --git a/modules/battery-level.py b/py3status/modules/battery-level.py index 6c30556..6c30556 100644 --- a/modules/battery-level.py +++ b/py3status/modules/battery-level.py diff --git a/modules/clementine.py b/py3status/modules/clementine.py index 61917b7..61917b7 100644 --- a/modules/clementine.py +++ b/py3status/modules/clementine.py diff --git a/modules/dpms.py b/py3status/modules/dpms.py index 783c53c..783c53c 100644 --- a/modules/dpms.py +++ b/py3status/modules/dpms.py diff --git a/modules/empty_class.py b/py3status/modules/empty_class.py index 077269c..077269c 100644 --- a/modules/empty_class.py +++ b/py3status/modules/empty_class.py diff --git a/modules/glpi.py b/py3status/modules/glpi.py index 929749d..929749d 100644 --- a/modules/glpi.py +++ b/py3status/modules/glpi.py diff --git a/modules/i3bar_click_events.py b/py3status/modules/i3bar_click_events.py index 792965c..792965c 100644 --- a/modules/i3bar_click_events.py +++ b/py3status/modules/i3bar_click_events.py diff --git a/modules/imap.py b/py3status/modules/imap.py index 5953ee3..5953ee3 100644 --- a/modules/imap.py +++ b/py3status/modules/imap.py diff --git a/modules/keyboard-layout.py b/py3status/modules/keyboard-layout.py index 1ed3cee..1ed3cee 100644 --- a/modules/keyboard-layout.py +++ b/py3status/modules/keyboard-layout.py diff --git a/modules/mpd_status.py b/py3status/modules/mpd_status.py index 28e99dc..28e99dc 100644 --- a/modules/mpd_status.py +++ b/py3status/modules/mpd_status.py diff --git a/modules/net_rate.py b/py3status/modules/net_rate.py index dff075c..dff075c 100644 --- a/modules/net_rate.py +++ b/py3status/modules/net_rate.py diff --git a/modules/netdata.py b/py3status/modules/netdata.py index 729983b..729983b 100644 --- a/modules/netdata.py +++ b/py3status/modules/netdata.py diff --git a/modules/ns_checker.py b/py3status/modules/ns_checker.py index 4a332b1..4a332b1 100644 --- a/modules/ns_checker.py +++ b/py3status/modules/ns_checker.py diff --git a/modules/pingdom.py b/py3status/modules/pingdom.py index cddf1a9..cddf1a9 100644 --- a/modules/pingdom.py +++ b/py3status/modules/pingdom.py diff --git a/modules/pomodoro.py b/py3status/modules/pomodoro.py index 28b5ea1..28b5ea1 100644 --- a/modules/pomodoro.py +++ b/py3status/modules/pomodoro.py diff --git a/modules/scratchpad-counter.py b/py3status/modules/scratchpad-counter.py index 390745f..390745f 100644 --- a/modules/scratchpad-counter.py +++ b/py3status/modules/scratchpad-counter.py diff --git a/modules/sysdata.py b/py3status/modules/sysdata.py index fbbd157..fbbd157 100644 --- a/modules/sysdata.py +++ b/py3status/modules/sysdata.py diff --git a/modules/vnstat.py b/py3status/modules/vnstat.py index 100138c..100138c 100644 --- a/modules/vnstat.py +++ b/py3status/modules/vnstat.py diff --git a/modules/weather_yahoo.py b/py3status/modules/weather_yahoo.py index 6b91b04..09e1c72 100644 --- a/modules/weather_yahoo.py +++ b/py3status/modules/weather_yahoo.py @@ -1,47 +1,51 @@ # -*- coding: utf-8 -*- +""" +Display current day + 3 days weather forecast as icons on your i3bar +Based on Yahoo! Weather. forecast, thanks guys ! + http://developer.yahoo.com/weather/ + +Find your city code using: + http://answers.yahoo.com/question/index?qid=20091216132708AAf7o0g + +The city_code in this example is for Paris, France => FRXX0076 +""" + from time import time import requests class Py3status: - """ - Display current day + 3 days weather forecast as icons on your i3bar - Based on Yahoo! Weather. forecast, thanks guys ! - See: http://developer.yahoo.com/weather/ - """ - def __init__(self): - """ - Basic configuration - Find your city code using: - http://answers.yahoo.com/question/index?qid=20091216132708AAf7o0g - The city_code in this example is for Paris, France - """ - self.cache_timeout = 1800 - self.city_code = 'FRXX0076' - self.request_timeout = 10 + + # available configuration parameters + cache_timeout = 1800 + city_code = 'FRXX0076' + forecast_days = 3 + request_timeout = 10 def _get_forecast(self): """ Ask Yahoo! Weather. for a forecast """ - r = requests.get( - 'http://query.yahooapis.com/v1/public/yql?q=select item from weather.forecast where location="%s"&format=json' % self.city_code, + q = requests.get( + 'http://query.yahooapis.com/v1/public/yql?q=' + + 'select item from weather.forecast ' + + 'where location="%s"&format=json' % self.city_code, timeout=self.request_timeout ) - result = r.json() - status = r.status_code + r = q.json() + status = q.status_code forecasts = [] if status == 200: - forecasts = result['query']['results']['channel']['item']['forecast'] + forecasts = r['query']['results']['channel']['item']['forecast'] # reset today - forecasts[0] = result['query']['results']['channel']['item']['condition'] + forecasts[0] = r['query']['results']['channel']['item']['condition'] else: raise Exception('got status {}'.format(status)) - # return current today + 3 days forecast - return forecasts[:4] + # return current today + forecast_days days forecast + return forecasts[:self.forecast_days + 1] def _get_icon(self, forecast): """ @@ -93,9 +97,8 @@ class Py3status: """ response = { 'cached_until': time() + self.cache_timeout, - 'full_text': '', - 'name': 'weather_yahoo' - } + 'full_text': '' + } forecasts = self._get_forecast() for forecast in forecasts: @@ -103,4 +106,4 @@ class Py3status: response['full_text'] += '{} '.format(icon) response['full_text'] = response['full_text'].strip() - return (0, response) + return response diff --git a/py3status/modules/whoami.py b/py3status/modules/whoami.py new file mode 100644 index 0000000..11ca9bf --- /dev/null +++ b/py3status/modules/whoami.py @@ -0,0 +1,29 @@ +""" +Simply output the currently logged in user in i3bar. + +Inspired by i3 FAQ: + https://faq.i3wm.org/question/1618/add-user-name-to-status-bar/ +""" + +from getpass import getuser +from time import time + + +class Py3status: + + # available configuration parameters + cache_timeout = 1800 + + def whoami(self, i3status_output_json, i3status_config): + """ + We use the getpass module to get the current user. + """ + # here you can change the format of the output + # default is just to show the username + username = '{}'.format(getuser()) + + response = { + 'cached_until': time() + self.cache_timeout, + 'full_text': username + } + return response diff --git a/modules/window-title.py b/py3status/modules/window-title.py index 4f6f062..5096bd3 100644 --- a/modules/window-title.py +++ b/py3status/modules/window-title.py @@ -1,6 +1,3 @@ -import i3 -from time import time - """ Py3status plugin - shows current window title. @@ -17,9 +14,8 @@ I can't fix or workaround that in PLUGIN, problem is in i3-py library. @license Eclipse Public License """ -CACHE_TIMEOUT = 0.5 # maximum time to update indicator -MAX_WIDTH = 120 # if width of title is greater, shrink it and add '...' -POSITION = 0 +import i3 +from time import time def find_focused(tree): @@ -28,7 +24,6 @@ def find_focused(tree): res = find_focused(el) if res: return res - elif type(tree) == dict: if tree['focused']: return tree @@ -37,6 +32,11 @@ def find_focused(tree): class Py3status: + + # available configuration parameters + cache_timeout = 0.5 + max_width = 120 # if width of title is greater, shrink it and add '...' + def __init__(self): self.text = '' @@ -45,14 +45,16 @@ class Py3status: transformed = False if window and 'name' in window and window['name'] != self.text: - self.text = len(window['name']) > MAX_WIDTH and "..." + window['name'][-(MAX_WIDTH-3):] or window['name'] + self.text = ( + len(window['name']) > self.max_width + and "..." + window['name'][-(self.max_width-3):] + or window['name'] + ) transformed = True response = { - 'cached_until': time() + CACHE_TIMEOUT, + 'cached_until': time() + self.cache_timeout, 'full_text': self.text, - 'name': 'window-title', 'transformed': transformed } - - return (POSITION, response) + return response |
