[MPlayer-dev-eng] [PATCH] Apple Remote support double click

Ulion ulion2002 at gmail.com
Fri Aug 24 18:05:26 CEST 2007


Hello,

Here's a patch to support Apple Remote double click event, judged by a
double click max difference time.

Original mp_input_ar_read function now become mp_input_ar_read_raw,
and add a wrapper function as mp_input_ar_read to judge double click
event.

Most useful usage come by the double click feature is double click to
toggle fullscreen (in default cmd mapping).

-- 
Ulion
-------------- next part --------------
Index: input/input.c
===================================================================
--- input/input.c	(revision 24142)
+++ input/input.c	(working copy)
@@ -288,14 +288,20 @@
 
   { AR_PLAY, "AR_PLAY" },
   { AR_PLAY_HOLD, "AR_PLAY_HOLD" },
+  { AR_PLAY_DCLICK, "AR_PLAY_DCLICK" },
   { AR_NEXT, "AR_NEXT" },
   { AR_NEXT_HOLD, "AR_NEXT_HOLD" },
+  { AR_NEXT_DCLICK, "AR_NEXT_DCLICK" },
   { AR_PREV, "AR_PREV" },
   { AR_PREV_HOLD, "AR_PREV_HOLD" },
+  { AR_PREV_DCLICK, "AR_PREV_DCLICK" },
   { AR_MENU, "AR_MENU" },
   { AR_MENU_HOLD, "AR_MENU_HOLD" },
+  { AR_MENU_DCLICK, "AR_MENU_DCLICK" },
   { AR_VUP, "AR_VUP" },
+  { AR_VUP_DCLICK, "AR_VUP_DCLICK" },
   { AR_VDOWN, "AR_VDOWN" },
+  { AR_VDOWN_DCLICK, "AR_VDOWN_DCLICK" },
 
   { KEY_POWER, "POWER" },
   { KEY_MENU, "MENU" },
@@ -426,14 +432,20 @@
 #ifdef HAVE_APPLE_REMOTE
   { { AR_PLAY, 0}, "pause" },
   { { AR_PLAY_HOLD, 0}, "quit" },
+  { { AR_PLAY_DCLICK, 0}, "vo_fullscreen" },
   { { AR_NEXT, 0 }, "seek 30" },
   { { AR_NEXT_HOLD, 0 }, "seek 120" },
+  { { AR_NEXT_DCLICK, 0 }, "seek 120" },
   { { AR_PREV, 0 }, "seek -10" },
   { { AR_PREV_HOLD, 0 }, "seek -120" },
+  { { AR_PREV_DCLICK, 0 }, "seek -120" },
   { { AR_MENU, 0 }, "osd" },
   { { AR_MENU_HOLD, 0 }, "mute" },
+  { { AR_MENU_DCLICK, 0 }, "sub_select" },
   { { AR_VUP, 0 }, "volume 1"},
+  { { AR_VUP_DCLICK, 0 }, "vo_ontop"},
   { { AR_VDOWN, 0 }, "volume -1"},
+//  { { AR_VDOWN_DCLICK, 0 }, "mute"},
 #endif
   { { 'T', 0 }, "vo_ontop" },
   { { 'f', 0 }, "vo_fullscreen" },
Index: input/ar.c
===================================================================
--- input/ar.c	(revision 24142)
+++ input/ar.c	(working copy)
@@ -32,6 +32,8 @@
 #include "input.h"
 #include "ar.h"
 
+#include "mp_msg.h"
+
 extern int slave_mode;
 
 extern const double NSAppKitVersionNumber;
@@ -113,7 +115,16 @@
    in queue begin to be lost. Set to 16 to hold at least 2 events. */
 static const int MAX_QUEUE_SIZE = 16;
 
+static const float MAX_DCLICK_TIME_INTERVAL=0.33;
 
+static const int CHECK_DOUBLE_CLICK_EVENT = 1;
+
+#define DURATION_LESS_THAN(interval, t) \
+    ((interval)>0 && (interval)<(t)*1000 || (interval)<0 && -(interval)<(t)*1000000)
+#define DURATION_GREAT_OR_EQUAL(interval, t) \
+    ((interval)>0 && (interval)>=(t)*1000 || -(interval)>=(t)*1000000)
+
+
 static int FindHIDDevices(mach_port_t masterPort,
                           io_iterator_t *hidObjectIterator)
 {
@@ -305,6 +316,9 @@
 
 int is_mplayer_front()
 {
+#ifdef TEST
+    return 1;
+#else
     ProcessSerialNumber myProc, frProc;
     Boolean sameProc;
     pid_t parentPID;
@@ -319,9 +333,10 @@
             return parentPID==getppid();
     }
     return 0;
+#endif
 }
 
