xinput_calibrator icon indicating copy to clipboard operation
xinput_calibrator copied to clipboard

evdev based touch xinput events, screen resolution, calibration

Open j4nn opened this issue 13 years ago • 1 comments

I've tested xinput_calibrator-0.7.5 with touchscreen driven by evdev, but it did not work because of several (design) problems of xinput_calibrator.

  1. the touch produces events with different axis range than is the screen resolution
  2. xinput_calibrator uses click events from xlib or gtk that are in screen coordinates range
  3. evdev calibration final setup changes xinput evdev calibration attributes that expect evdev range, not the screen resolution range
  4. evdev calibration uses wrong defaults if started for second time (unless explicit full range --precalib option is used)

Since I wanted to get xinput_calibrator running in order to use the feature to calibrate without restart of xserver, I had to make following changes/hacks (done only in x11 gui version):

  • register listening for xinput evdev events directly to obtain native evdev click coordinates
  • skip overwriting of old_axys in CalibratorEvdev constructor, use old_axys to setup default calibration parameters using full range of input coordinates
  • apply old_axys (i.e. full range) parameters as evdev calibration unconditionally always in the CalibratorEvdev constructor in order to avoid miss calibration when run for second time on already calibrated touchscreen
  • extend xlib event loop to listen also for xinput events
  • use xinput old_axys (i.e. full range) as target resolution for final calculation of calibration and let the conversion of coordinate ranges to screen to be handled by xserver

Please note, I am not experienced with xlib programming and I did not want to redesign the architecture of xinput_calibrator, so my changes are quite dirty, but they work for me. So I am posting the patch here for you so you may think about it and possibly fix or redesign the xinput_calibrator which I find as great application that was missing for a long time.

First few more information on what sw components were used:

  • linux kernel v2.6.35
  • x11-drivers/xf86-input-evdev-2.4.0
  • x11-base/xorg-server-1.8.2
  • x11-proto/inputproto-2.0
  • x11-apps/xinput-1.5.2
  • x11-proto/randrproto-1.3.1
  • x11-proto/xcalibrateproto-0.1_pre20081210 (this is not used, is it?)
-bash-4.1# evtest /dev/input/event4 
Input driver version is 1.0.0
Input device ID: bus 0x13 vendor 0x80 product 0x0 version 0x1
Input device name: "eGalax EETI Serial TouchScreen"
Supported events:
  Event type 0 (Sync)
  Event type 1 (Key)
    Event code 330 (Touch)
  Event type 3 (Absolute)
    Event code 0 (X)
      Value      0
      Min        0
      Max     2047
    Event code 1 (Y)
      Value      0
      Min        0
      Max     2047
    Event code 24 (Pressure)
      Value      0
      Min        0
      Max      127
Testing ... (interrupt to exit)
-bash-4.1# xinput list --long EETI
EETI                                            id=8    [slave  pointer  (2)]
        Reporting 4 classes:
                Class originated from: 8
                Buttons supported: 5
                Button labels: Button Unknown Button Unknown Button Unknown Button Wheel Up Button Wheel Down
                Button state:
                Class originated from: 8
                Detail for Valuator 0:
                  Label: Abs X
                  Range: 0.000000 - 2047.000000
                  Resolution: 10000 units/m
                  Mode: absolute
                  Current value: 400.000000
                Class originated from: 8
                Detail for Valuator 1:
                  Label: Abs Y
                  Range: 0.000000 - 2047.000000
                  Resolution: 10000 units/m
                  Mode: absolute
                  Current value: 240.000000
                Class originated from: 8
                Detail for Valuator 2:
                  Label: Abs Pressure
                  Range: 0.000000 - 127.000000
                  Resolution: 10000 units/m
                  Mode: absolute
                  Current value: 0.000000
-bash-4.1# xdpyinfo | grep dimensions
  dimensions:    800x480 pixels (212x127 millimeters)

And the dirty patch itself:

