#include "swayaway.h" #include #include #include #include #define SS_PATH "/org/freedesktop/ScreenSaver" #define SS_IFACE "org.freedesktop.ScreenSaver" #define GNOME_PATH "/org/gnome/screen_saver" #define GNOME_IFACE "org.gnome.ScreenSaver" // this sucks, yes, but who are you to judge static const char introspect_xml[] = "\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"; /* last emitted ActiveChanged value; emit only on real transitions to avoid * flooding the bus with a signal per keystroke gap */ static bool ss_active = false; static void recompute(void) { state.screensaver_inhibited = (state.inhibitors != NULL); idle_recompute_inhibited(); } static void remove_inhibitor(struct inhibitor *inh) { struct inhibitor **p = &state.inhibitors; while (*p && *p != inh) { p = &(*p)->next; } if (*p == inh) { *p = inh->next; } free(inh->sender); free(inh->app); free(inh->reason); free(inh); } static struct inhibitor *find_cookie(uint32_t cookie) { struct inhibitor *inh; for (inh = state.inhibitors; inh; inh = inh->next) { if (inh->cookie == cookie) { return inh; } } return NULL; } static void reply_simple(DBusConnection *conn, DBusMessage *msg, int type, void *value) { DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) { return; } dbus_message_append_args(reply, type, value, DBUS_TYPE_INVALID); dbus_connection_send(conn, reply, NULL); dbus_message_unref(reply); dbus_connection_flush(conn); } static void reply_error(DBusConnection *conn, DBusMessage *msg, const char *text) { DBusMessage *reply = dbus_message_new_error( msg, DBUS_ERROR_INVALID_ARGS, text); if (reply) { dbus_connection_send(conn, reply, NULL); dbus_message_unref(reply); dbus_connection_flush(conn); } } static void handle_inhibit(DBusConnection *conn, DBusMessage *msg) { const char *app = NULL; const char *reason = NULL; DBusError err; dbus_error_init(&err); if (!dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &app, DBUS_TYPE_STRING, &reason, DBUS_TYPE_INVALID)) { reply_error(conn, msg, err.message ? err.message : "invalid arguments"); dbus_error_free(&err); return; } uint32_t cookie = ++state.next_cookie; if (cookie == 0) { cookie = ++state.next_cookie; } struct inhibitor *inh = calloc(1, sizeof(*inh)); if (!inh) { reply_error(conn, msg, "out of memory"); return; } inh->cookie = cookie; const char *sender = dbus_message_get_sender(msg); inh->sender = sender ? strdup(sender) : strdup(":unknown"); inh->app = strdup(app ? app : ""); inh->reason = strdup(reason ? reason : ""); inh->next = state.inhibitors; state.inhibitors = inh; sway_log("inhibit requested by %s (%s): %s", inh->sender, inh->app, inh->reason); recompute(); reply_simple(conn, msg, DBUS_TYPE_UINT32, &cookie); } static void handle_uninhibit(DBusConnection *conn, DBusMessage *msg) { uint32_t cookie = 0; DBusError err; dbus_error_init(&err); if (!dbus_message_get_args(msg, &err, DBUS_TYPE_UINT32, &cookie, DBUS_TYPE_INVALID)) { reply_error(conn, msg, err.message ? err.message : "invalid arguments"); dbus_error_free(&err); return; } struct inhibitor *inh = find_cookie(cookie); if (inh) { sway_log("uninhibit cookie %u", cookie); remove_inhibitor(inh); recompute(); } else { sway_log_debug("uninhibit for unknown cookie %u", cookie); } reply_simple(conn, msg, DBUS_TYPE_INVALID, NULL); } static void handle_get_active(DBusConnection *conn, DBusMessage *msg) { dbus_bool_t active = idle_any_timeout_fired(); reply_simple(conn, msg, DBUS_TYPE_BOOLEAN, &active); } static void handle_get_active_time(DBusConnection *conn, DBusMessage *msg) { uint32_t secs = 0; if (idle_any_timeout_fired() && state.is_idle) { secs = (uint32_t)((mono_ms() - state.idle_since_ms) / 1000); } reply_simple(conn, msg, DBUS_TYPE_UINT32, &secs); } static void handle_name_owner_changed(DBusConnection *conn, DBusMessage *msg) { (void)conn; const char *name = NULL, *old_owner = NULL, *new_owner = NULL; DBusError err; dbus_error_init(&err); if (!dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old_owner, DBUS_TYPE_STRING, &new_owner, DBUS_TYPE_INVALID)) { dbus_error_free(&err); return; } // a connection died (new_owner empty): release everything it held if (name && *name && new_owner && *new_owner == '\0') { struct inhibitor *inh = state.inhibitors; while (inh) { struct inhibitor *next = inh->next; if (strcmp(inh->sender, name) == 0) { sway_log("client %s disconnected, releasing its inhibitors", name); remove_inhibitor(inh); } inh = next; } recompute(); } } static DBusHandlerResult filter(DBusConnection *conn, DBusMessage *msg, void *ud) { (void)ud; if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Introspectable", "Introspect")) { const char *xml = introspect_xml; reply_simple(conn, msg, DBUS_TYPE_STRING, &xml); return DBUS_HANDLER_RESULT_HANDLED; } if (dbus_message_is_method_call(msg, SS_IFACE, "Inhibit") || dbus_message_is_method_call(msg, GNOME_IFACE, "Inhibit")) { handle_inhibit(conn, msg); return DBUS_HANDLER_RESULT_HANDLED; } if (dbus_message_is_method_call(msg, SS_IFACE, "Uninhibit") || dbus_message_is_method_call(msg, GNOME_IFACE, "Uninhibit")) { handle_uninhibit(conn, msg); return DBUS_HANDLER_RESULT_HANDLED; } if (dbus_message_is_method_call(msg, SS_IFACE, "GetActive")) { handle_get_active(conn, msg); return DBUS_HANDLER_RESULT_HANDLED; } if (dbus_message_is_method_call(msg, SS_IFACE, "GetActiveTime")) { handle_get_active_time(conn, msg); return DBUS_HANDLER_RESULT_HANDLED; } if (dbus_message_is_signal(msg, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) { handle_name_owner_changed(conn, msg); return DBUS_HANDLER_RESULT_HANDLED; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } /* Emit ActiveChanged only when the "screensaver active" state (at least one * timeout command fired) actually transitions. */ void screensaver_emit_active(void) { if (!state.session_conn) { return; } bool now = idle_any_timeout_fired(); if (now == ss_active) { return; } ss_active = now; DBusMessage *sig = dbus_message_new_signal(SS_PATH, SS_IFACE, "ActiveChanged"); if (!sig) { return; } dbus_bool_t active = now; dbus_message_append_args(sig, DBUS_TYPE_BOOLEAN, &active, DBUS_TYPE_INVALID); dbus_connection_send(state.session_conn, sig, NULL); dbus_message_unref(sig); dbus_connection_flush(state.session_conn); } static void request_name(DBusConnection *conn, const char *name) { DBusError err; dbus_error_init(&err); int ret = dbus_bus_request_name(conn, name, DBUS_NAME_FLAG_DO_NOT_QUEUE, &err); if (ret == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { sway_log("exporting %s", name); } else { sway_log("could not acquire %s (%s); inhibitor service degraded", name, err.message ? err.message : "unknown error"); } dbus_error_free(&err); } void screensaver_init(void) { DBusError err; dbus_error_init(&err); state.session_conn = dbus_bus_get(DBUS_BUS_SESSION, &err); if (state.session_conn == NULL) { sway_log("could not connect to the session bus: %s", err.message ? err.message : "unknown error"); dbus_error_free(&err); return; } dbus_error_free(&err); dbus_connection_set_exit_on_disconnect(state.session_conn, FALSE); dbus_connection_get_unix_fd(state.session_conn, &state.session_fd); dbus_connection_add_filter(state.session_conn, filter, NULL, NULL); DBusError match_err; dbus_error_init(&match_err); dbus_bus_add_match(state.session_conn, "type='signal',interface='org.freedesktop.DBus'," "member='NameOwnerChanged',path='/org/freedesktop/DBus'", &match_err); if (dbus_error_is_set(&match_err)) { sway_log("could not add NameOwnerChanged match: %s", match_err.message); } dbus_error_free(&match_err); request_name(state.session_conn, SS_IFACE); request_name(state.session_conn, GNOME_IFACE); } bool screensaver_have_service(void) { return state.session_conn != NULL; }