From a16536ad3cf6a9476e2350229ad3f93ab3d7d34d Mon Sep 17 00:00:00 2001 From: Zero Rust Date: Sun, 17 May 2020 07:52:32 -0400 Subject: [PATCH 01/10] adding prefix icon to arandr --- themes/icons/awesome-fonts.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index 6504ce2..ec1de9a 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -271,5 +271,8 @@ "hddtemp": { "prefix": "" }, "octoprint": { "prefix": " " + }, + "arandr": { + "prefix": " " } } From e339158aed469db5658413d7d9356be114d27468 Mon Sep 17 00:00:00 2001 From: Zero Rust Date: Sun, 17 May 2020 07:56:58 -0400 Subject: [PATCH 02/10] updating all icon sets for arandr icon --- themes/icons/ascii.json | 3 +++ themes/icons/awesome-fonts.json | 2 +- themes/icons/ionicons.json | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/themes/icons/ascii.json b/themes/icons/ascii.json index 01aff79..1913752 100644 --- a/themes/icons/ascii.json +++ b/themes/icons/ascii.json @@ -359,5 +359,8 @@ }, "hddtemp": { "prefix": "hddtemp" + }, + "arandr": { + "prefix": " displays " } } diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index ec1de9a..e660c1d 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -273,6 +273,6 @@ "prefix": " " }, "arandr": { - "prefix": " " + "prefix": "" } } diff --git a/themes/icons/ionicons.json b/themes/icons/ionicons.json index cf7813a..4b90133 100644 --- a/themes/icons/ionicons.json +++ b/themes/icons/ionicons.json @@ -193,6 +193,8 @@ "off": { "prefix": "\uf24f" }, "paused": { "prefix": "\uf210" }, "on": { "prefix": "\uf488" } + }, + "arandr": { + "prefix": "\uf465" } - } From f5d296df8092df5cdb8035522c9040c3f90f8d70 Mon Sep 17 00:00:00 2001 From: Zero Rust Date: Sun, 17 May 2020 08:03:03 -0400 Subject: [PATCH 03/10] adding first version of arandr module to contrib --- bumblebee_status/modules/contrib/arandr.py | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 bumblebee_status/modules/contrib/arandr.py diff --git a/bumblebee_status/modules/contrib/arandr.py b/bumblebee_status/modules/contrib/arandr.py new file mode 100644 index 0000000..7d7960f --- /dev/null +++ b/bumblebee_status/modules/contrib/arandr.py @@ -0,0 +1,28 @@ +# pylint: disable=C0111,R0903 + +"""My TEST + +Requires the following executable: + * arandr + * xrandr +""" + +import core.module +import core.widget +import core.input +import core.decorators + + +class Module(core.module.Module): + @core.decorators.never + def __init__(self, config, theme): + super().__init__(config, theme, core.widget.Widget("")) + + core.input.register( + self, + button=core.input.LEFT_MOUSE, + cmd="arandr", + ) + + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From f5c927229161df2e52ba1ce4f879a7b5d585edb4 Mon Sep 17 00:00:00 2001 From: Zero Rust Date: Fri, 22 May 2020 22:44:38 -0400 Subject: [PATCH 04/10] first go at popup functionality for arandr --- bumblebee_status/modules/contrib/arandr.py | 29 ++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/bumblebee_status/modules/contrib/arandr.py b/bumblebee_status/modules/contrib/arandr.py index 7d7960f..7bbd84c 100644 --- a/bumblebee_status/modules/contrib/arandr.py +++ b/bumblebee_status/modules/contrib/arandr.py @@ -7,22 +7,47 @@ Requires the following executable: * xrandr """ +from functools import partial +import logging + import core.module import core.widget import core.input import core.decorators +import util.cli +from util import popup +from util.cli import execute + + +log = logging.getLogger(__name__) class Module(core.module.Module): @core.decorators.never def __init__(self, config, theme): - super().__init__(config, theme, core.widget.Widget("")) + super().__init__(config, theme, core.widget.Widget('')) + self.manager = self.parameter("manager", "arandr") core.input.register( self, button=core.input.LEFT_MOUSE, - cmd="arandr", + cmd=self.popup, ) + core.input.register(self, button=core.input.RIGHT_MOUSE, + cmd=self.popup) + + def popup(self, widget): + """Create Popup that allows the user to control their displays in one + of three ways: launch arandr, select a pre-set screenlayout, toggle a + display. + """ + log.info("arandr showing popup") + menu = popup.menu() + menu.add_menuitem("arandr", + callback=partial(execute, self.manager) + ) + + menu.show(widget, 0, 0) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From 1b34993fa989ae941213981bf98f887d8488d16e Mon Sep 17 00:00:00 2001 From: Zero Rust Date: Fri, 22 May 2020 23:53:21 -0400 Subject: [PATCH 05/10] adding separator addition to popup --- bumblebee_status/util/popup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bumblebee_status/util/popup.py b/bumblebee_status/util/popup.py index dee67f6..cf69d9a 100644 --- a/bumblebee_status/util/popup.py +++ b/bumblebee_status/util/popup.py @@ -71,6 +71,11 @@ class menu(object): label=menuitem, command=functools.partial(self.__on_click, callback) ) + """Adds a separator to the menu in the current location""" + + def add_separator(self): + self._menu.add_separator() + """Shows this menu :param event: i3wm event that triggered the menu (dict that contains "x" and "y" fields) From de00f9bdcebf118f2da3f012c857fdf770d09de0 Mon Sep 17 00:00:00 2001 From: Zero Rust Date: Fri, 22 May 2020 23:53:38 -0400 Subject: [PATCH 06/10] getting display and layout state for populating menu --- bumblebee_status/modules/contrib/arandr.py | 70 +++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/bumblebee_status/modules/contrib/arandr.py b/bumblebee_status/modules/contrib/arandr.py index 7bbd84c..16f66b5 100644 --- a/bumblebee_status/modules/contrib/arandr.py +++ b/bumblebee_status/modules/contrib/arandr.py @@ -7,8 +7,10 @@ Requires the following executable: * xrandr """ +import fnmatch from functools import partial import logging +import os import core.module import core.widget @@ -21,6 +23,7 @@ from util.cli import execute log = logging.getLogger(__name__) +__screenlayout_dir__ = os.path.expanduser("~/.screenlayout") class Module(core.module.Module): @core.decorators.never @@ -42,12 +45,77 @@ class Module(core.module.Module): of three ways: launch arandr, select a pre-set screenlayout, toggle a display. """ - log.info("arandr showing popup") menu = popup.menu() menu.add_menuitem("arandr", callback=partial(execute, self.manager) ) + menu.add_separator() + + displays = Module._get_displays() + layouts = Module._get_layouts() + available_layouts = Module._prune_layouts(layouts, displays) menu.show(widget, 0, 0) + @staticmethod + def _get_displays(): + """Queries xrandr and builds a dict of the displays and their state. + + The dict entries are key by the display and are bools (True if + connected). + """ + displays = {} + for line in execute("xrandr -q").split("\n"): + if not "connected" in line: + continue + parts = line.split(" ", 2) + display = parts[0] + displays[display] = True if parts[1] == "connected" else False + + return displays + + @staticmethod + def _get_layouts(): + """Loads and parses the arandr screen layout scripts.""" + layouts = {} + for filename in os.listdir(__screenlayout_dir__): + if fnmatch.fnmatch(filename, '*.sh'): + fullpath = os.path.join(__screenlayout_dir__, filename) + with open(fullpath, "r") as file: + for line in file: + s_line = line.strip() + if not "xrandr" in s_line: + continue + displays_in_file = Module._parse_layout(line) + layouts[filename] = displays_in_file + return layouts + + @staticmethod + def _parse_layout(line): + """Parses a single xrandr line to find what displays are active in the + command. Returns them as a list. + """ + active_displays = [] + to_check = line[7:].split("--output ") + for check in to_check: + if not check or "off" in check: + continue + active_displays.append(check.split(" ")[0]) + return active_displays + + @staticmethod + def _prune_layouts(layouts, displays): + """Return a list of layouts whose displays are actually connected.""" + available = [] + for layout, needs in layouts.items(): + still_valid = True + for need in needs: + if need not in displays or not displays[need]: + still_valid = False + break + if still_valid: + available.append(layout) + return available + + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From ffd04e9710f892f3d5962bb112597384eb8e291d Mon Sep 17 00:00:00 2001 From: Zero Rust Date: Sat, 23 May 2020 07:13:47 -0400 Subject: [PATCH 07/10] full menu and command for arandr scripts --- bumblebee_status/modules/contrib/arandr.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/bumblebee_status/modules/contrib/arandr.py b/bumblebee_status/modules/contrib/arandr.py index 16f66b5..a31ce29 100644 --- a/bumblebee_status/modules/contrib/arandr.py +++ b/bumblebee_status/modules/contrib/arandr.py @@ -54,6 +54,17 @@ class Module(core.module.Module): displays = Module._get_displays() layouts = Module._get_layouts() available_layouts = Module._prune_layouts(layouts, displays) + log.debug("Available layouts:") + log.debug(available_layouts) + + if len(available_layouts) > 0: + for layout in available_layouts: + sh = os.path.join(__screenlayout_dir__, layout) + sh_name = os.path.splitext(layout)[0] + cmd = self.parameter(sh_name, sh) + menu.add_menuitem(sh_name, + callback=partial(util.cli.execute, sh) + ) menu.show(widget, 0, 0) From 90fbc249af5e897821af68fd0f5b915defec57f2 Mon Sep 17 00:00:00 2001 From: Zero Rust Date: Sat, 23 May 2020 07:28:00 -0400 Subject: [PATCH 08/10] cleaner way to activate a layout --- bumblebee_status/modules/contrib/arandr.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bumblebee_status/modules/contrib/arandr.py b/bumblebee_status/modules/contrib/arandr.py index a31ce29..883859e 100644 --- a/bumblebee_status/modules/contrib/arandr.py +++ b/bumblebee_status/modules/contrib/arandr.py @@ -40,6 +40,11 @@ class Module(core.module.Module): core.input.register(self, button=core.input.RIGHT_MOUSE, cmd=self.popup) + def activate_layout(self, layout_path): + log.debug("activating layout") + log.debug(layout_path) + execute(layout_path) + def popup(self, widget): """Create Popup that allows the user to control their displays in one of three ways: launch arandr, select a pre-set screenlayout, toggle a @@ -61,10 +66,8 @@ class Module(core.module.Module): for layout in available_layouts: sh = os.path.join(__screenlayout_dir__, layout) sh_name = os.path.splitext(layout)[0] - cmd = self.parameter(sh_name, sh) menu.add_menuitem(sh_name, - callback=partial(util.cli.execute, sh) - ) + callback=partial(self.activate_layout, sh)) menu.show(widget, 0, 0) From 3921bab32e0ce4bd3e5ce04f043aba60386cdbef Mon Sep 17 00:00:00 2001 From: Zero Rust Date: Sat, 23 May 2020 07:53:28 -0400 Subject: [PATCH 09/10] added display toggling to the arandr module --- bumblebee_status/modules/contrib/arandr.py | 38 ++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/bumblebee_status/modules/contrib/arandr.py b/bumblebee_status/modules/contrib/arandr.py index 883859e..d625e51 100644 --- a/bumblebee_status/modules/contrib/arandr.py +++ b/bumblebee_status/modules/contrib/arandr.py @@ -11,6 +11,7 @@ import fnmatch from functools import partial import logging import os +import re import core.module import core.widget @@ -31,6 +32,7 @@ class Module(core.module.Module): super().__init__(config, theme, core.widget.Widget('')) self.manager = self.parameter("manager", "arandr") + self.toggle_cmd = "xrandr" core.input.register( self, button=core.input.LEFT_MOUSE, @@ -57,6 +59,7 @@ class Module(core.module.Module): menu.add_separator() displays = Module._get_displays() + log.debug(displays) layouts = Module._get_layouts() available_layouts = Module._prune_layouts(layouts, displays) log.debug("Available layouts:") @@ -69,8 +72,36 @@ class Module(core.module.Module): menu.add_menuitem(sh_name, callback=partial(self.activate_layout, sh)) + menu.add_separator() + count_on = 0 + for display, state in displays.items(): + if state[1]: + count_on += 1 + for display, state in displays.items(): + if not state[0]: + continue + on_off = "On" if state[1] else "Off" + menu_line = "{}: {}".format(display, on_off) + menu.add_menuitem(menu_line, + callback=partial(self.toggle_display, display, + state[1], count_on)) + menu.show(widget, 0, 0) + def toggle_display(self, display, current_state, count_on): + """Toggle a display on or off based on its current state.""" + if current_state: + log.debug("toggling off {}".format(display)) + if count_on == 1: + log.info("attempted to turn off last display") + return + execute("{} --output {} --off".format(self.toggle_cmd, display)) + else: + log.debug("toggling on {}".format(display)) + execute( + "{} --output {} --auto".format(self.toggle_cmd, display) + ) + @staticmethod def _get_displays(): """Queries xrandr and builds a dict of the displays and their state. @@ -82,9 +113,12 @@ class Module(core.module.Module): for line in execute("xrandr -q").split("\n"): if not "connected" in line: continue + is_on = bool(re.search(r"\d+x\d+\+(\d+)\+\d+", line)) parts = line.split(" ", 2) display = parts[0] - displays[display] = True if parts[1] == "connected" else False + displays[display] = ( + (True, is_on) if parts[1] == "connected" else (False, is_on) + ) return displays @@ -124,7 +158,7 @@ class Module(core.module.Module): for layout, needs in layouts.items(): still_valid = True for need in needs: - if need not in displays or not displays[need]: + if need not in displays or not displays[need][0]: still_valid = False break if still_valid: From 8c8fef61eb65484766f1d2b296bf7d2e0d7aaf28 Mon Sep 17 00:00:00 2001 From: Zero Rust Date: Sat, 23 May 2020 07:58:50 -0400 Subject: [PATCH 10/10] linting --- bumblebee_status/modules/contrib/arandr.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/bumblebee_status/modules/contrib/arandr.py b/bumblebee_status/modules/contrib/arandr.py index d625e51..80ba3b5 100644 --- a/bumblebee_status/modules/contrib/arandr.py +++ b/bumblebee_status/modules/contrib/arandr.py @@ -1,6 +1,12 @@ # pylint: disable=C0111,R0903 -"""My TEST +"""Enables handy interaction with arandr for display management. Left-clicking +will execute arandr for interactive display management. Right-clicking will +bring up a context- and state-sensitive menu that will allow you to switch to a +saved screen layout as well as toggle on/off individual connected displays. + +Parameters: + * No configuration parameters Requires the following executable: * arandr @@ -17,7 +23,6 @@ import core.module import core.widget import core.input import core.decorators -import util.cli from util import popup from util.cli import execute @@ -26,6 +31,7 @@ log = logging.getLogger(__name__) __screenlayout_dir__ = os.path.expanduser("~/.screenlayout") + class Module(core.module.Module): @core.decorators.never def __init__(self, config, theme): @@ -42,7 +48,8 @@ class Module(core.module.Module): core.input.register(self, button=core.input.RIGHT_MOUSE, cmd=self.popup) - def activate_layout(self, layout_path): + @staticmethod + def activate_layout(layout_path): log.debug("activating layout") log.debug(layout_path) execute(layout_path) @@ -53,7 +60,8 @@ class Module(core.module.Module): display. """ menu = popup.menu() - menu.add_menuitem("arandr", + menu.add_menuitem( + "arandr", callback=partial(execute, self.manager) ) menu.add_separator() @@ -111,7 +119,7 @@ class Module(core.module.Module): """ displays = {} for line in execute("xrandr -q").split("\n"): - if not "connected" in line: + if "connected" not in line: continue is_on = bool(re.search(r"\d+x\d+\+(\d+)\+\d+", line)) parts = line.split(" ", 2) @@ -132,7 +140,7 @@ class Module(core.module.Module): with open(fullpath, "r") as file: for line in file: s_line = line.strip() - if not "xrandr" in s_line: + if "xrandr" not in s_line: continue displays_in_file = Module._parse_layout(line) layouts[filename] = displays_in_file