From bcf0c984f0d5409fc42e9694c1677366881ff6ad Mon Sep 17 00:00:00 2001 From: diego Date: Tue, 14 Aug 2007 14:29:22 +0000 Subject: Apple Remote support patch by Zoltan Ponekker, pontscho kac.poliod hu cleaned up by Ulion, ulion2002 gmail com with some help by Reimar and me git-svn-id: svn://svn.mplayerhq.hu/mplayer/trunk@24057 b3059339-0415-0410-9bf9-f77b7e298cf2 --- input/Makefile | 1 + input/ar.c | 469 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ input/ar.h | 42 ++++++ input/input.c | 39 +++++ 4 files changed, 551 insertions(+) create mode 100644 input/ar.c create mode 100644 input/ar.h (limited to 'input') diff --git a/input/Makefile b/input/Makefile index 993c81605c..3b4d085581 100644 --- a/input/Makefile +++ b/input/Makefile @@ -3,6 +3,7 @@ include ../config.mak LIBNAME_MPLAYER = libinput.a SRCS_MPLAYER = input.c +SRCS_MPLAYER-$(APPLE_REMOTE) += ar.c SRCS_MPLAYER-$(JOYSTICK) += joystick.c SRCS_MPLAYER-$(LIRC) += lirc.c diff --git a/input/ar.c b/input/ar.c new file mode 100644 index 0000000000..96c5161953 --- /dev/null +++ b/input/ar.c @@ -0,0 +1,469 @@ +/* + * Apple Remote input interface + * + * Copyright (C) 2007 Zoltan Ponekker + * + * (modified a bit by Ulion ) + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MPlayer; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include + +#include +#include + +#include "input.h" +#include "ar.h" + +extern int slave_mode; + +extern const double NSAppKitVersionNumber; + +typedef struct cookie_keycode_map { + char *cookies; + int seq_len; + int keycode; +} cookie_keycode_map_t; + +/* On tiger, 5 always follows 6; on leopard, 18 always follows 19. + * On leopard, there seems to be no cookie value of 5 or 6. + * Following is the shortened cookie sequence list + * keycode cookies_on_tiger cookies_on_leopard *down_state + * AR_PREV_HOLD 14+6+3+2 31+19+3+2 yes + * AR_NEXT_HOLD 14+6+4+2 31+19+4+2 yes + * AR_MENU_HOLD 14+6+14+6 31+19+31+19 + * AR_VUP 14+12+11+6 31+29+28+19 yes + * AR_VDOWN 14+13+11+6 31+30+28+19 yes + * AR_MENU 14+7+6+14+7+6 31+20+19+31+20+19 + * AR_PLAY 14+8+6+14+8+6 31+21+19+31+21+19 + * AR_NEXT 14+9+6+14+9+6 31+22+19+31+22+19 + * AR_PREV 14+10+6+14+10+6 31+23+19+31+23+19 + * AR_PLAY_HOLD 18+14+6+18+14+6 35+31+19+35+31+19 + * + * *down_state: A button with this feature has a pressed event and + * a released event, with which we can trace the state of the button. + * A button without this feature will only return one release event. + * + * hidden keys currently not implemented: + * hold for 5 secs + * MENU_NEXT_HOLD 15+14+6+15+14+6 + * MENU_PREV_HOLD 16+14+6+16+14+6 + * MENU_VUP_HOLD 20+14+6+20+14+6 + * MENU_VDOWN_HOLD 19+14+6+19+14+6 + * + * It seems that pressing 'menu' and 'play' on the Apple Remote for + * 5 seconds will trigger the make-pair function of the remote. + * MENU_PLAY_HOLD 21+15+14+6+15+14+6 + */ + +static const cookie_keycode_map_t ar_codes_tiger[] = { + { "\x0E\x06\x03\x02", 4, AR_PREV_HOLD }, + { "\x0E\x06\x04\x02", 4, AR_NEXT_HOLD }, + { "\x0E\x06\x0E\x06", 4, AR_MENU_HOLD }, + { "\x0E\x0C\x0B\x06", 4, AR_VUP }, + { "\x0E\x0D\x0B\x06", 4, AR_VDOWN }, + { "\x0E\x07\x06\x0E\x07\x06", 6, AR_MENU }, + { "\x0E\x08\x06\x0E\x08\x06", 6, AR_PLAY }, + { "\x0E\x09\x06\x0E\x09\x06", 6, AR_NEXT }, + { "\x0E\x0A\x06\x0E\x0A\x06", 6, AR_PREV }, + { "\x12\x0E\x06\x12\x0E\x06", 6, AR_PLAY_HOLD }, + { NULL, 0, MP_INPUT_NOTHING }, +}; + +static const cookie_keycode_map_t ar_codes_leopard[] = { + { "\x1F\x13\x03\x02", 4, AR_PREV_HOLD }, + { "\x1F\x13\x04\x02", 4, AR_NEXT_HOLD }, + { "\x1F\x13\x1F\x13", 4, AR_MENU_HOLD }, + { "\x1F\x1D\x1C\x13", 4, AR_VUP }, + { "\x1F\x1E\x1C\x13", 4, AR_VDOWN }, + { "\x1F\x14\x13\x1F\x14\x13", 6, AR_MENU }, + { "\x1F\x15\x13\x1F\x15\x13", 6, AR_PLAY }, + { "\x1F\x16\x13\x1F\x16\x13", 6, AR_NEXT }, + { "\x1F\x17\x13\x1F\x17\x13", 6, AR_PREV }, + { "\x23\x1F\x13\x23\x1F\x13", 6, AR_PLAY_HOLD }, + { NULL, 0, MP_INPUT_NOTHING }, +}; + +static int is_leopard; +static const cookie_keycode_map_t *ar_codes; + +static IOHIDQueueInterface **queue; +static IOHIDDeviceInterface **hidDeviceInterface = NULL; +static int inited = 0; +static int hidDeviceIsOpen; + +/* Maximum number of elements in queue before oldest elements + in queue begin to be lost. Set to 16 to hold at least 2 events. */ +static const int MAX_QUEUE_SIZE = 16; + + +static int FindHIDDevices(mach_port_t masterPort, + io_iterator_t *hidObjectIterator) +{ + CFMutableDictionaryRef hidMatchDictionary; + IOReturn ioReturnValue; + + // Set up a matching dictionary to search the I/O Registry + // by class name for all HID class devices. + hidMatchDictionary = IOServiceMatching("AppleIRController"); + + // Now search I/O Registry for matching devices. + ioReturnValue = IOServiceGetMatchingServices(masterPort, + hidMatchDictionary, + hidObjectIterator); + + // If search is unsuccessful, print message and hang. + if (ioReturnValue != kIOReturnSuccess || + !IOIteratorIsValid(*hidObjectIterator)) { + return -1; + } + return 0; +} + +static int getHIDCookies(IOHIDDeviceInterface122 **handle, + IOHIDElementCookie **cookies, + int *nr_cookies) +{ + CFTypeRef object; + long number; + CFArrayRef elements; + CFDictionaryRef element; + CFIndex i; + + *nr_cookies = 0; + + if (!handle || !(*handle)) + return -1; + + // Copy all elements, since we're grabbing most of the elements + // for this device anyway, and thus, it's faster to iterate them + // ourselves. When grabbing only one or two elements, a matching + // dictionary should be passed in here instead of NULL. + if (((*handle)->copyMatchingElements(handle, NULL, &elements)) != kIOReturnSuccess) + return -1; + + // No elements, still a valid result. + if (CFArrayGetCount(elements)==0) + return 0; + + *cookies = calloc(CFArrayGetCount(elements), sizeof(IOHIDElementCookie)); + if (*cookies == NULL) + return -1; + + for (i=0; iQueryInterface(plugInInterface, + CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), + (LPVOID)hidDeviceInterface) != S_OK + || *hidDeviceInterface == NULL || **hidDeviceInterface == NULL) { + (*plugInInterface)->Release(plugInInterface); + return -1; + } + + (*plugInInterface)->Release(plugInInterface); + + return 0; +} + +int mp_input_ar_init(void) +{ + io_iterator_t hidObjectIterator; + io_object_t hidDevice; + int i; + IOHIDElementCookie *cookies = NULL; + int nr_cookies = 0; + + if (inited) + mp_input_ar_close(-1); + + if (floor(NSAppKitVersionNumber) <= 824 /* NSAppKitVersionNumber10_4 */) { + ar_codes = &ar_codes_tiger[0]; + is_leopard = 0; + } + else { + ar_codes = &ar_codes_leopard[0]; + is_leopard = 1; + } + + if (FindHIDDevices(kIOMasterPortDefault, &hidObjectIterator)) + return -1; + + // Multiple controls could be found, we only use the first usable one. + while ((hidDevice = IOIteratorNext(hidObjectIterator))) { + if (CreateHIDDeviceInterface(hidDevice, &hidDeviceInterface) < 0) { + hidDeviceInterface = NULL; + IOObjectRelease(hidDevice); + continue; + } + if (getHIDCookies((IOHIDDeviceInterface122 **)hidDeviceInterface, + &cookies, + &nr_cookies) < 0) { + (*hidDeviceInterface)->Release(hidDeviceInterface); + hidDeviceInterface = NULL; + IOObjectRelease(hidDevice); + continue; + } + IOObjectRelease(hidDevice); + break; + } + if (hidDeviceInterface == NULL) + goto mp_input_ar_init_error; + + // Open the device. + if ((*hidDeviceInterface)->open(hidDeviceInterface, + kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) + goto mp_input_ar_init_error; + hidDeviceIsOpen = 1; + + if ((queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface)) == NULL + || *queue == NULL) + goto mp_input_ar_init_error; + + // Create the queue. + (*queue)->create(queue, 0, MAX_QUEUE_SIZE); + + // Add elements to the queue to make the queue work + // on tiger. It's a sequence from 1 to 21, + // maybe it's the range of cookie values. + for (i = 0;i < nr_cookies;i++) + (*queue)->addElement(queue, cookies[i], 0); + + // not used anymore + if (cookies != NULL) + free(cookies); + + // Start data delivery to the queue. + (*queue)->start(queue); + + // not useful anymore + IOObjectRelease(hidObjectIterator); + + inited = 1; + return 0; + +mp_input_ar_init_error: + if (cookies != NULL) + free(cookies); + if (hidDeviceInterface != NULL) { + if (*hidDeviceInterface != NULL) { + (*hidDeviceInterface)->close(hidDeviceInterface); + (*hidDeviceInterface)->Release(hidDeviceInterface); + } + hidDeviceInterface = NULL; + } + IOObjectRelease(hidObjectIterator); + return -1; +} + +int is_mplayer_front() +{ + ProcessSerialNumber myProc, frProc; + Boolean sameProc; + pid_t parentPID; + + if (GetFrontProcess(&frProc) == noErr + && GetCurrentProcess(&myProc) == noErr + && SameProcess(&frProc, &myProc, &sameProc) == noErr) { + if (sameProc) + return 1; + // If MPlayer is running in slave mode, also check parent process. + if (slave_mode && GetProcessPID(&frProc, &parentPID) == noErr) + return parentPID==getppid(); + } + return 0; +} + +int mp_input_ar_read(int fd) +{ + int i, down = 0; + int ret = MP_INPUT_NOTHING; + AbsoluteTime zeroTime = {0,0}; + IOHIDEventStruct event; + static int prev_event = 0; + IOReturn result = kIOReturnSuccess; + + const cookie_keycode_map_t *ar_code; + int cookie_nr = 0; + char cookie_queue[MAX_QUEUE_SIZE]; + int value_queue[MAX_QUEUE_SIZE]; + + if (inited == 0) + return MP_INPUT_NOTHING; + + 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); +#endif + // Shorten cookie sequence by removing cookies value 5 and 18, + // since 5 always follows 6 (on tiger), 18 follows 19 (on leopard). + if ((int)event.elementCookie == 5 + || ((int)event.elementCookie == 18 && is_leopard)) + continue; + // Check valid cookie range. + if ((int)event.elementCookie > 35 || (int)event.elementCookie < 2) { + cookie_nr = 0; + continue; + } + cookie_queue[cookie_nr] = (char)(int)event.elementCookie; + value_queue[cookie_nr++] = event.value; + // 4 cookies are necessary to make up a valid sequence. + if (cookie_nr>=4) { + // Find matching sequence. + ar_code = ar_codes; + while (ar_code->cookies != NULL && + (cookie_nr < ar_code->seq_len || + 0 != memcmp(ar_code->cookies, + &cookie_queue[cookie_nr-ar_code->seq_len], + ar_code->seq_len))) + ++ar_code; + if (ar_code->cookies != NULL) { + ret = ar_code->keycode; + switch (ret) { + // For these 4 keys, the remote can keep a hold state. + case AR_VUP: + case AR_VDOWN: + case AR_NEXT_HOLD: + case AR_PREV_HOLD: + for (i = cookie_nr-ar_code->seq_len; i < cookie_nr; ++i) { + if (value_queue[i]) { + down = MP_KEY_DOWN; + break; + } + } + break; + default: + down = 0; + } + } + } + } + + 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); + } + return MP_INPUT_NOTHING; + } + // If we are switched from running as a foreground process to a + // background process and back again, re-open the device to make + // sure we are not affected by the system or other applications + // using the Apple Remote. + else if (!hidDeviceIsOpen) { + if ((*hidDeviceInterface)->open(hidDeviceInterface, + kIOHIDOptionsTypeSeizeDevice) == kIOReturnSuccess) + hidDeviceIsOpen = 1; + } + + if (ret > 0) + prev_event = ret; + return ret | down; +} + +void mp_input_ar_close(int fd) +{ + if (inited == 0) + return; + + // Close the device. + (*hidDeviceInterface)->close(hidDeviceInterface); + + // Stop data delivery to queue. + (*queue)->stop(queue); + // Dispose of queue. + (*queue)->dispose(queue); + // Release the queue we allocated. + (*queue)->Release(queue); + + // Release the interface. + (*hidDeviceInterface)->Release(hidDeviceInterface); + + inited = 0; +} + +#ifdef TEST +int main(void) +{ + int ret; + + if (mp_input_ar_init() < 0) { + printf("Unable to initialize Apple Remote.\n"); + return 1; + } + + while (1) { + 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_NEXT: printf(" - AR_NEXT."); break; + case AR_NEXT_HOLD: printf(" - AR_NEXT_HOLD."); break; + case AR_PREV: printf(" - AR_PREV."); break; + case AR_PREV_HOLD: printf(" - AR_PREV_HOLD."); break; + case AR_MENU: printf(" - AR_MENU."); break; + case AR_MENU_HOLD: printf(" - AR_MENU_HOLD."); break; + case AR_VUP: printf(" - AR_VUP."); break; + case AR_VDOWN: printf(" - AR_VDOWN."); break; + } + if ((ret > 0 )&&(ret & MP_KEY_DOWN)) + printf(" [hold]"); + if (ret > 0) + printf("\n"); + } + + mp_input_ar_close(0); + return 0; +} +#endif diff --git a/input/ar.h b/input/ar.h new file mode 100644 index 0000000000..c6b7c4d5b9 --- /dev/null +++ b/input/ar.h @@ -0,0 +1,42 @@ +/* + * Apple Remote input interface + * + * Copyright (C) 2007 Zoltan Ponekker + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MPlayer; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef INPUT_AR_H +#define INPUT_AR_H + +#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) + +int mp_input_ar_init(void); +int mp_input_ar_read(int fd); +void mp_input_ar_close(int fd); + +#endif /* INPUT_AR_H */ diff --git a/input/input.c b/input/input.c index affe926c9c..a14fe75edf 100644 --- a/input/input.c +++ b/input/input.c @@ -35,6 +35,8 @@ #include #endif +#include "ar.h" + /// This array defines all known commands. /// The first field is an id used to recognize the command without too many strcmp. /// The second is obviously the command name. @@ -283,6 +285,17 @@ static mp_key_name_t key_names[] = { { JOY_BTN8, "JOY_BTN8" }, { JOY_BTN9, "JOY_BTN9" }, + { AR_PLAY, "AR_PLAY" }, + { AR_PLAY_HOLD, "AR_PLAY_HOLD" }, + { AR_NEXT, "AR_NEXT" }, + { AR_NEXT_HOLD, "AR_NEXT_HOLD" }, + { AR_PREV, "AR_PREV" }, + { AR_PREV_HOLD, "AR_PREV_HOLD" }, + { AR_MENU, "AR_MENU" }, + { AR_MENU_HOLD, "AR_MENU_HOLD" }, + { AR_VUP, "AR_VUP" }, + { AR_VDOWN, "AR_VDOWN" }, + { KEY_POWER, "POWER" }, { KEY_MENU, "MENU" }, { KEY_PLAY, "PLAY" }, @@ -408,6 +421,18 @@ static mp_cmd_bind_t def_cmd_binds[] = { { { JOY_BTN1, 0 }, "osd" }, { { JOY_BTN2, 0 }, "volume 1"}, { { JOY_BTN3, 0 }, "volume -1"}, +#endif +#ifdef HAVE_APPLE_REMOTE + { { AR_PLAY, 0}, "pause" }, + { { AR_PLAY_HOLD, 0}, "quit" }, + { { AR_NEXT, 0 }, "seek 30" }, + { { AR_NEXT_HOLD, 0 }, "seek 120" }, + { { AR_PREV, 0 }, "seek -10" }, + { { AR_PREV_HOLD, 0 }, "seek -120" }, + { { AR_MENU, 0 }, "osd" }, + { { AR_MENU_HOLD, 0 }, "mute" }, + { { AR_VUP, 0 }, "volume 1"}, + { { AR_VDOWN, 0 }, "volume -1"}, #endif { { 'T', 0 }, "vo_ontop" }, { { 'f', 0 }, "vo_fullscreen" }, @@ -525,6 +550,9 @@ static unsigned int ar_delay = 100, ar_rate = 8, last_ar = 0; static int use_joystick = 1, use_lirc = 1, use_lircc = 1; static char* config_file = "input.conf"; +/* Apple Remote */ +static int use_ar = 1; + static char* js_dev = NULL; static char* in_file = NULL; @@ -553,6 +581,8 @@ static m_option_t mp_input_opts[] = { { "lirc", &use_lirc, CONF_TYPE_FLAG, CONF_GLOBAL, 0, 1, NULL }, { "nolircc", &use_lircc, CONF_TYPE_FLAG, CONF_GLOBAL, 1, 0, NULL }, { "lircc", &use_lircc, CONF_TYPE_FLAG, CONF_GLOBAL, 0, 1, NULL }, + { "noar", &use_ar, CONF_TYPE_FLAG, CONF_GLOBAL, 1, 0, NULL }, + { "ar", &use_ar, CONF_TYPE_FLAG, CONF_GLOBAL, 0, 1, NULL }, { NULL, NULL, 0, 0, 0, 0, NULL} }; @@ -1728,6 +1758,15 @@ mp_input_init(int use_gui) { } #endif +#ifdef HAVE_APPLE_REMOTE + if(use_ar) { + if(mp_input_ar_init() < 0) + mp_msg(MSGT_INPUT,MSGL_ERR,MSGTR_INPUT_INPUT_ErrCantInitAppleRemote); + else + mp_input_add_key_fd(-1,0,mp_input_ar_read,mp_input_ar_close); + } +#endif + if(in_file) { struct stat st; if(stat(in_file,&st)) -- cgit v1.2.3