Files
Modus/utils/inhibit.py
T
sankalp 345ef643e1 refactor(utils): replace requests with urllib and improve error handling
Replace external `requests` calls with `urllib` to eliminate third‑party dependency, add robust timeout handling, and improve error reporting. This also standardises URL fetching across the module and updates the process name configuration.

Enhancements:
- Added default request timeout fallback and ensured proper cleanup of response objects
- Enriched exception messages for better debugging

Bug Fixes:
- Fixed potential timeout‑related crashes in `CurrencyCache` and `Conversion`
- Corrected process name setting via `set_process_name` in the entry point
2026-04-09 19:20:55 +05:45

192 lines
6.0 KiB
Python

# From https://github.com/stwa/wayland-idle-inhibitor
# License: WTFPL Version 2
import argparse
import os
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
# Add the project root to sys.path to allow absolute imports from 'utils'
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from signal import SIGINT, SIGTERM, signal
from threading import Event, Timer
from utils.functions import set_process_name
from pywayland.client.display import Display
from pywayland.protocol.idle_inhibit_unstable_v1.zwp_idle_inhibit_manager_v1 import (
ZwpIdleInhibitManagerV1,
)
from pywayland.protocol.wayland.wl_compositor import WlCompositor
from pywayland.protocol.wayland.wl_registry import WlRegistryProxy
from pywayland.protocol.wayland.wl_surface import WlSurface
@dataclass
class GlobalRegistry:
surface: WlSurface | None = None
inhibit_manager: ZwpIdleInhibitManagerV1 | None = None
def parse_duration(duration_str: str) -> int:
"""Parse duration string into seconds.
Examples: '1h', '30m', '45s', '1.5h', '1.5m', '30.5s', 'on', 'off'
"""
try:
if duration_str.lower() in ["on", "off"]:
return 0
elif duration_str.endswith("h"):
return int(float(duration_str[:-1]) * 3600)
elif duration_str.endswith("m"):
return int(float(duration_str[:-1]) * 60)
elif duration_str.endswith("s"):
return int(float(duration_str[:-1]))
else:
return int(duration_str)
except ValueError:
raise ValueError(
"Invalid duration format. Use '1h', '30m', '45s', 'on', 'off', etc."
)
def kill_existing_inhibit_processes():
"""Kill any existing modus-inhibit processes."""
try:
# Find all modus-inhibit processes except the current one
output = subprocess.check_output(["pgrep", "-f", "modus-inhibit"], text=True)
pids = output.strip().split("\n")
current_pid = str(os.getpid())
killed_count = 0
for pid in pids:
pid = pid.strip()
if pid and pid != current_pid:
try:
subprocess.run(["kill", pid], check=True)
killed_count += 1
except subprocess.CalledProcessError:
pass # Process might have already exited
if killed_count > 0:
print(f"Stopped {killed_count} existing inhibit process(es)")
else:
print("No existing inhibit processes were running")
return killed_count
except subprocess.CalledProcessError:
# No processes found
print("No existing inhibit processes were running")
return 0
except Exception as e:
print(f"Error stopping existing processes: {e}")
return 0
def handle_registry_global(
wl_registry: WlRegistryProxy, id_num: int, iface_name: str, version: int
) -> None:
global_registry: GlobalRegistry = wl_registry.user_data or GlobalRegistry()
if iface_name == "wl_compositor":
compositor = wl_registry.bind(id_num, WlCompositor, version)
global_registry.surface = compositor.create_surface() # type: ignore
elif iface_name == "zwp_idle_inhibit_manager_v1":
global_registry.inhibit_manager = wl_registry.bind(
id_num, ZwpIdleInhibitManagerV1, version
)
def main() -> None:
parser = argparse.ArgumentParser(
description="Inhibit system idle for a specified duration"
)
parser.add_argument(
"duration",
nargs="?",
default="0",
help='Duration to inhibit (e.g., "1h", "30m", "45s", "on", "off"). Use "on" for indefinite, "off" to stop.',
)
args = parser.parse_args()
# Handle "off" argument - kill existing processes and exit
if args.duration.lower() == "off":
kill_existing_inhibit_processes()
sys.exit(0)
done = Event()
signal(SIGINT, lambda _, __: done.set())
signal(SIGTERM, lambda _, __: done.set())
global_registry = GlobalRegistry()
try:
display = Display()
display.connect()
registry = display.get_registry() # type: ignore
registry.user_data = global_registry
registry.dispatcher["global"] = handle_registry_global
def shutdown() -> None:
display.dispatch()
display.roundtrip()
display.disconnect()
display.dispatch()
display.roundtrip()
if global_registry.surface is None:
print("Error: Failed to create Wayland surface.")
shutdown()
sys.exit(1)
if global_registry.inhibit_manager is None:
print("Error: Your Wayland compositor does not support idle inhibition.")
print("This usually means either:")
print(
"1. Your compositor (like Hyprland) doesn't support the idle-inhibit protocol"
)
print("2. The protocol is not enabled in your compositor")
print("\nFor Hyprland, you can enable it by adding to your config:")
print("misc:disable_autoreload = true")
print("misc:enable_wayland_protocols = true")
shutdown()
sys.exit(1)
inhibitor = global_registry.inhibit_manager.create_inhibitor( # type: ignore
global_registry.surface
)
display.dispatch()
display.roundtrip()
duration = parse_duration(args.duration)
if duration > 0:
print(f"Inhibiting idle for {args.duration}...")
# Set up timer to release inhibition
timer = Timer(duration, lambda: done.set())
timer.start()
else:
print("Inhibiting idle indefinitely...")
done.wait()
print("Shutting down...")
inhibitor.destroy()
if duration > 0:
timer.cancel()
shutdown()
except Exception as e:
print(f"Error: {str(e)}")
print("Make sure you're running this under a Wayland session.")
sys.exit(1)
if __name__ == "__main__":
set_process_name("modus-inhibit")
main()