Check in files for idle inhibition

* idle-inhibit-unstable-v1.xml:
* idle_inhibit.c:
* process.c: New files.
This commit is contained in:
hujianwei 2022-10-22 04:43:14 +00:00
parent 7d11425454
commit 0b2a069ac5
3 changed files with 930 additions and 0 deletions

View file

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="idle_inhibit_unstable_v1">
<copyright>
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.
</copyright>
<interface name="zwp_idle_inhibit_manager_v1" version="1">
<description summary="control behavior when display idles">
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.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the idle inhibitor object">
Destroy the inhibit manager.
</description>
</request>
<request name="create_inhibitor">
<description summary="create a new inhibitor object">
Create a new inhibitor object associated with the given surface.
</description>
<arg name="id" type="new_id" interface="zwp_idle_inhibitor_v1"/>
<arg name="surface" type="object" interface="wl_surface"
summary="the surface that inhibits the idle behavior"/>
</request>
</interface>
<interface name="zwp_idle_inhibitor_v1" version="1">
<description summary="context object for inhibiting idle behavior">
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.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the idle inhibitor object">
Remove the inhibitor effect from the associated wl_surface.
</description>
</request>
</interface>
</protocol>

434
idle_inhibit.c Normal file
View file

@ -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 <https://www.gnu.org/licenses/>. */
#include <string.h>
#include <stdio.h>
#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 ();
}

413
process.c Normal file
View file

@ -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 <https://www.gnu.org/licenses/>. */
#include <sys/resource.h>
#include <sys/errno.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <spawn.h>
#include <stdio.h>
#include <unistd.h>
#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;
}