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