From 46a6d2c2ad239c0256b7df6b0529180c13707afc Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Sat, 8 Aug 2015 05:49:21 +0200 Subject: [PATCH] Add Linux kernel event handling code This is a simple handler for uevents send by the Linux kernel. --- CMakeLists.txt | 8 ++ src/tint.c | 11 +++ src/util/uevent.c | 204 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/uevent.h | 70 ++++++++++++++++ 4 files changed, 293 insertions(+) create mode 100644 src/util/uevent.c create mode 100644 src/util/uevent.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2133a39..155672e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,9 @@ option( ENABLE_EXAMPLES "Install additional tin2rc examples" ON ) option( ENABLE_RSVG "Rsvg support (launcher only)" ON ) option( ENABLE_SN "Startup notification support" ON ) option( ENABLE_ASAN "Build tint2 with AddressSanitizer" OFF ) +if( CMAKE_SYSTEM_NAME STREQUAL "Linux" ) + option( ENABLE_UEVENT "Kernel event handling support" ON ) +endif( CMAKE_SYSTEM_NAME STREQUAL "Linux" ) include( FindPkgConfig ) include( CheckLibraryExists ) @@ -111,6 +114,11 @@ if( ENABLE_SN ) endif( SN_FOUND ) endif( ENABLE_SN) +if( ENABLE_UEVENT ) + add_definitions( -DENABLE_UEVENT ) + set( SOURCES ${SOURCES} src/util/uevent.c) +endif( ENABLE_UEVENT ) + if( ENABLE_TINT2CONF ) add_definitions( -DHAVE_VERSION_H ) add_subdirectory( src/tint2conf ) diff --git a/src/tint.c b/src/tint.c index c2f3dfc..c03e089 100644 --- a/src/tint.c +++ b/src/tint.c @@ -49,6 +49,7 @@ #include "tooltip.h" #include "timer.h" #include "xsettings-client.h" +#include "uevent.h" // Drag and Drop state variables Window dnd_source_window; @@ -340,6 +341,8 @@ void cleanup() } } #endif + + uevent_cleanup(); } @@ -1201,6 +1204,8 @@ start: dnd_sent_request = 0; dnd_launcher_exec = 0; + int ufd = uevent_init(); + // sigset_t empty_mask; // sigemptyset(&empty_mask); @@ -1243,6 +1248,10 @@ start: FD_SET (sn_pipe[0], &fdset); maxfd = maxfd < sn_pipe[0] ? sn_pipe[0] : maxfd; } + if (ufd > 0) { + FD_SET (ufd, &fdset); + maxfd = maxfd < ufd ? ufd : maxfd; + } update_next_timeout(); if (next_timeout.tv_sec >= 0 && next_timeout.tv_usec >= 0) select_timeout = &next_timeout; @@ -1251,6 +1260,8 @@ start: // Wait for X Event or a Timer if (XPending(server.dsp) > 0 || select(maxfd+1, &fdset, 0, 0, select_timeout) >= 0) { + uevent_handler(); + if (sn_pipe_valid) { char buffer[1]; while (read(sn_pipe[0], buffer, sizeof(buffer)) > 0) { diff --git a/src/util/uevent.c b/src/util/uevent.c new file mode 100644 index 0000000..8dd18b3 --- /dev/null +++ b/src/util/uevent.c @@ -0,0 +1,204 @@ +/************************************************************************** +* +* Linux Kernel uevent handler +* +* Copyright (C) 2015 Sebastian Reichel +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License version 2 +* or any later version as published by the Free Software Foundation. +* +* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**************************************************************************/ + +#ifdef ENABLE_UEVENT + +#include +#include +#include + +#include +#include + +#include +#include + +#include "common.h" +#include "uevent.h" + +static int ueventfd = -1; +static struct sockaddr_nl nls; +static GList *notifiers = NULL; + +static const char* has_prefix(const char *str, const char *end, const char *prefix, size_t prefixlen) { + if ((end-str) < prefixlen) + return NULL; + + if (!memcmp(str, prefix, prefixlen)) + return str + prefixlen; + + return NULL; +} + +#define HAS_CONST_PREFIX(str,end,prefix) has_prefix((str),end,prefix,sizeof(prefix)-1) + +static void uevent_param_free(gpointer data) { + struct uevent_parameter *param = data; + free(param->key); + free(param->val); + free(param); +} + +static void uevent_free(struct uevent *ev) { + free(ev->path); + free(ev->subsystem); + g_list_free_full(ev->params, uevent_param_free); + free(ev); +} + +static struct uevent *uevent_new(char *buffer, int size) { + struct uevent *ev; + const char* s = buffer; + const char* end = s + size; + gboolean first = TRUE; + + if (size == 0) + return NULL; + + ev = calloc(1, sizeof(*ev)); + if (!ev) + return NULL; + + /* ensure nul termination required by strlen() */ + buffer[size-1] = '\0'; + + for (; s < end; s += strlen(s) + 1) { + if (first) { + const char *p; + for (p = s; *p != '@'; p++) { + if (!*p) { + /* error: kernel events contain @ */ + /* triggered by udev events, though */ + free(ev); + return NULL; + } + } + ev->path = strdup(p+1); + first = FALSE; + } else { + const char* val; + if ((val = HAS_CONST_PREFIX(s, end, "ACTION=")) != NULL) { + if (!strcmp(val, "add")) + ev->action = UEVENT_ADD; + else if (!strcmp(val, "remove")) + ev->action = UEVENT_REMOVE; + else if (!strcmp(val, "change")) + ev->action = UEVENT_CHANGE; + else + ev->action = UEVENT_UNKNOWN; + } else if ((val = HAS_CONST_PREFIX(s, end, "SEQNUM=")) != NULL) { + ev->sequence = atoi(val); + } else if ((val = HAS_CONST_PREFIX(s, end, "SUBSYSTEM=")) != NULL) { + ev->subsystem = strdup(val); + } else { + val = strchr(s, '='); + if (val) { + struct uevent_parameter *param = malloc(sizeof(*param)); + if(param) { + param->key = strndup(s, val-s); + param->val = strdup(val+1); + ev->params = g_list_append(ev->params, param); + } + } + } + } + } + + return ev; +} + +void uevent_register_notifier(struct uevent_notify *nb) { + notifiers = g_list_append(notifiers, nb); +} + +void uevent_unregister_notifier(struct uevent_notify *nb) { + GList *l = notifiers; + + while (l != NULL) { + GList *next = l->next; + struct uevent_notify *lnb = l->data; + + if(memcmp(nb, lnb, sizeof(struct uevent_notify)) == 0) + notifiers = g_list_delete_link(notifiers, l); + + l = next; + } +} + +void uevent_handler() { + struct uevent *ev; + char buf[512]; + GList *l; + + if (ueventfd < 0) + return; + + int len = recv(ueventfd, buf, sizeof(buf), MSG_DONTWAIT); + if (len < 0) + return; + + ev = uevent_new(buf, len); + if(ev) { + for (l = notifiers; l != NULL; l = l->next) { + struct uevent_notify *nb = l->data; + + if (!(ev->action & nb->action)) + continue; + + if (nb->subsystem && strcmp(ev->subsystem, nb->subsystem)) + continue; + + nb->cb(ev, nb->userdata); + } + + uevent_free(ev); + } +} + +int uevent_init() { + /* Open hotplug event netlink socket */ + memset(&nls,0,sizeof(struct sockaddr_nl)); + nls.nl_family = AF_NETLINK; + nls.nl_pid = getpid(); + nls.nl_groups = -1; + + /* open socket */ + ueventfd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); + if (ueventfd < 0) { + fprintf(stderr, "Error: socket open failed\n"); + return -1; + } + + /* Listen to netlink socket */ + if (bind(ueventfd, (void *)&nls, sizeof(struct sockaddr_nl))) { + fprintf(stderr, "Bind failed\n"); + return -1; + } + + printf("Kernel uevent interface initialized...\n"); + + return ueventfd; +} + +void uevent_cleanup() { + if (ueventfd >= 0) + close(ueventfd); +} + +#endif diff --git a/src/util/uevent.h b/src/util/uevent.h new file mode 100644 index 0000000..7b3c2a4 --- /dev/null +++ b/src/util/uevent.h @@ -0,0 +1,70 @@ +/************************************************************************** +* +* Linux Kernel uevent handler +* +* Copyright (C) 2015 Sebastian Reichel +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License version 2 +* or any later version as published by the Free Software Foundation. +* +* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**************************************************************************/ + +#ifndef UEVENT_H +#define UEVENT_H + +enum uevent_action { + UEVENT_UNKNOWN = 0x01, + UEVENT_ADD = 0x02, + UEVENT_REMOVE = 0x04, + UEVENT_CHANGE = 0x08, +}; + +struct uevent_parameter { + char *key; + char *val; +}; + +struct uevent { + char *path; + enum uevent_action action; + int sequence; + char *subsystem; + GList *params; +}; + +struct uevent_notify { + int action; /* bitfield */ + char *subsystem; /* NULL => any */ + void *userdata; + + void (*cb)(struct uevent *e, void *userdata); +}; + +#if ENABLE_UEVENT +int uevent_init(); +void uevent_cleanup(); +void uevent_handler(); + +void uevent_register_notifier(struct uevent_notify *nb); +void uevent_unregister_notifier(struct uevent_notify *nb); +#else +static inline int uevent_init() { + return -1; +} + +static inline void uevent_cleanup() { } +static inline void uevent_handler() { } + +static inline void uevent_register_notifier(struct uevent_notify *nb) { } +static inline void uevent_unregister_notifier(struct uevent_notify *nb) { } +#endif + +#endif