diff --git a/idle-inhibit-unstable-v1.xml b/idle-inhibit-unstable-v1.xml new file mode 100644 index 0000000..9c06cdc --- /dev/null +++ b/idle-inhibit-unstable-v1.xml @@ -0,0 +1,83 @@ + + + + + Copyright © 2015 Samsung Electronics Co., Ltd + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This interface permits inhibiting the idle behavior such as screen + blanking, locking, and screensaving. The client binds the idle manager + globally, then creates idle-inhibitor objects for each surface. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + Destroy the inhibit manager. + + + + + + Create a new inhibitor object associated with the given surface. + + + + + + + + + + An idle inhibitor prevents the output that the associated surface is + visible on from being set to a state where it is not visually usable due + to lack of user interaction (e.g. blanked, dimmed, locked, set to power + save, etc.) Any screensaver processes are also blocked from displaying. + + If the surface is destroyed, unmapped, becomes occluded, loses + visibility, or otherwise becomes not visually relevant for the user, the + idle inhibitor will not be honored by the compositor; if the surface + subsequently regains visibility the inhibitor takes effect once again. + Likewise, the inhibitor isn't honored if the system was already idled at + the time the inhibitor was established, although if the system later + de-idles and re-idles the inhibitor will take effect. + + + + + Remove the inhibitor effect from the associated wl_surface. + + + + + diff --git a/idle_inhibit.c b/idle_inhibit.c new file mode 100644 index 0000000..e37ee17 --- /dev/null +++ b/idle_inhibit.c @@ -0,0 +1,434 @@ +/* Wayland compositor running on top of an X server. + +Copyright (C) 2022 to various contributors. + +This file is part of 12to11. + +12to11 is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +12to11 is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with 12to11. If not, see . */ + +#include +#include + +#include "compositor.h" +#include "idle-inhibit-unstable-v1.h" + +typedef struct _IdleInhibitDataRecord IdleInhibitDataRecord; +typedef struct _IdleInhibitor IdleInhibitor; +typedef enum _IdleInhibition IdleInhibition; + +/* Idle inhibition is tricky because there is no threshold that tells + the protocol translator whether or not to apply idle inhibition for + surfaces that are already focused. So, contrary to the protocol + specification, we inhibit idle-ness as long as a surface with an + idle inhibitor is focused, even if the user was already idle at the + time the idle inhibitor was created. */ + +struct _IdleInhibitor +{ + /* The next and last idle inhibitors on this surface. */ + IdleInhibitor *next, *last; + + /* The next and last idle inhibitors globally. */ + IdleInhibitor *global_next, *global_last; + + /* The corresponding Surface. */ + Surface *surface; + + /* The corresponding wl_resource. */ + struct wl_resource *resource; +}; + +struct _IdleInhibitDataRecord +{ + /* List of idle inhibitors on this surface. */ + IdleInhibitor inhibitors; +}; + +enum _IdleInhibition + { + IdleAllowed, + IdleInhibited, + }; + +/* The idle inhibit manager global. */ +static struct wl_global *idle_inhibit_manager_global; + +/* List of all idle inhibitors. */ +static IdleInhibitor all_inhibitors; + +/* The current idle inhibition. */ +static IdleInhibition current_inhibition; + +/* Commands run while idle. The first command is run once upon idle + being inhibited; the second is run every N seconds while idle is + inhibited, and the third command is run every time idle is + deinhibited. */ +static char **inhibit_command, **timer_command, **deinhibit_command; + +/* How many seconds the protocol translator waits before running the + timer command. */ +static int timer_seconds; + +/* Timer used to run the timer command. */ +static Timer *command_timer; + +/* Process queue used to run those commands. */ +static ProcessQueue *process_queue; + +static void +HandleCommandTimer (Timer *timer, void *data, struct timespec time) +{ + /* The timer shouldn't have been started if the command is NULL. */ + RunProcess (process_queue, timer_command); +} + +static void +ChangeInhibitionTo (IdleInhibition inhibition) +{ + if (current_inhibition == inhibition) + /* Nothing changed. */ + return; + + current_inhibition = inhibition; + + if (current_inhibition == IdleInhibited) + { + /* Run the idle inhibit command, if it exists. */ + + if (inhibit_command) + RunProcess (process_queue, inhibit_command); + + /* Schedule a timer to run the timer command. */ + + if (timer_command) + command_timer = AddTimer (HandleCommandTimer, NULL, + MakeTimespec (timer_seconds, 0)); + } + else + { + /* Cancel the command timer. */ + if (command_timer) + RemoveTimer (command_timer); + command_timer = NULL; + + /* Run the deinhibit command. */ + + if (deinhibit_command) + RunProcess (process_queue, deinhibit_command); + } +} + +static void +DetectSurfaceIdleInhibit (void) +{ + IdleInhibitor *inhibitor; + + inhibitor = all_inhibitors.global_next; + while (inhibitor != &all_inhibitors) + { + if (inhibitor->surface->num_focused_seats) + { + ChangeInhibitionTo (IdleInhibited); + return; + } + + inhibitor = inhibitor->global_next; + } + + /* There are no live idle inhibitors for focused seats. */ + ChangeInhibitionTo (IdleAllowed); + + return; +} + +static void +NoticeSurfaceFocused (Surface *surface) +{ + IdleInhibitDataRecord *record; + + record = XLSurfaceFindClientData (surface, IdleInhibitData); + + if (!record) + return; + + if (record->inhibitors.next == &record->inhibitors) + return; + + /* There is an idle inhibitor for this idle surface. */ + ChangeInhibitionTo (IdleInhibited); +} + + + +static void +Destroy (struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static const struct zwp_idle_inhibitor_v1_interface idle_inhibitor_impl = + { + .destroy = Destroy, + }; + +static void +HandleResourceDestroy (struct wl_resource *resource) +{ + IdleInhibitor *inhibitor; + + inhibitor = wl_resource_get_user_data (resource); + + if (inhibitor->surface) + { + /* Unlink the inhibitor. */ + inhibitor->next->last = inhibitor->last; + inhibitor->last->next = inhibitor->next; + inhibitor->global_next->global_last = inhibitor->global_last; + inhibitor->global_last->global_next = inhibitor->global_next; + } + + /* Free the inhibitor; then, check if any other idle inhibitors are + still active. */ + XLFree (inhibitor); + DetectSurfaceIdleInhibit (); +} + + + +static void +FreeIdleInhibitData (void *data) +{ + IdleInhibitDataRecord *record; + IdleInhibitor *inhibitor, *last; + + record = data; + + /* Loop through each idle inhibitor. Unlink it. */ + inhibitor = record->inhibitors.next; + while (inhibitor != &record->inhibitors) + { + last = inhibitor; + inhibitor = inhibitor->next; + + last->next = NULL; + last->last = NULL; + last->global_next->global_last = last->global_last; + last->global_last->global_next = last->global_next; + last->surface = NULL; + } + + /* Check if any idle inhibitors are still active. */ + DetectSurfaceIdleInhibit (); +} + +static void +InitIdleInhibitData (IdleInhibitDataRecord *record) +{ + if (record->inhibitors.next) + /* The data is already initialized. */ + return; + + record->inhibitors.next = &record->inhibitors; + record->inhibitors.last = &record->inhibitors; +} + +static void +CreateInhibitor (struct wl_client *client, struct wl_resource *resource, + uint32_t id, struct wl_resource *surface_resource) +{ + Surface *surface; + IdleInhibitor *inhibitor; + IdleInhibitDataRecord *record; + + inhibitor = XLSafeMalloc (sizeof *inhibitor); + + if (!inhibitor) + { + wl_resource_post_no_memory (resource); + return; + } + + memset (inhibitor, 0, sizeof *inhibitor); + inhibitor->resource + = wl_resource_create (client, &zwp_idle_inhibitor_v1_interface, + wl_resource_get_version (resource), id); + + surface = wl_resource_get_user_data (surface_resource); + record = XLSurfaceGetClientData (surface, IdleInhibitData, + sizeof *record, FreeIdleInhibitData); + InitIdleInhibitData (record); + + /* Set the inhibitor's surface. */ + inhibitor->surface = surface; + + /* And link it onto the list of all idle inhibitors on both the + surface and globally. */ + inhibitor->next = record->inhibitors.next; + inhibitor->last = &record->inhibitors; + record->inhibitors.next->last = inhibitor; + record->inhibitors.next = inhibitor; + inhibitor->global_next = all_inhibitors.global_next; + inhibitor->global_last = &all_inhibitors; + all_inhibitors.global_next->global_last = inhibitor; + all_inhibitors.global_next = inhibitor; + + if (surface->num_focused_seats) + /* See the comment at the beginning of the file. */ + ChangeInhibitionTo (IdleInhibited); + + /* Set the implementation. */ + wl_resource_set_implementation (inhibitor->resource, &idle_inhibitor_impl, + inhibitor, HandleResourceDestroy); +} + +static struct zwp_idle_inhibit_manager_v1_interface idle_inhibit_manager_impl = + { + .create_inhibitor = CreateInhibitor, + }; + +static void +HandleBind (struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct wl_resource *resource; + + resource = wl_resource_create (client, + &zwp_idle_inhibit_manager_v1_interface, + version, id); + + if (!resource) + { + wl_client_post_no_memory (client); + return; + } + + wl_resource_set_implementation (resource, &idle_inhibit_manager_impl, + NULL, NULL); +} + +static char ** +ReadCommandResource (const char *name, const char *class) +{ + XrmDatabase rdb; + XrmName namelist[3]; + XrmClass classlist[3]; + XrmValue value; + XrmRepresentation type; + char **arguments; + size_t num_args; + + rdb = XrmGetDatabase (compositor.display); + + if (!rdb) + return NULL; + + namelist[1] = XrmStringToQuark (name); + namelist[0] = app_quark; + namelist[2] = NULLQUARK; + + classlist[1] = XrmStringToQuark (class); + classlist[0] = resource_quark; + classlist[2] = NULLQUARK; + + if (XrmQGetResource (rdb, namelist, classlist, + &type, &value) + && type == QString) + { + ParseProcessString ((const char *) value.addr, + &arguments, &num_args); + + return arguments; + } + + return NULL; +} + +static int +ReadIntegerResource (const char *name, const char *class, + int default_value) +{ + XrmDatabase rdb; + XrmName namelist[3]; + XrmClass classlist[3]; + XrmValue value; + XrmRepresentation type; + int result; + + rdb = XrmGetDatabase (compositor.display); + + if (!rdb) + return default_value; + + namelist[1] = XrmStringToQuark (name); + namelist[0] = app_quark; + namelist[2] = NULLQUARK; + + classlist[1] = XrmStringToQuark (class); + classlist[0] = resource_quark; + classlist[2] = NULLQUARK; + + if (XrmQGetResource (rdb, namelist, classlist, + &type, &value) + && type == QString) + { + result = atoi ((char *) value.addr); + + if (!result) + return default_value; + + return result; + } + + return default_value; +} + +void +XLInitIdleInhibit (void) +{ + idle_inhibit_manager_global + = wl_global_create (compositor.wl_display, + &zwp_idle_inhibit_manager_v1_interface, + 1, NULL, HandleBind); + + all_inhibitors.global_next = &all_inhibitors; + all_inhibitors.global_last = &all_inhibitors; + + /* Read various commands from resources. */ + inhibit_command = ReadCommandResource ("idleInhibitCommand", + "IdleInhibitCommand"); + timer_command = ReadCommandResource ("idleIntervalCommand", + "IdleInhibitCommand"); + deinhibit_command = ReadCommandResource ("idleDeinhibitCommand", + "IdleDeinhibitCommand"); + + /* Initialize the default value for timer_seconds. */ + timer_seconds = ReadIntegerResource ("idleCommandInterval", + "IdleCommandInterval", + 60); + + /* Initialize the process queue. */ + process_queue = MakeProcessQueue (); +} + +void +XLIdleInhibitNoticeSurfaceFocused (Surface *surface) +{ + NoticeSurfaceFocused (surface); +} + +void +XLDetectSurfaceIdleInhibit (void) +{ + DetectSurfaceIdleInhibit (); +} diff --git a/process.c b/process.c new file mode 100644 index 0000000..bb25442 --- /dev/null +++ b/process.c @@ -0,0 +1,413 @@ +/* Wayland compositor running on top of an X server. + +Copyright (C) 2022 to various contributors. + +This file is part of 12to11. + +12to11 is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +12to11 is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with 12to11. If not, see . */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "compositor.h" + +typedef struct _ProcessDescription ProcessDescription; + +/* Subprocess control and management. This module implements a + "process queue", which is an ordered list of commands to run. */ + +struct _ProcessDescription +{ + /* The next and last descriptions in the queue. */ + ProcessDescription *next, *last; + + /* NULL-terminated array of arguments. */ + char **arguments; + + /* Size of the argument list excluding the terminating NULL. */ + size_t num_arguments; +}; + +struct _ProcessQueue +{ + /* The next process queue in the chain. */ + ProcessQueue *next; + + /* List of commands that have not yet been run. */ + ProcessDescription descriptions; + + /* The process currently being run. SIGCHLD must be blocked while + reading from this field. */ + pid_t process; +}; + +/* Whether or not the process queue SIGCHLD handler has been + installed. */ +static Bool child_handler_installed; + +/* List of all process queues. */ +static ProcessQueue *all_queues; + +/* Whether or not child processes should be checked. */ +static volatile sig_atomic_t check_child_processes; + +static void +HandleChild (int signal, siginfo_t *siginfo, void *ucontext) +{ + ProcessQueue *considering; + int temp_errno, status; + pid_t pid; + + /* SIGCHILD should now be blocked here. This function cannot call + malloc or any other async-signal unsafe functions. That includes + free and posix_spawn, so the queue is drained in ProcessPoll. */ + + /* Reap the process(es) that exited. */ + + do + { + temp_errno = errno; + pid = TEMP_FAILURE_RETRY (waitpid (-1, &status, WNOHANG)); + errno = temp_errno; + + if (pid == (pid_t) -1) + break; + + considering = all_queues; + for (; considering; considering = considering->next) + { + if (considering->process == pid) + { + /* This process has finished. Set considering->process to + (pid_t) -1. The next queued process will then be run + later. */ + considering->process = (pid_t) -1; + + return; + } + } + } + while (pid != -1); +} + +static void +MaybeInstallChildHandler (void) +{ + struct sigaction act; + + /* Install the SIGCHLD handler used to drive the process queues if + it has not already been installed. */ + + if (child_handler_installed) + return; + + child_handler_installed = True; + memset (&act, 0, sizeof act); + + act.sa_flags = SA_SIGINFO; + act.sa_sigaction = HandleChild; + + if (sigaction (SIGCHLD, &act, NULL)) + { + perror ("sigaction"); + abort (); + } +} + +static void +Block (sigset_t *oldset) +{ + sigset_t sigset; + + sigemptyset (&sigset); + sigaddset (&sigset, SIGCHLD); + + if (sigprocmask (SIG_BLOCK, &sigset, oldset)) + { + perror ("sigprocmask"); + abort (); + } +} + +static void +Unblock (void) +{ + sigset_t sigset; + + sigemptyset (&sigset); + sigaddset (&sigset, SIGCHLD); + + if (sigprocmask (SIG_UNBLOCK, &sigset, NULL)) + { + perror ("sigprocmask"); + abort (); + } +} + +static void +RunNext (ProcessQueue *queue) +{ + ProcessDescription *description, *last; + int rc; + pid_t pid; + + description = queue->descriptions.last; + while (description != &queue->descriptions) + { + last = description; + description = description->last; + + rc = posix_spawnp (&pid, last->arguments[0], NULL, NULL, + last->arguments, environ); + + /* Unlink the description. */ + last->next->last = last->last; + last->last->next = last->next; + XLFree (last); + + if (!rc) + { + /* The child has been spawned. Set queue->process and + return. */ + queue->process = pid; + return; + } + else + /* Print an error and continue. */ + fprintf (stderr, "Subprocess creation failed: %s\n", + strerror (errno)); + } +} + +static void +ProcessPendingDescriptions (void) +{ + ProcessQueue *queue; + + Block (NULL); + + for (queue = all_queues; queue; queue = queue->next) + { + if (queue->process == (pid_t) -1) + RunNext (queue); + } + + Unblock (); +} + +static void +ProcessEscapes (char *string) +{ + int i, j; + Bool escaped; + + /* Naively process escapes in STRING. */ + escaped = False; + i = 0; + j = 0; + + while (string[j] != '\0') + { + if (escaped) + { + string[i++] = string[j]; + escaped = False; + } + else if (*string == '\\') + escaped = True; + else + string[i++] = string[j]; + + j++; + } + + string[i] = '\0'; +} + +void +ParseProcessString (const char *string, char ***arguments_return, + size_t *arg_count) +{ + char **arguments; + const char *start; + Bool escaped, quoted, non_whitespace_seen; + size_t nargs; + + /* This is the NULL termination. */ + arguments = XLCalloc (1, sizeof *arguments); + nargs = 0; + start = string; + escaped = False; + quoted = False; + non_whitespace_seen = False; + +#define AppendArg() \ + do \ + { \ + arguments \ + = XLRealloc (arguments, \ + sizeof *arguments * (++nargs + 1)); \ + arguments[nargs - 1] = XLMalloc (string - start + 1); \ + memcpy (arguments[nargs - 1], start, string - start); \ + arguments[nargs - 1][string - start] = '\0'; \ + ProcessEscapes (arguments[nargs - 1]); \ + } \ + while (0) + + while (*string != '\0') + { + if (!escaped) + { + if (*string == '\\') + escaped = True; + + if (*string == '"') + { + if (!quoted) + { + quoted = True; + + if (non_whitespace_seen) + AppendArg (); + + start = string + 1; + non_whitespace_seen = False; + } + else + { + quoted = False; + + /* Append the argument now. */ + AppendArg (); + + /* Set start to the character after string. */ + start = string + 1; + non_whitespace_seen = False; + } + } + else if (!quoted) + { + if (*string == ' ') + { + if (non_whitespace_seen) + AppendArg (); + + start = string + 1; + non_whitespace_seen = False; + } + else + non_whitespace_seen = True; + } + } + else + { + escaped = False; + non_whitespace_seen = True; + } + + string++; + + if (*string == '\0' && non_whitespace_seen) + AppendArg (); + } + +#undef AppendArg + + /* NULL-terminate the argument array. */ + arguments[nargs] = NULL; + + if (arg_count) + *arg_count = nargs; + + *arguments_return = arguments; +} + +void +RunProcess (ProcessQueue *queue, char **arguments) +{ + ProcessDescription *desc; + + MaybeInstallChildHandler (); + + if (!arguments[0]) + /* There is no executable, so just return. */ + return; + + /* First, allocate a process description. */ + desc = XLMalloc (sizeof *desc); + + /* Link it onto the queue. */ + desc->next = queue->descriptions.next; + desc->last = &queue->descriptions; + + /* Save the command line. */ + desc->arguments = arguments; + + /* Determine how many arguments there are. Note that the caller + owns the command line. */ + desc->num_arguments = 0; + + while (arguments[desc->num_arguments]) + ++desc->num_arguments; + + /* Finish the link. */ + queue->descriptions.next->last = desc; + queue->descriptions.next = desc; + + /* Process pending process descriptions. */ + ProcessPendingDescriptions (); +} + +ProcessQueue * +MakeProcessQueue (void) +{ + ProcessQueue *queue; + + queue = XLCalloc (1, sizeof *queue); + queue->next = all_queues; + queue->descriptions.next = &queue->descriptions; + queue->descriptions.last = &queue->descriptions; + queue->process = (pid_t) -1; + + Block (NULL); + all_queues = queue; + Unblock (); + + return queue; +} + +int +ProcessPoll (struct pollfd *fds, nfds_t nfds, + struct timespec *timeout) +{ + sigset_t oldset; + int rc; + + /* Block SIGPOLL. If SIGCHLD arrives before, then the process will + be run by ProcessPendingDescriptions. If it arrives after, then + ppoll will be interrupted with EINTR. */ + Block (&oldset); + ProcessPendingDescriptions (); + rc = ppoll (fds, nfds, timeout, &oldset); + Unblock (); + + return rc; +}