From e9d96e87acbd79b22837ac4b225ceb0bd0e1942e Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 18 May 2011 12:20:19 +1000 Subject: Add a property to toggle function key mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On some keyboards, the multimedia function keys are overlaid with the F keys. This property enables clients to switch the primary mode of these F keys between function keys and multimedia keys. Some keyboards provide an Fn key to toggle between the modes. This is hardware-specific and may or may not work on any given keyboard device. The current imlementation is only hooked up to apple keyboards. The kernel provides a tweak to enable/disable. /sys/module/hid_apple/parameters/fnmode 0 .. keyboard sends Fx keys, Fn disabled 1 .. keyboard sends multimedia keys, Fn toggles to function keys 2 .. keyboard sends function keys, Fn toggles to multimedia keys If fnmode is on 0, we force it to 2. Signed-off-by: Peter Hutterer Reviewed-by: Michel Dänzer --- include/evdev-properties.h | 12 ++ src/Makefile.am | 3 +- src/apple.c | 312 +++++++++++++++++++++++++++++++++++++++++++++ src/evdev.c | 1 + src/evdev.h | 10 ++ 5 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 src/apple.c diff --git a/include/evdev-properties.h b/include/evdev-properties.h index 16f2af7..745a1ba 100644 --- a/include/evdev-properties.h +++ b/include/evdev-properties.h @@ -75,4 +75,16 @@ /* CARD32 */ #define EVDEV_PROP_THIRDBUTTON_THRESHOLD "Evdev Third Button Emulation Threshold" +/* CARD8, 1 value, + This property is initialized on devices that have multimedia keys on the + function keys. The value of the property selects the default behaviour + for the function keys. The behaviour of the fn key (if any exists) is + hardware specific. On some hardware, fn may toggle the other set of + functions available on the keys. + + 0 send functions keys by default, fn may toggle to multimedia keys + 1 send multimedia keys by default, fn may toggle to function keys +*/ +#define EVDEV_PROP_FUNCTION_KEYS "Evdev Function Keys" + #endif diff --git a/src/Makefile.am b/src/Makefile.am index d1efe53..b3a5671 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -37,5 +37,6 @@ AM_CPPFLAGS =-I$(top_srcdir)/include emuMB.c \ emuThird.c \ emuWheel.c \ - draglock.c + draglock.c \ + apple.c diff --git a/src/apple.c b/src/apple.c new file mode 100644 index 0000000..8e00a84 --- /dev/null +++ b/src/apple.c @@ -0,0 +1,312 @@ +/* + * Copyright © 2011 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation, and that the name of Red Hat + * not be used in advertising or publicity pertaining to distribution + * of the software without specific, written prior permission. Red + * Hat makes no representations about the suitability of this software + * for any purpose. It is provided "as is" without express or implied + * warranty. + * + * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Authors: + * Peter Hutterer (peter.hutterer@redhat.com) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Apple-specific controls. + * + * On Apple keyboards, the multimedia function keys are overlaid with the F + * keys. F1 is also BrightnessDown, F10 is Mute, etc. The kernel provides a + * tweak to enable/disable this. + * + * /sys/module/hid_apple/parameters/fnmode + * 0 .. keyboard sends Fx keys, fn is disabled + * 1 .. keyboard sends multimedia keys, fn sends Fx keys + * 2 .. keyboard sends Fx keys, fn sends multimedia keys + * + * We only handle 1 and 2, don't care about 0. If fnmode is found to be on + * 0, we force it to 2 instead. + */ + +/* In this file: fkeymode refers to the evdev-specific enums and parameters, + * fnmode refers to the fnmode parameter exposed by the kernel. fnmode is + * apple-specific */ +#define FNMODE_PATH "/sys/module/hid_apple/parameters/fnmode" + +/* Taken from the kernel */ +#define USB_VENDOR_ID_APPLE 0x05ac +#define USB_DEVICE_ID_APPLE_ALU_MINI_ANSI 0x021d +#define USB_DEVICE_ID_APPLE_ALU_MINI_ISO 0x021e +#define USB_DEVICE_ID_APPLE_ALU_MINI_JIS 0x021f +#define USB_DEVICE_ID_APPLE_ALU_ANSI 0x0220 +#define USB_DEVICE_ID_APPLE_ALU_ISO 0x0221 +#define USB_DEVICE_ID_APPLE_ALU_JIS 0x0222 +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI 0x022c +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO 0x022d +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS 0x022e +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI 0x0239 +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO 0x023a +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS 0x023b + +static int EvdevAppleGetProperty (DeviceIntPtr dev, Atom property); +static int EvdevAppleSetProperty(DeviceIntPtr dev, Atom atom, + XIPropertyValuePtr val, BOOL checkonly); + +static Atom prop_fkeymode; +static Bool fnmode_readonly; /* set if we can only read fnmode */ + +struct product_table +{ + unsigned int vendor; + unsigned int product; +} apple_keyboard_table[] = { + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_MINI_ANSI}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_MINI_ISO}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_MINI_JIS}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_ANSI}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_ISO}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_JIS}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS}, + { 0, 0} +}; + +/** + * @return TRUE if the device matches a product in the given product table, + * FALSE otherwise + */ +static Bool product_check(const struct product_table *t, int vendor, int product) +{ + while (t->vendor) + { + if (vendor == t->vendor && product == t->product) + return TRUE; + t++; + } + + return FALSE; +} + +/** + * @return 0 on success, -1 otherwise (check errno) + */ +static int +set_fnmode(enum fkeymode fkeymode) +{ + int fd; + char mode; + int bytes_written; + + if (fkeymode == FKEYMODE_UNKNOWN) + { + errno = EINVAL; /* silly you */ + return -1; + } + + fd = open(FNMODE_PATH, O_WRONLY); + if (fd < 0) + return -1; + + mode = (fkeymode == FKEYMODE_FKEYS) ? '2' : '1'; + + bytes_written = write(fd, &mode, 1); + close(fd); + + return (bytes_written == 1) ? 0 : -1; +} + +/** + * Get the current value of fnmode. If fnmode is found to be on 0, we set it + * to 2 in the process. Yes, quite daring, I know. I live on the edge. + * + * @return The current setting of fnmode or FKEYMODE_UNKNOWN on error (check + * errno) + */ +static enum fkeymode +get_fnmode(void) +{ + int fd; + char retvalue; + + fd = open(FNMODE_PATH, O_RDWR); + if (fd < 0 && errno == EACCES) + { + fnmode_readonly = TRUE; + fd = open(FNMODE_PATH, O_RDONLY); + } + + if (fd < 0) + goto err; + + if (read(fd, &retvalue, 1) != 1) + goto err; + + if (retvalue != '0' && retvalue != '1' && retvalue != '2') + { + xf86Msg(X_ERROR, "Invalid fnmode value: %c\n", retvalue); + errno = EINVAL; + goto err; + } + + close(fd); + + /* we don't want 0, switch to 2 */ + if (retvalue == '0') + { + if (fnmode_readonly) + xf86Msg(X_WARNING, "fnmode is disabled and read-only. Fn key will" + "not toggle to multimedia keys.\n"); + else + set_fnmode(FKEYMODE_FKEYS); + } + + + return retvalue == '1' ? FKEYMODE_MMKEYS : FKEYMODE_FKEYS; + +err: + if (fd >= 0) + close(fd); + return FKEYMODE_UNKNOWN; +} + +/** + * Set the property value to fkeymode. If the property doesn't exist, + * initialize it. + */ +static void set_fkeymode_property(InputInfoPtr pInfo, enum fkeymode fkeymode) +{ + DeviceIntPtr dev = pInfo->dev; + BOOL init = FALSE; + char data; + + switch(fkeymode) + { + case FKEYMODE_FKEYS: data = 0; break; + case FKEYMODE_MMKEYS: data = 1; break; + case FKEYMODE_UNKNOWN: + xf86IDrvMsg(pInfo, X_ERROR, "Failed to get fnmode (%s)\n", strerror(errno)); + return; + } + + if (!prop_fkeymode) { + init = TRUE; + prop_fkeymode = MakeAtom(EVDEV_PROP_FUNCTION_KEYS, strlen(EVDEV_PROP_FUNCTION_KEYS), TRUE); + } + + /* Don't send an event if we're initializing the property */ + XIChangeDeviceProperty(dev, prop_fkeymode, XA_INTEGER, 8, + PropModeReplace, 1, &data, !init); + + if (init) + { + XISetDevicePropertyDeletable(dev, prop_fkeymode, FALSE); + XIRegisterPropertyHandler(dev, EvdevAppleSetProperty, EvdevAppleGetProperty, NULL); + } +} + + +/** + * Called when a client reads the property state. + * Update with current kernel state, it may have changed behind our back. + */ +static int +EvdevAppleGetProperty (DeviceIntPtr dev, Atom property) +{ + if (property == prop_fkeymode) + { + InputInfoPtr pInfo = dev->public.devicePrivate; + EvdevPtr pEvdev = pInfo->private; + enum fkeymode fkeymode; + + fkeymode = get_fnmode(); + if (fkeymode != pEvdev->fkeymode) { + /* set internal copy first, so we don't write to the file in + * SetProperty handler */ + pEvdev->fkeymode = fkeymode; + set_fkeymode_property(pInfo, fkeymode); + } + } + return Success; +} + +static int +EvdevAppleSetProperty(DeviceIntPtr dev, Atom atom, + XIPropertyValuePtr val, BOOL checkonly) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + EvdevPtr pEvdev = pInfo->private; + + if (atom == prop_fkeymode) + { + CARD8 v = *(CARD8*)val->data; + + if (val->format != 8 || val->type != XA_INTEGER) + return BadMatch; + + if (fnmode_readonly) + return BadAccess; + + if (v > 1) + return BadValue; + + if (!checkonly) + { + if ((!v && pEvdev->fkeymode != FKEYMODE_FKEYS) || + (v && pEvdev->fkeymode != FKEYMODE_MMKEYS)) + { + pEvdev->fkeymode = v ? FKEYMODE_MMKEYS : FKEYMODE_FKEYS; + set_fnmode(pEvdev->fkeymode); + } + } + } + + return Success; +} + +void +EvdevAppleInitProperty(DeviceIntPtr dev) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + EvdevPtr pEvdev = pInfo->private; + enum fkeymode fkeymode; + + if (!product_check(apple_keyboard_table, + pEvdev->id_vendor, pEvdev->id_product)) + return; + + fkeymode = get_fnmode(); + pEvdev->fkeymode = fkeymode; + set_fkeymode_property(pInfo, fkeymode); +} diff --git a/src/evdev.c b/src/evdev.c index f997490..596eed9 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -1359,6 +1359,7 @@ EvdevInit(DeviceIntPtr device) Evdev3BEmuInitProperty(device); EvdevWheelEmuInitProperty(device); EvdevDragLockInitProperty(device); + EvdevAppleInitProperty(device); return Success; } diff --git a/src/evdev.h b/src/evdev.h index 1741e59..ff50d0a 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -85,6 +85,13 @@ /* Number of longs needed to hold the given number of bits */ #define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS) +/* Function key mode */ +enum fkeymode { + FKEYMODE_UNKNOWN = 0, + FKEYMODE_FKEYS, /* function keys send function keys */ + FKEYMODE_MMKEYS, /* function keys send multimedia keys */ +}; + /* axis specific data for wheel emulation */ typedef struct { int up_button; @@ -197,6 +204,8 @@ typedef struct { /* Event queue used to defer keyboard/button events until EV_SYN time. */ int num_queue; EventQueueRec queue[EVDEV_MAXQUEUE]; + + enum fkeymode fkeymode; } EvdevRec, *EvdevPtr; /* Event posting functions */ @@ -243,4 +252,5 @@ void EvdevMBEmuInitProperty(DeviceIntPtr); void Evdev3BEmuInitProperty(DeviceIntPtr); void EvdevWheelEmuInitProperty(DeviceIntPtr); void EvdevDragLockInitProperty(DeviceIntPtr); +void EvdevAppleInitProperty(DeviceIntPtr); #endif -- cgit v1.2.3