[PATCH v3 3/4] Add a property to toggle function key mode
Peter Hutterer
peter.hutterer at who-t.net
Sun May 22 16:33:27 PDT 2011
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 <peter.hutterer at who-t.net>
---
Changes to v2:
- avoid leaking file descriptors
(affected are get_fnmode and set_fnmode)
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 deletions(-)
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 at redhat.com)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <evdev.h>
+#include <evdev-properties.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <exevents.h>
+#include <xf86.h>
+#include <xf86Xinput.h>
+#include <X11/Xatom.h>
+
+/* 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 d93adb4..38868c4 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
--
1.7.4.4
More information about the xorg-devel
mailing list