Skip to content

Instantly share code, notes, and snippets.

@mikeplus64
Last active September 10, 2025 10:17
Show Gist options
  • Save mikeplus64/ca5d057009f9d46428f627353a8f76c2 to your computer and use it in GitHub Desktop.
Save mikeplus64/ca5d057009f9d46428f627353a8f76c2 to your computer and use it in GitHub Desktop.
hypr-move-workspace
#!/usr/bin/python
# pyright: reportAny=false, reportExplicitAny=false
import argparse
import cProfile
from dataclasses import dataclass
import subprocess
import json
import sys
from typing import Any, Literal, TypedDict, cast
class WorkspaceRef(TypedDict):
id: int
name: str
class Monitor(TypedDict):
name: str
description: str
make: str
model: str
serial: str
id: int
focused: bool
width: int
height: int
refreshRate: float
x: int
y: int
activeWorkspace: WorkspaceRef
specialWorkspace: WorkspaceRef
class Workspace(TypedDict):
id: int
name: str
monitor: str
monitorID: int
windows: int
hasfullscreen: bool
lastwindow: str
lastwindowtitle: str
ispersistent: bool
@dataclass
class MonitorWithWorkspaces:
monitor: Monitor
active_workspace: Workspace | None
workspaces: list[Workspace]
def jsonproc(*args: str) -> Any:
return json.loads((subprocess.run(args, stdout=subprocess.PIPE)).stdout)
@dataclass
class Move():
direction: Literal['left'] | Literal['right']
with_window: bool
def offset(self):
return ((-1) if self.direction == 'left' else 1)
def __init__(self):
parser = argparse.ArgumentParser(
"hypr-crement-workspace",
description="Go to the next or previous available workspace on the current monitor",
)
_ = parser.add_argument('direction', choices=['left', 'right'])
_ = parser.add_argument('--with-window', action='store_true')
args = parser.parse_args(sys.argv[1:])
self.direction = 'left' if args.direction == 'left' else 'right'
self.with_window = args.with_window == True
@dataclass
class State:
monitors: dict[int, MonitorWithWorkspaces]
workspaces: dict[int, Workspace]
focus: MonitorWithWorkspaces
def __init__(self):
self.monitors = dict(map(lambda d: (d['id'],
MonitorWithWorkspaces(monitor = cast(Monitor, d),
workspaces = [],
active_workspace = None)),
jsonproc("hyprctl", "-j", "monitors")))
self.workspaces = dict(map(lambda d: (d['id'], cast(Workspace, d)),
jsonproc("hyprctl", "-j", "workspaces")))
for _, w in self.workspaces.items():
monitor = self.monitors[w['monitorID']]
monitor.workspaces.append(w)
if w['id'] == monitor.monitor['activeWorkspace']['id']:
monitor.active_workspace = w
self.focus = next(m for m in self.monitors.values() if m.monitor['focused'] == True)
self.focus.workspaces = sorted(self.focus.workspaces, key=lambda k: k['id'])
def next_workspace_id(offset: int, state: State) -> int:
active_workspace_id = state.focus.monitor['activeWorkspace']['id']
active_workspace_idx = next(
i
for i, w
in enumerate(state.focus.workspaces)
if w['id'] == active_workspace_id)
swap_to_idx = active_workspace_idx + offset
swap_to_id = active_workspace_id + offset
workspace_ids_on_active_monitor = dict((w['id'], w) for w in state.focus.workspaces)
if swap_to_id in workspace_ids_on_active_monitor:
print("swapping to existing workspace")
swap_to_workspace = workspace_ids_on_active_monitor[swap_to_id]
return swap_to_workspace['id']
else:
print("no existing workspace")
on_empty_workspace = state.focus.active_workspace is not None and state.focus.active_workspace['windows'] == 0
if on_empty_workspace:
return state.focus.workspaces[swap_to_idx % len(state.focus.workspaces)]['id']
else:
print("creating new workspace id")
id = active_workspace_id
while id in state.workspaces:
id += offset
if id == 0:
id = active_workspace_id
while id in state.workspaces:
id -= offset
return id
else:
return id
def main():
move = Move()
state = State()
r = next_workspace_id(move.offset(), state)
if move.with_window:
_ = subprocess.run(["hyprctl", "dispatch", "movetoworkspace", str(r)])
else:
_ = subprocess.run(["hyprctl", "dispatch", "workspace", str(r)])
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment