Merge branch 'hotplug' into 'master' Hotplug Hi, So I went on and added the hotplug stuff. It's currently used for two things: 1. reconfigure battery if a power_supply add/remove event has been received (e.g. new battery) 2. update battery if a power_supply change event has been received (e.g. sent for AC connect/disconnect) The second one is useful to make "ac_connect_cmd" and "ac_disconnect_cmd" react instantly. Otherwise they are only executed when the battery update routine is scheduled again. It does not introduce any new library dependencies and the kernel interface is considered as ABI, so there won't be any frequent changes. I also added a little fix on top, that prevents ac_connect_cmd execution during tint2 startup (with AC connected). -- Sebastian See merge request !10
o9000 mrovi9000@gmail.com
7 files changed,
327 insertions(+),
3 deletions(-)
M
CMakeLists.txt
→
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 )@@ -110,6 +113,11 @@ else()
message( FATAL_ERROR "Startup notification support enabled yet dependency not fulfilled: libstartup-notification-1.0" ) 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 )
M
src/battery/battery.c
→
src/battery/battery.c
@@ -67,12 +67,11 @@ int8_t old_minutes = battery_state.time.minutes;
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 @@ battery_found = battery_os_init();
if (!battery_timeout) battery_timeout = add_timeout(10, 30000, update_battery_tick, 0, &battery_timeout); + + update_battery(); } char* battery_get_tooltip(void* obj) {
M
src/battery/battery.h
→
src/battery/battery.h
@@ -94,6 +94,7 @@
// freed memory void cleanup_battery(); +void update_battery_tick(void* arg); int update_battery(); void init_battery();
M
src/battery/linux.c
→
src/battery/linux.c
@@ -23,6 +23,7 @@ #include <stdlib.h>
#include "common.h" #include "battery.h" +#include "uevent.h" enum psy_type { PSY_UNKNOWN,@@ -59,6 +60,28 @@ /* values */
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 @@
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;@@ -256,6 +282,9 @@ }
} g_dir_close(directory); + + uevent_register_notifier(&psy_change); + uevent_register_notifier(&psy_plug); return batteries != NULL; }
M
src/tint.c
→
src/tint.c
@@ -49,6 +49,7 @@ #include "panel.h"
#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 @@ close(sn_pipe[0]);
} } #endif + + uevent_cleanup(); }@@ -1201,6 +1204,8 @@ dnd_atom = None;
dnd_sent_request = 0; dnd_launcher_exec = 0; + int ufd = uevent_init(); + // sigset_t empty_mask; // sigemptyset(&empty_mask);@@ -1243,6 +1248,10 @@ if (sn_pipe_valid) {
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 @@ select_timeout = 0;
// 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) {
A
src/util/uevent.c
@@ -0,0 +1,204 @@
+/************************************************************************** +* +* Linux Kernel uevent handler +* +* Copyright (C) 2015 Sebastian Reichel <sre@ring0.de> +* +* 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 <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/types.h> + +#include <linux/types.h> +#include <linux/netlink.h> + +#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
A
src/util/uevent.h
@@ -0,0 +1,70 @@
+/************************************************************************** +* +* Linux Kernel uevent handler +* +* Copyright (C) 2015 Sebastian Reichel <sre@ring0.de> +* +* 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