2011-11-20, 05:05
UPDATE 2014-04-04
---
I recently needed to rebuild my HTPC. I went for ubuntu 14.04 server as a base.
Teknos suggestion in post #13 below is far simpler to get working and seems to work well.
---
I recently set up a HTPC running Linux on an old macbook pro. I wanted to get the apple remote working with lirc and long press, because I had an apple remote lying around. I couldn't find the solution on the interweb, so I hacked one together myself. Its a python script to proxy the Lirc socket and modify the events to contain long press events (when appropriate). Its rough, but seems to work. (forgive any formatting foibles.. my first posts here)
There a few moving parts (described in the following posts).
This script is the guts of it. Save it somewhere and remember to make it executable. I have it in /usr/local/bin/lircmangled
You will need some sort of init script to start it at boot time. I'm using ubuntu 10.04, so here is a simple upstart script. I have it saved as /etc/init/lircmangled.conf
---
I recently needed to rebuild my HTPC. I went for ubuntu 14.04 server as a base.
Teknos suggestion in post #13 below is far simpler to get working and seems to work well.
---
I recently set up a HTPC running Linux on an old macbook pro. I wanted to get the apple remote working with lirc and long press, because I had an apple remote lying around. I couldn't find the solution on the interweb, so I hacked one together myself. Its a python script to proxy the Lirc socket and modify the events to contain long press events (when appropriate). Its rough, but seems to work. (forgive any formatting foibles.. my first posts here)
There a few moving parts (described in the following posts).
- Daemon to proxy lirc port
- Run XBMC with custom lirc socket
- XBMC Lircmap.xml
- XBMC keymap.xml
This script is the guts of it. Save it somewhere and remember to make it executable. I have it in /usr/local/bin/lircmangled
Code:
#!/usr/bin/env python
#
# proxy the lirc socket to distinguish between short and long presses
# on apple remote buttons
#
# long press occurs when > n repeat events occur within t time of eachother.
# short press occurs when there is >= t time after an event.
#
# holding apple remote keys generates lirc events with increasing repeat counts.
# clicks on the apple remote keys can generate a few repeat lirc events, and
# if they are fast enough, can also have increasing repeat counts.
# this makes it a bit tricky to find a couple of short clicks followed by a
# long click as this looks like a continuous stream of repeat lirc events for
# the same button. can track this by monitoring the time beteen events and
# resequencing the repeat counts where appropriate
#
# Julian Neil
#
# this software is released under the Do It Yourself License.
# If it doesn't work for you, then fix it.
import logging
import asyncore
import asynchat
import socket
import os
import sys
import signal
import time
import threading
logging.basicConfig(level=logging.ERROR)
log = logging.getLogger(sys.argv[0])
hold_repeats = 3 # number of repeat lirc events for long press (3 or 4 seem ok)
repeat_threshold = 0.13 # time threshold for repeat events. (> 0.12 . might need more on a loaded machine)
proxy_socket = '/var/run/lirc/lircmangled' # socket for mangled output (and pass-through input)
proxied_socket = '/var/run/lirc/lircd' # lirc socket
# connect to lirc socket, mangle its output to show long presses, and forward to the proxy
class Mangler(asynchat.async_chat):
def __init__(self, mangle_proxy, proxied_socket):
asynchat.async_chat.__init__(self)
self.set_terminator('\n')
self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.connect(proxied_socket)
self.mangle_proxy = mangle_proxy
self.ibuffer = '' # buffer for incoming lirc data
self.last_repeats = 0 # repeat count from last lirc event
self.last_time = 0 # time of last lirc event
self.last_line = None # last lirc event split into strings
self.hold_repeats = 0 # number of repeats the key has been held
self.lock = threading.Lock()
self.timer = None
def collect_incoming_data(self, data):
if (data):
self.ibuffer = self.ibuffer + data
def found_terminator(self):
with self.lock:
now = time.time()
self.cancel_timer()
bits = self.ibuffer.split(None, 5)
# check for apple remote button presses
if (len(bits) == 4 and 'apple' in bits[3].lower()):
repeats = int(bits[1], 16)
# if this is a repeat event and the time since the last press was < t we need to check for a long press
if (repeats == self.last_repeats + 1 and now - self.last_time < repeat_threshold):
self.hold_repeats += 1
if (self.hold_repeats >= hold_repeats):
# if we get >= n repeats all within time t of eachother consider it a long press
bits[2] += '_HOLD'
bits[1] = "%0.2X" % (self.hold_repeats - hold_repeats)
# dont output repeat hold events for PLAY or MENU
if (self.hold_repeats == hold_repeats or ( 'PLAY' not in bits[2] and 'MENU' not in bits[2] )):
self.mangle_proxy.send(' '.join(bits) + '\n')
self.last_line = None
else:
# not enough repeats yet to distinguish between short and long press
self.last_line = bits
self.start_timer()
else:
# if we have a buffered line, it must have been a short press.. so send it
if (self.last_line):
self.last_line[1]='00'
self.mangle_proxy.send(' '.join(self.last_line) + '\n')
# new button press.. dont know if it is short or long press yet
self.hold_repeats = 0
self.last_line = bits
self.start_timer()
# remember the repeat count and time to check against when we get the next event
self.last_repeats = repeats
self.last_time = now
else:
# not an apple button press.. just forward it
self.mangle_proxy.send(self.ibuffer + '\n')
self.ibuffer = ''
# start a timer so we know when to send a short press
def start_timer(self):
self.timer = threading.Timer(repeat_threshold, self.timeout)
self.timer.start()
def cancel_timer(self):
if (self.timer):
self.timer.cancel()
# timer expired.. must have a short press.. replace its repeat count with zero
def timeout(self):
with self.lock:
if (self.last_line and time.time() - self.last_time > repeat_threshold):
self.last_line[1]='00'
self.mangle_proxy.send(' '.join(self.last_line) + '\n')
self.last_line = None
class MangleProxy(asyncore.dispatcher_with_send):
handler_id = 1
def __init__(self, socket, proxied_socket):
asyncore.dispatcher_with_send.__init__(self, socket)
self.handler_id = MangleProxy.handler_id
MangleProxy.handler_id += 1
log.info("Creating handler %d", self.handler_id)
self.mangler = Mangler(self, proxied_socket)
def handle_read(self):
data = self.recv(8192)
if data:
self.mangler.push(data)
def handle_close(self):
log.info("Closing handler %d", self.handler_id)
self.mangler.close();
self.close();
# listen for and accept connects to the proxy socket
class MangleServer(asyncore.dispatcher):
def __init__(self, proxy_socket, proxied_socket):
asyncore.dispatcher.__init__(self)
log.info('Starting server on %s', proxy_socket)
self.proxy_socket = proxy_socket
self.proxied_socket = proxied_socket
self.remove_proxy_socket()
self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind(self.proxy_socket)
self.listen(5)
os.chmod(self.proxy_socket, 0666)
self.closed = False
def remove_proxy_socket(self):
try:
if (os.path.exists(self.proxy_socket)):
os.unlink(self.proxy_socket)
except OSError, error:
log.error('Unable to remove socket file %s : %s', self.proxy_socket, str(error))
raise
def handle_accept(self):
pair = self.accept()
if pair is None:
pass
else:
sock, addr = pair
log.info('Incoming connection from %s', repr(addr))
handler = MangleProxy(sock, self.proxied_socket)
def handle_close(self):
if (not self.closed):
log.info('Stopping server on %s', self.proxy_socket)
self.close()
self.remove_proxy_socket()
self.closed = True
# fix for asyncore bug causing 100% cpu when listening on unix domain sockets
def writable(self):
return False
server = None
try:
# ignore HUPs.. like a good daemon
signal.signal(signal.SIGHUP, lambda x,y: None)
server = MangleServer(proxy_socket, proxied_socket)
asyncore.loop()
finally:
if (server):
server.handle_close()
You will need some sort of init script to start it at boot time. I'm using ubuntu 10.04, so here is a simple upstart script. I have it saved as /etc/init/lircmangled.conf
Code:
description "Lirc Mangle Daemon"
start on filesystem
stop on runlevel [!2345]
respawn
respawn limit 10 5
pre-start script
mkdir -p -m0755 /var/run/lirc
end script
exec /usr/local/bin/lircmangled