(linux-only) keyboard driver Medved

[email protected] (Michal Maruška)
Newsgroups gmane.comp.xfree86.devel
Message-ID <[email protected]>
hello fellow _xfree86_ hackers!

This is a second email in a series aimed at getting code into the X server
needed to use a more sophisticated way of typing --- exploiting the time
information:  key semantics changes according to the duration of the key press.

I am providing the code to be distributed under the xfree86 version 1.1 licence.



Linux provides an alternative way to read keyboard input:
/dev/input/eventXXX device, provided by the "evdev" driver/module.

This source provides 2 new features:

1/  every event has a timestamp, which is more precise that calling (inside X
server) GetTimeInMillis 

    Playing with the time of key events is the core point of my "rewriting
    plugin", so i had to make several changes to all parts of X server which
    handle key events.


2/ distinct hardware devices (keyboard) are reported in distinct devices/files
   /dev/input/event0 /dev/input/event1 ...


The driver, called Medved, wraps/represents the set of evdev devices (keyboards)
as 1 X keyboard.  I.e. X clients see 1 _core_ keyboard --- no need to play
with XInput:

The driver is quite unusual, b/c it handles more file descriptors, and therefore
it cannot use a higher level
             xf86AddInputDriver  and xf86Wakeup

but i have to use lower level RegisterBlockAndWakeupHandlers.

and look directly at the select mask to see which of managed files/devices has input ready.



While coding this driver, i realized the cause of several bugs in other
drivers.

I had previously communicated in
     http://www.mail-archive.com/devel%40xfree86.org/msg06818.html 

the conflict between "current state" of the 2 halves of key handling code,
"separated" by xf86EventQueue.

In that message, i talked about "down" bit-array, now i add, that keyc->state is
impossible to use in the lower half.

This (incorrect use of upper half's state in the lower half, which runs ahead in
 time) explain 2 bugs:

1/ XF86Config options:
  Option "AllowDeactivateGrabs" "yes"                 #  C-M kp-/      steal the grab   
  Option "AllowClosedownGrabs" "yes"                  #  C-M kp-*      kill the grabbing client
simply didn't work (in situations i needed them)

situation: 
    an application stopped accepting key events (i.e. holds a synchro grab &
    doesn't call XAllowEvent)  -> therefore keys accumulate in syncEvents
    (dix/events.c) w/o effecting the XKB state

    XKB state is not "meta & control modifiers"


now,  you want to steal the grab, so you press 3 keys Contol Meta kp-/

->  the first 2 events enter the queue, and the keyboard driver
  when processing the 3rd (kp-/) will look at the XKB state which is not
   "meta & control modifiers", hence it will not trigger any special function.

Same applies to C-M-Fn  to switch VTs (during such frozen situation).


2/  C-M-Fn occasionally leaked the Fn key into applications.
  When the Fn was processed before the the upper part picked C M from
  xf86EventQueue and changed the XKB state. This is possible when X server
  reads all 3 key events at once.

Solution is not very flexible, though. I just look if the fixed keys (say Alt_L,
Ctrl_L) are down.
  


(Since i play a lot with the time,) medved does not use HW autorepeat. This will
be explained in the next email.

More hw keyboards are used in the OR mode: a key is down, if and only if it is down
on any of the hw keyboards.

Hotplug is handled by polling, sorry.


I had to pull some functionality from the linux specific part of the "kbd"
driver (lnx_kbd.c) into a shared part.

I enclose these preparative changes as a patch.

The driver itself is at  http://maruska.dyndns.org/comp/packages/medved.tar.gz



If you build xfree86 server with this patch & driver, you should notice that:

- auto-repeat does not work

- X mixes its monotonic time (GetTimeInMillis) with the timestamp coming from
  evdev devices (gettimeofday) ... with various consequences (keyboard grabbing
  stops working, iirc)


I don't bother providing fix for this, because in the next messages i will
provide

-  patch for linux kernel & GetTimeInMillis:
       preversion  http://maruska.dyndns.org/wiki/kernel.html

- and an alternative method of generating auto-repeat events: at last
a reasonable auto-repeat, which i found lacking 2 years ago, when i started
fixing (hacking around) the key-event processing in X server.
medved.patch (text/x-patch, 8.6 KB)
--- /linux/13/x/xfree86/xc/programs/Xserver/hw/xfree86/common/xf86Events.c	2005-05-31 04:46:39.000000000 +0200
+++ hw/xfree86/common/xf86Events.c	2005-06-02 19:14:34.000000000 +0200
@@ -228,6 +228,11 @@
 }
 
 
