This repository was archived by the owner on Feb 16, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathclient.py
More file actions
188 lines (162 loc) · 5.76 KB
/
client.py
File metadata and controls
188 lines (162 loc) · 5.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
import asyncio
import websockets
import json
from websockets.exceptions import ConnectionClosedError, WebSocketException
import platform
import glob
if platform.system() == "Darwin":
from Quartz import CGEventSourceKeyState, kCGEventSourceStateHIDSystemState
import subprocess
def get_capslock_state():
return bool(CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, 0x39))
# huge thank you to https://github.com/erikpt/caps-lock-shell-script for showing me how to do this
def set_capslock_state(enabled):
script = """
ObjC.import("IOKit");
ObjC.import("CoreServices");
(() => {
var ioConnect = Ref();
var state = Ref();
$.IOServiceOpen(
$.IOServiceGetMatchingService(
$.kIOMasterPortDefault,
$.IOServiceMatching($.kIOHIDSystemClass)
),
$.mach_task_self_,
$.kIOHIDParamConnectType,
ioConnect
);
$.IOHIDSetModifierLockState(ioConnect, $.kIOHIDCapsLockState, %d);
$.IOServiceClose(ioConnect);
})();
""" % (
1 if enabled else 0
)
subprocess.run(["osascript", "-l", "JavaScript", "-e", script])
def check_dependencies():
pass
elif platform.system() == "Windows":
import ctypes
from ctypes import wintypes
user32 = ctypes.WinDLL("user32", use_last_error=True)
KEYEVENTF_EXTENDEDKEY = 0x1
KEYEVENTF_KEYUP = 0x2
VK_CAPITAL = 0x14
CAPSLOCK_SCANCODE = 0x45
user32.GetKeyState.restype = wintypes.SHORT
user32.GetKeyState.argtypes = [wintypes.INT]
user32.keybd_event.argtypes = [
wintypes.BYTE,
wintypes.BYTE,
wintypes.DWORD,
wintypes.ULONG,
]
user32.keybd_event.restype = None
def get_capslock_state():
return bool(user32.GetKeyState(VK_CAPITAL) & 1)
def toggle_capslock():
user32.keybd_event(VK_CAPITAL, CAPSLOCK_SCANCODE, KEYEVENTF_EXTENDEDKEY, 0)
user32.keybd_event(
VK_CAPITAL, CAPSLOCK_SCANCODE, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0
)
def set_capslock_state(enabled):
current = get_capslock_state()
if current != enabled:
toggle_capslock()
def check_dependencies():
pass
elif platform.system().lower().startswith("linux"):
import shutil
import subprocess
def check_dependencies():
commands = ["xdotool"]
missing = []
for c in commands:
if shutil.which(c) is None:
missing.append(c)
if missing:
l = ", ".join(missing)
raise NotImplementedError(f"Missing dependencies ({l}) - install xdotool using your package manager")
def get_capslock_state():
pattern = "/sys/class/leds/input*::capslock/brightness"
files = glob.glob(pattern)
if len(files) == 0:
raise RuntimeError(f"Could not find anything matching {pattern} to check caps lock status!")
with open(files[0]) as f:
return f.read().strip() == "1"
def set_capslock_state(enabled):
state = get_capslock_state()
if state != enabled:
subprocess.run(["xdotool", "key", "Caps_Lock+Caps_Lock"], check=True)
else:
plat = platform.system()
raise NotImplementedError(f"Unsupported platform: {plat}")
async def get_latest_message(websocket):
count = 0
max_count = 50
last = None
while True:
if count >= max_count:
return last
try:
last = await asyncio.wait_for(websocket.recv(), timeout=0.005)
except asyncio.TimeoutError:
return last
count += 1
async def run_client():
#uri = "ws://localhost:8000/ws"
uri = "wss://globalcapslock.com/ws"
async with websockets.connect(uri) as websocket:
print("connected")
last_state = False
while True:
current_state = get_capslock_state()
if current_state != last_state:
message = "1" if current_state else "0"
print(f"CHANGED {last_state} => {current_state}")
await websocket.send(message)
last_state = current_state
else:
try:
data = await get_latest_message(websocket)
if data == "1" and current_state == False:
set_capslock_state(True)
current_state = True
last_state = True
elif data == "0" and current_state == True:
set_capslock_state(False)
current_state = False
last_state = False
elif data == "0" or data == "1":
# consistent
pass
elif data is None:
# no update
pass
else:
print(f"ignoring invalid data...")
except asyncio.TimeoutError as e:
pass
try:
await asyncio.sleep(0.05)
except Exception as e:
print(e)
return
async def run_client_loop():
while True:
try:
await run_client()
except (KeyboardInterrupt, asyncio.CancelledError):
print("\nExiting.")
return
except (
OSError,
ConnectionClosedError,
WebSocketException,
ConnectionResetError,
) as e:
print(f"Error talking to server: {e}. Sleeping and trying again...")
await asyncio.sleep(2)
if __name__ == "__main__":
check_dependencies()
asyncio.run(run_client_loop())