swayaway
a drop-in replacement for swayidle(1)
screensaver.c
10151 bytes raw
1#include "swayaway.h"
2
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <dbus/dbus.h>
7
8#define SS_PATH "/org/freedesktop/ScreenSaver"
9#define SS_IFACE "org.freedesktop.ScreenSaver"
10#define GNOME_PATH "/org/gnome/screen_saver"
11#define GNOME_IFACE "org.gnome.ScreenSaver"
12
13// this sucks, yes, but who are you to judge
14static const char introspect_xml[] =
15 "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
16 "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
17 "<node>\n"
18 " <interface name=\"org.freedesktop.ScreenSaver\">\n"
19 " <method name=\"Inhibit\">\n"
20 " <arg name=\"application_name\" type=\"s\" direction=\"in\"/>\n"
21 " <arg name=\"reason\" type=\"s\" direction=\"in\"/>\n"
22 " <arg name=\"cookie\" type=\"u\" direction=\"out\"/>\n"
23 " </method>\n"
24 " <method name=\"Uninhibit\">\n"
25 " <arg name=\"cookie\" type=\"u\" direction=\"in\"/>\n"
26 " </method>\n"
27 " <method name=\"GetActive\">\n"
28 " <arg name=\"active\" type=\"b\" direction=\"out\"/>\n"
29 " </method>\n"
30 " <method name=\"GetActiveTime\">\n"
31 " <arg name=\"seconds\" type=\"u\" direction=\"out\"/>\n"
32 " </method>\n"
33 " <signal name=\"ActiveChanged\">\n"
34 " <arg name=\"active\" type=\"b\"/>\n"
35 " </signal>\n"
36 " </interface>\n"
37 " <interface name=\"org.gnome.ScreenSaver\">\n"
38 " <method name=\"Inhibit\">\n"
39 " <arg name=\"application_name\" type=\"s\" direction=\"in\"/>\n"
40 " <arg name=\"reason\" type=\"s\" direction=\"in\"/>\n"
41 " <arg name=\"cookie\" type=\"u\" direction=\"out\"/>\n"
42 " </method>\n"
43 " <method name=\"Uninhibit\">\n"
44 " <arg name=\"cookie\" type=\"u\" direction=\"in\"/>\n"
45 " </method>\n"
46 " </interface>\n"
47 " <interface name=\"org.freedesktop.DBus.Introspectable\">\n"
48 " <method name=\"Introspect\"><arg name=\"xml\" type=\"s\" direction=\"out\"/></method>\n"
49 " </interface>\n"
50 "</node>\n";
51
52/* last emitted ActiveChanged value; emit only on real transitions to avoid
53 * flooding the bus with a signal per keystroke gap */
54static bool ss_active = false;
55
56static void recompute(void)
57{
58 state.screensaver_inhibited = (state.inhibitors != NULL);
59 idle_recompute_inhibited();
60}
61
62static void remove_inhibitor(struct inhibitor *inh)
63{
64 struct inhibitor **p = &state.inhibitors;
65 while (*p && *p != inh) {
66 p = &(*p)->next;
67 }
68 if (*p == inh) {
69 *p = inh->next;
70 }
71 free(inh->sender);
72 free(inh->app);
73 free(inh->reason);
74 free(inh);
75}
76
77static struct inhibitor *find_cookie(uint32_t cookie)
78{
79 struct inhibitor *inh;
80 for (inh = state.inhibitors; inh; inh = inh->next) {
81 if (inh->cookie == cookie) {
82 return inh;
83 }
84 }
85 return NULL;
86}
87
88static void reply_simple(DBusConnection *conn, DBusMessage *msg,
89 int type, void *value)
90{
91 DBusMessage *reply = dbus_message_new_method_return(msg);
92 if (!reply) {
93 return;
94 }
95 dbus_message_append_args(reply, type, value, DBUS_TYPE_INVALID);
96 dbus_connection_send(conn, reply, NULL);
97 dbus_message_unref(reply);
98 dbus_connection_flush(conn);
99}
100
101static void reply_error(DBusConnection *conn, DBusMessage *msg,
102 const char *text)
103{
104 DBusMessage *reply = dbus_message_new_error(
105 msg, DBUS_ERROR_INVALID_ARGS, text);
106 if (reply) {
107 dbus_connection_send(conn, reply, NULL);
108 dbus_message_unref(reply);
109 dbus_connection_flush(conn);
110 }
111}
112
113static void handle_inhibit(DBusConnection *conn, DBusMessage *msg)
114{
115 const char *app = NULL;
116 const char *reason = NULL;
117 DBusError err;
118 dbus_error_init(&err);
119
120 if (!dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &app,
121 DBUS_TYPE_STRING, &reason, DBUS_TYPE_INVALID)) {
122 reply_error(conn, msg, err.message ? err.message : "invalid arguments");
123 dbus_error_free(&err);
124 return;
125 }
126
127 uint32_t cookie = ++state.next_cookie;
128 if (cookie == 0) {
129 cookie = ++state.next_cookie;
130 }
131
132 struct inhibitor *inh = calloc(1, sizeof(*inh));
133 if (!inh) {
134 reply_error(conn, msg, "out of memory");
135 return;
136 }
137 inh->cookie = cookie;
138 const char *sender = dbus_message_get_sender(msg);
139 inh->sender = sender ? strdup(sender) : strdup(":unknown");
140 inh->app = strdup(app ? app : "");
141 inh->reason = strdup(reason ? reason : "");
142 inh->next = state.inhibitors;
143 state.inhibitors = inh;
144
145 sway_log("inhibit requested by %s (%s): %s",
146 inh->sender, inh->app, inh->reason);
147 recompute();
148
149 reply_simple(conn, msg, DBUS_TYPE_UINT32, &cookie);
150}
151
152static void handle_uninhibit(DBusConnection *conn, DBusMessage *msg)
153{
154 uint32_t cookie = 0;
155 DBusError err;
156 dbus_error_init(&err);
157
158 if (!dbus_message_get_args(msg, &err, DBUS_TYPE_UINT32, &cookie,
159 DBUS_TYPE_INVALID)) {
160 reply_error(conn, msg, err.message ? err.message : "invalid arguments");
161 dbus_error_free(&err);
162 return;
163 }
164
165 struct inhibitor *inh = find_cookie(cookie);
166 if (inh) {
167 sway_log("uninhibit cookie %u", cookie);
168 remove_inhibitor(inh);
169 recompute();
170 } else {
171 sway_log_debug("uninhibit for unknown cookie %u", cookie);
172 }
173 reply_simple(conn, msg, DBUS_TYPE_INVALID, NULL);
174}
175
176static void handle_get_active(DBusConnection *conn, DBusMessage *msg)
177{
178 dbus_bool_t active = idle_any_timeout_fired();
179 reply_simple(conn, msg, DBUS_TYPE_BOOLEAN, &active);
180}
181
182static void handle_get_active_time(DBusConnection *conn, DBusMessage *msg)
183{
184 uint32_t secs = 0;
185 if (idle_any_timeout_fired() && state.is_idle) {
186 secs = (uint32_t)((mono_ms() - state.idle_since_ms) / 1000);
187 }
188 reply_simple(conn, msg, DBUS_TYPE_UINT32, &secs);
189}
190
191static void handle_name_owner_changed(DBusConnection *conn, DBusMessage *msg)
192{
193 (void)conn;
194 const char *name = NULL, *old_owner = NULL, *new_owner = NULL;
195 DBusError err;
196 dbus_error_init(&err);
197
198 if (!dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &name,
199 DBUS_TYPE_STRING, &old_owner, DBUS_TYPE_STRING, &new_owner,
200 DBUS_TYPE_INVALID)) {
201 dbus_error_free(&err);
202 return;
203 }
204
205 // a connection died (new_owner empty): release everything it held
206 if (name && *name && new_owner && *new_owner == '\0') {
207 struct inhibitor *inh = state.inhibitors;
208 while (inh) {
209 struct inhibitor *next = inh->next;
210 if (strcmp(inh->sender, name) == 0) {
211 sway_log("client %s disconnected, releasing its inhibitors",
212 name);
213 remove_inhibitor(inh);
214 }
215 inh = next;
216 }
217 recompute();
218 }
219}
220
221static DBusHandlerResult filter(DBusConnection *conn, DBusMessage *msg,
222 void *ud)
223{
224 (void)ud;
225
226 if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Introspectable",
227 "Introspect")) {
228 const char *xml = introspect_xml;
229 reply_simple(conn, msg, DBUS_TYPE_STRING, &xml);
230 return DBUS_HANDLER_RESULT_HANDLED;
231 }
232
233 if (dbus_message_is_method_call(msg, SS_IFACE, "Inhibit") ||
234 dbus_message_is_method_call(msg, GNOME_IFACE, "Inhibit")) {
235 handle_inhibit(conn, msg);
236 return DBUS_HANDLER_RESULT_HANDLED;
237 }
238 if (dbus_message_is_method_call(msg, SS_IFACE, "Uninhibit") ||
239 dbus_message_is_method_call(msg, GNOME_IFACE, "Uninhibit")) {
240 handle_uninhibit(conn, msg);
241 return DBUS_HANDLER_RESULT_HANDLED;
242 }
243 if (dbus_message_is_method_call(msg, SS_IFACE, "GetActive")) {
244 handle_get_active(conn, msg);
245 return DBUS_HANDLER_RESULT_HANDLED;
246 }
247 if (dbus_message_is_method_call(msg, SS_IFACE, "GetActiveTime")) {
248 handle_get_active_time(conn, msg);
249 return DBUS_HANDLER_RESULT_HANDLED;
250 }
251
252 if (dbus_message_is_signal(msg, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) {
253 handle_name_owner_changed(conn, msg);
254 return DBUS_HANDLER_RESULT_HANDLED;
255 }
256
257 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
258}
259
260/* Emit ActiveChanged only when the "screensaver active" state (at least one
261 * timeout command fired) actually transitions. */
262void screensaver_emit_active(void)
263{
264 if (!state.session_conn) {
265 return;
266 }
267 bool now = idle_any_timeout_fired();
268 if (now == ss_active) {
269 return;
270 }
271 ss_active = now;
272
273 DBusMessage *sig = dbus_message_new_signal(SS_PATH, SS_IFACE,
274 "ActiveChanged");
275 if (!sig) {
276 return;
277 }
278 dbus_bool_t active = now;
279 dbus_message_append_args(sig, DBUS_TYPE_BOOLEAN, &active,
280 DBUS_TYPE_INVALID);
281 dbus_connection_send(state.session_conn, sig, NULL);
282 dbus_message_unref(sig);
283 dbus_connection_flush(state.session_conn);
284}
285
286static void request_name(DBusConnection *conn, const char *name)
287{
288 DBusError err;
289 dbus_error_init(&err);
290 int ret = dbus_bus_request_name(conn, name,
291 DBUS_NAME_FLAG_DO_NOT_QUEUE, &err);
292 if (ret == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
293 sway_log("exporting %s", name);
294 } else {
295 sway_log("could not acquire %s (%s); inhibitor service degraded",
296 name, err.message ? err.message : "unknown error");
297 }
298 dbus_error_free(&err);
299}
300
301void screensaver_init(void)
302{
303 DBusError err;
304 dbus_error_init(&err);
305
306 state.session_conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
307 if (state.session_conn == NULL) {
308 sway_log("could not connect to the session bus: %s",
309 err.message ? err.message : "unknown error");
310 dbus_error_free(&err);
311 return;
312 }
313 dbus_error_free(&err);
314 dbus_connection_set_exit_on_disconnect(state.session_conn, FALSE);
315 dbus_connection_get_unix_fd(state.session_conn, &state.session_fd);
316
317 dbus_connection_add_filter(state.session_conn, filter, NULL, NULL);
318
319 DBusError match_err;
320 dbus_error_init(&match_err);
321 dbus_bus_add_match(state.session_conn,
322 "type='signal',interface='org.freedesktop.DBus',"
323 "member='NameOwnerChanged',path='/org/freedesktop/DBus'",
324 &match_err);
325 if (dbus_error_is_set(&match_err)) {
326 sway_log("could not add NameOwnerChanged match: %s",
327 match_err.message);
328 }
329 dbus_error_free(&match_err);
330
331 request_name(state.session_conn, SS_IFACE);
332 request_name(state.session_conn, GNOME_IFACE);
333}
334
335bool screensaver_have_service(void)
336{
337 return state.session_conn != NULL;
338}