With a bit of work, I have have a semi-functional patch for people to try. It currently requires a 2.5.x style input core, though that is an easy fix for everything except the hotplug stuff.
The hotplug stuff is the problem, and my current code just won't do for
it either.
At the moment it is told about hotplug events via a fifo, the problem
with this is simple enough, it as a design breaks the moment you have
more then one X server running at a time.
It has been suggested that perhaps relaying it over the X protocol would
be better, however I am rather reluctant to try to implement a new
extension that will be solely used for linux, especially as it is an
area of the X server that I have never even looked at in any detail.
At this point I need suggestions for a better solution to this problem.
Now, instructions for the patch:
Apply the attached evdev.patch to the X tree, recompile the X server.
As root mkfifo /dev/xevdev_control and make it writable.
Throw the attached input.agent into /etc/hotplug/, if you already have
an input.agent, er, then you should hopefully know enough to understand
what mine is doing. <G>
The needed config section should look something like this:
Section "InputDevice"
Identifier "Mouse1"
Driver "mouse"
Option "Protocol" "evdev"
Option "Dev Name" "A4Tech USB Optical Mouse"
Option "Buttons" "9"
Option "ZAxisMapping" "6 7 8 9"
EndSection
The 'Dev Name' option is the device name as reported by the input core,
you may use * and ? wild cards in the name, there is also a 'Dev Phys'
option which is the phys field reported by the input core, the same
wild cards can be used.
You MUST specify at least one of those options, and you can use both.
Please report any problems to me.
Zephaniah E. Hull.
--
1024D/E65A7801 Zephaniah E. Hull <[EMAIL PROTECTED]>
92ED 94E4 B1E6 3624 226D 5727 4453 008B E65A 7801
CCs of replies from mailing lists are requested.
Here's your cable. We made it fifty feet long, just in case. In case
what, in case tectonic movement makes the serial ports farther apart?
-- Carl Jacobs on ASR.
diff -urN -x '*Makefile*'
build-tree/xc/programs/Xserver/hw/xfree86/os-support/linux/Imakefile
build-tree.tmp/xc/programs/Xserver/hw/xfree86/os-support/linux/Imakefile
--- xc/programs/Xserver/hw/xfree86/os-support/linux/Imakefile 2000-11-16
14:45:03.000000000 -0500
+++ xc/programs/Xserver/hw/xfree86/os-support/linux/Imakefile 2002-12-07
+04:42:49.000000000 -0500
@@ -42,15 +42,16 @@
SRCS = lnx_init.c lnx_video.c lnx_io.c libc_wrapper.c bios_mmap.c \
VTsw_usl.c std_kbdEv.c posix_tty.c $(MOUSESRC) \
lnx_pci.c vidmem.c lnx_apm.c $(JOYSTICK_SRC) $(DRI_SRC) $(RES_SRCS) \
- $(AXP_SRC) lnx_kmod.c lnx_agp.c
+ $(AXP_SRC) lnx_kmod.c lnx_agp.c lnx_evdev.c
OBJS = lnx_init.o lnx_video.o lnx_io.o libc_wrapper.o bios_mmap.o \
VTsw_usl.o std_kbdEv.o posix_tty.o $(MOUSEOBJ) \
lnx_pci.o vidmem.o lnx_apm.o $(JOYSTICK_OBJ) $(DRI_OBJ) $(RES_OBJS) \
- $(AXP_OBJ) lnx_kmod.o lnx_agp.o
+ $(AXP_OBJ) lnx_kmod.o lnx_agp.o lnx_evdev.o
INCLUDES = -I$(XF86COMSRC) -I$(XF86OSSRC) -I. -I$(SERVERSRC)/include \
- -I$(XINCLUDESRC) -I$(EXTINCSRC) -I$(XF86OSSRC)/shared
+ -I$(XINCLUDESRC) -I$(EXTINCSRC) -I$(XF86OSSRC)/shared \
+ -I$(SERVERSRC)/mi
RESDEFINES = -DUSESTDRES
diff -urN -x '*Makefile*'
build-tree/xc/programs/Xserver/hw/xfree86/os-support/linux/lnx_evdev.c
build-tree.tmp/xc/programs/Xserver/hw/xfree86/os-support/linux/lnx_evdev.c
--- xc/programs/Xserver/hw/xfree86/os-support/linux/lnx_evdev.c 1969-12-31
19:00:00.000000000 -0500
+++ xc/programs/Xserver/hw/xfree86/os-support/linux/lnx_evdev.c 2002-12-08
+08:27:20.000000000 -0500
@@ -0,0 +1,233 @@
+/* $XFree86: xc/programs/Xserver/hw/xfree86/os-support/linux/lnx_mouse.c,v 1.1
+1999/05/17 13:17:18 dawes Exp $ */
+
+/*
+ * Copyright 1999 by The XFree86 Project, Inc.
+ */
+
+#include "X.h"
+#include "xf86.h"
+#include "xf86Priv.h"
+#include "xf86_OSlib.h"
+#include "xf86Xinput.h"
+#include "xf86OSmouse.h"
+#include "lnx_evdev.h"
+#include "mipointer.h"
+
+static Bool evdev_alive = FALSE;
+static evdevDriverPtr evdev_drivers = NULL;
+static char *evdev_buf;
+
+static int
+glob_match(const char *pattern, const char *matchp)
+{
+ int i, j = 0, ret = 0;
+ if (!(pattern && matchp))
+ return (strlen(pattern) == strlen(matchp));
+
+ for (i = 0; matchp[i]; i++) {
+ if (pattern[j] == '\\')
+ j++;
+ else if (pattern[j] == '*') {
+ if (pattern[j + 1]) {
+ if (!glob_match(pattern+j+1,matchp+i))
+ return 0;
+ } else
+ return 0;
+ continue;
+ } else if (pattern[j] == '?') {
+ j++;
+ continue;
+ }
+
+ if ((ret = (pattern[j] - matchp[i])))
+ return ret;
+
+ j++;
+ }
+ if (!pattern[j] || ((pattern[j] == '*') && !pattern[j + 1]))
+ return 0;
+ else
+ return 1;
+}
+
+
+int
+evdevGetFDForDriver (evdevDriverPtr driver)
+{
+ char dev[20];
+ char tmp[256] = "";
+ int fd, i;
+
+ if (!driver)
+ return -1;
+
+ for (i = 0; i < 32; i++) {
+ snprintf(dev, sizeof(dev), "/dev/input/event%d", i++);
+ SYSCALL(fd = open (dev, O_RDWR | O_NONBLOCK));
+ if (fd == -1)
+ continue;
+
+#define check(name,get) \
+ if (name) { \
+ ioctl(fd, get, tmp); \
+ if (glob_match(name, tmp)) { \
+ close(fd); \
+ continue; \
+ } \
+ }
+
+ check(driver->name, EVIOCGNAME(sizeof(tmp)));
+ check(driver->phys, EVIOCGPHYS(sizeof(tmp)));
+#undef check
+ return fd;
+ }
+ return -1;
+}
+
+static void
+evdevReadInput(InputInfoPtr pInfo)
+{
+ int n;
+ evdevDriverPtr driver;
+ char *cur, *end;
+ char *id, *value, *name, *phys, *action;
+ Bool up;
+
+ do {
+ SYSCALL(n = read(pInfo->fd, evdev_buf, 1022));
+ evdev_buf[n] = '\n'; /* Just to be extra safe. */
+ evdev_buf[n + 1] = '\0';/* Just to be extra safe. */
+
+ cur = evdev_buf;
+ while (cur[0] && (end = strchr(cur, '\n'))) {
+ name = phys = action = NULL;
+ if (!strncmp("2.0", cur, 3)) { /* New style protocol, good! */
+ for (; (*cur != '\n') && (*cur != '!'); cur++);
+ cur++;
+loop_start:
+ id = cur;
+ for (; (*cur != '\n') && (*cur != '|'); cur++);
+ *cur++ = '\0';
+ value = cur;
+ for (; (*cur != '\n') && (*cur != '!'); cur++);
+ *cur++ = '\0';
+ if (!strcmp(id, "ACTION"))
+ action = value;
+ else if (!strcmp(id, "NAME"))
+ name = value;
+ else if (!strcmp(id, "PHYS"))
+ phys = value;
+ if (*cur != '\n')
+ goto loop_start;
+
+ if (!(action && name && phys)) {
+ xf86Msg(X_ERROR,"%s: Incomplete v2.0 command! -%s-%s-%s-\n",
+ pInfo->name, action, name, phys);
+ } else {
+ if (!strcmp(action, "add"))
+ up = TRUE;
+ else
+ up = FALSE;
+
+ for (driver = evdev_drivers; driver; driver = driver->next) {
+ if (driver->name && glob_match(name, driver->name))
+ continue;
+ if (driver->phys && glob_match(phys, driver->phys))
+ continue;
+ if (up)
+ driver->callback(driver->cb_data, DEVICE_ON);
+ else
+ driver->callback(driver->cb_data, DEVICE_OFF);
+ }
+ }
+ }
+ cur = end + 1;
+ }
+ } while (xf86WaitForInput(pInfo->fd, 0));
+ return;
+}
+
+static void
+evdevSigioReadInput (int fd, void *closure)
+{
+ evdevReadInput ((InputInfoPtr) closure);
+}
+
+static int
+evdevControl(DeviceIntPtr pPointer, int what)
+{
+ InputInfoPtr pInfo;
+
+ pInfo = pPointer->public.devicePrivate;
+
+ switch (what) {
+ case DEVICE_INIT:
+ pPointer->public.on = FALSE;
+ break;
+
+ case DEVICE_ON:
+ SYSCALL(pInfo->fd = open ("/dev/xevdev_control",O_RDWR | O_NONBLOCK | O_EXCL));
+ if (pInfo->fd == -1) {
+ xf86Msg(X_ERROR, "%s: cannot open control FIFO.\n", pInfo->name);
+ return BadRequest;
+ }
+ xf86FlushInput(pInfo->fd);
+ if (!xf86InstallSIGIOHandler (pInfo->fd, evdevSigioReadInput, pInfo))
+ AddEnabledDevice(pInfo->fd);
+ pPointer->public.on = TRUE;
+ break;
+
+ case DEVICE_OFF:
+ case DEVICE_CLOSE:
+ if (pInfo->fd != -1) {
+ RemoveEnabledDevice(pInfo->fd);
+ xf86RemoveSIGIOHandler(pInfo->fd);
+ close (pInfo->fd);
+ pInfo->fd = -1;
+ }
+ pPointer->public.on = FALSE;
+ usleep(300000);
+ break;
+ }
+ return Success;
+}
+
+Bool
+evdevStart (InputDriverPtr drv)
+{
+ InputInfoPtr pInfo;
+
+ if (evdev_alive)
+ return TRUE;
+
+ if (!(evdev_buf = xcalloc(1024, 1)))
+ return FALSE;
+
+ if (!(pInfo = xf86AllocateInput(drv, 0)))
+ return FALSE;
+
+
+ pInfo->name = "evdev brain";
+ pInfo->type_name = "evdev brain";
+ pInfo->device_control = evdevControl;
+ pInfo->read_input = evdevReadInput;
+ pInfo->fd = -1;
+ evdev_alive = TRUE;
+ pInfo->flags = XI86_CONFIGURED | XI86_OPEN_ON_INIT;
+ return TRUE;
+}
+
+Bool
+evdevNewDriver (evdevDriverPtr driver)
+{
+ if (!evdev_alive)
+ return FALSE;
+ if (!(driver->name || driver->phys))
+ return FALSE;
+ if (!driver->callback)
+ return FALSE;
+
+ driver->next = evdev_drivers;
+ evdev_drivers = driver;
+ return TRUE;
+}
diff -urN -x '*Makefile*'
build-tree/xc/programs/Xserver/hw/xfree86/os-support/linux/lnx_evdev.h
build-tree.tmp/xc/programs/Xserver/hw/xfree86/os-support/linux/lnx_evdev.h
--- xc/programs/Xserver/hw/xfree86/os-support/linux/lnx_evdev.h 1969-12-31
19:00:00.000000000 -0500
+++ xc/programs/Xserver/hw/xfree86/os-support/linux/lnx_evdev.h 2002-12-08
+09:22:48.000000000 -0500
@@ -0,0 +1,42 @@
+/* $XFree86: xc/programs/Xserver/hw/xfree86/os-support/linux/lnx.h,v 3.2 2000/02/15
+02:00:14 eich Exp $ */
+
+#ifndef LNX_EVDEV_H_
+
+#include <linux/input.h>
+
+#define BITS_PER_LONG (sizeof(long) * 8)
+#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
+#define OFF(x) ((x)%BITS_PER_LONG)
+#define LONG(x) ((x)/BITS_PER_LONG)
+#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1)
+
+#ifndef EVIOCGPHYS
+#define EVIOCGPHYS(len) _IOC(_IOC_READ, 'E', 0x07, len) /* get physical
+location */
+#endif
+
+/*
+ * Synchronization events.
+ */
+#ifndef EV_SYN
+#define EV_SYN 0x00
+
+#define SYN_REPORT 0
+#define SYN_CONFIG 1
+#endif
+
+
+typedef struct _evdevDriver {
+ const char *name;
+ const char *phys;
+ void *cb_data;
+ void (*callback)(void *cb_data, int what);
+ struct _evdevDriver *next;
+} evdevDriver, *evdevDriverPtr;
+
+int evdevGetFDForDriver (evdevDriverPtr driver);
+Bool evdevStart (InputDriverPtr drv);
+Bool evdevNewDriver (evdevDriverPtr driver);
+
+#define LNX_EVDEV_H_
+
+#endif
diff -urN -x '*Makefile*'
build-tree/xc/programs/Xserver/hw/xfree86/os-support/linux/lnx_mouse.c
build-tree.tmp/xc/programs/Xserver/hw/xfree86/os-support/linux/lnx_mouse.c
--- xc/programs/Xserver/hw/xfree86/os-support/linux/lnx_mouse.c 1999-05-17
09:17:18.000000000 -0400
+++ xc/programs/Xserver/hw/xfree86/os-support/linux/lnx_mouse.c 2002-12-08
+09:23:00.000000000 -0500
@@ -6,8 +6,18 @@
#include "X.h"
#include "xf86.h"
+#include "xf86Priv.h"
+#include "xf86_OSlib.h"
#include "xf86Xinput.h"
#include "xf86OSmouse.h"
+#include "mipointer.h"
+#include "lnx_evdev.h"
+
+/* Names of protocols that are handled internally here. */
+static const char *internalNames[] = {
+ "evdev",
+ NULL
+};
static int
SupportedInterfaces(void)
@@ -15,6 +25,259 @@
return MSE_SERIAL | MSE_BUS | MSE_PS2 | MSE_XPS2 | MSE_AUTO;
}
+static const char **
+BuiltinNames(void)
+{
+ return internalNames;
+}
+
+static Bool
+CheckProtocol(const char *protocol)
+{
+ int i;
+
+ for (i = 0; internalNames[i]; i++)
+ if (xf86NameCmp(protocol, internalNames[i]) == 0)
+ return TRUE;
+ return FALSE;
+}
+
+typedef struct _evdevMseRec {
+ int packetSize;
+ int buttons;
+ evdevDriver evdev;
+} evdevMseRec, *evdevMsePtr;
+
+static void
+evdevMouseReadInput(InputInfoPtr pInfo)
+{
+ MouseDevPtr pMse;
+ evdevMsePtr evdevMse;
+ struct input_event *ev;
+ int n, bit;
+ int dx = 0, dy = 0, dz = 0, dw = 0;
+
+ pMse = pInfo->private;
+ ev = (struct input_event *) pMse->buffer;
+ evdevMse = pMse->mousePriv;
+
+ if (pInfo->fd == -1)
+ return;
+
+ do {
+ n = read(pInfo->fd, pMse->buffer, sizeof(struct input_event));
+ if (n == -1) {
+ xf86Msg(X_ERROR, "%s: Error in reading! (%s) Disabiling.\n",
+ pInfo->name, strerror(errno));
+ RemoveEnabledDevice(pInfo->fd);
+ xf86RemoveSIGIOHandler(pInfo->fd);
+ close (pInfo->fd);
+ pMse->device->public.on = FALSE;
+ pInfo->fd = -1;
+ pMse->PostEvent(pInfo, evdevMse->buttons, dx, dy, dz, dw);
+ return;
+ }
+ if (n != sizeof(struct input_event)) {
+ xf86Msg(X_WARNING, "%s: incomplete packet, size %d\n", pInfo->name, n);
+ pMse->PostEvent(pInfo, evdevMse->buttons, dx, dy, dz, dw);
+ return;
+ }
+
+ switch (ev->type) {
+ case EV_REL:
+ switch (ev->code) {
+ case REL_X:
+ dx += ev->value;
+ break;
+ case REL_Y:
+ dy += ev->value;
+ break;
+ case REL_Z:
+ case REL_WHEEL:
+ dz -= ev->value;
+ break;
+ case REL_HWHEEL:
+ dw -= ev->value;
+ break;
+ }
+ break;
+ case EV_KEY:
+ if ((ev->code < BTN_MOUSE) || (ev->code >= BTN_JOYSTICK))
+ break;
+ switch (ev->code) {
+ case BTN_RIGHT: bit = 1 << 0; break; /* 1 */
+ case BTN_MIDDLE: bit = 1 << 1; break; /* 2 */
+ case BTN_LEFT: bit = 1 << 2; break; /* 3 */
+ default: bit = 1 << (ev->code - BTN_MOUSE); break;
+ }
+ evdevMse->buttons &= ~bit;
+ if (ev->value)
+ evdevMse->buttons |= bit;
+ break;
+ case EV_SYN:
+ switch (ev->code) {
+ case SYN_REPORT:
+ pMse->PostEvent(pInfo,evdevMse->buttons,dx, dy, dz, dw);
+ dx = dy = dz = dw = 0;
+ break;
+ }
+ break;
+ }
+ } while (xf86WaitForInput(pInfo->fd, 0));
+
+ pMse->PostEvent(pInfo, evdevMse->buttons, dx, dy, dz, dw);
+
+ return;
+}
+
+static void
+evdevMouseSigioReadInput (int fd, void *closure)
+{
+ evdevMouseReadInput ((InputInfoPtr) closure);
+}
+
+static int
+evdevMouseProc(DeviceIntPtr pPointer, int what)
+{
+ InputInfoPtr pInfo;
+ MouseDevPtr pMse;
+ evdevMsePtr evdevMse;
+ unsigned char map[MSE_MAXBUTTONS + 1];
+ int i, blocked;
+
+ pInfo = pPointer->public.devicePrivate;
+ pMse = pInfo->private;
+ pMse->device = pPointer;
+ evdevMse = pMse->mousePriv;
+
+ switch (what) {
+ case DEVICE_INIT:
+ pPointer->public.on = FALSE;
+
+ evdevMse->evdev.name = xf86SetStrOption(pInfo->options,"Dev Name",NULL);
+ evdevMse->evdev.phys = xf86SetStrOption(pInfo->options,"Dev Phys",NULL);
+ evdevMse->evdev.cb_data = pInfo->dev;
+ evdevMse->evdev.callback = evdevMouseProc;
+ if (!evdevNewDriver (&evdevMse->evdev)) {
+ xf86Msg(X_ERROR, "%s: cannot register with evdev brain\n", pInfo->name);
+ return BadRequest;
+ }
+
+ for (i = 0; i < MSE_MAXBUTTONS; ++i)
+ map[i + 1] = i + 1;
+
+ InitPointerDeviceStruct((DevicePtr)pPointer, map,
+ min(pMse->buttons, MSE_MAXBUTTONS),
+ miPointerGetMotionEvents, pMse->Ctrl,
+ miPointerGetMotionBufferSize());
+
+ /* X valuator */
+ xf86InitValuatorAxisStruct(pPointer, 0, 0, -1, 1, 0, 1);
+ xf86InitValuatorDefaults(pPointer, 0);
+ /* Y valuator */
+ xf86InitValuatorAxisStruct(pPointer, 1, 0, -1, 1, 0, 1);
+ xf86InitValuatorDefaults(pPointer, 1);
+ xf86MotionHistoryAllocate(pInfo);
+ break;
+
+ case DEVICE_ON:
+ if (pPointer->public.on)
+ break;
+ if ((pInfo->fd = evdevGetFDForDriver (&evdevMse->evdev)) == -1) {
+ xf86Msg(X_ERROR, "%s: cannot open input device\n", pInfo->name);
+ return BadRequest;
+ }
+
+ xf86FlushInput(pInfo->fd);
+ if (!xf86InstallSIGIOHandler (pInfo->fd, evdevMouseSigioReadInput, pInfo))
+ AddEnabledDevice(pInfo->fd);
+ pMse->lastButtons = 0;
+ pMse->emulateState = 0;
+ evdevMse->buttons = 0;
+ pPointer->public.on = TRUE;
+ /*
+ * send button up events for sanity. If no button down is pending
+ * xf86PostButtonEvent() will discard them. So we are on the safe side.
+ */
+ blocked = xf86BlockSIGIO ();
+ for (i = 1; i <= 5; i++)
+ xf86PostButtonEvent(pPointer,0,i,0,0,0);
+ xf86UnblockSIGIO (blocked);
+ break;
+
+ case DEVICE_OFF:
+ case DEVICE_CLOSE:
+ if (pInfo->fd != -1) {
+ RemoveEnabledDevice(pInfo->fd);
+ xf86RemoveSIGIOHandler(pInfo->fd);
+ close (pInfo->fd);
+ pInfo->fd = -1;
+ }
+ pPointer->public.on = FALSE;
+ usleep(300000);
+ break;
+ }
+ return Success;
+}
+
+
+/* This function is called when the protocol is "evdev". */
+static Bool
+evdevMousePreInit(InputInfoPtr pInfo, const char *protocol, int flags)
+{
+ unsigned long evtype_bits[NBITS(KEY_MAX)];
+ unsigned long evkey_bits[NBITS(KEY_MAX)];
+ MouseDevPtr pMse = pInfo->private;
+ evdevMsePtr evdevMse;
+ int i, j;
+
+ pMse->protocol = protocol;
+ xf86Msg(X_CONFIG, "%s: Protocol: %s\n", pInfo->name, protocol);
+
+ /* Collect the options, and process the common options. */
+ xf86CollectInputOptions(pInfo, NULL, NULL);
+ xf86ProcessCommonOptions(pInfo, pInfo->options);
+
+ ioctl(pInfo->fd, EVIOCGBIT(0, EV_MAX), evtype_bits);
+ if (test_bit(EV_KEY, evtype_bits)) {
+ ioctl(pInfo->fd, EVIOCGBIT(EV_KEY, EV_MAX), evkey_bits);
+ i = BTN_LEFT;
+ pMse->buttons = 0;
+ for (i = BTN_LEFT, j = 0; i <= BTN_BACK; i++)
+ if (test_bit(i, evkey_bits))
+ pMse->buttons++;
+ }
+
+ close(pInfo->fd);
+ pInfo->fd = -1;
+
+ if (sizeof(struct input_event) <= sizeof(pMse->protoBuf))
+ pMse->buffer = pMse->protoBuf;
+ else
+ pMse->buffer = xcalloc(sizeof(struct input_event),1);
+ pMse->mousePriv = evdevMse = xcalloc(sizeof(evdevMseRec), 1);
+ if ((pMse->buffer == NULL) || (pMse->mousePriv == NULL)) {
+ xf86Msg(X_ERROR, "%s: cannot allocate buffer\n", pInfo->name);
+ xfree(pMse);
+ return FALSE;
+ }
+
+ if (!evdevStart (pInfo->drv)) {
+ xf86Msg(X_ERROR, "%s: cannot start evdev brain\n", pInfo->name);
+ return FALSE;
+ }
+
+ pMse->CommonOptions(pInfo);
+
+ /* Setup the local procs. */
+ pInfo->device_control = evdevMouseProc;
+ pInfo->read_input = evdevMouseReadInput;
+
+ pInfo->flags |= XI86_CONFIGURED;
+
+ return TRUE;
+}
+
OSMouseInfoPtr
xf86OSMouseInit(int flags)
{
@@ -24,6 +287,8 @@
if (!p)
return NULL;
p->SupportedInterfaces = SupportedInterfaces;
+ p->CheckProtocol = CheckProtocol;
+ p->BuiltinNames = BuiltinNames;
+ p->PreInit = evdevMousePreInit;
return p;
}
-
#!/bin/bash
cd /etc/hotplug
. hotplug.functions
#DEBUG=yes export DEBUG
ARG_SEP='!'
VAL_SEP='|'
if [ -e /dev/xevdev_control ]; then {
echo
"2.0${ARG_SEP}NAME${VAL_SEP}${NAME}${ARG_SEP}PHYS${VAL_SEP}${PHYS}${ARG_SEP}ACTION${VAL_SEP}${ACTION}"
>> /dev/xevdev_control;
} fi
msg11473/pgp00000.pgp
Description: PGP signature
