diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index e8932579..c3e684a4 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -216,6 +216,7 @@ endif() set(SOURCES ${CMAKE_BINARY_DIR}/version.c + platform/${LG_CLIENT_PLATFORM}/entry.c src/main.c src/core.c src/app.c @@ -397,6 +398,22 @@ target_link_libraries(looking-glass-client set_target_properties(looking-glass-client PROPERTIES LINK_FLAGS "${EXE_FLAGS}") +if(ENABLE_TESTS AND LG_CLIENT_PLATFORM_WINDOWS) + add_library(client_test_version OBJECT + "${CMAKE_BINARY_DIR}/version.c" + ) +endif() + +if(ENABLE_AUDIO) + if(LG_CLIENT_PLATFORM_WINDOWS) + find_package(SampleRate REQUIRED) + set(SAMPLERATE_TARGET SampleRate::samplerate) + else() + pkg_check_modules(SAMPLERATE REQUIRED IMPORTED_TARGET samplerate) + set(SAMPLERATE_TARGET PkgConfig::SAMPLERATE) + endif() +endif() + if(ENABLE_TESTS) include(CTest) enable_testing() @@ -405,13 +422,6 @@ endif() if(ENABLE_AUDIO) target_compile_definitions(looking-glass-client PRIVATE ENABLE_AUDIO) - if(LG_CLIENT_PLATFORM_WINDOWS) - find_package(SampleRate REQUIRED) - set(SAMPLERATE_TARGET SampleRate::samplerate) - else() - pkg_check_modules(SAMPLERATE REQUIRED IMPORTED_TARGET samplerate) - set(SAMPLERATE_TARGET PkgConfig::SAMPLERATE) - endif() target_link_libraries(looking-glass-client ${SAMPLERATE_TARGET} ) diff --git a/client/displayservers/Wayland/wayland.c b/client/displayservers/Wayland/wayland.c index 43b337aa..cdf1ab9c 100644 --- a/client/displayservers/Wayland/wayland.c +++ b/client/displayservers/Wayland/wayland.c @@ -21,11 +21,14 @@ #define _GNU_SOURCE #include "wayland.h" +#include #include #include #include +#include #include #include +#include #include "common/debug.h" #include "common/option.h" @@ -106,12 +109,53 @@ static bool getCompositor(char * dst, size_t size) return true; } +static bool eventSourceWake(void * opaque) +{ + const int fd = *(const int *)opaque; + int result; + do + result = eventfd_write(fd, 1); + while (result < 0 && errno == EINTR); + if (result == 0 || errno == EAGAIN) + return true; + + DEBUG_WARN("Failed to signal the display event wake fd: %s", + strerror(errno)); + return false; +} + static void eventSourceCallback(uint32_t events, void * opaque) { (void)events; + (void)opaque; - const LG_DSEventSource * source = (const LG_DSEventSource *)opaque; - source->callback(source->opaque); + eventfd_t value; + for (;;) + { + if (eventfd_read(wlWm.eventSourceFd, &value) == 0 || errno == EINTR) + continue; + if (errno != EAGAIN) + DEBUG_WARN("Failed to drain the display event wake fd: %s", + strerror(errno)); + break; + } + wlWm.eventSource.dispatch(wlWm.eventSource.opaque); +} + +static void eventSourceFree(void) +{ + if (wlWm.eventSourceSubscribed) + { + wlWm.eventSource.unsubscribe(wlWm.eventSource.opaque); + wlWm.eventSourceSubscribed = false; + } + + if (wlWm.eventSourceFd >= 0) + { + waylandPollUnregister(wlWm.eventSourceFd); + close(wlWm.eventSourceFd); + wlWm.eventSourceFd = -1; + } } static bool waylandInit(const LG_DSInitParams params) @@ -130,6 +174,7 @@ static bool waylandInit(const LG_DSInitParams params) atomic_init(&wlWm.hdrScRGBWhiteLevel, 80); atomic_init(&wlWm.pendingResize, 0); wlWm.resizeEventFd = -1; + wlWm.eventSourceFd = -1; wlWm.eventSource = params.eventSource; wlWm.display = wl_display_connect(NULL); @@ -162,42 +207,90 @@ static bool waylandInit(const LG_DSInitParams params) if (!waylandPollInit()) return false; - if (wlWm.eventSource.fd >= 0 && wlWm.eventSource.callback && - !waylandPollRegister(wlWm.eventSource.fd, eventSourceCallback, - &wlWm.eventSource, EPOLLIN)) + if (lgEventSourceValid(&wlWm.eventSource)) { - DEBUG_ERROR("Failed to register the external event source"); - return false; + wlWm.eventSourceFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (wlWm.eventSourceFd < 0) + { + DEBUG_ERROR("Failed to create the display event wake fd: %s", + strerror(errno)); + return false; + } + + if (!waylandPollRegister(wlWm.eventSourceFd, eventSourceCallback, + NULL, EPOLLIN)) + { + DEBUG_ERROR("Failed to register the display event wake fd"); + close(wlWm.eventSourceFd); + wlWm.eventSourceFd = -1; + return false; + } + + if (!wlWm.eventSource.subscribe(wlWm.eventSource.opaque, + eventSourceWake, &wlWm.eventSourceFd)) + { + DEBUG_ERROR("Failed to subscribe to the display event source"); + eventSourceFree(); + return false; + } + wlWm.eventSourceSubscribed = true; } if (!waylandOutputInit()) + { + eventSourceFree(); return false; + } if (!waylandRegistryInit()) + { + eventSourceFree(); return false; + } if (!waylandColorMgmtInit()) + { + eventSourceFree(); return false; + } if (!waylandActivationInit()) + { + eventSourceFree(); return false; + } if (!waylandIdleInit()) + { + eventSourceFree(); return false; + } if (!waylandPresentationInit()) + { + eventSourceFree(); return false; + } if (!waylandCursorInit()) + { + eventSourceFree(); return false; + } if (!waylandInputInit(params.allowNoInput)) + { + eventSourceFree(); return false; + } wlWm.desktop->setSize(params.w, params.h); if (!waylandWindowInit(params.title, params.appId, params.fullscreen, params.maximize, params.borderless, params.resizable)) + { + eventSourceFree(); return false; + } int width, height; wlWm.desktop->getSize(&width, &height); @@ -206,6 +299,7 @@ static bool waylandInit(const LG_DSInitParams params) waylandScaleMulInt(wlWm.scale, height))) { waylandWindowFree(); + eventSourceFree(); return false; } @@ -218,6 +312,7 @@ static bool waylandInit(const LG_DSInitParams params) if (params.opengl && !waylandOpenGLInit()) { waylandWindowFree(); + eventSourceFree(); return false; } #endif @@ -235,6 +330,7 @@ static void waylandShutdown(void) static void waylandFree(void) { + eventSourceFree(); waylandCBFree(); waylandIdleFree(); waylandPresentationFree(); diff --git a/client/displayservers/Wayland/wayland.h b/client/displayservers/Wayland/wayland.h index d5609a87..ee28ac41 100644 --- a/client/displayservers/Wayland/wayland.h +++ b/client/displayservers/Wayland/wayland.h @@ -283,6 +283,8 @@ struct WaylandDSState unsigned int pollWaiters; int epollFd; int displayFd; + int eventSourceFd; + bool eventSourceSubscribed; LG_DSEventSource eventSource; }; diff --git a/client/displayservers/Windows/windows.c b/client/displayservers/Windows/windows.c index 14e5a7ae..4ce064f5 100644 --- a/client/displayservers/Windows/windows.c +++ b/client/displayservers/Windows/windows.c @@ -70,6 +70,7 @@ enum WindowsMessage WM_LG_ACTIVATE, WM_LG_INHIBIT_IDLE, WM_LG_SET_HANDLER, + WM_LG_EVENT_SOURCE, }; struct WindowsPointMessage @@ -104,6 +105,8 @@ struct WindowsDSState WindowsInput input; + LG_DSEventSource eventSource; + bool eventSourceSubscribed; WindowsMessageHandler messageHandler; void * messageOpaque; @@ -820,12 +823,33 @@ static void setCursorShape(LG_DSPointer pointer) SetCursor(windows.cursors[pointer]); } +static bool eventSourceWake(void * opaque) +{ + const HWND window = (HWND)opaque; + if (PostMessageW(window, WM_LG_EVENT_SOURCE, 0, 0)) + return true; + + DEBUG_WINERROR("Failed to signal the display event source", + GetLastError()); + return false; +} + +static void eventSourceFree(void) +{ + if (!windows.eventSourceSubscribed) + return; + + windows.eventSource.unsubscribe(windows.eventSource.opaque); + windows.eventSourceSubscribed = false; +} + static LRESULT handleCustomMessage(UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_LG_SHUTDOWN: + eventSourceFree(); setKeyboardGrab(false); setPointerGrab(false, false); if (windows.idleInhibited) @@ -894,6 +918,10 @@ static LRESULT handleCustomMessage(UINT message, WPARAM wParam, windows.messageOpaque = handler->opaque; return 0; } + + case WM_LG_EVENT_SOURCE: + windows.eventSource.dispatch(windows.eventSource.opaque); + return 0; } return 0; @@ -902,7 +930,7 @@ static LRESULT handleCustomMessage(UINT message, WPARAM wParam, static LRESULT CALLBACK windowProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam) { - if (message >= WM_LG_SHUTDOWN && message <= WM_LG_SET_HANDLER) + if (message >= WM_LG_SHUTDOWN && message <= WM_LG_EVENT_SOURCE) return handleCustomMessage(message, wParam, lParam); if (windows.dispatchReady && windows.messageHandler) @@ -1309,6 +1337,19 @@ static int eventThread(void * opaque) return 0; } + if (lgEventSourceValid(&windows.eventSource) && + !windows.eventSource.subscribe(windows.eventSource.opaque, + eventSourceWake, windows.window)) + { + DEBUG_ERROR("Failed to subscribe to the display event source"); + DestroyWindow(windows.window); + windows.window = NULL; + UnregisterClassW(LG_WINDOW_CLASS, windows.instance); + lgSignalEvent(windows.initEvent); + return 0; + } + windows.eventSourceSubscribed = lgEventSourceValid(&windows.eventSource); + windows.dispatchReady = true; windows.initOK = true; updateWindowGeometry(); @@ -1325,6 +1366,7 @@ static int eventThread(void * opaque) DispatchMessageW(&message); } + eventSourceFree(); windows.dispatchReady = false; windows.window = NULL; removeKeyboardHook(); @@ -1446,6 +1488,7 @@ static bool windowsInit(const LG_DSInitParams params) windowsInputInit(&windows.input, &inputSink, NULL); windows.instance = GetModuleHandleW(NULL); + windows.eventSource = params.eventSource; windows.title = lgUtf8ToWide(params.title); windows.x = params.x; windows.y = params.y; diff --git a/client/displayservers/X11/x11.c b/client/displayservers/X11/x11.c index bd3662af..a92ee22f 100644 --- a/client/displayservers/X11/x11.c +++ b/client/displayservers/X11/x11.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -1063,12 +1064,27 @@ static bool x11GetProp(LG_DSProperty prop, void *ret) } } +static bool x11EventSourceWake(void * opaque) +{ + const int fd = *(const int *)opaque; + int result; + do + result = eventfd_write(fd, 1); + while (result < 0 && errno == EINTR); + if (result == 0 || errno == EAGAIN) + return true; + + DEBUG_WARN("Failed to signal the display event wake fd: %s", + strerror(errno)); + return false; +} + static int x11EventThread(void * unused) { enum { EPOLL_SOURCE_X11, - EPOLL_SOURCE_EXTERNAL, + EPOLL_SOURCE_EVENT, }; int epollfd = epoll_create1(0); @@ -1091,16 +1107,36 @@ static int x11EventThread(void * unused) return 0; } + int eventFd = -1; + bool eventSubscribed = false; bool pollEventSource = false; - if (x11.eventSource.fd >= 0 && x11.eventSource.callback) + if (lgEventSourceValid(&x11.eventSource)) { - ev.data.u32 = EPOLL_SOURCE_EXTERNAL; - if (epoll_ctl(epollfd, EPOLL_CTL_ADD, - x11.eventSource.fd, &ev) == -1) + eventFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (eventFd < 0) { - DEBUG_WARN("Failed to register the external event source; polling"); + DEBUG_WARN("Failed to create the display event wake fd: %s", + strerror(errno)); pollEventSource = true; } + else + { + ev.data.u32 = EPOLL_SOURCE_EVENT; + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, eventFd, &ev) == -1) + { + DEBUG_WARN("Failed to register the display event wake fd: %s", + strerror(errno)); + pollEventSource = true; + } + else if (!x11.eventSource.subscribe(x11.eventSource.opaque, + x11EventSourceWake, &eventFd)) + { + DEBUG_WARN("Failed to subscribe to the display event source"); + pollEventSource = true; + } + else + eventSubscribed = true; + } } while(app_isRunning()) @@ -1121,17 +1157,28 @@ static int x11EventThread(void * unused) if (errno == EINTR) continue; - close(epollfd); DEBUG_ERROR("epoll_wait failure"); - return 0; + break; } for (int i = 0; i < nfds; ++i) - if (events[i].data.u32 == EPOLL_SOURCE_EXTERNAL) - x11.eventSource.callback(x11.eventSource.opaque); + if (events[i].data.u32 == EPOLL_SOURCE_EVENT) + { + eventfd_t value; + for (;;) + { + if (eventfd_read(eventFd, &value) == 0 || errno == EINTR) + continue; + if (errno != EAGAIN) + DEBUG_WARN("Failed to drain the display event wake fd: %s", + strerror(errno)); + break; + } + x11.eventSource.dispatch(x11.eventSource.opaque); + } if (pollEventSource) - x11.eventSource.callback(x11.eventSource.opaque); + x11.eventSource.dispatch(x11.eventSource.opaque); if (!XPending(x11.display)) { @@ -1310,6 +1357,10 @@ static int x11EventThread(void * unused) } } + if (eventSubscribed) + x11.eventSource.unsubscribe(x11.eventSource.opaque); + if (eventFd >= 0) + close(eventFd); close(epollfd); return 0; } diff --git a/client/include/evdev.h b/client/include/evdev.h index d4073554..a980f2f0 100644 --- a/client/include/evdev.h +++ b/client/include/evdev.h @@ -20,6 +20,8 @@ #include +#include "interface/event_source.h" + /** * initialize configuration options */ @@ -46,14 +48,9 @@ void evdev_free(void); void evdev_setGrab(bool keyboard, bool pointer); /** - * return the event fd used to dispatch captured input + * return the event source used to dispatch captured input */ -int evdev_getEventFD(void); - -/** - * dispatch captured input on the display server's event thread - */ -void evdev_dispatch(void * opaque); +LG_DSEventSource evdev_getEventSource(void); /** * return true if keyboard input should only be processed by evdev diff --git a/client/include/event_source.h b/client/include/event_source.h new file mode 100644 index 00000000..96484d6c --- /dev/null +++ b/client/include/event_source.h @@ -0,0 +1,45 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _H_LG_EVENT_SOURCE_ +#define _H_LG_EVENT_SOURCE_ + +#include "interface/event_source.h" + +#include "common/locking.h" + +typedef struct LGEventSourceState +{ + LG_Lock lock; + LG_DSEventWake callback; + void * callbackOpaque; + LG_DSEventDispatch dispatch; + void * dispatchOpaque; + bool pending; +} +LGEventSourceState; + +bool lgEventSourceInit(LGEventSourceState * state, + LG_DSEventDispatch dispatch, void * dispatchOpaque); +void lgEventSourceFree(LGEventSourceState * state); +LG_DSEventSource lgEventSourceGet(LGEventSourceState * state); +void lgEventSourceNotify(LGEventSourceState * state); + +#endif diff --git a/client/include/font.h b/client/include/font.h index bbf88ef9..c0d3789e 100644 --- a/client/include/font.h +++ b/client/include/font.h @@ -24,7 +24,17 @@ #include #include +struct ImFont; +struct ImFontAtlas; + bool font_isStbTruetypeCompatible( const void * data, size_t size, int fontIndex); +bool font_init(void); +void font_free(void); +char * font_get(const char * fontName); +bool font_addText(const char * text); +bool font_buildAtlas(struct ImFontAtlas * atlas, float size, + struct ImFont ** large); + #endif diff --git a/client/include/interface/displayserver.h b/client/include/interface/displayserver.h index 7890ea5b..ba9755b8 100644 --- a/client/include/interface/displayserver.h +++ b/client/include/interface/displayserver.h @@ -27,6 +27,7 @@ #include "common/types.h" #include "common/debug.h" #include "interface/clipboard.h" +#include "interface/event_source.h" typedef enum LG_DSProperty { @@ -103,18 +104,6 @@ LG_DSWaitFrameResult; #define LG_POINTER_COUNT (LG_POINTER_NOT_ALLOWED + 1) -typedef void (*LG_DSEventCallback)(void * opaque); - -typedef struct LG_DSEventSource -{ - /* The callback must be nonblocking and tolerate spurious calls. The caller - * retains ownership of fd and opaque until display-server shutdown. */ - int fd; - LG_DSEventCallback callback; - void * opaque; -} -LG_DSEventSource; - typedef struct LG_DSInitParams { const char * title; diff --git a/client/include/interface/event_source.h b/client/include/interface/event_source.h new file mode 100644 index 00000000..d4da3bde --- /dev/null +++ b/client/include/interface/event_source.h @@ -0,0 +1,53 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _H_I_EVENT_SOURCE_ +#define _H_I_EVENT_SOURCE_ + +#include + +typedef bool (*LG_DSEventWake)(void * opaque); +typedef bool (*LG_DSEventSubscribe)(void * opaque, + LG_DSEventWake callback, void * callbackOpaque); +typedef void (*LG_DSEventUnsubscribe)(void * opaque); +typedef void (*LG_DSEventDispatch)(void * opaque); + +typedef struct LG_DSEventSource +{ + void * opaque; + LG_DSEventSubscribe subscribe; + LG_DSEventUnsubscribe unsubscribe; + LG_DSEventDispatch dispatch; +} +LG_DSEventSource; + +/* Notifications are coalesced until dispatch. The subscriber wake callback + * must be nonblocking, must not call back into the source, and returns true + * when the wake was delivered. Failed wakes may be retried by a later + * notification. Unsubscribe is synchronous with any wake already in + * progress. */ + +static inline bool lgEventSourceValid(const LG_DSEventSource * source) +{ + return source && source->subscribe && source->unsubscribe && + source->dispatch; +} + +#endif diff --git a/client/include/platform/input.h b/client/include/platform/input.h new file mode 100644 index 00000000..f06da98c --- /dev/null +++ b/client/include/platform/input.h @@ -0,0 +1,37 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _H_LG_PLATFORM_INPUT_ +#define _H_LG_PLATFORM_INPUT_ + +#include "interface/event_source.h" + +#include + +void inputPlatformEarlyInit(void); +bool inputPlatformStart(void); +void inputPlatformStop(void); +void inputPlatformFree(void); +void inputPlatformSetGrab(bool keyboard, bool pointer); +bool inputPlatformIsKeyboardExclusive(void); +bool inputPlatformIsPointerExclusive(void); +LG_DSEventSource inputPlatformGetEventSource(void); + +#endif diff --git a/client/include/platform/process.h b/client/include/platform/process.h new file mode 100644 index 00000000..053eddc8 --- /dev/null +++ b/client/include/platform/process.h @@ -0,0 +1,45 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _H_LG_PLATFORM_PROCESS_ +#define _H_LG_PLATFORM_PROCESS_ + +#include + +enum LG_ProcessPrivilege +{ + LG_PROCESS_PRIVILEGE_NORMAL, + LG_PROCESS_PRIVILEGE_ROOT, + LG_PROCESS_PRIVILEGE_SETUID +}; + +/* Return true when the request was handled. Returning false asks the platform + * to continue with its default forced-termination behavior. */ +typedef bool (*LG_ProcessStopHandler)(void * opaque); + +enum LG_ProcessPrivilege lgProcessGetPrivilege(void); +char * lgProcessGetExecutablePath(const char * argv0); + +bool lgProcessInstallStopHandler(LG_ProcessStopHandler handler, void * opaque); +void lgProcessRemoveStopHandler(void); + +int lgClientMain(int argc, char * argv[]); + +#endif diff --git a/client/platform/Linux/CMakeLists.txt b/client/platform/Linux/CMakeLists.txt index 2a6f9551..5755ea72 100644 --- a/client/platform/Linux/CMakeLists.txt +++ b/client/platform/Linux/CMakeLists.txt @@ -3,15 +3,14 @@ project(client_platform_Linux LANGUAGES C) set(CLIENT_PLATFORM_LINUX_SOURCES ../../src/clipboard_files.c + ../../src/event_source.c ../../src/evdev.c + config.c + font.c + input.c + process.c ) -foreach(source IN ITEMS config.c font.c input.c process.c) - if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${source}") - list(APPEND CLIENT_PLATFORM_LINUX_SOURCES ${source}) - endif() -endforeach() - add_library(client_platform_Linux STATIC ${CLIENT_PLATFORM_LINUX_SOURCES}) target_link_libraries(client_platform_Linux diff --git a/client/platform/Linux/entry.c b/client/platform/Linux/entry.c new file mode 100644 index 00000000..263a3bef --- /dev/null +++ b/client/platform/Linux/entry.c @@ -0,0 +1,26 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "platform/process.h" + +int main(int argc, char * argv[]) +{ + return lgClientMain(argc, argv); +} diff --git a/client/platform/Linux/font.c b/client/platform/Linux/font.c new file mode 100644 index 00000000..f49b2955 --- /dev/null +++ b/client/platform/Linux/font.c @@ -0,0 +1,284 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "font_platform.h" + +#include "common/debug.h" + +#include +#include +#include + +struct FontPlatform +{ + FcConfig * config; + FcPattern * query; + FcPattern * primary; +}; + +static bool font_platformAddPattern(const FcPattern * pattern, + uint32_t * remaining, + size_t * remainingCount, FontPlatformAddFaceFn addFace, void * opaque) +{ + FcChar8 * file; + FcCharSet * charset; + FcBool outline = FcTrue; + int index = 0; + + if (FcPatternGetString(pattern, FC_FILE, 0, &file) != FcResultMatch || + FcPatternGetCharSet(pattern, FC_CHARSET, 0, &charset) != FcResultMatch) + return true; + + if (FcPatternGetBool(pattern, FC_OUTLINE, 0, &outline) == FcResultMatch && + !outline) + return true; + + if (FcPatternGetInteger(pattern, FC_INDEX, 0, &index) == FcResultMatch) + index &= 0xffff; + + if (*remainingCount > SIZE_MAX / sizeof(*remaining)) + return false; + + uint32_t * covered = malloc(*remainingCount * sizeof(*covered)); + if (!covered && *remainingCount) + return false; + + size_t coveredCount = 0; + for (size_t i = 0; i < *remainingCount; ++i) + if (FcCharSetHasChar(charset, remaining[i])) + covered[coveredCount++] = remaining[i]; + + if (!coveredCount) + { + free(covered); + return true; + } + + const FontPlatformFaceResult result = addFace(opaque, + (const char *)file, index, covered, coveredCount); + if (result == FONT_PLATFORM_FACE_ERROR) + { + free(covered); + return false; + } + + if (result == FONT_PLATFORM_FACE_ACCEPT) + { + size_t src = 0; + size_t dst = 0; + size_t coveredAt = 0; + + while (src < *remainingCount) + { + if (coveredAt < coveredCount && + remaining[src] == covered[coveredAt]) + { + ++src; + ++coveredAt; + continue; + } + + remaining[dst++] = remaining[src++]; + } + + *remainingCount = dst; + } + + free(covered); + return true; +} + +bool font_platformInit(FontPlatform ** platform) +{ + if (!platform) + return false; + + *platform = NULL; + + FontPlatform * state = calloc(1, sizeof(*state)); + if (!state) + return false; + + state->config = FcInitLoadConfigAndFonts(); + if (!state->config) + { + DEBUG_ERROR("FcInitLoadConfigAndFonts failed"); + free(state); + return false; + } + + *platform = state; + return true; +} + +void font_platformFree(FontPlatform * platform) +{ + if (!platform) + return; + + if (platform->query) + FcPatternDestroy(platform->query); + if (platform->primary) + FcPatternDestroy(platform->primary); + if (platform->config) + FcConfigDestroy(platform->config); + + free(platform); + FcFini(); +} + +char * font_platformSelect(FontPlatform * platform, const char * fontName) +{ + if (!platform || !fontName) + return NULL; + + FcPattern * pattern = FcNameParse((const FcChar8 *)fontName); + if (!pattern) + { + DEBUG_ERROR("FcNameParse failed"); + return NULL; + } + + FcConfigSubstitute(platform->config, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + FcResult result; + FcPattern * match = FcFontMatch(platform->config, pattern, &result); + if (!match) + { + DEBUG_ERROR("FcFontMatch failed"); + FcPatternDestroy(pattern); + return NULL; + } + + FcChar8 * file; + if (FcPatternGetString(match, FC_FILE, 0, &file) != FcResultMatch) + { + DEBUG_ERROR("Failed to locate the requested font: %s", fontName); + FcPatternDestroy(match); + FcPatternDestroy(pattern); + return NULL; + } + + FcPattern * query = FcPatternDuplicate(pattern); + FcPattern * primary = FcPatternDuplicate(match); + char * resultPath = strdup((const char *)file); + if (!query || !primary || !resultPath) + { + if (query) + FcPatternDestroy(query); + if (primary) + FcPatternDestroy(primary); + free(resultPath); + FcPatternDestroy(match); + FcPatternDestroy(pattern); + return NULL; + } + + if (platform->query) + FcPatternDestroy(platform->query); + if (platform->primary) + FcPatternDestroy(platform->primary); + + platform->query = query; + platform->primary = primary; + + FcPatternDestroy(match); + FcPatternDestroy(pattern); + return resultPath; +} + +bool font_platformResolve(FontPlatform * platform, + const uint32_t * glyphs, size_t glyphCount, + FontPlatformAddFaceFn addFace, void * opaque, size_t * unresolved) +{ + if (!platform || !platform->query || !platform->primary || + (!glyphs && glyphCount) || !addFace || !unresolved || + glyphCount > SIZE_MAX / sizeof(*glyphs)) + return false; + + uint32_t * remaining = malloc(glyphCount * sizeof(*remaining)); + if (!remaining && glyphCount) + return false; + + if (glyphCount) + memcpy(remaining, glyphs, glyphCount * sizeof(*remaining)); + size_t remainingCount = glyphCount; + + if (!font_platformAddPattern(platform->primary, remaining, + &remainingCount, addFace, opaque)) + goto fail; + + if (remainingCount) + { + FcCharSet * charset = FcCharSetCreate(); + FcPattern * query = FcPatternDuplicate(platform->query); + if (!charset || !query) + { + if (charset) + FcCharSetDestroy(charset); + if (query) + FcPatternDestroy(query); + goto fail; + } + + for (size_t i = 0; i < remainingCount; ++i) + if (!FcCharSetAddChar(charset, remaining[i])) + { + FcPatternDestroy(query); + FcCharSetDestroy(charset); + goto fail; + } + + FcPatternDel(query, FC_CHARSET); + if (!FcPatternAddCharSet(query, FC_CHARSET, charset)) + { + FcPatternDestroy(query); + FcCharSetDestroy(charset); + goto fail; + } + + FcResult result; + FcFontSet * fonts = FcFontSort( + platform->config, query, FcFalse, NULL, &result); + FcPatternDestroy(query); + FcCharSetDestroy(charset); + if (!fonts) + goto fail; + + for (int i = 0; i < fonts->nfont && remainingCount; ++i) + if (!font_platformAddPattern(fonts->fonts[i], remaining, + &remainingCount, addFace, opaque)) + { + FcFontSetSortDestroy(fonts); + goto fail; + } + + FcFontSetSortDestroy(fonts); + } + + *unresolved = remainingCount; + free(remaining); + return true; + +fail: + free(remaining); + return false; +} diff --git a/client/platform/Linux/input.c b/client/platform/Linux/input.c new file mode 100644 index 00000000..ef2ada7a --- /dev/null +++ b/client/platform/Linux/input.c @@ -0,0 +1,63 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "platform/input.h" + +#include "evdev.h" + +void inputPlatformEarlyInit(void) +{ + evdev_earlyInit(); +} + +bool inputPlatformStart(void) +{ + return evdev_start(); +} + +void inputPlatformStop(void) +{ + evdev_stop(); +} + +void inputPlatformFree(void) +{ + evdev_free(); +} + +void inputPlatformSetGrab(bool keyboard, bool pointer) +{ + evdev_setGrab(keyboard, pointer); +} + +bool inputPlatformIsKeyboardExclusive(void) +{ + return evdev_isExclusive(); +} + +bool inputPlatformIsPointerExclusive(void) +{ + return evdev_isPointerExclusive(); +} + +LG_DSEventSource inputPlatformGetEventSource(void) +{ + return evdev_getEventSource(); +} diff --git a/client/platform/Linux/process.c b/client/platform/Linux/process.c new file mode 100644 index 00000000..b0e73f6f --- /dev/null +++ b/client/platform/Linux/process.c @@ -0,0 +1,145 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "platform/process.h" + +#include +#include +#include +#include +#include + +struct ProcessState +{ + LG_ProcessStopHandler handler; + void * opaque; + struct sigaction previousInt; + struct sigaction previousTerm; + bool installed; +}; + +static struct ProcessState process; + +static char * duplicateString(const char * value) +{ + if (!value) + return NULL; + + const size_t length = strlen(value) + 1U; + char * result = malloc(length); + if (result) + memcpy(result, value, length); + return result; +} + +static void stopSignalHandler(int signal) +{ + if (process.handler && process.handler(process.opaque)) + return; + + struct sigaction action = { 0 }; + action.sa_handler = SIG_DFL; + sigemptyset(&action.sa_mask); + sigaction(signal, &action, NULL); + raise(signal); +} + +enum LG_ProcessPrivilege lgProcessGetPrivilege(void) +{ + const uid_t user = getuid(); + const uid_t effective = geteuid(); + + if (user == 0) + return LG_PROCESS_PRIVILEGE_ROOT; + + if (user != effective) + return LG_PROCESS_PRIVILEGE_SETUID; + + return LG_PROCESS_PRIVILEGE_NORMAL; +} + +char * lgProcessGetExecutablePath(const char * argv0) +{ + size_t capacity = 256U; + while (capacity <= SIZE_MAX / 2U) + { + char * path = malloc(capacity); + if (!path) + break; + + const ssize_t length = readlink("/proc/self/exe", path, capacity - 1U); + if (length >= 0 && (size_t)length < capacity - 1U) + { + path[length] = '\0'; + return path; + } + + free(path); + if (length < 0) + break; + capacity *= 2U; + } + + return duplicateString(argv0); +} + +bool lgProcessInstallStopHandler(LG_ProcessStopHandler handler, void * opaque) +{ + if (!handler || process.installed) + return false; + + process.handler = handler; + process.opaque = opaque; + + struct sigaction action = { 0 }; + action.sa_handler = stopSignalHandler; + action.sa_flags = SA_RESTART; + sigemptyset(&action.sa_mask); + + if (sigaction(SIGINT, &action, &process.previousInt) < 0) + goto fail; + + if (sigaction(SIGTERM, &action, &process.previousTerm) < 0) + { + sigaction(SIGINT, &process.previousInt, NULL); + goto fail; + } + + process.installed = true; + return true; + +fail: + process.handler = NULL; + process.opaque = NULL; + return false; +} + +void lgProcessRemoveStopHandler(void) +{ + if (!process.installed) + return; + + sigaction(SIGINT , &process.previousInt , NULL); + sigaction(SIGTERM, &process.previousTerm, NULL); + + process.handler = NULL; + process.opaque = NULL; + process.installed = false; +} diff --git a/client/platform/Windows/clipboard_files.c b/client/platform/Windows/clipboard_files.c new file mode 100644 index 00000000..c2aa8975 --- /dev/null +++ b/client/platform/Windows/clipboard_files.c @@ -0,0 +1,182 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "core/clipboard_files.h" +#include "core/clipboard.h" + +bool clipboardFiles_init(void) +{ + return true; +} + +void clipboardFiles_free(void) +{ +} + +bool clipboardFiles_setLocal(const char * mime, + const void * data, size_t size) +{ + (void)mime; + (void)data; + (void)size; + return false; +} + +void clipboardFiles_clearLocal(void) +{ +} + +bool clipboardFiles_getRemote(const char * mime, + char ** data, size_t * size) +{ + (void)mime; + if (data) + *data = NULL; + if (size) + *size = 0; + return false; +} + +uint64_t clipboardFiles_remotePresentationAcquire(void) +{ + return 0; +} + +bool clipboardFiles_getRemotePresentation(uint64_t presentation, + const char * mime, char ** data, size_t * size) +{ + (void)presentation; + (void)mime; + if (data) + *data = NULL; + if (size) + *size = 0; + return false; +} + +void clipboardFiles_remotePresentationDelivered(uint64_t presentation) +{ + (void)presentation; +} + +void clipboardFiles_remotePresentationRelease(uint64_t presentation) +{ + (void)presentation; +} + +bool clipboardFiles_remoteReady(uint64_t dataset) +{ + (void)dataset; + return false; +} + +bool clipboardFiles_remoteOffer(uint64_t dataset) +{ + (void)dataset; + return false; +} + +void clipboardFiles_remoteClear(void) +{ +} + +void clipboardFiles_providerUnavailable(void) +{ +} + +void clipboardFiles_remoteAcquired(uint64_t dataset, + uint64_t acquisition, LG_ClipboardFileError error) +{ + (void)dataset; + (void)acquisition; + (void)error; +} + +LG_ClipboardResult clipboardFiles_remoteDataBegin( + const LG_ClipboardFileRequest * request, uint64_t sizeHint) +{ + (void)request; + (void)sizeHint; + return LG_CLIPBOARD_RESULT_FAILED; +} + +LG_ClipboardResult clipboardFiles_remoteDataChunk( + const LG_ClipboardFileRequest * request, uint64_t responseOffset, + const void * data, size_t size) +{ + (void)request; + (void)responseOffset; + (void)data; + (void)size; + return LG_CLIPBOARD_RESULT_FAILED; +} + +LG_ClipboardResult clipboardFiles_remoteDataEnd( + const LG_ClipboardFileRequest * request, uint64_t finalSize) +{ + (void)request; + (void)finalSize; + return LG_CLIPBOARD_RESULT_FAILED; +} + +void clipboardFiles_remoteCancel(uint64_t dataset, + uint64_t request, LG_ClipboardFileError reason) +{ + (void)dataset; + (void)request; + (void)reason; +} + +void clipboardFiles_localAcquire(uint64_t dataset, + uint64_t acquisition) +{ + clipboard_fileAcquired(dataset, acquisition, + LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED); +} + +void clipboardFiles_localRelease(uint64_t dataset, + uint64_t acquisition) +{ + (void)dataset; + (void)acquisition; +} + +void clipboardFiles_localRequest( + const LG_ClipboardFileRequest * request) +{ + if (!request) + return; + + clipboard_fileCancel(request->dataset, request->request, + LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED); +} + +void clipboardFiles_localCancel(uint64_t dataset, + uint64_t request, LG_ClipboardFileError reason) +{ + (void)dataset; + (void)request; + (void)reason; +} + +void clipboardFiles_localReady(uint64_t request) +{ + (void)request; +} diff --git a/client/platform/Windows/entry.c b/client/platform/Windows/entry.c new file mode 100644 index 00000000..6c89044e --- /dev/null +++ b/client/platform/Windows/entry.c @@ -0,0 +1,78 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "platform/process.h" + +#include "common/win32.h" + +#include +#include +#include + +static void freeArguments(int argc, char * argv[]) +{ + if (!argv) + return; + + for (int i = 0; i < argc; ++i) + free(argv[i]); + free(argv); +} + +int WINAPI WinMain(HINSTANCE instance, HINSTANCE previousInstance, + LPSTR commandLine, int showCommand) +{ + (void)instance; + (void)previousInstance; + (void)commandLine; + (void)showCommand; + + int argc = 0; + wchar_t ** wideArgv = CommandLineToArgvW(GetCommandLineW(), &argc); + if (!wideArgv || argc < 1) + { + if (wideArgv) + LocalFree(wideArgv); + return EXIT_FAILURE; + } + + char ** argv = calloc((size_t)argc + 1U, sizeof(*argv)); + if (!argv) + { + LocalFree(wideArgv); + return EXIT_FAILURE; + } + + for (int i = 0; i < argc; ++i) + { + argv[i] = lgWideToUtf8(wideArgv[i]); + if (!argv[i]) + { + freeArguments(argc, argv); + LocalFree(wideArgv); + return EXIT_FAILURE; + } + } + LocalFree(wideArgv); + + const int result = lgClientMain(argc, argv); + freeArguments(argc, argv); + return result; +} diff --git a/client/platform/Windows/font.c b/client/platform/Windows/font.c new file mode 100644 index 00000000..a44a276e --- /dev/null +++ b/client/platform/Windows/font.c @@ -0,0 +1,459 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define COBJMACROS +#include +#include +#include + +#include "font_platform.h" + +#include "common/debug.h" +#include "common/win32.h" + +#include +#include +#include + +struct FontPlatform +{ + IDWriteFactory * factory; + IDWriteFontCollection * collection; + IDWriteFont * primary; + UINT32 primaryFamily; + bool comInitialized; +}; + +static bool font_platformGetFacePath( + IDWriteFontFace * face, char ** path, int * index) +{ + *path = NULL; + *index = 0; + + UINT32 fileCount = 0; + HRESULT result = IDWriteFontFace_GetFiles(face, &fileCount, NULL); + if (FAILED(result) || fileCount != 1) + return false; + + IDWriteFontFile * file = NULL; + result = IDWriteFontFace_GetFiles(face, &fileCount, &file); + if (FAILED(result) || !file) + return false; + + IDWriteFontFileLoader * loader = NULL; + result = IDWriteFontFile_GetLoader(file, &loader); + if (FAILED(result) || !loader) + { + IDWriteFontFile_Release(file); + return false; + } + + IDWriteLocalFontFileLoader * local = NULL; + result = IDWriteFontFileLoader_QueryInterface(loader, + &IID_IDWriteLocalFontFileLoader, (void **)&local); + if (FAILED(result) || !local) + { + IDWriteFontFileLoader_Release(loader); + IDWriteFontFile_Release(file); + return false; + } + + const void * key; + UINT32 keySize; + result = IDWriteFontFile_GetReferenceKey(file, &key, &keySize); + if (FAILED(result)) + goto done; + + UINT32 pathLength; + result = IDWriteLocalFontFileLoader_GetFilePathLengthFromKey( + local, key, keySize, &pathLength); + if (FAILED(result)) + goto done; + + const UINT32 pathCapacity = pathLength + 1; + if (!pathCapacity) + goto done; + + const size_t pathSize = (size_t)pathCapacity * sizeof(wchar_t); + if (pathSize / sizeof(wchar_t) != pathCapacity) + goto done; + + wchar_t * widePath = malloc(pathSize); + if (!widePath) + goto done; + + result = IDWriteLocalFontFileLoader_GetFilePathFromKey( + local, key, keySize, widePath, pathCapacity); + if (SUCCEEDED(result)) + *path = lgWideToUtf8(widePath); + free(widePath); + + const UINT32 faceIndex = IDWriteFontFace_GetIndex(face); + if (!*path || faceIndex > INT_MAX) + { + free(*path); + *path = NULL; + goto done; + } + + *index = (int)faceIndex; + +done: + IDWriteLocalFontFileLoader_Release(local); + IDWriteFontFileLoader_Release(loader); + IDWriteFontFile_Release(file); + return *path != NULL; +} + +static bool font_platformGetFontPath( + IDWriteFont * font, char ** path, int * index) +{ + IDWriteFontFace * face = NULL; + const HRESULT result = IDWriteFont_CreateFontFace(font, &face); + if (FAILED(result) || !face) + return false; + + const bool success = font_platformGetFacePath(face, path, index); + IDWriteFontFace_Release(face); + return success; +} + +static bool font_platformGetFamilyFont(FontPlatform * platform, + UINT32 familyIndex, IDWriteFont ** font) +{ + *font = NULL; + + IDWriteFontFamily * family = NULL; + HRESULT result = IDWriteFontCollection_GetFontFamily( + platform->collection, familyIndex, &family); + if (FAILED(result) || !family) + return false; + + result = IDWriteFontFamily_GetFirstMatchingFont(family, + DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STRETCH_NORMAL, + DWRITE_FONT_STYLE_NORMAL, font); + IDWriteFontFamily_Release(family); + return SUCCEEDED(result) && *font; +} + +static bool font_platformFindFamily(FontPlatform * platform, + const wchar_t * name, UINT32 * familyIndex) +{ + BOOL exists = FALSE; + const HRESULT result = IDWriteFontCollection_FindFamilyName( + platform->collection, name, familyIndex, &exists); + return SUCCEEDED(result) && exists; +} + +static bool font_platformAddFont(IDWriteFont * font, + uint32_t * remaining, size_t * remainingCount, + FontPlatformAddFaceFn addFace, void * opaque) +{ + if (!*remainingCount) + return true; + + if (*remainingCount > UINT32_MAX || + *remainingCount > SIZE_MAX / sizeof(uint16_t) || + *remainingCount > SIZE_MAX / sizeof(uint32_t)) + return false; + + IDWriteFontFace * face = NULL; + HRESULT result = IDWriteFont_CreateFontFace(font, &face); + if (FAILED(result) || !face) + return true; + + uint16_t * indices = malloc(*remainingCount * sizeof(*indices)); + uint32_t * covered = malloc(*remainingCount * sizeof(*covered)); + if (!indices || !covered) + { + free(indices); + free(covered); + IDWriteFontFace_Release(face); + return false; + } + + result = IDWriteFontFace_GetGlyphIndices(face, remaining, + (UINT32)*remainingCount, indices); + if (FAILED(result)) + { + free(indices); + free(covered); + IDWriteFontFace_Release(face); + return true; + } + + size_t coveredCount = 0; + for (size_t i = 0; i < *remainingCount; ++i) + if (indices[i]) + covered[coveredCount++] = remaining[i]; + free(indices); + + if (!coveredCount) + { + free(covered); + IDWriteFontFace_Release(face); + return true; + } + + char * path; + int index; + if (!font_platformGetFacePath(face, &path, &index)) + { + free(covered); + IDWriteFontFace_Release(face); + return true; + } + + IDWriteFontFace_Release(face); + const FontPlatformFaceResult faceResult = addFace( + opaque, path, index, covered, coveredCount); + free(path); + if (faceResult == FONT_PLATFORM_FACE_ERROR) + { + free(covered); + return false; + } + + if (faceResult == FONT_PLATFORM_FACE_ACCEPT) + { + size_t src = 0; + size_t dst = 0; + size_t coveredAt = 0; + + while (src < *remainingCount) + { + if (coveredAt < coveredCount && + remaining[src] == covered[coveredAt]) + { + ++src; + ++coveredAt; + continue; + } + + remaining[dst++] = remaining[src++]; + } + + *remainingCount = dst; + } + + free(covered); + return true; +} + +bool font_platformInit(FontPlatform ** platform) +{ + if (!platform) + return false; + + *platform = NULL; + + FontPlatform * state = calloc(1, sizeof(*state)); + if (!state) + return false; + + HRESULT result = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (SUCCEEDED(result)) + state->comInitialized = true; + else if (result != RPC_E_CHANGED_MODE) + { + DEBUG_ERROR("CoInitializeEx failed: 0x%08lx", (unsigned long)result); + free(state); + return false; + } + + result = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, + &IID_IDWriteFactory, (IUnknown **)&state->factory); + if (FAILED(result) || !state->factory) + { + DEBUG_ERROR("DWriteCreateFactory failed: 0x%08lx", + (unsigned long)result); + if (state->comInitialized) + CoUninitialize(); + free(state); + return false; + } + + result = IDWriteFactory_GetSystemFontCollection( + state->factory, &state->collection, FALSE); + if (FAILED(result) || !state->collection) + { + DEBUG_ERROR("GetSystemFontCollection failed: 0x%08lx", + (unsigned long)result); + IDWriteFactory_Release(state->factory); + if (state->comInitialized) + CoUninitialize(); + free(state); + return false; + } + + *platform = state; + return true; +} + +void font_platformFree(FontPlatform * platform) +{ + if (!platform) + return; + + if (platform->primary) + IDWriteFont_Release(platform->primary); + if (platform->collection) + IDWriteFontCollection_Release(platform->collection); + if (platform->factory) + IDWriteFactory_Release(platform->factory); + if (platform->comInitialized) + CoUninitialize(); + + free(platform); +} + +char * font_platformSelect(FontPlatform * platform, const char * fontName) +{ + if (!platform || !fontName) + return NULL; + + const size_t familyLength = strcspn(fontName, ":"); + if (!familyLength || familyLength == SIZE_MAX) + return NULL; + + char * familyName = malloc(familyLength + 1); + if (!familyName) + return NULL; + memcpy(familyName, fontName, familyLength); + familyName[familyLength] = '\0'; + + wchar_t * wideName = lgUtf8ToWide(familyName); + free(familyName); + if (!wideName) + return NULL; + + static const wchar_t * fallbackNames[] = + { + L"Consolas", + L"Segoe UI", + }; + + IDWriteFont * selected = NULL; + UINT32 selectedFamily = 0; + char * selectedPath = NULL; + int selectedIndex; + + UINT32 familyIndex; + if (font_platformFindFamily(platform, wideName, &familyIndex) && + font_platformGetFamilyFont(platform, familyIndex, &selected) && + font_platformGetFontPath(selected, &selectedPath, &selectedIndex)) + selectedFamily = familyIndex; + else + { + if (selected) + { + IDWriteFont_Release(selected); + selected = NULL; + } + + for (size_t i = 0; + i < sizeof(fallbackNames) / sizeof(fallbackNames[0]); ++i) + { + if (!font_platformFindFamily( + platform, fallbackNames[i], &familyIndex) || + !font_platformGetFamilyFont(platform, familyIndex, &selected) || + !font_platformGetFontPath(selected, &selectedPath, &selectedIndex)) + { + if (selected) + { + IDWriteFont_Release(selected); + selected = NULL; + } + continue; + } + + selectedFamily = familyIndex; + break; + } + + if (!selected && + IDWriteFontCollection_GetFontFamilyCount(platform->collection) && + font_platformGetFamilyFont(platform, 0, &selected) && + font_platformGetFontPath(selected, &selectedPath, &selectedIndex)) + selectedFamily = 0; + } + + free(wideName); + if (!selected || !selectedPath) + { + if (selected) + IDWriteFont_Release(selected); + DEBUG_ERROR("Failed to locate the requested font: %s", fontName); + return NULL; + } + + if (platform->primary) + IDWriteFont_Release(platform->primary); + platform->primary = selected; + platform->primaryFamily = selectedFamily; + return selectedPath; +} + +bool font_platformResolve(FontPlatform * platform, + const uint32_t * glyphs, size_t glyphCount, + FontPlatformAddFaceFn addFace, void * opaque, size_t * unresolved) +{ + if (!platform || !platform->primary || (!glyphs && glyphCount) || + !addFace || !unresolved || + glyphCount > SIZE_MAX / sizeof(*glyphs)) + return false; + + uint32_t * remaining = malloc(glyphCount * sizeof(*remaining)); + if (!remaining && glyphCount) + return false; + if (glyphCount) + memcpy(remaining, glyphs, glyphCount * sizeof(*remaining)); + + size_t remainingCount = glyphCount; + if (!font_platformAddFont(platform->primary, remaining, + &remainingCount, addFace, opaque)) + goto fail; + + const UINT32 familyCount = IDWriteFontCollection_GetFontFamilyCount( + platform->collection); + for (UINT32 i = 0; i < familyCount && remainingCount; ++i) + { + if (i == platform->primaryFamily) + continue; + + IDWriteFont * font = NULL; + if (!font_platformGetFamilyFont(platform, i, &font)) + continue; + + const bool success = font_platformAddFont( + font, remaining, &remainingCount, addFace, opaque); + IDWriteFont_Release(font); + if (!success) + goto fail; + } + + *unresolved = remainingCount; + free(remaining); + return true; + +fail: + free(remaining); + return false; +} diff --git a/client/platform/Windows/input.c b/client/platform/Windows/input.c new file mode 100644 index 00000000..0d1f57e0 --- /dev/null +++ b/client/platform/Windows/input.c @@ -0,0 +1,59 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "platform/input.h" + +void inputPlatformEarlyInit(void) +{ +} + +bool inputPlatformStart(void) +{ + return false; +} + +void inputPlatformStop(void) +{ +} + +void inputPlatformFree(void) +{ +} + +void inputPlatformSetGrab(bool keyboard, bool pointer) +{ + (void)keyboard; + (void)pointer; +} + +bool inputPlatformIsKeyboardExclusive(void) +{ + return false; +} + +bool inputPlatformIsPointerExclusive(void) +{ + return false; +} + +LG_DSEventSource inputPlatformGetEventSource(void) +{ + return (LG_DSEventSource) { 0 }; +} diff --git a/client/platform/Windows/process.c b/client/platform/Windows/process.c new file mode 100644 index 00000000..43a734c4 --- /dev/null +++ b/client/platform/Windows/process.c @@ -0,0 +1,130 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "platform/process.h" + +#include "common/win32.h" + +#include +#include +#include +#include + +struct ProcessState +{ + LG_ProcessStopHandler handler; + void * opaque; + bool installed; +}; + +static struct ProcessState process; + +static char * duplicateString(const char * value) +{ + if (!value) + return NULL; + + const size_t length = strlen(value) + 1U; + char * result = malloc(length); + if (result) + memcpy(result, value, length); + return result; +} + +static BOOL WINAPI consoleHandler(DWORD type) +{ + switch (type) + { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + if (process.handler && process.handler(process.opaque)) + return TRUE; + return FALSE; + + default: + return FALSE; + } +} + +enum LG_ProcessPrivilege lgProcessGetPrivilege(void) +{ + return LG_PROCESS_PRIVILEGE_NORMAL; +} + +char * lgProcessGetExecutablePath(const char * argv0) +{ + DWORD capacity = MAX_PATH; + while (capacity <= UINT32_MAX / 2U) + { + wchar_t * path = malloc((size_t)capacity * sizeof(*path)); + if (!path) + break; + + const DWORD length = GetModuleFileNameW(NULL, path, capacity); + if (length > 0 && length < capacity) + { + path[length] = L'\0'; + char * result = lgWideToUtf8(path); + free(path); + if (result) + return result; + break; + } + + free(path); + if (!length) + break; + capacity *= 2U; + } + + return duplicateString(argv0); +} + +bool lgProcessInstallStopHandler(LG_ProcessStopHandler handler, void * opaque) +{ + if (!handler || process.installed) + return false; + + process.handler = handler; + process.opaque = opaque; + if (!SetConsoleCtrlHandler(consoleHandler, TRUE)) + { + process.handler = NULL; + process.opaque = NULL; + return false; + } + + process.installed = true; + return true; +} + +void lgProcessRemoveStopHandler(void) +{ + if (!process.installed) + return; + + SetConsoleCtrlHandler(consoleHandler, FALSE); + process.handler = NULL; + process.opaque = NULL; + process.installed = false; +} diff --git a/client/src/app.c b/client/src/app.c index 975ea281..ee693b31 100644 --- a/client/src/app.c +++ b/client/src/app.c @@ -24,7 +24,7 @@ #include "core.h" #include "util.h" #include "render_queue.h" -#include "evdev.h" +#include "platform/input.h" #include "input.h" #include "sw_surface.h" @@ -135,10 +135,10 @@ void app_mouseTrace(const char * file, unsigned int line, if (result < 0) return; - flockfile(stderr); - debug_info(file, line, function, "Mouse %06lu: %s", seq, + debug_lock(); + debug_info(file, line, function, "Mouse %06" PRIu64 ": %s", seq, message); - funlockfile(stderr); + debug_unlock(); } bool app_isCaptureOnlyMode(void) @@ -174,7 +174,7 @@ bool app_isOverlayMode(void) void app_updateCursorPos(double x, double y) { - if (evdev_isPointerExclusive()) + if (inputPlatformIsPointerExclusive()) return; const bool overlay = app_isOverlayMode(); @@ -404,7 +404,7 @@ void app_handleButtonPressInternal(int button) void app_handleButtonPress(int button) { - if (!evdev_isPointerExclusive()) + if (!inputPlatformIsPointerExclusive()) app_handleButtonPressInternal(button); } @@ -433,13 +433,13 @@ void app_handleButtonReleaseInternal(int button) void app_handleButtonRelease(int button) { - if (!evdev_isPointerExclusive()) + if (!inputPlatformIsPointerExclusive()) app_handleButtonReleaseInternal(button); } void app_handleWheelMotion(double motion) { - if (evdev_isPointerExclusive()) + if (inputPlatformIsPointerExclusive()) return; if (app_isOverlayMode()) @@ -576,19 +576,19 @@ void app_handleKeyReleaseInternal(int sc) void app_handleKeyPress(int sc) { - if (!evdev_isExclusive()) + if (!inputPlatformIsKeyboardExclusive()) app_handleKeyPressInternal(sc); } void app_handleKeyRelease(int sc) { - if (!evdev_isExclusive()) + if (!inputPlatformIsKeyboardExclusive()) app_handleKeyReleaseInternal(sc); } void app_handleKeyboardTyped(const char * typed) { - if (evdev_isExclusive()) + if (inputPlatformIsKeyboardExclusive()) return; app_registerImGuiText(typed); @@ -606,7 +606,7 @@ void app_handleKeyboardModifiersInternal( void app_handleKeyboardModifiers(bool ctrl, bool shift, bool alt, bool super) { - if (!evdev_isExclusive()) + if (!inputPlatformIsKeyboardExclusive()) app_handleKeyboardModifiersInternal(ctrl, shift, alt, super); } @@ -652,7 +652,7 @@ void app_handleInputResetInternal(bool keyboard, bool pointer) void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock) { - if (evdev_isExclusive()) + if (inputPlatformIsKeyboardExclusive()) return; if (!lgInput_keyboardLEDs(numLock, capsLock, scrollLock)) @@ -662,7 +662,7 @@ void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock) void app_handleMouseRelative(double normx, double normy, double rawx, double rawy) { - if (evdev_isPointerExclusive()) + if (inputPlatformIsPointerExclusive()) return; MTRACE("rel delta=%.3f,%.3f raw=%.3f,%.3f inWin=%d inView=%d " diff --git a/client/src/core.c b/client/src/core.c index 0c039df0..6580e481 100644 --- a/client/src/core.c +++ b/client/src/core.c @@ -25,7 +25,7 @@ #include "kb.h" #include "message.h" #include "input.h" -#include "evdev.h" +#include "platform/input.h" #include "common/time.h" #include "common/debug.h" @@ -147,7 +147,7 @@ bool core_inputEnabled(void) void core_updateKeyboardLEDsSync(void) { lgInput_setKeyboardLEDsSync( - inputPolicyEnabled() && !evdev_isExclusive()); + inputPolicyEnabled() && !inputPlatformIsKeyboardExclusive()); } void core_updateKeyboardGrab(void) @@ -165,7 +165,7 @@ void core_updateKeyboardGrab(void) const bool active = g_state.focused && !app_isOverlayMode() && (capture || (inputEnabled && (view || automatic))); - evdev_setGrab(active, localActive && g_cursor.grab); + inputPlatformSetGrab(active, localActive && g_cursor.grab); core_updateKeyboardLEDsSync(); if (active) g_state.ds->grabKeyboard(); diff --git a/client/src/evdev.c b/client/src/evdev.c index 41e87176..c315e5cf 100644 --- a/client/src/evdev.c +++ b/client/src/evdev.c @@ -19,6 +19,7 @@ */ #include "evdev.h" +#include "event_source.h" #include #include @@ -181,7 +182,9 @@ struct EvdevState int epoll; int commandEvent; - int outputEvent; + LGEventSourceState eventSource; + bool eventSourceInitialized; + bool started; LGThread * thread; uint64_t observedState; uint64_t nextBarrier; @@ -217,7 +220,6 @@ static struct EvdevState state = { .epoll = -1, .commandEvent = -1, - .outputEvent = -1, }; static struct Option options[] = @@ -521,7 +523,7 @@ static void evdev_availabilityChanged( (void)available; atomic_store_explicit(&state.availabilityDirty, true, memory_order_release); - evdev_signal(state.outputEvent, "output"); + lgEventSourceNotify(&state.eventSource); } static void evdev_signal(int fd, const char * name) @@ -593,7 +595,7 @@ static bool evdev_queueOutput(const EvdevOutput * output) last->motion.y = evdev_addLimited(last->motion.y, output->motion.y, EVDEV_MOTION_LIMIT); LG_UNLOCK(state.outputLock); - evdev_signal(state.outputEvent, "output"); + lgEventSourceNotify(&state.eventSource); return true; } @@ -606,7 +608,7 @@ static bool evdev_queueOutput(const EvdevOutput * output) last->wheel.horizontal = evdev_addLimited(last->wheel.horizontal, output->wheel.horizontal, EVDEV_WHEEL_LIMIT); LG_UNLOCK(state.outputLock); - evdev_signal(state.outputEvent, "output"); + lgEventSourceNotify(&state.eventSource); return true; } } @@ -654,7 +656,7 @@ static bool evdev_queueOutput(const EvdevOutput * output) DEBUG_WARN("evdev output queue overflowed; input state was reset"); } - evdev_signal(state.outputEvent, "output"); + lgEventSourceNotify(&state.eventSource); return !overflow; } @@ -712,7 +714,7 @@ static void evdev_cancelOutput(void) LG_UNLOCK(state.outputLock); evdev_clearForwarded(); - evdev_signal(state.outputEvent, "output"); + lgEventSourceNotify(&state.eventSource); } static void evdev_updateKey(EvdevDevice * device, unsigned int code, @@ -2001,18 +2003,17 @@ static bool evdev_outputCurrent( output->generation == evdev_generation(desiredState, lane); } -int evdev_getEventFD(void) +LG_DSEventSource evdev_getEventSource(void) { - return state.outputEvent; + if (!state.started) + return (LG_DSEventSource) { 0 }; + + return lgEventSourceGet(&state.eventSource); } -void evdev_dispatch(void * opaque) +static void evdev_dispatch(void * opaque) { (void)opaque; - if (state.outputEvent < 0) - return; - - evdev_drainEvent(state.outputEvent); if (!atomic_load_explicit(&state.stop, memory_order_acquire) && atomic_exchange_explicit(&state.availabilityDirty, false, @@ -2129,6 +2130,9 @@ bool evdev_start(void) LG_LOCK_INIT(state.outputLock); state.outputLockInitialized = true; + if (!lgEventSourceInit(&state.eventSource, evdev_dispatch, NULL)) + return false; + state.eventSourceInitialized = true; state.deviceList = strdup(deviceList); if (!state.deviceList) @@ -2192,13 +2196,6 @@ bool evdev_start(void) return false; } - state.outputEvent = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); - if (state.outputEvent < 0) - { - DEBUG_ERROR("Failed to create evdev output event: %s", strerror(errno)); - return false; - } - struct epoll_event commandEvent = { .events = EPOLLIN, @@ -2219,9 +2216,10 @@ bool evdev_start(void) } lgInput_setKeyboardLEDsListener(evdev_keyboardLEDs, NULL); - state.ledsListenerRegistered = true; + state.ledsListenerRegistered = true; lgInput_setAvailabilityListener(evdev_availabilityChanged, NULL); state.availabilityListenerRegistered = true; + state.started = true; return true; } @@ -2241,6 +2239,8 @@ void evdev_stop(void) state.ledsListenerRegistered = false; } + state.started = false; + if (!state.thread) return; @@ -2254,10 +2254,10 @@ void evdev_free(void) { evdev_stop(); - if (state.outputEvent >= 0) + if (state.eventSourceInitialized) { - close(state.outputEvent); - state.outputEvent = -1; + lgEventSourceFree(&state.eventSource); + state.eventSourceInitialized = false; } if (state.commandEvent >= 0) diff --git a/client/src/event_source.c b/client/src/event_source.c new file mode 100644 index 00000000..bc25328c --- /dev/null +++ b/client/src/event_source.c @@ -0,0 +1,115 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "event_source.h" + +#include + +static bool subscribe(void * opaque, LG_DSEventWake callback, + void * callbackOpaque) +{ + LGEventSourceState * state = (LGEventSourceState *)opaque; + if (!callback) + return false; + + LG_LOCK(state->lock); + if (state->callback) + { + LG_UNLOCK(state->lock); + return false; + } + + state->callback = callback; + state->callbackOpaque = callbackOpaque; + if (state->pending && !state->callback(state->callbackOpaque)) + { + state->callback = NULL; + state->callbackOpaque = NULL; + LG_UNLOCK(state->lock); + return false; + } + LG_UNLOCK(state->lock); + return true; +} + +static void unsubscribe(void * opaque) +{ + LGEventSourceState * state = (LGEventSourceState *)opaque; + + LG_LOCK(state->lock); + state->callback = NULL; + state->callbackOpaque = NULL; + LG_UNLOCK(state->lock); +} + +static void dispatch(void * opaque) +{ + LGEventSourceState * state = (LGEventSourceState *)opaque; + + LG_LOCK(state->lock); + state->pending = false; + LG_UNLOCK(state->lock); + state->dispatch(state->dispatchOpaque); +} + +bool lgEventSourceInit(LGEventSourceState * state, + LG_DSEventDispatch dispatchCallback, void * dispatchOpaque) +{ + if (!state || !dispatchCallback) + return false; + + memset(state, 0, sizeof(*state)); + LG_LOCK_INIT(state->lock); + state->dispatch = dispatchCallback; + state->dispatchOpaque = dispatchOpaque; + return true; +} + +void lgEventSourceFree(LGEventSourceState * state) +{ + unsubscribe(state); + LG_LOCK_FREE(state->lock); +} + +LG_DSEventSource lgEventSourceGet(LGEventSourceState * state) +{ + if (!state || !state->dispatch) + return (LG_DSEventSource) { 0 }; + + return (LG_DSEventSource) + { + .opaque = state, + .subscribe = subscribe, + .unsubscribe = unsubscribe, + .dispatch = dispatch, + }; +} + +void lgEventSourceNotify(LGEventSourceState * state) +{ + LG_LOCK(state->lock); + if (!state->pending) + { + state->pending = true; + if (state->callback && !state->callback(state->callbackOpaque)) + state->pending = false; + } + LG_UNLOCK(state->lock); +} diff --git a/client/src/font.c b/client/src/font.c index 9b384f48..1fd04b53 100644 --- a/client/src/font.c +++ b/client/src/font.c @@ -19,14 +19,27 @@ */ #include "font.h" +#include "font_platform.h" + +#include "common/debug.h" +#include "common/filesystem.h" +#include "common/locking.h" + +#include "cimgui.h" #include #include +#include +#include +#include #if defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-function" #endif +#define STBRP_STATIC +#define STB_RECT_PACK_IMPLEMENTATION +#include "repos/gui/cimgui/imgui/imstb_rectpack.h" #define STBTT_STATIC #define STB_TRUETYPE_IMPLEMENTATION #include "repos/gui/cimgui/imgui/imstb_truetype.h" @@ -34,6 +47,34 @@ #pragma GCC diagnostic pop #endif +typedef struct UIFontGlyphSet +{ + uint32_t * data; + size_t count; + size_t capacity; +} +UIFontGlyphSet; + +typedef struct UIFontFace +{ + char * file; + void * data; + size_t dataSize; + int index; + ImVector_ImWchar ranges; +} +UIFontFace; + +static FontPlatform * UIFontPlatform = NULL; +static UIFontGlyphSet UIFontChars = { 0 }; +static UIFontGlyphSet UIFontRequested = { 0 }; +static UIFontGlyphSet UIFontResolved = { 0 }; +static UIFontFace * UIFontFaces = NULL; +static size_t UIFontFaceCount = 0; +static LG_Lock UIFontLock; +static bool UIFontLockInitialized = false; +static bool UIFontSelected = false; + static uint16_t font_readU16(const unsigned char * data) { return (uint16_t)(((uint16_t)data[0] << 8) | data[1]); @@ -54,8 +95,8 @@ static bool font_getSfntOffset(const unsigned char * data, size_t size, if (!data || size < 12 || size > INT_MAX || fontIndex < 0) return false; - size_t fontOffset = 0; - const uint32_t signature = font_readU32(data); + size_t fontOffset = 0; + const uint32_t signature = font_readU32(data); switch (signature) { case 0x00010000: // TrueType 1.0 @@ -68,7 +109,7 @@ static bool font_getSfntOffset(const unsigned char * data, size_t size, case 0x74746366: // ttcf { - const uint32_t version = font_readU32(data + 4); + const uint32_t version = font_readU32(data + 4); const uint32_t fontCount = font_readU32(data + 8); if ((version != 0x00010000 && version != 0x00020000) || (uint32_t)fontIndex >= fontCount || @@ -117,3 +158,445 @@ bool font_isStbTruetypeCompatible( stbtt_fontinfo info; return stbtt_InitFont(&info, data, stbOffset); } + +static size_t uiFontGlyphLowerBound( + const UIFontGlyphSet * glyphs, uint32_t glyph) +{ + size_t low = 0; + size_t high = glyphs->count; + + while (low < high) + { + const size_t middle = low + (high - low) / 2; + if (glyphs->data[middle] < glyph) + low = middle + 1; + else + high = middle; + } + + return low; +} + +static bool uiFontGlyphReserve(UIFontGlyphSet * glyphs, size_t count) +{ + if (count <= glyphs->capacity) + return true; + + if (count > SIZE_MAX / sizeof(*glyphs->data)) + return false; + + size_t capacity = glyphs->capacity ? glyphs->capacity : 128; + while (capacity < count) + { + if (capacity > SIZE_MAX / 2) + { + capacity = count; + break; + } + capacity *= 2; + } + + uint32_t * data = realloc(glyphs->data, capacity * sizeof(*data)); + if (!data) + return false; + + glyphs->data = data; + glyphs->capacity = capacity; + return true; +} + +static bool uiFontGlyphAdd( + UIFontGlyphSet * glyphs, uint32_t glyph, bool * added) +{ + const size_t position = uiFontGlyphLowerBound(glyphs, glyph); + if (position < glyphs->count && glyphs->data[position] == glyph) + { + *added = false; + return true; + } + + if (!uiFontGlyphReserve(glyphs, glyphs->count + 1)) + return false; + + memmove(glyphs->data + position + 1, glyphs->data + position, + (glyphs->count - position) * sizeof(*glyphs->data)); + glyphs->data[position] = glyph; + ++glyphs->count; + *added = true; + return true; +} + +static bool uiFontGlyphHas( + const UIFontGlyphSet * glyphs, uint32_t glyph) +{ + const size_t position = uiFontGlyphLowerBound(glyphs, glyph); + return position < glyphs->count && glyphs->data[position] == glyph; +} + +static void uiFontGlyphFree(UIFontGlyphSet * glyphs) +{ + free(glyphs->data); + glyphs->data = NULL; + glyphs->count = 0; + glyphs->capacity = 0; +} + +static int uiFontGlyphCompare(const void * lhs, const void * rhs) +{ + const uint32_t left = *(const uint32_t *)lhs; + const uint32_t right = *(const uint32_t *)rhs; + return (left > right) - (left < right); +} + +static void uiFontClearFaces(void) +{ + for (size_t i = 0; i < UIFontFaceCount; ++i) + { + free(UIFontFaces[i].file); + free(UIFontFaces[i].data); + ImVector_ImWchar_UnInit(&UIFontFaces[i].ranges); + } + + free(UIFontFaces); + UIFontFaces = NULL; + UIFontFaceCount = 0; + UIFontResolved.count = 0; +} + +static bool uiFontAddRanges(const ImWchar * ranges) +{ + for (; ranges[0]; ranges += 2) + for (uint32_t glyph = ranges[0]; glyph <= (uint32_t)ranges[1]; ++glyph) + { + bool added; + if (!uiFontGlyphAdd(&UIFontChars, glyph, &added)) + return false; + } + + return true; +} + +static bool uiFontBuildRanges(const uint32_t * glyphs, size_t glyphCount, + ImVector_ImWchar * ranges) +{ + ImFontGlyphRangesBuilder * builder = + ImFontGlyphRangesBuilder_ImFontGlyphRangesBuilder(); + if (!builder) + return false; + + for (size_t i = 0; i < glyphCount; ++i) + ImFontGlyphRangesBuilder_AddChar(builder, (ImWchar)glyphs[i]); + + ImVector_ImWchar_Init(ranges); + ImFontGlyphRangesBuilder_BuildRanges(builder, ranges); + ImFontGlyphRangesBuilder_destroy(builder); + if (ranges->Size > 1) + return true; + + ImVector_ImWchar_UnInit(ranges); + return false; +} + +static void * uiFontLoadFile(const char * path, size_t * size) +{ + *size = 0; + + FILE * file = lgOpenFile(path, "rb"); + if (!file) + return NULL; + + if (fseek(file, 0, SEEK_END) != 0) + goto fail; + + const long length = ftell(file); + if (length <= 0 || length > INT_MAX || fseek(file, 0, SEEK_SET) != 0) + goto fail; + + void * data = malloc((size_t)length); + if (!data) + goto fail; + + if (fread(data, 1, (size_t)length, file) != (size_t)length) + { + free(data); + goto fail; + } + + fclose(file); + *size = (size_t)length; + return data; + +fail: + fclose(file); + return NULL; +} + +static FontPlatformFaceResult uiFontAddFace(void * opaque, + const char * file, int index, + const uint32_t * glyphs, size_t glyphCount) +{ + (void)opaque; + + size_t dataSize; + void * data = uiFontLoadFile(file, &dataSize); + if (!data) + { + DEBUG_WARN("Failed to read UI font face: %s", file); + return FONT_PLATFORM_FACE_SKIP; + } + + if (!font_isStbTruetypeCompatible(data, dataSize, index)) + { + DEBUG_WARN("Skipping stb-incompatible UI font face: %s (index %d)", + file, index); + free(data); + return FONT_PLATFORM_FACE_SKIP; + } + + ImVector_ImWchar ranges; + if (!uiFontBuildRanges(glyphs, glyphCount, &ranges)) + { + free(data); + return FONT_PLATFORM_FACE_ERROR; + } + + char * fileCopy = strdup(file); + if (!fileCopy || + !uiFontGlyphReserve( + &UIFontResolved, UIFontResolved.count + glyphCount)) + { + free(fileCopy); + ImVector_ImWchar_UnInit(&ranges); + free(data); + return FONT_PLATFORM_FACE_ERROR; + } + + UIFontFace * faces = realloc(UIFontFaces, + (UIFontFaceCount + 1) * sizeof(*faces)); + if (!faces) + { + free(fileCopy); + ImVector_ImWchar_UnInit(&ranges); + free(data); + return FONT_PLATFORM_FACE_ERROR; + } + UIFontFaces = faces; + + memcpy(UIFontResolved.data + UIFontResolved.count, + glyphs, glyphCount * sizeof(*glyphs)); + UIFontResolved.count += glyphCount; + + UIFontFace * face = &UIFontFaces[UIFontFaceCount++]; + face->file = fileCopy; + face->data = data; + face->dataSize = dataSize; + face->index = index; + face->ranges = ranges; + return FONT_PLATFORM_FACE_ACCEPT; +} + +static bool uiFontBuildFaces(ImFontAtlas * atlas) +{ + uiFontClearFaces(); + + static const ImWchar arrowRanges[] = + { + 0x2190, 0x2193, + 0, + }; + + if (!uiFontAddRanges(ImFontAtlas_GetGlyphRangesDefault(atlas)) || + !uiFontAddRanges(arrowRanges)) + return false; + + size_t unresolved; + if (!font_platformResolve(UIFontPlatform, + UIFontChars.data, UIFontChars.count, uiFontAddFace, NULL, &unresolved)) + { + uiFontClearFaces(); + return false; + } + + if (UIFontResolved.count) + qsort(UIFontResolved.data, UIFontResolved.count, + sizeof(*UIFontResolved.data), uiFontGlyphCompare); + + size_t missingRequested = 0; + for (size_t i = 0; i < UIFontRequested.count; ++i) + if (!uiFontGlyphHas(&UIFontResolved, UIFontRequested.data[i])) + ++missingRequested; + + if (unresolved && missingRequested) + DEBUG_WARN("%u requested UI glyphs have no usable outline font", + (unsigned int)missingRequested); + + return UIFontFaceCount > 0; +} + +static ImFont * uiFontAddSize(ImFontAtlas * atlas, float size) +{ + ImFont * result = NULL; + + for (size_t i = 0; i < UIFontFaceCount; ++i) + { + ImFontConfig * config = ImFontConfig_ImFontConfig(); + if (!config) + return NULL; + + config->MergeMode = result != NULL; + config->FontDataOwnedByAtlas = false; + config->FontNo = UIFontFaces[i].index; + + ImFont * font = ImFontAtlas_AddFontFromMemoryTTF(atlas, + UIFontFaces[i].data, (int)UIFontFaces[i].dataSize, + size, config, UIFontFaces[i].ranges.Data); + ImFontConfig_destroy(config); + + if (!font) + { + DEBUG_WARN("Failed to add UI font face: %s", UIFontFaces[i].file); + continue; + } + + if (!result) + result = font; + } + + return result; +} + +bool font_init(void) +{ + if (UIFontPlatform) + return true; + + FontPlatform * platform; + if (!font_platformInit(&platform)) + return false; + + UIFontPlatform = platform; + LG_LOCK_INIT(UIFontLock); + UIFontLockInitialized = true; + return true; +} + +char * font_get(const char * fontName) +{ + if (!UIFontPlatform || !fontName) + return NULL; + + LG_LOCK(UIFontLock); + char * path = font_platformSelect(UIFontPlatform, fontName); + if (path) + UIFontSelected = true; + LG_UNLOCK(UIFontLock); + return path; +} + +bool font_addText(const char * text) +{ + if (!UIFontPlatform || !text) + return false; + + bool changed = false; + LG_LOCK(UIFontLock); + + const char * pos = text; + while (*pos) + { + unsigned int glyph; + const int length = igImTextCharFromUtf8(&glyph, pos, NULL); + if (length <= 0) + break; + pos += length; + + if (glyph < 0x20 || glyph > IM_UNICODE_CODEPOINT_MAX) + continue; + + bool added; + if (!uiFontGlyphAdd(&UIFontChars, glyph, &added)) + { + DEBUG_ERROR("Failed to record a UI glyph"); + break; + } + changed |= added; + + if (!uiFontGlyphAdd(&UIFontRequested, glyph, &added)) + { + DEBUG_ERROR("Failed to record a requested UI glyph"); + break; + } + } + + LG_UNLOCK(UIFontLock); + return changed; +} + +bool font_buildAtlas(ImFontAtlas * atlas, float size, ImFont ** large) +{ + if (!UIFontPlatform || !UIFontSelected || !atlas || !large || size <= 0.0f) + return false; + + bool result = false; + *large = NULL; + LG_LOCK(UIFontLock); + + ImFontAtlas_Clear(atlas); + if (!uiFontBuildFaces(atlas)) + { + DEBUG_ERROR("Failed to resolve UI fonts"); + goto done; + } + + if (!uiFontAddSize(atlas, size)) + { + DEBUG_ERROR("Failed to add the primary UI font"); + goto done; + } + + *large = uiFontAddSize(atlas, size * 1.3f); + if (!*large) + { + DEBUG_ERROR("Failed to add the large UI font"); + goto done; + } + + result = true; + +done: + if (!result) + { + ImFontAtlas_Clear(atlas); + uiFontClearFaces(); + } + + LG_UNLOCK(UIFontLock); + return result; +} + +void font_free(void) +{ + if (!UIFontPlatform) + return; + + if (UIFontLockInitialized) + LG_LOCK(UIFontLock); + + uiFontClearFaces(); + uiFontGlyphFree(&UIFontChars); + uiFontGlyphFree(&UIFontRequested); + uiFontGlyphFree(&UIFontResolved); + + FontPlatform * platform = UIFontPlatform; + UIFontPlatform = NULL; + UIFontSelected = false; + + if (UIFontLockInitialized) + { + LG_UNLOCK(UIFontLock); + LG_LOCK_FREE(UIFontLock); + UIFontLockInitialized = false; + } + + font_platformFree(platform); +} diff --git a/client/src/font_platform.h b/client/src/font_platform.h new file mode 100644 index 00000000..e2c4d603 --- /dev/null +++ b/client/src/font_platform.h @@ -0,0 +1,51 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _H_LG_FONT_PLATFORM_ +#define _H_LG_FONT_PLATFORM_ + +#include +#include +#include + +typedef struct FontPlatform FontPlatform; + +typedef enum FontPlatformFaceResult +{ + FONT_PLATFORM_FACE_ERROR, + FONT_PLATFORM_FACE_SKIP, + FONT_PLATFORM_FACE_ACCEPT, +} +FontPlatformFaceResult; + +typedef FontPlatformFaceResult (*FontPlatformAddFaceFn)( + void * opaque, const char * file, int index, + const uint32_t * glyphs, size_t glyphCount); + +bool font_platformInit(FontPlatform ** platform); +void font_platformFree(FontPlatform * platform); + +char * font_platformSelect(FontPlatform * platform, const char * fontName); + +bool font_platformResolve(FontPlatform * platform, + const uint32_t * glyphs, size_t glyphCount, + FontPlatformAddFaceFn addFace, void * opaque, size_t * unresolved); + +#endif diff --git a/client/src/main.c b/client/src/main.c index 93fa7473..bd9b5da1 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -21,21 +21,13 @@ #include "main.h" #include "config.h" -#include -#include #include #include -#include -#include -#include -#include -#include #include #include #include #include #include -#include #include #include "common/array.h" @@ -67,9 +59,10 @@ #include "overlay_utils.h" #include "util.h" #include "render_queue.h" -#include "evdev.h" #include "frame_scheduler.h" #include "input.h" +#include "platform/input.h" +#include "platform/process.h" #include "sw_surface.h" #ifdef ENABLE_TESTS @@ -731,7 +724,7 @@ static void preSwapCallback(void * udata) if (!written) DEBUG_ERROR("Failed to write test capture to: %s", l_testCapture.path); else - DEBUG_INFO("Wrote test capture for frame %lu to: %s", + DEBUG_INFO("Wrote test capture for frame %" PRIu64 " to: %s", serial, l_testCapture.path); app_setState(APP_STATE_SHUTDOWN); #endif @@ -2734,26 +2727,20 @@ static void fallbackHandleEvents(void) "SPICE fallback services will not function until this is corrected."); } -void intHandler(int sig) +static bool processStopHandler(void * opaque) { - switch(sig) + (void)opaque; + + if (app_getState() != APP_STATE_SHUTDOWN) { - case SIGINT: - case SIGTERM: - if (app_getState() != APP_STATE_SHUTDOWN) - { - DEBUG_INFO("Caught signal, shutting down..."); - app_setState(APP_STATE_SHUTDOWN); - } - else - { - DEBUG_INFO("Caught second signal, force quitting..."); - printAllThreadBacktraces(); - signal(sig, SIG_DFL); - raise(sig); - } - break; + DEBUG_INFO("Caught stop request, shutting down..."); + app_setState(APP_STATE_SHUTDOWN); + return true; } + + DEBUG_INFO("Caught second stop request, force quitting..."); + printAllThreadBacktraces(); + return false; } static bool tryRenderer(const int index, const LG_RendererParams lgrParams, @@ -3625,13 +3612,11 @@ static int lg_run(void) return -1; } - if (evdev_start()) - DEBUG_INFO("Using evdev for input capture"); + if (inputPlatformStart()) + DEBUG_INFO("Using platform input capture"); - // override the SIGINIT handler so that we can tell the difference between - // SIGINT and the user sending a close event, such as ALT+F4 - signal(SIGINT , intHandler); - signal(SIGTERM, intHandler); + if (!lgProcessInstallStopHandler(processStopHandler, NULL)) + DEBUG_WARN("Failed to install the process stop handler"); if (!lgTransport_create(g_params.transport, &g_state.transport)) { @@ -3763,12 +3748,7 @@ static int lg_run(void) .allowNoInput = strcmp(g_params.transport, "test") == 0, .opengl = needsOpenGL, .jitRender = g_params.jitRender, - .eventSource = - { - .fd = evdev_getEventFD(), - .callback = evdev_dispatch, - .opaque = NULL, - }, + .eventSource = inputPlatformGetEventSource(), }; g_state.dsInitialized = g_state.ds->init(params); @@ -4183,7 +4163,7 @@ static void lg_shutdown(void) } // Stop external input callbacks before tearing down shared client state - evdev_stop(); + inputPlatformStop(); if (g_state.ds && g_state.dsInitialized) g_state.ds->shutdown(); @@ -4229,8 +4209,12 @@ static void lg_shutdown(void) e_startup = NULL; } - app_releaseAllKeybinds(); - ll_free(g_state.bindings); + if (g_state.bindings) + { + app_releaseAllKeybinds(); + ll_free(g_state.bindings); + g_state.bindings = NULL; + } if (g_state.ds && g_state.dsInitialized) { @@ -4242,8 +4226,8 @@ static void lg_shutdown(void) * display server before the filesystem is unmounted. */ clipboard_free(); - // Input callbacks have stopped; the evdev state and overlays can go - evdev_free(); + // Input callbacks have stopped; the platform input state and overlays can go + inputPlatformFree(); if (g_state.overlays) { @@ -4276,21 +4260,25 @@ static void lg_shutdown(void) free(g_state.imGuiIni); } -int main(int argc, char * argv[]) +int lgClientMain(int argc, char * argv[]) { // initialize for DEBUG_* macros debug_init(); - if (getuid() == 0) - { - DEBUG_ERROR("Do not run looking glass as root!"); - return -1; - } + int result = -1; - if (getuid() != geteuid()) + switch (lgProcessGetPrivilege()) { - DEBUG_ERROR("Do not run looking glass as setuid!"); - return -1; + case LG_PROCESS_PRIVILEGE_ROOT: + DEBUG_ERROR("Do not run looking glass as root!"); + return -1; + + case LG_PROCESS_PRIVILEGE_SETUID: + DEBUG_ERROR("Do not run looking glass as setuid!"); + return -1; + + case LG_PROCESS_PRIVILEGE_NORMAL: + break; } lgInitProcessTitle(argc, &argv); @@ -4300,8 +4288,11 @@ int main(int argc, char * argv[]) DEBUG_INFO("Locking Method: " LG_LOCK_MODE); cpuInfo_log(); - if (!installCrashHandler("/proc/self/exe", argv[0])) + char * executable = lgProcessGetExecutablePath(argv[0]); + const char * crashExecutable = executable ? executable : argv[0]; + if (!installCrashHandler(crashExecutable, argv[0])) DEBUG_WARN("Failed to install the crash handler"); + free(executable); lgPathsInit("looking-glass"); config_init(); @@ -4310,11 +4301,16 @@ int main(int argc, char * argv[]) gl_dynProcsInit(); if (!lgMessage_init()) - return -1; + goto cleanup; g_state.bindings = ll_new(); - g_state.overlays = ll_new(); + if (!g_state.bindings || !g_state.overlays) + { + DEBUG_ERROR("Failed to create the application lists"); + goto cleanup; + } + app_registerOverlay(&LGOverlaySplash, NULL); app_registerOverlay(&LGOverlayConfig, NULL); app_registerOverlay(&LGOverlayAlert , NULL); @@ -4335,18 +4331,34 @@ int main(int argc, char * argv[]) if (LG_AudioDevs[i]->earlyInit) LG_AudioDevs[i]->earlyInit(); - evdev_earlyInit(); + inputPlatformEarlyInit(); if (!config_load(argc, argv)) - return -1; + goto cleanup; - const int ret = lg_run(); + result = lg_run(); lg_shutdown(); + +cleanup: + lgProcessRemoveStopHandler(); + + if (g_state.overlays) + { + app_freeOverlays(); + ll_free(g_state.overlays); + g_state.overlays = NULL; + } + + if (g_state.bindings) + { + app_releaseAllKeybinds(); + ll_free(g_state.bindings); + g_state.bindings = NULL; + } + lgMessage_deinit(); - config_free(); - util_freeUIFonts(); cleanupCrashHandler(); - return ret; + return result; } diff --git a/client/src/util.c b/client/src/util.c index 400272cc..a3ee106b 100644 --- a/client/src/util.c +++ b/client/src/util.c @@ -30,253 +30,6 @@ #include #include #include -#include - -struct UIFontFace -{ - char * file; - int index; - ImVector_ImWchar ranges; -}; - -static FcConfig * FontConfig = NULL; -static FcPattern * UIFontQuery = NULL; -static FcPattern * UIFontPrimary = NULL; -static FcCharSet * UIFontChars = NULL; -static FcCharSet * UIFontRequested = NULL; -static struct UIFontFace * UIFontFaces = NULL; -static unsigned int UIFontFaceCount = 0; -static LG_Lock UIFontLock; -static bool UIFontLockInitialized = false; - -static void uiFontClearFaces(void) -{ - for (unsigned int i = 0; i < UIFontFaceCount; ++i) - { - free(UIFontFaces[i].file); - ImVector_ImWchar_UnInit(&UIFontFaces[i].ranges); - } - - free(UIFontFaces); - UIFontFaces = NULL; - UIFontFaceCount = 0; -} - -static void uiFontAddRanges(const ImWchar * ranges) -{ - for (; ranges[0]; ranges += 2) - for (FcChar32 c = ranges[0]; c <= (FcChar32)ranges[1]; ++c) - FcCharSetAddChar(UIFontChars, c); -} - -static bool uiFontCharsetToRanges( - const FcCharSet * charset, ImVector_ImWchar * ranges) -{ - ImFontGlyphRangesBuilder * builder = - ImFontGlyphRangesBuilder_ImFontGlyphRangesBuilder(); - if (!builder) - return false; - - FcChar32 map[FC_CHARSET_MAP_SIZE]; - FcChar32 next; - for (FcChar32 base = FcCharSetFirstPage(charset, map, &next); - base != FC_CHARSET_DONE; - base = FcCharSetNextPage(charset, map, &next)) - { - for (unsigned int i = 0; i < FC_CHARSET_MAP_SIZE; ++i) - for (unsigned int bit = 0; bit < 32; ++bit) - { - if (!(map[i] & (UINT32_C(1) << bit))) - continue; - - const FcChar32 c = base + i * 32 + bit; - if (c >= 0x20 && c <= IM_UNICODE_CODEPOINT_MAX) - ImFontGlyphRangesBuilder_AddChar(builder, (ImWchar)c); - } - } - - ImVector_ImWchar_Init(ranges); - ImFontGlyphRangesBuilder_BuildRanges(builder, ranges); - ImFontGlyphRangesBuilder_destroy(builder); - return ranges->Size > 1; -} - -static bool uiFontAddFace(const FcPattern * pattern, FcCharSet ** remainingPtr) -{ - FcCharSet * remaining = *remainingPtr; - FcChar8 * file; - FcCharSet * charset; - FcBool outline = FcTrue; - int index = 0; - - if (FcPatternGetString(pattern, FC_FILE, 0, &file) != FcResultMatch || - FcPatternGetCharSet(pattern, FC_CHARSET, 0, &charset) != FcResultMatch) - return true; - - if (FcPatternGetBool(pattern, FC_OUTLINE, 0, &outline) == FcResultMatch && - !outline) - return true; - - if (FcPatternGetInteger(pattern, FC_INDEX, 0, &index) == FcResultMatch) - index &= 0xFFFF; - - FcCharSet * covered = FcCharSetIntersect(remaining, charset); - if (!covered) - return false; - - if (FcCharSetCount(covered) == 0) - { - FcCharSetDestroy(covered); - return true; - } - - size_t fontDataSize; - void * fontData = igImFileLoadToMemory( - (const char *)file, "rb", &fontDataSize, 0); - if (!fontData) - { - DEBUG_WARN("Failed to read UI font face: %s", file); - FcCharSetDestroy(covered); - return true; - } - - const bool compatible = font_isStbTruetypeCompatible( - fontData, fontDataSize, index); - igMemFree(fontData); - if (!compatible) - { - DEBUG_WARN("Skipping stb-incompatible UI font face: %s (index %d)", - file, index); - FcCharSetDestroy(covered); - return true; - } - - struct UIFontFace * faces = realloc(UIFontFaces, - sizeof(*UIFontFaces) * (UIFontFaceCount + 1)); - if (!faces) - { - FcCharSetDestroy(covered); - return false; - } - UIFontFaces = faces; - - struct UIFontFace * face = &UIFontFaces[UIFontFaceCount]; - memset(face, 0, sizeof(*face)); - face->file = strdup((const char *)file); - if (!face->file || !uiFontCharsetToRanges(covered, &face->ranges)) - { - free(face->file); - face->file = NULL; - FcCharSetDestroy(covered); - return false; - } - - face->index = index; - - ++UIFontFaceCount; - - FcCharSet * next = FcCharSetSubtract(remaining, covered); - FcCharSetDestroy(covered); - if (!next) - return false; - - FcCharSetDestroy(remaining); - *remainingPtr = next; - return true; -} - -static bool uiFontBuildFaces(ImFontAtlas * atlas) -{ - uiFontClearFaces(); - - uiFontAddRanges(ImFontAtlas_GetGlyphRangesDefault(atlas)); - uiFontAddRanges((ImWchar[]) { - 0x2190, 0x2193, // four directional arrows - 0, - }); - - FcCharSet * remaining = FcCharSetCopy(UIFontChars); - if (!remaining) - return false; - - if (!uiFontAddFace(UIFontPrimary, &remaining)) - goto fail; - - if (FcCharSetCount(remaining) > 0) - { - FcPattern * query = FcPatternDuplicate(UIFontQuery); - if (!query) - goto fail; - - FcPatternDel(query, FC_CHARSET); - FcPatternAddCharSet(query, FC_CHARSET, remaining); - - FcResult result; - FcFontSet * fonts = FcFontSort(FontConfig, query, FcTrue, NULL, &result); - FcPatternDestroy(query); - if (!fonts) - goto fail; - - for (int i = 0; i < fonts->nfont && FcCharSetCount(remaining) > 0; ++i) - if (!uiFontAddFace(fonts->fonts[i], &remaining)) - { - FcFontSetSortDestroy(fonts); - goto fail; - } - - FcFontSetSortDestroy(fonts); - } - - if (FcCharSetCount(remaining) > 0) - { - FcCharSet * missing = FcCharSetIntersect(remaining, UIFontRequested); - if (missing) - { - if (FcCharSetCount(missing) > 0) - DEBUG_WARN("%u requested UI glyphs have no usable outline font", - FcCharSetCount(missing)); - FcCharSetDestroy(missing); - } - } - - FcCharSetDestroy(remaining); - return UIFontFaceCount > 0; - -fail: - FcCharSetDestroy(remaining); - uiFontClearFaces(); - return false; -} - -static ImFont * uiFontAddSize(ImFontAtlas * atlas, float size) -{ - ImFont * result = NULL; - - for (unsigned int i = 0; i < UIFontFaceCount; ++i) - { - ImFontConfig * config = ImFontConfig_ImFontConfig(); - if (!config) - return NULL; - - config->MergeMode = result != NULL; - config->FontNo = UIFontFaces[i].index; - - ImFont * font = ImFontAtlas_AddFontFromFileTTF(atlas, - UIFontFaces[i].file, size, config, UIFontFaces[i].ranges.Data); - ImFontConfig_destroy(config); - - if (!font) - { - DEBUG_WARN("Failed to add UI font face: %s", UIFontFaces[i].file); - continue; - } - - if (!result) - result = font; - } - - return result; -} bool util_fileGetContents(const char * filename, char ** buffer, size_t * length) { @@ -473,177 +226,26 @@ bool util_hasGLExt(const char * exts, const char * ext) bool util_initUIFonts(void) { - if (FontConfig) - return true; - - FontConfig = FcInitLoadConfigAndFonts(); - - if (!FontConfig) - { - DEBUG_ERROR("FcInitLoadConfigAndFonts Failed"); - return false; - } - - UIFontChars = FcCharSetCreate(); - UIFontRequested = FcCharSetCreate(); - if (!UIFontChars || !UIFontRequested) - { - DEBUG_ERROR("FcCharSetCreate Failed"); - if (UIFontChars) - FcCharSetDestroy(UIFontChars); - if (UIFontRequested) - FcCharSetDestroy(UIFontRequested); - UIFontChars = NULL; - UIFontRequested = NULL; - FcConfigDestroy(FontConfig); - FontConfig = NULL; - FcFini(); - return false; - } - - LG_LOCK_INIT(UIFontLock); - UIFontLockInitialized = true; - return true; + return font_init(); } char * util_getUIFont(const char * fontName) { - char * ttf = NULL; - - FcPattern * pat = FcNameParse((const FcChar8*) fontName); - if (!pat) - { - DEBUG_ERROR("FCNameParse failed"); - return NULL; - } - - FcConfigSubstitute(FontConfig, pat, FcMatchPattern); - FcDefaultSubstitute(pat); - FcResult result; - FcChar8 * file = NULL; - FcPattern * match = FcFontMatch(FontConfig, pat, &result); - - if (!match) - { - DEBUG_ERROR("FcFontMatch Failed"); - goto fail_parse; - } - - FcPatternDestroy(UIFontQuery); - FcPatternDestroy(UIFontPrimary); - UIFontQuery = FcPatternDuplicate(pat); - UIFontPrimary = FcPatternDuplicate(match); - if (!UIFontQuery || !UIFontPrimary) - { - DEBUG_ERROR("Failed to save the UI font pattern"); - goto fail_match; - } - - if (FcPatternGetString(match, FC_FILE, 0, &file) == FcResultMatch) - ttf = strdup((char *) file); - else - DEBUG_ERROR("Failed to locate the requested font: %s", fontName); - -fail_match: - FcPatternDestroy(match); - -fail_parse: - FcPatternDestroy(pat); - return ttf; + return font_get(fontName); } bool util_uiFontAddText(const char * text) { - if (!UIFontChars || !text) - return false; - - bool changed = false; - LG_LOCK(UIFontLock); - - const char * pos = text; - while (*pos) - { - unsigned int c; - const int length = igImTextCharFromUtf8(&c, pos, NULL); - if (length <= 0) - break; - pos += length; - - if (c < 0x20 || c > IM_UNICODE_CODEPOINT_MAX) - continue; - - FcCharSetAddChar(UIFontRequested, c); - if (!FcCharSetHasChar(UIFontChars, c) && - FcCharSetAddChar(UIFontChars, c)) - changed = true; - } - - LG_UNLOCK(UIFontLock); - return changed; + return font_addText(text); } bool util_buildUIFontAtlas( ImFontAtlas * atlas, float size, ImFont ** large) { - if (!atlas || !UIFontQuery || !UIFontPrimary || !UIFontChars) - return false; - - bool result = false; - LG_LOCK(UIFontLock); - - ImFontAtlas_Clear(atlas); - if (!uiFontBuildFaces(atlas)) - { - DEBUG_ERROR("Failed to resolve UI fonts"); - goto done; - } - - if (!uiFontAddSize(atlas, size)) - { - DEBUG_ERROR("Failed to add the primary UI font"); - goto done; - } - - *large = uiFontAddSize(atlas, size * 1.3f); - if (!*large) - { - DEBUG_ERROR("Failed to add the large UI font"); - goto done; - } - - result = true; - -done: - LG_UNLOCK(UIFontLock); - return result; + return font_buildAtlas(atlas, size, large); } void util_freeUIFonts(void) { - if (!FontConfig) - return; - - if (UIFontLockInitialized) - LG_LOCK(UIFontLock); - - uiFontClearFaces(); - FcCharSetDestroy(UIFontChars); - FcCharSetDestroy(UIFontRequested); - FcPatternDestroy(UIFontQuery); - FcPatternDestroy(UIFontPrimary); - UIFontChars = NULL; - UIFontRequested = NULL; - UIFontQuery = NULL; - UIFontPrimary = NULL; - - if (UIFontLockInitialized) - { - LG_UNLOCK(UIFontLock); - LG_LOCK_FREE(UIFontLock); - UIFontLockInitialized = false; - } - - FcConfigDestroy(FontConfig); - FontConfig = NULL; - FcFini(); + font_free(); } diff --git a/client/tests/CMakeLists.txt b/client/tests/CMakeLists.txt index c7a451ec..0533e12e 100644 --- a/client/tests/CMakeLists.txt +++ b/client/tests/CMakeLists.txt @@ -1,5 +1,12 @@ cmake_minimum_required(VERSION 3.10) +if(LG_CLIENT_PLATFORM_WINDOWS) + link_libraries( + $ + lg_egl_provider + ) +endif() + add_executable(common-platform-tests common_platform_test.c ) @@ -17,6 +24,38 @@ set_tests_properties(common-platform-tests PROPERTIES TIMEOUT 10 ) +add_executable(event-source-tests + event_source_test.c + ../src/event_source.c +) +target_include_directories(event-source-tests PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../src" +) +target_link_libraries(event-source-tests + ${EXE_FLAGS} + lg_common +) +add_test(NAME event-source-tests + COMMAND event-source-tests +) +set_tests_properties(event-source-tests PROPERTIES + TIMEOUT 10 +) + +add_executable(windows-clipboard-files-tests + clipboard_files_windows_test.c + ../platform/Windows/clipboard_files.c +) +target_link_libraries(windows-clipboard-files-tests + ${EXE_FLAGS} +) +add_test(NAME windows-clipboard-files-tests + COMMAND windows-clipboard-files-tests +) +set_tests_properties(windows-clipboard-files-tests PROPERTIES + TIMEOUT 10 +) + if(LG_CLIENT_PLATFORM_LINUX) add_executable(config-platform-tests config_platform_test.c @@ -59,6 +98,20 @@ if(LG_CLIENT_PLATFORM_LINUX) set_tests_properties(keycode-tests PROPERTIES TIMEOUT 10 ) + + add_executable(process-tests + process_test.c + ../platform/Linux/process.c + ) + target_link_libraries(process-tests + ${EXE_FLAGS} + ) + add_test(NAME process-tests + COMMAND process-tests + ) + set_tests_properties(process-tests PROPERTIES + TIMEOUT 10 + ) endif() if(LG_CLIENT_PLATFORM_WINDOWS) @@ -76,6 +129,7 @@ endif() add_executable(font-tests font_test.c + font_platform_stub.c ../src/font.c ) @@ -93,10 +147,17 @@ target_link_libraries(sw-surface-tests add_test(NAME sw-surface-tests COMMAND sw-surface-tests) set_tests_properties(sw-surface-tests PROPERTIES TIMEOUT 10) target_compile_definitions(font-tests PRIVATE + CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1 FONT_TEST_FILE="${PROJECT_TOP}/repos/gui/cimgui/imgui/misc/fonts/DroidSans.ttf" ) target_include_directories(font-tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../include" + "${CMAKE_CURRENT_SOURCE_DIR}/../src" +) +target_link_libraries(font-tests + ${EXE_FLAGS} + lg_common + lg_gui ) add_test(NAME font-tests COMMAND font-tests @@ -105,160 +166,164 @@ set_tests_properties(font-tests PROPERTIES TIMEOUT 10 ) -add_executable(lgmp-transport-tests - lgmp_transport_test.c - ../src/kb_hid.c -) -target_link_libraries(lgmp-transport-tests - ${EXE_FLAGS} - transport_LGMP - lg_common - lgmp -) -if(ENABLE_AUDIO) - target_compile_definitions(lgmp-transport-tests PRIVATE - ENABLE_AUDIO - ) -endif() -add_test(NAME lgmp-transport-tests - COMMAND lgmp-transport-tests -) -set_tests_properties(lgmp-transport-tests PROPERTIES - TIMEOUT 10 -) - -add_executable(lgmp-frame-tests - lgmp_frame_test.c - ../src/kb_hid.c -) -target_link_libraries(lgmp-frame-tests - ${EXE_FLAGS} - transport_LGMP - lg_common - lgmp -) -set(LGMP_FRAME_CASES - malformed - serial-damage - lanes - leases - dma -) -foreach(name IN LISTS LGMP_FRAME_CASES) - add_test(NAME lgmp-frame-${name} - COMMAND lgmp-frame-tests ${name} - ) - set_tests_properties(lgmp-frame-${name} PROPERTIES - TIMEOUT 10 - ) -endforeach() - -add_executable(lgmp-input-tests - lgmp_input_test.c - ../transports/LGMP/input.c - ../src/kb_hid.c -) -target_include_directories(lgmp-input-tests PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/../src" -) -target_link_libraries(lgmp-input-tests - ${EXE_FLAGS} - lg_common - lgmp -) -set(LGMP_INPUT_CASES - claim - blocked - restart - pressure - idle -) -foreach(name IN LISTS LGMP_INPUT_CASES) - add_test(NAME lgmp-input-${name} - COMMAND lgmp-input-tests ${name} - ) - set_tests_properties(lgmp-input-${name} PROPERTIES - TIMEOUT 10 - ) -endforeach() - -add_executable(lgmp-clipboard-tests - lgmp_clipboard_test.c - ../transports/LGMP/clipboard.c -) -target_include_directories(lgmp-clipboard-tests PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/../transports/LGMP" -) -target_link_libraries(lgmp-clipboard-tests - ${EXE_FLAGS} - lg_common - lgmp -) -set(LGMP_CLIPBOARD_CASES - claim - offer-request - inbound-stream - outbound-stream - blocked - cancel - restart - process-restart - file-stream - file-limits - file-owner-loss - file-malformed - disconnect - malformed - file-format -) -foreach(name IN LISTS LGMP_CLIPBOARD_CASES) - add_test(NAME lgmp-clipboard-${name} - COMMAND lgmp-clipboard-tests ${name} - ) - set_tests_properties(lgmp-clipboard-${name} PROPERTIES - TIMEOUT 10 - ) -endforeach() - -if(ENABLE_AUDIO) - add_executable(lgmp-audio-tests - lgmp_audio_test.c +if(LG_CLIENT_PLATFORM_LINUX) + add_executable(lgmp-transport-tests + lgmp_transport_test.c ../src/kb_hid.c ) - target_compile_definitions(lgmp-audio-tests PRIVATE - ENABLE_AUDIO - ) - target_include_directories(lgmp-audio-tests PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/../transports/LGMP" - ) - target_link_libraries(lgmp-audio-tests + target_link_libraries(lgmp-transport-tests ${EXE_FLAGS} transport_LGMP lg_common lgmp ) - add_test(NAME lgmp-audio - COMMAND lgmp-audio-tests + if(ENABLE_AUDIO) + target_compile_definitions(lgmp-transport-tests PRIVATE + ENABLE_AUDIO + ) + endif() + add_test(NAME lgmp-transport-tests + COMMAND lgmp-transport-tests ) - set_tests_properties(lgmp-audio PROPERTIES - TIMEOUT 20 + set_tests_properties(lgmp-transport-tests PROPERTIES + TIMEOUT 10 + ) + + add_executable(lgmp-frame-tests + lgmp_frame_test.c + ../src/kb_hid.c + ) + target_link_libraries(lgmp-frame-tests + ${EXE_FLAGS} + transport_LGMP + lg_common + lgmp + ) + set(LGMP_FRAME_CASES + malformed + serial-damage + lanes + leases + dma + ) + foreach(name IN LISTS LGMP_FRAME_CASES) + add_test(NAME lgmp-frame-${name} + COMMAND lgmp-frame-tests ${name} + ) + set_tests_properties(lgmp-frame-${name} PROPERTIES + TIMEOUT 10 + ) + endforeach() + + add_executable(lgmp-input-tests + lgmp_input_test.c + ../transports/LGMP/input.c + ../src/kb_hid.c + ) + target_include_directories(lgmp-input-tests PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../src" + ) + target_link_libraries(lgmp-input-tests + ${EXE_FLAGS} + lg_common + lgmp + ) + set(LGMP_INPUT_CASES + claim + blocked + restart + pressure + idle + ) + foreach(name IN LISTS LGMP_INPUT_CASES) + add_test(NAME lgmp-input-${name} + COMMAND lgmp-input-tests ${name} + ) + set_tests_properties(lgmp-input-${name} PROPERTIES + TIMEOUT 10 + ) + endforeach() + + add_executable(lgmp-clipboard-tests + lgmp_clipboard_test.c + ../transports/LGMP/clipboard.c + ) + target_include_directories(lgmp-clipboard-tests PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../transports/LGMP" + ) + target_link_libraries(lgmp-clipboard-tests + ${EXE_FLAGS} + lg_common + lgmp + ) + set(LGMP_CLIPBOARD_CASES + claim + offer-request + inbound-stream + outbound-stream + blocked + cancel + restart + process-restart + file-stream + file-limits + file-owner-loss + file-malformed + disconnect + malformed + file-format + ) + foreach(name IN LISTS LGMP_CLIPBOARD_CASES) + add_test(NAME lgmp-clipboard-${name} + COMMAND lgmp-clipboard-tests ${name} + ) + set_tests_properties(lgmp-clipboard-${name} PROPERTIES + TIMEOUT 10 + ) + endforeach() + + if(ENABLE_AUDIO) + add_executable(lgmp-audio-tests + lgmp_audio_test.c + ../src/kb_hid.c + ) + target_compile_definitions(lgmp-audio-tests PRIVATE + ENABLE_AUDIO + ) + target_include_directories(lgmp-audio-tests PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../transports/LGMP" + ) + target_link_libraries(lgmp-audio-tests + ${EXE_FLAGS} + transport_LGMP + lg_common + lgmp + ) + add_test(NAME lgmp-audio + COMMAND lgmp-audio-tests + ) + set_tests_properties(lgmp-audio PROPERTIES + TIMEOUT 20 + ) + endif() +endif() + +if(LG_CLIENT_PLATFORM_LINUX) + add_executable(wayland-motion-tests + motion_test.c + ) + target_include_directories(wayland-motion-tests PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../displayservers/Wayland" + ) + add_test(NAME wayland-motion-tests + COMMAND wayland-motion-tests + ) + set_tests_properties(wayland-motion-tests PROPERTIES + TIMEOUT 10 ) endif() -add_executable(wayland-motion-tests - motion_test.c -) -target_include_directories(wayland-motion-tests PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/../displayservers/Wayland" -) -add_test(NAME wayland-motion-tests - COMMAND wayland-motion-tests -) -set_tests_properties(wayland-motion-tests PROPERTIES - TIMEOUT 10 -) - -if(ENABLE_WAYLAND) +if(LG_CLIENT_PLATFORM_LINUX AND ENABLE_WAYLAND) add_executable(wayland-presentation-tests wayland_presentation_test.c ../displayservers/Wayland/presentation.c @@ -339,7 +404,7 @@ if(ENABLE_WAYLAND) endforeach() endif() -if(ENABLE_X11) +if(LG_CLIENT_PLATFORM_LINUX AND ENABLE_X11) add_executable(x11-clipboard-tests x11_clipboard_test.c ../displayservers/X11/clipboard.c @@ -380,54 +445,56 @@ if(ENABLE_X11) endforeach() endif() -add_executable(displayserver-input-tests - displayserver_input_test.c - ../displayservers/Wayland/input_event.c - ../displayservers/X11/input_event.c -) -target_include_directories(displayserver-input-tests PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/../displayservers" -) +if(LG_CLIENT_PLATFORM_LINUX) + add_executable(displayserver-input-tests + displayserver_input_test.c + ../displayservers/Wayland/input_event.c + ../displayservers/X11/input_event.c + ) + target_include_directories(displayserver-input-tests PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../displayservers" + ) -set(DISPLAYSERVER_INPUT_CASES - pointer-events - pointer-buttons - pointer-release - wheel-steps - relative-motion - keyboard-focus - keyboard-keys - keyboard-state -) + set(DISPLAYSERVER_INPUT_CASES + pointer-events + pointer-buttons + pointer-release + wheel-steps + relative-motion + keyboard-focus + keyboard-keys + keyboard-state + ) -foreach(backend IN ITEMS wayland x11) - foreach(name IN LISTS DISPLAYSERVER_INPUT_CASES) - add_test(NAME display-input-${backend}-${name} - COMMAND displayserver-input-tests ${backend} ${name} + foreach(backend IN ITEMS wayland x11) + foreach(name IN LISTS DISPLAYSERVER_INPUT_CASES) + add_test(NAME display-input-${backend}-${name} + COMMAND displayserver-input-tests ${backend} ${name} + ) + set_tests_properties(display-input-${backend}-${name} PROPERTIES + TIMEOUT 10 + ) + endforeach() + endforeach() + + foreach(name IN ITEMS wheel-residual wheel-multiple) + add_test(NAME display-input-wayland-${name} + COMMAND displayserver-input-tests wayland ${name} ) - set_tests_properties(display-input-${backend}-${name} PROPERTIES + set_tests_properties(display-input-wayland-${name} PROPERTIES TIMEOUT 10 ) endforeach() -endforeach() -foreach(name IN ITEMS wheel-residual wheel-multiple) - add_test(NAME display-input-wayland-${name} - COMMAND displayserver-input-tests wayland ${name} - ) - set_tests_properties(display-input-wayland-${name} PROPERTIES - TIMEOUT 10 - ) -endforeach() - -foreach(name IN ITEMS pointer-routing keyboard-routing held-keys) - add_test(NAME display-input-x11-${name} - COMMAND displayserver-input-tests x11 ${name} - ) - set_tests_properties(display-input-x11-${name} PROPERTIES - TIMEOUT 10 - ) -endforeach() + foreach(name IN ITEMS pointer-routing keyboard-routing held-keys) + add_test(NAME display-input-x11-${name} + COMMAND displayserver-input-tests x11 ${name} + ) + set_tests_properties(display-input-x11-${name} PROPERTIES + TIMEOUT 10 + ) + endforeach() +endif() add_executable(windows-input-tests windows_input_test.c @@ -575,6 +642,7 @@ target_include_directories(app-keybind-tests PRIVATE target_link_libraries(app-keybind-tests ${EXE_FLAGS} lg_common + lg_gui ) set(APP_KEYBIND_CASES invalid @@ -641,37 +709,39 @@ foreach(name IN LISTS CLIPBOARD_CASES) ) endforeach() -add_executable(clipboard-files-tests - clipboard_files_test.c - ../src/clipboard_files.c -) -target_include_directories(clipboard-files-tests PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/../src" -) -target_link_libraries(clipboard-files-tests - ${EXE_FLAGS} - lg_common - PkgConfig::FUSE3 -) -add_test(NAME clipboard-files-lifecycle - COMMAND clipboard-files-tests -) -set_tests_properties(clipboard-files-lifecycle PROPERTIES - TIMEOUT 10 -) -add_test(NAME clipboard-files-failures - COMMAND clipboard-files-tests failures -) -set_tests_properties(clipboard-files-failures PROPERTIES - TIMEOUT 10 -) -add_test(NAME clipboard-files-mount - COMMAND clipboard-files-tests mount -) -set_tests_properties(clipboard-files-mount PROPERTIES - TIMEOUT 10 - SKIP_RETURN_CODE 77 -) +if(LG_CLIENT_PLATFORM_LINUX) + add_executable(clipboard-files-tests + clipboard_files_test.c + ../src/clipboard_files.c + ) + target_include_directories(clipboard-files-tests PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../src" + ) + target_link_libraries(clipboard-files-tests + ${EXE_FLAGS} + lg_common + PkgConfig::FUSE3 + ) + add_test(NAME clipboard-files-lifecycle + COMMAND clipboard-files-tests + ) + set_tests_properties(clipboard-files-lifecycle PROPERTIES + TIMEOUT 10 + ) + add_test(NAME clipboard-files-failures + COMMAND clipboard-files-tests failures + ) + set_tests_properties(clipboard-files-failures PROPERTIES + TIMEOUT 10 + ) + add_test(NAME clipboard-files-mount + COMMAND clipboard-files-tests mount + ) + set_tests_properties(clipboard-files-mount PROPERTIES + TIMEOUT 10 + SKIP_RETURN_CODE 77 + ) +endif() if(ENABLE_SPICE) add_executable(spice-clipboard-tests @@ -817,6 +887,7 @@ add_executable(frame-scheduler-tests ) target_compile_definitions(frame-scheduler-tests PRIVATE CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1 + nanotime=testNanotime ) target_include_directories(frame-scheduler-tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../src" @@ -890,12 +961,12 @@ endforeach() add_executable(config-message-tests config_message_test.c ../src/message.c - ../src/core.c ../src/config.c ../platform/${LG_CLIENT_PLATFORM}/config.c ) target_compile_definitions(config-message-tests PRIVATE CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1 + microtime=testMicrotime ) target_include_directories(config-message-tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../src" @@ -927,8 +998,6 @@ foreach(name IN LISTS CONFIG_MESSAGE_CASES) endforeach() if(ENABLE_AUDIO) - pkg_check_modules(AUDIO_TEST_SAMPLERATE REQUIRED IMPORTED_TARGET - samplerate) add_executable(audio-tests audio_test.c ) @@ -944,7 +1013,7 @@ if(ENABLE_AUDIO) target_link_libraries(audio-tests ${EXE_FLAGS} lg_common - PkgConfig::AUDIO_TEST_SAMPLERATE + ${SAMPLERATE_TARGET} ) set(AUDIO_CASES format @@ -1078,109 +1147,114 @@ if(ENABLE_EGL) TIMEOUT 10 ) - add_executable(texture-upload-tests - texture_upload_test.c - ../renderers/EGL/texture_buffer.c - ) - target_include_directories(texture-upload-tests PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/../renderers/EGL" - "${PROJECT_TOP}/common/include" - ) - target_link_libraries(texture-upload-tests - ${EXE_FLAGS} - LGProtocol::LGProtocol - lg_egl_provider - ) - set(TEXTURE_UPLOAD_CASES - direct - direct-full - direct-rect - persistent - persistent-full - persistent-rect - fallback - ) - foreach(name IN LISTS TEXTURE_UPLOAD_CASES) - add_test(NAME texture-upload-${name} - COMMAND texture-upload-tests ${name} + if(LG_CLIENT_PLATFORM_LINUX) + add_executable(texture-upload-tests + texture_upload_test.c + ../renderers/EGL/texture_buffer.c ) - set_tests_properties(texture-upload-${name} PROPERTIES - TIMEOUT 10 + target_include_directories(texture-upload-tests PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../renderers/EGL" + "${PROJECT_TOP}/common/include" ) - endforeach() -endif() - -find_package(GTest REQUIRED) -find_program(WESTON_EXECUTABLE NAMES weston) -if(NOT WESTON_EXECUTABLE) - message(FATAL_ERROR "weston is required to run framebuffer rendering tests") -endif() -if(ENABLE_X11) - find_program(XVFB_RUN_EXECUTABLE NAMES xvfb-run) - if(NOT XVFB_RUN_EXECUTABLE) - message(FATAL_ERROR "xvfb-run is required to run X11 rendering tests") + target_link_libraries(texture-upload-tests + ${EXE_FLAGS} + LGProtocol::LGProtocol + lg_egl_provider + ) + set(TEXTURE_UPLOAD_CASES + direct + direct-full + direct-rect + persistent + persistent-full + persistent-rect + fallback + ) + foreach(name IN LISTS TEXTURE_UPLOAD_CASES) + add_test(NAME texture-upload-${name} + COMMAND texture-upload-tests ${name} + ) + set_tests_properties(texture-upload-${name} PROPERTIES + TIMEOUT 10 + ) + endforeach() endif() endif() -add_executable(render-tests - render_test.cpp -) -add_dependencies(render-tests looking-glass-client) +if(LG_CLIENT_PLATFORM_LINUX) + find_package(GTest REQUIRED) + find_program(WESTON_EXECUTABLE NAMES weston) + if(NOT WESTON_EXECUTABLE) + message(FATAL_ERROR + "weston is required to run framebuffer rendering tests") + endif() + if(ENABLE_X11) + find_program(XVFB_RUN_EXECUTABLE NAMES xvfb-run) + if(NOT XVFB_RUN_EXECUTABLE) + message(FATAL_ERROR "xvfb-run is required to run X11 rendering tests") + endif() + endif() -set_target_properties(render-tests PROPERTIES - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED ON -) + add_executable(render-tests + render_test.cpp + ) + add_dependencies(render-tests looking-glass-client) -target_compile_options(render-tests PRIVATE - -fno-fast-math -) - -target_compile_definitions(render-tests PRIVATE - LG_CLIENT_PATH="$" -) - -target_include_directories(render-tests PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/../include" - "${PROJECT_TOP}/common/include" -) - -target_link_libraries(render-tests - LGProtocol::LGProtocol -) - -if(TARGET GTest::gtest_main) - target_link_libraries(render-tests GTest::gtest_main) -else() - target_link_libraries(render-tests GTest::Main) -endif() - -add_test(NAME render-tests - COMMAND /bin/bash - "${CMAKE_CURRENT_SOURCE_DIR}/run-with-weston.sh" - "${WESTON_EXECUTABLE}" - "$" - "--gtest_filter=-X11*" -) - -set_tests_properties(render-tests PROPERTIES - TIMEOUT 120 -) - -if(ENABLE_X11) - add_test(NAME render-x11-tests - COMMAND "${CMAKE_COMMAND}" -E env - --unset=WAYLAND_DISPLAY - LIBGL_ALWAYS_SOFTWARE=1 - "${XVFB_RUN_EXECUTABLE}" - -a - -e /dev/stderr - -s "-screen 0 1024x768x24 +extension GLX" - "$" - "--gtest_filter=X11*" + set_target_properties(render-tests PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON ) - set_tests_properties(render-x11-tests PROPERTIES + target_compile_options(render-tests PRIVATE + -fno-fast-math + ) + + target_compile_definitions(render-tests PRIVATE + LG_CLIENT_PATH="$" + ) + + target_include_directories(render-tests PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../include" + "${PROJECT_TOP}/common/include" + ) + + target_link_libraries(render-tests + LGProtocol::LGProtocol + ) + + if(TARGET GTest::gtest_main) + target_link_libraries(render-tests GTest::gtest_main) + else() + target_link_libraries(render-tests GTest::Main) + endif() + + add_test(NAME render-tests + COMMAND /bin/bash + "${CMAKE_CURRENT_SOURCE_DIR}/run-with-weston.sh" + "${WESTON_EXECUTABLE}" + "$" + "--gtest_filter=-X11*" + ) + + set_tests_properties(render-tests PROPERTIES TIMEOUT 120 ) + + if(ENABLE_X11) + add_test(NAME render-x11-tests + COMMAND "${CMAKE_COMMAND}" -E env + --unset=WAYLAND_DISPLAY + LIBGL_ALWAYS_SOFTWARE=1 + "${XVFB_RUN_EXECUTABLE}" + -a + -e /dev/stderr + -s "-screen 0 1024x768x24 +extension GLX" + "$" + "--gtest_filter=X11*" + ) + + set_tests_properties(render-x11-tests PROPERTIES + TIMEOUT 120 + ) + endif() endif() diff --git a/client/tests/app_keybind_test.c b/client/tests/app_keybind_test.c index b155a14a..dda06a24 100644 --- a/client/tests/app_keybind_test.c +++ b/client/tests/app_keybind_test.c @@ -20,8 +20,16 @@ #include "app.h" #include "app_internal.h" +#include "core.h" +#include "input.h" #include "main.h" +#include "overlay/msg.h" +#include "overlays.h" +#include "render_queue.h" +#include "sw_surface.h" #include "test.h" +#include "transport_fallback.h" +#include "util.h" #include "common/array.h" #include "common/ll.h" @@ -194,11 +202,16 @@ void core_updateOverlayState(void) { } -bool evdev_isExclusive(void) +bool inputPlatformIsKeyboardExclusive(void) { return t.exclusive; } +bool inputPlatformIsPointerExclusive(void) +{ + return false; +} + bool lgInput_available(void) { return t.available; @@ -219,8 +232,235 @@ void lgInput_releaseKeys(void) ++t.releaseN; } -void ImGuiIO_AddKeyEvent(ImGuiIO * self, ImGuiKey key, bool down) +void core_handleMousePosition(double x, double y) { + (void)x; + (void)y; +} + +void core_handleMouseAbsolute(void) +{ +} + +void core_handleMouseGrabbed(double x, double y) +{ + (void)x; + (void)y; +} + +void core_handleMouseNormal(double x, double y) +{ + (void)x; + (void)y; +} + +void core_handleGrabEvent(bool active) +{ + (void)active; +} + +void core_handleCaptureLost(void) +{ +} + +void core_updatePositionInfo(void) +{ +} + +void core_alignToGuest(void) +{ +} + +bool core_startVideoThreads(void) +{ + return true; +} + +void core_stopVideoThreads(void) +{ +} + +bool lgInput_mousePress(unsigned int button) +{ + (void)button; + return true; +} + +bool lgInput_mouseRelease(unsigned int button) +{ + (void)button; + return true; +} + +void lgInput_releaseAll(void) +{ +} + +void lgInput_releaseButtons(void) +{ +} + +bool lgInput_keyboardLEDs(bool numLock, bool capsLock, bool scrollLock) +{ + (void)numLock; + (void)capsLock; + (void)scrollLock; + return true; +} + +void main_framePresented(uint64_t frameToken, uint64_t presentTime, + bool valid) +{ + (void)frameToken; + (void)presentTime; + (void)valid; +} + +void main_videoStreamEnabled(void) +{ +} + +struct LG_OverlayOps LGOverlayMsg; + +void overlayAlert_show(LG_MsgAlert type, const char * format, va_list args) +{ + (void)type; + (void)format; + (void)args; +} + +MsgBoxHandle overlayMsg_show(const char * caption, + MsgBoxConfirmCallback confirm, void * confirmOpaque, + MsgBoxCloseCallback close, void * closeOpaque, + const char * format, va_list args) +{ + (void)caption; + (void)confirm; + (void)confirmOpaque; + (void)close; + (void)closeOpaque; + (void)format; + (void)args; + return NULL; +} + +void overlayMsg_close(MsgBoxHandle handle) +{ + (void)handle; +} + +bool overlayMsg_modal(void) +{ + return false; +} + +void overlayStatus_set(LGUserStatus status, bool value) +{ + (void)status; + (void)value; +} + +GraphHandle overlayGraph_register(const char * name, RingBuffer buffer, + float min, float max, GraphFormatFn formatFn) +{ + (void)name; + (void)buffer; + (void)min; + (void)max; + (void)formatFn; + return NULL; +} + +void overlayGraph_unregister(GraphHandle handle) +{ + (void)handle; +} + +void overlayGraph_invalidate(GraphHandle handle) +{ + (void)handle; +} + +void overlayGraph_setCompact(GraphHandle handle, bool compact) +{ + (void)handle; + (void)compact; +} + +void overlayConfig_register(const char * title, + void (*callback)(void * udata, int * id), void * udata) +{ + (void)title; + (void)callback; + (void)udata; +} + +void overlayConfig_registerTab(const char * title, + void (*callback)(void * udata, int * id), void * udata) +{ + (void)title; + (void)callback; + (void)udata; +} + +bool util_uiFontAddText(const char * text) +{ + (void)text; + return true; +} + +void lgTransportFallback_requestVideoActive( + LG_TransportFallback * fallback, bool active) +{ + (void)fallback; + (void)active; +} + +bool lgSwSurface_setActive(const LG_SwSurfaceOps * ops, + LG_Transport * transport, bool active, + LG_TransportCancelledFn cancelled, void * opaque) +{ + (void)ops; + (void)transport; + (void)active; + (void)cancelled; + (void)opaque; + return true; +} + +uint64_t renderQueue_sourceTransition(RenderQueueSource source, + uint64_t generation, bool swSurface, + atomic_uint_least64_t * publishedSerial) +{ + (void)source; + (void)generation; + (void)swSurface; + (void)publishedSerial; + return 0; +} + +uint64_t renderQueue_sourceSwSurfaceConfigureTransition( + RenderQueueSource source, uint64_t generation, int width, int height, + atomic_uint_least64_t * publishedSerial) +{ + (void)source; + (void)generation; + (void)width; + (void)height; + (void)publishedSerial; + return 0; +} + +void renderQueue_sourceCursorState(RenderQueueSource source, + uint64_t generation, bool visible, int x, int y, int hx, int hy) +{ + (void)source; + (void)generation; + (void)visible; + (void)x; + (void)y; + (void)hx; + (void)hy; } static void count(int key, void * opaque) diff --git a/client/tests/clipboard_files_windows_test.c b/client/tests/clipboard_files_windows_test.c new file mode 100644 index 00000000..24a9f958 --- /dev/null +++ b/client/tests/clipboard_files_windows_test.c @@ -0,0 +1,103 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "core/clipboard_files.h" +#include "test.h" + +static struct +{ + uint64_t dataset; + uint64_t transfer; + LG_ClipboardFileError error; + unsigned int acquired; + unsigned int cancelled; +} +trace; + +bool clipboard_fileAcquired(uint64_t dataset, uint64_t acquisition, + LG_ClipboardFileError error) +{ + trace.dataset = dataset; + trace.transfer = acquisition; + trace.error = error; + ++trace.acquired; + return true; +} + +bool clipboard_fileCancel(uint64_t dataset, uint64_t request, + LG_ClipboardFileError reason) +{ + trace.dataset = dataset; + trace.transfer = request; + trace.error = reason; + ++trace.cancelled; + return true; +} + +int main(void) +{ + const LG_ClipboardFileRequest request = + { + .dataset = UINT64_C(0x1234), + .request = UINT64_C(0x5678), + .node = 1, + .operation = LG_CLIPBOARD_FILE_READ, + }; + + CHECK(clipboardFiles_init()); + CHECK(!clipboardFiles_remoteOffer(request.dataset)); + CHECK(!clipboardFiles_remoteReady(request.dataset)); + + char * data = (char *)(uintptr_t)1; + size_t size = 1; + CHECK(!clipboardFiles_getRemote("text/uri-list", &data, &size)); + CHECK(!data); + CHECK(size == 0); + + data = (char *)(uintptr_t)1; + size = 1; + CHECK(!clipboardFiles_getRemotePresentation( + 1, "text/uri-list", &data, &size)); + CHECK(!data); + CHECK(size == 0); + + clipboardFiles_localAcquire(request.dataset, request.request); + CHECK(trace.acquired == 1); + CHECK(trace.dataset == request.dataset); + CHECK(trace.transfer == request.request); + CHECK(trace.error == LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED); + + clipboardFiles_localRequest(&request); + CHECK(trace.cancelled == 1); + CHECK(trace.dataset == request.dataset); + CHECK(trace.transfer == request.request); + CHECK(trace.error == LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED); + + clipboardFiles_localRequest(NULL); + CHECK(trace.cancelled == 1); + CHECK(clipboardFiles_remoteDataBegin(&request, 0) == + LG_CLIPBOARD_RESULT_FAILED); + CHECK(clipboardFiles_remoteDataChunk(&request, 0, NULL, 0) == + LG_CLIPBOARD_RESULT_FAILED); + CHECK(clipboardFiles_remoteDataEnd(&request, 0) == + LG_CLIPBOARD_RESULT_FAILED); + clipboardFiles_free(); + return 0; +} diff --git a/client/tests/clipboard_test.c b/client/tests/clipboard_test.c index 3a2b4de2..7f062636 100644 --- a/client/tests/clipboard_test.c +++ b/client/tests/clipboard_test.c @@ -34,7 +34,6 @@ #define MAX_CALL 16U #define MAX_DATA 32U -#define TEST_ALARM_S 5U struct Provider { @@ -1412,7 +1411,6 @@ int main(int argc, char ** argv) return EXIT_FAILURE; } - alarm(TEST_ALARM_S); debug_init(); for (unsigned int i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i) if (strcmp(argv[1], tests[i].name) == 0) diff --git a/client/tests/config_message_test.c b/client/tests/config_message_test.c index 7ebd019f..a2521a7e 100644 --- a/client/tests/config_message_test.c +++ b/client/tests/config_message_test.c @@ -36,7 +36,6 @@ #define MAX_CTL 16U #define DEBOUNCE_US UINT64_C(500000) -#define TEST_ALARM_S 5U struct LG_Transport { @@ -98,12 +97,27 @@ bool lgTransport_isValid(const char * name) (strcmp(name, "lgmp") == 0 || strcmp(name, "spice") == 0); } -int clock_gettime(clockid_t id, struct timespec * ts) +uint64_t testMicrotime(void) { - const uint64_t ns = t.now * UINT64_C(1000); - ts->tv_sec = ns / UINT64_C(1000000000); - ts->tv_nsec = ns % UINT64_C(1000000000); - return 0; + return t.now; +} + +void core_onWindowSizeChanged(unsigned int width, unsigned int height) +{ + if (!g_state.transport.handle || + !(g_state.transportFeatures & LG_TRANSPORT_FEATURE_WINDOW_SIZE) || + (g_state.srcSize.x == width && g_state.srcSize.y == height)) + return; + + const LG_TransportControl control = + { + .type = LG_TRANSPORT_CONTROL_WINDOW_SIZE, + .windowSize = { .width = width, .height = height }, + }; + + LG_TransportControlToken token; + g_state.transport.ops->sendControl( + g_state.transport.handle, &control, &token); } static LG_TransportStatus sendCtl(LG_Transport * transport, @@ -459,7 +473,6 @@ int main(int argc, char ** argv) return EXIT_FAILURE; } - alarm(TEST_ALARM_S); debug_init(); for (unsigned int i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i) if (strcmp(argv[1], tests[i].name) == 0) diff --git a/client/tests/event_source_test.c b/client/tests/event_source_test.c new file mode 100644 index 00000000..c07afcef --- /dev/null +++ b/client/tests/event_source_test.c @@ -0,0 +1,129 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "event_source.h" +#include "test.h" + +struct Trace +{ + unsigned int notifications; + unsigned int wakeAttempts; + unsigned int wakeFailures; + unsigned int dispatches; + bool notifyDuringDispatch; + LGEventSourceState * state; +}; + +static bool notify(void * opaque) +{ + struct Trace * trace = (struct Trace *)opaque; + ++trace->wakeAttempts; + if (trace->wakeFailures) + { + --trace->wakeFailures; + return false; + } + + ++trace->notifications; + return true; +} + +static void dispatch(void * opaque) +{ + struct Trace * trace = (struct Trace *)opaque; + ++trace->dispatches; + if (trace->notifyDuringDispatch) + { + trace->notifyDuringDispatch = false; + lgEventSourceNotify(trace->state); + } +} + +int main(void) +{ + struct Trace trace = { 0 }; + LGEventSourceState state; + LG_DSEventSource source; + + CHECK(!lgEventSourceInit(NULL, dispatch, &trace)); + CHECK(!lgEventSourceInit(&state, NULL, &trace)); + source = lgEventSourceGet(NULL); + CHECK(!lgEventSourceValid(&source)); + + CHECK(lgEventSourceInit(&state, dispatch, &trace)); + trace.state = &state; + source = lgEventSourceGet(&state); + CHECK(lgEventSourceValid(&source)); + + lgEventSourceNotify(&state); + CHECK(trace.notifications == 0); + CHECK(trace.wakeAttempts == 0); + CHECK(source.subscribe(source.opaque, notify, &trace)); + CHECK(trace.notifications == 1); + CHECK(trace.wakeAttempts == 1); + CHECK(!source.subscribe(source.opaque, notify, &trace)); + + lgEventSourceNotify(&state); + CHECK(trace.notifications == 1); + CHECK(trace.wakeAttempts == 1); + source.dispatch(source.opaque); + CHECK(trace.dispatches == 1); + + lgEventSourceNotify(&state); + lgEventSourceNotify(&state); + CHECK(trace.notifications == 2); + CHECK(trace.wakeAttempts == 2); + source.dispatch(source.opaque); + CHECK(trace.dispatches == 2); + + trace.wakeFailures = 1; + lgEventSourceNotify(&state); + CHECK(trace.notifications == 2); + CHECK(trace.wakeAttempts == 3); + lgEventSourceNotify(&state); + CHECK(trace.notifications == 3); + CHECK(trace.wakeAttempts == 4); + + trace.notifyDuringDispatch = true; + source.dispatch(source.opaque); + CHECK(trace.dispatches == 3); + CHECK(trace.notifications == 4); + CHECK(trace.wakeAttempts == 5); + source.dispatch(source.opaque); + CHECK(trace.dispatches == 4); + + source.unsubscribe(source.opaque); + lgEventSourceNotify(&state); + CHECK(trace.notifications == 4); + CHECK(trace.wakeAttempts == 5); + trace.wakeFailures = 1; + CHECK(!source.subscribe(source.opaque, notify, &trace)); + CHECK(trace.notifications == 4); + CHECK(trace.wakeAttempts == 6); + CHECK(source.subscribe(source.opaque, notify, &trace)); + CHECK(trace.notifications == 5); + CHECK(trace.wakeAttempts == 7); + source.dispatch(source.opaque); + CHECK(trace.dispatches == 5); + + source.unsubscribe(source.opaque); + lgEventSourceFree(&state); + return 0; +} diff --git a/client/tests/font_platform_stub.c b/client/tests/font_platform_stub.c new file mode 100644 index 00000000..c9c06023 --- /dev/null +++ b/client/tests/font_platform_stub.c @@ -0,0 +1,140 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "font_platform.h" +#include "font_platform_stub.h" + +#include +#include + +struct FontPlatform +{ + unsigned int unused; +}; + +static FontPlatformStubStats stats; + +const FontPlatformStubStats * font_platformStubStats(void) +{ + return &stats; +} + +bool font_platformInit(FontPlatform ** platform) +{ + if (!platform) + return false; + + *platform = calloc(1, sizeof(**platform)); + if (!*platform) + return false; + + ++stats.initCount; + return true; +} + +void font_platformFree(FontPlatform * platform) +{ + if (!platform) + return; + + ++stats.freeCount; + free(platform); +} + +char * font_platformSelect(FontPlatform * platform, const char * fontName) +{ + if (!platform || !fontName) + return NULL; + + ++stats.selectCount; + const size_t length = strlen(FONT_TEST_FILE) + 1; + char * path = malloc(length); + if (path) + memcpy(path, FONT_TEST_FILE, length); + return path; +} + +bool font_platformResolve(FontPlatform * platform, + const uint32_t * glyphs, size_t glyphCount, + FontPlatformAddFaceFn addFace, void * opaque, size_t * unresolved) +{ + if (!platform || (!glyphs && glyphCount) || !addFace || !unresolved || + glyphCount > SIZE_MAX / sizeof(*glyphs)) + return false; + + ++stats.resolveCount; + uint32_t * primary = malloc(glyphCount * sizeof(*primary)); + uint32_t * fallback = malloc(glyphCount * sizeof(*fallback)); + if ((!primary || !fallback) && glyphCount) + { + free(primary); + free(fallback); + return false; + } + + size_t primaryCount = 0; + size_t fallbackCount = 0; + for (size_t i = 0; i < glyphCount; ++i) + if (glyphs[i] <= 0xff) + primary[primaryCount++] = glyphs[i]; + else + fallback[fallbackCount++] = glyphs[i]; + + FontPlatformFaceResult result = FONT_PLATFORM_FACE_ACCEPT; + if (primaryCount) + result = addFace( + opaque, FONT_TEST_FILE, 0, primary, primaryCount); + if (result == FONT_PLATFORM_FACE_ERROR) + goto fail; + + size_t missing = result == FONT_PLATFORM_FACE_ACCEPT ? 0 : primaryCount; + if (fallbackCount) + { + result = addFace(opaque, FONT_TEST_FILE, 1, + fallback, fallbackCount); + if (result == FONT_PLATFORM_FACE_ERROR) + goto fail; + if (result == FONT_PLATFORM_FACE_SKIP) + ++stats.incompatibleSkipCount; + else + goto fail; + + result = addFace(opaque, FONT_TEST_FILE, 0, + fallback, fallbackCount); + if (result == FONT_PLATFORM_FACE_ERROR) + goto fail; + if (result == FONT_PLATFORM_FACE_ACCEPT) + { + ++stats.fallbackAcceptCount; + } + else + missing += fallbackCount; + } + + *unresolved = missing; + free(primary); + free(fallback); + return true; + +fail: + free(primary); + free(fallback); + return false; +} diff --git a/client/tests/font_platform_stub.h b/client/tests/font_platform_stub.h new file mode 100644 index 00000000..557175fe --- /dev/null +++ b/client/tests/font_platform_stub.h @@ -0,0 +1,37 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _H_LG_FONT_PLATFORM_STUB_ +#define _H_LG_FONT_PLATFORM_STUB_ + +typedef struct FontPlatformStubStats +{ + unsigned int initCount; + unsigned int freeCount; + unsigned int selectCount; + unsigned int resolveCount; + unsigned int incompatibleSkipCount; + unsigned int fallbackAcceptCount; +} +FontPlatformStubStats; + +const FontPlatformStubStats * font_platformStubStats(void); + +#endif diff --git a/client/tests/font_test.c b/client/tests/font_test.c index af8255f5..8bdbfed4 100644 --- a/client/tests/font_test.c +++ b/client/tests/font_test.c @@ -19,10 +19,16 @@ */ #include "font.h" +#include "font_platform_stub.h" #include "test.h" +#include "common/debug.h" + +#include "cimgui.h" + #include #include +#include static void testInvalidFontData(void) { @@ -59,9 +65,59 @@ static void testValidFontData(void) free(data); } +static void testFontLifecycle(void) +{ + const FontPlatformStubStats * stats = font_platformStubStats(); + + CHECK(!font_addText("before init")); + CHECK(font_init()); + CHECK(font_init()); + CHECK(stats->initCount == 1); + + char * path = font_get("test font"); + CHECK(path); + CHECK(strcmp(path, FONT_TEST_FILE) == 0); + free(path); + CHECK(stats->selectCount == 1); + + const char text[] = "fallback \xE4\xB8\xAD"; + CHECK(font_addText(text)); + CHECK(!font_addText(text)); + + ImFontAtlas * atlas = ImFontAtlas_ImFontAtlas(); + CHECK(atlas); + + ImFont * large = NULL; + CHECK(font_buildAtlas(atlas, 14.0f, &large)); + CHECK(large); + CHECK(stats->resolveCount == 1); + CHECK(stats->incompatibleSkipCount == 1); + CHECK(stats->fallbackAcceptCount == 1); + + large = NULL; + CHECK(font_buildAtlas(atlas, 18.0f, &large)); + CHECK(large); + CHECK(stats->resolveCount == 2); + CHECK(stats->incompatibleSkipCount == 2); + CHECK(stats->fallbackAcceptCount == 2); + + ImFontAtlas_destroy(atlas); + font_free(); + font_free(); + CHECK(stats->freeCount == 1); + CHECK(!font_addText("after free")); + + CHECK(font_init()); + CHECK(stats->initCount == 2); + font_free(); + CHECK(stats->freeCount == 2); +} + int main(void) { + debug_init(); testInvalidFontData(); testValidFontData(); + testFontLifecycle(); return 0; } diff --git a/client/tests/frame_scheduler_test.c b/client/tests/frame_scheduler_test.c index db9b5757..d0ebeefe 100644 --- a/client/tests/frame_scheduler_test.c +++ b/client/tests/frame_scheduler_test.c @@ -66,11 +66,9 @@ static struct LG_Transport tr; struct AppState g_state; -int clock_gettime(clockid_t id, struct timespec * ts) +uint64_t testNanotime(void) { - ts->tv_sec = t.now / UINT64_C(1000000000); - ts->tv_nsec = t.now % UINT64_C(1000000000); - return 0; + return t.now; } static bool getPeriod(uint64_t * period) diff --git a/client/tests/frame_timing_test.c b/client/tests/frame_timing_test.c index ea24d62e..2ba94b04 100644 --- a/client/tests/frame_timing_test.c +++ b/client/tests/frame_timing_test.c @@ -26,8 +26,8 @@ #include #include +#include -#define TEST_ALARM_S 5U struct Feedback { @@ -307,7 +307,7 @@ static OverlayFrameTiming take(void) return out; } -static bool near(float value, float expected) +static bool isNear(float value, float expected) { return fabsf(value - expected) < 0.000001f; } @@ -360,13 +360,13 @@ static void testOrder(void) CHECK(ringbuffer_getCount(g_state.frameLatency) == 1); const OverlayFrameTiming out = take(); CHECK(out.validMask == OVERLAY_FRAME_TIMING_VALID_ALL); - CHECK(near(out.capture, 0.000011f)); - CHECK(near(out.transport, 0.000010f)); - CHECK(near(out.receive, 0.000002f)); - CHECK(near(out.providerPrepare, 0.000003f)); - CHECK(near(out.dispatch, 0.000035f)); - CHECK(near(out.queue, 0.000050f)); - CHECK(near(out.present, 0.000500f)); + CHECK(isNear(out.capture, 0.000011f)); + CHECK(isNear(out.transport, 0.000010f)); + CHECK(isNear(out.receive, 0.000002f)); + CHECK(isNear(out.providerPrepare, 0.000003f)); + CHECK(isNear(out.dispatch, 0.000035f)); + CHECK(isNear(out.queue, 0.000050f)); + CHECK(isNear(out.present, 0.000500f)); frameTimingFinishFrame(token, &timing); main_framePresented(token, 600, true); @@ -394,8 +394,8 @@ static void testUntracked(void) CHECK((out.validMask & OVERLAY_FRAME_TIMING_VALID_PROVIDER) == OVERLAY_FRAME_TIMING_VALID_PROVIDER); CHECK(!(out.validMask & OVERLAY_FRAME_TIMING_VALID_PRESENT)); - CHECK(near(out.receive, 0.000002f)); - CHECK(near(out.providerPrepare, 0.000003f)); + CHECK(isNear(out.receive, 0.000002f)); + CHECK(isNear(out.providerPrepare, 0.000003f)); CHECK(out.present == 0.0f); finish(); } @@ -440,7 +440,7 @@ static void testProviderUnavailable(void) CHECK(out.validMask & OVERLAY_FRAME_TIMING_VALID_PRODUCER); CHECK(out.validMask & OVERLAY_FRAME_TIMING_VALID_TRANSPORT); CHECK(!(out.validMask & OVERLAY_FRAME_TIMING_VALID_PROVIDER)); - CHECK(near(out.transport, 0.000015f)); + CHECK(isNear(out.transport, 0.000015f)); finish(); } @@ -491,8 +491,8 @@ static void testFifo(void) CHECK(ringbuffer_getCount(g_state.frameLatency) == 2); const OverlayFrameTiming out1 = take(); const OverlayFrameTiming out2 = take(); - CHECK(near(out1.capture, 0.000081f)); - CHECK(near(out2.capture, 0.000082f)); + CHECK(isNear(out1.capture, 0.000081f)); + CHECK(isNear(out2.capture, 0.000082f)); finish(); } @@ -786,7 +786,6 @@ int main(int argc, char ** argv) return EXIT_FAILURE; } - alarm(TEST_ALARM_S); debug_init(); for (size_t i = 0; i < ARRAY_LENGTH(tests); ++i) if (strcmp(argv[1], tests[i].name) == 0) diff --git a/client/tests/mouse_test.c b/client/tests/mouse_test.c index 8ae5b225..57012a51 100644 --- a/client/tests/mouse_test.c +++ b/client/tests/mouse_test.c @@ -20,8 +20,11 @@ #include "app.h" #include "core.h" +#include "font.h" #include "input.h" +#include "kb.h" #include "main.h" +#include "message.h" #include "test.h" #include "util.h" @@ -80,6 +83,81 @@ struct AppState g_state; struct CursorState g_cursor; struct AppParams g_params; +const int lg_keycode_to_imgui[LG_KEY_MAX] = { 0 }; + +void app_alert(LG_MsgAlert type, const char * format, ...) +{ + (void)type; + (void)format; +} + +void app_handleMouseRelative(double normx, double normy, + double rawx, double rawy) +{ + (void)normx; + (void)normy; + (void)rawx; + (void)rawy; +} + +void app_updateMouseState(void) +{ +} + +void lgMessage_post(const LGMsg * msg) +{ + (void)msg; +} + +void ImGuiIO_AddKeyEvent(ImGuiIO * self, ImGuiKey key, bool down) +{ + (void)self; + (void)key; + (void)down; +} + +int main_cursorThread(void * unused) +{ + (void)unused; + return 0; +} + +int main_frameThread(void * unused) +{ + (void)unused; + return 0; +} + +bool font_init(void) +{ + return true; +} + +void font_free(void) +{ +} + +char * font_get(const char * fontName) +{ + (void)fontName; + return NULL; +} + +bool font_addText(const char * text) +{ + (void)text; + return true; +} + +bool font_buildAtlas(struct ImFontAtlas * atlas, float size, + struct ImFont ** large) +{ + (void)atlas; + (void)size; + (void)large; + return true; +} + static void push(enum EvType type, double x, double y, bool leaving) { CHECK(m.count < sizeof(m.ev) / sizeof(m.ev[0])); @@ -282,13 +360,13 @@ void app_mouseTrace(const char * file, unsigned int line, (void)format; } -void evdev_setGrab(bool keyboard, bool pointer) +void inputPlatformSetGrab(bool keyboard, bool pointer) { (void)keyboard; (void)pointer; } -bool evdev_isExclusive(void) +bool inputPlatformIsKeyboardExclusive(void) { return false; } @@ -390,7 +468,7 @@ static void setLocal(double x, double y) g_cursor.guest.y = lround(guest.y); } -static bool near(double a, double b) +static bool isNear(double a, double b) { return fabs(a - b) < 0.000001; } @@ -405,8 +483,8 @@ static void testInsetExit(void) CHECK(count(EV_UNGRAB) == 0); CHECK(count(EV_WARP) == 0); - CHECK(near(g_cursor.exitPos.x, 9.75)); - CHECK(near(g_cursor.exitPos.y, 19.75)); + CHECK(isNear(g_cursor.exitPos.x, 9.75)); + CHECK(isNear(g_cursor.exitPos.y, 19.75)); CHECK(g_cursor.exit); CHECK(!g_cursor.inView); CHECK(!g_cursor.viewReq); @@ -511,8 +589,8 @@ static void testExitImmediate(void) startExit(); CHECK(count(EV_UNGRAB) == 0); CHECK(count(EV_WARP) == 0); - CHECK(near(g_cursor.exitPos.x, 111)); - CHECK(near(g_cursor.exitPos.y, 50)); + CHECK(isNear(g_cursor.exitPos.x, 111)); + CHECK(isNear(g_cursor.exitPos.y, 50)); CHECK(!g_cursor.inView); CHECK(!g_cursor.viewReq); CHECK(g_cursor.exit); @@ -903,8 +981,8 @@ static void testRotateScale(void) struct DoublePoint guest; util_localCurToGuest(&guest); - CHECK(near(guest.x, 40)); - CHECK(near(guest.y, 20)); + CHECK(isNear(guest.x, 40)); + CHECK(isNear(guest.y, 20)); } } @@ -941,13 +1019,13 @@ static void testGeometry(void) struct DoublePoint local; CHECK(util_guestCurToLocal(&local)); - CHECK(near(local.x, expected[rot].x)); - CHECK(near(local.y, expected[rot].y)); + CHECK(isNear(local.x, expected[rot].x)); + CHECK(isNear(local.y, expected[rot].y)); struct DoublePoint delta = { 1, 2 }; util_rotatePoint(&delta); - CHECK(near(delta.x, rotated[rot].x)); - CHECK(near(delta.y, rotated[rot].y)); + CHECK(isNear(delta.x, rotated[rot].x)); + CHECK(isNear(delta.y, rotated[rot].y)); } static const double scales[] = { 0.5, 1.0, 2.0 }; @@ -966,15 +1044,15 @@ static void testGeometry(void) struct DoublePoint round; util_localCurToGuest(&round); - CHECK(near(round.x, 40)); - CHECK(near(round.y, 20)); + CHECK(isNear(round.x, 40)); + CHECK(isNear(round.y, 20)); } g_cursor.guest.valid = false; struct DoublePoint local = { -1, -2 }; CHECK(!util_guestCurToLocal(&local)); - CHECK(near(local.x, -1)); - CHECK(near(local.y, -2)); + CHECK(isNear(local.x, -1)); + CHECK(isNear(local.y, -2)); } static struct DoublePoint inputFor(int rot, double x, double y) @@ -1030,8 +1108,8 @@ static void testEdges(void) CHECK(g_cursor.exit); CHECK(!g_cursor.inView); CHECK(!g_cursor.viewReq); - CHECK(near(g_cursor.exitPos.x, cases[i].target.x)); - CHECK(near(g_cursor.exitPos.y, cases[i].target.y)); + CHECK(isNear(g_cursor.exitPos.x, cases[i].target.x)); + CHECK(isNear(g_cursor.exitPos.y, cases[i].target.y)); } } @@ -1065,8 +1143,8 @@ static void testScales(void) CHECK(g_cursor.exit); CHECK(!g_cursor.inView); CHECK(!g_cursor.viewReq); - CHECK(near(g_cursor.exitPos.x, cases[i].target)); - CHECK(near(g_cursor.exitPos.y, 50)); + CHECK(isNear(g_cursor.exitPos.x, cases[i].target)); + CHECK(isNear(g_cursor.exitPos.y, 50)); } } @@ -1101,8 +1179,8 @@ static void testBorderExit(void) CHECK(g_cursor.exit); CHECK(!g_cursor.inView); CHECK(!g_cursor.viewReq); - CHECK(near(g_cursor.exitPos.x, 111)); - CHECK(near(g_cursor.exitPos.y, 50)); + CHECK(isNear(g_cursor.exitPos.x, 111)); + CHECK(isNear(g_cursor.exitPos.y, 50)); } } diff --git a/client/tests/process_test.c b/client/tests/process_test.c new file mode 100644 index 00000000..ae955a78 --- /dev/null +++ b/client/tests/process_test.c @@ -0,0 +1,146 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "platform/process.h" +#include "test.h" + +#include +#include +#include +#include +#include +#include + +static volatile sig_atomic_t stopCount; +static volatile sig_atomic_t restoredCount; + +static bool acceptStop(void * opaque) +{ + const int expected = 42; + if (!opaque || *(const int *)opaque != expected) + return false; + + ++stopCount; + return true; +} + +static bool rejectStop(void * opaque) +{ + (void)opaque; + return false; +} + +static void restoredHandler(int signal) +{ + (void)signal; + ++restoredCount; +} + +static void testPrivilege(void) +{ + enum LG_ProcessPrivilege expected = LG_PROCESS_PRIVILEGE_NORMAL; + if (getuid() == 0) + expected = LG_PROCESS_PRIVILEGE_ROOT; + else if (getuid() != geteuid()) + expected = LG_PROCESS_PRIVILEGE_SETUID; + + CHECK(lgProcessGetPrivilege() == expected); +} + +static void testExecutablePath(void) +{ + char * path = lgProcessGetExecutablePath(NULL); + CHECK(path); + CHECK(path[0] == '/'); + + struct stat actual; + struct stat expected; + CHECK(stat(path, &actual) == 0); + CHECK(stat("/proc/self/exe", &expected) == 0); + CHECK(actual.st_dev == expected.st_dev); + CHECK(actual.st_ino == expected.st_ino); + free(path); +} + +static void testHandledStop(void) +{ + const int opaque = 42; + + CHECK(!lgProcessInstallStopHandler(NULL, NULL)); + CHECK(lgProcessInstallStopHandler(acceptStop, (void *)&opaque)); + CHECK(!lgProcessInstallStopHandler(acceptStop, (void *)&opaque)); + + struct sigaction action; + CHECK(sigaction(SIGINT, NULL, &action) == 0); + CHECK(action.sa_flags & SA_RESTART); + + raise(SIGINT); + CHECK(stopCount == 1); + + raise(SIGTERM); + CHECK(stopCount == 2); + + lgProcessRemoveStopHandler(); + lgProcessRemoveStopHandler(); +} + +static void testPreviousHandlerRestored(void) +{ + struct sigaction previous; + struct sigaction action = { 0 }; + action.sa_handler = restoredHandler; + sigemptyset(&action.sa_mask); + CHECK(sigaction(SIGINT, &action, &previous) == 0); + + CHECK(lgProcessInstallStopHandler(acceptStop, NULL)); + lgProcessRemoveStopHandler(); + raise(SIGINT); + CHECK(restoredCount == 1); + + CHECK(sigaction(SIGINT, &previous, NULL) == 0); +} + +static void testRejectedStopTerminates(void) +{ + const pid_t child = fork(); + CHECK(child >= 0); + if (child == 0) + { + if (!lgProcessInstallStopHandler(rejectStop, NULL)) + _exit(EXIT_FAILURE); + raise(SIGTERM); + _exit(EXIT_FAILURE); + } + + int status = 0; + CHECK(waitpid(child, &status, 0) == child); + CHECK(WIFSIGNALED(status)); + CHECK(WTERMSIG(status) == SIGTERM); +} + +int main(void) +{ + testPrivilege(); + testExecutablePath(); + testHandledStop(); + testPreviousHandlerRestored(); + testRejectedStopTerminates(); + return 0; +} diff --git a/client/tests/transport_fallback_test.c b/client/tests/transport_fallback_test.c index d2419242..a2d3f6fc 100644 --- a/client/tests/transport_fallback_test.c +++ b/client/tests/transport_fallback_test.c @@ -31,7 +31,6 @@ #include #define TEST_WAIT_MS 3000U -#define TEST_ALARM_S 5U struct LG_Transport { @@ -874,7 +873,6 @@ int main(int argc, char ** argv) return EXIT_FAILURE; } - alarm(TEST_ALARM_S); debug_init(); for (unsigned int i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i) if (strcmp(argv[1], tests[i].name) == 0) diff --git a/client/transports/LGMP/input.c b/client/transports/LGMP/input.c index 6cf7a76e..9982b9b2 100644 --- a/client/transports/LGMP/input.c +++ b/client/transports/LGMP/input.c @@ -1293,13 +1293,13 @@ static bool collectStats(LGMPInput * input, uint64_t now, bool force, static void logStats(const struct LGMPInputCounters * stats) { - DEBUG_TRACE("LGMP input: sent immediate/deferred %lu/%lu" - ", queued %lu (high %u), initial/retry full %lu/%lu" - ", coalesced relative/absolute %lu/%lu" - ", motion rejected/evicted %lu/%lu" - ", overflow failures/resets %lu/%lu" - ", published claim/release/keepalive %lu/%lu" - "/%lu, terminal failures %lu", + DEBUG_TRACE("LGMP input: sent immediate/deferred %" PRIu64 "/%" PRIu64 + ", queued %" PRIu64 " (high %u), initial/retry full %" PRIu64 + "/%" PRIu64 ", coalesced relative/absolute %" PRIu64 "/%" PRIu64 + ", motion rejected/evicted %" PRIu64 "/%" PRIu64 + ", overflow failures/resets %" PRIu64 "/%" PRIu64 + ", published claim/release/keepalive %" PRIu64 "/%" PRIu64 + "/%" PRIu64 ", terminal failures %" PRIu64, stats->immediateSends, stats->deferredSends, stats->localEnqueues, stats->pendingHighWater, stats->initialFull, stats->retryFull, stats->relativeCoalesces, diff --git a/common/include/common/debug.h b/common/include/common/debug.h index 73d072cb..21f34cbf 100644 --- a/common/include/common/debug.h +++ b/common/include/common/debug.h @@ -48,6 +48,8 @@ void debug_enableTracing(void); // platform specific debug initialization void platform_debugInit(void); +void debug_lock(void); +void debug_unlock(void); #ifdef ENABLE_BACKTRACE void printBacktrace(void); diff --git a/common/src/platform/linux/debug.c b/common/src/platform/linux/debug.c index eac2edc8..e8e96ad8 100644 --- a/common/src/platform/linux/debug.c +++ b/common/src/platform/linux/debug.c @@ -57,3 +57,13 @@ void platform_debugInit(void) debug_lookup = (isatty(STDERR_FILENO) == 1) ? colorLookup : plainLookup; } + +void debug_lock(void) +{ + flockfile(stderr); +} + +void debug_unlock(void) +{ + funlockfile(stderr); +} diff --git a/common/src/platform/windows/crash.c b/common/src/platform/windows/crash.c index ea326760..dba29aad 100644 --- a/common/src/platform/windows/crash.c +++ b/common/src/platform/windows/crash.c @@ -30,6 +30,14 @@ #include #include +struct CrashHandlerState +{ + LPTOP_LEVEL_EXCEPTION_FILTER previous; + bool installed; +}; + +static struct CrashHandlerState crashHandler; + static const char * exception_name(DWORD code) { switch (code) @@ -124,8 +132,8 @@ static LONG CALLBACK exception_filter(EXCEPTION_POINTERS * exc) if (SymFromAddr(hProcess, frame.AddrPC.Offset, &disp, symbol)) { - IMAGEHLP_LINE line = { sizeof(IMAGEHLP_LINE), 0 }; - DWORD lineDisp; + IMAGEHLP_LINE line = { .SizeOfStruct = sizeof(line) }; + DWORD lineDisp; if (SymGetLineFromAddr64(hProcess, frame.AddrPC.Offset, &lineDisp, &line)) DEBUG_ERROR("[trace]: %2d: %s:%s+0x%" PRIx64 " (%s:%" PRIx64 "+0x%" PRIx64 ")", @@ -152,10 +160,25 @@ bool installCrashHandler(const char * exe, char * argv0) { (void)exe; (void)argv0; - SetUnhandledExceptionFilter(exception_filter); + + if (!crashHandler.installed) + { + crashHandler.previous = SetUnhandledExceptionFilter(exception_filter); + crashHandler.installed = true; + } return true; } +void cleanupCrashHandler(void) +{ + if (!crashHandler.installed) + return; + + SetUnhandledExceptionFilter(crashHandler.previous); + crashHandler.previous = NULL; + crashHandler.installed = false; +} + void printBacktrace(void) { } @@ -172,6 +195,10 @@ bool installCrashHandler(const char * exe, char * argv0) return true; } +void cleanupCrashHandler(void) +{ +} + void printBacktrace(void) { } diff --git a/common/src/platform/windows/debug.c b/common/src/platform/windows/debug.c index 45ec6994..1629ff81 100644 --- a/common/src/platform/windows/debug.c +++ b/common/src/platform/windows/debug.c @@ -37,3 +37,13 @@ void platform_debugInit(void) debug_lookup = plainLookup; } + +void debug_lock(void) +{ + _lock_file(stderr); +} + +void debug_unlock(void) +{ + _unlock_file(stderr); +}