A simple window switcher for sway.
Part of this code is derived from: <https://github.com/tobiaspc/wofi-scripts> Huge thanks to Tobi!
This commit is contained in:
parent
015cf0d321
commit
dac804e64f
2 changed files with 150 additions and 0 deletions
23
LICENSE
23
LICENSE
|
@ -672,3 +672,26 @@ may consider it more useful to permit linking proprietary applications with
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
Public License instead of this License. But first, please read
|
Public License instead of this License. But first, please read
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
|
|
||||||
|
Sections of this code is derived from covered under the MIT License
|
||||||
|
Copyright (c) 2020 Tobi <https://github.com/tobiaspc/wofi-scripts>
|
||||||
|
See source file for more information
|
||||||
|
|
||||||
|
The MIT License
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
127
swayswitch
Executable file
127
swayswitch
Executable file
|
@ -0,0 +1,127 @@
|
||||||
|
#!/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()
|
Loading…
Add table
Reference in a new issue