From 14c0528be56485696a8563495daa8352254abb91 Mon Sep 17 00:00:00 2001 From: Matthieu Coudron Date: Fri, 19 Dec 2014 18:15:32 +0100 Subject: [PATCH 1/5] This commit introduces a skeleton that allows personalized callbacks in a backwards compatible way. Settings 'on_lclick','on_rclick', 'on_scrollup','on_scrolldown' are inherited by all modules. These parameters should be a string. Then when a matching action is detected (ie mouseclick, scrolling), the module check if this string corresponds to a: 1/ python callable 2/ module method, In cases 1 and 2, it calls the python function with the module as the first parameter. Otherwise it considers the string is an external command and launches it via run_through_shell --- i3pystatus/clock.py | 6 ++++-- i3pystatus/core/modules.py | 32 ++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/i3pystatus/clock.py b/i3pystatus/clock.py index dc03bee..a47684e 100644 --- a/i3pystatus/clock.py +++ b/i3pystatus/clock.py @@ -28,6 +28,8 @@ class Clock(IntervalModule): color = "#ffffff" interval = 1 current_format_id = 0 + on_scrollup = "next_format" + on_scrolldown = "next_format" def init(self): if self.format is None: @@ -78,8 +80,8 @@ class Clock(IntervalModule): if self.color != "i3Bar": self.output["color"] = self.color - def on_upscroll(self): + def next_format(self): self.current_format_id = (self.current_format_id + 1) % len(self.format) - def on_downscroll(self): + def previous_format(self): self.current_format_id = (self.current_format_id - 1) % len(self.format) diff --git a/i3pystatus/core/modules.py b/i3pystatus/core/modules.py index fc1e569..bf9438a 100644 --- a/i3pystatus/core/modules.py +++ b/i3pystatus/core/modules.py @@ -7,6 +7,22 @@ class Module(SettingsBase): output = None position = 0 + settings = ( + 'on_lclick', "Callback called on left click", + 'on_rclick', "Callback called on right click", + 'on_scrollup', "Callback called on scrolling up", + 'on_scrolldown', "Callback called on scrolling down", + ) + + # this allows for backward compatibility + on_lclick = None + on_rclick = None + on_scrollup = None + on_scrolldown = None + # on_rclick = "on_rightclick" + # on_scrollup = "on_upscroll" + # on_scrolldown = "on_downscroll" + def registered(self, status_handler): """Called when this module is registered with a status handler""" @@ -23,14 +39,22 @@ class Module(SettingsBase): pass def on_click(self, button): + cb = None if button == 1: # Left mouse button - self.on_leftclick() + cb = self.on_lclick or "on_leftclick" elif button == 3: # Right mouse button - self.on_rightclick() + cb = self.on_rclick or "on_rightclick" elif button == 4: # mouse wheel up - self.on_upscroll() + cb = self.on_scrollup or "on_upscroll" elif button == 5: # mouse wheel down - self.on_downscroll() + cb = self.on_scrolldown or "on_downscroll" + + if callable(cb): + return cb(self) + elif hasattr(self, cb): + return getattr(self, cb)() + else: + return run_through_shell(cb) def move(self, position): self.position = position From 98e46ac3d6f324c4967348bd9f27d35124ba0c40 Mon Sep 17 00:00:00 2001 From: Matthieu Coudron Date: Fri, 19 Dec 2014 22:02:46 +0100 Subject: [PATCH 2/5] Updated alsa & clock modules with new mechanism --- i3pystatus/alsa.py | 18 ++++++++++-------- i3pystatus/core/modules.py | 7 +++++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/i3pystatus/alsa.py b/i3pystatus/alsa.py index 1c37753..021d1f2 100644 --- a/i3pystatus/alsa.py +++ b/i3pystatus/alsa.py @@ -46,6 +46,11 @@ class ALSA(IntervalModule): alsamixer = None has_mute = True + on_scrollup = "increase_volume" + on_scrolldown = "decrease_volume" + on_lclick = "switch_mute" + on_rclick = on_lclick + def init(self): self.create_mixer() try: @@ -82,18 +87,15 @@ class ALSA(IntervalModule): "color": self.color_muted if muted else self.color, } - def on_leftclick(self): - self.on_rightclick() - - def on_rightclick(self): + def switch_mute(self): if self.has_mute: muted = self.alsamixer.getmute()[self.channel] self.alsamixer.setmute(not muted) - def on_upscroll(self): + def increase_volume(self, delta=None): vol = self.alsamixer.getvolume()[self.channel] - self.alsamixer.setvolume(min(100, vol + self.increment)) + self.alsamixer.setvolume(min(100, vol + (delta if delta else self.increment))) - def on_downscroll(self): + def decrease_volume(self, delta=None): vol = self.alsamixer.getvolume()[self.channel] - self.alsamixer.setvolume(max(0, vol - self.increment)) + self.alsamixer.setvolume(max(0, vol - (delta if delta else self.increment))) diff --git a/i3pystatus/core/modules.py b/i3pystatus/core/modules.py index bf9438a..5c55345 100644 --- a/i3pystatus/core/modules.py +++ b/i3pystatus/core/modules.py @@ -1,14 +1,14 @@ from i3pystatus.core.settings import SettingsBase from i3pystatus.core.threading import Manager from i3pystatus.core.util import convert_position +from i3pystatus.core.command import run_through_shell class Module(SettingsBase): output = None position = 0 - settings = ( - 'on_lclick', "Callback called on left click", + settings = ('on_lclick', "Callback called on left click", 'on_rclick', "Callback called on right click", 'on_scrollup', "Callback called on scrolling up", 'on_scrolldown', "Callback called on scrolling down", @@ -48,6 +48,9 @@ class Module(SettingsBase): cb = self.on_scrollup or "on_upscroll" elif button == 5: # mouse wheel down cb = self.on_scrolldown or "on_downscroll" + else: + self.logger.debug("Button not handled") + return if callable(cb): return cb(self) From d31cc380ef10fe883abc09ef46cee1824a3cccb4 Mon Sep 17 00:00:00 2001 From: Matthieu Coudron Date: Sat, 20 Dec 2014 15:30:41 +0100 Subject: [PATCH 3/5] Follow up of https://github.com/enkore/i3pystatus/pull/149#issuecomment-67712619 . This commit removes and replaces all the old methods 'on_*' by settings with the same name. The old methods were renamed into more explicit names that can be used for the callbacks like "next_song","mute" etc... For instance, you can test with: status.register("clock", format=[ ("Format 0",'Europe/London'), ("%a %-d Format 1",'Europe/Dublin'), "%a %-d %b %X format 2", ("%a %-d %b %X format 3", 'Europe/Paris'), ], on_leftclick= ["urxvtc"] , # launch urxvtc on left click on_rightclick= ["scroll_format", 2] , # update format by steps of 2 log_level=logging.DEBUG, ) This way much code could be removed from other modules, though I did it only for the clock module here. --- i3pystatus/alsa.py | 8 +-- i3pystatus/bitcoin.py | 7 ++- i3pystatus/clock.py | 11 ++-- i3pystatus/cmus.py | 14 +++--- i3pystatus/core/modules.py | 84 +++++++++++++++++++------------ i3pystatus/core/util.py | 2 +- i3pystatus/mail/__init__.py | 7 ++- i3pystatus/modsde.py | 4 +- i3pystatus/mpd.py | 16 +++--- i3pystatus/network.py | 4 +- i3pystatus/now_playing.py | 6 ++- i3pystatus/parcel.py | 3 +- i3pystatus/pianobar.py | 13 +++-- i3pystatus/pomodoro.py | 7 ++- i3pystatus/pulseaudio/__init__.py | 14 +++--- i3pystatus/pyload.py | 3 +- i3pystatus/reddit.py | 7 ++- i3pystatus/spotify.py | 7 ++- i3pystatus/text.py | 12 ----- 19 files changed, 129 insertions(+), 100 deletions(-) diff --git a/i3pystatus/alsa.py b/i3pystatus/alsa.py index 021d1f2..f53815b 100644 --- a/i3pystatus/alsa.py +++ b/i3pystatus/alsa.py @@ -46,10 +46,10 @@ class ALSA(IntervalModule): alsamixer = None has_mute = True - on_scrollup = "increase_volume" - on_scrolldown = "decrease_volume" - on_lclick = "switch_mute" - on_rclick = on_lclick + on_upscroll = "increase_volume" + on_downscroll = "decrease_volume" + on_leftclick = "switch_mute" + on_rightclick = on_leftclick def init(self): self.create_mixer() diff --git a/i3pystatus/bitcoin.py b/i3pystatus/bitcoin.py index 592ac25..d412830 100644 --- a/i3pystatus/bitcoin.py +++ b/i3pystatus/bitcoin.py @@ -59,6 +59,9 @@ class Bitcoin(IntervalModule): "price_down": "▼", } + on_leftclick = "handle_leftclick" + on_rightclick = "handle_rightclick" + _price_prev = 0 def _fetch_price_data(self): @@ -122,8 +125,8 @@ class Bitcoin(IntervalModule): "color": color, } - def on_leftclick(self): + def handle_leftclick(self): user_open(self.leftclick) - def on_rightclick(self): + def handle_rightclick(self): user_open(self.rightclick) diff --git a/i3pystatus/clock.py b/i3pystatus/clock.py index a47684e..ba95678 100644 --- a/i3pystatus/clock.py +++ b/i3pystatus/clock.py @@ -28,8 +28,8 @@ class Clock(IntervalModule): color = "#ffffff" interval = 1 current_format_id = 0 - on_scrollup = "next_format" - on_scrolldown = "next_format" + on_upscroll = ["scroll_format", 1] + on_downscroll = ["scroll_format", -1] def init(self): if self.format is None: @@ -80,8 +80,5 @@ class Clock(IntervalModule): if self.color != "i3Bar": self.output["color"] = self.color - def next_format(self): - self.current_format_id = (self.current_format_id + 1) % len(self.format) - - def previous_format(self): - self.current_format_id = (self.current_format_id - 1) % len(self.format) + def scroll_format(self, step=1): + self.current_format_id = (self.current_format_id + step) % len(self.format) diff --git a/i3pystatus/cmus.py b/i3pystatus/cmus.py index c3b8490..60f13ee 100644 --- a/i3pystatus/cmus.py +++ b/i3pystatus/cmus.py @@ -49,6 +49,11 @@ class Cmus(IntervalModule): "stopped": "◾", } + on_leftclick = "playpause" + on_rightclick = "next_song" + on_upscroll = "next_song" + on_downscroll = "previous_song" + def _cmus_command(self, command): p = subprocess.Popen('cmus-remote --{command}'.format(command=command), shell=True, stdout=subprocess.PIPE, @@ -108,7 +113,7 @@ class Cmus(IntervalModule): "color": self.color } - def on_leftclick(self): + def playpause(self): status = self._query_cmus().get('status', '') if status == 'playing': self._cmus_command('pause') @@ -117,11 +122,8 @@ class Cmus(IntervalModule): if status == 'stopped': self._cmus_command('play') - def on_rightclick(self): + def next_song(self): self._cmus_command("next") - def on_upscroll(self): - self._cmus_command("next") - - def on_downscroll(self): + def previous_song(self): self._cmus_command("prev") diff --git a/i3pystatus/core/modules.py b/i3pystatus/core/modules.py index 5c55345..79367c6 100644 --- a/i3pystatus/core/modules.py +++ b/i3pystatus/core/modules.py @@ -8,20 +8,16 @@ class Module(SettingsBase): output = None position = 0 - settings = ('on_lclick', "Callback called on left click", - 'on_rclick', "Callback called on right click", - 'on_scrollup', "Callback called on scrolling up", - 'on_scrolldown', "Callback called on scrolling down", + settings = ('on_leftclick', "Callback called on left click (string)", + 'on_rightclick', "Callback called on right click (string)", + 'on_upscroll', "Callback called on scrolling up (string)", + 'on_downscroll', "Callback called on scrolling down (string)", ) - # this allows for backward compatibility - on_lclick = None - on_rclick = None - on_scrollup = None - on_scrolldown = None - # on_rclick = "on_rightclick" - # on_scrollup = "on_upscroll" - # on_scrolldown = "on_downscroll" + on_leftclick = None + on_rightclick = None + on_upscroll = None + on_downscroll = None def registered(self, status_handler): """Called when this module is registered with a status handler""" @@ -39,42 +35,68 @@ class Module(SettingsBase): pass def on_click(self, button): + """ + Maps a click event (include mousewheel events) with its associated callback. + It then triggers the callback depending on the nature (ie type) of + the callback variable: + 1. if null callback, do nothing + 2. if it's a python function + 3. if it's a method of the current module + + To setup the callbacks, you can set the settings 'on_leftclick', + 'on_rightclick', 'on_upscroll', 'on_downscroll'. + + For instance, you can test with: + status.register("clock", + format=[ + ("Format 0",'Europe/London'), + ("%a %-d Format 1",'Europe/Dublin'), + "%a %-d %b %X format 2", + ("%a %-d %b %X format 3", 'Europe/Paris'), + ], + on_leftclick= ["urxvtc"] , # launch urxvtc on left click + on_rightclick= ["scroll_format", 2] , # update format by steps of 2 + log_level=logging.DEBUG, + ) + """ + + def split_callback_and_args(cb): + if isinstance(cb, list): + return cb[0], cb[1:] + else: + return cb, [] + cb = None if button == 1: # Left mouse button - cb = self.on_lclick or "on_leftclick" + cb = self.on_leftclick elif button == 3: # Right mouse button - cb = self.on_rclick or "on_rightclick" + cb = self.on_rightclick elif button == 4: # mouse wheel up - cb = self.on_scrollup or "on_upscroll" + cb = self.on_upscroll elif button == 5: # mouse wheel down - cb = self.on_scrolldown or "on_downscroll" + cb = self.on_downscroll else: - self.logger.debug("Button not handled") + self.logger.info("Button '%d' not handled yet." % (button)) return + if not cb: + self.logger.info("no cb attached") + return + else: + cb, args = split_callback_and_args(cb) + self.logger.debug("cb=%s args=%s" % (cb, args)) + if callable(cb): return cb(self) elif hasattr(self, cb): - return getattr(self, cb)() + return getattr(self, cb)(*args) else: - return run_through_shell(cb) + return run_through_shell(cb, *args) def move(self, position): self.position = position return self - def on_leftclick(self): - pass - - def on_rightclick(self): - pass - - def on_upscroll(self): - pass - - def on_downscroll(self): - pass - class IntervalModuleMeta(type): """Add interval setting to `settings` attribute if it does not exist.""" diff --git a/i3pystatus/core/util.py b/i3pystatus/core/util.py index 0f6de1a..c3b093e 100644 --- a/i3pystatus/core/util.py +++ b/i3pystatus/core/util.py @@ -431,7 +431,7 @@ def make_bar(percentage): def user_open(url_or_command): """Open the specified paramater in the web browser if a URL is detected, othewrise pass the paramater to the shell as a subprocess. This function - is inteded to bu used in on_leftclick()/on_rightclick() events. + is inteded to bu used in on_leftclick/on_rightclick callbacks. :param url_or_command: String containing URL or command """ diff --git a/i3pystatus/mail/__init__.py b/i3pystatus/mail/__init__.py index 330494c..ff69b50 100644 --- a/i3pystatus/mail/__init__.py +++ b/i3pystatus/mail/__init__.py @@ -38,6 +38,8 @@ class Mail(IntervalModule): hide_if_null = True email_client = None + on_leftclick = "open_client" + def init(self): for backend in self.backends: pass @@ -69,11 +71,8 @@ class Mail(IntervalModule): "color": color, } - def on_leftclick(self): + def open_client(self): if self.email_client: retcode, _, stderr = run_through_shell(self.email_client) if retcode != 0 and stderr: self.logger.error(stderr) - - def on_rightclick(self): - self.run() diff --git a/i3pystatus/modsde.py b/i3pystatus/modsde.py index b524c7c..ff5f9b0 100644 --- a/i3pystatus/modsde.py +++ b/i3pystatus/modsde.py @@ -35,6 +35,8 @@ class ModsDeChecker(IntervalModule): cj = None logged_in = False + on_leftclick = "open_browser" + def init(self): self.cj = http.cookiejar.CookieJar() self.opener = urllib.request.build_opener( @@ -94,5 +96,5 @@ class ModsDeChecker(IntervalModule): return True return False - def on_leftclick(self): + def open_browser(self): webbrowser.open_new_tab("http://forum.mods.de/bb/") diff --git a/i3pystatus/mpd.py b/i3pystatus/mpd.py index 441c3dd..8946f19 100644 --- a/i3pystatus/mpd.py +++ b/i3pystatus/mpd.py @@ -50,6 +50,10 @@ class MPD(IntervalModule): color = "#FFFFFF" text_len = 25 truncate_fields = ("title", "album", "artist") + on_leftclick = "switch_playpause" + on_rightclick = "next_song" + on_upscroll = on_rightclick + on_downscroll = "previous_song" def _mpd_command(self, sock, command): try: @@ -101,26 +105,20 @@ class MPD(IntervalModule): "color": self.color, } - def on_leftclick(self): + def switch_playpause(self): try: self._mpd_command(self.s, "%s" % ("play" if self._mpd_command(self.s, "status")["state"] in ["pause", "stop"] else "pause")) except Exception as e: pass - def on_rightclick(self): + def next_song(self): try: self._mpd_command(self.s, "next") except Exception as e: pass - def on_upscroll(self): - try: - self._mpd_command(self.s, "next") - except Exception as e: - pass - - def on_downscroll(self): + def previous_song(self): try: self._mpd_command(self.s, "previous") except Exception as e: diff --git a/i3pystatus/network.py b/i3pystatus/network.py index e476b7c..a20cc38 100644 --- a/i3pystatus/network.py +++ b/i3pystatus/network.py @@ -105,6 +105,7 @@ class Network(IntervalModule): color_down = "#FF0000" detached_down = True unknown_up = False + on_leftclick = "nm-connection-editor" def init(self): if self.interface not in netifaces.interfaces() and not self.detached_down: @@ -174,6 +175,3 @@ class Network(IntervalModule): "color": color, "instance": self.interface } - - def on_leftclick(self): - subprocess.Popen(["nm-connection-editor"]) diff --git a/i3pystatus/now_playing.py b/i3pystatus/now_playing.py index 40b1654..fbbccdf 100644 --- a/i3pystatus/now_playing.py +++ b/i3pystatus/now_playing.py @@ -54,6 +54,8 @@ class NowPlaying(IntervalModule): color = "#FFFFFF" old_player = None + on_leftclick = "playpause" + on_rightclick = "next_song" def find_player(self): players = [a for a in dbus.SessionBus().get_object("org.freedesktop.DBus", "/org/freedesktop/DBus").ListNames() if a.startswith("org.mpris.MediaPlayer2.")] @@ -111,8 +113,8 @@ class NowPlaying(IntervalModule): "color": self.color, } - def on_leftclick(self): + def playpause(self): self.get_player().PlayPause() - def on_rightclick(self): + def next_song(self): self.get_player().Next() diff --git a/i3pystatus/parcel.py b/i3pystatus/parcel.py index ea81331..f98e818 100644 --- a/i3pystatus/parcel.py +++ b/i3pystatus/parcel.py @@ -153,6 +153,7 @@ class ParcelTracker(IntervalModule): required = ("instance",) format = "{name}:{progress}" + on_leftclick = "open_browser" @require(internet) def run(self): @@ -166,5 +167,5 @@ class ParcelTracker(IntervalModule): "instance": self.name, } - def on_leftclick(self): + def open_browser(self): webbrowser.open_new_tab(self.instance.get_url()) diff --git a/i3pystatus/pianobar.py b/i3pystatus/pianobar.py index 5314ee9..2f24850 100644 --- a/i3pystatus/pianobar.py +++ b/i3pystatus/pianobar.py @@ -27,6 +27,11 @@ class Pianobar(IntervalModule): required = ("format", "songfile", "ctlfile") color = "#FFFFFF" + on_leftclick = "playpause" + on_rightclick = "next_song" + on_upscroll = "increase_volume" + on_downscroll = "decrease_volume" + def run(self): with open(self.songfile, "r") as f: contents = f.readlines() @@ -39,14 +44,14 @@ class Pianobar(IntervalModule): "color": self.color } - def on_leftclick(self): + def playpause(self): open(self.ctlfile, "w").write("p") - def on_rightclick(self): + def next_song(self): open(self.ctlfile, "w").write("n") - def on_upscroll(self): + def increase_volume(self): open(self.ctlfile, "w").write(")") - def on_downscroll(self): + def decrease_volume(self): open(self.ctlfile, "w").write("(") diff --git a/i3pystatus/pomodoro.py b/i3pystatus/pomodoro.py index 1d4317c..3755612 100644 --- a/i3pystatus/pomodoro.py +++ b/i3pystatus/pomodoro.py @@ -42,6 +42,9 @@ class Pomodoro(IntervalModule): break_duration = 5 * 60 long_break_duration = 15 * 60 + on_rightclick = "stop" + on_leftclick = "start" + def init(self): # state could be either running/break or stopped self.state = 'stopped' @@ -90,12 +93,12 @@ class Pomodoro(IntervalModule): 'color': color } - def on_leftclick(self): + def start(self): self.state = 'running' self.time = datetime.now() + timedelta(seconds=self.pomodoro_duration) self.breaks = 0 - def on_rightclick(self): + def stop(self): self.state = 'stopped' self.time = None diff --git a/i3pystatus/pulseaudio/__init__.py b/i3pystatus/pulseaudio/__init__.py index 669ce72..390caa2 100644 --- a/i3pystatus/pulseaudio/__init__.py +++ b/i3pystatus/pulseaudio/__init__.py @@ -50,6 +50,11 @@ class PulseAudio(Module, ColorRangeModule): bar_type = 'vertical' vertical_bar_width = 2 + on_rightclick = "switch_mute" + on_leftclick = "pavucontrol" + on_upscroll = "increase_volume" + on_downscroll = "decrease_volume" + def init(self): """Creates context, when context is ready context_notify_cb is called""" # Wrap callback methods in appropriate ctypefunc instances so @@ -155,10 +160,7 @@ class PulseAudio(Module, ColorRangeModule): volume_bar=volume_bar), } - def on_leftclick(self): - subprocess.Popen(["pavucontrol"]) - - def on_rightclick(self): + def switch_mute(self): if self.has_amixer: command = "amixer -q -D pulse sset Master " if self.currently_muted: @@ -167,12 +169,12 @@ class PulseAudio(Module, ColorRangeModule): command += 'mute' subprocess.Popen(command.split()) - def on_upscroll(self): + def increase_volume(self): if self.has_amixer: command = "amixer -q -D pulse sset Master %s%%+" % self.step subprocess.Popen(command.split()) - def on_downscroll(self): + def decrease_volume(self): if self.has_amixer: command = "amixer -q -D pulse sset Master %s%%-" % self.step subprocess.Popen(command.split()) diff --git a/i3pystatus/pyload.py b/i3pystatus/pyload.py index 7e58650..f960114 100644 --- a/i3pystatus/pyload.py +++ b/i3pystatus/pyload.py @@ -39,6 +39,7 @@ class pyLoad(IntervalModule): captcha_false = "" download_true = "Downloads enabled" download_false = "Downloads disabled" + on_leftclick = "open_webbrowser" def _rpc_call(self, method, data=None): if not data: @@ -83,5 +84,5 @@ class pyLoad(IntervalModule): "instance": self.address, } - def on_leftclick(self): + def open_webbrowser(self): webbrowser.open_new_tab(self.address) diff --git a/i3pystatus/reddit.py b/i3pystatus/reddit.py index f875de1..b5a0f73 100644 --- a/i3pystatus/reddit.py +++ b/i3pystatus/reddit.py @@ -61,6 +61,9 @@ class Reddit(IntervalModule): "no_mail": "", } + on_leftclick = "open_permalink" + on_click = "open_link" + _permalink = "" _url = "" @@ -133,8 +136,8 @@ class Reddit(IntervalModule): "color": color, } - def on_leftclick(self): + def open_permalink(self): user_open(self._permalink) - def on_rightclick(self): + def open_link(self): user_open(self._url) diff --git a/i3pystatus/spotify.py b/i3pystatus/spotify.py index 0d7d760..8e26435 100644 --- a/i3pystatus/spotify.py +++ b/i3pystatus/spotify.py @@ -23,6 +23,9 @@ class Spotify(Module): ("color", "color of the output"), ) + on_leftclick = "switch_playpause" + on_rightclick = "next_song" + def main_loop(self): """ Mainloop blocks so we thread it.""" self.player = Playerctl.Player() @@ -62,8 +65,8 @@ class Spotify(Module): "color": self.color } - def on_leftclick(self): + def switch_playpause(self): self.player.play_pause() - def on_rightclick(self): + def next_song(self): self.player.next() diff --git a/i3pystatus/text.py b/i3pystatus/text.py index 214337f..44b964b 100644 --- a/i3pystatus/text.py +++ b/i3pystatus/text.py @@ -1,5 +1,3 @@ -import subprocess - from i3pystatus import Module @@ -11,14 +9,10 @@ class Text(Module): settings = ( "text", ("color", "HTML color code #RRGGBB"), - ("cmd_leftclick", "Shell command to execute on left click"), - ("cmd_rightclick", "Shell command to execute on right click"), ) required = ("text",) color = None - cmd_leftclick = "test" - cmd_rightclick = "test" def init(self): self.output = { @@ -26,9 +20,3 @@ class Text(Module): } if self.color: self.output["color"] = self.color - - def on_leftclick(self): - subprocess.call(self.cmd_leftclick, shell=True) - - def on_rightclick(self): - subprocess.call(self.cmd_rightclick, shell=True) From 12546736a6f7dfa745a7594508e59fda955bf57b Mon Sep 17 00:00:00 2001 From: Matthieu Coudron Date: Sat, 20 Dec 2014 15:46:34 +0100 Subject: [PATCH 4/5] Fix in documentation --- i3pystatus/core/modules.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/i3pystatus/core/modules.py b/i3pystatus/core/modules.py index 79367c6..152f754 100644 --- a/i3pystatus/core/modules.py +++ b/i3pystatus/core/modules.py @@ -47,17 +47,19 @@ class Module(SettingsBase): 'on_rightclick', 'on_upscroll', 'on_downscroll'. For instance, you can test with: - status.register("clock", - format=[ - ("Format 0",'Europe/London'), - ("%a %-d Format 1",'Europe/Dublin'), - "%a %-d %b %X format 2", - ("%a %-d %b %X format 3", 'Europe/Paris'), - ], - on_leftclick= ["urxvtc"] , # launch urxvtc on left click - on_rightclick= ["scroll_format", 2] , # update format by steps of 2 - log_level=logging.DEBUG, - ) + :: + + status.register("clock", + format=[ + ("Format 0",'Europe/London'), + ("%a %-d Format 1",'Europe/Dublin'), + "%a %-d %b %X format 2", + ("%a %-d %b %X format 3", 'Europe/Paris'), + ], + on_leftclick= ["urxvtc"] , # launch urxvtc on left click + on_rightclick= ["scroll_format", 2] , # update format by steps of 2 + log_level=logging.DEBUG, + ) """ def split_callback_and_args(cb): From c5933a3b266f3bf9d45a5f051857f0ab8ddb56ad Mon Sep 17 00:00:00 2001 From: Matthieu Coudron Date: Sun, 21 Dec 2014 13:31:40 +0100 Subject: [PATCH 5/5] This commit adds a basic test to check callbacks work. This can also serve as an exemple. --- i3pystatus/core/modules.py | 5 +-- tests/test_callbacks.py | 68 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 tests/test_callbacks.py diff --git a/i3pystatus/core/modules.py b/i3pystatus/core/modules.py index 152f754..c9b66d9 100644 --- a/i3pystatus/core/modules.py +++ b/i3pystatus/core/modules.py @@ -40,8 +40,8 @@ class Module(SettingsBase): It then triggers the callback depending on the nature (ie type) of the callback variable: 1. if null callback, do nothing - 2. if it's a python function - 3. if it's a method of the current module + 2. if it's a python function () + 3. if it's the name of a method of the current module (string) To setup the callbacks, you can set the settings 'on_leftclick', 'on_rightclick', 'on_upscroll', 'on_downscroll'. @@ -58,6 +58,7 @@ class Module(SettingsBase): ], on_leftclick= ["urxvtc"] , # launch urxvtc on left click on_rightclick= ["scroll_format", 2] , # update format by steps of 2 + on_upscroll= [print, "hello world"] , # call python function print log_level=logging.DEBUG, ) """ diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py new file mode 100644 index 0000000..5b2fbd0 --- /dev/null +++ b/tests/test_callbacks.py @@ -0,0 +1,68 @@ +import unittest + + +from i3pystatus.core.modules import Module + + +class CallbacksMetaTest(unittest.TestCase): + + valid_msg_count = "hello world" + notmuch_config = "notmuch-config" + + @staticmethod + def send_clicks(module): + # simulate click events for buttons 1 to 10 + for button in range(1, 10): + module.on_click(button) + + class NoDefaultCounter(Module): + + counter = 0 + + def update_counter(self, step=1): + self.counter = self.counter + step + + class WithDefaultsCounter(NoDefaultCounter): + on_leftclick = "update_counter" + on_rightclick = ["update_counter", 2] + on_upscroll = ["callable_callback", "i3 is", "awesome"] + on_downscroll = ["update_counter", -1] + + def test_count_is_correct(self): + """Checks module works in the intended way, ie increase correctly + a counter""" + counter = self.NoDefaultCounter() + self.assertEqual(counter.counter, 0) + counter.update_counter(1) + self.assertEqual(counter.counter, 1) + counter.update_counter(2) + self.assertEqual(counter.counter, 3) + counter.update_counter(-2) + self.assertEqual(counter.counter, 1) + + CallbacksMetaTest.send_clicks(counter) + + # retcode, out, err = run_through_shell("notmuch --config=%s new" % (self.notmuch_config), enable_shell=True) + + # self.assertEqual(out.strip(), self.valid_output) + def test_callback_set_post_instanciation(self): + + counter = self.NoDefaultCounter() + + # set callbacks + counter.on_leftclick = "update_counter" + counter.on_rightclick = ["update_counter", 2] + counter.on_upscroll = [print, "i3 is", "awesome"] + counter.on_downscroll = ["update_counter", -1] + + self.assertEqual(counter.counter, 0) + counter.on_click(1) # left_click + self.assertEqual(counter.counter, 1) + counter.on_click(2) # middle button + self.assertEqual(counter.counter, 1) + counter.on_click(3) # right click + self.assertEqual(counter.counter, 3) + counter.on_click(4) # upscroll + self.assertEqual(counter.counter, 3) + counter.on_click(5) # downscroll + self.assertEqual(counter.counter, 2)