[LINUX] Lirc and Apple Remote long press hack - julian.neil - 2011-11-20
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).
- 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
Configure and Start XBMC - julian.neil - 2011-11-20
You'll need a custom Lircmap.xml and keymap.xml. I use the follwing. You might want to change the keymap if you want different functions mapped to the remote buttons.
~/.xbmc/userdata/Lircmap.xml
Code: <lircmap>
<remote device="Apple_A1156">
<obc1>VOLUP</obc1>
<obc2>VOLDOWN</obc2>
<obc3>BACKWARD</obc3>
<obc4>FORWARD</obc4>
<obc5>PLAY</obc5>
<obc6>MENU</obc6>
<obc7>PLAY_HOLD</obc7>
<obc8>MENU_HOLD</obc8>
<obc9>BACKWARD_HOLD</obc9>
<obc10>FORWARD_HOLD</obc10>
<obc11>VOLUP_HOLD</obc11>
<obc12>VOLDOWN_HOLD</obc12>
</remote>
</lircmap>
~/.xbmc/userdata/keymaps/keymap.xml
Code: <keymap>
<global>
<universalremote>
<!-- plus --> <obc1>Up</obc1>
<!-- minus --> <obc2>Down</obc2>
<!-- left --> <obc3>Left</obc3>
<!-- right --> <obc4>Right</obc4>
<!-- center --> <obc5>Select</obc5>
<!-- menu --> <obc6>PreviousMenu</obc6>
<!-- hold center --> <obc7>Fullscreen</obc7>
<!-- hold menu --> <obc8>ContextMenu</obc8>
<!-- hold left --> <obc9>Left</obc9>
<!-- hold right --> <obc10>Right</obc10>
<!-- hold up --> <obc11>Up</obc11>
<!-- hold down --> <obc12>Down</obc12>
</universalremote>
</global>
<Home>
<universalremote>
<obc6>XBMC.ActivateWindow(Favourites)</obc6>
<obc8>ActivateWindow(shutdownmenu)</obc8>
</universalremote>
</Home>
<MyFiles>
<universalremote>
</universalremote>
</MyFiles>
<MyMusicPlaylist>
<universalremote>
<obc6>Playlist</obc6>
</universalremote>
</MyMusicPlaylist>
<MyMusicPlaylistEditor>
<universalremote>
<obc6>ParentDir</obc6>
</universalremote>
</MyMusicPlaylistEditor>
<MyMusicFiles>
<universalremote>
<obc6>ParentDir</obc6>
</universalremote>
</MyMusicFiles>
<MyMusicLibrary>
<universalremote>
<obc6>ParentDir</obc6>
</universalremote>
</MyMusicLibrary>
<FullscreenVideo>
<universalremote>
<obc1>VolumeUp</obc1>
<obc2>VolumeDown</obc2>
<obc3>StepBack</obc3>
<obc4>StepForward</obc4>
<obc5>Pause</obc5>
<obc6>OSD</obc6>
<obc7>Stop</obc7>
<obc8>Fullscreen</obc8>
<obc9>StepBack</obc9>
<obc10>StepForward</obc10>
<obc11>VolumeUp</obc11>
<obc12>VolumeDown</obc12>
</universalremote>
</FullscreenVideo>
<VideoTimeSeek>
<universalremote>
<obc5>Select</obc5>
<obc7>Select</obc7>
</universalremote>
</VideoTimeSeek>
<FullscreenInfo>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</FullscreenInfo>
<PlayerControls>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</PlayerControls>
<Visualisation>
<universalremote>
<obc1>VolumeUp</obc1>
<obc2>VolumeDown</obc2>
<obc3>SkipPrevious</obc3>
<obc4>SkipNext</obc4>
<obc5>Pause</obc5>
<obc6>Fullscreen</obc6>
<obc7>XBMC.ActivateWindow(MusicOSD)</obc7>
<obc8>Stop</obc8>
</universalremote>
</Visualisation>
<MusicOSD>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</MusicOSD>
<VisualisationSettings>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</VisualisationSettings>
<VisualisationPresetList>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</VisualisationPresetList>
<SlideShow>
<universalremote>
<obc1>ZoomIn</obc1>
<obc2>ZoomOut</obc2>
<obc3>PreviousPicture</obc3>
<obc4>NextPicture</obc4>
<obc6>Stop</obc6>
<obc7>Info</obc7>
<obc8>Rotate</obc8>
<obc11>ZoomIn</obc11>
<obc12>ZoomOut</obc12>
</universalremote>
</SlideShow>
<ScreenCalibration>
<universalremote>
<obc5>NextCalibration</obc5>
</universalremote>
</ScreenCalibration>
<GUICalibration>
<universalremote>
<obc5>NextCalibration</obc5>
</universalremote>
</GUICalibration>
<SelectDialog>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</SelectDialog>
<VideoOSD>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</VideoOSD>
<VideoMenu>
<universalremote>
<obc5>Select</obc5>
<obc6>Stop</obc6>
<obc7>OSD</obc7>
<obc8></obc8>
</universalremote>
</VideoMenu>
<OSDVideoSettings>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</OSDVideoSettings>
<OSDAudioSettings>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</OSDAudioSettings>
<VideoBookmarks>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</VideoBookmarks>
<MyVideoLibrary>
<universalremote>
<obc6>ParentDir</obc6>
<obc7>Info</obc7>
</universalremote>
</MyVideoLibrary>
<MyVideoFiles>
<universalremote>
<obc6>ParentDir</obc6>
<obc7>Info</obc7>
</universalremote>
</MyVideoFiles>
<MyVideoPlaylist>
<universalremote>
<obc6>Playlist</obc6>
</universalremote>
</MyVideoPlaylist>
<VirtualKeyboard>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</VirtualKeyboard>
<ContextMenu>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</ContextMenu>
<FileStackingDialog>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</FileStackingDialog>
<Scripts>
<universalremote>
</universalremote>
</Scripts>
<NumericInput>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</NumericInput>
<Weather>
<universalremote>
<obc6>PreviousMenu</obc6>
</universalremote>
</Weather>
<Settings>
<universalremote>
<obc6>PreviousMenu</obc6>
</universalremote>
</Settings>
<AddonBrowser>
<universalremote>
<obc6>ParentDir</obc6>
</universalremote>
</AddonBrowser>
<AddonInformation>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</AddonInformation>
<AddonSettings>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</AddonSettings>
<TextViewer>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</TextViewer>
<MyPictures>
<universalremote>
<obc6>ParentDir</obc6>
</universalremote>
</MyPictures>
<MyPicturesSettings>
<universalremote>
<obc6>PreviousMenu</obc6>
</universalremote>
</MyPicturesSettings>
<MyWeatherSettings>
<universalremote>
<obc6>PreviousMenu</obc6>
</universalremote>
</MyWeatherSettings>
<MyMusicSettings>
<universalremote>
<obc6>PreviousMenu</obc6>
</universalremote>
</MyMusicSettings>
<SystemSettings>
<universalremote>
<obc6>PreviousMenu</obc6>
</universalremote>
</SystemSettings>
<MyVideoSettings>
<universalremote>
<obc6>PreviousMenu</obc6>
</universalremote>
</MyVideoSettings>
<NetworkSettings>
<universalremote>
<obc6>PreviousMenu</obc6>
</universalremote>
</NetworkSettings>
<AppearanceSettings>
<universalremote>
<obc6>PreviousMenu</obc6>
</universalremote>
</AppearanceSettings>
<Profiles>
<universalremote>
<obc6>PreviousMenu</obc6>
</universalremote>
</Profiles>
<systeminfo>
<universalremote>
<obc6>PreviousMenu</obc6>
</universalremote>
</systeminfo>
<shutdownmenu>
<universalremote>
<obc6>PreviousMenu</obc6>
</universalremote>
</shutdownmenu>
<submenu>
<universalremote>
<obc6>PreviousMenu</obc6>
</universalremote>
</submenu>
<MusicInformation>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</MusicInformation>
<MovieInformation>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</MovieInformation>
<LockSettings>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</LockSettings>
<ProfileSettings>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</ProfileSettings>
<PictureInfo>
<universalremote>
<obc3>Left</obc3>
<obc4>Right</obc4>
<obc6>Close</obc6>
</universalremote>
</PictureInfo>
<Teletext>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</Teletext>
<Favourites>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</Favourites>
<FileBrowser>
<universalremote>
<obc6>Close</obc6>
</universalremote>
</FileBrowser>
</keymap>
Finally, when you start XBMC you need to tell it to use the new lirc socket using the -l option.. e.g.
Code: xbmc --standalone -l /var/run/lirc/lircmangled
- remcoj - 2011-12-29
Thanks a lot for posting this, Julian! Works quite nicely for me as well. Although I only have it running for 5 minutes by now, it seems do it's job without any big issues. Only thing I noticed is that keeping a button pressed for a prolonged amount of time may sometimes still result in additional events. (Didn't have time to look into it yet though.)
Btw, shouldn't this be included in Lirc itself?
Anyway, thanks again for posting!
Cheers, Remco
- adriankoooo - 2012-01-01
This should work with the new alu remote?
- blackdot - 2012-01-14
Nice job! Thank you.
Just I had to change
proxied_socket = '/dev/lircd'
This is default location in debian.
- julian.neil - 2012-02-11
Glad it was of use to someone else
It should work fine for aluminium remote.. so long as the events reported by lirc have APPLE.... in them. Of course if they dont, you could always edit the lirc config (or my script).
If the aluminium remote has more buttons, you might want to customise the xbmc lircmap and keymap as well.
Jules
RE: [LINUX] Lirc and Apple Remote long press hack - joethefox - 2012-03-12
Thank you! this is exactly what I was looking for. I will try your work.
RE: [LINUX] Lirc and Apple Remote long press hack - Kissell - 2012-03-13
I would love to get the long key presses working... but they aren't...
I'm using the white Apple A1156, it works, but no holding of keys....
when i use your script, it doesn't map things right...
I think this is maybe because my /etc/lirc/lircd.conf file is mapping different names? and those names have to match Lircmap.xml
If that's the case, maybe posting your /etc/lirc/lircd.conf file would be helpful, because I used "irrecord" to create mine, and I'm not sure how to add a HOLD button key in there.
RE: [LINUX] Lirc and Apple Remote long press hack - julian.neil - 2012-03-21
(2012-03-13, 16:16)Kissell Wrote: I think this is maybe because my /etc/lirc/lircd.conf file is mapping different names? and those names have to match Lircmap.xml
If that's the case, maybe posting your /etc/lirc/lircd.conf file would be helpful, because I used "irrecord" to create mine, and I'm not sure how to add a HOLD button key in there.
Hi Kissell.
It isn't possible to configure lircd.conf to recognise long presses, or I would have done that instead of resorting to this workaround. This script runs in the background, taking the output from lirc and post-processing it to identify long presses. It then posts output containing long presses (using a format identical to lirc) on a different socket. xbmc then reads this socket instead of the usual lirc socket.
I am using the lircd config for apple remote that comes with the ubuntu lirc package. It utilizes the macmini driver.
It is located at /usr/share/lirc/remotes/apple/lircd.conf.macmini .. included here, but I'm not sure that it will help.
Code: #
# this config file was automatically generated
# using lirc-0.8.2(macmini) on Tue Dec 11 11:35:26 2007
#
# contributed by Sebastian Schaetzel
#
# brand: Apple
# model no. of remote control: A1156
# devices being controlled by this remote: Mac mini, MacBookPro 15"
# SantaRosa (3.1), MacBook2
#
begin remote
name Apple_A1156
bits 8
eps 30
aeps 100
one 0 0
zero 0 0
pre_data_bits 24
pre_data 0x87EE81
gap 211982
toggle_bit_mask 0x0
ignore_mask 0x0000ff01
begin codes
VOLUP 0x0B
VOLDOWN 0x0D
BACKWARD 0x08
FORWARD 0x07
PLAY 0x04
MENU 0x02
end codes
end remote
RE: [LINUX] Lirc and Apple Remote long press hack - SkillPhil - 2012-06-02
has anyone tried to get this script to work under openelec?
RE: [LINUX] Lirc and Apple Remote long press hack - Desensitized - 2012-10-03
Yes, I have gotten this working in openelec. Although it is somewhat difficult. I had to compile my own openelec build because lircd was remove from the official build way back. (I haven't been tinkering with openelec in about 6 months so this may have changed with newer builds)
I am not an expert linux guy and figured out how to get this working after many many hours. I wanted to make a write up but put it off too long and not I forgot how I got this working. I will try, over the next few weeks to go back through the process and make a writeup. The Apple Remote w/ long presses really is a nice remote.
RE: [LINUX] Lirc and Apple Remote long press hack - Desensitized - 2012-10-03
Looks like the new official build of Openelec has the "lirc_serial" module included so you don't have to compile your own kernel.
I just created a writeup on how I got this working on openelec. Hope this helps...
http://openelec.tv/forum/103-infared-remotes/48208-openelec-ion-apple-remote-silver-with-long-presses#48208
RE: [LINUX] Lirc and Apple Remote long press hack - tekno - 2012-10-12
Cough, cough!
Way easier, and seeing this thread inspired me to prove it. Long keypress support too!
See
http://teknogeekz.com/blog/?p=422
And try not to drive up the cost of the Apple IR replacent receivers!
RE: [LINUX] Lirc and Apple Remote long press hack - julian.neil - 2012-10-12
Nice.. I had a go at getting the atvclient to compile a while back, but had no luck. Will give it a go. Thanks tekno.
RE: [LINUX] Lirc and Apple Remote long press hack - Desensitized - 2012-10-13
Forgive me if i'm wrong. But, I don't believe this will work on openelec since it's a "Read-Only" operating system and doesn't have your apt-get. Any extra modules would have to be compiled into the OS.
|