-int mp_input_ar_read(int fd)
+static int mp_input_ar_read_raw(AbsoluteTime *eventTime)
 {
     int i, down = 0;
     int ret = MP_INPUT_NOTHING;
@@ -334,14 +349,28 @@
     int cookie_nr = 0;
     char cookie_queue[MAX_QUEUE_SIZE];
     int value_queue[MAX_QUEUE_SIZE];
+    AbsoluteTime eventTimeQueue[MAX_QUEUE_SIZE];
+    static int first_time = 1;
 
     if (inited == 0)
         return MP_INPUT_NOTHING;
 
+    // We has to close and re-open the hid device once to make
+    // system not response to the remote input.
+    if (first_time) {
+        first_time = 0;
+        (*hidDeviceInterface)->close(hidDeviceInterface);
+        if ((*hidDeviceInterface)->open(hidDeviceInterface,
+                            kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess)
+            hidDeviceIsOpen = 0;
+    }
+
+
     while ((result = (*queue)->getNextEvent(queue, &event, zeroTime, 0)) == kIOReturnSuccess) {
 #ifdef TEST
-        printf(" - event cookie: %d, value: %d, long value: %d\n",
-              (int)event.elementCookie, (int)event.value, (int)event.longValue);
+        AbsoluteTime upTime = UpTime();
+        mp_msg(MSGT_INPUT,MSGL_DBG3, " - cookie: %d, value: %d, timestamp: %lu,%lu, uptime: %lu,%lu\n",
+              (int)event.elementCookie, (int)event.value, event.timestamp.hi, event.timestamp.lo, upTime.hi, upTime.lo);
 #endif
         // Shorten cookie sequence by removing cookies value 5 and 18,
         // since 5 always follows 6 (on tiger), 18 follows 19 (on leopard).
@@ -353,6 +382,7 @@
             cookie_nr = 0;
             continue;
         }
+        eventTimeQueue[cookie_nr] = event.timestamp;
         cookie_queue[cookie_nr] = (char)(int)event.elementCookie;
         value_queue[cookie_nr++] = event.value;
         // 4 cookies are necessary to make up a valid sequence.
@@ -367,6 +397,14 @@
                 ++ar_code;
             if (ar_code->cookies != NULL) {
                 ret = ar_code->keycode;
+                *eventTime = zeroTime;
+                for (i = cookie_nr-ar_code->seq_len; i < cookie_nr; ++i) {
+                    if (eventTimeQueue[i].hi>eventTime->hi
+                            || (eventTimeQueue[i].hi==eventTime->hi
+                            && eventTimeQueue[i].lo>eventTime->lo)) {
+                        *eventTime = eventTimeQueue[i];
+                    }
+                }
                 switch (ret) {
                     // For these 4 keys, the remote can keep a hold state.
                     case AR_VUP:
@@ -383,18 +421,217 @@
                     default:
                         down = 0;
                 }
+                break;
             }
         }
     }
 