+/* mmc:  called from Dispatch !!!  (to serve the events collected in signal handlers?)
+ *   but more importantly from WakeupHandler (after drivers have a go on reading from their FDs)!
+ *   But, that means that they will not be in chronological order!  if i have 2 FDs for a keyboard.
+ *   Unless either the driver handles both of them & takes care, or ... we sort them later. (todo)
+ */
 
 /*
  * ProcessInputEvents --
@@ -1643,6 +1648,6 @@
         xf86PostKbdEvent(keycode);
         xf86UnblockSIGIO(blocked);
     }
-  }
+}
 }
 #endif /* WSCONS_SUPPORT */
--- /linux/13/x/xfree86/xc/programs/Xserver/hw/xfree86/common/xf86Init.c	2005-03-07 18:32:40.000000000 +0100
+++ hw/xfree86/common/xf86Init.c	2005-04-10 16:51:21.000000000 +0200
@@ -604,6 +604,7 @@
         xfree(driverlist);
       }
 
+      /* mmc: unconditionally??  */
       /* Setup the builtin input drivers */
       xf86AddInputDriver(&xf86KEYBOARD, NULL, 0);
       /* Load all input driver modules specified in the config file. */
--- /linux/13/x/xfree86/xc/programs/Xserver/hw/xfree86/common/xf86KbdLnx.c	2004-02-14 00:58:37.000000000 +0100
+++ hw/xfree86/common/xf86KbdLnx.c	2005-04-13 00:06:44.000000000 +0200
@@ -126,8 +126,8 @@
    * compute the modifier map
    */
   for (i = 0; i < MAP_LENGTH; i++)
-    pModMap[i] = NoSymbol;  /* make sure it is restored */
-  
+     pModMap[i] = NoSymbol;  /* make sure it is restored */ /*mmc: why not simply 0? NoSymbol is keysym,
+                                                             * but here we set CARD8*/
   for (k = map, i = MIN_KEYCODE;
        i < (NUM_KEYCODES + MIN_KEYCODE);
        i++, k += 4)
--- /linux/13/x/xfree86/xc/programs/Xserver/hw/xfree86/common/xf86Xinput.c	2004-02-14 00:58:39.000000000 +0100
+++ hw/xfree86/common/xf86Xinput.c	2005-06-02 19:11:08.000000000 +0200
@@ -1349,12 +1349,14 @@
     va_end(var);
 }
 
+/* new entry point for drivers which provide the event's timestamp ("medved") */
 void
-xf86PostKeyboardEvent(DeviceIntPtr      device,
+xf86PostKeyboardTimedEvent(DeviceIntPtr      device,
                       unsigned int      key_code,
-                      int               is_down)
+                           int               is_down,
+                           CARD32            time)
 {
-    xEvent                      xE[2];
+    xEvent                      xE[1]; /* why 2 ?? */
     deviceKeyButtonPointer      *xev = (deviceKeyButtonPointer*) xE;
 
     if (xf86IsCoreKeyboard(device)) {
@@ -1363,7 +1365,7 @@
         xev->type = is_down ? DeviceKeyPress : DeviceKeyRelease;
     }
     xev->detail = key_code;
-    xf86Info.lastEventTime = xev->time = GetTimeInMillis();
+    xf86Info.lastEventTime = xev->time = time;
 
 #ifdef XFreeXDGA
     /* if(!DGAStealKeyEvent(xf86EventQueue.pEnqueueScreen->myNum, xE)) */
@@ -1371,6 +1373,15 @@
     ENQUEUE(xE);
 }
 
