refactor(services): split screen capture into screenshot + recorder services

Split the monolithic ScreenCapture service into focused Screenshot and
ScreenRecorder services behind a pluggable capture backend.

- Add shared/capture.py: CaptureBackend abstraction + HyprlandCaptureBackend
  (hyprshot/grim/slurp/wf-recorder) and reusable CaptureNotifier
- Add services/screenshot.py and services/screenrecorder.py with Fabric
  properties/signals; recording state kept in memory (no /tmp files)
- Keep services/screencapture.py as a backwards-compatible facade so
  existing widgets and signals keep working
- Resolve monitors via Hyprland IPC helpers in services/modus.py instead of
  spawning `hyprctl monitors -j` per capture
- Implement real window recording (slurp window boxes) and split the
  record-window / record-region UI mappings
- Register screenshot_service / screen_recorder_service singletons
- Remove dead src/utils/inhibit.py duplicate; minor modus.lua quoting fix
This commit is contained in:
sankalp committed 2026-07-13 23:52:32 +05:45
1 parent 54ff85ffc9
commit f3f17bb5fc
9 files changed
+1063 -566

No files matched your search

+31
View File
@@ -43,6 +43,18 @@ def __getattr__(name):
_inst = ScreenCapture()
setattr(mod, name, _inst)
return _inst
if name == "screenshot_service":
from services.screenshot import Screenshot
_inst = Screenshot()
setattr(mod, name, _inst)
return _inst
if name == "screen_recorder_service":
from services.screenrecorder import ScreenRecorder
_inst = ScreenRecorder()
setattr(mod, name, _inst)
return _inst
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -490,6 +502,25 @@ def get_monitors() -> list:
return data if isinstance(data, list) else []
def get_focused_monitor() -> dict | None:
monitors = get_monitors()
for monitor in monitors:
if monitor.get("focused"):
return monitor
return monitors[0] if monitors else None
def get_monitor_names() -> list[str]:
return [name for m in get_monitors() if (name := m.get("name"))]
def get_active_monitor_name(fallback: str = "eDP-1") -> str:
monitor = get_focused_monitor()
if monitor and (name := monitor.get("name")):
return name
return fallback
def get_active_workspace_id() -> int:
data = _hyprctl_json("j/activeworkspace")
if isinstance(data, dict):