You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
swayswitch/swayswitch

128 lines
4.5 KiB

#!/bin/python3
"""A simple windowswitcher for sway"""
# switch_window, get_windows and extract_nodes_iterative is derived from
# https://github.com/tobiaspc/wofi-scripts Copyright (c) 2020 Tobi which
# is covered by the MIT License. However this program is licenced under
# the GPLv3+ Copyright (c) 2020 Micke Nordin. See LICENSE file for details.
import json
import math
import subprocess
import wx
class SwaySwitch(wx.Frame): # pylint: disable=no-member
"""Frame for the swayswitcher"""
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
self.pnl = wx.Panel(self) # pylint: disable=no-member
# get windows from sway
windows = get_windows()
label_len = 20
# 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:
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]
winid = window['id']
size = wx.Window.GetFont(self).GetPointSize() * label_len # pylint: disable=no-member
btn = wx.Button( # pylint: disable=no-member
parent=self.pnl,
id=winid,
label=label,
size=wx.Size(size, size)) # pylint: disable=no-member
btn.Bind(
wx.EVT_BUTTON,
lambda event, mwinid=winid: self.switch_window(event, mwinid))
sizer.Add(btn, 0, wx.ALIGN_CENTER) # pylint: disable=no-member
# Set up esc keybinding
self.Bind(wx.EVT_CHAR_HOOK, lambda event: self.on_key_press(event)) # pylint: disable=unnecessary-lambda
sizer.Fit(self)
sizer.Layout()
def on_key_press(self, event):
"""Intercept esc key press"""
keycode = event.GetUnicodeKey()
if keycode == wx.WXK_ESCAPE: # pylint: disable=no-member
self.Close(True)
else:
event.Skip(True)
def switch_window(self, event, winid): # pylint: disable=unused-argument
"""Switches the focus to the given id"""
command = "swaymsg [con_id={}] focus".format(winid)
subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
self.Close(True)
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])
# Select outputs that are active
windows = []
for output in data['nodes']:
# The scratchpad (under __i3) is not supported
if output.get('name') != '__i3' and output.get('type') == 'output':
workspaces = output.get('nodes')
for workspace in workspaces:
if workspace.get('type') == 'workspace':
windows += extract_nodes_iterative(workspace)
return windows
def extract_nodes_iterative(workspace):
"""Extracts all windows from a sway workspace json object"""
all_nodes = []
floating_nodes = workspace.get('floating_nodes')
for floating_node in floating_nodes:
all_nodes.append(floating_node)
nodes = workspace.get('nodes')
for node in nodes:
# Leaf node
if len(node.get('nodes')) == 0:
all_nodes.append(node)
# Nested node, handled iterative
else:
for inner_node in node.get('nodes'):
nodes.append(inner_node)
return all_nodes
# Entry point
if __name__ == "__main__":
# When this module is run (not imported) then create the app, the
# frame, show it, and start the event loop.
app = wx.App()
frm = SwaySwitch(None, title="")
frm.Show()
app.MainLoop()