+/* mmc: called from the "kbd" driver. PostKbdEvent() from the builtin driver instead */
+void
+xf86PostKeyboardEvent(DeviceIntPtr      device,
+                      unsigned int      key_code,
+                      int               is_down)
+{
+   xf86PostKeyboardTimedEvent(device, key_code, is_down, GetTimeInMillis());
+}
+
 /* 
  * Motion history management.
  */
--- /linux/13/x/xfree86/xc/programs/Xserver/hw/xfree86/common/xf86Xinput.h	2004-02-14 00:58:39.000000000 +0100
+++ hw/xfree86/common/xf86Xinput.h	2005-04-10 17:36:43.000000000 +0200
@@ -209,6 +209,10 @@
 		      ...);
 void xf86PostKeyboardEvent(DeviceIntPtr device, unsigned int key_code,
                            int is_down);
+
+void xf86PostKeyboardTimedEvent(DeviceIntPtr device, unsigned int key_code,
+                           int is_down, CARD32 time);
+
 void xf86MotionHistoryAllocate(LocalDevicePtr local);
 int xf86GetMotionEvents(DeviceIntPtr dev, xTimecoord *buff,
 			unsigned long start, unsigned long stop,
--- /linux/13/x/xfree86/xc/programs/Xserver/hw/xfree86/loader/xf86sym.c	2005-02-26 19:31:48.000000000 +0100
+++ hw/xfree86/loader/xf86sym.c	2005-06-02 13:49:50.000000000 +0200
@@ -339,6 +339,15 @@
     SYMFUNC(xf86EnableAGP)
     SYMFUNC(xf86SoundKbdBell)
     SYMFUNC(xf86GARTCloseScreen)
+#ifdef __linux__
+    /* mmc: export some more functions for the keyboard driver. They are all linux specific! */
+    /* fixme: i don't know if linux or __linux__ is correct! */
+    SYMFUNC(put_console_into_raw)
+    SYMFUNC(restore_console)
+    SYMFUNC(select_console)
+    SYMFUNC(console_beep)
+#endif /* __linux__ */
+
 #ifdef XINPUT
     /* XISB routines  (Merged from Metrolink tree) */
     SYMFUNC(XisbNew)
@@ -561,6 +570,8 @@
     SYMFUNC(xf86RemoveEnabledDevice)
     SYMFUNC(xf86InterceptSignals)
     SYMFUNC(xf86EnableVTSwitch)
+    /* (mmc:) keyboard driver might know better than xf86CommonSpecialKey, when to take action */
+    SYMFUNC(xf86ProcessActionEvent)
 
     /* xf86Helper.c */
     SYMFUNC(xf86AddDriver)
@@ -847,6 +858,7 @@
     SYMFUNC(xf86PostButtonEvent)
     SYMFUNC(xf86PostKeyEvent)
     SYMFUNC(xf86PostKeyboardEvent)
+    SYMFUNC(xf86PostKeyboardTimedEvent) /* new entry point */
     SYMFUNC(xf86GetMotionEvents)
     SYMFUNC(xf86MotionHistoryAllocate)
     SYMFUNC(xf86FirstLocalDevice)
--- /linux/13/x/xfree86/xc/programs/Xserver/hw/xfree86/os-support/linux/lnx_kbd.c	2004-11-10 05:28:38.000000000 +0100
+++ hw/xfree86/os-support/linux/lnx_kbd.c	2005-06-02 19:28:34.000000000 +0200
@@ -305,11 +305,6 @@
 #endif /* KBD_DIRECTHW */
 }
 
