capslockstep 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- capslockstep/__init__.py +2 -0
- capslockstep/cli.py +49 -0
- capslockstep/key.py +55 -0
- capslockstep/models.py +12 -0
- capslockstep/py.typed +0 -0
- capslockstep-0.1.0.dist-info/METADATA +10 -0
- capslockstep-0.1.0.dist-info/RECORD +9 -0
- capslockstep-0.1.0.dist-info/WHEEL +4 -0
- capslockstep-0.1.0.dist-info/entry_points.txt +3 -0
capslockstep/__init__.py
ADDED
capslockstep/cli.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import asyncio
|
|
3
|
+
import platform
|
|
4
|
+
from contextlib import suppress
|
|
5
|
+
|
|
6
|
+
import aiohttp
|
|
7
|
+
|
|
8
|
+
from capslockstep.key import CapsLock, CapsLockLinux
|
|
9
|
+
from capslockstep.models import CapsLockEvent, CapsLockState
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
parser = argparse.ArgumentParser()
|
|
14
|
+
parser.add_argument("room_id", help="The ID of the room to join")
|
|
15
|
+
parser.add_argument("--api-url", default="capslockstep.fastapicloud.dev")
|
|
16
|
+
args = parser.parse_args()
|
|
17
|
+
|
|
18
|
+
match platform.system():
|
|
19
|
+
case "Linux":
|
|
20
|
+
caps_lock = CapsLockLinux()
|
|
21
|
+
case _:
|
|
22
|
+
raise NotImplementedError(f"Unsupported system: {platform.system()}")
|
|
23
|
+
|
|
24
|
+
asyncio.run(stay_lock_step(caps_lock, args.api_url, args.room_id))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def stay_lock_step(caps_lock: CapsLock, api_url: str, room_id: str) -> None:
|
|
28
|
+
async with aiohttp.ClientSession() as session:
|
|
29
|
+
async with session.ws_connect(f"wss://{api_url}/caps-lock/{room_id}") as ws:
|
|
30
|
+
|
|
31
|
+
async def writer():
|
|
32
|
+
with suppress(asyncio.CancelledError):
|
|
33
|
+
async for new_value in caps_lock.watch():
|
|
34
|
+
event = CapsLockEvent(value=new_value)
|
|
35
|
+
await ws.send_str(event.model_dump_json())
|
|
36
|
+
|
|
37
|
+
writer_task = asyncio.create_task(writer())
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
async for message in ws:
|
|
41
|
+
if message.type == aiohttp.WSMsgType.TEXT:
|
|
42
|
+
serialized_state = message.data
|
|
43
|
+
state = CapsLockState.model_validate_json(serialized_state)
|
|
44
|
+
caps_lock.set(state.value)
|
|
45
|
+
except asyncio.CancelledError:
|
|
46
|
+
pass
|
|
47
|
+
finally:
|
|
48
|
+
writer_task.cancel()
|
|
49
|
+
await writer_task
|
capslockstep/key.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from collections.abc import AsyncGenerator
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import libevdev
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CapsLock(ABC):
|
|
10
|
+
@abstractmethod
|
|
11
|
+
def watch(self) -> AsyncGenerator[bool]:
|
|
12
|
+
"""Watch for changes to the Caps Lock key state"""
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def set(self, value: bool) -> None:
|
|
16
|
+
"""Set the state of the Caps Lock key to the given value."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CapsLockLinux(CapsLock):
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
self.dev = libevdev.Device()
|
|
22
|
+
self.dev.name = "Caps Lock Step Device"
|
|
23
|
+
self.dev.enable(libevdev.KEY_CAPSLOCK)
|
|
24
|
+
self.uinput = self.dev.create_uinput_device()
|
|
25
|
+
self.old_value = self.get_current_value()
|
|
26
|
+
|
|
27
|
+
async def watch(self) -> AsyncGenerator[bool]:
|
|
28
|
+
while True:
|
|
29
|
+
new_value = self.get_current_value()
|
|
30
|
+
|
|
31
|
+
if new_value != self.old_value:
|
|
32
|
+
yield new_value
|
|
33
|
+
self.old_value = new_value
|
|
34
|
+
|
|
35
|
+
await asyncio.sleep(0.1)
|
|
36
|
+
|
|
37
|
+
def set(self, value: bool) -> None:
|
|
38
|
+
if value != self.get_current_value():
|
|
39
|
+
self.toggle()
|
|
40
|
+
|
|
41
|
+
def toggle(self) -> None:
|
|
42
|
+
self.uinput.send_events(
|
|
43
|
+
[
|
|
44
|
+
libevdev.InputEvent(libevdev.KEY_CAPSLOCK, 1),
|
|
45
|
+
libevdev.InputEvent(libevdev.SYN_REPORT, 0),
|
|
46
|
+
libevdev.InputEvent(libevdev.KEY_CAPSLOCK, 0),
|
|
47
|
+
libevdev.InputEvent(libevdev.SYN_REPORT, 0),
|
|
48
|
+
]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def get_current_value(self) -> bool:
|
|
52
|
+
return any(
|
|
53
|
+
path.read_text().strip() == "1"
|
|
54
|
+
for path in Path("/sys/class/leds").glob("input*::capslock/brightness")
|
|
55
|
+
)
|
capslockstep/models.py
ADDED
capslockstep/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: capslockstep
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author: Jonathan Ehwald
|
|
6
|
+
Author-email: Jonathan Ehwald <github@ehwald.info>
|
|
7
|
+
Requires-Dist: aiohttp>=3.13.3
|
|
8
|
+
Requires-Dist: libevdev>=0.13.1
|
|
9
|
+
Requires-Dist: pydantic>=2.12.5
|
|
10
|
+
Requires-Python: >=3.14
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
capslockstep/__init__.py,sha256=fmP3oPXZUQmBTKVNUnby5jiUHJKvZZ9fOwaWWe_NUG4,58
|
|
2
|
+
capslockstep/cli.py,sha256=6iERETurPy7xZLKn0rfHs5sVf-mHfHn-i3NecMwX2ho,1731
|
|
3
|
+
capslockstep/key.py,sha256=k69BFU_SWniSNrI2xc5qAwyKFy-ZJzcACf-BKGtyaj8,1656
|
|
4
|
+
capslockstep/models.py,sha256=fch_0CvcKEi47kUuYnTIhwWbVOO9ONdlaACp1pOvssg,181
|
|
5
|
+
capslockstep/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
capslockstep-0.1.0.dist-info/WHEEL,sha256=hbX8mDThv1n7VEIpQRy6c2yAFTw4iAQlEC53gDAhHSo,80
|
|
7
|
+
capslockstep-0.1.0.dist-info/entry_points.txt,sha256=HgoXWCLvgoztpIV6rsDwW-WI70TSCVZC8gM5pZ6YHYM,56
|
|
8
|
+
capslockstep-0.1.0.dist-info/METADATA,sha256=f5nH6cyqAKH9paX5t6IF9j1ebIp6XDmnhcu06qooWBA,285
|
|
9
|
+
capslockstep-0.1.0.dist-info/RECORD,,
|