mirror of
https://github.com/gnif/LookingGlass.git
synced 2025-04-24 23:56:26 +00:00
[client] input: add support for evdev keyboard capture mode
Some checks are pending
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / module (push) Waiting to run
build / host-linux (push) Waiting to run
build / host-windows-cross (push) Waiting to run
build / host-windows-native (push) Waiting to run
build / obs (clang) (push) Waiting to run
build / obs (gcc) (push) Waiting to run
build / docs (push) Waiting to run
Some checks are pending
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / module (push) Waiting to run
build / host-linux (push) Waiting to run
build / host-windows-cross (push) Waiting to run
build / host-windows-native (push) Waiting to run
build / obs (clang) (push) Waiting to run
build / obs (gcc) (push) Waiting to run
build / docs (push) Waiting to run
The new configuration option `input:evdev` accepts a comma separated list of `/dev/input/` keyboard devices to use for input when in capture mode. This makes it possible to capture only a specific keyboard instead of all keyboards.
This commit is contained in:
parent
27fe47cbe2
commit
66ac453c98
@ -124,6 +124,7 @@ set(SOURCES
|
||||
src/eglutil.c
|
||||
src/overlay_utils.c
|
||||
src/render_queue.c
|
||||
src/evdev.c
|
||||
|
||||
src/overlay/splash.c
|
||||
src/overlay/alert.c
|
||||
|
@ -209,7 +209,7 @@ done:
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static int getCharcode(uint32_t key)
|
||||
int waylandGetCharCode(int key)
|
||||
{
|
||||
key += 8; // xkb scancode is evdev scancode + 8
|
||||
xkb_keysym_t sym = xkb_state_key_get_one_sym(wlWm.xkbState, key);
|
||||
@ -232,7 +232,7 @@ static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard,
|
||||
|
||||
uint32_t * key;
|
||||
wl_array_for_each(key, keys)
|
||||
app_handleKeyPress(*key, getCharcode(*key));
|
||||
app_handleKeyPress(*key);
|
||||
}
|
||||
|
||||
static void keyboardLeaveHandler(void * data, struct wl_keyboard * keyboard,
|
||||
@ -253,9 +253,9 @@ static void keyboardKeyHandler(void * data, struct wl_keyboard * keyboard,
|
||||
return;
|
||||
|
||||
if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
|
||||
app_handleKeyPress(key, getCharcode(key));
|
||||
app_handleKeyPress(key);
|
||||
else
|
||||
app_handleKeyRelease(key, getCharcode(key));
|
||||
app_handleKeyRelease(key);
|
||||
|
||||
if (!wlWm.xkbState || !app_isOverlayMode() || state != WL_KEYBOARD_KEY_STATE_PRESSED)
|
||||
return;
|
||||
|
@ -263,6 +263,7 @@ struct LG_DisplayServerOps LGDS_Wayland =
|
||||
.uncapturePointer = waylandUncapturePointer,
|
||||
.grabKeyboard = waylandGrabKeyboard,
|
||||
.ungrabKeyboard = waylandUngrabKeyboard,
|
||||
.getCharCode = waylandGetCharCode,
|
||||
.warpPointer = waylandWarpPointer,
|
||||
.realignPointer = waylandRealignPointer,
|
||||
.isValidPointerPos = waylandIsValidPointerPos,
|
||||
|
@ -277,6 +277,7 @@ void waylandUncapturePointer(void);
|
||||
void waylandRealignPointer(void);
|
||||
void waylandWarpPointer(int x, int y, bool exiting);
|
||||
void waylandGuestPointerUpdated(double x, double y, double localX, double localY);
|
||||
int waylandGetCharCode(int key);
|
||||
|
||||
// output module
|
||||
bool waylandOutputInit(void);
|
||||
|
@ -1087,8 +1087,9 @@ static void setFocus(bool focused, double x, double y)
|
||||
app_handleFocusEvent(focused);
|
||||
}
|
||||
|
||||
static int getCharcode(int detail)
|
||||
static int x11GetCharCode(int detail)
|
||||
{
|
||||
detail += x11.minKeycode;
|
||||
if (detail < x11.minKeycode || detail > x11.maxKeycode)
|
||||
return 0;
|
||||
|
||||
@ -1229,8 +1230,7 @@ static void x11XInputEvent(XGenericEventCookie *cookie)
|
||||
return;
|
||||
|
||||
XIDeviceEvent *device = cookie->data;
|
||||
app_handleKeyPress(device->detail - x11.minKeycode,
|
||||
getCharcode(device->detail));
|
||||
app_handleKeyPress(device->detail - x11.minKeycode);
|
||||
|
||||
if (!x11.xic || !app_isOverlayMode())
|
||||
return;
|
||||
@ -1280,8 +1280,7 @@ static void x11XInputEvent(XGenericEventCookie *cookie)
|
||||
return;
|
||||
|
||||
XIDeviceEvent *device = cookie->data;
|
||||
app_handleKeyRelease(device->detail - x11.minKeycode,
|
||||
getCharcode(device->detail));
|
||||
app_handleKeyRelease(device->detail - x11.minKeycode);
|
||||
|
||||
if (!x11.xic || !app_isOverlayMode())
|
||||
return;
|
||||
@ -1310,8 +1309,7 @@ static void x11XInputEvent(XGenericEventCookie *cookie)
|
||||
return;
|
||||
|
||||
XIRawEvent *raw = cookie->data;
|
||||
app_handleKeyPress(raw->detail - x11.minKeycode,
|
||||
getCharcode(raw->detail));
|
||||
app_handleKeyPress(raw->detail - x11.minKeycode);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1321,8 +1319,7 @@ static void x11XInputEvent(XGenericEventCookie *cookie)
|
||||
return;
|
||||
|
||||
XIRawEvent *raw = cookie->data;
|
||||
app_handleKeyRelease(raw->detail - x11.minKeycode,
|
||||
getCharcode(raw->detail));
|
||||
app_handleKeyRelease(raw->detail - x11.minKeycode);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2017,6 +2014,7 @@ struct LG_DisplayServerOps LGDS_X11 =
|
||||
.ungrabPointer = x11UngrabPointer,
|
||||
.capturePointer = x11CapturePointer,
|
||||
.uncapturePointer = x11UncapturePointer,
|
||||
.getCharCode = x11GetCharCode,
|
||||
.grabKeyboard = x11GrabKeyboard,
|
||||
.ungrabKeyboard = x11UngrabKeyboard,
|
||||
.warpPointer = x11WarpPointer,
|
||||
|
@ -59,8 +59,8 @@ void app_handleButtonPress(int button);
|
||||
void app_handleButtonRelease(int button);
|
||||
void app_handleWheelMotion(double motion);
|
||||
void app_handleKeyboardTyped(const char * typed);
|
||||
void app_handleKeyPress(int scancode, int charcode);
|
||||
void app_handleKeyRelease(int scancode, int charcode);
|
||||
void app_handleKeyPress(int scancode);
|
||||
void app_handleKeyRelease(int scancode);
|
||||
void app_handleKeyboardModifiers(bool ctrl, bool shift, bool alt, bool super);
|
||||
void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock);
|
||||
void app_handleEnterEvent(bool entered);
|
||||
|
46
client/include/evdev.h
Normal file
46
client/include/evdev.h
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2025 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 <stdbool.h>
|
||||
|
||||
/**
|
||||
* initialize configuration options
|
||||
*/
|
||||
void evdev_earlyInit(void);
|
||||
|
||||
/**
|
||||
* start the evdev layer
|
||||
*/
|
||||
bool evdev_start(void);
|
||||
|
||||
/**
|
||||
* stop the evdev layer
|
||||
*/
|
||||
void evdev_stop(void);
|
||||
|
||||
/**
|
||||
* grab the keyboard for exclusive access
|
||||
*/
|
||||
void evdev_grabKeyboard(void);
|
||||
|
||||
/**
|
||||
* ungrab the keyboard
|
||||
*/
|
||||
void evdev_ungrabKeyboard(void);
|
@ -183,6 +183,9 @@ struct LG_DisplayServerOps
|
||||
void (*capturePointer)(void);
|
||||
void (*uncapturePointer)(void);
|
||||
|
||||
/* get the character code for the provided scancode */
|
||||
int (*getCharCode)(int sc);
|
||||
|
||||
/* exiting = true if the warp is to leave the window */
|
||||
void (*warpPointer)(int x, int y, bool exiting);
|
||||
|
||||
@ -253,6 +256,7 @@ struct LG_DisplayServerOps
|
||||
DEBUG_ASSERT((x)->ungrabPointer ); \
|
||||
DEBUG_ASSERT((x)->capturePointer ); \
|
||||
DEBUG_ASSERT((x)->uncapturePointer ); \
|
||||
DEBUG_ASSERT((x)->getCharCode ); \
|
||||
DEBUG_ASSERT((x)->warpPointer ); \
|
||||
DEBUG_ASSERT((x)->realignPointer ); \
|
||||
DEBUG_ASSERT((x)->isValidPointerPos ); \
|
||||
|
@ -119,7 +119,7 @@ void app_handleFocusEvent(bool focused)
|
||||
if (g_params.releaseKeysOnFocusLoss)
|
||||
for (int key = 0; key < KEY_MAX; key++)
|
||||
if (g_state.keyDown[key])
|
||||
app_handleKeyRelease(key, 0);
|
||||
app_handleKeyRelease(key);
|
||||
|
||||
g_state.escapeActive = false;
|
||||
|
||||
@ -311,7 +311,7 @@ void app_handleWheelMotion(double motion)
|
||||
g_state.io->MouseWheel -= motion;
|
||||
}
|
||||
|
||||
void app_handleKeyPress(int sc, int charcode)
|
||||
void app_handleKeyPress(int sc)
|
||||
{
|
||||
if (!app_isOverlayMode() || !g_state.io->WantCaptureKeyboard)
|
||||
{
|
||||
@ -327,6 +327,7 @@ void app_handleKeyPress(int sc, int charcode)
|
||||
{
|
||||
g_state.escapeAction = sc;
|
||||
KeybindHandle handle;
|
||||
int charcode = g_state.ds->getCharCode(sc);
|
||||
ll_forEachNL(g_state.bindings, item, handle)
|
||||
{
|
||||
if ((handle->sc && handle->sc == sc ) ||
|
||||
@ -374,7 +375,7 @@ void app_handleKeyPress(int sc, int charcode)
|
||||
}
|
||||
}
|
||||
|
||||
void app_handleKeyRelease(int sc, int charcode)
|
||||
void app_handleKeyRelease(int sc)
|
||||
{
|
||||
if (g_state.escapeActive)
|
||||
{
|
||||
|
238
client/src/evdev.c
Normal file
238
client/src/evdev.c
Normal file
@ -0,0 +1,238 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2025 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 "evdev.h"
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <linux/input.h>
|
||||
#include <string.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
#include "app.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "common/stringlist.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char * path;
|
||||
int fd;
|
||||
bool grabbed;
|
||||
}
|
||||
EvdevDevice;
|
||||
|
||||
struct EvdevState
|
||||
{
|
||||
char * deviceList;
|
||||
EvdevDevice * devices;
|
||||
int deviceCount;
|
||||
int epoll;
|
||||
LGThread * thread;
|
||||
bool grabbed;
|
||||
};
|
||||
|
||||
static struct EvdevState state = {};
|
||||
|
||||
static struct Option options[] =
|
||||
{
|
||||
{
|
||||
.module = "input",
|
||||
.name = "evdev",
|
||||
.description = "csv list of evdev keyboard devices to use "
|
||||
"for capture mode (ie: /dev/input/by-id/usb-some_device-event-kbd)",
|
||||
.type = OPTION_TYPE_STRING,
|
||||
.value.x_string = NULL,
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
void evdev_earlyInit(void)
|
||||
{
|
||||
option_register(options);
|
||||
}
|
||||
|
||||
static int evdev_thread(void * opaque)
|
||||
{
|
||||
struct epoll_event * events = alloca(sizeof(*events) * state.deviceCount);
|
||||
DEBUG_INFO("evdev_thread Started");
|
||||
while(app_isRunning())
|
||||
{
|
||||
int waiting = epoll_wait(state.epoll, events, state.deviceCount, 100);
|
||||
for(int i = 0; i < waiting; ++i)
|
||||
{
|
||||
struct input_event ev;
|
||||
size_t n = read(events[i].data.fd, &ev, sizeof(ev));
|
||||
if (n != sizeof(ev))
|
||||
{
|
||||
DEBUG_WARN("Failed to read evdev event");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!state.grabbed || ev.type != EV_KEY)
|
||||
continue;
|
||||
|
||||
if (ev.value == 1)
|
||||
app_handleKeyPress(ev.code);
|
||||
else if (ev.value == 0)
|
||||
app_handleKeyRelease(ev.code);
|
||||
}
|
||||
}
|
||||
DEBUG_INFO("evdev_thread Stopped");
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool evdev_start(void)
|
||||
{
|
||||
const char * deviceList = option_get_string("input", "evdev");
|
||||
if (!deviceList)
|
||||
return false;
|
||||
|
||||
state.deviceList = strdup(deviceList);
|
||||
StringList sl = stringlist_new(false);
|
||||
|
||||
char * token = strtok(state.deviceList, ",");
|
||||
while(token != NULL)
|
||||
{
|
||||
stringlist_push(sl, token);
|
||||
token = strtok(NULL, ",");
|
||||
}
|
||||
|
||||
state.deviceCount = stringlist_count(sl);
|
||||
state.devices = calloc(state.deviceCount, sizeof(*state.devices));
|
||||
for(int i = 0; i < state.deviceCount; ++i)
|
||||
state.devices[i].path = stringlist_at(sl, i);
|
||||
stringlist_free(&sl);
|
||||
|
||||
// nothing to do if there are no configured devices
|
||||
if (state.deviceCount == 0)
|
||||
return false;
|
||||
|
||||
state.epoll = epoll_create1(0);
|
||||
if (state.epoll < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create epoll (%s)", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 0; i < state.deviceCount; ++i)
|
||||
{
|
||||
EvdevDevice * device = &state.devices[i];
|
||||
device->fd = open(device->path, O_RDWR);
|
||||
if (device->fd < 0)
|
||||
{
|
||||
DEBUG_ERROR("Unable to open %s (%s)", device->path, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
struct epoll_event event =
|
||||
{
|
||||
.events = EPOLLIN,
|
||||
.data.fd = device->fd
|
||||
};
|
||||
|
||||
if (epoll_ctl(state.epoll, EPOLL_CTL_ADD, device->fd, &event) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to add fd to epoll");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Opened: %s", device->path);
|
||||
}
|
||||
|
||||
if (!lgCreateThread("Evdev", evdev_thread, NULL, &state.thread))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the evdev thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void evdev_stop(void)
|
||||
{
|
||||
if (state.deviceList)
|
||||
{
|
||||
free(state.deviceList);
|
||||
state.deviceList = NULL;
|
||||
}
|
||||
|
||||
if (state.thread)
|
||||
{
|
||||
lgJoinThread(state.thread, NULL);
|
||||
state.thread = NULL;
|
||||
}
|
||||
|
||||
if (state.epoll >= 0)
|
||||
{
|
||||
close(state.epoll);
|
||||
state.epoll = 0;
|
||||
}
|
||||
|
||||
for(EvdevDevice * device = state.devices; device->path; ++device)
|
||||
{
|
||||
if (device->fd <= 0)
|
||||
continue;
|
||||
|
||||
close(device->fd);
|
||||
device->fd = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void evdev_grabKeyboard(void)
|
||||
{
|
||||
for(EvdevDevice * device = state.devices; device->path; ++device)
|
||||
{
|
||||
if (device->fd <= 0 || device->grabbed)
|
||||
continue;
|
||||
|
||||
if (ioctl(device->fd, EVIOCGRAB, (void *)1) < 0)
|
||||
{
|
||||
DEBUG_ERROR("EVIOCGRAB=1 failed: %s", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Grabbed %s", device->path);
|
||||
device->grabbed = true;
|
||||
}
|
||||
state.grabbed = true;
|
||||
}
|
||||
|
||||
void evdev_ungrabKeyboard(void)
|
||||
{
|
||||
for(EvdevDevice * device = state.devices; device->path; ++device)
|
||||
{
|
||||
if (device->fd <= 0 || !device->grabbed)
|
||||
continue;
|
||||
|
||||
if (ioctl(device->fd, EVIOCGRAB, (void *)0) < 0)
|
||||
{
|
||||
DEBUG_ERROR("EVIOCGRAB=0 failed: %s", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Ungrabbed %s", device->path);
|
||||
device->grabbed = false;
|
||||
}
|
||||
state.grabbed = false;
|
||||
}
|
@ -64,6 +64,7 @@
|
||||
#include "overlay_utils.h"
|
||||
#include "util.h"
|
||||
#include "render_queue.h"
|
||||
#include "evdev.h"
|
||||
|
||||
// forwards
|
||||
static int renderThread(void * unused);
|
||||
@ -1240,6 +1241,14 @@ static int lg_run(void)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (evdev_start())
|
||||
{
|
||||
DEBUG_INFO("Using evdev for keyboard capture");
|
||||
//override the display server's grab methods if we are using evdev
|
||||
g_state.ds->grabKeyboard = &evdev_grabKeyboard;
|
||||
g_state.ds->ungrabKeyboard = &evdev_ungrabKeyboard;
|
||||
}
|
||||
|
||||
// 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);
|
||||
@ -1891,6 +1900,8 @@ int main(int argc, char * argv[])
|
||||
if (LG_AudioDevs[i]->earlyInit)
|
||||
LG_AudioDevs[i]->earlyInit();
|
||||
|
||||
evdev_earlyInit();
|
||||
|
||||
if (!config_load(argc, argv))
|
||||
return -1;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user