summaryrefslogtreecommitdiffstats
path: root/tdekbdledsync
diff options
context:
space:
mode:
Diffstat (limited to 'tdekbdledsync')
-rw-r--r--tdekbdledsync/CMakeLists.txt30
-rw-r--r--tdekbdledsync/getfd.c84
-rw-r--r--tdekbdledsync/getfd.h1
-rw-r--r--tdekbdledsync/main.cpp503
4 files changed, 618 insertions, 0 deletions
diff --git a/tdekbdledsync/CMakeLists.txt b/tdekbdledsync/CMakeLists.txt
new file mode 100644
index 000000000..a7ba5c621
--- /dev/null
+++ b/tdekbdledsync/CMakeLists.txt
@@ -0,0 +1,30 @@
+#################################################
+#
+# (C) 2013 Timothy Pearson
+# kb9vqf (AT) pearsoncomputing.net
+#
+# Improvements and feedback are welcome
+#
+# This file is released under GPL >= 2
+#
+#################################################
+
+include_directories(
+ ${TDE_INCLUDE_DIR}
+ ${TQT_INCLUDE_DIRS}
+)
+
+link_directories(
+ ${TQT_LIBRARY_DIRS}
+)
+
+
+##### tdekbdledsync (executable) ################
+
+tde_add_executable( tdekbdledsync
+ SOURCES getfd.c main.cpp
+ LINK udev X11
+ DESTINATION ${BIN_INSTALL_DIR}
+ SETUID
+)
+
diff --git a/tdekbdledsync/getfd.c b/tdekbdledsync/getfd.c
new file mode 100644
index 000000000..589d8dd31
--- /dev/null
+++ b/tdekbdledsync/getfd.c
@@ -0,0 +1,84 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <linux/kd.h>
+#include "getfd.h"
+
+/*
+ * getfd.c
+ *
+ * Get an fd for use with kbd/console ioctls.
+ * We try several things because opening /dev/console will fail
+ * if someone else used X (which does a chown on /dev/console).
+ */
+
+static int
+is_a_console(int fd) {
+ char arg;
+
+ arg = 0;
+ return (ioctl(fd, KDGKBTYPE, &arg) == 0
+ && ((arg == KB_101) || (arg == KB_84)));
+}
+
+static int
+open_a_console(const char *fnam) {
+ int fd;
+
+ /*
+ * For ioctl purposes we only need some fd and permissions
+ * do not matter. But setfont:activatemap() does a write.
+ */
+ fd = open(fnam, O_RDWR);
+ if (fd < 0 && errno == EACCES)
+ fd = open(fnam, O_WRONLY);
+ if (fd < 0 && errno == EACCES)
+ fd = open(fnam, O_RDONLY);
+ if (fd < 0)
+ return -1;
+ if (!is_a_console(fd)) {
+ close(fd);
+ return -1;
+ }
+ return fd;
+}
+
+int getfd(const char *fnam) {
+ int fd;
+
+ if (fnam) {
+ fd = open_a_console(fnam);
+ if (fd >= 0)
+ return fd;
+ fprintf(stderr,
+ ("Couldnt open %s\n"), fnam);
+ exit(1);
+ }
+
+ fd = open_a_console("/dev/tty");
+ if (fd >= 0)
+ return fd;
+
+ fd = open_a_console("/dev/tty0");
+ if (fd >= 0)
+ return fd;
+
+ fd = open_a_console("/dev/vc/0");
+ if (fd >= 0)
+ return fd;
+
+ fd = open_a_console("/dev/console");
+ if (fd >= 0)
+ return fd;
+
+ for (fd = 0; fd < 3; fd++)
+ if (is_a_console(fd))
+ return fd;
+
+ fprintf(stderr,
+ ("Couldnt get a file descriptor referring to the console\n"));
+ exit(1); /* total failure */
+}
diff --git a/tdekbdledsync/getfd.h b/tdekbdledsync/getfd.h
new file mode 100644
index 000000000..991f33d4a
--- /dev/null
+++ b/tdekbdledsync/getfd.h
@@ -0,0 +1 @@
+extern int getfd(const char *fnam);
diff --git a/tdekbdledsync/main.cpp b/tdekbdledsync/main.cpp
new file mode 100644
index 000000000..a439ebd41
--- /dev/null
+++ b/tdekbdledsync/main.cpp
@@ -0,0 +1,503 @@
+/*
+Copyright 2011-2013 Timothy Pearson <[email protected]>
+
+This file is part of tdekbdledsync, the TDE Keyboard LED Synchronization Daemon
+
+tdekbdledsync 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 3
+of the License, or (at your option) any later version.
+
+tdekbdledsync 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 tdekbdledsync. If not, see http://www.gnu.org/licenses/.
+
+*/
+
+// The idea here is to periodically read the Xorg core keyboard state, and then forcibly set the physical LED states on all attached keyboards to match (via the event interface)
+// Once every half second should work well enough on most systems
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <exception>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <dirent.h>
+#include <linux/vt.h>
+#include <linux/input.h>
+#include <linux/uinput.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <termios.h>
+#include <signal.h>
+#include <stdint.h>
+extern "C" {
+#include <libudev.h>
+#include "getfd.h"
+}
+#include <libgen.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/XKBlib.h>
+#include <X11/extensions/XTest.h>
+#include <X11/keysym.h>
+
+using namespace std;
+
+// WARNING
+// MAX_KEYBOARDS must be greater than or equal to MAX_INPUT_NODE
+#define MAX_KEYBOARDS 128
+#define MAX_INPUT_NODE 128
+
+#define TestBit(bit, array) (array[(bit) / 8] & (1 << ((bit) % 8)))
+
+typedef unsigned char byte;
+
+char filename[32];
+char key_bitmask[(KEY_MAX + 7) / 8];
+
+int keyboard_fd_num;
+int keyboard_fds[MAX_KEYBOARDS];
+
+Display* display = NULL;
+
+int lockfd = -1;
+char lockFileName[256];
+
+// --------------------------------------------------------------------------------------
+// Useful function from Stack Overflow
+// http://stackoverflow.com/questions/874134/find-if-string-endswith-another-string-in-c
+// --------------------------------------------------------------------------------------
+/* returns 1 iff str ends with suffix */
+int str_ends_with(const char * str, const char * suffix) {
+
+ if( str == NULL || suffix == NULL )
+ return 0;
+
+ size_t str_len = strlen(str);
+ size_t suffix_len = strlen(suffix);
+
+ if(suffix_len > str_len)
+ return 0;
+
+ return 0 == strncmp( str + str_len - suffix_len, suffix, suffix_len );
+}
+// --------------------------------------------------------------------------------------
+
+// --------------------------------------------------------------------------------------
+// Useful function from Stack Overflow
+// http://stackoverflow.com/questions/1599459/optimal-lock-file-method
+// --------------------------------------------------------------------------------------
+int tryGetLock(char const *lockName) {
+ mode_t m = umask( 0 );
+ int fd = open( lockName, O_RDWR|O_CREAT, 0666 );
+ umask( m );
+ if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 ) {
+ close( fd );
+ fd = -1;
+ }
+ return fd;
+}
+
+// --------------------------------------------------------------------------------------
+// Useful function from Stack Overflow
+// http://stackoverflow.com/questions/1599459/optimal-lock-file-method
+// --------------------------------------------------------------------------------------
+void releaseLock(int fd, char const *lockName) {
+ if( fd < 0 ) {
+ return;
+ }
+ remove( lockName );
+ close( fd );
+}
+
+
+// --------------------------------------------------------------------------------------
+// Get the VT number X is running on
+// (code taken from GDM, daemon/getvt.c, GPLv2+)
+// --------------------------------------------------------------------------------------
+int get_x_vtnum(Display *dpy)
+{
+ Atom prop;
+ Atom actualtype;
+ int actualformat;
+ unsigned long nitems;
+ unsigned long bytes_after;
+ unsigned char *buf;
+ int num;
+
+ prop = XInternAtom (dpy, "XFree86_VT", False);
+ if (prop == None)
+ return -1;
+
+ if (XGetWindowProperty (dpy, DefaultRootWindow (dpy), prop, 0, 1,
+ False, AnyPropertyType, &actualtype, &actualformat,
+ &nitems, &bytes_after, &buf)) {
+ return -1;
+ }
+
+ if (nitems != 1) {
+ XFree (buf);
+ return -1;
+ }
+
+ switch (actualtype) {
+ case XA_CARDINAL:
+ case XA_INTEGER:
+ case XA_WINDOW:
+ switch (actualformat) {
+ case 8:
+ num = (*(uint8_t *)(void *)buf);
+ break;
+ case 16:
+ num = (*(uint16_t *)(void *)buf);
+ break;
+ case 32:
+ num = (*(uint32_t *)(void *)buf);
+ break;
+ default:
+ XFree (buf);
+ return -1;
+ }
+ break;
+ default:
+ XFree (buf);
+ return -1;
+ }
+
+ XFree (buf);
+
+ return num;
+}
+// --------------------------------------------------------------------------------------
+
+// --------------------------------------------------------------------------------------
+// Get the specified xkb mask modifier
+// (code taken from numlockx)
+// --------------------------------------------------------------------------------------
+unsigned int xkb_mask_modifier(XkbDescPtr xkb, const char *name) {
+ int i;
+ if( !xkb || !xkb->names ) {
+ return 0;
+ }
+ for( i = 0; i < XkbNumVirtualMods; i++ ) {
+ char* modStr = XGetAtomName( xkb->dpy, xkb->names->vmods[i] );
+ if( modStr != NULL && strcmp(name, modStr) == 0 ) {
+ unsigned int mask;
+ XkbVirtualModsToReal( xkb, 1 << i, &mask );
+ return mask;
+ }
+ }
+ return 0;
+}
+// --------------------------------------------------------------------------------------
+
+// --------------------------------------------------------------------------------------
+// Get the capslock xkb mask modifier
+// --------------------------------------------------------------------------------------
+unsigned int xkb_capslock_mask() {
+ return LockMask;
+}
+// --------------------------------------------------------------------------------------
+
+// --------------------------------------------------------------------------------------
+// Get the numlock xkb mask modifier
+// (code taken from numlockx)
+// --------------------------------------------------------------------------------------
+unsigned int xkb_numlock_mask() {
+ XkbDescPtr xkb;
+ if(( xkb = XkbGetKeyboard(display, XkbAllComponentsMask, XkbUseCoreKbd )) != NULL ) {
+ unsigned int mask = xkb_mask_modifier( xkb, "NumLock" );
+ XkbFreeKeyboard( xkb, 0, True );
+ return mask;
+ }
+ return 0;
+}
+// --------------------------------------------------------------------------------------
+
+// --------------------------------------------------------------------------------------
+// Get the scroll lock xkb mask modifier
+// (code taken from numlockx and modified)
+// --------------------------------------------------------------------------------------
+unsigned int xkb_scrolllock_mask() {
+ XkbDescPtr xkb;
+ if(( xkb = XkbGetKeyboard(display, XkbAllComponentsMask, XkbUseCoreKbd )) != NULL ) {
+ unsigned int mask = xkb_mask_modifier( xkb, "ScrollLock" );
+ XkbFreeKeyboard( xkb, 0, True );
+ return mask;
+ }
+ return 0;
+}
+// --------------------------------------------------------------------------------------
+
+
+int find_keyboards() {
+ int i, j;
+ int fd;
+ char name[256] = "Unknown";
+
+ keyboard_fd_num = 0;
+ for (i=0; i<MAX_KEYBOARDS; i++) {
+ keyboard_fds[i] = 0;
+ }
+
+ for (i=0; i<MAX_INPUT_NODE; i++) {
+ snprintf(filename, sizeof(filename), "/dev/input/event%d", i);
+
+ fd = open(filename, O_RDWR|O_SYNC);
+ if (fd >= 0) {
+ ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), key_bitmask);
+
+ // Ensure that we do not detect tsak faked keyboards
+ ioctl (fd, EVIOCGNAME(sizeof(name)), name);
+ if (str_ends_with(name, "+tsak") == 0) {
+ struct input_id input_info;
+ ioctl (fd, EVIOCGID, &input_info);
+ if ((input_info.vendor != 0) && (input_info.product != 0)) {
+ /* We assume that anything that has an alphabetic key in the
+ QWERTYUIOP range in it is the main keyboard. */
+ for (j = KEY_Q; j <= KEY_P; j++) {
+ if (TestBit(j, key_bitmask)) {
+ keyboard_fds[keyboard_fd_num] = fd;
+ }
+ }
+ }
+ }
+
+ if (keyboard_fds[keyboard_fd_num] == 0) {
+ close(fd);
+ }
+ else {
+ keyboard_fd_num++;
+ }
+ }
+ }
+ return 0;
+}
+
+void handle_sigterm(int signum) {
+ if (lockfd >= 0) {
+ releaseLock(lockfd, lockFileName);
+ }
+ exit(0);
+}
+
+int main() {
+ int current_keyboard;
+ char name[256] = "Unknown";
+ unsigned int states;
+ struct input_event ev;
+ struct vt_stat vtstat;
+ int vt_fd;
+ int x11_vt_num = -1;
+// XEvent xev;
+ XkbStateRec state;
+
+ bool num_lock_set = false;
+ bool caps_lock_set = false;
+ bool scroll_lock_set = false;
+
+ int num_lock_mask;
+ int caps_lock_mask;
+ int scroll_lock_mask;
+
+ int evBase;
+ int errBase;
+
+ // Register cleanup handlers
+ struct sigaction action;
+ memset(&action, 0, sizeof(struct sigaction));
+ action.sa_handler = handle_sigterm;
+ sigaction(SIGTERM, &action, NULL);
+
+ // Open X11 display
+ display = XOpenDisplay(NULL);
+ if (!display) {
+ printf ("[tdekbdledsync] Unable to open X11 display!\n");
+ return -1;
+ }
+
+ // Ensure only one process is running on a given display
+ sprintf(lockFileName, "/var/lock/tdekbdledsync-%s.lock", XDisplayString(display));
+ lockfd = tryGetLock(lockFileName);
+ if (lockfd < 0) {
+ printf ("[tdekbdledsync] Another instance of this program is already running on this X11 display!\n[tdekbdledsync] Lockfile detected at '%s'\n", lockFileName);
+ return -2;
+ }
+
+ // Set up Xkb extension
+ int i1, mn, mj;
+ mj = XkbMajorVersion;
+ mn = XkbMinorVersion;
+ if (!XkbQueryExtension(display, &i1, &evBase, &errBase, &mj, &mn)) {
+ printf("[tdekbdledsync] Server doesn't support a compatible XKB\n");
+ releaseLock(lockfd, lockFileName);
+ return -3;
+ }
+ XkbSelectEvents(display, XkbUseCoreKbd, XkbStateNotifyMask, XkbStateNotifyMask);
+
+ // Get X server VT number
+ x11_vt_num = get_x_vtnum(display);
+
+ // Open console socket
+ vt_fd = getfd(NULL);
+
+ // Monitor for hotplugged keyboards
+ struct udev *udev;
+ struct udev_device *dev;
+ struct udev_monitor *mon;
+ struct timeval tv;
+
+ // Create the udev object
+ udev = udev_new();
+ if (!udev) {
+ printf("[tdekbdledsync] Cannot connect to udev interface\n");
+ releaseLock(lockfd, lockFileName);
+ return -4;
+ }
+
+ // Set up a udev monitor to monitor input devices
+ mon = udev_monitor_new_from_netlink(udev, "udev");
+ udev_monitor_filter_add_match_subsystem_devtype(mon, "input", NULL);
+ udev_monitor_enable_receiving(mon);
+
+ while (1) {
+ // Get masks
+ num_lock_mask = xkb_numlock_mask();
+ caps_lock_mask = xkb_capslock_mask();
+ scroll_lock_mask = xkb_scrolllock_mask();
+
+ // Find keyboards
+ find_keyboards();
+ if (keyboard_fd_num == 0) {
+ printf ("[tdekbdledsync] Could not find any usable keyboard(s)!\n");
+ releaseLock(lockfd, lockFileName);
+ return -5;
+ }
+ else {
+ fprintf(stderr, "[tdekbdledsync] Found %d keyboard(s)\n", keyboard_fd_num);
+
+ for (current_keyboard=0;current_keyboard<keyboard_fd_num;current_keyboard++) {
+ // Print device name
+ ioctl(keyboard_fds[current_keyboard], EVIOCGNAME (sizeof (name)), name);
+ fprintf(stderr, "[tdekbdledsync] Syncing keyboard: (%s)\n", name);
+ }
+
+ while (1) {
+ // Get current active VT
+ if (ioctl(vt_fd, VT_GETSTATE, &vtstat)) {
+ fprintf(stderr, "[tdekbdledsync] Unable to get current VT!\n");
+ releaseLock(lockfd, lockFileName);
+ return -6;
+ }
+
+ if (x11_vt_num == vtstat.v_active) {
+ // Get Virtual Core keyboard status
+ if (XkbGetIndicatorState(display, XkbUseCoreKbd, &states) != Success) {
+ fprintf(stderr, "[tdekbdledsync] Unable to query X11 Virtual Core keyboard!\n");
+ releaseLock(lockfd, lockFileName);
+ return -7;
+ }
+
+ XkbGetState(display, XkbUseCoreKbd, &state);
+
+ caps_lock_set = (state.mods & caps_lock_mask);
+ num_lock_set = (state.mods & num_lock_mask);
+ scroll_lock_set = (state.mods & scroll_lock_mask);
+
+ for (current_keyboard=0;current_keyboard<keyboard_fd_num;current_keyboard++) {
+ // Set LEDs
+ ev.type = EV_LED;
+ ev.code = LED_CAPSL;
+ ev.value = caps_lock_set;
+ if (write(keyboard_fds[current_keyboard], &ev, sizeof(ev)) < 0) {
+ fprintf(stderr, "[tdekbdledsync] Unable to set LED state\n");
+ }
+
+ ev.type = EV_LED;
+ ev.code = LED_NUML;
+ ev.value = num_lock_set;
+ if (write(keyboard_fds[current_keyboard], &ev, sizeof(ev)) < 0) {
+ fprintf(stderr, "[tdekbdledsync] Unable to set LED state\n");
+ }
+
+ ev.type = EV_LED;
+ ev.code = LED_SCROLLL;
+ ev.value = scroll_lock_set;
+ if (write(keyboard_fds[current_keyboard], &ev, sizeof(ev)) < 0) {
+ fprintf(stderr, "[tdekbdledsync] Unable to set LED state\n");
+ }
+ }
+ }
+
+ // Check the hotplug monitoring process to see if any keyboards were added or removed
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(udev_monitor_get_fd(mon), &readfds);
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ int fdcount = select(udev_monitor_get_fd(mon)+1, &readfds, NULL, NULL, &tv);
+ if (fdcount < 0) {
+ if (errno == EINTR) {
+ fprintf(stderr, "[tdekbdledsync] Signal caught in hotplug monitoring process; ignoring\n");
+ }
+ else {
+ fprintf(stderr, "[tdekbdledsync] Select failed on udev file descriptor in hotplug monitoring process\n");
+ }
+ }
+ else {
+ dev = udev_monitor_receive_device(mon);
+ if (dev) {
+ if (strcmp(udev_device_get_action(dev), "add") == 0) {
+ // Reload keyboards
+ break;
+ }
+ if (strcmp(udev_device_get_action(dev), "remove") == 0) {
+ // Reload keyboards
+ break;
+ }
+ }
+ }
+
+ // Poll
+ usleep(250*1000);
+
+// // Wait for an Xkb event
+// // FIXME
+// // This prevents the udev hotplug monitor from working, as XNextEvent does not stop blocking when a keyboard hotplug occurs
+// while (1) {
+// XNextEvent(display, &xev);
+// if (xev.type == evBase + XkbEventCode) {
+// XkbEvent *xkb_event = reinterpret_cast<XkbEvent*>(&xev);
+// if (xkb_event->any.xkb_type & XkbStateNotify) {
+// if ((xkb_event->state.changed & XkbModifierStateMask) || (xkb_event->state.changed & XkbModifierBaseMask)) {
+// // Modifier state has changed
+// // Synchronize keyboard indicators
+// break;
+// }
+// }
+// }
+// }
+ }
+ }
+
+ // Close all keyboard file descriptors
+ for (int current_keyboard=0;current_keyboard<keyboard_fd_num;current_keyboard++) {
+ close(keyboard_fds[current_keyboard]);
+ }
+ }
+
+ releaseLock(lockfd, lockFileName);
+ return 0;
+}