+    if (ret > 0)
+        prev_event = ret;
+    return ret | down;
+}
+
+int mp_input_ar_read(int fd)
+{
+    int i, down;
+    int ret;
+    AbsoluteTime zeroTime = {0,0};
+    AbsoluteTime eventTime, upTime;
+    Duration interval = 0;
+    static AbsoluteTime pendingEventTime = {0,0};
+    static AbsoluteTime lastClickEventTime = {0,0}, secondClickDownTime = {0,0};
+    static int prev_event = 0, pending_event = 0, last_click_event = 0;
+    // Click down record is only for AR_VUP and AR_VDOWN.
+    static int first_click_down = 0, second_click_down = 0;
+
+    if (inited == 0)
+        return MP_INPUT_NOTHING;
+mp_input_ar_read_again:
+    ret = MP_INPUT_NOTHING;
+    eventTime = zeroTime;
+
+    if (pending_event) {
+        ret = pending_event & ~MP_KEY_DOWN;
+        down = pending_event & MP_KEY_DOWN;
+        eventTime = pendingEventTime;
+        pending_event = 0;
+        upTime = UpTime();
+        mp_msg(MSGT_INPUT,MSGL_DBG2, "[%lu, %lu] got pending event %d, %d, %lu, %lu\n",
+                upTime.hi, upTime.lo, ret, down, eventTime.hi, eventTime.lo);
+    }
+    else {
+        down = 0;
+        ret = mp_input_ar_read_raw(&eventTime);
+        if (ret != MP_INPUT_NOTHING) {
+            down = ret & MP_KEY_DOWN;
+            ret &= ~MP_KEY_DOWN;
+            upTime = UpTime();
+            mp_msg(MSGT_INPUT,MSGL_DBG2, "[%lu, %lu] got raw event %d, %d, %lu, %lu\n",
+                    upTime.hi, upTime.lo, ret, down, eventTime.hi, eventTime.lo);
+        }
+    }
+
+    if (CHECK_DOUBLE_CLICK_EVENT) {
+        switch (ret) {
+            // These 2 keys have both press and release events.
+            // Here we only handle press event, release event keeps going down.
+            case AR_VUP:
+            case AR_VDOWN:
+                if (down) {
+                    if (last_click_event == ret) {
+                        interval = AbsoluteDeltaToDuration(eventTime, lastClickEventTime);
+                        if (DURATION_LESS_THAN(interval, MAX_DCLICK_TIME_INTERVAL)) {
+                            // It's the second down event, we record it.
+                            second_click_down = 1;
+                            secondClickDownTime = eventTime;
+                            mp_msg(MSGT_INPUT,MSGL_DBG2, "got second click down, interval %d, continue read\n", (int)interval);
+                            goto mp_input_ar_read_again;
+                        }
+                    }
+                    if (last_click_event) {
+                        // Double click timeouted or key changed,
+                        // send the last click event we delayed.
+                        if (last_click_event == ret) {
+                            mp_msg(MSGT_INPUT,MSGL_DBG2, "double click down timeouted %d, send last click %lu, %lu\n",
+                                (int)interval, lastClickEventTime.hi, lastClickEventTime.lo);
+                        }
+                        else {
+                            mp_msg(MSGT_INPUT,MSGL_DBG2, "click event changed %d=>%d, send last click %lu, %lu\n",
+                                last_click_event, ret, lastClickEventTime.hi, lastClickEventTime.lo);
+                        }
+                        pending_event = ret | down;
+                        pendingEventTime = eventTime;
+                        ret = last_click_event;
+                        down = 0;
+                        eventTime = lastClickEventTime;
+                        last_click_event = 0;
+                    }
+                    else {
+                        // First click down, record it.
+                        printf("first click down, record and continue read\n");
+                        last_click_event = ret;
+                        first_click_down = 1;
+                        lastClickEventTime = eventTime;
+                        goto mp_input_ar_read_again;
+                    }
+                    break;
+                }
+            // For these 4 keys, we always get release events.
+            case AR_PLAY:
+            case AR_NEXT:
+            case AR_PREV:
+            case AR_MENU:
+                if (last_click_event == ret) {
+                    interval = AbsoluteDeltaToDuration(eventTime, lastClickEventTime);
+                    if (DURATION_LESS_THAN(interval, MAX_DCLICK_TIME_INTERVAL)) {
+                        // Send the double click event.
+                        if (!first_click_down) {
+                            mp_msg(MSGT_INPUT,MSGL_DBG2, "got double click event for %d, interval %d, (%lu, %lu=>%lu, %lu)\n",
+                                ret, (int)interval, lastClickEventTime.hi, lastClickEventTime.lo, eventTime.hi, eventTime.lo);
+                            ret += 2;
+                            down = 0;
+                            last_click_event = 0;
+                            second_click_down = 0;
+                            break;
+                        }
+                        // First click down released in time (volume up/down).
+                        mp_msg(MSGT_INPUT,MSGL_DBG2, "got first click down released in time for %d, interval %d, (%lu, %lu=>%lu, %lu) clear down flag and continue read\n",
+                            ret, (int)interval, lastClickEventTime.hi, lastClickEventTime.lo, eventTime.hi, eventTime.lo);
+                        // Because click down and release has a delay,
+                        // we use the first release time to judge double click.
+                        lastClickEventTime = eventTime;
+                        first_click_down = 0;
+                        goto mp_input_ar_read_again;
+                    }
+                    else if (first_click_down) {
+                        // First click down too long time, so the double click
+                        // should fail. Just send out current release event.
+                        mp_msg(MSGT_INPUT,MSGL_DBG2, "first click down %d released timeout, interval %d, (%lu, %lu=>%lu, %lu), just send the release event\n",
+                            ret, (int)interval, lastClickEventTime.hi, lastClickEventTime.lo, eventTime.hi, eventTime.lo);
+                        last_click_event = 0;
+                        first_click_down = 0;
+                        break;
+                    }
+                }
+                if (last_click_event) {
+                    // Send out last click event we delayed.
+                    if (last_click_event == ret) {
+                        mp_msg(MSGT_INPUT,MSGL_DBG2, "double click release timeouted %d, send last click %lu, %lu\n",
+                            (int)interval, lastClickEventTime.hi, lastClickEventTime.lo);
+                    }
+                    else {
+                        mp_msg(MSGT_INPUT,MSGL_DBG2, "click event changed %d=>%d, send last click %lu, %lu, and record current click\n",
+                            last_click_event, ret, lastClickEventTime.hi, lastClickEventTime.lo);
+                    }
+                    i = ret;
+                    ret = last_click_event;
+                    down = first_click_down ? MP_KEY_DOWN : 0;
+                    eventTime = lastClickEventTime;
+                    last_click_event = i;
+                    lastClickEventTime = eventTime;
+                    first_click_down = 0;
+                    second_click_down = 0;
+                }
+                else {
+                    mp_msg(MSGT_INPUT,MSGL_DBG2, "first click released, record and continue read\n");
+                    last_click_event = ret;
+                    lastClickEventTime = eventTime;
+                    goto mp_input_ar_read_again;
+                }
+                break;
+            case MP_INPUT_NOTHING:
+                if (last_click_event == 0)
+                    break;
+                // We has click event and no input, check the interval.
+                interval = AbsoluteDeltaToDuration(UpTime(), lastClickEventTime);
+                if (DURATION_GREAT_OR_EQUAL(interval, MAX_DCLICK_TIME_INTERVAL)) {
+                    // Last click event reach the time
+                    // Send out the click event we delayed.
+                    ret = last_click_event;
+                    down = first_click_down ? MP_KEY_DOWN : 0;
+                    if (second_click_down) {
+                        upTime = UpTime();
+                        mp_msg(MSGT_INPUT,MSGL_DBG2, "[%lu,%lu] double click timeouted %d, send last click %d, %d, %lu, %lu, and pending the second down %lu,%lu\n",
+                            upTime.hi, upTime.lo, (int)interval, ret, down, lastClickEventTime.hi, lastClickEventTime.lo, secondClickDownTime.hi, secondClickDownTime.lo);
+                        pending_event = last_click_event | MP_KEY_DOWN;
+                        pendingEventTime = secondClickDownTime;
+                        second_click_down = 0;
+                    }
+                    else {
+                        upTime = UpTime();
+                        mp_msg(MSGT_INPUT,MSGL_DBG2, "[%lu,%lu] double click timeouted %d, send last click %d, %d, %lu, %lu\n",
+                            upTime.hi, upTime.lo, (int)interval, ret, down, lastClickEventTime.hi, lastClickEventTime.lo);
+                    }
+                    last_click_event = 0;
+                    first_click_down = 0;
+                }
+                break;
+            default:
+                // other keys
+                if (last_click_event) {
+                    // pending current event and send out last click.
+                    i = ret | down;
+                    ret = last_click_event & ~MP_KEY_DOWN;
+                    down = last_click_event & MP_KEY_DOWN;
+                    mp_msg(MSGT_INPUT,MSGL_DBG2, "click event, send last click %d, %d, %lu, %lu and pending current event\n",
+                        ret, down, lastClickEventTime.hi, lastClickEventTime.lo);
+                    last_click_event = 0;
+                    pending_event = i;
+                    pendingEventTime = eventTime;
+                }
+        }
+    }
+
     if (!is_mplayer_front()) {
         if (hidDeviceIsOpen) {
             (*hidDeviceInterface)->close(hidDeviceInterface);
             hidDeviceIsOpen = 0;
 
             // read out all pending events
-            while (result == kIOReturnSuccess)
-                result = (*queue)->getNextEvent(queue, &event, zeroTime, 0);
+            while (ret != MP_INPUT_NOTHING)
+                ret = mp_input_ar_read_raw(&eventTime);
+            pending_event = 0;
+            last_click_event = first_click_down = second_click_down = 0;
         }
         return MP_INPUT_NOTHING;
     }
@@ -410,6 +647,8 @@
 
     if (ret > 0)
         prev_event = ret;
+    if (ret != MP_INPUT_NOTHING)
+        mp_msg(MSGT_INPUT,MSGL_V, "got Apple Remote event %d, %d\n", ret, down);
     return ret | down;
 }
 