-typedef struct {
-   int kbdtrans;
-   struct termios kbdtty;
-} LnxKbdPrivRec, *LnxKbdPrivPtr;
-
 static int
 KbdInit(InputInfoPtr pInfo, int what)
 {
@@ -317,7 +312,7 @@
     LnxKbdPrivPtr priv = (LnxKbdPrivPtr) pKbd->private;
 
     if (pKbd->isConsole) {
-        ioctl (pInfo->fd, KDGKBMODE, &(priv->kbdtrans));
+        ioctl (pInfo->fd, KDGKBMODE, &(priv->kbdtrans)); /* gets current keyboard mode */
         tcgetattr (pInfo->fd, &(priv->kbdtty));
     }
     if (!pKbd->CustomKeycodes) {
@@ -338,7 +333,7 @@
 	if (pKbd->CustomKeycodes)
 	    ioctl(pInfo->fd, KDSKBMODE, K_MEDIUMRAW);
 	else
-	    ioctl(pInfo->fd, KDSKBMODE, K_RAW);
+	    ioctl(pInfo->fd, KDSKBMODE, K_RAW); /* mmc: i need this to avoid M-Right changing the VT? */
 
 	nTty = priv->kbdtty;
 	nTty.c_iflag = (IGNPAR | IGNBRK) & (~PARMRK) & (~ISTRIP);
@@ -353,6 +348,48 @@
     }
     return Success;
 }
+/* extracted functionality from  SoundBell (see above)!*/
+void
+console_beep(int loudness, int pitch, int duration)
+{
+   if (loudness && pitch)
+      {
+         ioctl(xf86Info.consoleFd, KDMKTONE,
+               ((1193190 / pitch) & 0xffff) |
+               (((unsigned long)duration *
+                 loudness / 50) << 16));
+      }
+}
+
+
+
+void
+put_console_into_raw(LnxKbdPrivPtr save)
+{
+   int fd = xf86Info.consoleFd;
+   
+   ioctl (fd, KDGKBMODE, &(save->kbdtrans)); /* mmc: gets current keyboard mode */
+   tcgetattr (fd, &(save->kbdtty));
+
+   ioctl(fd, KDSKBMODE, K_RAW);
+}
+
+void
+restore_console(LnxKbdPrivPtr save)
+{
+   int fd = xf86Info.consoleFd;
+
+   ioctl(fd, KDSKBMODE, save->kbdtrans);
+   tcsetattr(fd, TCSANOW, &(save->kbdtty));
+}
+
+
+void
+select_console(int console)
+{
+   int fd = xf86Info.consoleFd;
+   ioctl(fd, VT_ACTIVATE, console);
+}
 
 static int
 KbdOff(InputInfoPtr pInfo, int what)
--- /linux/13/x/xfree86/xc/programs/Xserver/hw/xfree86/os-support/linux/lnx_KbdMap.c	2002-10-11 03:40:35.000000000 +0200
+++ hw/xfree86/os-support/linux/lnx_KbdMap.c	2005-06-02 19:18:01.000000000 +0200
@@ -49,7 +49,7 @@
    * compute the modifier map
    */
   for (i = 0; i < MAP_LENGTH; i++)
-    pModMap[i] = NoSymbol;  /* make sure it is restored */
+    pModMap[i] = NoSymbol;  /* make sure it is restored mmc: i really prefer 0L: this is a bitmask, not a keysym */
   
   for (k = map, i = MIN_KEYCODE;
        i < (NUM_KEYCODES + MIN_KEYCODE);
--- /linux/13/x/xfree86/xc/programs/Xserver/hw/xfree86/os-support/xf86OSKbd.h	2003-11-03 06:11:51.000000000 +0100
+++ hw/xfree86/os-support/xf86OSKbd.h	2005-06-02 19:30:01.000000000 +0200
@@ -114,6 +114,23 @@
 
 Bool xf86OSKbdPreInit(InputInfoPtr pInfo);
 
+
+#ifdef __linux__
+/* mmc:  see my comment in ../loader/xf86sym.c */
+/* taken from lnx_kbd.c  this should be common for medved and kbd(on linux), but for now it's not. */
+
+#include <termios.h>
+typedef struct {
+   int kbdtrans;
+   struct termios kbdtty;
+} LnxKbdPrivRec, *LnxKbdPrivPtr;
+
+extern void put_console_into_raw(LnxKbdPrivPtr);
+extern void restore_console (LnxKbdPrivPtr);
+extern void select_console(int console);
+extern void console_beep(int loudness, int pitch, int duration);
+#endif
+
 /* Adjust this when the kbd interface changes. */
 
 /*
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.