From 4dc56ae808b1a351c7abc7f80dfa64371a581b12 Mon Sep 17 00:00:00 2001 From: Thomas Perl Date: Thu, 30 Jul 2026 09:27:06 +0200 Subject: [PATCH 1/6] Unified moved_monitor_wait() for moved --- src/daemon/moved.cpp | 9 +++++++++ src/daemon/moved_monitor.h | 20 +++++++++++++------- src/daemon/moved_monitor_linux.c | 17 +++++++++++++++++ src/daemon/moved_monitor_osx.mm | 19 +++++++++++++++++++ src/daemon/moved_monitor_windows.cpp | 22 +++++++++++++++------- src/psmove_port.h | 2 -- src/psmoveapi.cpp | 12 +----------- src/utils/psmovepair.c | 26 ++------------------------ 8 files changed, 76 insertions(+), 51 deletions(-) diff --git a/src/daemon/moved.cpp b/src/daemon/moved.cpp index 6912cb9f..a255d310 100644 --- a/src/daemon/moved.cpp +++ b/src/daemon/moved.cpp @@ -43,6 +43,12 @@ #include "psmove.h" +#if defined(__APPLE__) +# include +#elif defined(__linux) +# include +#endif + #include struct move_daemon; @@ -167,6 +173,9 @@ main(int argc, char *argv[]) #else while (true) { moved.handle_request(); + if (moved_monitor_wait(monitor, false)) { + moved_monitor_poll(monitor); + } moved.write_reports(); } #endif diff --git a/src/daemon/moved_monitor.h b/src/daemon/moved_monitor.h index c4ffcf3e..2e62f38c 100644 --- a/src/daemon/moved_monitor.h +++ b/src/daemon/moved_monitor.h @@ -37,6 +37,7 @@ extern "C" { #endif #include +#include enum MonitorEvent { EVENT_DEVICE_ADDED, @@ -59,13 +60,18 @@ typedef struct _moved_monitor moved_monitor; ADDAPI moved_monitor * ADDCALL moved_monitor_new(moved_event_callback callback, void *user_data); -#ifdef _WIN32 -// Block until Windows signals a device change or the fallback rescan is due. -// Call moved_monitor_poll() after this returns to process any controller -// additions or removals. -ADDAPI void -ADDCALL moved_monitor_wait(moved_monitor *monitor); -#endif +// If blocking is true: +// Block until the OS signals a device change (or in some cases, a fallback +// mechanism is used, e.g. interval-based polling), and return true. +// +// If blocking is false: +// Will immediately return true if there are outstanding device changes, +// or immediately return false otherwise. +// +// Call moved_monitor_poll() after this returns true to process any +// controller additions or removals. +ADDAPI bool +ADDCALL moved_monitor_wait(moved_monitor *monitor, bool blocking); ADDAPI void ADDCALL moved_monitor_poll(moved_monitor *monitor); diff --git a/src/daemon/moved_monitor_linux.c b/src/daemon/moved_monitor_linux.c index ed5bfa02..0a8f6745 100644 --- a/src/daemon/moved_monitor_linux.c +++ b/src/daemon/moved_monitor_linux.c @@ -34,6 +34,8 @@ #include #include +#include +#include #include "moved_monitor.h" @@ -215,6 +217,21 @@ moved_monitor_poll(moved_monitor *monitor) } } +bool +moved_monitor_wait(moved_monitor *monitor, bool blocking) +{ + psmove_return_val_if_fail(monitor != NULL, false); + + int monitor_fd = moved_monitor_get_fd(monitor); + + struct pollfd pfd; + + pfd.fd = monitor_fd; + pfd.events = POLLIN; + + return (poll(&pfd, 1, blocking ? -1 : 0) > 0); +} + void moved_monitor_free(moved_monitor *monitor) { diff --git a/src/daemon/moved_monitor_osx.mm b/src/daemon/moved_monitor_osx.mm index 3bd13fd4..c3b79199 100644 --- a/src/daemon/moved_monitor_osx.mm +++ b/src/daemon/moved_monitor_osx.mm @@ -41,6 +41,10 @@ #import #import +#include +#include +#include + // Convenience functions copied from hidapi #include "moved_monitor_osx_hidapi.mm" @@ -160,6 +164,21 @@ static void on_device_removal(void *context, IOReturn result, void *sender, IOHI monitor->pump_loop(); } +bool +moved_monitor_wait(moved_monitor *monitor, bool blocking) +{ + psmove_return_val_if_fail(monitor != nullptr, false); + + int monitor_fd = moved_monitor_get_fd(monitor); + + struct pollfd pfd; + + pfd.fd = monitor_fd; + pfd.events = POLLIN; + + return (poll(&pfd, 1, blocking ? -1 : 0) > 0); +} + void moved_monitor_free(moved_monitor *monitor) { diff --git a/src/daemon/moved_monitor_windows.cpp b/src/daemon/moved_monitor_windows.cpp index d6a2eb0a..415b765c 100644 --- a/src/daemon/moved_monitor_windows.cpp +++ b/src/daemon/moved_monitor_windows.cpp @@ -303,14 +303,17 @@ moved_monitor_get_fd(moved_monitor *) return -1; } -void -moved_monitor_wait(moved_monitor *monitor) +bool +moved_monitor_wait(moved_monitor *monitor, bool blocking) { - psmove_return_if_fail(monitor != nullptr); + psmove_return_val_if_fail(monitor != nullptr, false); + + // TODO: This function only returns "true" for now, causing a + // rescan all the time. Eventually implement returning "false". const auto now = GetTickCount64(); if (monitor->rescan_requested.load() || now >= monitor->next_rescan) { - return; + return true; } const auto remaining = monitor->next_rescan - now; @@ -319,18 +322,23 @@ moved_monitor_wait(moved_monitor *monitor) : static_cast(remaining); if (monitor->device_event == nullptr) { - Sleep(timeout); - return; + if (blocking) { + // Simulated blocking based on timeout + Sleep(timeout); + } + return true; } // The notification callback signals the event immediately. The timeout // preserves periodic rescanning if Windows misses a notification. - const auto result = WaitForSingleObject(monitor->device_event, timeout); + const auto result = WaitForSingleObject(monitor->device_event, blocking ? timeout : 0); if (result == WAIT_FAILED) { PSMOVE_WARNING( "Could not wait for Windows device notification (%lu)", static_cast(GetLastError())); } + + return true; } diff --git a/src/psmove_port.h b/src/psmove_port.h index b29a15b3..b262cd13 100644 --- a/src/psmove_port.h +++ b/src/psmove_port.h @@ -35,13 +35,11 @@ # include # include # include -# include #endif #ifdef __linux # include # include -# include #endif #ifdef _WIN32 diff --git a/src/psmoveapi.cpp b/src/psmoveapi.cpp index 0a270bfd..156334d6 100644 --- a/src/psmoveapi.cpp +++ b/src/psmoveapi.cpp @@ -213,19 +213,9 @@ PSMoveAPI::~PSMoveAPI() void PSMoveAPI::update() { - if (moved_monitor_get_fd(monitor) == -1) { + if (moved_monitor_wait(monitor, false)) { moved_monitor_poll(monitor); } -#ifndef _WIN32 - else { - struct pollfd pfd; - pfd.fd = moved_monitor_get_fd(monitor); - pfd.events = POLLIN; - while (poll(&pfd, 1, 0) > 0) { - moved_monitor_poll(monitor); - } - } -#endif for (auto &c: controllers) { c->update_connection_flags(); diff --git a/src/utils/psmovepair.c b/src/utils/psmovepair.c index 16bb4249..d13ae3d4 100644 --- a/src/utils/psmovepair.c +++ b/src/utils/psmovepair.c @@ -106,37 +106,15 @@ on_monitor_update_pair(enum MonitorEvent event, int run_daemon() { -#if defined(_WIN32) moved_monitor *monitor = moved_monitor_new(on_monitor_update_pair, NULL); - while (1) { - moved_monitor_wait(monitor); - moved_monitor_poll(monitor); - } - moved_monitor_free(monitor); -#elif defined(__linux) || defined(__APPLE__) - // TODO: Use a blocking monitor wait here after runtime testing it on - // Linux and macOS. - moved_monitor *monitor = moved_monitor_new(on_monitor_update_pair, NULL); - int monitor_fd = moved_monitor_get_fd(monitor); - struct pollfd pfd; - - pfd.fd = monitor_fd; - pfd.events = POLLIN; - - while (1) { - if (poll(&pfd, 1, 0) > 0) { + while (true) { + if (moved_monitor_wait(monitor, true)) { moved_monitor_poll(monitor); } } moved_monitor_free(monitor); -#else - for(;;) { - psmove_port_sleep_ms(5000); - pair(NULL); - } -#endif return 0; } From ce5a21f35b044ba378c78cb5ae305931362ff3f2 Mon Sep 17 00:00:00 2001 From: Thomas Perl Date: Thu, 30 Jul 2026 10:20:16 +0200 Subject: [PATCH 2/6] Create moved_monitor also on Win32 --- src/daemon/moved.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/daemon/moved.cpp b/src/daemon/moved.cpp index a255d310..0317cee4 100644 --- a/src/daemon/moved.cpp +++ b/src/daemon/moved.cpp @@ -144,9 +144,9 @@ main(int argc, char *argv[]) moved.handle_connection(NULL, NULL); } -#if defined(__linux) || defined(__APPLE__) moved_monitor *monitor = moved_monitor_new(on_monitor_update_moved, &moved); +#if defined(__linux) || defined(__APPLE__) struct pollfd pfd[2]; pfd[0].fd = moved.get_socket(); @@ -169,7 +169,6 @@ main(int argc, char *argv[]) moved.write_reports(); } - moved_monitor_free(monitor); #else while (true) { moved.handle_request(); @@ -180,6 +179,8 @@ main(int argc, char *argv[]) } #endif + moved_monitor_free(monitor); + return 0; } From 1bd0f1107eca83d0c7b073a9ddfc74a25b3a7310 Mon Sep 17 00:00:00 2001 From: Thomas Perl Date: Mon, 3 Aug 2026 21:56:50 +0200 Subject: [PATCH 3/6] Win32: moved_monitor_wait(): Return true when blocking and signalled --- src/daemon/moved_monitor_windows.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/daemon/moved_monitor_windows.cpp b/src/daemon/moved_monitor_windows.cpp index 415b765c..3231d3e7 100644 --- a/src/daemon/moved_monitor_windows.cpp +++ b/src/daemon/moved_monitor_windows.cpp @@ -338,7 +338,7 @@ moved_monitor_wait(moved_monitor *monitor, bool blocking) static_cast(GetLastError())); } - return true; + return (result == WAIT_OBJECT_0); } From ad8a808a0d1fac7c2d203262b1cfa12127783988 Mon Sep 17 00:00:00 2001 From: Thomas Perl Date: Mon, 3 Aug 2026 22:16:37 +0200 Subject: [PATCH 4/6] macOS: Use CFRunLoop when waiting for moved monitor events --- src/daemon/moved_monitor_osx.mm | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/daemon/moved_monitor_osx.mm b/src/daemon/moved_monitor_osx.mm index c3b79199..574522b3 100644 --- a/src/daemon/moved_monitor_osx.mm +++ b/src/daemon/moved_monitor_osx.mm @@ -43,7 +43,6 @@ #include #include -#include // Convenience functions copied from hidapi #include "moved_monitor_osx_hidapi.mm" @@ -95,6 +94,10 @@ void pump_loop() } } + bool has_events() const { + return !events.empty(); + } + private: void make_event(enum MonitorEvent event, IOHIDDeviceRef device) @@ -169,14 +172,27 @@ static void on_device_removal(void *context, IOReturn result, void *sender, IOHI { psmove_return_val_if_fail(monitor != nullptr, false); - int monitor_fd = moved_monitor_get_fd(monitor); - - struct pollfd pfd; + /* Blocking wait for events */ + while (blocking && !monitor->has_events()) { + switch (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 60.0, TRUE)) { + case kCFRunLoopRunFinished: + case kCFRunLoopRunStopped: + return false; + case kCFRunLoopRunTimedOut: + /* Timeout hit, try again */ + break; + case kCFRunLoopRunHandledSource: + /* One source was handled, potentially events */ + break; + } + } - pfd.fd = monitor_fd; - pfd.events = POLLIN; + if (!monitor->has_events()) { + /* Poll once, ignore result, as we peek at has_events() below */ + (void)CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.001, TRUE); + } - return (poll(&pfd, 1, blocking ? -1 : 0) > 0); + return monitor->has_events(); } void From 29f7862fe9cb7841208314060280064e14d9bdf0 Mon Sep 17 00:00:00 2001 From: Thomas Perl Date: Mon, 3 Aug 2026 22:19:19 +0200 Subject: [PATCH 5/6] Remove obsolete comment --- src/daemon/moved_monitor_windows.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/daemon/moved_monitor_windows.cpp b/src/daemon/moved_monitor_windows.cpp index 3231d3e7..007e961f 100644 --- a/src/daemon/moved_monitor_windows.cpp +++ b/src/daemon/moved_monitor_windows.cpp @@ -308,9 +308,6 @@ moved_monitor_wait(moved_monitor *monitor, bool blocking) { psmove_return_val_if_fail(monitor != nullptr, false); - // TODO: This function only returns "true" for now, causing a - // rescan all the time. Eventually implement returning "false". - const auto now = GetTickCount64(); if (monitor->rescan_requested.load() || now >= monitor->next_rescan) { return true; From 0430c426454a2e870d8bada75a5a4e09946aed65 Mon Sep 17 00:00:00 2001 From: Thomas Perl Date: Tue, 4 Aug 2026 09:52:05 +0200 Subject: [PATCH 6/6] Add FIXME comments for port differences --- src/daemon/moved.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/daemon/moved.cpp b/src/daemon/moved.cpp index 6d398534..8dd1b905 100644 --- a/src/daemon/moved.cpp +++ b/src/daemon/moved.cpp @@ -158,6 +158,8 @@ main(int argc, char *argv[]) pfd[0].fd = moved.get_socket(); pfd[0].events = POLLIN; + // FIXME: On macOS, moved_monitor_get_fd() always returns -1, + // so the file descriptor cannot be polled. pfd[1].fd = moved_monitor_get_fd(monitor); pfd[1].events = POLLIN; @@ -177,6 +179,12 @@ main(int argc, char *argv[]) #else while (true) { + // In this fallback case (currently used on Windows), the + // moved.handle_request() function blocks in recvfrom(), + // so any monitor events might only be visible to clients + // once they send UDP requests. In the future, using + // select() from WinSock2 might be an option (or using + // threads and blocking I/O). moved.handle_request(); if (moved_monitor_wait(monitor, false)) { moved_monitor_poll(monitor);