Gość <account_deleted> Opublikowano 26 Kwietnia 2010 Zgłoś Opublikowano 26 Kwietnia 2010 (edytowane) Dnia pewnego stwierdziłem, że mam niezły bajzel jeśli chodzi o sterowanie z pilota: dziesiątki drobnych skryptów do wszystkiego, niektóre odpalające kolejne skrypty i programy, wszystko w drobnych kawałkach i do tego irexec, który jest imo raczej daleki od ideału. Aplikacje z natywnym plugiem do lircd to inna bajka, ale i tak wolę je sterować po swojemu ;) Tak więc po paru godzinach rozproszonych w tygodnie ukleiłem skrypcik zastępujący w pełni irexec, posiadający w zasadzie nieograniczone możliwości konfiguracji i rozbudowy. Nazwałem to sobie lircexec.sh, a brzmi jak następuje: #!/bin/bash#%TAB=4#dependancies:#lirc, xautomation, wmctrl, notify-osd# lircexec.sh v0.20 <testing possibilities> by tomazzi <tomazzi@wp.pl>ver="0.20"# This script tends to replace irexec (part of LIRC project, see [url=http://www.lirc.org),][url=http://www.lirc.org),][url=http://www.lirc.org),]http://www.lirc.org),[/url][/url][/url]# giving more flexibility and capabilities to control typical Linux Desktop. For now,# it is not applicable for dedicated HTPC solutions, but maybe someday, someone will# convert it to fast c-code with sockets support, etc? Who knows... :)####################################################################################### As usual: this code is free and that means free from any kind of warranty too. #### It can format your hard disk or even damage your hardware - I don't care. #######################################################################################-----------------------------| config:shopt -s expand_aliases #(it is on by default only for interactive shells)shopt -u checkwinsize #do not check terminal size#default config:lircpath=$(dirname "$0")"/" #following slash required here! (but not in --path option)rampath=""osd_level=1 #0= osd off, 1= report mode change only, 2= all modes & events (n/a for now)dbg_mode=0 #1= debug mode onkeypress_flt=250000000 #default time limit for repeating keypresses (nanosecs here, secs in --keyfilter)#command line parameters:for parm in $*do parmtype=$(sed -n 's_=_=\n_;P' <(echo $parm)) parmval=$(sed -n 's/'"$parmtype"'//p' <(echo $parm)) case $parmtype in --path=) #path to mode-files and helper scripts, add following slash if [ -d $parmval ];then lircpath=$(echo $parmval | sed -n 's_[^/]$_&/_;p') else echo "Path not found: "$parmval ;exit 1 fi ;; --fastpath=) #copy lircpath/files to mounted ramdisk for faster execution, lower CPU load (thanks to tempfs/ramfs) #and for lower HDD I/O load - it has more important things to do ;) #...assuming one have rights to do so... if [ -d $parmval ];then rampath=$(echo $parmval | sed -n 's_[^/]$_&/_;p') else echo "FastPath not found: "$parmval ;exit 1 fi ;; --debug) dbg_mode=1 ;; --osdlevel=) osd_level=$parmval ;; --keyfilter=) #seconds to nanoseconds conversion #desired formats: ii ii.ff .ff but even most stupid errors will be corrected, f.e. "0an0ther.some0thing?.23" -> 0.023s -> 23000000ns :) keypress_flt=$(echo "$parmval" | sed -n 's_[.0-9]$_&\._ #one dot at least, even for whole numbers #extract digits, allow first dot only: s_[^\.0-9]__g;s_\.__2g #integer part: h s_\.[.0-9]*$__ #fraction part, formatting: x s_^[^\.]*\.__ s_^[0-9]*$_&000000000_;s_[0-9]_&\n_9 #merge, remove leading zeros: H;x s_\n__ s_^[0]*__;s_^\n_0\n_;P' ) ;; *) echo -e "\nUnknown parameter: "$parm"\nUsage:\n $0 [ --path= | --fastpath= | --debug | --osdlevel= | --keyfilter= ]" exit 1 ;; esacdone#-----------------------------| aliases: fast and tricky way to obtain "self-modifying" code... :)if [ $dbg_mode = "1" ]; then alias dbg_echo=echo alias dbg_cmd="" alias dbg_else=elseelse alias dbg_echo=# alias dbg_cmd=# alias dbg_else=#fiif [ $keypress_flt != "0" ]; then alias keyfilter=""else alias keyfilter=#fi#TODO: osd notifications, simple workaround for now:case $osd_level in 1) alias osd_mode_info1='notify-send --urgency=low --icon=$lircpath"LIRC_logo.png"' alias osd_mode_info2=# ;; 2) alias osd_mode_info1='notify-send --urgency=low --icon=$lircpath"LIRC_logo.png"' alias osd_mode_info2='notify-send --urgency=low --icon=$lircpath"LIRC_logo.png"' ;; *) alias osd_mode_info1=# alias osd_mode_info2=# ;;esac#-----------------------------| functions:keycmd() # $1=keyline field (key command) { echo -n ${keydef[$1]} }keylinedef() # $1=mode-file { #key definition in current mode IFS="%" keydef=( $(sed -n '/^'${keycat[2]}'/ p' <"$lircpath$1") ) #this converts continous line to dynamic array of words, delimiter="%" unset IFS ktype=${keydef[1]} dbg_echo -en ", ktype:"$ktype": " }switchmode() # $1=noexit (option) { if [ -f "$lircpath$newmode" ]; then caption=$(sed -n '2 {s_^caption:[ \t]*%__;s_%_\n_;P;q}' <$lircpath$newmode) winid=$(sed -n '/'"$caption"'/!d;s_ _\n_;s_^0x[0]*__;P;q' <(wmctrl -l)) #check if requested target window already exist dbg_echo -en "\n...search for window: "$caption" ..." # TRICK: (evaluate "exec <cmd>" &) - this effectively detaches <cmd> from current process, keeping # all advantages of evaluateuate -> lower memory usage, higher speed, lower overall system load !!! # "Disown" does not do this (!!!) - it creates "ghosts" (waiting) parent instances as a way to separate child process. # exec will terminate forked evaluateuate - both are built-in, so there's no extra IO traffic. if [ "$1" != "noexit" ]; then #if app from previous mode appears to be dead already, do not execute cmd_exit dbg_echo -en "\nexecuting cmd_exit: "; sed -n '3 {s|^cmd_exit:[ \t]*%||;s|%|\n|;P;q}' <$lircpath$lircmode (evaluate "exec $(sed -n '3 {s|^cmd_exit:[ \t]*%||;s|%|\n|;P;q}' <$lircpath$lircmode)" &) #cmd_exit fi if [ "$winid" = "" ]; then #execute app_cmd only if target app is not running already dbg_echo -en "window not found.\nexecuting app_cmd: "; sed -n '1 {s|^app_cmd:[ \t]*%||;s|%|\n|;P;q}' <$lircpath$newmode (evaluate "exec $(sed -n '1 {s|^app_cmd:[ \t]*%||;s|%|\n|;P;q}' <$lircpath$newmode)" &) #app_cmd #disown wait_modesw="1" #next focus check will update winid - this should give enough time for window initialization #the trick is in that user normally will wait for window to apperar before applying comands. Mode will be dropped otherwise. else dbg_echo -e "found: requested app already running!\nbring to front: 0x"$winid wmctrl -i -a "0x$winid" fi lircmode=$newmode chkfocus=$(sed -n '4 {s|^chk_focus:[ \t]*%||;s|%|\n|;P;q}' <$lircpath$newmode) #should I check focus in this mode? osd_mode_info1 $lircmode dbg_echo " new mode: "$lircmode dbg_else dbg_echo "mode-file not exist:"$lircpath$newmode; fi }ktype_mapping() { case $ktype in kt_normal|kt_mode|kt_cycle|kt_seqset) key_exec "$lircmode";; kt_disabled) dbg_echo ", key disabled" ;; #key disabled (global override) - do nothing *) # key not defined in current mode or kt_global # temporarily force mode-global to find key-definition keylinedef "mode-global" #if_debug "badkey" dbg_echo -en "\nmode override: global" #debug key_exec "mode-global" ;; esac }key_exec() # $1=mode (current or global) for override mode { case $ktype in kt_normal) dbg_echo -ne "\n cmd: "; keycmd 5 (evaluate "exec $(keycmd 5)" &) ;; #execute cmd0 kt_mode) newmode=$(keycmd 5) if [ $lircmode != $newmode ]; then dbg_echo -n ", new mode request:" $newmode #new mode requested switchmode else dbg_echo ": mode already set: (" $newmode ")" #requested mode already active ($caption and $winid initialized) if [ $1 = "mode-global" ];then #mode=global or global mode override, key not defined in current mode if [ -z "$(sed -n '/'$winid'/!d;p;q' <(wmctrl -l))" ]; then #if target window does not exist, switch to mode-global (f.e. app was closed manually) dbg_echo "target window (" $winid ") not exist, switchmode=global" newmode="mode-global" switchmode noexit #(do not execute cmd_exit from prev. mode when program appears to be dead already) else #otherwise: bring target window to front - mode key works as shortcut ;) #besides, this prevents unintentional launching of another instatnce or closing invisible window dbg_echo "target window:0x"$winid "has no focus, bring to front" wmctrl -i -a "0x$winid" fi fi fi ;; kt_cycle) kstate=${keydef[3]} dbg_echo ", kt_cycle state:"$kstate #indirect addressing of command field in current key def. line (evaluate "exec ${keydef[5 + $kstate]}" &) (( kstate += 1 )) if (( kstate > ${keydef[4]} )); then kstate=0 dbg_echo ", kt_cycle reset:"$kstate dbg_else dbg_echo ", kt_cycle next:"$kstate fi #save kstate to file (remember current state in key def. line) #$1 - current or global mode (override) sed -i '/^'${keycat[2]}'/ s_%[0-9]%_%'$kstate'%_' "$lircpath$1" ;; kt_seqset) #set (or reset) kt_cycle sequence for current/override mode kt_cycle keys #cmd0 (field 5) = key name, cmd1 (field 6) = new kt_cycle state / seq.step number seqset_key=$(echo ${keydef[5]}) #echo is the simplest string converter dbg_echo -en "\nseqset target key: $seqset_key sequence step set to: ${keydef[6]}\n" sed -i '/^'$seqset_key'/ s_%[0-9]%_%'"${keydef[6]}"'%_' "$lircpath$1" ;; esac }scan() { while read -a keycat do keyfilter keyflt_now=$(date +%s%N); keyflt_delta=$(( keyflt_now - keyflt_last )) keyfilter dbg_echo -ne "\nkeytime: "$keyflt_now"ns ,delta: "$keyflt_delta"ns" keyfilter keyflt_last=$keyflt_now dbg_echo -ne "\n----> scan: "$lircmode", key: "${keycat[2]} keyfilter if (( keypress_flt > keyflt_delta )) ;then keyfilter dbg_echo -ne "\n keyfilter: keypress ignored!\n" keyfilter continue keyfilter fi if [ "$chkfocus" = "1" ];then if [ $wait_modesw = 1 ]; then #just after mode switching: get target window id winid=$(sed -n '/'"$caption"'/!d;s_ _\n_;s_^0x[0]*__;P;q' <(wmctrl -l)) wait_modesw=0 #checked fi dbg_echo -n ", chkfocus="$chkfocus #check if current-mode app window has focus #get active window ID activewin=$(sed -n '/_NET_ACTIVE_WINDOW/!d;s/^.*# 0x//;p;q' <(xprop -root -notype)) dbg_echo -n ", active:0x"$activewin" vs target:0x"$winid if [ "$activewin" = "$winid" ];then #key definition in current mode dbg_echo -n " = focus hit" keylinedef $lircmode ktype_mapping else #current-mode window has no focus - override keymap with mode-global #TODO: check if window still exist when focus is lost !!! dbg_echo -n " = !focus lost! mode override: global" keylinedef "mode-global" key_exec "mode-global" fi else dbg_echo -n ", chkfocus0="$chkfocus keylinedef $lircmode ktype_mapping fi keyfilter dbg_echo -en " :exec time: "$(( $(date +%s%N) - keyflt_now ))"ns\n" done }#-----------------------------| begin#move to ram?if [ "$rampath" != "" ]; then dbg_echo -en "--fastpath: moving to ram\n" mkdir $rampath"lircexec" cp -RP --remove-destination "$lircpath"* -t $rampath"lircexec" rm $rampath"lircexec/"$(basename $0) #script already in memory - no need to waste additional ram space lircpath=$rampath"lircexec/"fiPATH=$PATH:$lircpath#wait for lircd to startwhile [ -z "$(pidof lircd)" ] ; do dbg_echo -en "\nwaiting for lirc daemon...\n" sleep 1 done#startup, init runtime varslircmode="mode-global"winid=""wait_modesw="0"activewin=""caption=""chkfocus="0"keyfilter keyflt_last=$(date +%s%N); keyflt_now="0"; keyflt_delta="0"#print config if debugging is enabled:dbg_echo -en "\n lircexec.sh v$ver, startup configuration:\n path="$lircpath"\n osd_level="$osd_leveldbg_echo -en "\n keyfilter="$keypress_flt"ns\n dbg_mode="$dbg_modekeyfilter dbg_echo -e "\ntimestamp: "$keyflt_last"ns"#lircd running, start key scanning: (irw is part of LIRC project, see [url=http://www.lirc.org)][url=http://www.lirc.org)][url=http://www.lirc.org)]http://www.lirc.org)[/url][/url][/url]#without parameters irw connects to default lircd socket: "/var/run/lirc/lircd"(irw | scan &)#fork in subshell, then exit - without this, current instance stays in wait state (wchannel),#while the second will be invoked to satisfy pipe feeder (irw)exit 0 W skrócie: Skrypt mapuje klawisze pilota w zależności od aktualnego trybu (lircmode), sprawdzjąc jednak czy okno docelowe jest aktywne i czy wogóle istnieje - np. włączenie trybu gdy dana aplikacja jest już odpalona powoduje wyciągnięcie jej okna do przodu. Obsługiwane są klawisze globalne, oddzielne komendy dla wejścia w dany tryb (odpalenie aplikacji) i dla wyjścia, filtrowanie klawiszy typu autorepeat, pełny dostęp do klawiszy XFree86 - poprzez xte i odpowiednie bindingi -> np wyświetlanie notyfikacji volume/mute, notyfikacje o aktualnym mapowaniu pilota (lircmode), w miarę wszystkomówiący debug... w skrócie ;) Wszystkie akcje definiowanie są w pliku tekstowym, przykład dla "mode-power" czyli okienka zamykania systemu: app_cmd: %power-selector.sh%caption: %Shut Down%cmd_exit: %xte "key Return"%chk_focus: %0%POWER %kt_disabled% %0%0 % %CH_UP %kt_normal% %0%0 %xte "key Up" %CH_DOWN %kt_normal% %0%0 %xte "key Down" %ENTER %kt_mode% %0%0 %mode-global % przykład dla SMplayera: app_cmd: %smplayer%caption: %SMPlayer%cmd_exit: %xte "keydown Control_L" "key x" "keyup Control_L"%chk_focus: %1%POWER %kt_mode% %0%0 %mode-global %MUTE %kt_normal% %0%0 %xte "key m" % # Mute - smplayer internalFULLSCREEN %kt_normal% %0%0 %xte "key f" % # fullscreen togglePREVIOUS %kt_normal% %0%0 %xte "key Down" % # -1min.NEXT %kt_normal% %0%0 %xte "key Up" % # +1min.PLAYPAUSE %kt_normal% %0%0 %xte "key BackSpace" %REWIND %kt_normal% %0%0 %xte "key Left" % # -10sec.STOP %kt_normal% %0%0 %xte "key s" %FFORWARD %kt_normal% %0%0 %xte "key Right" % # +10sec.EPG %kt_normal% %0%0 %xte "key v" % #subtitles visiblePIP %kt_normal% %0%0 %xte "keydown Shift_L" "key w" "keyup Shift_L" % #Auto Zoom Wszelkie pomysły mile widziane - krytyka: też, pod warunkiem że konstruktywna ;) --------------------- Update to v0.20: ...parka kt_cycle | kt_seqset daje całkiem nieplanowane możliwości ;) --fastpath=/path/to/mounted/ramdisk - działa. No ale dobra: podstawowy cel, jakim było pozbycie się upośledzonego irexec uważam za osiągnięty - na razie muszę sobie odpuścić z braku czasu, ale przy następnej okazji zabiorę się za wersję w cpp + libcairo ;) Kilka słów o tym rozwiązaniu: - nie jest zbyt szybkie (czas reakcji typowo kilkanaście ms), ale za to ma zdecydowanie mniejszy footprint niż wersja w pythonie (z którą też eksperymentowałem). Do normalnych zadań wystarcza... zaliczając do normalnych również sterowanie VLC odpalonym na drugim kompie... ;) - tak na prawdę irw nie jest potrzebne: bash może czytać tcp socket z lirca, ale jest to mniej wygodne - dostajemy kody w hexie zamiast nazw klawiszy. - tak naprawdę wogóle nie potrzeba lircd: bash może czytać character_devices jakimi są porty COM oraz wszystkie (afain) porty I2C w kartch TV. ...tyle że wtedy dostajemy format binarny i do tego już trzeba ukleić jakiś wrapper co by definicje klawiszy wyglądały po ludzku... - notify-osd to typowy pain-in-arse - każdy, nawet stary client osd daje większe możliwości do zabawy - niemiej jednak wygląda ładnie przy włączonym composite i nie gryzie się z pozostałymi aplikacjami. Zapowiadają się zmiany w tym temacie ... zobaczymy. Edytowane 31 Maja 2010 przez tomazzi Cytuj Udostępnij tę odpowiedź Odnośnik do odpowiedzi Udostępnij na innych stronach Więcej opcji udostępniania...