@@ -439,6 +678,9 @@
 {
     int ret;
 
+    mp_msg_init();
+    verbose = 3;
+
     if (mp_input_ar_init() < 0) {
         printf("Unable to initialize Apple Remote.\n");
         return 1;
@@ -448,19 +690,26 @@
         switch ((ret = mp_input_ar_read(0)) & ~MP_KEY_DOWN) {
             case AR_PLAY:       printf(" - AR_PLAY."); break;
             case AR_PLAY_HOLD:  printf(" - AR_PLAY_HOLD."); break;
+            case AR_PLAY_DCLICK:printf(" - AR_PLAY_DCLICK."); break;
             case AR_NEXT:       printf(" - AR_NEXT."); break;
             case AR_NEXT_HOLD:  printf(" - AR_NEXT_HOLD."); break;
+            case AR_NEXT_DCLICK:printf(" - AR_NEXT_DCLICK."); break;
             case AR_PREV:       printf(" - AR_PREV."); break;
             case AR_PREV_HOLD:  printf(" - AR_PREV_HOLD."); break;
+            case AR_PREV_DCLICK:printf(" - AR_PREV_DCLICK."); break;
             case AR_MENU:       printf(" - AR_MENU."); break;
             case AR_MENU_HOLD:  printf(" - AR_MENU_HOLD."); break;
+            case AR_MENU_DCLICK:printf(" - AR_MENU_DCLICK."); break;
             case AR_VUP:        printf(" - AR_VUP."); break;
+            case AR_VUP_DCLICK: printf(" - AR_VUP_DCLICK."); break;
             case AR_VDOWN:      printf(" - AR_VDOWN."); break;
+            case AR_VDOWN_DCLICK:printf(" - AR_VDOWN_DCLICK."); break;
         }
         if ((ret > 0 )&&(ret & MP_KEY_DOWN))
-            printf(" [hold]");
+            printf(" [down]");
         if (ret > 0)
             printf("\n");
+        usleep(100000);
     }
 
     mp_input_ar_close(0);
Index: input/ar.h
===================================================================
--- input/ar.h	(revision 24142)
+++ input/ar.h	(working copy)
@@ -23,17 +23,26 @@
 #ifndef INPUT_AR_H
 #define INPUT_AR_H
 
+// AR_VUP_HOLD and AR_VDOWN_HOLD currently not implemented.
 #define AR_BASE      0x500
 #define AR_PLAY      (AR_BASE + 0)
 #define AR_PLAY_HOLD (AR_BASE + 1)
-#define AR_NEXT      (AR_BASE + 2)
-#define AR_NEXT_HOLD (AR_BASE + 3)
-#define AR_PREV      (AR_BASE + 4)
-#define AR_PREV_HOLD (AR_BASE + 5)
-#define AR_MENU      (AR_BASE + 6)
-#define AR_MENU_HOLD (AR_BASE + 7)
-#define AR_VUP       (AR_BASE + 8)
-#define AR_VDOWN     (AR_BASE + 9)
+#define AR_PLAY_DCLICK (AR_BASE + 2)
+#define AR_NEXT      (AR_BASE + 3)
+#define AR_NEXT_HOLD (AR_BASE + 4)
+#define AR_NEXT_DCLICK (AR_BASE + 5)
+#define AR_PREV      (AR_BASE + 6)
+#define AR_PREV_HOLD (AR_BASE + 7)
+#define AR_PREV_DCLICK (AR_BASE + 8)
+#define AR_MENU      (AR_BASE + 9)
+#define AR_MENU_HOLD (AR_BASE + 10)
+#define AR_MENU_DCLICK (AR_BASE + 11)
+#define AR_VUP       (AR_BASE + 12)
+#define AR_VUP_HOLD  (AR_BASE + 13)
+#define AR_VUP_DCLICK (AR_BASE + 14)
+#define AR_VDOWN     (AR_BASE + 15)
+#define AR_VDOWN_HOLD (AR_BASE + 16)
+#define AR_VDOWN_DCLICK (AR_BASE + 17)
 
 int mp_input_ar_init(void);
 int mp_input_ar_read(int fd);
Index: etc/input.conf
===================================================================
--- etc/input.conf	(revision 24142)
+++ etc/input.conf	(working copy)
@@ -105,14 +105,20 @@
 
 AR_PLAY pause
 AR_PLAY_HOLD quit
+AR_PLAY_DCLICK vo_fullscreen
 AR_NEXT seek 30
 AR_NEXT_HOLD seek 120
+AR_NEXT_DCLICK seek 120
 AR_PREV seek -10
 AR_PREV_HOLD seek -120
+AR_PREV_DCLICK seek -120
 AR_MENU osd
 AR_MENU_HOLD mute
+AR_MENU_DCLICK sub_select
 AR_VUP volume 1
+AR_VUP_DCLICK vo_ontop
 AR_VDOWN volume -1
+#AR_VDOWN_DCLICK mute
 
 ##
 ## OSD Menu movement keys


More information about the MPlayer-dev-eng mailing list