From 89ba780e4aabd3c4de9977cf6c5e2a3f207867a0 Mon Sep 17 00:00:00 2001 From: Micke Nordin Date: Tue, 5 Jan 2021 10:16:33 +0100 Subject: [PATCH] Refactoring and clean up --- src/swayswitch | 247 +++++++++++++++++++++++++++---------------------- 1 file changed, 137 insertions(+), 110 deletions(-) diff --git a/src/swayswitch b/src/swayswitch index 2c7f2eb..27e37d0 100755 --- a/src/swayswitch +++ b/src/swayswitch @@ -18,7 +18,7 @@ import wx class SwaySwitch(wx.Frame): # pylint: disable=no-member """Frame for the swayswitcher""" - def __init__(self, *args, **kw): # pylint: disable=unused-argument,too-many-locals,too-many-branches,too-many-statements + def __init__(self, *args, **kw): # pylint: disable=unused-argument """Constructor""" wx.Frame.__init__(self, None, title="", style=wx.STAY_ON_TOP) # pylint: disable=no-member # create a panel in the frame @@ -26,7 +26,27 @@ class SwaySwitch(wx.Frame): # pylint: disable=no-member # Some xdg data self.home = os.environ.get('HOME') - self.base_dirs = [] + self.base_dirs = self.get_base_dirs() + # get windows from sway + windows = get_windows() + + # Get config + self.label_len = 20 + self.icon_size = 128 + + self.icon_dirs = self.get_icon_dirs() + + # and create a sizer to manage the layout of child widgets + x_and_y = int(math.sqrt(len(windows)) + 0.5) + self.sizer = wx.GridSizer(x_and_y) # pylint: disable=no-member + self.pnl.SetSizer(self.sizer) + self.set_buttons(windows) + self.sizer.Fit(self) + self.sizer.Layout() + + def get_base_dirs(self): + """We want to follow the XDG standard if possible""" + base_dirs = [] xdg_dirs = os.environ.get('XDG_DATA_DIRS') pos_dirs = [] if xdg_dirs: @@ -39,72 +59,8 @@ class SwaySwitch(wx.Frame): # pylint: disable=no-member ] for pos_dir in pos_dirs: if os.path.exists(pos_dir): - self.base_dirs.append(pos_dir) - - # get windows from sway - windows = get_windows() - label_len = 20 - - # Icon size - self.icon_size = 128 - - # and create a sizer to manage the layout of child widgets - x_and_y = int(math.sqrt(len(windows)) + 0.5) - sizer = wx.GridSizer(x_and_y) # pylint: disable=no-member - self.pnl.SetSizer(sizer) - for window in windows: - inner_sizer = wx.BoxSizer(orient=wx.VERTICAL) # pylint: disable=no-member - try: - label = window['window_properties']['class'] - except KeyError: - try: - label = window['app_id'] - if len(label) < label_len: - label = label + ':' + window['name'][:label_len - - len(label)] - except KeyError: - label = window['name'] - if len(label) > label_len: - label = label[:label_len] - - # This is setting up an inner sizer with a static text label and an image icon - label = "ws" + str(window['workspace']) + ": " + label - winid = window['id'] - size = wx.Window.GetFont(self).GetPointSize() * label_len # pylint: disable=no-member - command = get_command(window['pid']) - desktop_file = self.get_desktop_file(command) - icon = self.get_icon(desktop_file) # pylint: disable=no-member - if icon: - if icon.endswith(".svg"): - svgpng = cairosvg.svg2png( - bytestring=open(icon).read().encode('utf-8')) - image = wx.Image(BytesIO(svgpng), wx.BITMAP_TYPE_PNG) # pylint: disable=no-member - else: - unscaled_bitmap = wx.Bitmap(self.get_icon(desktop_file)) # pylint: disable=no-member - image = unscaled_bitmap.ConvertToImage() - image = image.Scale(self.icon_size, self.icon_size, - wx.IMAGE_QUALITY_HIGH) # pylint: disable=no-member - bitmap = wx.Bitmap(image) # pylint: disable=no-member - else: - bitmap = wx.Bitmap() # pylint: disable=no-member - btn = wx.BitmapButton( # pylint: disable=no-member - self.pnl, - id=winid, - bitmap=bitmap, - size=wx.Size(size, size)) # pylint: disable=no-member - btn.Bind( - wx.EVT_BUTTON, - lambda event, mwinid=winid: self.switch_window(event, mwinid)) - # Set up esc keybinding - self.Bind(wx.EVT_CHAR_HOOK, lambda event: self.on_key_press(event)) # pylint: disable=unnecessary-lambda - statictext = wx.StaticText(self.pnl, -1, label) # pylint: disable=no-member - inner_sizer.Add(statictext, 0, wx.ALIGN_CENTER) # pylint: disable=no-member - inner_sizer.Add(btn, 0, wx.ALIGN_CENTER) # pylint: disable=no-member - inner_sizer.Fit(self) - inner_sizer.Layout() - sizer.Add(inner_sizer, 0, wx.ALIGN_CENTER) # pylint: disable=no-member - sizer.Fit(self) - sizer.Layout() + base_dirs.append(pos_dir) + return base_dirs def get_desktop_file(self, command): """From here we return first dektopfile""" @@ -114,29 +70,38 @@ class SwaySwitch(wx.Frame): # pylint: disable=no-member if os.path.exists(directory): command = "grep -s -l {} {}/*.desktop".format( command, directory) - process = subprocess.Popen(command, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - for data in [i for i in process.communicate() if i]: + for data in [i for i in run_command(command) if i]: for result in data.decode().split('\n'): desktop_file = result break return desktop_file - def get_icon(self, desktop_file): # pylint: disable=too-many-branches - """ Find icon from a desktop file""" - command = "grep Icon= {}".format(desktop_file) - process = subprocess.Popen(command, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - for data in [i.rstrip() for i in process.communicate() if i]: - try: - icon_name = data.decode().split('=')[1] - except IndexError: - return "" - # Find icon dirs + def get_icon_bitmap(self, icon): + """Create a bitmap with right size from svg or png""" + if icon: + if icon.endswith(".svg"): + svgpng = cairosvg.svg2png( + bytestring=open(icon).read().encode('utf-8')) + image = wx.Image(BytesIO(svgpng), wx.BITMAP_TYPE_PNG) # pylint: disable=no-member + else: + unscaled_bitmap = wx.Bitmap(icon) # pylint: disable=no-member + image = unscaled_bitmap.ConvertToImage() + image = image.Scale(self.icon_size, self.icon_size, + wx.IMAGE_QUALITY_HIGH) # pylint: disable=no-member + bitmap = wx.Bitmap(image) # pylint: disable=no-member + else: + bitmap = wx.Bitmap() # pylint: disable=no-member + return bitmap + + def get_icon_path(self, desktop_file): + """Glue function to get icon path""" + icon_name = get_icon_name(desktop_file) + pos_icons = self.get_pos_icons(icon_name) + icon_path = self.get_right_size_icon(pos_icons) + return icon_path + + def get_icon_dirs(self): + """Find icon dirs""" icon_dirs = [] for base_dir in [ directory for directory in self.base_dirs if directory @@ -145,28 +110,48 @@ class SwaySwitch(wx.Frame): # pylint: disable=no-member icon_dirs.append(directory) icon_dirs.append(self.home + "/.icons") icon_dirs.append("/usr/share/pixmaps") + + return icon_dirs + + def get_label(self, window): + """Construct the label for the window""" + try: + label = window['window_properties']['class'] + except KeyError: + try: + label = window['app_id'] + if len(label) < self.label_len: + label = label + ':' + window['name'][:self.label_len - + len(label)] + except KeyError: + label = window['name'] + if len(label) > self.label_len: + label = label[:self.label_len] + + label = "ws" + str(window['workspace']) + ": " + label + return label + + def get_pos_icons(self, icon_name): + """Collect possible icons, prefer svg""" possible_icons = [] - for icon_dir in [directory for directory in icon_dirs if directory]: + for icon_dir in [ + directory for directory in self.icon_dirs if directory + ]: if os.path.exists(icon_dir): command = "find {} -name *{}.svg".format(icon_dir, icon_name) - process = subprocess.Popen(command, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - result = process.communicate()[0].split() + result = run_command(command)[0].split() if not result: command = "find {} -name *{}.png".format( icon_dir, icon_name) - process = subprocess.Popen(command, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - result = process.communicate()[0].split() + result = run_command(command)[0].split() for data in result: icon_cand = data.decode() if icon_cand not in possible_icons: possible_icons.append(icon_cand) - # Try to find prefered size + return possible_icons + + def get_right_size_icon(self, possible_icons): + """Try to find prefered size""" icon = None for pos_icon in possible_icons: if pos_icon.endswith(".svg"): @@ -188,37 +173,69 @@ class SwaySwitch(wx.Frame): # pylint: disable=no-member if keycode == wx.WXK_ESCAPE: # pylint: disable=no-member # enter normal mode and exit command = 'swaymsg mode "default"' - subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + run_command(command) self.Close(True) else: event.Skip(True) + def set_buttons(self, windows): + """Loop over windows and create buttons for them""" + for window in windows: + # This is setting up an inner sizer with a static text label and an image icon + inner_sizer = wx.BoxSizer(orient=wx.VERTICAL) # pylint: disable=no-member + label = self.get_label(window) + winid = window['id'] + size = wx.Window.GetFont(self).GetPointSize() * self.label_len # pylint: disable=no-member + command = get_command(window['pid']) + desktop_file = self.get_desktop_file(command) + icon_path = self.get_icon_path(desktop_file) + bitmap = self.get_icon_bitmap(icon_path) + btn = wx.BitmapButton( # pylint: disable=no-member + self.pnl, + id=winid, + bitmap=bitmap, + size=wx.Size(size, size)) # pylint: disable=no-member + btn.Bind( + wx.EVT_BUTTON, + lambda event, mwinid=winid: self.switch_window(event, mwinid)) + # Set up esc keybinding + self.Bind(wx.EVT_CHAR_HOOK, lambda event: self.on_key_press(event)) # pylint: disable=unnecessary-lambda + statictext = wx.StaticText(self.pnl, -1, label) # pylint: disable=no-member + inner_sizer.Add(statictext, 0, wx.ALIGN_CENTER) # pylint: disable=no-member + inner_sizer.Add(btn, 0, wx.ALIGN_CENTER) # pylint: disable=no-member + inner_sizer.Fit(self) + inner_sizer.Layout() + self.sizer.Add(inner_sizer, 0, wx.ALIGN_CENTER) # pylint: disable=no-member + def switch_window(self, event, winid): # pylint: disable=unused-argument """Switches the focus to the given id and enter normalmode""" command = 'swaymsg [con_id={}] focus, mode "default"'.format(winid) - subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + run_command(command) self.Close(True) def get_command(pid): """Returns a list of all json window objects""" command = "ps h c -p {} -o command".format(pid) - process = subprocess.Popen(command, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - result = process.communicate()[0].rstrip().decode() + result = run_command(command)[0].rstrip().decode() return result +def get_icon_name(desktop_file): + """ Find icon from a desktop file""" + command = "grep Icon= {}".format(desktop_file) + for data in [i.rstrip() for i in run_command(command) if i]: + try: + icon_name = data.decode().split('=')[1] + return icon_name + except IndexError: + return "" + + def get_windows(): """Returns a list of all json window objects""" command = "swaymsg -t get_tree" - process = subprocess.Popen(command, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - data = json.loads(process.communicate()[0]) + data = json.loads(run_command(command)[0]) # Select outputs that are active windows = [] @@ -260,6 +277,16 @@ def extract_nodes_iterative(workspace): return all_nodes +def run_command(command): + """Run a command on system and capture result""" + process = subprocess.Popen(command, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + result = process.communicate() + return result + + # Entry point if __name__ == "__main__": # When this module is run (not imported) then create the app, the