diff -urNp xinput_calibrator-0.7.5-orig/src/calibrator/calibratorEvdev.cpp xinput_calibrator-0.7.5/src/calibrator/calibratorEvdev.cpp
--- xinput_calibrator-0.7.5-orig/src/calibrator/calibratorEvdev.cpp 2010-09-12 21:46:10.000000000 +0200
+++ xinput_calibrator-0.7.5/src/calibrator/calibratorEvdev.cpp  2010-12-21 09:33:06.000000000 +0100
@@ -34,6 +34,8 @@
 #define EXIT_FAILURE 0
 #endif

+#define INVALID_EVENT_TYPE      -1
+
 /***************************************
  * Class for dynamic evdev calibration
  * uses xinput "Evdev Axis Calibration"
@@ -46,12 +48,18 @@ private:
     XDevice     *dev;

     int old_swap_xy;
+    int button_press_type;
+    int button_release_type;
+    int motion_type;
+    int motion_a, motion_b;
 public:
     CalibratorEvdev(const char* const device_name, const XYinfo& axys, const bool verbose,
         XID device_id=(XID)-1, const int thr_misclick=0, const int thr_doubleclick=0,
         const OutputType output_type=OUTYPE_AUTO);
     ~CalibratorEvdev();

+    virtual int register_events();
+    virtual int check_press_event(int *x, int *y);
     virtual bool finish_data(const XYinfo new_axys, int swap_xy);

     bool set_swapxy(const int swap_xy);
@@ -67,9 +75,16 @@ protected:
     bool output_xinput(const XYinfo new_axys, int swap_xy, int new_swap_xy);
 };

+
 CalibratorEvdev::CalibratorEvdev(const char* const device_name0, const XYinfo& axys0, const bool verbose0, XID device_id, const int thr_misclick, const int thr_doubleclick, const OutputType output_type)
   : Calibrator(device_name0, axys0, verbose0, thr_misclick, thr_doubleclick, output_type), old_swap_xy(0)
 {
+    XYinfo curr_calib;
+    button_press_type = INVALID_EVENT_TYPE;
+    button_release_type = INVALID_EVENT_TYPE;
+    motion_type = INVALID_EVENT_TYPE;
+    motion_a = motion_b = 0;
+
     // init
     display = XOpenDisplay(NULL);
     if (display == NULL) {
@@ -139,19 +154,37 @@ CalibratorEvdev::CalibratorEvdev(const c
         } else if (nitems > 0) {
             ptr = data;

-            old_axys.x_min = *((long*)ptr);
+            curr_calib.x_min = *((long*)ptr);
             ptr += sizeof(long);
-            old_axys.x_max = *((long*)ptr);
+            curr_calib.x_max = *((long*)ptr);
             ptr += sizeof(long);
-            old_axys.y_min = *((long*)ptr);
+            curr_calib.y_min = *((long*)ptr);
             ptr += sizeof(long);
-            old_axys.y_max = *((long*)ptr);
+            curr_calib.y_max = *((long*)ptr);
             ptr += sizeof(long);
         }

         XFree(data);
     }

+    {
+            if (verbose)
+                printf("DEBUG: setting Evdev Axis Calibration to axis valuators.\n");
+
+            // No axis calibration set, set it to the default one
+            // QUIRK: when my machine resumes from a sleep,
+            // the calibration property is no longer exported thourgh xinput, but still active
+            // not setting the values here would result in a wrong first calibration
+            bool ok = set_calibration(old_axys);
+
+            if (verbose) {
+                if (ok)
+                    printf("DEBUG: Successfully applied axis calibration.\n");
+                else
+                    printf("DEBUG: Failed to apply axis calibration.\n");
+            }
+    }
+
     // get "Evdev Axes Swap" property
     property = xinput_parse_atom(display, "Evdev Axes Swap");
     if (XGetDeviceProperty(display, dev, property, 0, 1000, False,
@@ -169,8 +202,8 @@ CalibratorEvdev::CalibratorEvdev(const c
     // see http://cgit.freedesktop.org/xorg/driver/xf86-input-evdev/commit/?h=evdev-2.3-branch&id=3772676fd65065b43a94234127537ab5030b09f8

     printf("Calibrating EVDEV driver for \"%s\" id=%i\n", device_name, (int)device_id);
-    printf("\tcurrent calibration values (from XInput): min_x=%d, max_x=%d and min_y=%d, max_y=%d\n",
-                old_axys.x_min, old_axys.x_max, old_axys.y_min, old_axys.y_max);
+    printf("\tprevious calibration values (from XInput): min_x=%d, max_x=%d and min_y=%d, max_y=%d\n",
+                curr_calib.x_min, curr_calib.x_max, curr_calib.y_min, curr_calib.y_max);
 #endif // HAVE_XI_PROP

 }
@@ -180,6 +213,72 @@ CalibratorEvdev::~CalibratorEvdev () {
     XCloseDisplay(display);
 }

+
+
+int CalibratorEvdev::register_events()
+{
+    int number = 0;
+    XEventClass         event_list[7];
+    int                 i;
+    Window              root_win;
+    unsigned long       screen;
+    XInputClassInfo     *ip;
+
+    screen = DefaultScreen(display);
+    root_win = RootWindow(display, screen);
+
+    if (dev->num_classes > 0) {
+        printf("register_events: num_classes=%d\n", dev->num_classes);
+        for (ip = dev->classes, i=0; i<dev->num_classes; ip++, i++) {
+            printf("register_events: input_class=%d\n", ip->input_class);
+            switch (ip->input_class) {
+            case ButtonClass:
+                DeviceButtonPress(dev, button_press_type, event_list[number]); number++;
+                DeviceButtonRelease(dev, button_release_type, event_list[number]); number++;
+                break;
+            case ValuatorClass:
+                DeviceMotionNotify(dev, motion_type, event_list[number]); number++;
+                break;
+            default:
+                fprintf(stderr, "unknown class\n");
+                break;
+            }
+        }
+
+        if (XSelectExtensionEvent(display, root_win, event_list, number)) {
+            fprintf(stderr, "error selecting extended events\n");
+            return 0;
+        }
+        printf("register_events: registered %d events with xinput\n", number);
+    }
+    return 1;
+}
+
+int CalibratorEvdev::check_press_event(int *x, int *y)
+{
+        XEvent event;
+
+        if (XCheckTypedEvent(display, motion_type, &event)) {
+                XDeviceMotionEvent *motion = (XDeviceMotionEvent *)&event;
+
+//              printf("motion a=%d b=%d\n",
+//                      motion->axis_data[0], motion->axis_data[1]);
+                motion_a = motion->axis_data[0];
+                motion_b = motion->axis_data[1];
+        }
+        if (XCheckTypedEvent(display, button_release_type, &event)) {
+                XDeviceButtonEvent *button = (XDeviceButtonEvent *)&event;
+
+                printf("button release %d [%d,%d]\n", button->button, motion_a, motion_b);
+                *x = motion_a;
+                *y = motion_b;
+                return 1;
+        }
+        *x = -1;
+        *y = -1;
+        return 0;
+}
+
 bool CalibratorEvdev::finish_data(const XYinfo new_axys, int swap_xy)
 {
     bool success = true;
diff -urNp xinput_calibrator-0.7.5-orig/src/calibrator.cpp xinput_calibrator-0.7.5/src/calibrator.cpp
--- xinput_calibrator-0.7.5-orig/src/calibrator.cpp 2010-09-12 21:46:10.000000000 +0200
+++ xinput_calibrator-0.7.5/src/calibrator.cpp  2010-12-20 12:42:26.000000000 +0100
@@ -124,12 +124,18 @@ inline bool Calibrator::along_axis(int x
             (abs(xy - y0) <= threshold_misclick));
 }

-bool Calibrator::finish(int width, int height)
+bool Calibrator::finish()
 {
+    int width;
+    int height;
+
     if (get_numclicks() != 4) {
         return false;
     }

+    width = abs(old_axys.x_max - old_axys.x_min) + 1;
+    height = abs(old_axys.y_max - old_axys.y_min) + 1;
+
     // Should x and y be swapped?
     const bool swap_xy = (abs (clicked_x [UL] - clicked_x [UR]) < abs (clicked_y [UL] - clicked_y [UR]));
     if (swap_xy) {
diff -urNp xinput_calibrator-0.7.5-orig/src/calibrator.hh xinput_calibrator-0.7.5/src/calibrator.hh
--- xinput_calibrator-0.7.5-orig/src/calibrator.hh  2010-09-12 21:46:10.000000000 +0200
+++ xinput_calibrator-0.7.5/src/calibrator.hh   2010-12-20 12:42:26.000000000 +0100
@@ -50,11 +50,16 @@ public:
     // add a click with the given coordinates
     bool add_click(int x, int y);
     // calculate and apply the calibration
-    bool finish(int width, int height);
+    bool finish();
     // get the sysfs name of the device,
     // returns NULL if it can not be found
     const char* get_sysfs_name();

+    // optionally overload this to listen for events ourselves
+    virtual int register_events() { return 0; }
+    // optionally overload this to process events directly
+    virtual int check_press_event(int *x, int *y) { return 0; }
+
 protected:
     // check whether the coordinates are along the respective axis
     bool along_axis(int xy, int x0, int y0);
diff -urNp xinput_calibrator-0.7.5-orig/src/gui/gui_x11.cpp xinput_calibrator-0.7.5/src/gui/gui_x11.cpp
--- xinput_calibrator-0.7.5-orig/src/gui/gui_x11.cpp    2010-09-12 21:46:10.000000000 +0200
+++ xinput_calibrator-0.7.5/src/gui/gui_x11.cpp 2010-12-21 09:33:48.000000000 +0100
@@ -92,12 +92,13 @@ protected:
     XFontStruct* font_info;
     // color mngmt
     unsigned long pixel[nr_colors];
+    bool calib_input;


     // Signal handlers
     bool on_timer_signal();
     bool on_expose_event();
-    bool on_button_press_event(XEvent event);
+    bool on_button_press_event(int x, int y);

     // Helper functions
     void set_display_size(int width, int height);
@@ -159,8 +160,11 @@ GuiCalibratorX11::GuiCalibratorX11(Calib
     // Listen to events
     XGrabKeyboard(display, win, False, GrabModeAsync, GrabModeAsync,
                 CurrentTime);
-    XGrabPointer(display, win, False, ButtonPressMask, GrabModeAsync,
+    calib_input = calibrator->register_events();
+    if (!calib_input) {
+            XGrabPointer(display, win, False, ButtonPressMask, GrabModeAsync,
                 GrabModeAsync, None, None, CurrentTime);
+    }

     Colormap colormap = DefaultColormap(display, screen_num);
     XColor color;
@@ -294,7 +298,7 @@ bool GuiCalibratorX11::on_timer_signal()
     return true;
 }

-bool GuiCalibratorX11::on_button_press_event(XEvent event)
+bool GuiCalibratorX11::on_button_press_event(int x, int y)
 {
     // Clear window, maybe a bit overdone, but easiest for me atm.
     // (goal is to clear possible message and other clicks)
@@ -302,7 +306,7 @@ bool GuiCalibratorX11::on_button_press_e

     // Handle click
     time_elapsed = 0;
-    bool success = calibrator->add_click(event.xbutton.x, event.xbutton.y);
+    bool success = calibrator->add_click(x, y);

     if (!success && calibrator->get_numclicks() == 0) {
         draw_message("Mis-click detected, restarting...");
@@ -311,7 +315,7 @@ bool GuiCalibratorX11::on_button_press_e
     // Are we done yet?
     if (calibrator->get_numclicks() >= 4) {
         // Recalibrate
-        success = calibrator->finish(display_width, display_height);
+        success = calibrator->finish();

         if (success) {
             exit(0);
@@ -352,6 +356,10 @@ void GuiCalibratorX11::give_timer_signal

         // process events
         XEvent event;
+        int x, y;
+        if (instance->calib_input)
+                if (instance->calibrator->check_press_event(&x, &y))
+                        instance->on_button_press_event(x, y);
         while (XCheckWindowEvent(instance->display, instance->win, -1, &event) == True) {
             switch (event.type) {
                 case Expose:
@@ -360,11 +368,10 @@ void GuiCalibratorX11::give_timer_signal
                         break;
                     instance->on_expose_event();
                     break;
-
                 case ButtonPress:
-                    instance->on_button_press_event(event);
+                    if (!instance->calib_input)
+                        instance->on_button_press_event(event.xbutton.x, event.xbutton.y);
                     break;
-
                 case KeyPress:
                     exit(0);
                     break;

Finally a debug output of successful calibration:

-bash-4.1# ./xinput_calibrator --output-type xinput -v
DEBUG: XInputExtension version is 2.0
DEBUG: Skipping virtual master devices and devices without axis valuators.
DEBUG: Skipping device 'Virtual core XTEST pointer' id=4, does not report Absolute events.
DEBUG: Skipping device 'Mouse0' id=6, does not report Absolute events.
DEBUG: Selected device: EETI
DEBUG: Not usbtouchscreen calibrator: Not a usbtouchscreen device
DEBUG: setting Evdev Axis Calibration to axis valuators.
DEBUG: Successfully applied axis calibration.
DEBUG: Read axes swap value of 0.
Calibrating EVDEV driver for "EETI" id=8
        previous calibration values (from XInput): min_x=0, max_x=2047 and min_y=0, max_y=2047
register_events: num_classes=4
register_events: input_class=1
register_events: input_class=2
register_events: input_class=3
unknown class
register_events: input_class=6
unknown class
register_events: registered 3 events with xinput
button release 1 [381,1691]
DEBUG: Adding click 0 (X=381, Y=1691)
button release 1 [1699,1676]
DEBUG: Adding click 1 (X=1699, Y=1676)
button release 1 [380,363]
DEBUG: Adding click 2 (X=380, Y=363)
button release 1 [1711,369]
DEBUG: Adding click 3 (X=1711, Y=369)

Doing dynamic recalibration:
        Setting new calibration data: 160, 1924, 1901, 146
DEBUG: Successfully applied axis calibration.


--> Making the calibration permanent
  Install the 'xinput' tool and copy the command(s) below in a script that starts with your X session
    xinput set-int-prop "EETI" "Evdev Axis Calibration" 32 160 1924 1901 146

Hope that helps...

And maybe a final note - I am not completely sure about the Calibrator::finish() formulas - there are used 'width' and 'height' parameters on one side but on the other side inside the scaling computation, difference between 'max' and 'min' coordinates is used. If the source and target resolutions were the same, there would be off by one difference between (max - min) and width/height. Probably would not influence much and I do not see too deep into the calculation so it may be ok, but I was just wondering...

j4nn avatar Dec 21 '10 09:12 j4nn

This sounds pretty reasonable. I wonder why the code is not merged...

BTW, I've got the following problems with xinput_calibrator:

  • It does not recognise that the axis should be swapped for my touch screen. As it is pretty hard for xinput_calibrator alone to find out, I guess that there should be an command option the force the swap, i.e. something like '--swapaxes 1'.
  • In my case the calibration leads to a state where it is difficult to point to the very screen borders. I could imagine the outcome would be better if the calibration points where more near to the screen corners.
  • It would be a nice enhancement if the calibration would also allow mouse button (emulation) to gesture mapping.

aanno avatar Jun 02 '11 09:06 aanno