Commit f2faf0ec authored by Andrej Shadura's avatar Andrej Shadura

Import Upstream version 0.2

parents
repo: ba12bed620ca5d736e46a971aa8ee1591e9ce128
node: 44c45966bf03b901197b15c04e164be9afaaf0fb
branch: default
tag: 0.2
b0ed7b912cdf78224fec234d051280fad7228b35 0.1
CFLAGS = -Wall -Wextra -pedantic -std=c99
LDLIBS = $(shell pkg-config --libs x11 xext xi)
all: inputplug inputplug.1
inputplug: inputplug.o
inputplug.1: inputplug.pod
pod2man -r "" -c "" -n $(shell echo $(@:%.1=%) | tr a-z A-Z) $< > $@
inputplug.md: inputplug.pod
pod2markdown < $< | sed -e 's, - , — ,g' \
-e 's,^- ,* ,g' \
-e 's,man.he.net/man./,manpages.debian.org/cgi-bin/man.cgi?query=,g' \
-e 's,\[\(<.*@.*>\)\](.*),\1,' > $@
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 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 project is best built with mk-configure(7).
# To build the project, type `mkcmake`.
MKC_REQD = 0.23.0
PROG = inputplug
MAN = inputplug.1
CLEANFILES = ${MAN}
PKG_CONFIG_DEPS = x11 xext xi
MKC_CHECK_HEADERS = ixp.h
MKC_CHECK_FUNCLIBS = ixp_mount:ixp
WARNS = 2
VERSION = 0.1
.SUFFIXES: .md .pod
.pod.md:
pod2markdown < $< | sed -e 's, - , — ,g' \
-e 's,^- ,* ,g' \
-e 's,man.he.net/man./,manpages.debian.org/cgi-bin/man.cgi?query=,g' \
-e 's,\[\(<.*@.*>\)\](.*),\1,' > $@
.include <mkc.mk>
POD2MAN_FLAGS += -d 2013-12-07
inputplug
=========
inputplug is a very simple daemon monitoring XInput events and running
scripts on hierarchy changes (such as device attach, remove, enable or
disable).
To build the project, it's best to use [mk-configure(7)](http://github.com/cheusov/mk-configure),
a build system based on [bmake(1)](http://www.crufty.net/help/sjg/bmake.html). However, GNU Makefile
is provided for convenience.
* * *
# NAME
inputplug — XInput event monitor
# SYNOPSIS
__inputplug__ \[__\-v__\] \[__\-n__\] __\-c__ _command-prefix_
# DESCRIPTION
__inputplug__ is a daemon which connects to a running X server
and monitors its XInput hierarchy change events. Such events arrive
when a device being attached or removed, enabled or disabled etc.
When a hierarchy change happens, __inputplug__ parses the event notification
structure, and calls the command specified by _command-prefix_. The command
receives three arguments:
* _command-prefix_ _event-type_ _device-id_ _device-type_
Event type may be one of the following:
* _XIMasterAdded_
* _XIMasterRemoved_
* _XISlaveAdded_
* _XISlaveRemoved_
* _XISlaveAttached_
* _XISlaveDetached_
* _XIDeviceEnabled_
* _XIDeviceDisabled_
Device type may be any of those:
* _XIMasterPointer_
* _XIMasterKeyboard_
* _XISlavePointer_
* _XISlaveKeyboard_
* _XIFloatingSlave_
Device identifier is an integer.
# OPTIONS
A summary of options is included below.
* __\-v__
Be a bit more verbose.
* __\-n__
Start up, monitor events, but don't actually run anything.
With verbose more enabled, would print the actual command it'd
run. Currently useless, as __inputplug__ detaches from the terminal
immediately after start.
* __\-c__ _command-prefix_
Command prefix to run. Unfortunately, currently this is passed to
[execvp(3)](http://man.he.net/man3/execvp) directly, so spaces aren't allowed. This is subject to
change in future.
# ENVIRONMENT
* _DISPLAY_
X11 display to connect to.
# BUGS
Probably, there are some.
# SEE ALSO
[xinput(1)](http://manpages.debian.org/cgi-bin/man.cgi?query=xinput)
# COPYRIGHT
Copyright (C) 2013, Andrew Shadura.
Licensed as MIT/X11.
# AUTHOR
Andrew Shadura <andrewsh@debian.org>
#define _BSD_SOURCE
#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XInput.h>
#include <X11/extensions/XInput2.h>
#if HAVE_HEADER_IXP_H
#include <ixp.h>
#endif
struct pair {
int key;
char * value;
};
#define T(x) {x, #x}
#define T_END {0, NULL}
bool verbose = false;
bool no_act = false;
static const struct pair * map(int key, const struct pair * table, bool strict) {
if (!table) return NULL;
while (table->value) {
if ((!strict && (table->key & key)) || (table->key == key)) {
return table;
} else {
table++;
}
}
return NULL;
}
const struct pair device_types[] = {
T(XIMasterPointer),
T(XIMasterKeyboard),
T(XISlavePointer),
T(XISlaveKeyboard),
T(XIFloatingSlave),
T_END
};
const struct pair changes[] = {
T(XIMasterAdded),
T(XIMasterRemoved),
T(XISlaveAdded),
T(XISlaveRemoved),
T(XISlaveAttached),
T(XISlaveDetached),
T(XIDeviceEnabled),
T(XIDeviceDisabled),
T_END
};
#if HAVE_HEADER_IXP_H
static IxpCFid *fid = NULL;
static void postevent(const char *command, char *const args[])
{
assert(command != NULL);
assert(args != NULL);
if (fid != NULL) {
ixp_print(fid, "%s %s %s\n", args[1], args[2], args[3]);
}
}
#endif
static void execute(const char *command, char *const args[])
{
assert(command != NULL);
assert(args != NULL);
if (verbose || no_act) {
int i;
for (i = 0; args[i] != NULL; i++) {
fprintf(stderr, "%s ", args[i]);
}
fputc('\n', stderr);
}
if (!no_act) {
pid_t child;
fflush(NULL);
setpgid(0, 0);
switch (child = fork()) {
case 0: /* child */
execvp(command, args);
exit(127);
default: /* parent */
break;
}
}
}
#define STRINGIFY(x) #x
#define EXPAND_STRINGIFY(x) STRINGIFY(x)
#define UINT_MAX_STRING EXPAND_STRINGIFY(UINT_MAX)
static char * command = NULL;
static void parse_event(XIHierarchyEvent *event)
{
int i;
for (i = 0; i < event->num_info; i++)
{
int flags = event->info[i].flags;
int j = 16;
while (flags && j) {
j--;
const struct pair * use = map(event->info[i].use, device_types, true);
const struct pair * change = map(flags, changes, false);
if (change) {
char deviceid[strlen(UINT_MAX_STRING) + 1];
sprintf(deviceid, "%d", event->info[i].deviceid);
char *const args[] = {
command,
change->value,
deviceid,
use ? use->value : "",
NULL
};
execute(command, args);
#if HAVE_HEADER_IXP_H
postevent(command, args);
#endif
flags -= change->key;
} else {
break;
}
}
}
}
static char * getenv_dup(const char * name) {
const char * value = getenv(name);
if (value != NULL) {
return strdup(value);
} else {
return NULL;
}
}
static pid_t daemonise(void) {
pid_t pid;
switch (pid = fork()) {
case -1: /* failure */
exit(EXIT_FAILURE);
case 0: /* child */
break;
default: /* parent */
return pid;
}
umask(0);
if (setsid() < 0) {
exit(EXIT_FAILURE);
}
if (chdir("/") < 0) {
exit(EXIT_FAILURE);
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
return 0;
}
int main(int argc, char *argv[])
{
XIEventMask mask;
int xi_opcode;
int event, error;
int opt;
#if HAVE_HEADER_IXP_H
IxpClient *client;
#endif
char * address = getenv_dup("WMII_ADDRESS");
char * path = strdup("/event");
while (((opt = getopt(argc, argv, "a:f:vnc:")) != -1) ||
((opt == -1) && (command == NULL))) {
switch (opt) {
case 'v':
verbose = true;
break;
case 'n':
no_act = true;
break;
case 'c':
if (command)
free(command);
command = realpath(optarg, NULL);
break;
#if HAVE_HEADER_IXP_H
case 'a':
if (address)
free(address);
address = strdup(optarg);
break;
case 'f':
if (path)
free(path);
path = strdup(optarg);
break;
#endif
default:
fprintf(stderr, "Usage: %s [-a address] [-f path] [-v] [-n] -c command-prefix\n", argv[0]);
exit(EXIT_FAILURE);
}
}
Display *display;
display = XOpenDisplay(NULL);
if (display == NULL) {
fprintf(stderr, "Can't open X display.\n");
return EXIT_FAILURE;
}
if (!XQueryExtension(display, "XInputExtension", &xi_opcode, &event, &error)) {
printf("X Input extension not available.\n");
goto out;
}
XCloseDisplay(display);
#if HAVE_HEADER_IXP_H
if (address) {
if (*address) {
if (verbose) {
fprintf(stderr, "Connecting to 9P server at %s.\n", address);
}
client = ixp_mount(address);
} else {
if (verbose) {
fprintf(stderr, "Connecting to wmii 9P server.\n");
}
client = ixp_nsmount("wmii");
}
if (client == NULL) {
fprintf(stderr, "Failed to connect to 9P server: %s\n", ixp_errbuf());
free(address);
address = NULL;
} else {
ixp_unmount(client);
}
}
#endif
pid_t pid;
if ((pid = daemonise()) != 0) {
if (verbose) {
fprintf(stderr, "Daemonised as %ju.\n", (uintmax_t)pid);
}
exit(EXIT_SUCCESS);
}
display = XOpenDisplay(NULL);
mask.deviceid = XIAllDevices;
mask.mask_len = XIMaskLen(XI_LASTEVENT);
mask.mask = calloc(mask.mask_len, sizeof(char));
XISetMask(mask.mask, XI_HierarchyChanged);
XISelectEvents(display, DefaultRootWindow(display), &mask, 1);
XSync(display, False);
free(mask.mask);
/* avoid zombies when spawning processes */
struct sigaction sigchld_action = {
.sa_handler = SIG_DFL,
.sa_flags = SA_NOCLDWAIT
};
sigaction(SIGCHLD, &sigchld_action, NULL);
#if HAVE_HEADER_IXP_H
if (address) {
if (*address) {
client = ixp_mount(address);
} else {
client = ixp_nsmount("wmii");
}
if (client != NULL) {
fid = ixp_open(client, path, P9_OWRITE);
}
}
#endif
while(1) {
XEvent ev;
XGenericEventCookie *cookie = (XGenericEventCookie*)&ev.xcookie;
XNextEvent(display, (XEvent*)&ev);
if (XGetEventData(display, cookie)) {
if (cookie->type == GenericEvent &&
cookie->extension == xi_opcode &&
cookie->evtype == XI_HierarchyChanged) {
parse_event(cookie->data);
}
XFreeEventData(display, cookie);
}
}
XSync(display, False);
out:
XCloseDisplay(display);
return EXIT_SUCCESS;
}
=head1 NAME
inputplug - XInput event monitor
=head1 SYNOPSIS
B<inputplug> [B<-a> I<address>] [B<-f> I<path>] [B<-v>] [B<-n>] B<-c> I<command-prefix>
=head1 DESCRIPTION
B<inputplug> is a daemon which connects to a running X server
and monitors its XInput hierarchy change events. Such events arrive
when a device being attached or removed, enabled or disabled etc.
When a hierarchy change happens, B<inputplug> parses the event notification
structure, and calls the command specified by I<command-prefix>. The command
receives three arguments:
=over
=item I<command-prefix> I<event-type> I<device-id> I<device-type>
=back
Event type may be one of the following:
=over
=item * I<XIMasterAdded>
=item * I<XIMasterRemoved>
=item * I<XISlaveAdded>
=item * I<XISlaveRemoved>
=item * I<XISlaveAttached>
=item * I<XISlaveDetached>
=item * I<XIDeviceEnabled>
=item * I<XIDeviceDisabled>
=back
Device type may be any of those:
=over
=item * I<XIMasterPointer>
=item * I<XIMasterKeyboard>
=item * I<XISlavePointer>
=item * I<XISlaveKeyboard>
=item * I<XIFloatingSlave>
=back
Device identifier is an integer.
Also, if compiled with B<libixp>, inputplug can post events to the B<wmii> event file.
To enable B<wmii> support, the address of its B<9P> server needs to be specified.
=head1 OPTIONS
A summary of options is included below.
=over
=item B<-v>
Be a bit more verbose.
=item B<-n>
Start up, monitor events, but don't actually run anything.
With verbose more enabled, would print the actual command it'd
run. Currently useless, as B<inputplug> detaches from the terminal
immediately after start.
=item B<-c> I<command-prefix>
Command prefix to run. Unfortunately, currently this is passed to
L<execvp(3)> directly, so spaces aren't allowed. This is subject to
change in future.
=item B<-a> I<address>
The address at which to connect to B<wmii>. The address takes the
form I<< <protocol> >>!I<< <address> >>. If an empty string is passed,
B<inputplug> tries to find B<wmii> automatically.
=item B<-f> I<path>
Path to the event file within B<9P> filesystem served by B<wmii>.
The default is B</event>.
=back
=head1 ENVIRONMENT
=over
=item I<DISPLAY>
X11 display to connect to.
=item I<WMII_ADDRESS>
B<wmii> address.
=back
=head1 BUGS
Probably, there are some.
=head1 SEE ALSO
L<xinput(1)>
=head1 COPYRIGHT
Copyright (C) 2013, Andrew Shadura.
Licensed as MIT/X11.
=head1 AUTHOR
Andrew Shadura L<< <andrewsh@debian.org> >>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment