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;
60 struct wl_event_source *sfd_source;
63 struct wl_event_source *dbus_ctx;
65 DBusPendingCall *pending_active;
69 weston_logind_take_device(struct weston_logind *wl, uint32_t major,
70 uint32_t minor, bool *paused_out)
72 DBusMessage *m, *reply;
77 m = dbus_message_new_method_call("org.freedesktop.login1",
79 "org.freedesktop.login1.Session",
84 b = dbus_message_append_args(m,
85 DBUS_TYPE_UINT32, &major,
86 DBUS_TYPE_UINT32, &minor,
93 reply = dbus_connection_send_with_reply_and_block(wl->dbus, m,
100 b = dbus_message_get_args(reply, NULL,
101 DBUS_TYPE_UNIX_FD, &fd,
102 DBUS_TYPE_BOOLEAN, &paused,
111 *paused_out = paused;
114 dbus_message_unref(reply);
116 dbus_message_unref(m);
121 weston_logind_release_device(struct weston_logind *wl, uint32_t major,
127 m = dbus_message_new_method_call("org.freedesktop.login1",
129 "org.freedesktop.login1.Session",
132 b = dbus_message_append_args(m,
133 DBUS_TYPE_UINT32, &major,
134 DBUS_TYPE_UINT32, &minor,
137 dbus_connection_send(wl->dbus, m, NULL);
138 dbus_message_unref(m);
143 weston_logind_pause_device_complete(struct weston_logind *wl, uint32_t major,
149 m = dbus_message_new_method_call("org.freedesktop.login1",
151 "org.freedesktop.login1.Session",
152 "PauseDeviceComplete");
154 b = dbus_message_append_args(m,
155 DBUS_TYPE_UINT32, &major,
156 DBUS_TYPE_UINT32, &minor,
159 dbus_connection_send(wl->dbus, m, NULL);
160 dbus_message_unref(m);
165 weston_logind_open(struct weston_logind *wl, const char *path,
174 if (!S_ISCHR(st.st_mode)) {
179 fd = weston_logind_take_device(wl, major(st.st_rdev),
180 minor(st.st_rdev), NULL);
184 /* Compared to weston_launcher_open() we cannot specify the open-mode
185 * directly. Instead, logind passes us an fd with sane default modes.
186 * For DRM and evdev this means O_RDWR | O_CLOEXEC. If we want
187 * something else, we need to change it afterwards. We currently
188 * only support dropping FD_CLOEXEC and setting O_NONBLOCK. Changing
189 * access-modes is not possible so accept whatever logind passes us. */
191 fl = fcntl(fd, F_GETFL);
197 if (flags & O_NONBLOCK)
200 r = fcntl(fd, F_SETFL, fl);
206 fl = fcntl(fd, F_GETFD);
212 if (!(flags & O_CLOEXEC))
215 r = fcntl(fd, F_SETFD, fl);
225 weston_logind_release_device(wl, major(st.st_rdev),
232 weston_logind_close(struct weston_logind *wl, int fd)
239 weston_log("logind: cannot fstat fd: %m\n");
243 if (!S_ISCHR(st.st_mode)) {
244 weston_log("logind: invalid device passed\n");
248 weston_logind_release_device(wl, major(st.st_rdev),
253 weston_logind_restore(struct weston_logind *wl)
255 struct vt_mode mode = { 0 };
257 ioctl(wl->vt, KDSETMODE, KD_TEXT);
258 ioctl(wl->vt, KDSKBMUTE, 0);
259 ioctl(wl->vt, KDSKBMODE, wl->kb_mode);
261 ioctl(wl->vt, VT_SETMODE, &mode);
265 weston_logind_activate_vt(struct weston_logind *wl, int vt)
269 r = ioctl(wl->vt, VT_ACTIVATE, vt);
277 weston_logind_set_active(struct weston_logind *wl, bool active)
279 if (!wl->compositor->session_active == !active)
282 wl->compositor->session_active = active;
284 wl_signal_emit(&wl->compositor->session_signal,
289 get_active_cb(DBusPendingCall *pending, void *data)
291 struct weston_logind *wl = data;
293 DBusMessageIter iter, sub;
297 dbus_pending_call_unref(wl->pending_active);
298 wl->pending_active = NULL;
300 m = dbus_pending_call_steal_reply(pending);
304 type = dbus_message_get_type(m);
305 if (type != DBUS_MESSAGE_TYPE_METHOD_RETURN)
308 if (!dbus_message_iter_init(m, &iter) ||
309 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
312 dbus_message_iter_recurse(&iter, &sub);
314 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
317 dbus_message_iter_get_basic(&sub, &b);
319 weston_logind_set_active(wl, false);
322 dbus_message_unref(m);
326 weston_logind_get_active(struct weston_logind *wl)
328 DBusPendingCall *pending;
331 const char *iface, *name;
333 m = dbus_message_new_method_call("org.freedesktop.login1",
335 "org.freedesktop.DBus.Properties",
340 iface = "org.freedesktop.login1.Session";
342 b = dbus_message_append_args(m,
343 DBUS_TYPE_STRING, &iface,
344 DBUS_TYPE_STRING, &name,
349 b = dbus_connection_send_with_reply(wl->dbus, m, &pending, -1);
353 b = dbus_pending_call_set_notify(pending, get_active_cb, wl, NULL);
355 dbus_pending_call_cancel(pending);
356 dbus_pending_call_unref(pending);
360 if (wl->pending_active) {
361 dbus_pending_call_cancel(wl->pending_active);
362 dbus_pending_call_unref(wl->pending_active);
364 wl->pending_active = pending;
368 dbus_message_unref(m);
372 disconnected_dbus(struct weston_logind *wl)
374 weston_log("logind: dbus connection lost, exiting..\n");
375 weston_logind_restore(wl);
380 session_removed(struct weston_logind *wl, DBusMessage *m)
382 const char *name, *obj;
385 r = dbus_message_get_args(m, NULL,
386 DBUS_TYPE_STRING, &name,
387 DBUS_TYPE_OBJECT_PATH, &obj,
390 weston_log("logind: cannot parse SessionRemoved dbus signal\n");
394 if (!strcmp(name, wl->sid)) {
395 weston_log("logind: our session got closed, exiting..\n");
396 weston_logind_restore(wl);
402 property_changed(struct weston_logind *wl, DBusMessage *m)
404 DBusMessageIter iter, sub, entry;
405 const char *interface, *name;
408 if (!dbus_message_iter_init(m, &iter) ||
409 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
412 dbus_message_iter_get_basic(&iter, &interface);
414 if (!dbus_message_iter_next(&iter) ||
415 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
418 dbus_message_iter_recurse(&iter, &sub);
420 while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY) {
421 dbus_message_iter_recurse(&sub, &entry);
423 if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
426 dbus_message_iter_get_basic(&entry, &name);
427 if (!dbus_message_iter_next(&entry))
430 if (!strcmp(name, "Active")) {
431 if (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_BOOLEAN) {
432 dbus_message_iter_get_basic(&entry, &b);
434 weston_logind_set_active(wl, false);
439 dbus_message_iter_next(&sub);
442 if (!dbus_message_iter_next(&iter) ||
443 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
446 dbus_message_iter_recurse(&iter, &sub);
448 while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
449 dbus_message_iter_get_basic(&sub, &name);
451 if (!strcmp(name, "Active")) {
452 weston_logind_get_active(wl);
456 dbus_message_iter_next(&sub);
462 weston_log("logind: cannot parse PropertiesChanged dbus signal\n");
466 device_paused(struct weston_logind *wl, DBusMessage *m)
470 uint32_t major, minor;
472 r = dbus_message_get_args(m, NULL,
473 DBUS_TYPE_UINT32, &major,
474 DBUS_TYPE_UINT32, &minor,
475 DBUS_TYPE_STRING, &type,
478 weston_log("logind: cannot parse PauseDevice dbus signal\n");
482 /* "pause" means synchronous pausing. Acknowledge it unconditionally
483 * as we support asynchronous device shutdowns, anyway.
484 * "force" means asynchronous pausing.
485 * "gone" means the device is gone. We handle it the same as "force" as
486 * a following udev event will be caught, too.
488 * If it's our main DRM device, tell the compositor to go asleep. */
490 if (!strcmp(type, "pause"))
491 weston_logind_pause_device_complete(wl, major, minor);
493 if (major == DRM_MAJOR)
494 weston_logind_set_active(wl, false);
498 device_resumed(struct weston_logind *wl, DBusMessage *m)
503 r = dbus_message_get_args(m, NULL,
504 DBUS_TYPE_UINT32, &major,
505 /*DBUS_TYPE_UINT32, &minor,
506 DBUS_TYPE_UNIX_FD, &fd,*/
509 weston_log("logind: cannot parse ResumeDevice dbus signal\n");
513 /* DeviceResumed messages provide us a new file-descriptor for
514 * resumed devices. For DRM devices it's the same as before, for evdev
515 * devices it's a new open-file. As we reopen evdev devices, anyway,
516 * there is no need for us to handle this event for evdev. For DRM, we
517 * notify the compositor to wake up. */
519 if (major == DRM_MAJOR)
520 weston_logind_set_active(wl, true);
523 static DBusHandlerResult
524 filter_dbus(DBusConnection *c, DBusMessage *m, void *data)
526 struct weston_logind *wl = data;
528 if (dbus_message_is_signal(m, DBUS_INTERFACE_LOCAL, "Disconnected")) {
529 disconnected_dbus(wl);
530 } else if (dbus_message_is_signal(m, "org.freedesktop.login1.Manager",
532 session_removed(wl, m);
533 } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.Properties",
534 "PropertiesChanged")) {
535 property_changed(wl, m);
536 } else if (dbus_message_is_signal(m, "org.freedesktop.login1.Session",
538 device_paused(wl, m);
539 } else if (dbus_message_is_signal(m, "org.freedesktop.login1.Session",
541 device_resumed(wl, m);
544 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
548 weston_logind_setup_dbus(struct weston_logind *wl)
553 r = asprintf(&wl->spath, "/org/freedesktop/login1/session/%s",
558 b = dbus_connection_add_filter(wl->dbus, filter_dbus, wl, NULL);
560 weston_log("logind: cannot add dbus filter\n");
565 r = weston_dbus_add_match_signal(wl->dbus,
566 "org.freedesktop.login1",
567 "org.freedesktop.login1.Manager",
569 "/org/freedesktop/login1");
571 weston_log("logind: cannot add dbus match\n");
575 r = weston_dbus_add_match_signal(wl->dbus,
576 "org.freedesktop.login1",
577 "org.freedesktop.login1.Session",
581 weston_log("logind: cannot add dbus match\n");
585 r = weston_dbus_add_match_signal(wl->dbus,
586 "org.freedesktop.login1",
587 "org.freedesktop.login1.Session",
591 weston_log("logind: cannot add dbus match\n");
595 r = weston_dbus_add_match_signal(wl->dbus,
596 "org.freedesktop.login1",
597 "org.freedesktop.DBus.Properties",
601 weston_log("logind: cannot add dbus match\n");
608 /* don't remove any dbus-match as the connection is closed, anyway */
614 weston_logind_destroy_dbus(struct weston_logind *wl)
616 /* don't remove any dbus-match as the connection is closed, anyway */
621 weston_logind_take_control(struct weston_logind *wl)
624 DBusMessage *m, *reply;
629 dbus_error_init(&err);
631 m = dbus_message_new_method_call("org.freedesktop.login1",
633 "org.freedesktop.login1.Session",
639 b = dbus_message_append_args(m,
640 DBUS_TYPE_BOOLEAN, &force,
647 reply = dbus_connection_send_with_reply_and_block(wl->dbus,
650 if (dbus_error_has_name(&err, DBUS_ERROR_UNKNOWN_METHOD))
651 weston_log("logind: old systemd version detected\n");
653 weston_log("logind: cannot take control over session %s\n", wl->sid);
655 dbus_error_free(&err);
660 dbus_message_unref(reply);
661 dbus_message_unref(m);
665 dbus_message_unref(m);
670 weston_logind_release_control(struct weston_logind *wl)
674 m = dbus_message_new_method_call("org.freedesktop.login1",
676 "org.freedesktop.login1.Session",
679 dbus_connection_send(wl->dbus, m, NULL);
680 dbus_message_unref(m);
685 signal_event(int fd, uint32_t mask, void *data)
687 struct weston_logind *wl = data;
688 struct signalfd_siginfo sig;
690 if (read(fd, &sig, sizeof sig) != sizeof sig) {
691 weston_log("logind: cannot read signalfd: %m\n");
695 switch (sig.ssi_signo) {
697 ioctl(wl->vt, VT_RELDISP, 1);
700 ioctl(wl->vt, VT_RELDISP, VT_ACKACQ);
708 weston_logind_setup_vt(struct weston_logind *wl)
712 struct vt_mode mode = { 0 };
715 struct wl_event_loop *loop;
717 snprintf(buf, sizeof(buf), "/dev/tty%d", wl->vtnr);
718 buf[sizeof(buf) - 1] = 0;
720 wl->vt = open(buf, O_RDWR|O_CLOEXEC|O_NONBLOCK);
724 weston_log("logind: cannot open VT %s: %m\n", buf);
728 if (fstat(wl->vt, &st) == -1 ||
729 major(st.st_rdev) != TTY_MAJOR || minor(st.st_rdev) <= 0 ||
730 minor(st.st_rdev) >= 64) {
732 weston_log("logind: TTY %s is no virtual terminal\n", buf);
737 if (r < 0 && errno != EPERM) {
739 weston_log("logind: setsid() failed: %m\n");
743 r = ioctl(wl->vt, TIOCSCTTY, 0);
745 weston_log("logind: VT %s already in use\n", buf);*/
747 if (ioctl(wl->vt, KDGKBMODE, &wl->kb_mode) < 0) {
748 weston_log("logind: cannot read keyboard mode on %s: %m\n",
750 wl->kb_mode = K_UNICODE;
751 } else if (wl->kb_mode == K_OFF) {
752 wl->kb_mode = K_UNICODE;
755 if (ioctl(wl->vt, KDSKBMUTE, 1) < 0 &&
756 ioctl(wl->vt, KDSKBMODE, K_OFF) < 0) {
758 weston_log("logind: cannot set K_OFF KB-mode on %s: %m\n",
763 if (ioctl(wl->vt, KDSETMODE, KD_GRAPHICS) < 0) {
765 weston_log("logind: cannot set KD_GRAPHICS mode on %s: %m\n",
771 sigaddset(&mask, SIGUSR1);
772 sigaddset(&mask, SIGUSR2);
773 sigprocmask(SIG_BLOCK, &mask, NULL);
775 wl->sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
778 weston_log("logind: cannot create signalfd: %m\n");
782 loop = wl_display_get_event_loop(wl->compositor->wl_display);
783 wl->sfd_source = wl_event_loop_add_fd(loop, wl->sfd,
786 if (!wl->sfd_source) {
788 weston_log("logind: cannot create signalfd source: %m\n");
792 mode.mode = VT_PROCESS;
793 mode.relsig = SIGUSR1;
794 mode.acqsig = SIGUSR2;
795 if (ioctl(wl->vt, VT_SETMODE, &mode) < 0) {
797 weston_log("logind: cannot take over VT: %m\n");
801 weston_log("logind: using VT %s\n", buf);
805 wl_event_source_remove(wl->sfd_source);
809 ioctl(wl->vt, KDSETMODE, KD_TEXT);
811 ioctl(wl->vt, KDSKBMUTE, 0);
812 ioctl(wl->vt, KDSKBMODE, wl->kb_mode);
819 weston_logind_destroy_vt(struct weston_logind *wl)
821 weston_logind_restore(wl);
822 wl_event_source_remove(wl->sfd_source);
828 weston_logind_connect(struct weston_logind **out,
829 struct weston_compositor *compositor,
830 const char *seat_id, int tty)
832 struct weston_logind *wl;
833 struct wl_event_loop *loop;
837 wl = calloc(1, sizeof(*wl));
843 wl->compositor = compositor;
845 wl->seat = strdup(seat_id);
851 r = sd_pid_get_session(getpid(), &wl->sid);
853 weston_log("logind: not running in a systemd session\n");
858 r = sd_session_get_seat(wl->sid, &t);
860 weston_log("logind: failed to get session seat\n");
863 } else if (strcmp(seat_id, t)) {
864 weston_log("logind: weston's seat '%s' differs from session-seat '%s'\n",
872 r = weston_sd_session_get_vt(wl->sid, &wl->vtnr);
874 weston_log("logind: session not running on a VT\n");
876 } else if (tty > 0 && wl->vtnr != (unsigned int )tty) {
877 weston_log("logind: requested VT --tty=%d differs from real session VT %u\n",
883 loop = wl_display_get_event_loop(compositor->wl_display);
884 r = weston_dbus_open(loop, DBUS_BUS_SYSTEM, &wl->dbus, &wl->dbus_ctx);
886 weston_log("logind: cannot connect to system dbus\n");
890 r = weston_logind_setup_dbus(wl);
894 r = weston_logind_take_control(wl);
896 goto err_dbus_cleanup;
898 r = weston_logind_setup_vt(wl);
902 weston_log("logind: session control granted\n");
907 weston_logind_release_control(wl);
909 weston_logind_destroy_dbus(wl);
911 weston_dbus_close(wl->dbus, wl->dbus_ctx);
919 weston_log("logind: cannot setup systemd-logind helper (%d), using legacy fallback\n", r);
925 weston_logind_destroy(struct weston_logind *wl)
927 if (wl->pending_active) {
928 dbus_pending_call_cancel(wl->pending_active);
929 dbus_pending_call_unref(wl->pending_active);
932 weston_logind_destroy_vt(wl);
933 weston_logind_release_control(wl);
934 weston_logind_destroy_dbus(wl);
935 weston_dbus_close(wl->dbus, wl->dbus_ctx);