/* $Id: plugin_dbus.c -1 $ * $URL: https://ssl.bulix.org/svn/lcd4linux/trunk/plugin_dbus.c $ * * plugin template * * Copyright (C) 2009 Edward Martin * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 The LCD4Linux Team * * This file is part of LCD4Linux. * * LCD4Linux 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 2, or (at your option) * any later version. * * LCD4Linux 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., 675 Mass Ave, Cambridge, MA 02139, USA. * */ /* * exported functions: * * int plugin_init_dbus (void) * adds various functions * */ /* define the include files you need */ #include "config.h" #include #include #include #include #include /* these should always be included */ #include "debug.h" #include "plugin.h" #include "cfg.h" #include "timer.h" #ifdef WITH_DMALLOC #include #endif #include "event.h" #include #include #include /* * Internal Text Storage (to catch the events) */ //a buffer of the most recent results (store the strings you see on the screen) typedef struct { int argc; char **arguments; } dbus_signal_t; static struct { int signals; dbus_signal_t *a; } dbus_results = { 0, NULL}; //free all resources for the text of a signal static int clear_signal_txt(const int sig); //return a pointer to the struct of text for the signal static dbus_signal_t *get_signal_txt(const int sig); //set the struct of text for the signal static int set_signal_txt(const int sig, const dbus_signal_t * data); /* * DBus Functions/types */ //the max that a message may be in characters (if you have a screen that fits more go crazy) static const int LCD_MAX_MSG_LEN = 255; #define DBUS_MAX_SIGNALS 64 static const char *DBUS_PLUGIN_SECTION = "Plugin:DBus"; /* * This connects to dbus and gets relavant things for the LCD */ typedef struct lcd_sig_t lcd_sig_t; //do not look inside, its private! struct lcd_sig_t { void *user_data; void (*callback) (void *user_data, int argc, char **argv); void (*user_free) (void *); char *sender; char *path; char *interface; char *member; char *rule; }; typedef struct { int id; char *event_name; } handle_signal_t; static DBusConnection *sessconn; static DBusConnection *sysconn; static DBusError err; static lcd_sig_t *lcd_registered_signals[DBUS_MAX_SIGNALS]; //used mostly for freeing resources static int registered_sig_count = 0; //takes the signal, matches it against a rule, and sends the correct agrument, as a string, to the screen static DBusHandlerResult lcd_sig_received(DBusConnection * connection, DBusMessage * message, void *sigv); //frees all resources for a signal (use lcd_unregister_signal instead) static void free_signal(lcd_sig_t * sig); //unregisters a signal and frees its resources static void lcd_unregister_signal(lcd_sig_t * sig); //these copy data in/out of dbus static void fill_args(DBusMessage * message, int *argcount, char ***argv); static void free_args(int argc, char **argv); //returns 0 on error (no connection opened) // 1 when only the system connection was opened // 2 when only the session connection was opened // 3 both connections were opened static int lcd_dbus_init(void); static void handle_inbound_signal(void *signal, int argc, char **argv); static void free_handle_signal(handle_signal_t * sig); //given a signal, will add a hook so when the signal appears your callback is called static lcd_sig_t *lcd_register_signal(const char *sender, const char *path, const char *interface, const char *member, void (*callback) (void *user_data, int argc, char **argv), void *user_data, void (*user_free) (void *)); static void setup_dbus_events(DBusConnection * conn); //to handle watches through the main loop static dbus_bool_t add_watch(DBusWatch * w, void *data); static void remove_watch(DBusWatch * w, void *data); static void toggle_watch(DBusWatch * w, void *data); static void watch_handle(event_flags_t f, void *data); static void dispatch_dbus(void); //tell dbus to read something //to handle timers through the main loop static void timeout_dbus_handle(void *data); static dbus_bool_t add_dbus_timeout(DBusTimeout * t, void *data); static void remove_dbus_timeout(DBusTimeout * t, void *data); static void toggle_dbus_timeout(DBusTimeout * t, void *data); static lcd_sig_t *create_signal(const char *sender, const char *path, const char *interface, const char *member, void (*callback) (void *user_data, int argc, char **argv), void *user_data, void (*user_free) (void *)); static int clear_signal_txt(const int sig) { dbus_signal_t *s = get_signal_txt(sig); if (s == NULL) { return 1; } int i; if (s->arguments != NULL) { for (i = 0; i < s->argc; i++) { if (s->arguments[i] != NULL) { free(s->arguments[i]); } } free(s->arguments); s->arguments = NULL; } s->argc = 0; return 0; } static dbus_signal_t *get_signal_txt(const int sig) { if (sig < 0 || sig >= dbus_results.signals) { return NULL; } return &dbus_results.a[sig]; } //sets a signal struct for the given data static int set_signal_txt(const int sig, const dbus_signal_t * data) { if (sig < 0 || sig >= dbus_results.signals) { int new_sigs = sig + 1; //allocate it dbus_results.a = realloc(dbus_results.a, sizeof(dbus_signal_t) * new_sigs); int i; //clear anything just allocated that we are not writing to right now for (i = dbus_results.signals; i < new_sigs; i++) { dbus_results.a[i].argc = 0; dbus_results.a[i].arguments = NULL; } dbus_results.signals = new_sigs; } //free the old version if (dbus_results.a[sig].argc != 0) { clear_signal_txt(sig); } dbus_results.a[sig].argc = data->argc; //need to dup the strings dbus_results.a[sig].arguments = malloc(sizeof(data->arguments) * data->argc); int i; for (i = 0; i < data->argc; i++) { dbus_results.a[sig].arguments[i] = strdup(data->arguments[i]); } return 0; } /* * lcd4linux interface fucntions */ static void get_argument(RESULT * result, RESULT * sig, RESULT * arg) { int signal = (int) R2N(sig); int argument = (int) R2N(arg); dbus_signal_t *signal_value = get_signal_txt(signal); char *value = ""; if (signal_value != NULL && signal_value->argc > argument && signal_value->arguments[argument] != NULL) { value = signal_value->arguments[argument]; } /* store result */ SetResult(&result, R_STRING, value); } static void clear_arguments(RESULT * result, RESULT * sig) { int signal = (int) R2N(sig); dbus_signal_t *signal_value = get_signal_txt(signal); int i; if (signal_value->arguments != NULL) { for (i = 0; i < signal_value->argc; i++) { if (signal_value->arguments[i] != NULL) { free(signal_value->arguments[i]); } } free(signal_value->arguments); signal_value->arguments = NULL; } signal_value->argc = 0; /* store result */ SetResult(&result, R_STRING, ""); } static void load_dbus_cfg(void) { char *sender, *path, *interface, *member, *eventname; int i; const char *sender_fmt = "signal%dsender"; const char *path_fmt = "signal%dpath"; const char *interface_fmt = "signal%dinterface"; const char *member_fmt = "signal%dmember"; const char *eventname_fmt = "signal%deventname"; const int max_cfg_len = 32; char cfg_name[max_cfg_len]; for (i = 0; i < DBUS_MAX_SIGNALS; i++) { snprintf(cfg_name, max_cfg_len - 1, sender_fmt, i); sender = cfg_get(DBUS_PLUGIN_SECTION, cfg_name, ""); snprintf(cfg_name, max_cfg_len - 1, path_fmt, i); path = cfg_get(DBUS_PLUGIN_SECTION, cfg_name, ""); snprintf(cfg_name, max_cfg_len - 1, interface_fmt, i); interface = cfg_get(DBUS_PLUGIN_SECTION, cfg_name, ""); snprintf(cfg_name, max_cfg_len - 1, member_fmt, i); member = cfg_get(DBUS_PLUGIN_SECTION, cfg_name, ""); snprintf(cfg_name, max_cfg_len - 1, eventname_fmt, i); eventname = cfg_get(DBUS_PLUGIN_SECTION, cfg_name, ""); if (*path == '\0' && *interface == '\0' && *member == '\0') { goto cleanup; } else if (*path == '\0' || *interface == '\0' || *member == '\0') { error("[DBus] Incomplete configuration specified for signal%d", i); goto cleanup; } handle_signal_t *sig_info = malloc(sizeof(handle_signal_t)); sig_info->id = i; if (*eventname == '\0') { sig_info->event_name = NULL; } else { sig_info->event_name = strdup(eventname); } if (!lcd_register_signal(sender, path, interface, member, handle_inbound_signal, sig_info, (void (*)(void *)) free_handle_signal)) { error("[DBus] Error Registering signal %d", i); } cleanup: free(sender); free(path); free(interface); free(member); free(eventname); } } static void free_handle_signal(handle_signal_t * sig) { if (sig->event_name != NULL) { free(sig->event_name); } free(sig); } /* plugin initialization */ int plugin_init_dbus(void) { if (!lcd_dbus_init()) { error("[DBus] Could not connect to DBus"); return 1; } //dbus::argument(, )//displays arg# for signal# AddFunction("dbus::argument", 2, get_argument); //dbus::clear()//sets the arguments for signal# AddFunction("dbus::clear", 1, clear_arguments); //read out config load_dbus_cfg(); return 0; } void plugin_exit_dbus(void) { int i; //remove all known signals for (i = registered_sig_count - 1; i >= 0; i--) { lcd_unregister_signal(lcd_registered_signals[i]); } if (sysconn != NULL) { dbus_connection_unref(sysconn); } if (sessconn != NULL) { dbus_connection_unref(sessconn); } //remove all knows signal results for (i = dbus_results.signals - 1; i >= 0; i--) { clear_signal_txt(i); } #ifdef DEBUG //needs to be called to actually free everything, but might free stuff //mpris_dbus uses and cause a crash on shutdown, enable for leak debugging dbus_shutdown(); #endif } /* * From here one out it is all dbus specific funtions, * should probably be split into a seperate file, but i * keept it here to make the plugin a single file * */ //watch functions static dbus_bool_t add_watch(DBusWatch * w, void *data) { (void) data; //ignore it #if (DBUS_VERSION_MAJOR == 1 && DBUS_VERSION_MINOR == 1 && DBUS_VERSION_MICRO >= 1) || (DBUS_VERSION_MAJOR == 1 && DBUS_VERSION_MINOR > 1) || (DBUS_VERSION_MAJOR > 1) int fd = dbus_watch_get_unix_fd(w); #else int fd = dbus_watch_get_fd(w); #endif // int fd = dbus_watch_get_unix_fd(w); //we assume we are using unix int flags = dbus_watch_get_flags(w); event_add(watch_handle, w, fd, flags & DBUS_WATCH_READABLE, flags & DBUS_WATCH_WRITABLE, dbus_watch_get_enabled(w)); return TRUE; } static void remove_watch(DBusWatch * w, void *data) { (void) data; //ignore it #if (DBUS_VERSION_MAJOR == 1 && DBUS_VERSION_MINOR == 1 && DBUS_VERSION_MICRO >= 1) || (DBUS_VERSION_MAJOR == 1 && DBUS_VERSION_MINOR > 1) || (DBUS_VERSION_MAJOR > 1) event_del(dbus_watch_get_unix_fd(w)); #else event_del(dbus_watch_get_fd(w)); #endif // event_del(dbus_watch_get_unix_fd(w)); } static void toggle_watch(DBusWatch * w, void *data) { (void) data; #if (DBUS_VERSION_MAJOR == 1 && DBUS_VERSION_MINOR == 1 && DBUS_VERSION_MICRO >= 1) || (DBUS_VERSION_MAJOR == 1 && DBUS_VERSION_MINOR > 1) || (DBUS_VERSION_MAJOR > 1) int fd = dbus_watch_get_unix_fd(w); #else int fd = dbus_watch_get_fd(w); #endif // int fd = dbus_watch_get_unix_fd(w); //we assume we are using unix int flags = dbus_watch_get_flags(w); event_modify(fd, flags & DBUS_WATCH_READABLE, flags & DBUS_WATCH_WRITABLE, dbus_watch_get_enabled(w)); } static void watch_handle(event_flags_t f, void *data) { DBusWatch *w = (DBusWatch *) data; //convert the flags unsigned int flags = 0; flags |= (f & EVENT_READ) ? DBUS_WATCH_READABLE : 0; flags |= (f & EVENT_WRITE) ? DBUS_WATCH_WRITABLE : 0; flags |= (f & EVENT_HUP) ? DBUS_WATCH_HANGUP : 0; flags |= (f & EVENT_ERR) ? DBUS_WATCH_ERROR : 0; //tell dbus if (!dbus_watch_handle(w, flags)) { info("[DBus] dbus_watch_handle(): Not enough memory!"); } dispatch_dbus(); } static void dispatch_dbus(void) { if (sessconn != NULL && dbus_connection_get_dispatch_status(sessconn) == DBUS_DISPATCH_DATA_REMAINS) { while (DBUS_DISPATCH_DATA_REMAINS == dbus_connection_dispatch(sessconn)); } if (sysconn != NULL && dbus_connection_get_dispatch_status(sysconn) == DBUS_DISPATCH_DATA_REMAINS) { while (DBUS_DISPATCH_DATA_REMAINS == dbus_connection_dispatch(sysconn)); } } static void timeout_dbus_handle(void *data) { DBusTimeout *t = (DBusTimeout *) data; if (!dbus_timeout_handle(t)) { info("[DBus] Not enough memory to handle timeout!"); } dispatch_dbus(); } static dbus_bool_t add_dbus_timeout(DBusTimeout * t, void *data) { (void) data; //ignore warning if (!timer_add_late(timeout_dbus_handle, t, dbus_timeout_get_interval(t), 0)) { return FALSE; } return TRUE; } static void remove_dbus_timeout(DBusTimeout * t, void *data) { (void) data; //ignore warning timer_remove(timeout_dbus_handle, t); } static void toggle_dbus_timeout(DBusTimeout * t, void *data) { remove_dbus_timeout(t, data); add_dbus_timeout(t, data); } //add the proper events/callbacks so we get notified about out messages static void setup_dbus_events(DBusConnection * conn) { if (!dbus_connection_set_watch_functions(conn, add_watch, remove_watch, toggle_watch, NULL, NULL)) { error("[DBus] dbus_connection_set_watch_functions(): Not enough memory!"); } if (!dbus_connection_set_timeout_functions (conn, add_dbus_timeout, remove_dbus_timeout, toggle_dbus_timeout, NULL, NULL)) { error("[DBus] dbus_connection_set_timeout_functions(): Not enough memory!"); } } static int lcd_dbus_init(void) { int success = 3; dbus_error_init(&err); //dbus_error_free(&err); sessconn = dbus_bus_get(DBUS_BUS_SESSION, &err); if (sessconn == NULL) { info("[DBus] Error connecting to the dbus session bus: %s\n", err.message); dbus_error_free(&err); success &= 1; } else { #ifdef DEBUG dbus_connection_set_exit_on_disconnect(sessconn, FALSE); #endif setup_dbus_events(sessconn); } sysconn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); if (sysconn == NULL) { info("[DBus] Error connecting to the dbus system bus: %s\n", err.message); success &= 2; } else { #ifdef DEBUG dbus_connection_set_exit_on_disconnect(sysconn, FALSE); #endif setup_dbus_events(sysconn); } return success; } static lcd_sig_t *create_signal(const char *sender, const char *path, const char *interface, const char *member, void (*callback) (void *user_data, int argc, char **argv), void *user_data, void (*user_free) (void *)) { lcd_sig_t *sig = malloc(sizeof(lcd_sig_t)); sig->callback = callback; sig->user_data = user_data; sig->user_free = user_free; sig->sender = malloc(sizeof(char) * (1 + strlen(sender))); strcpy(sig->sender, sender); sig->path = malloc(sizeof(char) * (1 + strlen(path))); strcpy(sig->path, path); sig->interface = malloc(sizeof(char) * (1 + strlen(interface))); strcpy(sig->interface, interface); sig->member = malloc(sizeof(char) * (1 + strlen(member))); strcpy(sig->member, member); size_t len = strlen(sender) + strlen(path) + strlen(interface) + strlen(member); char *format; if (strlen(sig->sender) == 0) { format = "type='signal',path='%s',interface='%s',member='%s'"; } else { format = "type='signal',sender='%s',path='%s',interface='%s',member='%s'"; } len += strlen(format); len *= sizeof(char); sig->rule = malloc(len); if (strlen(sig->sender) == 0) { sprintf(sig->rule, format, path, interface, member); } else { sprintf(sig->rule, format, sender, path, interface, member); } assert(strlen(sig->rule) < len); return sig; } static void handle_inbound_signal(void *signal, int argc, char **argv) { handle_signal_t *signal_info = (handle_signal_t *) signal; dbus_signal_t sig; sig.argc = argc; sig.arguments = argv; set_signal_txt(signal_info->id, &sig); if (signal_info->event_name != NULL) { named_event_trigger(signal_info->event_name); } } static lcd_sig_t *lcd_register_signal(const char *sender, const char *path, const char *interface, const char *member, void (*callback) (void *user_data, int argc, char **argv), void *user_data, void (*user_free) (void *)) { if (__builtin_expect(registered_sig_count >= DBUS_MAX_SIGNALS, 0)) { //gcc >= 2.96 //i don't think anything will allow this to ever be hit..in fact It's impossible in the form i wrote the plugin error ("[DBus] Attempted to add more than %d dus signals, if you actually need more than that edit DBUS_MAX_SIGNALS in plugin_dbus.c", DBUS_MAX_SIGNALS); return NULL; } //store everything we need in the singal struct lcd_sig_t *sig = create_signal(sender, path, interface, member, callback, user_data, user_free); int success = 3; if (sessconn != NULL) { dbus_bus_add_match(sessconn, sig->rule, &err); if (dbus_error_is_set(&err)) { info("[DBus] Error adding dbus match to the session bus: %s \n", err.message); dbus_error_free(&err); success ^= 1; } if (!dbus_connection_add_filter(sessconn, lcd_sig_received, sig, NULL) && (success & 1)) { info("[DBus] Dbus signal registration failed to the session bus!\n"); dbus_bus_remove_match(sessconn, sig->rule, &err); success ^= 1; } } else { success ^= 1; } if (sysconn != NULL) { dbus_bus_add_match(sysconn, sig->rule, &err); if (dbus_error_is_set(&err)) { info("[DBus] Error adding dbus match to the system bus: %s \n", err.message); success ^= 2; } if (!dbus_connection_add_filter(sysconn, lcd_sig_received, sig, NULL) && (success & 2)) { info("[DBus] Dbus signal registration failed to the system bus!\n"); dbus_bus_remove_match(sysconn, sig->rule, &err); success ^= 2; } } else { success ^= 2; } if (!success) { free_signal(sig); return NULL; } lcd_registered_signals[registered_sig_count] = sig; registered_sig_count++; return sig; } static void free_signal(lcd_sig_t * sig) { if (sig->user_free != NULL) { sig->user_free(sig->user_data); } free(sig->sender); free(sig->path); free(sig->interface); free(sig->member); free(sig->rule); free(sig); } static void lcd_unregister_signal(lcd_sig_t * sig) { if (sig == NULL) { return; } //remove filter and match if (sessconn != NULL) { dbus_connection_remove_filter(sessconn, lcd_sig_received, sig); dbus_bus_remove_match(sessconn, sig->rule, NULL); } if (sysconn != NULL) { dbus_connection_remove_filter(sysconn, lcd_sig_received, sig); dbus_bus_remove_match(sysconn, sig->rule, NULL); } //free user data free_signal(sig); //drop it off the list int i; for (i = 0; i < registered_sig_count; i++) { if (lcd_registered_signals[i] == sig) { registered_sig_count--; lcd_registered_signals[i] = lcd_registered_signals[registered_sig_count]; break; } } } static void fill_args(DBusMessage * message, int *argcount, char ***argv) { dbus_message_ref(message); int argc = 0; char **args = NULL; DBusMessageIter iter; int buf_size = 0; dbus_message_iter_init(message, &iter); int current_type; //iterate over all arguments, casting all primitaves to strings while ((current_type = dbus_message_iter_get_arg_type(&iter)) != DBUS_TYPE_INVALID) { assert(argc <= buf_size); if (argc >= buf_size) { buf_size = argc * 2; if (buf_size == 0) { buf_size = 1; } args = realloc(args, sizeof(char **) * buf_size); } if (dbus_type_is_basic(current_type)) { args[argc] = malloc(sizeof(char) * (1 + LCD_MAX_MSG_LEN)); args[argc][LCD_MAX_MSG_LEN] = '\0'; //determine the type switch (current_type) { /* Primitive types */ case DBUS_TYPE_BYTE: { char value; dbus_message_iter_get_basic(&iter, &value); snprintf(args[argc], LCD_MAX_MSG_LEN, "%hhx", value); } break; case DBUS_TYPE_BOOLEAN: { dbus_bool_t value; dbus_message_iter_get_basic(&iter, &value); if (value) { strcpy(args[argc], "true"); } else { strcpy(args[argc], "false"); } } break; case DBUS_TYPE_INT16: { dbus_int16_t value; dbus_message_iter_get_basic(&iter, &value); snprintf(args[argc], LCD_MAX_MSG_LEN, "%hd", value); } break; case DBUS_TYPE_UINT16: { dbus_uint16_t value; dbus_message_iter_get_basic(&iter, &value); snprintf(args[argc], LCD_MAX_MSG_LEN, "%hu", value); } break; case DBUS_TYPE_INT32: { dbus_int32_t value; dbus_message_iter_get_basic(&iter, &value); snprintf(args[argc], LCD_MAX_MSG_LEN, "%d", value); } break; case DBUS_TYPE_UINT32: { dbus_uint32_t value; dbus_message_iter_get_basic(&iter, &value); snprintf(args[argc], LCD_MAX_MSG_LEN, "%u", value); } break; case DBUS_TYPE_INT64: { dbus_int64_t value; dbus_message_iter_get_basic(&iter, &value); snprintf(args[argc], LCD_MAX_MSG_LEN, "%jd", (intmax_t) value); } break; case DBUS_TYPE_UINT64: { dbus_uint64_t value; dbus_message_iter_get_basic(&iter, &value); snprintf(args[argc], LCD_MAX_MSG_LEN, "%ju", (uintmax_t) value); } break; case DBUS_TYPE_DOUBLE: { double value; dbus_message_iter_get_basic(&iter, &value); snprintf(args[argc], LCD_MAX_MSG_LEN, "%f", value); } break; case DBUS_TYPE_STRING: //all strings case DBUS_TYPE_OBJECT_PATH: case DBUS_TYPE_SIGNATURE: { char *value; dbus_message_iter_get_basic(&iter, &value); snprintf(args[argc], LCD_MAX_MSG_LEN, "%s", value); } break; case DBUS_TYPE_INVALID: default: //not supported free(args[argc]); args[argc] = NULL; assert(0); //should never ever happen... break; } } else { args[argc] = NULL; } argc++; dbus_message_iter_next(&iter); } *argcount = argc; *argv = args; dbus_message_unref(message); } static DBusHandlerResult lcd_sig_received(DBusConnection * connection, DBusMessage * msg, void *sigv) { (void) connection; dbus_message_ref(msg); lcd_sig_t *sig = sigv; //compare the signal to the one we were assigned if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) { dbus_message_unref(msg); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } //we don't check the sender because we (probably) asked by name, not by sender if (!dbus_message_has_member(msg, sig->member) || !dbus_message_has_path(msg, sig->path) || !dbus_message_has_interface(msg, sig->interface)) { dbus_message_unref(msg); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } //call the users function if (sig->callback != NULL) { char **args; int argc; fill_args(msg, &argc, &args); sig->callback(sig->user_data, argc, args); free_args(argc, args); } dbus_message_unref(msg); return DBUS_HANDLER_RESULT_HANDLED; } static void free_args(int argc, char **argv) { int i; //free args for (i = 0; i < argc; i++) { if (argv[i] != NULL) { free(argv[i]); } } if (argv != NULL && argc != 0) { free(argv); } }