From 46a6d2c2ad239c0256b7df6b0529180c13707afc Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Sat, 8 Aug 2015 05:49:21 +0200 Subject: [PATCH 1/3] 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 From 0d0b1249c74124826a8cc4c3542eb9a96c383b4c Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Sat, 8 Aug 2015 05:53:10 +0200 Subject: [PATCH 2/3] Battery: Handle Linux kernel events The Kernel sends notifications for AC (un)plug and some other important power supply events, so that we can instantly update the widget. Apart from that it sends notifications for any added or removed power supplies, so that the battery support can be reinitialized (useful on systems with removable batteries). --- src/battery/battery.h | 1 + src/battery/linux.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/battery/battery.h b/src/battery/battery.h index 8142760..c39434e 100644 --- a/src/battery/battery.h +++ b/src/battery/battery.h @@ -94,6 +94,7 @@ void default_battery(); // freed memory void cleanup_battery(); +void update_battery_tick(void* arg); int update_battery(); void init_battery(); diff --git a/src/battery/linux.c b/src/battery/linux.c index cf26a44..1897706 100644 --- a/src/battery/linux.c +++ b/src/battery/linux.c @@ -23,6 +23,7 @@ #include "common.h" #include "battery.h" +#include "uevent.h" enum psy_type { PSY_UNKNOWN, @@ -59,6 +60,28 @@ struct psy_mains { gboolean online; }; +static void uevent_battery_update() { + update_battery_tick(NULL); +} +static struct uevent_notify psy_change = { + UEVENT_CHANGE, + "power_supply", + NULL, + uevent_battery_update +}; + +static void uevent_battery_plug() { + printf("reinitialize batteries after HW change\n"); + cleanup_battery(); + init_battery(); +} +static struct uevent_notify psy_plug = { + UEVENT_ADD | UEVENT_REMOVE, + "power_supply", + NULL, + uevent_battery_plug +}; + #define RETURN_ON_ERROR(err) if(error) { g_error_free(err); return FALSE; } static GList *batteries = NULL; @@ -173,6 +196,9 @@ static gboolean init_linux_mains(struct psy_mains *ac) { void battery_os_free() { GList *l = batteries; + uevent_unregister_notifier(&psy_change); + uevent_unregister_notifier(&psy_plug); + while (l != NULL) { GList *next = l->next; struct psy_battery *bat = l->data; @@ -257,6 +283,9 @@ gboolean battery_os_init() { g_dir_close(directory); + uevent_register_notifier(&psy_change); + uevent_register_notifier(&psy_plug); + return batteries != NULL; } From 9e85b6dcfe5a2b5ab17724d21cd4489ba3eb9587 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Sat, 8 Aug 2015 06:39:45 +0200 Subject: [PATCH 3/3] Battery: Avoid executing ac_connected cmd on startup Previously ac_connected_cmd was executed during tint2 startup (if AC is connected during startup). --- src/battery/battery.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/battery/battery.c b/src/battery/battery.c index ca024a0..ba7b82e 100644 --- a/src/battery/battery.c +++ b/src/battery/battery.c @@ -67,12 +67,11 @@ void update_battery_tick(void* arg) if (!battery_found) { init_battery(); + old_ac_connected = battery_state.ac_connected; } if (update_battery() != 0) { - // Reconfigure + // Try to reconfigure on failed update init_battery(); - // Try again - update_battery(); } if (old_ac_connected != battery_state.ac_connected) { @@ -185,6 +184,8 @@ void init_battery() if (!battery_timeout) battery_timeout = add_timeout(10, 30000, update_battery_tick, 0, &battery_timeout); + + update_battery(); } char* battery_get_tooltip(void* obj) {