diff --git a/picom.sample.conf b/picom.sample.conf index 777cf0d0..898b1deb 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -203,6 +203,9 @@ blur-background-exclude = [ # General Settings # ################################# +# Enable remote control via D-Bus. See the man page for more details. +# dbus = true + # Daemonize process. Fork to background after initialization. Causes issues with certain (badly-written) drivers. # daemon = false diff --git a/src/config_libconfig.c b/src/config_libconfig.c index e9818ebc..03fb4b51 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -30,8 +30,9 @@ static inline int lcfg_lookup_bool(const config_t *config, const char *path, boo int ival; int ret = config_lookup_bool(config, path, &ival); - if (ret) + if (ret) { *value = ival; + } return ret; } @@ -360,15 +361,21 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad // Get options from the configuration file. We don't do range checking // right now. It will be done later + // --dbus + lcfg_lookup_bool(&cfg, "dbus", &opt->dbus); + // -D (fade_delta) - if (config_lookup_int(&cfg, "fade-delta", &ival)) + if (config_lookup_int(&cfg, "fade-delta", &ival)) { opt->fade_delta = ival; + } // -I (fade_in_step) - if (config_lookup_float(&cfg, "fade-in-step", &dval)) + if (config_lookup_float(&cfg, "fade-in-step", &dval)) { opt->fade_in_step = normalize_d(dval); + } // -O (fade_out_step) - if (config_lookup_float(&cfg, "fade-out-step", &dval)) + if (config_lookup_float(&cfg, "fade-out-step", &dval)) { opt->fade_out_step = normalize_d(dval); + } // -r (shadow_radius) config_lookup_int(&cfg, "shadow-radius", &opt->shadow_radius); // -o (shadow_opacity) @@ -378,11 +385,13 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad // -t (shadow_offset_y) config_lookup_int(&cfg, "shadow-offset-y", &opt->shadow_offset_y); // -i (inactive_opacity) - if (config_lookup_float(&cfg, "inactive-opacity", &dval)) + if (config_lookup_float(&cfg, "inactive-opacity", &dval)) { opt->inactive_opacity = normalize_d(dval); + } // --active_opacity - if (config_lookup_float(&cfg, "active-opacity", &dval)) + if (config_lookup_float(&cfg, "active-opacity", &dval)) { opt->active_opacity = normalize_d(dval); + } // --corner-radius config_lookup_int(&cfg, "corner-radius", &opt->corner_radius); // --rounded-corners-exclude @@ -390,8 +399,7 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad // -e (frame_opacity) config_lookup_float(&cfg, "frame-opacity", &opt->frame_opacity); // -c (shadow_enable) - if (config_lookup_bool(&cfg, "shadow", &ival)) - *shadow_enable = ival; + lcfg_lookup_bool(&cfg, "shadow", shadow_enable); // -m (menu_opacity) if (config_lookup_float(&cfg, "menu-opacity", &dval)) { log_warn("Option `menu-opacity` is deprecated, and will be removed." diff --git a/src/dbus.c b/src/dbus.c index 55dad279..e0371671 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -72,7 +72,11 @@ typedef uint32_t cdbus_enum_t; cdbus_reply_errm((ps), dbus_message_new_error_printf( \ (srcmsg), (err_name), (err_format), ##__VA_ARGS__)) +#define PICOM_WINDOW_INTERFACE "picom.Window" +#define PICOM_COMPOSITOR_INTERFACE "picom.Compositor" + static DBusHandlerResult cdbus_process(DBusConnection *conn, DBusMessage *m, void *); +static DBusHandlerResult cdbus_process_windows(DBusConnection *c, DBusMessage *msg, void *ud); static dbus_bool_t cdbus_callback_add_timeout(DBusTimeout *timeout, void *data); @@ -182,6 +186,9 @@ bool cdbus_init(session_t *ps, const char *uniq) { dbus_connection_register_object_path( cd->dbus_conn, CDBUS_OBJECT_NAME, (DBusObjectPathVTable[]){{NULL, cdbus_process}}, ps); + dbus_connection_register_fallback( + cd->dbus_conn, CDBUS_OBJECT_NAME "/windows", + (DBusObjectPathVTable[]){{NULL, cdbus_process_windows}}, ps); return true; fail: ps->dbus_data = NULL; @@ -437,6 +444,56 @@ static bool cdbus_apdarg_wid(session_t *ps attr_unused, DBusMessage *msg, const return true; } +/** + * Callback to append a Window argument to a message as a variant. + */ +static bool +cdbus_append_wid_variant(session_t *ps attr_unused, DBusMessage *msg, const void *data) { + assert(data); + cdbus_window_t val = *(const xcb_window_t *)data; + + DBusMessageIter it, it2; + dbus_message_iter_init_append(msg, &it); + if (!dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, + CDBUS_TYPE_WINDOW_STR, &it2)) { + return false; + } + if (!dbus_message_iter_append_basic(&it2, CDBUS_TYPE_WINDOW, &val)) { + log_error("Failed to append argument."); + return false; + } + if (!dbus_message_iter_close_container(&it, &it2)) { + return false; + } + + return true; +} + +/** + * Callback to append a bool argument to a message as a variant. + */ +static bool +cdbus_append_bool_variant(session_t *ps attr_unused, DBusMessage *msg, const void *data) { + assert(data); + + dbus_bool_t val = *(const bool *)data; + DBusMessageIter it, it2; + dbus_message_iter_init_append(msg, &it); + if (!dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, + DBUS_TYPE_BOOLEAN_AS_STRING, &it2)) { + return false; + } + if (!dbus_message_iter_append_basic(&it2, DBUS_TYPE_BOOLEAN, &val)) { + log_error("Failed to append argument."); + return false; + } + if (!dbus_message_iter_close_container(&it, &it2)) { + return false; + } + + return true; +} + /** * Callback to append an cdbus_enum_t argument to a message. */ @@ -467,6 +524,46 @@ cdbus_apdarg_string(session_t *ps attr_unused, DBusMessage *msg, const void *dat return true; } +/** + * Callback to append a string argument to a message as a variant. + */ +static bool +cdbus_append_string_variant(session_t *ps attr_unused, DBusMessage *msg, const void *data) { + const char *str = *(const char **)data; + if (!str) { + str = ""; + } + + DBusMessageIter it, it2; + dbus_message_iter_init_append(msg, &it); + if (!dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, &it2)) { + return false; + } + if (!dbus_message_iter_append_basic(&it2, DBUS_TYPE_STRING, &str)) { + log_error("Failed to append argument."); + return false; + } + if (!dbus_message_iter_close_container(&it, &it2)) { + return false; + } + + return true; +} + +static bool cdbus_append_empty_dict(session_t *ps attr_unused, DBusMessage *msg, + const void *data attr_unused) { + DBusMessageIter it, it2; + dbus_message_iter_init_append(msg, &it); + if (!dbus_message_iter_open_container(&it, DBUS_TYPE_ARRAY, "{sv}", &it2)) { + return false; + } + if (!dbus_message_iter_close_container(&it, &it2)) { + return false; + } + return true; +} + /** * Callback to append all window IDs to a message. */ @@ -517,14 +614,14 @@ static bool cdbus_apdarg_wids(session_t *ps, DBusMessage *msg, const void *data * add an argument * @param data data pointer to pass to the function */ -static bool cdbus_signal(session_t *ps, const char *name, +static bool cdbus_signal(session_t *ps, const char *interface, const char *name, bool (*func)(session_t *ps, DBusMessage *msg, const void *data), const void *data) { struct cdbus_data *cd = ps->dbus_data; DBusMessage *msg = NULL; // Create a signal - msg = dbus_message_new_signal(CDBUS_OBJECT_NAME, CDBUS_INTERFACE_NAME, name); + msg = dbus_message_new_signal(CDBUS_OBJECT_NAME, interface, name); if (!msg) { log_error("Failed to create D-Bus signal."); return false; @@ -553,8 +650,9 @@ static bool cdbus_signal(session_t *ps, const char *name, /** * Send a signal with a Window ID as argument. */ -static inline bool cdbus_signal_wid(session_t *ps, const char *name, xcb_window_t wid) { - return cdbus_signal(ps, name, cdbus_apdarg_wid, &wid); +static inline bool +cdbus_signal_wid(session_t *ps, const char *interface, const char *name, xcb_window_t wid) { + return cdbus_signal(ps, interface, name, cdbus_apdarg_wid, &wid); } /** @@ -734,6 +832,84 @@ static bool cdbus_process_list_win(session_t *ps, DBusMessage *msg) { return true; } +/** + * Process a win_get D-Bus request. + */ +static bool +cdbus_process_window_property_get(session_t *ps, DBusMessage *msg, cdbus_window_t wid) { + const char *target = NULL; + const char *interface = NULL; + DBusError err = {}; + + if (!dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &target, DBUS_TYPE_INVALID)) { + log_error("Failed to parse argument of \"Get\" (%s).", err.message); + dbus_error_free(&err); + return false; + } + + if (strcmp(interface, PICOM_WINDOW_INTERFACE)) { + return false; + } + + auto w = find_managed_win(ps, wid); + + if (!w) { + log_error("Window %#010x not found.", wid); + cdbus_reply_err(ps, msg, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid); + return true; + } + +#define cdbus_m_win_get_do(tgt, member, apdarg_func) \ + if (!strcmp(#tgt, target)) { \ + cdbus_reply(ps, msg, apdarg_func, &w->member); \ + return true; \ + } + + if (!strcmp("Mapped", target)) { + cdbus_reply(ps, msg, cdbus_append_bool_variant, + (bool[]){win_is_mapped_in_x(w)}); + return true; + } + + if (!strcmp(target, "Id")) { + cdbus_reply(ps, msg, cdbus_append_wid_variant, &w->base.id); + return true; + } + + // next + if (!strcmp("Next", target)) { + cdbus_window_t next_id = 0; + if (!list_node_is_last(&ps->window_stack, &w->base.stack_neighbour)) { + next_id = list_entry(w->base.stack_neighbour.next, struct win, + stack_neighbour) + ->id; + } + cdbus_reply(ps, msg, cdbus_append_wid_variant, &next_id); + return true; + } + + cdbus_m_win_get_do(ClientWin, client_win, cdbus_append_wid_variant); + cdbus_m_win_get_do(Leader, leader, cdbus_append_wid_variant); + cdbus_m_win_get_do(Name, name, cdbus_append_string_variant); + if (!strcmp("Type", target)) { + cdbus_reply(ps, msg, cdbus_append_string_variant, &WINTYPES[w->window_type]); + return true; + } + if (!strcmp("RawFocused", target)) { + cdbus_reply(ps, msg, cdbus_append_bool_variant, + (bool[]){win_is_focused_raw(ps, w)}); + return true; + } + +#undef cdbus_m_win_get_do + + log_error(CDBUS_ERROR_BADTGT_S, target); + cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target); + + return true; +} + /** * Process a win_get D-Bus request. */ @@ -1212,6 +1388,21 @@ static bool cdbus_process_introspect(session_t *ps, DBusMessage *msg) { " \n" " \n" " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" "\n"; cdbus_reply_string(ps, msg, str_introspect); @@ -1220,6 +1411,90 @@ static bool cdbus_process_introspect(session_t *ps, DBusMessage *msg) { } ///@} +/** + * Process an D-Bus Introspect request, for /windows. + */ +static bool cdbus_process_windows_root_introspect(session_t *ps, DBusMessage *msg) { + static const char *str_introspect = + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n"; + + char *ret = NULL; + mstrextend(&ret, str_introspect); + + HASH_ITER2(ps->windows, w) { + assert(!w->destroyed); + if (!w->managed) { + continue; + } + char *tmp = NULL; + asprintf(&tmp, "\n", w->id); + mstrextend(&ret, tmp); + free(tmp); + } + mstrextend(&ret, ""); + + bool success = cdbus_reply_string(ps, msg, ret); + free(ret); + return success; +} + +static bool cdbus_process_window_introspect(session_t *ps, DBusMessage *msg) { + // clang-format off + static const char *str_introspect = + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + // clang-format on + + return cdbus_reply_string(ps, msg, str_introspect); +} + /** * Process a message from D-Bus. */ @@ -1304,42 +1579,122 @@ cdbus_process(DBusConnection *c attr_unused, DBusMessage *msg, void *ud) { return handled ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } +/** + * Process a message from D-Bus, for /windows path. + */ +static DBusHandlerResult +cdbus_process_windows(DBusConnection *c attr_unused, DBusMessage *msg, void *ud) { + session_t *ps = ud; + bool handled = false; + const char *path = dbus_message_get_path(msg); + const char *last_segment = strrchr(path, '/'); + if (last_segment == NULL) { + if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) && + !dbus_message_get_no_reply(msg)) + cdbus_reply_err(ps, msg, CDBUS_ERROR_BADMSG, CDBUS_ERROR_BADMSG_S); + return DBUS_HANDLER_RESULT_HANDLED; + } + bool is_root = strncmp(last_segment, "/windows", 8) == 0; + if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Introspectable", + "Introspect")) { + if (is_root) { + handled = cdbus_process_windows_root_introspect(ps, msg); + } else { + handled = cdbus_process_window_introspect(ps, msg); + } + goto finished; + } + + if (!is_root) { + auto wid = (cdbus_window_t)strtol(last_segment + 1, NULL, 0); + if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Properties", + "GetAll")) { + handled = cdbus_reply(ps, msg, cdbus_append_empty_dict, NULL); + goto finished; + } + + if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Properties", "Get")) { + handled = cdbus_process_window_property_get(ps, msg, wid); + goto finished; + } + } + + if (dbus_message_is_signal(msg, "org.freedesktop.DBus", "NameAcquired") || + dbus_message_is_signal(msg, "org.freedesktop.DBus", "NameLost")) { + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (DBUS_MESSAGE_TYPE_ERROR == dbus_message_get_type(msg)) { + log_error("Error message of path \"%s\" " + "interface \"%s\", member \"%s\", error \"%s\"", + dbus_message_get_path(msg), dbus_message_get_interface(msg), + dbus_message_get_member(msg), dbus_message_get_error_name(msg)); + } else { + log_error("Illegal message of type \"%s\", path \"%s\" " + "interface \"%s\", member \"%s\"", + cdbus_repr_msgtype(msg), dbus_message_get_path(msg), + dbus_message_get_interface(msg), dbus_message_get_member(msg)); + } + if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) && + !dbus_message_get_no_reply(msg)) { + handled = cdbus_reply_err(ps, msg, CDBUS_ERROR_BADMSG, CDBUS_ERROR_BADMSG_S); + } + +finished: + if (!handled && dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_CALL && + !dbus_message_get_no_reply(msg)) { + handled = + cdbus_reply_err(ps, msg, CDBUS_ERROR_UNKNOWN, CDBUS_ERROR_UNKNOWN_S); + } + return handled ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + /** @name Core callbacks */ ///@{ void cdbus_ev_win_added(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_added", w->id); + if (cd->dbus_conn) { + cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_added", w->id); + cdbus_signal_wid(ps, PICOM_COMPOSITOR_INTERFACE, "WinAdded", w->id); + } } void cdbus_ev_win_destroyed(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_destroyed", w->id); + if (cd->dbus_conn) { + cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_destroyed", w->id); + cdbus_signal_wid(ps, PICOM_COMPOSITOR_INTERFACE, "WinDestroyed", w->id); + } } void cdbus_ev_win_mapped(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_mapped", w->id); + if (cd->dbus_conn) { + cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_mapped", w->id); + cdbus_signal_wid(ps, PICOM_COMPOSITOR_INTERFACE, "WinMapped", w->id); + } } void cdbus_ev_win_unmapped(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_unmapped", w->id); + if (cd->dbus_conn) { + cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_unmapped", w->id); + cdbus_signal_wid(ps, PICOM_COMPOSITOR_INTERFACE, "WinUnmapped", w->id); + } } void cdbus_ev_win_focusout(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_focusout", w->id); + if (cd->dbus_conn) { + cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_focusout", w->id); + } } void cdbus_ev_win_focusin(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; - if (cd->dbus_conn) - cdbus_signal_wid(ps, "win_focusin", w->id); + if (cd->dbus_conn) { + cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_focusin", w->id); + } } //!@}