From: Andrew Ruthven Date: Sat, 16 Feb 2019 07:11:32 +0000 (+0000) Subject: First, belated, commit (started work in 2016) X-Git-Tag: debian/7.5-1~29 X-Git-Url: http://git.etc.gen.nz/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=15decd611438c171844cd53f15ea583830bf4f94;p=sapphire-remote.git First, belated, commit (started work in 2016) --- 15decd611438c171844cd53f15ea583830bf4f94 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..99ee691 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build-stamp +configure-stamp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..57e13d8 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +#/usr/bin/make +SRC = $(DESTDIR)/usr/src +SHARE = $(DESTDIR)/usr/share/$(NAME)-dkms + +all: + +clean: + +install: + +#source tree +ifeq ("$(wildcard $(NAME)-$(VERSION))", "$(NAME)-$(VERSION)") + install -d "$(SRC)" + cp -a $(NAME)-$(VERSION) $(SRC) + chmod 644 -R "$(SRC)/$(NAME)-$(VERSION)" +endif + +#tarball, possibly with binaries +ifeq ("$(wildcard $(NAME)-$(VERSION).dkms.tar.gz)", "$(NAME)-$(VERSION).dkms.tar.gz") + install -d "$(SHARE)" + install -m 644 $(NAME)-$(VERSION).dkms.tar.gz "$(SHARE)" +endif + +#postinst, only if we are supporting legacy mode +ifeq ("$(wildcard common.postinst)", "common.postinst") + install -d "$(SHARE)" + install -m 755 $(PREFIX)/usr/lib/dkms/common.postinst $(SHARE)/postinst +endif diff --git a/common.postinst b/common.postinst new file mode 100755 index 0000000..0c8cfc1 --- /dev/null +++ b/common.postinst @@ -0,0 +1,295 @@ +#!/bin/sh +# Copyright (C) 2002-2005 Flavio Stanchina +# Copyright (C) 2005-2006 Aric Cyr +# Copyright (C) 2007 Mario Limonciello +# Copyright (C) 2009 Alberto Milone + +set -e + +uname_s=$(uname -s) + +_get_kernel_dir() { + KVER=$1 + case ${uname_s} in + Linux) DIR="/lib/modules/$KVER/build" ;; + GNU/kFreeBSD) DIR="/usr/src/kfreebsd-headers-$KVER/sys" ;; + esac + echo $DIR +} + +_check_kernel_dir() { + DIR=$(_get_kernel_dir $1) + case ${uname_s} in + Linux) test -e $DIR/include ;; + GNU/kFreeBSD) test -e $DIR/kern && test -e $DIR/conf/kmod.mk ;; + *) return 1 ;; + esac + return $? +} + +# Check the existence of a kernel named as $1 +_is_kernel_name_correct() { + CORRECT="no" + KERNEL_NAME=$1 + + for kernel in /boot/config-*; do + KERNEL=${kernel#*-} + if [ "${KERNEL}" = "${KERNEL_NAME}" ]; then + CORRECT="yes" + break + fi + done + + echo $CORRECT +} + + +# Get the most recent kernel on Debian based systems. This keeps +# into account both the version and the ABI. If the current kernel +# is the most recent kernel then the function will print a null string. +_get_newest_kernel_debian() { + NEWEST_KERNEL= + NEWEST_VERSION= + NEWEST_ABI= + + for kernel in /boot/config-*; do + KERNEL=${kernel#*-} + KERNEL_VERSION=${KERNEL%%-*} + ABI=${KERNEL#*-} + ABI=${ABI%%-*} + + if [ -z "$NEWEST_KERNEL" ]; then + # The 1st time get a version which is bigger than $1 + COMPARE_TO=$1 + else + # Get the biggest version + COMPARE_TO="$NEWEST_VERSION-$NEWEST_ABI" + fi + + # if $kernel is greater than $COMPARE_TO + if [ `dpkg --compare-versions "$KERNEL_VERSION-$ABI" gt "$COMPARE_TO" && echo "yes" || \ + echo "no"` = "yes" ]; then + NEWEST_KERNEL=$KERNEL + NEWEST_VERSION=$KERNEL_VERSION + NEWEST_ABI=$ABI + fi + done + + echo "$NEWEST_KERNEL" +} + +# Get the most recent kernel in Rhel based systems. If the current kernel +# is the most recent kernel then the function will print a null string. +_get_newest_kernel_rhel() { + NEWEST_KERNEL= + + LAST_INSTALLED_KERNEL=$(rpm -q --whatprovides kernel --last | grep kernel -m1 | cut -f1 -d' ') + + LIK_FORMATTED_NAME=$(rpm -q $LAST_INSTALLED_KERNEL --queryformat="%{VERSION}-%{RELEASE}.%{ARCH}\n") + + if [ `echo $LIK_FORMATTED_NAME | grep 2.6 >/dev/null` ]; then + # Fedora and Suse + NEWEST_KERNEL=$LIK_FORMATTED_NAME + else + # Hack for Mandriva where $LIK_FORMATTED_NAME is broken + LIK_NAME=$(rpm -q $LAST_INSTALLED_KERNEL --queryformat="%{NAME}\n") + LIK_TYPE=${LIK_NAME#kernel-} + LIK_TYPE=${LIK_TYPE%%-*} + LIK_STRIPPED=${LIK_NAME#kernel-} + LIK_STRIPPED=${LIK_STRIPPED#$LIK_TYPE-} + LIK_STRIPPED_BASE=${LIK_STRIPPED%%-*} + LIK_STRIPPED_END=${LIK_STRIPPED#$LIK_STRIPPED_BASE-} + LIK_FINAL=$LIK_STRIPPED_BASE-$LIK_TYPE-$LIK_STRIPPED_END + + NEWEST_KERNEL=$LIK_FINAL + fi + + echo $NEWEST_KERNEL +} + +# Get the newest kernel on Debian and Rhel based systems. +get_newest_kernel() { + NEWEST_KERNEL= + # Try Debian first as rpm can be installed in Debian based distros + if [ -e /usr/bin/dpkg ]; then + # If DEB based + CURRENT_KERNEL=$1 + CURRENT_VERSION=${CURRENT_KERNEL%%-*} + CURRENT_ABI=${CURRENT_KERNEL#*-} + CURRENT_FLAVOUR=${CURRENT_ABI#*-} + CURRENT_ABI=${CURRENT_ABI%%-*} + NEWEST_KERNEL=$(_get_newest_kernel_debian "$CURRENT_VERSION-$CURRENT_ABI") + + elif [ `which rpm >/dev/null` ]; then + # If RPM based + NEWEST_KERNEL=$(_get_newest_kernel_rhel) + fi + + # Make sure that kernel name that we extracted corresponds to an installed + # kernel + if [ -n "$NEWEST_KERNEL" ] && [ `_is_kernel_name_correct $NEWEST_KERNEL` = "no" ]; then + NEWEST_KERNEL= + fi + + echo $NEWEST_KERNEL +} + +NAME=$1 +VERSION=$2 +TARBALL_ROOT=$3 +ARCH=$4 +UPGRADE=$5 + +if [ -z "$NAME" ] || [ -z "$VERSION" ]; then + echo "Need NAME, and VERSION defined" + echo "ARCH is optional" + exit 1 +fi + +KERNELS=$(ls /lib/modules/ 2>/dev/null || true) +CURRENT_KERNEL=$(uname -r) + +#We never want to keep an older version side by side to prevent conflicts +if [ -e "/var/lib/dkms/$NAME/$VERSION" ]; then + echo "Removing old $NAME-$VERSION DKMS files..." + dkms remove -m $NAME -v $VERSION --all +fi + +#Load new files, by source package and by tarball +if [ -f "$TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz" ]; then + if ! dkms ldtarball --archive "$TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz"; then + echo "" + echo "" + echo "Unable to load DKMS tarball $TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz." + echo "Common causes include: " + echo " - You must be using DKMS 2.1.0.0 or later to support binaries only" + echo " distribution specific archives." + echo " - Corrupt distribution specific archive" + echo "" + echo "" + exit 2 + fi +elif [ -d "/usr/src/$NAME-$VERSION" ]; then + echo "Loading new $NAME-$VERSION DKMS files..." + dkms add -m $NAME -v $VERSION > /dev/null +fi + +# On 1st installation, let us look for a directory +# in /lib/modules which matches `uname -r`. If none +# is found it is possible that buildd is being used +# and that uname -r is giving us the name of the +# kernel used by the buildd machine. +# +# If this is the case we try to build the kernel +# module for each kernel which has a directory in +# /lib/modules. Furthermore we will have to tell +# DKMS which architecture it should build the module +# for (e.g. if the buildd machine is using a +# 2.6.24-23-xen 64bit kernel). +# +# NOTE: if the headers are not installed then the +# module won't be built, as usual +if [ -z "$UPGRADE" ]; then + echo "First Installation: checking all kernels..." + for KERNEL in $KERNELS; do + if [ ${KERNEL} = ${CURRENT_KERNEL} ]; then + # Kernel found + KERNELS=$CURRENT_KERNEL + break + fi + done +else + KERNELS=$CURRENT_KERNEL +fi + +# Here we look for the most recent kernel so that we can +# build the module for it (in addition to doing it for the +# current kernel. +NEWEST_KERNEL=$(get_newest_kernel "$KERNELS") + +# If the current kernel doesn't come from the host of a chroot +if [ `_is_kernel_name_correct $CURRENT_KERNEL` = "yes" ]; then + # See if it's worth building the module for both the newest kernel + # and for the current kernel + if [ -n "$NEWEST_KERNEL" ] && [ ${CURRENT_KERNEL} != ${NEWEST_KERNEL} ]; then + echo "Building for $CURRENT_KERNEL and $NEWEST_KERNEL" + KERNELS="$CURRENT_KERNEL $NEWEST_KERNEL" + else + echo "Building only for $CURRENT_KERNEL" + fi +# The current kernel is not useful as it's the host's +else + echo "It is likely that $CURRENT_KERNEL belongs to a chroot's host" + + # Let's use only the newest kernel + if [ -n "$NEWEST_KERNEL" ]; then + KERNELS="$NEWEST_KERNEL" + echo "Building only for $NEWEST_KERNEL" + fi +fi + +if [ -n "$ARCH" ]; then + if which lsb_release >/dev/null && [ $(lsb_release -s -i) = "Ubuntu" ]; then + case $ARCH in + amd64) + ARCH="x86_64" + ;; + lpia|i?86) + ARCH="i686" + ;; + esac + fi + echo "Building for architecture $ARCH" + ARCH="-a $ARCH" +fi + +for KERNEL in $KERNELS; do + dkms_status=`dkms status -m $NAME -v $VERSION -k $KERNEL $ARCH` + if [ `echo $KERNEL | grep -c "BOOT"` -gt 0 ]; then + echo "" + echo "Module build and install for $KERNEL was skipped as " + echo "it is a BOOT variant" + continue + fi + + + #if the module isn't yet built, try to build it + if [ `echo $dkms_status | grep -c ": built"` -eq 0 ]; then + if [ ! -L /var/lib/dkms/$NAME/$VERSION/source ]; then + echo "This package appears to be a binaries-only package" + echo " you will not be able to build against kernel $KERNEL" + echo " since the package source was not provided" + continue + fi + if _check_kernel_dir $KERNEL; then + echo "Building initial module for $KERNEL" + set +e + dkms build -m $NAME -v $VERSION -k $KERNEL $ARCH > /dev/null + case $? in + 9) + set -e + echo "Skipped." + continue + ;; + 0) + set -e + echo "Done." + ;; + *) + exit $? + ;; + esac + dkms_status=`dkms status -m $NAME -v $VERSION -k $KERNEL $ARCH` + else + echo "Module build for the currently running kernel was skipped since the" + echo "kernel source for this kernel does not seem to be installed." + fi + fi + + #if the module is built (either pre-built or just now), install it + if [ `echo $dkms_status | grep -c ": built"` -eq 1 ] && + [ `echo $dkms_status | grep -c ": installed"` -eq 0 ]; then + dkms install -m $NAME -v $VERSION -k $KERNEL $ARCH + fi +done + diff --git a/debian/.gitignore b/debian/.gitignore new file mode 100644 index 0000000..033aa4a --- /dev/null +++ b/debian/.gitignore @@ -0,0 +1,4 @@ +*.log +*.substvars +sapphire-remote-dkms/ +sapphire-remote/ diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100755 index 0000000..befa406 --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,5 @@ +sapphire-remote DKMS module for Debian + +This package was automatically generated by the DKMS system, +for distribution on Debian based operating systems. + diff --git a/debian/changelog b/debian/changelog new file mode 100755 index 0000000..5c120b4 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,6 @@ +sapphire-remote-dkms (6.6) stable; urgency=low + + * Automatically packaged by DKMS. + + -- Dynamic Kernel Modules Support Team Sun, 21 Aug 2016 21:31:06 +0000 + diff --git a/debian/compat b/debian/compat new file mode 100755 index 0000000..7f8f011 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/control b/debian/control new file mode 100755 index 0000000..ac8ac30 --- /dev/null +++ b/debian/control @@ -0,0 +1,16 @@ +Source: sapphire-remote-dkms +Section: misc +Priority: optional +Maintainer: Dynamic Kernel Modules Support Team +Build-Depends: debhelper (>= 7), dkms, gawk +Standards-Version: 3.8.1 + +Package: sapphire-remote-dkms +Architecture: all +Depends: dkms (>= 1.95), ${misc:Depends} +Description: sapphire-remote driver in DKMS format. + +Package: sapphire-remote +Architecture: all +Depends: sapphire-remote-dkms, ${misc:Depends} +Description: sapphire-remote tools diff --git a/debian/copyright b/debian/copyright new file mode 100755 index 0000000..ad983f3 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,2 @@ + +This copyright has not been completed by the author of this package. diff --git a/debian/dirs.sapphire-remote-dkms b/debian/dirs.sapphire-remote-dkms new file mode 100755 index 0000000..b601f22 --- /dev/null +++ b/debian/dirs.sapphire-remote-dkms @@ -0,0 +1 @@ +usr/src diff --git a/debian/files b/debian/files new file mode 100644 index 0000000..e2bfa16 --- /dev/null +++ b/debian/files @@ -0,0 +1,2 @@ +sapphire-remote-dkms_6.6_all.deb misc optional +sapphire-remote_6.6_all.deb misc optional diff --git a/debian/postinst.sapphire-remote-dkms b/debian/postinst.sapphire-remote-dkms new file mode 100755 index 0000000..5714e0b --- /dev/null +++ b/debian/postinst.sapphire-remote-dkms @@ -0,0 +1,49 @@ +#!/bin/sh +# Copyright (C) 2002-2005 Flavio Stanchina +# Copyright (C) 2005-2006 Aric Cyr +# Copyright (C) 2007 Mario Limonciello +# Copyright (C) 2009 Alberto Milone + +set -e + +NAME=sapphire-remote +PACKAGE_NAME=$NAME-dkms +DEB_NAME=$(echo $PACKAGE_NAME | sed 's,_,-,') +CVERSION=`dpkg-query -W -f='${Version}' $DEB_NAME | awk -F "-" '{print $1}' | cut -d\: -f2` +ARCH=`dpkg --print-architecture` + +dkms_configure () { + for POSTINST in /usr/lib/dkms/common.postinst "/usr/share/$PACKAGE_NAME/postinst"; do + if [ -f "$POSTINST" ]; then + "$POSTINST" "$NAME" "$CVERSION" "/usr/share/$PACKAGE_NAME" "$ARCH" "$2" + return $? + fi + echo "WARNING: $POSTINST does not exist." >&2 + done + echo "ERROR: DKMS version is too old and $PACKAGE_NAME was not" >&2 + echo "built with legacy DKMS support." >&2 + echo "You must either rebuild $PACKAGE_NAME with legacy postinst" >&2 + echo "support or upgrade DKMS to a more current version." >&2 + return 1 +} + +case "$1" in + configure) + dkms_configure + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/debian/prerm.sapphire-remote-dkms b/debian/prerm.sapphire-remote-dkms new file mode 100755 index 0000000..cf927b9 --- /dev/null +++ b/debian/prerm.sapphire-remote-dkms @@ -0,0 +1,28 @@ +#!/bin/sh + +NAME=sapphire-remote +VERSION=6.6 + +set -e + +case "$1" in + remove|upgrade|deconfigure) + if [ "`dkms status -m $NAME`" ]; then + dkms remove -m $NAME -v $VERSION --all + fi + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 + + diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..0a2855e --- /dev/null +++ b/debian/rules @@ -0,0 +1,54 @@ +#!/usr/bin/make -f +# -*- makefile -*- + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +DEB_NAME=sapphire-remote +NAME=sapphire-remote +VERSION=6.6 + +configure: configure-stamp +configure-stamp: + dh_testdir + touch configure-stamp + + +build: build-stamp + +build-stamp: configure-stamp + dh_testdir + $(MAKE) + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + -$(MAKE) clean + dh_clean + +install: build + dh_testdir + dh_testroot + dh_prep + dh_installdirs + $(MAKE) DESTDIR=$(CURDIR)/debian/$(DEB_NAME)-dkms NAME=$(NAME) VERSION=$(VERSION) install + +binary-arch: build install + +binary-indep: build install + dh_testdir + dh_testroot + dh_link + dh_strip + dh_compress + dh_fixperms + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/debian/service.sapphire-remote b/debian/service.sapphire-remote new file mode 100644 index 0000000..b528d7b --- /dev/null +++ b/debian/service.sapphire-remote @@ -0,0 +1,8 @@ +[Unit] +Description=Sapphire HID driver + +[Service] +ExecStartPre=/usr/lib/sapphire-remote/sapphire_keymap.sh + +[Install] +WantedBy=multi-user.target diff --git a/sapphire-remote-6.6.dkms.tar.gz b/sapphire-remote-6.6.dkms.tar.gz new file mode 100644 index 0000000..dc482ac Binary files /dev/null and b/sapphire-remote-6.6.dkms.tar.gz differ diff --git a/sapphire-remote-6.6/Makefile b/sapphire-remote-6.6/Makefile new file mode 100644 index 0000000..8fd1233 --- /dev/null +++ b/sapphire-remote-6.6/Makefile @@ -0,0 +1,86 @@ +# +# Makefile and (un-)Installer for Sapphire remote control driver, +# by Mark Lord 2012-2015. +# +MODNAME=sapphire +MODSOURCE=$(MODNAME).c +CONFLICTS=hid_topseed +BLACKLIST=/etc/modprobe.d/blacklist-$(CONFLICTS).conf +KEYMAP_SCRIPT=sapphire_keymap.sh +EXTRACT_KEYDEFS=extract_keydefs.sh +INPUT_H=$(shell [ -e /usr/include/linux/input-event-codes.h ] && echo /usr/include/linux/input-event-codes.h || echo /usr/include/linux/input.h) + +## MODPARMS is not used any more, but we have to nuke old copies to prevent issues: +MODPARMS=/etc/modprobe.d/$(MODNAME).conf + +CWD = $(shell pwd -P) +KVER ?= $(shell uname -r) +KDIR = /lib/modules/$(KVER) +obj-m += $(MODNAME).o + +#EXTRA_CFLAGS += -Werror + +default: $(INPUT_H) kmod $(KEYMAP_SCRIPT) + +$(INPUT_H): + @echo "ERROR: $@: not found." + @echo + @echo "Please install the system development libraries first." + @echo "Eg. on Ubuntu/Mint systems: sudo apt-get install build-essential" + @echo + @exit 1 + +kmod: + $(MAKE) -C $(KDIR)/build M=$(CWD) modules + +$(MODNAME).ko: $(MODSOURCE) $(MODNAME).h + $(MAKE) kmod + +$(KEYMAP_SCRIPT): $(INPUT_H) $(EXTRACT_KEYDEFS) $(KEYMAP_SCRIPT).part1 $(KEYMAP_SCRIPT).part3 $(MODNAME).h $(INPUT_H) + @bash -c "type -all gawk" >/dev/null 2>&1 || (\ + echo ;\ + echo "gawk not found, needed by installer; Aborted." ;\ + echo "For Ubuntu/Debian based systems, try this: sudo apt-get install gawk" ;\ + echo ;\ + exit 2 ;\ + ) + chmod 0755 $(EXTRACT_KEYDEFS) + ./$(EXTRACT_KEYDEFS) $(INPUT_H) $(MODNAME).h | cat $(KEYMAP_SCRIPT).part1 - $(KEYMAP_SCRIPT).part3 > $(KEYMAP_SCRIPT) + chmod 0755 $(KEYMAP_SCRIPT) + +$(BLACKLIST): + @echo "Creating $(BLACKLIST)" + @echo "## $(CONFLICTS) Conflicts with $(MODNAME) driver" > $(BLACKLIST) + @echo "blacklist $(CONFLICTS)" >> $(BLACKLIST) + +modinstall: $(MODNAME).ko $(KEYMAP_SCRIPT) + $(MAKE) -C $(KDIR)/build M=$(CWD) modules_install || exit 0 + @[ -e $(KDIR)/extra/sapphire.ko ] || exit 1 + depmod $(KVER) + @if [ "$(KVER)" = "$$(uname -r)" ]; then \ + rmmod $(MODNAME) 2>/dev/null ;\ + rmmod $(CONFLICTS) 2>/dev/null ;\ + ./sapphire_startup.sh ;\ + else \ + exit 0 ;\ + fi + +clean: + $(MAKE) -C $(KDIR)/build M=$(CWD) clean + -rm -f $(KEYMAP_SCRIPT) + +clean_modparms: + @if [ -e $(MODPARAMS) ]; then rm -f $(MODPARMS) ; fi ; exit 0 + +uninstall: clean clean_modparms + -rmmod $(MODNAME) + -rm -f $(BLACKLIST) + -rm -f /usr/local/bin/$(KEYMAP_SCRIPT) + -rm -f $(KDIR)/extra/$(MODNAME).ko + depmod + +install: $(MODNAME).ko clean_modparms modinstall $(BLACKLIST) $(KEYMAP_SCRIPT) + install -m 0755 $(KEYMAP_SCRIPT) /usr/local/bin/ + install -m 0755 sapphire_startup.sh /usr/local/bin/ + +all: install diff --git a/sapphire-remote-6.6/README.txt b/sapphire-remote-6.6/README.txt new file mode 100644 index 0000000..5a9a168 --- /dev/null +++ b/sapphire-remote-6.6/README.txt @@ -0,0 +1,450 @@ +Sapphire hid driver, by Mark Lord , September/2015. +http://rtr.ca/sapphire_remote/ + +This package contains a standalone Linux "hid" driver +for the Sapphire Theatrix I/R remote control. + +To build/install the driver, simply do this: + + sudo make install + +That command will need to be repeated after any +subsequent kernel upgrades. + +This driver competes with hid-topseed for control of the device. +As of 2012, several distros are now building the hid-topseed driver +into the main kernel image, preventing it from being blacklisted +and unloaded at run time. To work around this, there is now a script +included in this package for unbinding the device(s) from hid-topseed. +This script should be run from /etc/rc.local, by manually editing +that file and adding this line: + + /usr/local/bin/sapphire_startup.sh + +This driver comes pre-configured for use with Mythtv, +with the default mappings for most buttons set to +what Mythtv usually expects. + +You can customize the mappings, by copying/editing +the keymap.default file, and then feeding your modified +copy as a parameter to /usr/local/bin/sapphire_keymap.sh +after each system startup. + +Or you can directly edit the default keymap that is +built into the driver source code, but that runs a risk +of conflicts when the driver gets updated in the future. + +Best method: copy/edit the keymap.default file, +and save it as: /etc/sapphire.keymap + +Then ensure you have this line (from above) in /etc/rc.local: + + /usr/local/bin/sapphire_startup.sh ## already discussed above + +Done. + +One can also use /proc/sapphire to inject KEY_ presses into the system +from a script, just as if they had been typed on a keyboard or remote. + + Eg. echo "SENDKEY 5" > /proc/sapphire ## sends KEY_4 (defined as 5 in input.h). + +This feature could be used to control mythfrontend from external scripts, +or to automate keyboard input for any other application as well. + +A special keyword "REPEAT_RATE" is recognized by /proc/sapphire, +and enables modifying the ramping repeat attack rate and maximum repeat rate. +Two values must be provided: the number of times/second that the rate is +adjusted, and the maxiumum number of repeats per second. +The default values can be restored like this: + + Eg. echo "REPEAT_RATE 40 10" > /proc/sapphire + +The same syntax can also be used in a keymap file. + +* * * * * * * * * * + +Below is a handy list of most of the available KEY_* names +for use with definitions in the keymap file. +These were extracted from /usr/include/linux/input.h: + +KEY_0 +KEY_102ND +KEY_1 +KEY_2 +KEY_3 +KEY_4 +KEY_5 +KEY_6 +KEY_7 +KEY_8 +KEY_9 +KEY_A +KEY_AB +KEY_ADDRESSBOOK +KEY_AGAIN +KEY_ALTERASE +KEY_ANGLE +KEY_APOSTROPHE +KEY_ARCHIVE +KEY_AUDIO +KEY_AUX +KEY_B +KEY_BACK +KEY_BACKSLASH +KEY_BACKSPACE +KEY_BASSBOOST +KEY_BATTERY +KEY_BLUE +KEY_BLUETOOTH +KEY_BOOKMARKS +KEY_BREAK +KEY_BRIGHTNESS_CYCLE +KEY_BRIGHTNESSDOWN +KEY_BRIGHTNESSUP +KEY_BRIGHTNESS_ZERO +KEY_BRL_DOT10 +KEY_BRL_DOT1 +KEY_BRL_DOT2 +KEY_BRL_DOT3 +KEY_BRL_DOT4 +KEY_BRL_DOT5 +KEY_BRL_DOT6 +KEY_BRL_DOT7 +KEY_BRL_DOT8 +KEY_BRL_DOT9 +KEY_C +KEY_CALC +KEY_CALENDAR +KEY_CAMERA +KEY_CAMERA_FOCUS +KEY_CANCEL +KEY_CAPSLOCK +KEY_CD +KEY_CHANNEL +KEY_CHANNELDOWN +KEY_CHANNELUP +KEY_CHAT +KEY_CLEAR +KEY_CLOSE +KEY_CLOSECD +KEY_COFFEE +KEY_COMMA +KEY_COMPOSE +KEY_COMPUTER +KEY_CONFIG +KEY_CONNECT +KEY_CONTEXT_MENU +KEY_COPY +KEY_CUT +KEY_CYCLEWINDOWS +KEY_D +KEY_DASHBOARD +KEY_DATABASE +KEY_DEL_EOL +KEY_DEL_EOS +KEY_DELETE +KEY_DELETEFILE +KEY_DEL_LINE +KEY_DIGITS +KEY_DIRECTION +KEY_DIRECTORY +KEY_DISPLAY_OFF +KEY_DISPLAYTOGGLE +KEY_DOCUMENTS +KEY_DOLLAR +KEY_DOT +KEY_DOWN +KEY_DVD +KEY_E +KEY_EDIT +KEY_EDITOR +KEY_EJECTCD +KEY_EJECTCLOSECD +KEY_EMAIL +KEY_END +KEY_ENTER +KEY_EPG +KEY_EQUAL +KEY_ESC +KEY_EURO +KEY_EXIT +KEY_F10 +KEY_F11 +KEY_F12 +KEY_F13 +KEY_F14 +KEY_F15 +KEY_F1 +KEY_F16 +KEY_F17 +KEY_F18 +KEY_F19 +KEY_F20 +KEY_F21 +KEY_F22 +KEY_F23 +KEY_F24 +KEY_F2 +KEY_F +KEY_F3 +KEY_F4 +KEY_F5 +KEY_F6 +KEY_F7 +KEY_F8 +KEY_F9 +KEY_FASTFORWARD +KEY_FAVORITES +KEY_FILE +KEY_FINANCE +KEY_FIND +KEY_FIRST +KEY_FN +KEY_FN_1 +KEY_FN_2 +KEY_FN_B +KEY_FN_D +KEY_FN_E +KEY_FN_ESC +KEY_FN_F +KEY_FN_F10 +KEY_FN_F1 +KEY_FN_F11 +KEY_FN_F12 +KEY_FN_F2 +KEY_FN_F3 +KEY_FN_F4 +KEY_FN_F5 +KEY_FN_F6 +KEY_FN_F7 +KEY_FN_F8 +KEY_FN_F9 +KEY_FN_S +KEY_FORWARD +KEY_FORWARDMAIL +KEY_FRAMEBACK +KEY_FRAMEFORWARD +KEY_FRONT +KEY_G +KEY_GAMES +KEY_GOTO +KEY_GRAPHICSEDITOR +KEY_GRAVE +KEY_GREEN +KEY_H +KEY_HANGEUL +KEY_HANJA +KEY_HELP +KEY_HENKAN +KEY_HIRAGANA +KEY_HOME +KEY_HOMEPAGE +KEY_HP +KEY_I +KEY_INFO +KEY_INSERT +KEY_INS_LINE +KEY_ISO +KEY_J +KEY_K +KEY_KATAKANA +KEY_KATAKANAHIRAGANA +KEY_KBDILLUMDOWN +KEY_KBDILLUMTOGGLE +KEY_KBDILLUMUP +KEY_KEYBOARD +KEY_KP0 +KEY_KP1 +KEY_KP2 +KEY_KP3 +KEY_KP4 +KEY_KP5 +KEY_KP6 +KEY_KP7 +KEY_KP8 +KEY_KP9 +KEY_KPASTERISK +KEY_KPCOMMA +KEY_KPDOT +KEY_KPENTER +KEY_KPEQUAL +KEY_KPJPCOMMA +KEY_KPLEFTPAREN +KEY_KPMINUS +KEY_KPPLUS +KEY_KPPLUSMINUS +KEY_KPRIGHTPAREN +KEY_KPSLASH +KEY_L +KEY_LANGUAGE +KEY_LAST +KEY_LEFT +KEY_LEFTALT +KEY_LEFTBRACE +KEY_LEFTCTRL +KEY_LEFTMETA +KEY_LEFTSHIFT +KEY_LINEFEED +KEY_LIST +KEY_LOGOFF +KEY_M +KEY_MACRO +KEY_MAIL +KEY_MAX +KEY_MEDIA +KEY_MEDIA_REPEAT +KEY_MEMO +KEY_MENU +KEY_MESSENGER +KEY_MHP +KEY_MINUS +KEY_MODE +KEY_MOVE +KEY_MP3 +KEY_MSDOS +KEY_MUHENKAN +KEY_MUTE +KEY_N +KEY_NEW +KEY_NEWS +KEY_NEXT +KEY_NEXTSONG +KEY_NUMERIC_0 +KEY_NUMERIC_1 +KEY_NUMERIC_2 +KEY_NUMERIC_3 +KEY_NUMERIC_4 +KEY_NUMERIC_5 +KEY_NUMERIC_6 +KEY_NUMERIC_7 +KEY_NUMERIC_8 +KEY_NUMERIC_9 +KEY_NUMERIC_POUND +KEY_NUMERIC_STAR +KEY_NUMLOCK +KEY_O +KEY_OK +KEY_OPEN +KEY_OPTION +KEY_P +KEY_PAGEDOWN +KEY_PAGEUP +KEY_PASTE +KEY_PAUSE +KEY_PAUSECD +KEY_PC +KEY_PHONE +KEY_PLAY +KEY_PLAYCD +KEY_PLAYER +KEY_PLAYPAUSE +KEY_POWER +KEY_POWER2 +KEY_PRESENTATION +KEY_PREVIOUS +KEY_PREVIOUSSONG +KEY_PRINT +KEY_PROG1 +KEY_PROG2 +KEY_PROG3 +KEY_PROG4 +KEY_PROGRAM +KEY_PROPS +KEY_PVR +KEY_Q +KEY_QUESTION +KEY_R +KEY_RADIO +KEY_RECORD +KEY_RED +KEY_REDO +KEY_REFRESH +KEY_REPLY +KEY_RESERVED +KEY_RESTART +KEY_REWIND +KEY_RFKILL +KEY_RIGHT +KEY_RIGHTALT +KEY_RIGHTBRACE +KEY_RIGHTCTRL +KEY_RIGHTMETA +KEY_RIGHTSHIFT +KEY_RO +KEY_S +KEY_SAT +KEY_SAT2 +KEY_SAVE +KEY_SCALE +KEY_SCREEN +KEY_SCROLLDOWN +KEY_SCROLLLOCK +KEY_SCROLLUP +KEY_SEARCH +KEY_SELECT +KEY_SEMICOLON +KEY_SEND +KEY_SENDFILE +KEY_SETUP +KEY_SHOP +KEY_SHUFFLE +KEY_SLASH +KEY_SLEEP +KEY_SLOW +KEY_SOUND +KEY_SPACE +KEY_SPELLCHECK +KEY_SPORT +KEY_SPREADSHEET +KEY_STOP +KEY_STOPCD +KEY_SUBTITLE +KEY_SUSPEND +KEY_SWITCHVIDEOMODE +KEY_SYSRQ +KEY_T +KEY_TAB +KEY_TAPE +KEY_TEEN +KEY_TEXT +KEY_TIME +KEY_TITLE +KEY_TUNER +KEY_TV +KEY_TV2 +KEY_TWEN +KEY_U +KEY_UNDO +KEY_UNKNOWN +KEY_UP +KEY_UWB +KEY_V +KEY_VCR +KEY_VCR2 +KEY_VENDOR +KEY_VIDEO +KEY_VIDEO_NEXT +KEY_VIDEOPHONE +KEY_VIDEO_PREV +KEY_VOICEMAIL +KEY_VOLUMEDOWN +KEY_VOLUMEUP +KEY_W +KEY_WAKEUP +KEY_WIMAX +KEY_WLAN +KEY_WORDPROCESSOR +KEY_WPS_BUTTON +KEY_WWW +KEY_X +KEY_XFER +KEY_Y +KEY_YELLOW +KEY_YEN +KEY_Z +KEY_ZENKAKUHANKAKU +KEY_ZOOM +KEY_ZOOMIN +KEY_ZOOMOUT +KEY_ZOOMRESET + diff --git a/sapphire-remote-6.6/dkms.conf b/sapphire-remote-6.6/dkms.conf new file mode 100644 index 0000000..7c4066e --- /dev/null +++ b/sapphire-remote-6.6/dkms.conf @@ -0,0 +1,12 @@ +PACKAGE_VERSION="6.6" + +# Items below here should not have to change with each driver version +PACKAGE_NAME="sapphire-remote" +MAKE[0]="make -C ${kernel_source_dir} SUBDIRS=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build modules" +CLEAN="make -C ${kernel_source_dir} SUBDIRS=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build clean" + +BUILT_MODULE_NAME[0]="sapphire" +DEST_MODULE_LOCATION[0]="/extra/" + +AUTOINSTALL=yes +REMAKE_INITRD=no diff --git a/sapphire-remote-6.6/extract_keydefs.sh b/sapphire-remote-6.6/extract_keydefs.sh new file mode 100755 index 0000000..865db6f --- /dev/null +++ b/sapphire-remote-6.6/extract_keydefs.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# +# Extract button and key definitions from sapphire.h and input.h: +# + +function parse_sapphire_h(){ + echo + echo "## buttons, macros, and other definitions extracted from $1:" + echo "##" + gawk ' + ($2 == "BEGIN_PARSING"){c=1;next} + (c == 0){next} + /^ [XS]APPHIRE_([A-Z0-9_]+)\>([ ]+)= 0x/{printf "B[%s]=%s\n",$1,$3;next} + /^ KEY_MACRO_[0-7]\>([ ]+)= 0x/{printf "B[%s]=0x%08x\n",$1,strtonum($3)} + /^ KEY_([A-Z0-9_]+)\>([ ]+)= 0x/{printf "K[%s]=0x%08x\n",$1,strtonum($3);next} + /^ ([A-Z0-9_]+)\>([ ]+)= 0x/{printf "M[%s]=0x%08x\n",$1,strtonum($3);next} + /^[}];/{if (c == 1) nextfile} + ' < "$1" +} + +function parse_input_h(){ + echo + echo "## keycodes extracted from $1:" + echo "##" + gawk ' /^#define KEY_([A-Z0-9_]+)\>([ ]+)((0x[0-9a-fA-F]+)|([0-9]+))\>/{ + printf "K[%s]=0x%08x\n",$2,strtonum($3)} + ' < "$1" | grep -v KEY_RESERVED +} + +parse_input_h "$1" && parse_sapphire_h "$2" + diff --git a/sapphire-remote-6.6/keymap.default b/sapphire-remote-6.6/keymap.default new file mode 100644 index 0000000..ed0dcab --- /dev/null +++ b/sapphire-remote-6.6/keymap.default @@ -0,0 +1,100 @@ +# +# Default keymap from sapphire.c v3.0. +# +# Edit this as you please, keeping in mind that spaces are NOT permitted +# within each column. +# +# Eg. this is INCORRECT: KEY_F15 | ALT +# whereas this is good: KEY_F15|ALT +# +# Use the provided sapphire_keymap.sh script to install these +# mappings at runtime in place of the driver's built-in mappings. +# Keep your own copy of this file separate from the driver source, +# so that subsequent changes in the driver mappings won't clobber +# your own definitions here. +# +# As in the example above, "KeyCode" values can be combined with +# any mix of these "modifier" keys: SHIFT, ALT, CTRL, and/or META. +# +# Eg. SAPPHIRE_PLAY KEY_P|CTRL NO_REPEAT ## Mythtv Play/Pause function +# Or: SAPPHIRE_PLAY KEY_P|CTRL|ALT NO_REPEAT ## something else +# +# The "SpecialOptions" field is used to indicate the desired auto-repeat rate +# for each button individually. The choices are: +# +# NO_REPEAT - the button will not auto-repeat when held down. +# SLOW_REPEAT - the button will repeat slowly. +# RAMP_REPEAT - the button will repeat slowly at first, but speed up if held down. +# LONGKEY - no auto-repeat: special behaviour described below. +# +# The special value "LONGKEY" can be used in "SpecialOptions" +# insted of an auto-repeat setting. This causes a button on the remote +# to have two separate functions, depending upon how quickly it is released. +# +# A short-press/release gets the regular "KeyCode" value, but if the button +# is held down for one second or longer, then an alternate value can be send instead. +# Just combine the desired alternate keycode with the LONGKEY tag. +# +# Eg. SAPPHIRE_MUTE KEY_F9 LONGKEY|KEY_F15 ## Mute (tap) or Audiosync (hold) +# +# ButtonName KeyCode SpecialOptions ## Comments +# +SAPPHIRE_UP KEY_UP RAMP_REPEAT ## up (CUSTOM) +SAPPHIRE_DOWN KEY_DOWN RAMP_REPEAT ## down (CUSTOM) +SAPPHIRE_RIGHT KEY_RIGHT SLOW_REPEAT ## right +SAPPHIRE_LEFT KEY_LEFT SLOW_REPEAT ## left +SAPPHIRE_ENTEROK KEY_ENTER LONGKEY|KEY_F15 ## Select (tap) or Audiosync (hold) +SAPPHIRE_BACK KEY_ESC SLOW_REPEAT ## Back +SAPPHIRE_PLAY KEY_P|CTRL NO_REPEAT ## Play/Pause +SAPPHIRE_PAUSE KEY_P NO_REPEAT ## Play/Pause +SAPPHIRE_VOLUP KEY_RIGHTBRACE FAST_REPEAT ## Volume Up +SAPPHIRE_VOLDOWN KEY_LEFTBRACE FAST_REPEAT ## Volume Down +SAPPHIRE_CHUP KEY_PAGEUP SLOW_REPEAT ## channel up (CUSTOM) +SAPPHIRE_CHDOWN KEY_PAGEDOWN SLOW_REPEAT ## channel down (CUSTOM) +SAPPHIRE_MUTE KEY_F9 LONGKEY|KEY_F15 ## Mute (tap) or Audiosync (hold) +SAPPHIRE_RECORD KEY_R NO_REPEAT ## Record/Delete (CUSTOM) +SAPPHIRE_FWD KEY_DOT NO_REPEAT ## FFwd +SAPPHIRE_REW KEY_COMMA NO_REPEAT ## Rewind +SAPPHIRE_ANGLE KEY_W NO_REPEAT ## Adjust Fill +SAPPHIRE_SAP KEY_W|CTRL NO_REPEAT ## Toggle Aspect Ratio +SAPPHIRE_DVDMENU KEY_M NO_REPEAT ## Menu +SAPPHIRE_INFOEPG KEY_I NO_REPEAT ## Info +SAPPHIRE_TAB KEY_END SLOW_REPEAT ## Commskip Fwd +SAPPHIRE_BACKTAB KEY_HOME SLOW_REPEAT ## Commskip Rev +SAPPHIRE_RADIO KEY_F7|ALT NO_REPEAT ## Signal Monitor +SAPPHIRE_LASTCH KEY_H NO_REPEAT ## previous chan +SAPPHIRE_LANGUAGE KEY_EQUAL|SHIFT NO_REPEAT ## Next Audio Track +SAPPHIRE_TELETEXTCC KEY_T NO_REPEAT ## Toggle Closed-Caption +SAPPHIRE_SUBTITLE KEY_F17|ALT NO_REPEAT ## Toggle subtitle (CUSTOM) +SAPPHIRE_HOMEHOUSE KEY_F18 NO_REPEAT ## jump to MainMenu (CUSTOM jumppoint) +SAPPHIRE_BLUEVIDEOS KEY_F14 NO_REPEAT ## MythVideo (CUSTOM jumppoint) +SAPPHIRE_LIVETV KEY_F16 NO_REPEAT ## Program Guide (CUSTOM jumppoint) +SAPPHIRE_REDDVDVCD KEY_F13 LONGKEY|KEY_F13|ALT ## PlayDVD/DVDMenu (CUSTOM jumppoints) +SAPPHIRE_YELLOWPICTURES KEY_F14|ALT NO_REPEAT ## Watch Recordings (CUSTOM jumppoint) +SAPPHIRE_1 KEY_1 SLOW_REPEAT ## 1 +SAPPHIRE_2 KEY_2 SLOW_REPEAT ## 2 +SAPPHIRE_3 KEY_3 SLOW_REPEAT ## 3 +SAPPHIRE_4 KEY_4 SLOW_REPEAT ## 4 +SAPPHIRE_5 KEY_5 SLOW_REPEAT ## 5 +SAPPHIRE_6 KEY_6 SLOW_REPEAT ## 6 +SAPPHIRE_7 KEY_7 SLOW_REPEAT ## 7 +SAPPHIRE_8 KEY_8 SLOW_REPEAT ## 8 +SAPPHIRE_9 KEY_9 SLOW_REPEAT ## 9 +SAPPHIRE_0 KEY_0 SLOW_REPEAT ## 0 +SAPPHIRE_STOP KEY_MINUS LONGKEY|KEY_MINUS|SHIFT ## dash/underscore symbols +SAPPHIRE_POWER KEY_F2 NO_REPEAT ## start frontend (CUSTOM desktop script) +SAPPHIRE_CLEAR KEY_MACRO_0 NO_REPEAT ## Adjust audiosync by 90msecs (see below) +SAPPHIRE_GREENMUSIC KEY_A NO_REPEAT ## Adjust time stretch + +# +# Now for the macro definitions: each macro sends a list of up to eight keycodes: +# +KEY_MACRO_0 KEY_F15 KEY_DOWN KEY_LEFT ## Adjust AudioSync by +90msecs +KEY_MACRO_1 +KEY_MACRO_2 +KEY_MACRO_2 +KEY_MACRO_3 +KEY_MACRO_4 +KEY_MACRO_5 +KEY_MACRO_6 +KEY_MACRO_7 diff --git a/sapphire-remote-6.6/sapphire.c b/sapphire-remote-6.6/sapphire.c new file mode 100644 index 0000000..b7414b0 --- /dev/null +++ b/sapphire-remote-6.6/sapphire.c @@ -0,0 +1,826 @@ +/* + * sapphire.c + * + * Copyright Mark Lord , 2012-2015. + * http://rtr.ca/sapphire_remote/ + * + * This is a HID driver for the TopSeed Cyberlink receiver + * when paired with the "Sapphire TheatriX" remote control. + * + * This replaces the in-kernel "hid-topseed" driver, + * and is tailored specifically for use with Mythtv + * without need of any external configuration or LIRC etc. + * + * Buttons can have variable repeat rates, distinguish between short + * and long presses, and can send macros instead of just a single keycode. + * + * Wake-from-suspend works by pressing the remote's "Power" button, + * after correctly configuring /proc/acpi/wakeup before suspending. + * + * User-defined keymaps and macros can be (re)configured on the fly + * via a /proc/ interface, using the supplied sapphire_keymap.sh script. + * + * This program 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; specifically version 2 of the License (GPLv2). + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sapphire.h" + +#define SAPPHIRE_NAME "sapphire" +#define SAPPHIRE_VERSION "6.6" + +/* + * "LONGKEY": Any button can be used to generate a second, different keycode, + * by setting the "repeats" value to "LONGKEY | KEY_XXX" for the second keycode. + * When that button is then tapped (quick press/release), the regular keycode is sent. + * But when the button is pressed/held for one second, the second keycode is sent + * instead of the regular keycode. This allows buttons to have dual functionalty, + * to solve some sticky issues. + * + * Eg. by default below, the MUTE button is set up this way: a short tap of MUTE + * sends the "Mute" command, but holding it down longer will instead activate + * the "Adjust Audio Sync" pop-up. Very, VERY handy, that. + * + * EDIT the "keycode" and "repeats" columns below to customize the keymap to your needs: + */ +#define SAPPHIRE_NUM_BUTTONS 46 /* The Sapphire remote sends 46 unique button codes */ +#define SAPPHIRE_KEYMAP_SIZE (2 * SAPPHIRE_NUM_BUTTONS) /* Extra room for a full XAPPHIRE_* set */ +static struct sapphire_map { + unsigned int button; /* raw data from sensor */ + unsigned int keycode; /* keyboard value to pass to mythtv, or "0" for nothing */ + unsigned int repeats; /* auto-repeat setting */ +} sapphire_keymap[SAPPHIRE_KEYMAP_SIZE] = { + /* button keycode repeats mythtv action */ + /* ========== ======== =========== ============= */ + {SAPPHIRE_UP , KEY_UP , RAMP_REPEAT }, /* up (CUSTOM) */ + {SAPPHIRE_DOWN , KEY_DOWN , RAMP_REPEAT }, /* down (CUSTOM) */ + {SAPPHIRE_RIGHT , KEY_RIGHT , SLOW_REPEAT }, /* right */ + {SAPPHIRE_LEFT , KEY_LEFT , SLOW_REPEAT }, /* left */ + {SAPPHIRE_ENTEROK , KEY_ENTER , LONGKEY|KEY_F15 }, /* Select (tap) or Audiosync (hold) */ + {SAPPHIRE_BACK , KEY_ESC , SLOW_REPEAT }, /* Back */ + {SAPPHIRE_PLAY , KEY_P|CTRL , NO_REPEAT }, /* Play/Pause */ + {SAPPHIRE_PAUSE , KEY_P , NO_REPEAT }, /* Play/Pause */ + {SAPPHIRE_VOLUP , KEY_RIGHTBRACE , FAST_REPEAT }, /* Volume Up */ + {SAPPHIRE_VOLDOWN , KEY_LEFTBRACE , FAST_REPEAT }, /* Volume Down */ + {SAPPHIRE_CHUP , KEY_PAGEUP , SLOW_REPEAT }, /* channel up (CUSTOM) */ + {SAPPHIRE_CHDOWN , KEY_PAGEDOWN , SLOW_REPEAT }, /* channel down (CUSTOM) */ + {SAPPHIRE_MUTE , KEY_F9 , LONGKEY|KEY_F15 }, /* Mute (tap) or Audiosync (hold) */ + {SAPPHIRE_RECORD , KEY_R , NO_REPEAT }, /* Record/Delete (CUSTOM) */ + {SAPPHIRE_FWD , KEY_DOT , NO_REPEAT }, /* FFwd */ + {SAPPHIRE_REW , KEY_COMMA , NO_REPEAT }, /* Rewind */ + {SAPPHIRE_ANGLE , KEY_W , NO_REPEAT }, /* Adjust Fill */ + {SAPPHIRE_SAP , KEY_W|CTRL , NO_REPEAT }, /* Toggle Aspect Ratio */ + {SAPPHIRE_DVDMENU , KEY_M , LONGKEY|KEY_F13|ALT }, /* Menu/DVDMenu (CUSTOM) */ + {SAPPHIRE_INFOEPG , KEY_I , NO_REPEAT }, /* Info */ + {SAPPHIRE_TAB , KEY_END , SLOW_REPEAT }, /* Commskip Fwd */ + {SAPPHIRE_BACKTAB , KEY_HOME , SLOW_REPEAT }, /* Commskip Rev */ + {SAPPHIRE_RADIO , KEY_F7|ALT , NO_REPEAT }, /* Signal Monitor */ + {SAPPHIRE_LASTCH , KEY_H , NO_REPEAT }, /* previous chan */ + {SAPPHIRE_LANGUAGE , KEY_EQUAL|SHIFT, NO_REPEAT }, /* Next Audio Track */ + {SAPPHIRE_TELETEXTCC , KEY_T , NO_REPEAT }, /* Toggle Closed-Caption */ + {SAPPHIRE_SUBTITLE , KEY_F17|ALT , NO_REPEAT }, /* Toggle subtitle (CUSTOM) */ + {SAPPHIRE_HOMEHOUSE , KEY_F18 , NO_REPEAT }, /* jump to MainMenu (CUSTOM jumppoint) */ + {SAPPHIRE_BLUEVIDEOS , KEY_F14 , NO_REPEAT }, /* MythVideo (CUSTOM jumppoint) */ + {SAPPHIRE_LIVETV , KEY_F16 , NO_REPEAT }, /* Program Guide (CUSTOM jumppoint) */ + {SAPPHIRE_REDDVDVCD , KEY_F13 , LONGKEY|KEY_F13|ALT }, /* PlayDVD/DVDMenu (CUSTOM jumppoints) */ + {SAPPHIRE_YELLOWPICTURES, KEY_F14|ALT , NO_REPEAT }, /* Watch Recordings (CUSTOM jumppoint) */ + {SAPPHIRE_1 , KEY_1 , SLOW_REPEAT }, /* 1 */ + {SAPPHIRE_2 , KEY_2 , SLOW_REPEAT }, /* 2 */ + {SAPPHIRE_3 , KEY_3 , SLOW_REPEAT }, /* 3 */ + {SAPPHIRE_4 , KEY_4 , SLOW_REPEAT }, /* 4 */ + {SAPPHIRE_5 , KEY_5 , SLOW_REPEAT }, /* 5 */ + {SAPPHIRE_6 , KEY_6 , SLOW_REPEAT }, /* 6 */ + {SAPPHIRE_7 , KEY_7 , SLOW_REPEAT }, /* 7 */ + {SAPPHIRE_8 , KEY_8 , SLOW_REPEAT }, /* 8 */ + {SAPPHIRE_9 , KEY_9 , SLOW_REPEAT }, /* 9 */ + {SAPPHIRE_0 , KEY_0 , SLOW_REPEAT }, /* 0 */ + {SAPPHIRE_STOP , KEY_MINUS , LONGKEY|KEY_MINUS|SHIFT }, /* dash/underscore symbols */ + {SAPPHIRE_POWER , KEY_F2 , NO_REPEAT }, /* start frontend (CUSTOM desktop script) */ + {SAPPHIRE_CLEAR , KEY_MACRO_0 , NO_REPEAT }, /* Adjust audiosync by 90msecs */ + {SAPPHIRE_GREENMUSIC , KEY_A , NO_REPEAT }, /* Adjust time stretch */ +}; + +/* + * Up to 8 key "macros" can be defined here. + * These are mapped using the MACRO_[0-7] enums above, + * and each can consist of up to 8 KEY_* values, + * with zeros for any trailing unused values. + * The special KEY_DELAY can be used anywhere inside a macro + * to inject a half-second pause before continuing. + */ +static unsigned int sapphire_macros[8][8] = { + /* KEY_MACRO_0 */ {KEY_F15,KEY_DOWN,KEY_LEFT,0,}, /* adjust audiosync by 90msec */ + /* KEY_MACRO_1 */ {0,}, + /* KEY_MACRO_2 */ {0,}, + /* KEY_MACRO_3 */ {0,}, + /* KEY_MACRO_4 */ {0,}, + /* KEY_MACRO_5 */ {0,}, + /* KEY_MACRO_6 */ {0,}, + /* KEY_MACRO_7 */ {0,} +}; + +static struct sapphire_dev { + spinlock_t lock; + struct input_dev *idev; + struct timer_list key_timer; + u64 last_event; + unsigned long delay_end; + unsigned long next_wakeup; + unsigned int ready; + unsigned int event_count; + unsigned int down_key; /* button currently being pressed */ + unsigned int next_repeat; /* repeat interval for down_key */ + unsigned int last_key; + unsigned int dualkey_short; /* dual-function button "short press" value */ + unsigned int dualkey_long; /* dual-function button "long press" value */ + unsigned int *delayed_macro; /* macro to be resumed after a 1/2 second delay */ + unsigned int delayed_next; /* next button within the macro to be resumed */ + unsigned int raw_key; /* a completely untranslated button being pressed */ +} sapphire_dev, *dev = &sapphire_dev; + +enum { + RAMPING = 0x80000000, /* flag to indicate current dev->down_key is using RAMP_REPEAT */ + RAMPING_RATE = HZ / 40, /* amount to speed up by after each repeat */ + RAMPING_MAX = HZ / 10, /* max repeats/sec when RAMPING */ + MODIFIERS = CTRL|SHIFT|ALT|META, /* convenient mask of all modifier keys */ + PRESSED = 1, /* for use with input_event() */ + RELEASED = 0, /* for use with input_event() */ +}; + +static unsigned int ramping_rate = RAMPING_RATE; +static unsigned int ramping_max = RAMPING_MAX; + +/* + * Translate a [XS]APPHIRE_ "button" into a KEY_ "keycode" or "macro". + */ +static unsigned int sapphire_remap(unsigned int button, unsigned int *repeats) +{ + struct sapphire_map *map; + + for (map = sapphire_keymap; map != (sapphire_keymap + SAPPHIRE_KEYMAP_SIZE); ++map) { + if (map->button == button && map->keycode) { + *repeats = map->repeats; + return map->keycode; + } + } + *repeats = NO_REPEAT; + return 0; +} + +/* + * Remember the last key pressed for later retrieval from /proc/sapphire + */ +static void sapphire_save_key(unsigned int key) +{ + if (key) + dev->last_key = key; + dev->event_count++; + dev->last_event = get_jiffies_64(); +} + +/* + * Send "modifier" (CTRL, SHIFT, ALT, META) press/release events to the Linux input system. + */ +static void sapphire_send_modifiers(unsigned int modifiers, int pressed_released) +{ + if (modifiers & CTRL) + input_event(dev->idev, EV_KEY, KEY_LEFTCTRL, pressed_released); + if (modifiers & SHIFT) + input_event(dev->idev, EV_KEY, KEY_LEFTSHIFT, pressed_released); + if (modifiers & ALT) + input_event(dev->idev, EV_KEY, KEY_LEFTALT, pressed_released); + if (modifiers & META) + input_event(dev->idev, EV_KEY, KEY_LEFTMETA, pressed_released); +} + +/* + * Start/modify the keypress/repeat timer: + */ +static void sapphire_set_timer (unsigned long timeout) +{ + if (timeout) + dev->next_wakeup = jiffies + timeout; + else if (!time_before(jiffies, dev->next_wakeup)) + dev->next_wakeup = jiffies + 2; + mod_timer(&dev->key_timer, dev->next_wakeup); +} + +static void sapphire_send_key(unsigned int key, unsigned int repeat_delay); + +static int sapphire_send_macro (unsigned int *macro, unsigned int next) +{ + unsigned int i; + for (i = next; i <= 7 && macro[i]; ++i) { + unsigned int key = macro[i]; + if (key == KEY_DELAY) { + if (i < 7 && macro[i + 1]) { + dev->delayed_macro = macro; + dev->delayed_next = i + 1; + sapphire_set_timer(HZ / 2); + return 1; + } + break; + } + sapphire_send_key(key, NO_REPEAT); + } + return 0; +} + +/* + * Send a translated key/macro to the Linux input system. + */ +static void sapphire_send_key(unsigned int key, unsigned int repeat_delay) +{ + if (!key) + return; + sapphire_save_key(key); + if ((key & KEY_MACRO_0) == KEY_MACRO_0) { + unsigned int n = key & (KEY_MACRO_0 ^ KEY_MACRO_7); + if (sapphire_send_macro(sapphire_macros[n], 0)) + return; + } else { + unsigned int modifiers = key & MODIFIERS; + if (modifiers) { + key &= ~MODIFIERS; + sapphire_send_modifiers(modifiers, PRESSED); + } + input_event(dev->idev, EV_KEY, key, PRESSED); + input_event(dev->idev, EV_KEY, key, RELEASED); + if (modifiers) + sapphire_send_modifiers(modifiers, RELEASED); + input_sync(dev->idev); + } + if (repeat_delay) + sapphire_set_timer(repeat_delay); +} + +/* + * Timer callback function, used to handle repeats and "LONGKEYs". + */ +static void sapphire_key_timeout(unsigned long data) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + dev->ready = 1; + /* + * FIXME: not sure if this can ever happen for real. + * + * In theory, we could get blocked on the spin_lock_irqsave() above + * while another part of the driver is busy modifying the timer value + * in response to a new button event. This could lead to us handling + * the new event immediately rather than after the newly set timeout. + * Guard against it by verifying that "next_wakeup" has really arrived. + */ + if (time_before(jiffies, dev->next_wakeup)) { + /* Spinlock race on an old timer value; skip this wakeup. */ + printk(KERN_INFO "%s: workaround activated: jiffies=%lu wakeup=%lu\n", __func__, jiffies, dev->next_wakeup); + sapphire_set_timer(0); /* reset the timer with same next_wakeup value */ + } else if (dev->delayed_macro) { + unsigned int *macro = dev->delayed_macro; + dev->delayed_macro = NULL; + sapphire_send_macro(macro, dev->delayed_next); + } else if (dev->down_key) { + unsigned int next_repeat = dev->next_repeat & ~RAMPING; + if (dev->next_repeat & RAMPING) { + /* Gradually ramp-up the repeat rate for this key */ + if (next_repeat > ramping_max) { + next_repeat -= ramping_rate; + if (next_repeat < ramping_max) + next_repeat = ramping_max; + } + dev->next_repeat = next_repeat | RAMPING; + } + sapphire_send_key(dev->down_key, next_repeat); + } else if (dev->dualkey_short) { + sapphire_send_key(dev->dualkey_long, NO_REPEAT); + dev->dualkey_long = dev->dualkey_short = 0; + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* + * Pass along a button press event, setting up the repeat mechanism appropriately. + */ +static void sapphire_handle_key (unsigned int key, unsigned int repeats) +{ + unsigned int repeat_delay = NO_REPEAT, next_repeat = 0; + + switch (repeats) { + case RAWKEY: + del_timer(&dev->key_timer); + input_event(dev->idev, EV_KEY, key, PRESSED); + input_sync(dev->idev); + dev->raw_key = key; + return; + case SLOW_REPEAT: + repeat_delay = HZ/2; + next_repeat = HZ/SLOW_REPEAT; + break; + case FAST_REPEAT: + repeat_delay = HZ/3; + next_repeat = HZ/FAST_REPEAT; + break; + case RAMP_REPEAT: + repeat_delay = (HZ/2) - (HZ/6); + next_repeat = (HZ/3) | RAMPING; + break; + default: /* LONGKEY or NO_REPEAT */ + if (repeats & LONGKEY) { + /* + * We don't know if it's a short or long press yet. + * Start the key_timer to sort things out. + */ + dev->dualkey_short = key; + dev->dualkey_long = repeats & ~LONGKEY; + sapphire_set_timer(HZ); + dev->delay_end = dev->next_wakeup; + return; + } + } + sapphire_send_key(key, repeat_delay); + if (next_repeat ) { + dev->down_key = key; + dev->next_repeat = next_repeat; + } +} + +/* + * Handle a low-level press or release event, + */ +static void sapphire_event(u32 data) +{ + /* Treat each new event as a "release" for any previous "press" of a button */ + if (dev->down_key || dev->dualkey_short) { + dev->down_key = 0; + del_timer(&dev->key_timer); + if (dev->dualkey_short) { + if (time_before(jiffies, dev->delay_end)) + sapphire_send_key(dev->dualkey_short, NO_REPEAT); + else + sapphire_send_key(dev->dualkey_long, NO_REPEAT); + dev->dualkey_long = dev->dualkey_short = 0; + } + } + if (dev->raw_key) { + input_event(dev->idev, EV_KEY, dev->raw_key, RELEASED); + input_sync(dev->idev); + dev->raw_key = 0; + } + /* Key "press" events are > 0x10; nothing required here for "release" events (<= 0x10) */ + if (data > 0x10) { /* key "press" event? */ + unsigned int repeats, key = sapphire_remap(data, &repeats); + if (key) { + sapphire_handle_key(key, repeats); + return; + } + printk(KERN_INFO "%s: unmapped button: 0x%08x\n", __func__, data); + } + sapphire_save_key(0); /* timestamp the event in /proc/, for shutdown timing.. */ +} + +/* + * This gets called each time the IR receiver "receives" something from a remote control. + * The "size" of "raw_data" bytes received varies depending upon the button pressed, + * but we need only the first four bytes to uniquely distinguish all buttons from each other. + * The exceptions are the "OK" and "Enter" buttons, which send 100% identical data, + * so we cannot distinguish those from each other. All other buttons send unique data. + */ +static int sapphire_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *raw_data, int size) +{ + unsigned long flags; + u32 data = 0; + int i; + + if (!(hdev->claimed & HID_CLAIMED_INPUT)) + return 0; + if (dev->ready) { + if (size > 4) /* Use only the first four bytes or less */ + size = 4; + for (i = size; i > 0; ) + data = (data << 8) | raw_data[--i]; + spin_lock_irqsave(&dev->lock, flags); + sapphire_event(data); + spin_unlock_irqrestore(&dev->lock, flags); + } + return -1; /* event was handled (used to return "1" here) */ +} + +/* + * This hook provides a means for other drivers to feed + * their button press/release events through this driver, + * to take advantage of sapphire's macros, variable repeats, + * long/short press mappings, modifier keys (CTRL,ALT,SHIFT,META), + * and the /proc/sapphire interface. + * + * The incoming data must be pre-translated to sapphire buttons + * by the caller, sending non-zero [XS]APPHIRE_* codes for presses, + * and zero for button release events. + * + * There is a "glue.c" driver available demonstrating this, + * in conjunction with a tiny patch to cx88-input.c. + * This allows cx88 remote controls to feed through sapphire.c + * and behave similarly to the native Sapphire remote-control. + */ +void sapphire_relay (u32 data) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + sapphire_event(data); + spin_unlock_irqrestore(&dev->lock, flags); +} +EXPORT_SYMBOL(sapphire_relay); + +/* + * Parse and save a single macro definition written via /proc/ + */ +static int sapphire_store_macro(char *buf) +{ + unsigned long flags; + unsigned int vals[8] = {0,}, macro, n; + char excess; + int count; + + count = sscanf(buf, "%x %x %x %x %x %x %x %x %x %c", ¯o, + &vals[0], &vals[1], &vals[2], &vals[3], + &vals[4], &vals[5], &vals[6], &vals[7], &excess); + if (count < 1 || count > 9 || (macro & KEY_MACRO_0) != KEY_MACRO_0) + return -EINVAL; + spin_lock_irqsave(&dev->lock, flags); + n = macro & (KEY_MACRO_0 ^ KEY_MACRO_7); + memcpy(sapphire_macros[n], vals, sizeof(vals)); + spin_unlock_irqrestore(&dev->lock, flags); + return 0; +} + +/* + * Parse and save a single button mapping written via /proc/ + */ +static int sapphire_store_keymap(struct sapphire_map *map, char *buf) +{ + unsigned long flags; + unsigned int vals[3] = {0,}; + char excess; + + if (3 != sscanf(buf, "%x %x %x %c", &vals[0], &vals[1], &vals[2], &excess)) + return -EINVAL; + spin_lock_irqsave(&dev->lock, flags); + map->button = vals[0]; + map->keycode = vals[1]; + map->repeats = vals[2]; + spin_unlock_irqrestore(&dev->lock, flags); + return 0; +} + +/* + * Provide useful info via /proc/ + */ +static int sapphire_oldproc_read(char *buf, char **start, off_t offset, int count, int *eof, void *data) +{ + u64 last_event, now, elapsed; + unsigned long flags; + unsigned int len, last_key = 0, secs = ~0, event_count; + + spin_lock_irqsave(&dev->lock, flags); + event_count = dev->event_count; + last_event = dev->last_event; + last_key = dev->last_key; + spin_unlock_irqrestore(&dev->lock, flags); + + now = get_jiffies_64(); + elapsed = now - last_event; + secs = ((unsigned int)elapsed) / HZ; + len = sprintf(buf, "event_count %u\nlast_key 0x%08x\nelapsed_secs %u\n", event_count, last_key, secs); + *eof = 1; + return len; +} + +/* + * Handles writes to /proc/ for altering key/macro mappings on the fly, + * and also for injecting keycodes from scripts etc. + */ +static int sapphire_oldproc_write(struct file *file, const char __user *ubuf, unsigned long count, void *data) +{ + static const char SENDKEY[] = "SENDKEY "; + static const char REPEAT_RATE[] = "REPEAT_RATE "; + unsigned int len = count, val, ret = 0; + u8 *buf = kmalloc(len + 2, GFP_KERNEL), *line, *eol; + struct sapphire_map *map; + + if (!buf) + return -ENOMEM; + if (copy_from_user(buf, ubuf, len)) { + kfree(buf); + return -EFAULT; + } + if (len == 0 || buf[len - 1] != '\n') + buf[len++] = '\n'; + buf[len++] = '\0'; + for (line = buf; line[0]; ) { + int sendkey = 0; + for (eol = line; *eol != '\n'; ++eol); + *eol = '\0'; + if (0 == strncmp(line, SENDKEY, sizeof(SENDKEY)-1)) { + line += sizeof(SENDKEY)-1; + sendkey = 1; + } else if (0 == strncmp(line, REPEAT_RATE, sizeof(REPEAT_RATE)-1)) { + unsigned int rate, max; + line += sizeof(REPEAT_RATE)-1; + if (2 != sscanf(line, "%u %u", &rate, &max)) { + ret = -EINVAL; + break; + } + ramping_rate = HZ / rate; + ramping_max = HZ / max; + goto next_line; + } + if (1 != sscanf(line, "%x", &val)) { + ret = -EINVAL; + break; + } + if (sendkey) { + unsigned long flags; + spin_lock_irqsave(&dev->lock, flags); + sapphire_send_key(val, NO_REPEAT); + spin_unlock_irqrestore(&dev->lock, flags); + } else { + ret = sapphire_store_macro(line); + if (ret) { + ret = -EINVAL; + for (map = sapphire_keymap; map != (sapphire_keymap + SAPPHIRE_KEYMAP_SIZE); ++map) { + if (!map->button || map->button == val) { + ret = sapphire_store_keymap(map, line); + break; + } + } + if (ret) + break; + } + } +next_line: + line = eol + 1; + } + if (ret && line) + printk(KERN_ERR "%s: parse error: \"%s\"\n", __func__, line); + kfree(buf); + return ret ? ret : count; +} + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0)) + +static void sapphire_create_proc_entry (void) +{ + static struct proc_dir_entry *entry; + + entry = create_proc_entry(SAPPHIRE_NAME, S_IFREG|S_IRUGO|S_IWUGO, NULL); + if (entry) { + entry->write_proc = sapphire_oldproc_write; + entry->read_proc = sapphire_oldproc_read; + } +} + +#else /* LINUX_VERSION_CODE */ + +static ssize_t sapphire_newproc_write (struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + return sapphire_oldproc_write(file, buffer, count, PDE_DATA(file_inode(file))); +} + +static ssize_t sapphire_newproc_read (struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + unsigned char *kbuf = (void *)__get_free_page(GFP_KERNEL); + ssize_t len; + int eof = 0; + + if (!kbuf) + return -ENOMEM; + len = sapphire_oldproc_read(kbuf, NULL, 0, PAGE_SIZE - 1, &eof, PDE_DATA(file_inode(file))); + len = simple_read_from_buffer(buffer, count, ppos, kbuf, len); + free_page((long)kbuf); + return len; +} + +static const struct file_operations sapphire_newproc_fops = { + .read = sapphire_newproc_read, + .write = sapphire_newproc_write, + .llseek = default_llseek, +}; + +static void sapphire_create_proc_entry (void) +{ + static struct proc_dir_entry *entry; + entry = proc_create_data(SAPPHIRE_NAME, S_IFREG|S_IRUGO|S_IWUGO, NULL, &sapphire_newproc_fops, dev); +} + +#endif /* LINUX_VERSION_CODE */ + +/* + * Invoked by the kernel each time one of our IR receivers is plugged into a USB port. + * We can/do handle multiple receivers, but there's no point to ever have more than one, + * because the receiver hardware is not keyed to a specific transmitter (remote control). + * + * If buttons are pressed before probe is run, the driver sometimes receives + * a single PRESS event at startup, with no corresponding RELEASE event. + * This is bad. We prevent the stray presses by ignoring all input from the device + * for one second after probe, and then set ready=1 to enable normal processing. + */ +static int sapphire_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret = hid_parse(hdev); + if (ret) + return ret; + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + printk(KERN_INFO SAPPHIRE_NAME ": hw_start failed, err=%d\n", ret); + } else { + /* Eat spurious data from the device for one second after connect */ + unsigned long flags; + spin_lock_irqsave(&dev->lock, flags); + sapphire_event(0); /* clear current downkey state */ + dev->ready = 0; /* ignore input until the timer fires */ + sapphire_set_timer(HZ); + spin_unlock_irqrestore(&dev->lock, flags); + } + return ret; +} + +/* + * Invoked by the kernel each time one of our IR receivers is unplugged from a USB port. + */ +static void sapphire_remove(struct hid_device *hdev) +{ + /* + * We are supposed to do hid_hw_stop() here. + * But some Ubuntu kernels (eg. 3.11.x, 3.2.x) totally kill the device + * when doing this, such that even hid_hw_start() won't revive it. + * Other kernels handle it much better, but picking and choosing + * can be very problematic. So.. + */ + hid_hw_stop(hdev); +} + +#ifdef CONFIG_PM +/* + * The IR receiver becomes non-responsive when subjected to USB autosuspend. + * This is disabled by default at probe() time, but system scripts + * (particularly on suspend/resume) may modify the setting, + * and the result is that the IR receiver could still suffer autosuspend. + * + * So.. when the system does try it, by calling sapphire_suspend() below, + * we reject the attempt (-EBUSY) and also reset the autosuspend setting + * back to "on" again. Unfortunately, this cannot be done directly + * from sapphire_suspend() due to spinlock recursion in the PM code, + * so we schedule a worker to do it from a less-encumbered state. + */ +struct sapphire_pm_work { + struct work_struct work; + struct usb_device *udev; +}; + +static void sapphire_disable_usb_autosuspend(struct work_struct *work) +{ + struct sapphire_pm_work *w = container_of(work, struct sapphire_pm_work, work); + + usb_disable_autosuspend(w->udev); + kfree(w); + printk(KERN_INFO SAPPHIRE_NAME ": disabled USB autosuspend\n"); +} + +static void sapphire_schedule_pm_work(struct hid_device *hdev) +{ + struct sapphire_pm_work *w = kmalloc(sizeof(struct sapphire_pm_work), GFP_ATOMIC); + if (w) { + w->udev = container_of(hdev->dev.parent->parent, struct usb_device, dev); + INIT_WORK(&w->work, sapphire_disable_usb_autosuspend); + schedule_work(&w->work); + } +} + +static int sapphire_suspend(struct hid_device *hdev, pm_message_t message) +{ + if (PMSG_IS_AUTO(message)) { + sapphire_schedule_pm_work(hdev); /* prevent future attempts */ + return -EBUSY; /* prevent _this_ USB autosuspend attempt */ + } + return 0; +} + +static int sapphire_resume(struct hid_device *hdev) +{ + return 0; +} + +#endif + +/* + * Structures used to associate this driver with specific USB device types. + */ +static const struct hid_device_id sapphire_devices[] = { + {HID_USB_DEVICE(0x0766,0x0204)}, + {} +}; +MODULE_DEVICE_TABLE(hid, sapphire_devices); +static struct hid_driver sapphire_driver = { + .name = SAPPHIRE_NAME, + .id_table = sapphire_devices, + .raw_event = sapphire_raw_event, + .probe = sapphire_probe, + .remove = sapphire_remove, +#ifdef CONFIG_PM + .suspend = sapphire_suspend, + .resume = sapphire_resume, +#endif +}; + +/* + * We don't use the input_dev provided by the hid layer, + * because (1) it provides two of them (mouse & keyboard), + * and (2) we need an input_dev even if no receiver is plugged in. + * Here we allocate a single global input_dev for use by the driver. + */ +static int sapphire_create (void) +{ + int keycode, err; + + memset(dev, 0, sizeof(*dev)); /* paranoia */ + spin_lock_init(&dev->lock); + init_timer(&dev->key_timer); + dev->key_timer.function = sapphire_key_timeout; + dev->key_timer.data = (unsigned long)dev; + + dev->idev = input_allocate_device(); + if (!dev->idev) { + printk(KERN_ERR "%s: input_allocate_device() failed\n", __func__); + return -ENOMEM; + } + dev->idev->name = SAPPHIRE_NAME; + dev->idev->phys = "none"; + dev->idev->evbit[0] = BIT_MASK(EV_KEY); + for (keycode = 1; keycode < KEY_MAX; ++keycode) + __set_bit(keycode, dev->idev->keybit); + err = input_register_device(dev->idev); + if (err) { + printk(KERN_ERR "%s: input_register_device() failed, ret=%d\n", __func__, err); + input_free_device(dev->idev); + dev->idev = NULL; + return err; + } + return 0; +} + +/* + * This is the inverse of sapphire_create_inputdev(). + */ +static void sapphire_destroy (void) +{ + del_timer_sync(&dev->key_timer); + input_unregister_device(dev->idev); + input_free_device(dev->idev); +} + +/* + * Invoked by the kernel when this module is loaded. + */ +static int __init sapphire_init(void) +{ + int ret; + + printk(KERN_INFO "%s: " SAPPHIRE_NAME " remote control driver v%s\n", __func__, SAPPHIRE_VERSION); + ret = sapphire_create(); + if (ret) + return ret; + dev->last_event = get_jiffies_64(); + ret = hid_register_driver(&sapphire_driver); + if (ret) + sapphire_destroy(); + else + sapphire_create_proc_entry(); + return ret; +} + +/* + * Invoked by the kernel when this module is removed/unloaded. + */ +static void __exit sapphire_exit(void) +{ + remove_proc_entry(SAPPHIRE_NAME, NULL); + hid_unregister_driver(&sapphire_driver); + sapphire_destroy(); +} + +module_init(sapphire_init); +module_exit(sapphire_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mark Lord"); diff --git a/sapphire-remote-6.6/sapphire.h b/sapphire-remote-6.6/sapphire.h new file mode 100644 index 0000000..f6305b4 --- /dev/null +++ b/sapphire-remote-6.6/sapphire.h @@ -0,0 +1,169 @@ +/* + * sapphire.h + * + * Copyright Mark Lord , 2012-2015. + * http://rtr.ca/sapphire_remote/ + * + * Button definitions shared with sapphire_keymap.sh + * and the external glue driver. + * + * This program 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; specifically version 2 of the License (GPLv2). + */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * These enum's are shared with external keymaps and the sapphire_keymap.sh script: + */ +enum { /* + * BEGIN_PARSING (marker used by script that extracts these for sapphire_keymap.sh) + * + * Raw button values from the Sapphire remote: + * + */ + SAPPHIRE_UP = 0x00520000 , /* UP */ + SAPPHIRE_DOWN = 0x00510000 , /* DOWN */ + SAPPHIRE_RIGHT = 0x004f0000 , /* RIGHT */ + SAPPHIRE_LEFT = 0x00500000 , /* LEFT */ + SAPPHIRE_ENTEROK = 0x00280000 , /* ENTER/OK */ + SAPPHIRE_BACK = 0x00040003 , /* BACK */ + SAPPHIRE_PLAY = 0x40000003 , /* PLAY */ + SAPPHIRE_PAUSE = 0x80000003 , /* PAUSE */ + SAPPHIRE_VOLUP = 0x00002003 , /* VOL+ */ + SAPPHIRE_VOLDOWN = 0x00004003 , /* VOL- */ + SAPPHIRE_CHUP = 0x00010003 , /* CH/PG+ */ + SAPPHIRE_CHDOWN = 0x00020003 , /* CH/PG- */ + SAPPHIRE_MUTE = 0x00001003 , /* MUTE */ + SAPPHIRE_RECORD = 0x00008003 , /* RECORD */ + SAPPHIRE_FWD = 0x08000003 , /* FWD */ + SAPPHIRE_REW = 0x04000003 , /* REW */ + SAPPHIRE_ANGLE = 0x00010004 , /* ANGLE */ + SAPPHIRE_SAP = 0x00100004 , /* SAP */ + SAPPHIRE_DVDMENU = 0x00040004 , /* DVDMENU */ + SAPPHIRE_INFOEPG = 0x02000003 , /* INFO/EPG */ + SAPPHIRE_TAB = 0x00000103 , /* TAB */ + SAPPHIRE_BACKTAB = 0x00000203 , /* BACKTAB */ + SAPPHIRE_RADIO = 0x00000403 , /* RADIO */ + SAPPHIRE_LASTCH = 0x00400004 , /* LASTCH */ + SAPPHIRE_LANGUAGE = 0x00020004 , /* LANGUAGE */ + SAPPHIRE_TELETEXTCC = 0x00200004 , /* TELETEXT/CC */ + SAPPHIRE_SUBTITLE = 0x00080004 , /* SUBTITLE */ + SAPPHIRE_HOMEHOUSE = 0x00000104 , /* HOME (house) */ + SAPPHIRE_BLUEVIDEOS = 0x00002004 , /* BLUE/VIDEOS */ + SAPPHIRE_LIVETV = 0x00000204 , /* LIVETV */ + SAPPHIRE_REDDVDVCD = 0x00008004 , /* RED/DVD/VCD */ + SAPPHIRE_YELLOWPICTURES = 0x00001004 , /* YELLOW/PICTURES*/ + SAPPHIRE_1 = 0x001e0000 , /* 1 */ + SAPPHIRE_2 = 0x001f0000 , /* 2 */ + SAPPHIRE_3 = 0x00200000 , /* 3 */ + SAPPHIRE_4 = 0x00210000 , /* 4 */ + SAPPHIRE_5 = 0x00220000 , /* 5 */ + SAPPHIRE_6 = 0x00230000 , /* 6 */ + SAPPHIRE_7 = 0x00240000 , /* 7 */ + SAPPHIRE_8 = 0x00250000 , /* 8 */ + SAPPHIRE_9 = 0x00260000 , /* 9 */ + SAPPHIRE_0 = 0x00270000 , /* 0 */ + SAPPHIRE_STOP = 0x00100003 , /* STOP */ + SAPPHIRE_POWER = 0x00000202 , /* POWER */ + SAPPHIRE_CLEAR = 0x004c0000 , /* CLEAR */ + SAPPHIRE_GREENMUSIC = 0x00000804 , /* GREEN/MUSIC */ + + /* + * A second set of "virtual" sapphire buttons, + * not for normal mapping use. Instead, these are + * intended for use with the external "glue" module. + * If you don't know what that is, then just ignore these! + */ + XAPPHIRE_UP = 0x005200f0 , /* UP */ + XAPPHIRE_DOWN = 0x005100f0 , /* DOWN */ + XAPPHIRE_RIGHT = 0x004f00f0 , /* RIGHT */ + XAPPHIRE_LEFT = 0x005000f0 , /* LEFT */ + XAPPHIRE_ENTEROK = 0x002800f0 , /* ENTER/OK */ + XAPPHIRE_BACK = 0x000400f3 , /* BACK */ + XAPPHIRE_PLAY = 0x400000f3 , /* PLAY */ + XAPPHIRE_PAUSE = 0x800000f3 , /* PAUSE */ + XAPPHIRE_VOLUP = 0x000020f3 , /* VOL+ */ + XAPPHIRE_VOLDOWN = 0x000040f3 , /* VOL- */ + XAPPHIRE_CHUP = 0x000100f3 , /* CH/PG+ */ + XAPPHIRE_CHDOWN = 0x000200f3 , /* CH/PG- */ + XAPPHIRE_MUTE = 0x000010f3 , /* MUTE */ + XAPPHIRE_RECORD = 0x000080f3 , /* RECORD */ + XAPPHIRE_FWD = 0x080000f3 , /* FWD */ + XAPPHIRE_REW = 0x040000f3 , /* REW */ + XAPPHIRE_ANGLE = 0x000100f4 , /* ANGLE */ + XAPPHIRE_SAP = 0x001000f4 , /* SAP */ + XAPPHIRE_DVDMENU = 0x000400f4 , /* DVDMENU */ + XAPPHIRE_INFOEPG = 0x020000f3 , /* INFO/EPG */ + XAPPHIRE_TAB = 0x000001f3 , /* TAB */ + XAPPHIRE_BACKTAB = 0x000002f3 , /* BACKTAB */ + XAPPHIRE_RADIO = 0x000004f3 , /* RADIO */ + XAPPHIRE_LASTCH = 0x004000f4 , /* LASTCH */ + XAPPHIRE_LANGUAGE = 0x000200f4 , /* LANGUAGE */ + XAPPHIRE_TELETEXTCC = 0x002000f4 , /* TELETEXT/CC */ + XAPPHIRE_SUBTITLE = 0x000800f4 , /* SUBTITLE */ + XAPPHIRE_HOMEHOUSE = 0x000001f4 , /* HOME (house) */ + XAPPHIRE_BLUEVIDEOS = 0x000020f4 , /* BLUE/VIDEOS */ + XAPPHIRE_LIVETV = 0x000002f4 , /* LIVETV */ + XAPPHIRE_REDDVDVCD = 0x000080f4 , /* RED/DVD/VCD */ + XAPPHIRE_YELLOWPICTURES = 0x000010f4 , /* YELLOW/PICTURES*/ + XAPPHIRE_1 = 0x001e00f0 , /* 1 */ + XAPPHIRE_2 = 0x001f00f0 , /* 2 */ + XAPPHIRE_3 = 0x002000f0 , /* 3 */ + XAPPHIRE_4 = 0x002100f0 , /* 4 */ + XAPPHIRE_5 = 0x002200f0 , /* 5 */ + XAPPHIRE_6 = 0x002300f0 , /* 6 */ + XAPPHIRE_7 = 0x002400f0 , /* 7 */ + XAPPHIRE_8 = 0x002500f0 , /* 8 */ + XAPPHIRE_9 = 0x002600f0 , /* 9 */ + XAPPHIRE_0 = 0x002700f0 , /* 0 */ + XAPPHIRE_STOP = 0x001000f3 , /* STOP */ + XAPPHIRE_POWER = 0x000002f2 , /* POWER */ + XAPPHIRE_CLEAR = 0x004c00f0 , /* CLEAR */ + XAPPHIRE_GREENMUSIC = 0x000008f4 , /* GREEN/MUSIC */ + + /* + * Modifier buttons: "OR" these with "regular" KEY_ values as desired: + */ + CTRL = 0x80000000 , + SHIFT = 0x40000000 , + ALT = 0x20000000 , + META = 0x10000000 , + + /* + * Special "macro" keys: + */ + KEY_DELAY = 0x00fffff7 , + KEY_MACRO_0 = 0x00fffff8 , + KEY_MACRO_1 = 0x00fffff9 , + KEY_MACRO_2 = 0x00fffffa , + KEY_MACRO_3 = 0x00fffffb , + KEY_MACRO_4 = 0x00fffffc , + KEY_MACRO_5 = 0x00fffffd , + KEY_MACRO_6 = 0x00fffffe , + KEY_MACRO_7 = 0x00ffffff , + + /* + * Per-button automatic repeat rates: + */ + NO_REPEAT = 0x00000000 , /* no autorepeat */ + SLOW_REPEAT = 0x00000004 , /* repeats 4 times per second */ + FAST_REPEAT = 0x00000008 , /* repeats 8 times per second */ + RAMP_REPEAT = 0x00000003 , /* repeats increase in rate as button is held, up to 10/sec */ + RAWKEY = 0xffffffff , /* do not send "release" event until button is really released */ + LONGKEY = 0x08000000 , /* no autorepeat; short/long presses send different codes */ +}; + +/* + * This is a hook for external modules to tie into, + * allowing other remote-drivers to use the sapphire + * driver's translations and advanced features. + */ +extern void sapphire_relay (u32 data); diff --git a/sapphire-remote-6.6/sapphire_keymap.sh.part1 b/sapphire-remote-6.6/sapphire_keymap.sh.part1 new file mode 100644 index 0000000..e594104 --- /dev/null +++ b/sapphire-remote-6.6/sapphire_keymap.sh.part1 @@ -0,0 +1,20 @@ +#!/bin/bash +# +# sapphire_keymap.sh by Mark Lord , April/2013. +# http://rtr.ca/sapphire_remote/ +# +# Script to translate a symbolic keymap file for the Sapphire driver +# into hex format and feed the results to /proc/sapphire. +# +# This can be invoked on the fly, as often as desired. +# Eg. to change keymaps to match a specific program, such as mythfrontend or xbmc. +# +# By default, this script reads key mappings from /etc/sapphire.keymap +# but this can be overridden by supplying an alternative path/file as a parameter. +# +# Eg. sapphire_keymap.sh ~/.sapphire_keymap +# + +## Arrays for Buttons, Keycodes, and Modifiers: +##' +declare -A K B M diff --git a/sapphire-remote-6.6/sapphire_keymap.sh.part3 b/sapphire-remote-6.6/sapphire_keymap.sh.part3 new file mode 100644 index 0000000..d694b3a --- /dev/null +++ b/sapphire-remote-6.6/sapphire_keymap.sh.part3 @@ -0,0 +1,97 @@ + +PROCFILE=/proc/sapphire +MYNAME="${0##*/}" + +function get_multipart_keycode(){ + local parts="$1" val=0 zero_ok=0 p v + while [ "$parts" != "" ]; do + p="${parts%%|*}" + [ "$p" = "$parts" ] && parts="" || parts="${parts#*|}" + v="${K[$p]}" + if [ "$v" = "" ]; then + v="${M[$p]}" + if [ "$v" = "" ]; then + logger -s "$MYNAME: ERROR0: $p: unrecognized key/modifier" + exit 1 + fi + fi + [ $((v)) -eq 0 ] && zero_ok=1 || val=$((val + v)) + done + [ $val -ne 0 -o $zero_ok -eq 1 ] && printf "0x%08x" $val +} + +function parse_keymap(){ + local line blabel bval klabel kval olabel oval out i + while read line ; do + line="${line%%#*}" + set -- $line + [ "$1" = "" ] && continue + if [ "$1" = "REPEAT_RATE" -a "$3" != "" ]; then + out="$1 $2 $3" + else + blabel="$1" + bval="${B[$blabel]}" + if [ "$bval" = "" ]; then + logger -s "$MYNAME: ERROR1: $line blabel=$blabel" + exit 1 + fi + out= + if [ "${blabel:0:10}" = "KEY_MACRO_" ]; then + out="$bval" + olabel="" + for i in {2..8} ; do + klabel="${!i}" + [ "$klabel" = "" ] && break + olabel="$olabel $klabel" + kval=$(get_multipart_keycode "$klabel") + if [ "$kval" = "" ]; then + logger -s "$MYNAME: ERROR2: $line" + exit 1 + fi + out="$out $kval" + done + klabel="" + else + klabel="$2" + olabel="$3" + if [ "$blabel" = "" -o "$klabel" = "" -o "$olabel" = "" ]; then + logger -s "$MYNAME: ERROR3: $line" + exit 1 + fi + kval=$(get_multipart_keycode "$klabel") + oval=$(get_multipart_keycode "$olabel") + if [ "$bval" = "" -o "$kval" = "" -o "$oval" = "" ]; then + logger -s "$MYNAME: ERROR4: $blabel=$bval $klabel=$kval $olabel=$oval" + exit 1 + fi + out="$bval $kval $oval" + fi + fi + [ $VERBOSE -gt 0 ] && echo "echo \"$out\" >$PROCFILE # $blabel $klabel $olabel" >&2 + echo "$out" >$PROCFILE || exit 2 + out= + done +} + +if [ ! -e $PROCFILE ]; then + echo "$PROCFILE: not found (is driver loaded?)" + exit 1 +fi + +VERBOSE=0 +while [ "$1" = "-v" -o "$1" = "--verbose" ]; do + VERBOSE=$((VERBOSE + 1)) + shift +done + +KEYMAP="$1" +[ "$KEYMAP" = "" ] && KEYMAP=/etc/sapphire.keymap +if [ ! -e "$KEYMAP" ]; then + logger -s "$MYNAME: $KEYMAP: not readable" + exit 1 +fi + +type -all logger &>/dev/null && logcmd="logger -s --" || logcmd=echo +$logcmd "${0##*/}: sending \"$KEYMAP\" to $PROCFILE" +cat "$KEYMAP" | parse_keymap + diff --git a/sapphire-remote-6.6/sapphire_startup.sh b/sapphire-remote-6.6/sapphire_startup.sh new file mode 100755 index 0000000..96097fb --- /dev/null +++ b/sapphire-remote-6.6/sapphire_startup.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# +# Some distros seem to be building the hid-topseed driver into +# the core kernel image, rather than leaving as a loadable module. +# +# So, for our sapphire driver to bind to a device, +# we first have to get the hid-topseed driver to unbind from it. +# +TOPSEED=/sys/bus/hid/drivers/topseed +SAPPHIRE=/sys/bus/hid/drivers/sapphire +rmmod hid-topseed &>/dev/null +modprobe sapphire &>/dev/null +if [ -d $SAPPHIRE -a -e $TOPSEED/unbind ]; then + cd $TOPSEED + for dev in [0-9]*[-0-9A-F] ; do + if [ -e "$dev" ]; then + echo "$dev" > unbind + echo "$dev" > $SAPPHIRE/bind + fi + done +fi +# +# Ubuntu/Mint kernels (and likely others too) don't like it +# when we unload and reload the sapphire driver. +# They disable the USB IR receiver and fail to reenable it. +# The workaround below seems to restore functionality. +# +cd $SAPPHIRE || exit 1 +dev="$(/bin/ls -1d [0-9]*[0-9A-F] 2>/dev/null | head -1)" +if [ "$dev" != "" -a -e "$dev" ]; then + if cd -P "$dev/../.." ; then + if [ -e authorized ]; then + echo 0 > authorized + echo 1 > authorized + fi + cd - >/dev/null + fi +fi +[ -e /etc/sapphire.keymap -a -x /usr/local/bin/sapphire_keymap.sh ] && /usr/local/bin/sapphire_keymap.sh +exit 0