[common/client] platform: abstract runtime services

This commit is contained in:
Geoffrey McRae
2026-09-12 12:51:17 +10:00
parent a7d2e4592f
commit eabc56a6de
49 changed files with 4064 additions and 940 deletions

View File

@@ -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 "

View File

@@ -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();

View File

@@ -19,6 +19,7 @@
*/
#include "evdev.h"
#include "event_source.h"
#include <errno.h>
#include <fcntl.h>
@@ -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)

115
client/src/event_source.c Normal file
View File

@@ -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 <string.h>
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);
}

View File

@@ -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 <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}

View File

@@ -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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
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

View File

@@ -21,21 +21,13 @@
#include "main.h"
#include "config.h"
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <inttypes.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <math.h>
#include <stdlib.h>
#include <stdatomic.h>
#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;
}

View File

@@ -30,253 +30,6 @@
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <fontconfig/fontconfig.h>
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();
}