Skip to content

Commit 82bcd62

Browse files
Believe it or not, the mouse works on the Mac, and there's even keyboard shortcuts!
1 parent 3fd49be commit 82bcd62

File tree

2 files changed

+101
-18
lines changed

2 files changed

+101
-18
lines changed

client_win-mac-nix/main.py

+64-18
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,65 @@
44
from dataclasses import dataclass
55
from collections import deque # for storing x, y time series
66
import numpy as np # for smoothing moving average
7-
import ctypes # for windows mouse
7+
import ctypes # for windows mouse POINT struct
88
import socket # udp networking
99
import struct # binary unpacking
10+
# import keyboard # keyboard shortcuts
11+
12+
from pynput import keyboard
13+
14+
15+
16+
17+
# Collect events until released
18+
# with keyboard.Listener(
19+
# on_press=on_press,
20+
# on_release=on_release) as listener:
21+
# listener.join()
22+
23+
# ...or, in a non-blocking fashion:
24+
25+
26+
enabled = True
27+
28+
def toggle():
29+
global enabled
30+
logging.info("\nToggled PhilNav on/off\n")
31+
enabled = not enabled
32+
33+
# https://pynput.readthedocs.io/en/latest/keyboard.html#global-hotkeys
34+
35+
def for_canonical(f):
36+
return lambda k: f(listener.canonical(k))
37+
38+
hotkey = keyboard.HotKey(
39+
[keyboard.Key.shift, keyboard.Key.f7.value],
40+
toggle)
41+
42+
listener = keyboard.Listener(
43+
on_press=for_canonical(hotkey.press),
44+
on_release=for_canonical(hotkey.release))
45+
46+
listener.start()
47+
48+
# keyboard.add_hotkey('space', toggle)
49+
# keyboard.add_hotkey('space', toggle)
50+
51+
# keyboard.write('The quick brown fox jumps over the lazy dog.')
52+
# keyboard.add_hotkey('space', lambda: print('space was pressed!'))
53+
54+
# returned from ctypes.windll.user32.GetCursorPos
55+
# simple point.x, point.y
56+
class POINT(ctypes.Structure):
57+
_fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)]
58+
59+
import platform
1060
# Done: Windows
1161
# TODO: Mac, Linux
62+
match platform.system():
63+
case "Darwin": # macOS
64+
from mouse_mac import getCursorPos, setCursorPos
65+
1266

1367
print("\n\nCLIENT: Starting PhilNav\n")
1468

@@ -43,10 +97,6 @@
4397
logging.info(" Logging verbosely\n")
4498

4599

46-
# returned from ctypes.windll.user32.GetCursorPos
47-
# simple point.x, point.y
48-
class POINT(ctypes.Structure):
49-
_fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)]
50100

51101

52102
# initialize networking
@@ -65,8 +115,7 @@ class POINT(ctypes.Structure):
65115
# would allow discovery, but when you are *in public* - like at a coffee shop -
66116
# you don't want strangers to access your PC.
67117
text_listening = (
68-
f"Listening on {
69-
sock.getsockname()} for mouse data from Raspberry Pi server..."
118+
f"Listening on {sock.getsockname()} for mouse data from Raspberry Pi server..."
70119
)
71120
print(ctime() + " - " + text_listening)
72121
print("\nPress Ctrl-C to exit\n")
@@ -137,16 +186,15 @@ def smooth(q):
137186
y_smooth = y
138187

139188
# The Magic Happens Now! eg. move mouse cursor =P
140-
pt = POINT()
141-
ctypes.windll.user32.GetCursorPos(
142-
ctypes.byref(pt)
143-
) # get current mouse position by reference (C++ thing)
189+
x_cur, y_cur = getCursorPos()
190+
# get current mouse position by reference (C++ thing)
144191
# I'm moving the Y axis slightly faster because looking left and right
145192
# is easier than nodding up and down. Also, monitors are wider than they
146193
# are tall.
147-
x_new = round(pt.x + x_smooth * args.speed)
148-
y_new = round(pt.y + y_smooth * args.speed * 1.25)
149-
ctypes.windll.user32.SetCursorPos(x_new, y_new) # move mouse cursor
194+
x_new = round(x_cur + x_smooth * args.speed)
195+
y_new = round(y_cur + y_smooth * args.speed * 1.25)
196+
if enabled:
197+
setCursorPos(x_new, y_new) # move mouse cursor
150198

151199
# I'm trying to measure the total time from capturing the frame on the
152200
# camera to moving the mouse cursor on my PC. This isn't super accurate.
@@ -161,10 +209,8 @@ def smooth(q):
161209
# display legend every 5 seconds
162210
if PhilNav.debug_num % 5 == 1:
163211
logging.info(
164-
f" {ctime()} - Received: ({'x_diff':>8},{'y_diff':>8},{
165-
'n/a':>8},{'n/a':>8},{'loc ns':>8},{'net ms':>8} )"
212+
f" {ctime()} - Received: ({'x_diff':>8},{'y_diff':>8},{'n/a':>8},{'n/a':>8},{'loc ns':>8},{'net ms':>8} )"
166213
)
167214
logging.info(
168-
f" {ctime()} - Received: ({x:> 8.2f},{y:> 8.2f},{z:> 8.2f},{pitch:> 8.2f},{
169-
(time() - PhilNav.msg_time_start)*1000:> 8.2f},{time_diff_ms:> 8} )"
215+
f" {ctime()} - Received: ({x:> 8.2f},{y:> 8.2f},{z:> 8.2f},{pitch:> 8.2f},{(time() - PhilNav.msg_time_start)*1000:> 8.2f},{time_diff_ms:> 8} )"
170216
)

client_win-mac-nix/mouse_mac.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Quartz
2+
3+
# PhilNav will only move the mouse on the main display. This might not be what you want, but I like it.
4+
Qcgd_origin, Qcgd_size = Quartz.CGDisplayBounds(0)
5+
6+
def getCursorPos():
7+
blankEvent = Quartz.CGEventCreate(None)
8+
x, y = Quartz.CGEventGetLocation(blankEvent)
9+
return x, y
10+
11+
def setCursorPos(x, y):
12+
global Qcgd_origin, Qcgd_size
13+
# https://developer.apple.com/documentation/coregraphics/1454356-cgeventcreatemouseevent
14+
# CGEventType: case mouseMoved = 5
15+
# Ignored. CGMouseButton: case left = 0
16+
17+
# On a Mac, the mouse is allowed to be "out of bounds". I'm keeping it in-bounds.
18+
# https://developer.apple.com/documentation/coregraphics/1456395-cgdisplaybounds
19+
if x < Qcgd_origin.x:
20+
x = Qcgd_origin.x
21+
if y < Qcgd_origin.y:
22+
y = Qcgd_origin.y
23+
if x > Qcgd_size.width:
24+
x = Qcgd_size.width
25+
if y > Qcgd_size.height:
26+
y = Qcgd_size.height
27+
28+
mouseEvent = Quartz.CGEventCreateMouseEvent(None, 5, (x, y), 0)
29+
# CGEventTapLocation: case cghidEventTap = 0
30+
Quartz.CGEventPost(0, mouseEvent)
31+
32+
Quartz.CGAssociateMouseAndMouseCursorPosition(True)
33+
34+
# Two other ways of doing the same thing.
35+
# But these "lock" the physical mouse and don't send events.
36+
# Quartz.CGDisplayMoveCursorToPoint(0, (x, y))
37+
# Quartz.CGWarpMouseCursorPosition((x,y))

0 commit comments

Comments
 (0)