aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--LICENSE13
-rw-r--r--README.md15
-rw-r--r--main.c299
-rw-r--r--meson.build36
-rw-r--r--protocol/meson.build46
-rw-r--r--shell.nix5
7 files changed, 416 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e050ba0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/build
+/.cache \ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..456c488
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..12095d1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+# wlr-idle-proxy
+
+A simple org.freedesktop.ScreenSaver implementation for Wayland compositors that support idle-inhibit-unstable-v1.
+This allows software that inhibits screen locking over DBus to transparently inhibit idling on these compositors.
+
+## build
+
+```
+meson build
+ninja -C build
+```
+
+## usage
+
+Just run the output binary in `build`.
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..db8edc9
--- /dev/null
+++ b/main.c
@@ -0,0 +1,299 @@
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <dbus/dbus.h>
+
+#include "dbus/dbus-protocol.h"
+#include "dbus/dbus-shared.h"
+#include "idle-inhibit-unstable-v1-client-protocol.h"
+#include "wayland-client-core.h"
+#include "wayland-util.h"
+#include <wayland-client.h>
+
+const char *introspection_data =
+ "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection "
+ "1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">"
+ "<node name=\"/org/freedesktop/ScreenSaver\">"
+ " <interface name=\"org.freedesktop.ScreenSaver\">"
+ " <tp:docstring>"
+ " The Idle Inhibition Service manages the inhibition requests."
+ " </tp:docstring>"
+ " <method name=\"Inhibit\">"
+ " <tp:docstring>Inhibit idleness for the caller "
+ "application.</tp:docstring>"
+ " <arg name=\"application_name\" type=\"s\" direction=\"in\">"
+ " <tp:docstring>A unique identifier for the application, usually a "
+ "reverse domain (such as 'org.freedesktop.example').</tp:docstring>"
+ " </arg>"
+ " <arg name=\"reason_for_inhibit\" type=\"s\" direction=\"in\">"
+ " <tp:docstring>A human-readable and possibly translated string "
+ "explaining the reason why idleness is inhibited (such as 'Playing a "
+ "movie').</tp:docstring>"
+ " </arg>"
+ " <arg name=\"cookie\" type=\"u\" direction=\"out\">"
+ " <tp:docstring>A cookie uniquely representing the inhibition "
+ "request, to be passed to UnInhibit when done.</tp:docstring>"
+ " </arg>"
+ " </method>"
+ " <method name=\"UnInhibit\">"
+ " <tp:docstring>Disable inhibit idleness for the caller "
+ "application.</tp:docstring>"
+ " <arg name=\"cookie\" type=\"u\" direction=\"in\">"
+ " <tp:docstring>A cookie representing the inhibition request, as "
+ "returned by the 'Inhibit' function.</tp:docstring>"
+ " </arg>"
+ " </method>"
+ " </interface>"
+ "</node>";
+
+struct Inhibitor {
+ struct wl_list link;
+ char *application_name;
+ char *reason_for_inhibit;
+ uint32_t cookie;
+};
+
+static struct wl_list inhibitor_list;
+
+static struct wl_surface *surface = NULL;
+struct wl_display *display = NULL;
+static struct wl_compositor *compositor = NULL;
+static struct wl_seat *seat = NULL;
+static struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = NULL;
+static struct zwp_idle_inhibitor_v1 *idle_inhibitor = NULL;
+
+static void handle_global(void *data, struct wl_registry *registry,
+ uint32_t name, const char *interface,
+ uint32_t version) {
+ if (strcmp(interface, "wl_compositor") == 0) {
+ compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1);
+ } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) ==
+ 0) {
+ idle_inhibit_manager = wl_registry_bind(
+ registry, name, &zwp_idle_inhibit_manager_v1_interface, 1);
+ } else if (strcmp(interface, wl_seat_interface.name) == 0) {
+ seat = wl_registry_bind(registry, name, &wl_seat_interface, version);
+ }
+}
+
+static void handle_global_remove(void *data, struct wl_registry *registry,
+ uint32_t name) {
+ // who cares
+}
+
+static const struct wl_registry_listener registry_listener = {
+ .global = handle_global,
+ .global_remove = handle_global_remove,
+};
+
+static DBusHandlerResult handle_message(DBusConnection *conn,
+ DBusMessage *message, void *data) {
+ const char *interface_name = dbus_message_get_interface(message);
+ const char *member_name = dbus_message_get_member(message);
+ if (!strncmp(interface_name, "org.freedesktop.DBus.Introspectable",
+ strlen("org.freedesktop.DBus.Introspectable"))) {
+ if (!strncmp(member_name, "Introspect", strlen("Introspect"))) {
+ DBusMessage *reply = dbus_message_new_method_return(message);
+ dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection_data,
+ DBUS_TYPE_INVALID);
+ dbus_connection_send(conn, reply, NULL);
+ dbus_message_unref(reply);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ } else if (!strncmp(interface_name, "org.freedesktop.ScreenSaver",
+ strlen("org.freedesktop.ScreenSaver"))) {
+ if (!strncmp(member_name, "Inhibit", strlen("Inhibit"))) {
+ DBusMessage *reply = dbus_message_new_method_return(message);
+ DBusError err;
+ char *application_name;
+ char *reason_for_inhibit;
+ dbus_error_init(&err);
+ struct Inhibitor *new_inhibitor = malloc(sizeof(struct Inhibitor));
+ if (!new_inhibitor) {
+ fprintf(stderr, "Memory allocation failed!");
+ exit(EXIT_FAILURE);
+ }
+ dbus_message_get_args(message, &err, DBUS_TYPE_STRING, &application_name,
+ DBUS_TYPE_STRING, &reason_for_inhibit,
+ DBUS_TYPE_INVALID);
+ if (dbus_error_is_set(&err)) {
+ reply = dbus_message_new_error(message, "wrong_arguments",
+ "Illegal arguments");
+ dbus_connection_send(conn, reply, NULL);
+ dbus_message_unref(reply);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ new_inhibitor->application_name = malloc(strlen(application_name) + 1);
+ if (!new_inhibitor->application_name) {
+ fprintf(stderr, "Memory allocation failed!");
+ exit(EXIT_FAILURE);
+ }
+ strcpy(new_inhibitor->application_name, application_name);
+ new_inhibitor->reason_for_inhibit =
+ malloc(strlen(reason_for_inhibit) + 1);
+ if (!new_inhibitor->reason_for_inhibit) {
+ fprintf(stderr, "Memory allocation failed!");
+ exit(EXIT_FAILURE);
+ }
+ strcpy(new_inhibitor->reason_for_inhibit, reason_for_inhibit);
+ uint32_t cookie = rand();
+ bool cookie_in_list = true;
+ while (cookie_in_list) {
+ cookie_in_list = false;
+ struct Inhibitor *elm;
+ wl_list_for_each(elm, &inhibitor_list, link) {
+ if (elm->cookie == cookie) {
+ cookie_in_list = true;
+ cookie = rand();
+ break;
+ }
+ }
+ }
+ new_inhibitor->cookie = cookie;
+ wl_list_insert(inhibitor_list.prev, &new_inhibitor->link);
+ reply = dbus_message_new_method_return(message);
+ dbus_message_append_args(reply, DBUS_TYPE_UINT32, &cookie,
+ DBUS_TYPE_INVALID);
+ dbus_connection_send(conn, reply, NULL);
+ dbus_message_unref(reply);
+ printf("Added inhibitor: %s, %s, %d\n", new_inhibitor->application_name,
+ new_inhibitor->reason_for_inhibit, new_inhibitor->cookie);
+ if (!wl_list_empty(&inhibitor_list) && idle_inhibitor == NULL) {
+ printf("Began inhibiting over wayland...\n");
+ idle_inhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(
+ idle_inhibit_manager, surface);
+ if (wl_display_roundtrip(display) == -1) {
+ fprintf(stderr, "Failed to commit wayland events!");
+ exit(EXIT_FAILURE);
+ }
+ }
+ return DBUS_HANDLER_RESULT_HANDLED;
+ } else if (!strncmp(member_name, "UnInhibit", strlen("UnInhibit"))) {
+ DBusMessage *reply = dbus_message_new_method_return(message);
+ DBusError err;
+ uint32_t cookie;
+ struct Inhibitor *to_remove = NULL;
+ struct Inhibitor *elm;
+ dbus_error_init(&err);
+ dbus_message_get_args(message, &err, DBUS_TYPE_UINT32, &cookie,
+ DBUS_TYPE_INVALID);
+ if (dbus_error_is_set(&err)) {
+ reply = dbus_message_new_error(message, "wrong_arguments",
+ "Illegal arguments");
+ dbus_connection_send(conn, reply, NULL);
+ dbus_message_unref(reply);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ wl_list_for_each(elm, &inhibitor_list, link) {
+ if (elm->cookie == cookie) {
+ to_remove = elm;
+ break;
+ }
+ }
+ if (!to_remove) {
+ printf("Recieved illegal cookie!");
+ reply = dbus_message_new_error(message, "wrong_arguments",
+ "Illegal cookie");
+ dbus_connection_send(conn, reply, NULL);
+ dbus_message_unref(reply);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ printf("Removed inhibitor: %s, %s, %d\n", to_remove->application_name,
+ to_remove->reason_for_inhibit, to_remove->cookie);
+ wl_list_remove(&to_remove->link);
+ free(to_remove->application_name);
+ free(to_remove->reason_for_inhibit);
+ free(to_remove);
+ if (wl_list_empty(&inhibitor_list) && idle_inhibitor != NULL) {
+ printf("Stopped inhibiting over wayland...\n");
+ zwp_idle_inhibitor_v1_destroy(idle_inhibitor);
+ idle_inhibitor = NULL;
+ if (wl_display_roundtrip(display) == -1) {
+ fprintf(stderr, "Failed to commit wayland events!");
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+ }
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+int main(int argc, char **argv) {
+ srand(time(NULL));
+ display = wl_display_connect(NULL);
+ if (display == NULL) {
+ fprintf(stderr, "Failed to create display\n");
+ return EXIT_FAILURE;
+ }
+
+ struct wl_registry *registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, &registry_listener, NULL);
+ wl_display_roundtrip(display);
+
+ if (compositor == NULL) {
+ fprintf(stderr, "wl-compositor not available\n");
+ return EXIT_FAILURE;
+ }
+ if (idle_inhibit_manager == NULL) {
+ fprintf(stderr, "idle-inhibit not available\n");
+ return EXIT_FAILURE;
+ }
+
+ surface = wl_compositor_create_surface(compositor);
+
+ wl_surface_commit(surface);
+ wl_display_roundtrip(display);
+
+ DBusConnection *conn;
+ DBusError err;
+ DBusObjectPathVTable vtable;
+ int ret;
+
+ dbus_error_init(&err);
+ conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
+ if (dbus_error_is_set(&err)) {
+ fprintf(stderr, "DBus connection error: %s\n", err.message);
+ dbus_error_free(&err);
+ }
+ if (conn == NULL) {
+ fprintf(stderr, "DBus connection failed.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ ret = dbus_bus_request_name(conn, "org.freedesktop.ScreenSaver",
+ DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
+ if (dbus_error_is_set(&err)) {
+ fprintf(stderr, "DBus name error: %s\n", err.message);
+ dbus_error_free(&err);
+ }
+ if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+ fprintf(stderr, "DBus: Failed to become primary owner of "
+ "org.freedesktop.ScreenSaver\n");
+ exit(EXIT_FAILURE);
+ }
+
+ vtable.message_function = handle_message;
+ vtable.unregister_function = NULL;
+
+ dbus_connection_try_register_object_path(conn, "/org/freedesktop/ScreenSaver",
+ &vtable, NULL, &err);
+ if (dbus_error_is_set(&err)) {
+ fprintf(stderr, "DBus object error: %s\n", err.message);
+ dbus_error_free(&err);
+ exit(EXIT_FAILURE);
+ }
+
+ wl_list_init(&inhibitor_list);
+
+ // TODO: wl_display_dispatch
+ while (true) {
+ dbus_connection_read_write_dispatch(conn, 1000);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..21efd00
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,36 @@
+project(
+ 'wlr-idle-proxy',
+ 'c',
+ version: '1.6',
+ license: 'WTFPL',
+ meson_version: '>=0.50.0',
+ default_options: [
+ 'c_std=c11',
+ ],
+)
+
+cc = meson.get_compiler('c')
+
+dbus = dependency('dbus-1')
+wayland_client = dependency('wayland-client')
+wayland_protos = dependency('wayland-protocols', version: '>=1.21')
+
+subdir('protocol')
+
+src_files = [
+ 'main.c',
+]
+
+executable(
+ 'wlr-idle-proxy',
+ files(src_files),
+ dependencies: [
+ client_protos,
+ dbus,
+ wayland_client,
+ ],
+ install: true,
+)
+
+conf_data = configuration_data()
+conf_data.set('bindir', join_paths(get_option('prefix'), get_option('bindir')))
diff --git a/protocol/meson.build b/protocol/meson.build
new file mode 100644
index 0000000..9ac6c07
--- /dev/null
+++ b/protocol/meson.build
@@ -0,0 +1,46 @@
+wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir')
+
+wayland_scanner = find_program('wayland-scanner', native: true)
+
+# should check wayland_scanner's version, but it is hard to get
+if wayland_client.version().version_compare('>=1.14.91')
+ code_type = 'private-code'
+else
+ code_type = 'code'
+endif
+
+wayland_scanner_code = generator(
+ wayland_scanner,
+ output: '@BASENAME@-protocol.c',
+ arguments: [code_type, '@INPUT@', '@OUTPUT@'],
+)
+
+wayland_scanner_client = generator(
+ wayland_scanner,
+ output: '@BASENAME@-client-protocol.h',
+ arguments: ['client-header', '@INPUT@', '@OUTPUT@'],
+)
+
+client_protocols = [
+ [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
+]
+
+client_protos_src = []
+client_protos_headers = []
+
+foreach p : client_protocols
+ xml = join_paths(p)
+ client_protos_src += wayland_scanner_code.process(xml)
+ client_protos_headers += wayland_scanner_client.process(xml)
+endforeach
+
+lib_client_protos = static_library(
+ 'client_protos',
+ client_protos_src + client_protos_headers,
+ dependencies: [wayland_client]
+) # for the include directory
+
+client_protos = declare_dependency(
+ link_with: lib_client_protos,
+ sources: client_protos_headers,
+)
diff --git a/shell.nix b/shell.nix
new file mode 100644
index 0000000..0be7253
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,5 @@
+{ pkgs ? import <nixpkgs> {} }:
+ pkgs.mkShell {
+ # nativeBuildInputs is usually what you want -- tools you need to run
+ nativeBuildInputs = with pkgs.buildPackages; [ meson ninja pkg-config wayland wayland-protocols wlroots dbus ];
+}