1. Handled WM_SETCURSOR explicitly in the window procedure, setting IDC_HAND
when dragging/hovering and returning 1 to prevent Windows from displaying
an hourglass/wait cursor.
2. Pre-allocated persistent memory DC, DIB section, and buffer pointer in
_init_gdi_resources() instead of allocating and freeing them 60 times/sec
in draw_frame(), eliminating GDI handle churn.
3. Added cleanup of DIB/DC handles and class unregistration in destroy().
Synchronous urllib calls in the 60 FPS main loop caused a 3-second network
connect timeout every 5 seconds whenever ActivityWatch was offline (due to
Windows IPv6/IPv4 localhost resolution). This completely froze the main thread,
stalled the Windows message pump, and triggered Windows Hung-App detection
(hourglass / wait cursor).
ActivityWatchClient now polls in a dedicated daemon worker thread:
- All socket connections and HTTP timeouts occur in the background thread.
- get_current_window_event() reads cached state in 0ms without blocking.
- Fallback to native Win32 window APIs is immediate with zero latency.
The walk polygon was rendered 25% too large due to an erroneous * 1.25
factor on scale_x / scale_y. On top of this, GAIT_SHIFT_X=-6.383 and
GAIT_SHIFT_Y=3.76 (internal cat.js group transforms) were incorrectly
ported as canvas offsets, pushing the polygon partially above the top
edge of the overlay window (visible clipping).
Root cause: the SVG viewBox is 0 0 150 120. Walk polygon coordinates
span 4-145 in X and 8-112 in Y -- already fitting the viewBox with no
additional scaling or translation needed.
Fix: use scale_x = (cat_width / 150) * SS and scale_y = (cat_height / 120) * SS
with zero offset. Walk frame now renders at the same visual size as paw/
scruff/tail/stretch frames (all on a cat_width x cat_height canvas).
Result: cat body is no longer clipped at the top and matches size of all
other animation states.
Split screen boundary usage into two sources:
1. walk / roam / run-to-target (unchanged):
- self.screen_* = WindowManager.get_work_area() (primary monitor, taskbar-aware)
- Cat autonomously roams the primary desktop floor only
2. drag clamping (new):
- self.vd_* = WindowManager.get_virtual_desktop_bounds() (all monitors)
- on_mouse_move() clamps to vd_left/right/top/bottom instead of
screen_left/right/top/bottom
- User can now pick up the cat and drag it to any connected monitor
Previously on_mouse_move() clamped to the primary work area rectangle
(0, 0, 1920, 1032), so moving the cursor past x=1776 would immediately
snap the cat back, making the second monitor unreachable.
_premultiply_bgra() was iterating over every RGBA pixel in a pure-Python
for-loop at 60 FPS (144x110 sprite = ~950k iterations/sec). This was the
primary cause of UI hangs.
New implementation uses numpy:
- np.frombuffer + reshape to view raw bytes as (H*W, 4) uint8 array
- Fancy-index column reorder [2,1,0,3] for R,G,B,A -> B,G,R,A in one op
- float32 alpha / 255 premultiplication via broadcasting
- tobytes() to emit the final DIB data
Roughly 100x faster than the previous loop, freeing the main thread to
sustain 60 FPS without freezing.