Check in files for idle inhibition
* idle-inhibit-unstable-v1.xml: * idle_inhibit.c: * process.c: New files.
This commit is contained in:
parent
7d11425454
commit
0b2a069ac5
3 changed files with 930 additions and 0 deletions
83
idle-inhibit-unstable-v1.xml
Normal file
83
idle-inhibit-unstable-v1.xml
Normal 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
434
idle_inhibit.c
Normal 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
413
process.c
Normal 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;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue