2 * Copyright © 2013 David Herrmann
4 * Permission to use, copy, modify, distribute, and sell this software and
5 * its documentation for any purpose is hereby granted without fee, provided
6 * that the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation, and that the name of the copyright holders not be used in
9 * advertising or publicity pertaining to distribution of the software
10 * without specific, written prior permission. The copyright holders make
11 * no representations about the suitability of this software for any
12 * purpose. It is provided "as is" without express or implied warranty.
14 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
15 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
17 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
18 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
19 * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
20 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
29 #include <linux/major.h>
36 #include <sys/ioctl.h>
37 #include <sys/signalfd.h>
39 #include <systemd/sd-login.h>
42 #include "compositor.h"
44 #include "logind-util.h"
49 #define KDSKBMUTE 0x4B51
52 struct weston_logind {
53 struct weston_compositor *compositor;
61 struct wl_event_source *sfd_source;
64 struct wl_event_source *dbus_ctx;
66 DBusPendingCall *pending_active;
70 weston_logind_take_device(struct weston_logind *wl, uint32_t major,
71 uint32_t minor, bool *paused_out)
73 DBusMessage *m, *reply;
78 m = dbus_message_new_method_call("org.freedesktop.login1",
80 "org.freedesktop.login1.Session",
85 b = dbus_message_append_args(m,
86 DBUS_TYPE_UINT32, &major,
87 DBUS_TYPE_UINT32, &minor,
94 reply = dbus_connection_send_with_reply_and_block(wl->dbus, m,
101 b = dbus_message_get_args(reply, NULL,
102 DBUS_TYPE_UNIX_FD, &fd,
103 DBUS_TYPE_BOOLEAN, &paused,
112 *paused_out = paused;
115 dbus_message_unref(reply);
117 dbus_message_unref(m);
122 weston_logind_release_device(struct weston_logind *wl, uint32_t major,
128 m = dbus_message_new_method_call("org.freedesktop.login1",
130 "org.freedesktop.login1.Session",
133 b = dbus_message_append_args(m,
134 DBUS_TYPE_UINT32, &major,
135 DBUS_TYPE_UINT32, &minor,
138 dbus_connection_send(wl->dbus, m, NULL);
139 dbus_message_unref(m);
144 weston_logind_pause_device_complete(struct weston_logind *wl, uint32_t major,
150 m = dbus_message_new_method_call("org.freedesktop.login1",
152 "org.freedesktop.login1.Session",
153 "PauseDeviceComplete");
155 b = dbus_message_append_args(m,
156 DBUS_TYPE_UINT32, &major,
157 DBUS_TYPE_UINT32, &minor,
160 dbus_connection_send(wl->dbus, m, NULL);
161 dbus_message_unref(m);
166 weston_logind_open(struct weston_logind *wl, const char *path,
175 if (!S_ISCHR(st.st_mode)) {
180 fd = weston_logind_take_device(wl, major(st.st_rdev),
181 minor(st.st_rdev), NULL);
185 /* Compared to weston_launcher_open() we cannot specify the open-mode
186 * directly. Instead, logind passes us an fd with sane default modes.
187 * For DRM and evdev this means O_RDWR | O_CLOEXEC. If we want
188 * something else, we need to change it afterwards. We currently
189 * only support setting O_NONBLOCK. Changing access-modes is not
190 * possible so accept whatever logind passes us. */
192 fl = fcntl(fd, F_GETFL);
198 if (flags & O_NONBLOCK)
201 r = fcntl(fd, F_SETFL, fl);
210 weston_logind_release_device(wl, major(st.st_rdev),
217 weston_logind_close(struct weston_logind *wl, int fd)
224 weston_log("logind: cannot fstat fd: %m\n");
228 if (!S_ISCHR(st.st_mode)) {
229 weston_log("logind: invalid device passed\n");
233 weston_logind_release_device(wl, major(st.st_rdev),
238 weston_logind_restore(struct weston_logind *wl)
240 struct vt_mode mode = { 0 };
242 ioctl(wl->vt, KDSETMODE, KD_TEXT);
243 ioctl(wl->vt, KDSKBMUTE, 0);
244 ioctl(wl->vt, KDSKBMODE, wl->kb_mode);
246 ioctl(wl->vt, VT_SETMODE, &mode);
250 weston_logind_activate_vt(struct weston_logind *wl, int vt)
254 r = ioctl(wl->vt, VT_ACTIVATE, vt);
262 weston_logind_set_active(struct weston_logind *wl, bool active)
264 if (!wl->compositor->session_active == !active)
267 wl->compositor->session_active = active;
269 wl_signal_emit(&wl->compositor->session_signal,
274 parse_active(struct weston_logind *wl, DBusMessage *m, DBusMessageIter *iter)
279 if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT)
282 dbus_message_iter_recurse(iter, &sub);
284 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
287 dbus_message_iter_get_basic(&sub, &b);
289 /* If the backend requested DRM master-device synchronization, we only
290 * wake-up the compositor once the master-device is up and running. For
291 * other backends, we immediately forward the Active-change event. */
292 if (!wl->sync_drm || !b)
293 weston_logind_set_active(wl, b);
297 get_active_cb(DBusPendingCall *pending, void *data)
299 struct weston_logind *wl = data;
300 DBusMessageIter iter;
304 dbus_pending_call_unref(wl->pending_active);
305 wl->pending_active = NULL;
307 m = dbus_pending_call_steal_reply(pending);
311 type = dbus_message_get_type(m);
312 if (type == DBUS_MESSAGE_TYPE_METHOD_RETURN &&
313 dbus_message_iter_init(m, &iter))
314 parse_active(wl, m, &iter);
316 dbus_message_unref(m);
320 weston_logind_get_active(struct weston_logind *wl)
322 DBusPendingCall *pending;
325 const char *iface, *name;
327 m = dbus_message_new_method_call("org.freedesktop.login1",
329 "org.freedesktop.DBus.Properties",
334 iface = "org.freedesktop.login1.Session";
336 b = dbus_message_append_args(m,
337 DBUS_TYPE_STRING, &iface,
338 DBUS_TYPE_STRING, &name,
343 b = dbus_connection_send_with_reply(wl->dbus, m, &pending, -1);
347 b = dbus_pending_call_set_notify(pending, get_active_cb, wl, NULL);
349 dbus_pending_call_cancel(pending);
350 dbus_pending_call_unref(pending);
354 if (wl->pending_active) {
355 dbus_pending_call_cancel(wl->pending_active);
356 dbus_pending_call_unref(wl->pending_active);
358 wl->pending_active = pending;
362 dbus_message_unref(m);
366 disconnected_dbus(struct weston_logind *wl)
368 weston_log("logind: dbus connection lost, exiting..\n");
369 weston_logind_restore(wl);
374 session_removed(struct weston_logind *wl, DBusMessage *m)
376 const char *name, *obj;
379 r = dbus_message_get_args(m, NULL,
380 DBUS_TYPE_STRING, &name,
381 DBUS_TYPE_OBJECT_PATH, &obj,
384 weston_log("logind: cannot parse SessionRemoved dbus signal\n");
388 if (!strcmp(name, wl->sid)) {
389 weston_log("logind: our session got closed, exiting..\n");
390 weston_logind_restore(wl);
396 property_changed(struct weston_logind *wl, DBusMessage *m)
398 DBusMessageIter iter, sub, entry;
399 const char *interface, *name;
401 if (!dbus_message_iter_init(m, &iter) ||
402 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
405 dbus_message_iter_get_basic(&iter, &interface);
407 if (!dbus_message_iter_next(&iter) ||
408 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
411 dbus_message_iter_recurse(&iter, &sub);
413 while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY) {
414 dbus_message_iter_recurse(&sub, &entry);
416 if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
419 dbus_message_iter_get_basic(&entry, &name);
420 if (!dbus_message_iter_next(&entry))
423 if (!strcmp(name, "Active")) {
424 parse_active(wl, m, &entry);
428 dbus_message_iter_next(&sub);
431 if (!dbus_message_iter_next(&iter) ||
432 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
435 dbus_message_iter_recurse(&iter, &sub);
437 while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
438 dbus_message_iter_get_basic(&sub, &name);
440 if (!strcmp(name, "Active")) {
441 weston_logind_get_active(wl);
445 dbus_message_iter_next(&sub);
451 weston_log("logind: cannot parse PropertiesChanged dbus signal\n");
455 device_paused(struct weston_logind *wl, DBusMessage *m)
459 uint32_t major, minor;
461 r = dbus_message_get_args(m, NULL,
462 DBUS_TYPE_UINT32, &major,
463 DBUS_TYPE_UINT32, &minor,
464 DBUS_TYPE_STRING, &type,
467 weston_log("logind: cannot parse PauseDevice dbus signal\n");
471 /* "pause" means synchronous pausing. Acknowledge it unconditionally
472 * as we support asynchronous device shutdowns, anyway.
473 * "force" means asynchronous pausing.
474 * "gone" means the device is gone. We handle it the same as "force" as
475 * a following udev event will be caught, too.
477 * If it's our main DRM device, tell the compositor to go asleep. */
479 if (!strcmp(type, "pause"))
480 weston_logind_pause_device_complete(wl, major, minor);
482 if (wl->sync_drm && major == DRM_MAJOR)
483 weston_logind_set_active(wl, false);
487 device_resumed(struct weston_logind *wl, DBusMessage *m)
492 r = dbus_message_get_args(m, NULL,
493 DBUS_TYPE_UINT32, &major,
494 /*DBUS_TYPE_UINT32, &minor,
495 DBUS_TYPE_UNIX_FD, &fd,*/
498 weston_log("logind: cannot parse ResumeDevice dbus signal\n");
502 /* DeviceResumed messages provide us a new file-descriptor for
503 * resumed devices. For DRM devices it's the same as before, for evdev
504 * devices it's a new open-file. As we reopen evdev devices, anyway,
505 * there is no need for us to handle this event for evdev. For DRM, we
506 * notify the compositor to wake up. */
508 if (wl->sync_drm && major == DRM_MAJOR)
509 weston_logind_set_active(wl, true);
512 static DBusHandlerResult
513 filter_dbus(DBusConnection *c, DBusMessage *m, void *data)
515 struct weston_logind *wl = data;
517 if (dbus_message_is_signal(m, DBUS_INTERFACE_LOCAL, "Disconnected")) {
518 disconnected_dbus(wl);
519 } else if (dbus_message_is_signal(m, "org.freedesktop.login1.Manager",
521 session_removed(wl, m);
522 } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.Properties",
523 "PropertiesChanged")) {
524 property_changed(wl, m);
525 } else if (dbus_message_is_signal(m, "org.freedesktop.login1.Session",
527 device_paused(wl, m);
528 } else if (dbus_message_is_signal(m, "org.freedesktop.login1.Session",
530 device_resumed(wl, m);
533 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
537 weston_logind_setup_dbus(struct weston_logind *wl)
542 r = asprintf(&wl->spath, "/org/freedesktop/login1/session/%s",
547 b = dbus_connection_add_filter(wl->dbus, filter_dbus, wl, NULL);
549 weston_log("logind: cannot add dbus filter\n");
554 r = weston_dbus_add_match_signal(wl->dbus,
555 "org.freedesktop.login1",
556 "org.freedesktop.login1.Manager",
558 "/org/freedesktop/login1");
560 weston_log("logind: cannot add dbus match\n");
564 r = weston_dbus_add_match_signal(wl->dbus,
565 "org.freedesktop.login1",
566 "org.freedesktop.login1.Session",
570 weston_log("logind: cannot add dbus match\n");
574 r = weston_dbus_add_match_signal(wl->dbus,
575 "org.freedesktop.login1",
576 "org.freedesktop.login1.Session",
580 weston_log("logind: cannot add dbus match\n");
584 r = weston_dbus_add_match_signal(wl->dbus,
585 "org.freedesktop.login1",
586 "org.freedesktop.DBus.Properties",
590 weston_log("logind: cannot add dbus match\n");
597 /* don't remove any dbus-match as the connection is closed, anyway */
603 weston_logind_destroy_dbus(struct weston_logind *wl)
605 /* don't remove any dbus-match as the connection is closed, anyway */
610 weston_logind_take_control(struct weston_logind *wl)
613 DBusMessage *m, *reply;
618 dbus_error_init(&err);
620 m = dbus_message_new_method_call("org.freedesktop.login1",
622 "org.freedesktop.login1.Session",
628 b = dbus_message_append_args(m,
629 DBUS_TYPE_BOOLEAN, &force,
636 reply = dbus_connection_send_with_reply_and_block(wl->dbus,
639 if (dbus_error_has_name(&err, DBUS_ERROR_UNKNOWN_METHOD))
640 weston_log("logind: old systemd version detected\n");
642 weston_log("logind: cannot take control over session %s\n", wl->sid);
644 dbus_error_free(&err);
649 dbus_message_unref(reply);
650 dbus_message_unref(m);
654 dbus_message_unref(m);
659 weston_logind_release_control(struct weston_logind *wl)
663 m = dbus_message_new_method_call("org.freedesktop.login1",
665 "org.freedesktop.login1.Session",
668 dbus_connection_send(wl->dbus, m, NULL);
669 dbus_message_unref(m);
674 signal_event(int fd, uint32_t mask, void *data)
676 struct weston_logind *wl = data;
677 struct signalfd_siginfo sig;
679 if (read(fd, &sig, sizeof sig) != sizeof sig) {
680 weston_log("logind: cannot read signalfd: %m\n");
684 if (sig.ssi_signo == (unsigned int)SIGRTMIN)
685 ioctl(wl->vt, VT_RELDISP, 1);
686 else if (sig.ssi_signo == (unsigned int)SIGRTMIN + 1)
687 ioctl(wl->vt, VT_RELDISP, VT_ACKACQ);
693 weston_logind_setup_vt(struct weston_logind *wl)
697 struct vt_mode mode = { 0 };
700 struct wl_event_loop *loop;
702 snprintf(buf, sizeof(buf), "/dev/tty%d", wl->vtnr);
703 buf[sizeof(buf) - 1] = 0;
705 wl->vt = open(buf, O_RDWR|O_CLOEXEC|O_NONBLOCK);
709 weston_log("logind: cannot open VT %s: %m\n", buf);
713 if (fstat(wl->vt, &st) == -1 ||
714 major(st.st_rdev) != TTY_MAJOR || minor(st.st_rdev) <= 0 ||
715 minor(st.st_rdev) >= 64) {
717 weston_log("logind: TTY %s is no virtual terminal\n", buf);
722 if (r < 0 && errno != EPERM) {
724 weston_log("logind: setsid() failed: %m\n");
728 r = ioctl(wl->vt, TIOCSCTTY, 0);
730 weston_log("logind: VT %s already in use\n", buf);*/
732 if (ioctl(wl->vt, KDGKBMODE, &wl->kb_mode) < 0) {
733 weston_log("logind: cannot read keyboard mode on %s: %m\n",
735 wl->kb_mode = K_UNICODE;
736 } else if (wl->kb_mode == K_OFF) {
737 wl->kb_mode = K_UNICODE;
740 if (ioctl(wl->vt, KDSKBMUTE, 1) < 0 &&
741 ioctl(wl->vt, KDSKBMODE, K_OFF) < 0) {
743 weston_log("logind: cannot set K_OFF KB-mode on %s: %m\n",
748 if (ioctl(wl->vt, KDSETMODE, KD_GRAPHICS) < 0) {
750 weston_log("logind: cannot set KD_GRAPHICS mode on %s: %m\n",
756 * SIGRTMIN is used as global VT-release signal, SIGRTMIN + 1 is used
757 * as VT-acquire signal. Note that SIGRT* must be tested on runtime, as
758 * their exact values are not known at compile-time. POSIX requires 32
759 * of them to be available, though.
761 if (SIGRTMIN + 1 > SIGRTMAX) {
762 weston_log("logind: not enough RT signals available: %u-%u\n",
768 sigaddset(&mask, SIGRTMIN);
769 sigaddset(&mask, SIGRTMIN + 1);
770 sigprocmask(SIG_BLOCK, &mask, NULL);
772 wl->sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
775 weston_log("logind: cannot create signalfd: %m\n");
779 loop = wl_display_get_event_loop(wl->compositor->wl_display);
780 wl->sfd_source = wl_event_loop_add_fd(loop, wl->sfd,
783 if (!wl->sfd_source) {
785 weston_log("logind: cannot create signalfd source: %m\n");
789 mode.mode = VT_PROCESS;
790 mode.relsig = SIGRTMIN;
791 mode.acqsig = SIGRTMIN + 1;
792 if (ioctl(wl->vt, VT_SETMODE, &mode) < 0) {
794 weston_log("logind: cannot take over VT: %m\n");
798 weston_log("logind: using VT %s\n", buf);
802 wl_event_source_remove(wl->sfd_source);
806 ioctl(wl->vt, KDSETMODE, KD_TEXT);
808 ioctl(wl->vt, KDSKBMUTE, 0);
809 ioctl(wl->vt, KDSKBMODE, wl->kb_mode);
816 weston_logind_destroy_vt(struct weston_logind *wl)
818 weston_logind_restore(wl);
819 wl_event_source_remove(wl->sfd_source);
825 weston_logind_connect(struct weston_logind **out,
826 struct weston_compositor *compositor,
827 const char *seat_id, int tty, bool sync_drm)
829 struct weston_logind *wl;
830 struct wl_event_loop *loop;
834 wl = zalloc(sizeof(*wl));
840 wl->compositor = compositor;
841 wl->sync_drm = sync_drm;
843 wl->seat = strdup(seat_id);
849 r = sd_pid_get_session(getpid(), &wl->sid);
851 weston_log("logind: not running in a systemd session\n");
856 r = sd_session_get_seat(wl->sid, &t);
858 weston_log("logind: failed to get session seat\n");
861 } else if (strcmp(seat_id, t)) {
862 weston_log("logind: weston's seat '%s' differs from session-seat '%s'\n",
870 r = weston_sd_session_get_vt(wl->sid, &wl->vtnr);
872 weston_log("logind: session not running on a VT\n");
874 } else if (tty > 0 && wl->vtnr != (unsigned int )tty) {
875 weston_log("logind: requested VT --tty=%d differs from real session VT %u\n",
881 loop = wl_display_get_event_loop(compositor->wl_display);
882 r = weston_dbus_open(loop, DBUS_BUS_SYSTEM, &wl->dbus, &wl->dbus_ctx);
884 weston_log("logind: cannot connect to system dbus\n");
888 r = weston_logind_setup_dbus(wl);
892 r = weston_logind_take_control(wl);
894 goto err_dbus_cleanup;
896 r = weston_logind_setup_vt(wl);
900 weston_log("logind: session control granted\n");
905 weston_logind_release_control(wl);
907 weston_logind_destroy_dbus(wl);
909 weston_dbus_close(wl->dbus, wl->dbus_ctx);
917 weston_log("logind: cannot setup systemd-logind helper (%d), using legacy fallback\n", r);
923 weston_logind_destroy(struct weston_logind *wl)
925 if (wl->pending_active) {
926 dbus_pending_call_cancel(wl->pending_active);
927 dbus_pending_call_unref(wl->pending_active);
930 weston_logind_destroy_vt(wl);
931 weston_logind_release_control(wl);
932 weston_logind_destroy_dbus(wl);
933 weston_dbus_close(wl->dbus, wl->dbus_ctx);