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/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) { 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; } 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