daemon that reads keystrokes from /dev/input/event* and acts on the "multimedia keys"
[soepkiptng.git] / soepkiptngd_mobile
blob9ea056f6c6dcfaeb6ade5ad3f6e26b184e55f858
1 #!/usr/bin/perl
2 ############################################################################
3 # soepkiptngd_mobile (Soepkip The Next Generation daemon, Mobile version)
5 # (c) copyright 2001 Eric Lammerts <eric@lammerts.org>
7 # $Id$
8 ############################################################################
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License, version 2, as
11 # published by the Free Software Foundation.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # A copy of the GNU General Public License is available on the World Wide Web
19 # at `http://www.gnu.org/copyleft/gpl.html'. You can also obtain it by
20 # writing to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 # Boston, MA 02111-1307, USA.
22 ############################################################################
24 # fork, and start soepkiptngd
25 # fork, and start funkey handler
26 # wait until soepkip is playing stuff, or timeout (timeout is needed when no preload is available)
27 # exec /sbin/init
29 use integer;
30 use Audio::Mixer;
31 use Cwd 'abs_path';
32 use DBI;
33 use Getopt::Std;
34 use Socket;
36 # find program directory
37 $_ = $0;
38 while(-l) {
39 my $l = readlink or die "readlink $_: $!\n";
40 if($l =~ m|^/|) { $_ = $l; } else { s|[^/]*$|/$l|; }
42 m|(.*)/|;
43 my $progdir = abs_path($1);
45 require "$progdir/soepkiptng.lib";
47 getopts('c:');
49 read_configfile(\%conf, $opt_c);
51 $mixerchannel = 'pcm';
53 sub numtext($)
55 return join(" ", split(//, $_[0]));
58 sub open_db() {
59 # (re)open database connection if necessary
60 if(!$dbh) {
61 $dbh = DBI->connect("DBI:mysql:$conf{db_name}:$conf{db_host}",
62 $conf{db_user}, $conf{db_pass}) or do {
64 warn "Can't connect to database $conf{db_name}" .
65 "\@$conf{db_host} as user $conf{db_user}\n";
66 return undef;
69 return 1;
72 sub playlist_clear() {
73 open_db() or return;
74 $dbh->do("DELETE FROM queue");
75 warn "playlist cleared.\n";
78 sub open_db_getnow() {
79 open_db() or return;
80 my $now = get_nowplaying($dbh) or do {
81 warn "no now!\n";
82 return;
84 warn "now->{id}=$now->{id}\n";
85 return $now;
88 sub playlist_addall_artist() {
89 my $now = open_db_getnow() or return;
91 my $songsref = $dbh->selectcol_arrayref(
92 "SELECT id FROM song WHERE present AND filename LIKE '/%' AND ".
93 "artist_id=$now->{arid} AND id != $now->{id} AND ".
94 "unix_timestamp(now()) - unix_timestamp(last_played) > ".
95 "$conf{min_random_time} ORDER BY RAND()");
96 warn "adding songs: " . join(",", @{$songsref}) . ".\n";
97 return if @{$songsref} == 0;
98 add_song($dbh, "queue", '', @{$songsref});
101 sub playlist_addall_album($) {
102 my ($alltracks) = @_;
104 my $now = open_db_getnow() or return;
105 $now->{track} = -1 if $alltracks;
107 my $songsref = $dbh->selectcol_arrayref(
108 "SELECT song.id FROM song,album WHERE present AND filename LIKE '/%' AND ".
109 "song.album_id=album.id AND album.name <> '' AND ".
110 "album_id=$now->{alid} AND song.id != $now->{id} AND ".
111 "track > $now->{track} AND ".
112 "unix_timestamp(now()) - unix_timestamp(last_played) > ".
113 "$conf{min_random_time} ORDER BY track");
114 warn "adding songs: " . join(",", @{$songsref}) . ".\n";
115 return if @{$songsref} == 0;
116 add_song($dbh, "queue", '', @{$songsref});
119 sub shuffle() {
120 open_db() or return;
121 shuffle_table($dbh, "queue");
122 warn "shuffle\n";
125 # return higest of (left, right) volume
126 sub getvol() {
127 my ($l, $r) = Audio::Mixer::get_cval($mixerchannel);
128 return $l > $r? $l : $r;
131 sub setvol($;$) {
132 my ($incr, $abs) = @_;
134 if($muted) {
135 if($incr > 0) {
136 $muted = 0;
137 } elsif($incr < 0) {
138 $volume += $incr;
139 return;
141 } else {
142 $volume = getvol() + $incr;
143 warn "volume=$volume\n";
146 if($incr == 0) { $muted = !$muted; }
147 if($abs) { $volume = $incr; $muted = 0; }
149 if($volume > 100) { $volume = 100; }
150 if($volume < 0) { $volume = 0; }
152 warn "setting volume, volume=$volume, muted=$muted\n";
153 say("volume " . numtext($volume)) if $debug;
154 Audio::Mixer::set_cval($mixerchannel, ($muted? 0 : $volume));
157 sub killsong() {
158 my $iaddr = inet_aton($killhost) or return;
159 $paddr = sockaddr_in($killport, $iaddr);
161 say("kill song $keypadvalue") if $debug;
162 if($keypadvalue && open_db()) {
163 $dbh->do("LOCK TABLES queue WRITE, song READ");
164 my $res = $dbh->selectall_arrayref(
165 "SELECT song_id,song.track FROM queue " .
166 "LEFT JOIN song ON queue.song_id=song.id " .
167 "ORDER BY song_order");
168 my @goners;
169 foreach(@$res) {
170 last if $_->[1] == $keypadvalue;
171 push @goners, $_->[0];
173 warn sprintf "deleting songs up to track %d: %s\n", $keypadvalue, join(",", @goners);
174 del_song($dbh, "queue", @goners);
175 $dbh->do("UNLOCK TABLES");
176 digit_reset();
179 socket SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp')
180 or return;
181 connect SOCK, $paddr
182 or return;
183 warn "killed song.\n";
184 close SOCK;
187 sub killdelete() {
188 local *ST;
189 my $filename;
191 open ST, $conf{statusfile} or return;
192 chop((undef, $filename) = <ST>);
193 close ST;
195 unlink $filename and killsong();
198 sub digit($) {
199 $keypadvalue *= 10;
200 $keypadvalue += $_[0];
201 push @keypadvalues, $_[0];
202 warn "keypadvalue=$keypadvalue\n";
205 sub digit_reset() {
206 $keypadvalue = 0;
207 @keypadvalues = ();
208 warn "digit_reset\n";
211 sub playlist() {
212 my $reboots = 0;
213 my $killdelete = 0;
214 foreach(@keypadvalues) {
215 say("dot command $_") if $debug;
216 if($_ == 0) {
218 elsif($_ == 1) {
219 shuffle();
221 elsif($_ == 2) {
222 playlist_addall_album(1);
224 elsif($_ == 3) {
225 playlist_addall_album(0);
227 elsif($_ == 4) {
228 playlist_addall_artist();
230 elsif($_ == 5) {
231 $debug++;
232 if($debug == 3) { $debug = 0; }
233 say("deebug $debug");
235 elsif($_ == 6) {
236 $killdelete++;
238 elsif($_ == 7) {
239 $reboots++;
241 elsif($_ == 8) {
242 sayit();
244 elsif($_ == 9) {
245 playlist_clear();
248 digit_reset();
249 if($reboots >= 3) {
250 say("reboot") if $debug;
251 warn "sync\n";
252 system "sync";
253 sleep 2;
254 warn "reboot -nf\n";
255 system "reboot -nf";
257 if($killdelete >= 3) {
258 say("kill and delete") if $debug;
259 killdelete();
263 sub prepare_word($) {
264 my ($t) = @_;
266 my $wavfile = "/tmp/soepkiptngd_mobile_say_$t.wav";
267 warn "say wavfile=$wavfile\n";
269 my $tmpfile = "/tmp/soepkiptngd_mobile_say.wav";
270 unlink $tmpfile;
271 symlink $wavfile, $tmpfile;
273 if(! -f $wavfile) {
274 local *F;
275 open F, "| text2wave -scale 5 -F 44100 -o $tmpfile";
276 print F "$t\n";
277 close F;
279 warn "wavfile size=" . (-s $wavfile) . " bytes.\n";
280 return $wavfile;
283 sub say($;$) {
284 my ($t, $dontsplit) = @_;
285 my @words;
286 if($dontsplit) {
287 @words = ($t);
288 } else {
289 @words = split /\s+/, $t;
292 # pause _before_ doing text2speech (to speed it up)
293 warn "kill -USR2 $cdrplaypid\n";
294 kill 'USR2', $cdrplaypid; # pause: make it close /dev/dsp
296 my @wavs;
297 foreach(@words) {
298 push @wavs, prepare_word($_);
300 open PL, "|cdrplay -w";
301 foreach(@wavs) {
302 my $pid;
303 if(($pid = fork) == 0) {
304 open STDOUT, ">&PL" or die;
305 exec "wav2raw", $_;
307 waitpid $pid, 0;
309 close PL;
311 warn "kill -HUP $cdrplaypid\n";
312 kill 'HUP', $cdrplaypid; # resume
313 unlink $tmpfile;
316 sub sayit() {
317 my $now = get_nowplaying($dbh) or do {
318 warn "no now!\n";
319 return;
322 my $t = "$now->{artist} ... $now->{title}";
323 $t =~ s|/| |g;
324 say($t, 1);
327 sub request() {
328 open_db() or return;
330 say("request song " . numtext($keypadvalue))
331 if $debug;
332 add_song($dbh, "queue", '', $keypadvalue);
333 warn "adding song: $keypadvalue.\n";
334 digit_reset();
337 sub request_album() {
338 my $alid = $keypadvalue;
339 open_db() or return;
341 my $songsref = $dbh->selectcol_arrayref(
342 "SELECT song.id FROM song,album WHERE present AND filename LIKE '/%' AND ".
343 "song.album_id=album.id AND album.id=$alid ".
344 "ORDER BY track");
345 digit_reset();
346 warn "adding songs: " . join(",", @{$songsref}) . ".\n";
347 return if @{$songsref} == 0;
348 say("request album " . numtext($alid) .
349 " found " . numtext(scalar @{$songsref}))
350 if $debug;
351 add_song($dbh, "queue", '', @{$songsref});
352 warn "adding song: @{$songsref}.\n";
355 sub request_artist() {
356 my $arid = $keypadvalue;
357 open_db() or return;
359 my $songsref = $dbh->selectcol_arrayref(
360 "SELECT song.id FROM song,artist WHERE present AND filename LIKE '/%' AND ".
361 "song.artist_id=artist.id AND artist.id=$arid ".
362 "ORDER BY track");
363 digit_reset();
364 warn "adding songs: " . join(",", @{$songsref}) . ".\n";
365 say("request artist " . numtext($arid) .
366 " found " . numtext(scalar @{$songsref}))
367 if $debug;
368 return if @{$songsref} == 0;
369 add_song($dbh, "queue", '', @{$songsref});
370 warn "adding song: @{$songsref}.\n";
373 sub funkeyd() {
374 warn "funkeyd cdrplaypid=$cdrplaypid k=$killhost:$killport\n";
375 local *F;
377 my $keynum = 89;
378 print STDERR "settings keycodes:";
379 my $loadkeys_input;
380 foreach(keys %conf) {
381 next unless /^funkey_(\d+)$/;
382 $keynum < 128 or die "too many funkeys defined!\n";
383 $loadkeys_input .= sprintf " keycode %d = U+%04x\n", $1, 0xfe00 + $keynum;
384 print STDERR " $1=>$keynum";
385 $scancode{$keynum} = $1;
386 $funkey_handle{$keynum} = $conf{$_};
387 $keynum++;
389 warn "---\n$loadkeys_input---\n";
390 warn "loadkeys\n";
391 if(open(F, "|-") == 0) {
392 $ENV{'PATH'} = $mypath;
393 setpgrp;
394 exec 'loadkeys', '-';
395 die "loadkeys: $!\n";
397 print F $loadkeys_input;
398 warn "\nloadkeys wait\n";
399 close F;
401 warn "funkey fork\n";
402 if(fork == 0) {
403 setpgrp;
404 $ENV{'PATH'} = $mypath;
405 open LOGERR, ">>/tmp/soepkiptngd_mobile.out";
406 $SIG{__WARN__} = sub {
407 my $msg = $_[0];
408 $msg =~ s/\n/\r\n/g;
409 print STDERR $msg;
410 printf LOGERR "%s %s",
411 scalar localtime, $msg;
414 Audio::Mixer::init_mixer();
415 open F, "/dev/funkey" or die "/dev/funkey: $!\n";
416 for(;;) {
417 sysread F, $key, 1;
418 $key = unpack "C", $key;
420 # skip autorepeated keys
421 next if $key == $prevkey;
422 $prevkey = $key;
424 # don't act on releasing a key
425 next if $key & 0x80;
427 if(time - $lastkeytime > 10) {
428 # timeout
429 warn "timeout -> digit_reset()\n";
430 digit_reset();
432 $lastkeytime = time;
434 warn "scancode $scancode{$key} keynum $key action $funkey_handle{$key}!\n";
435 eval $funkey_handle{$key};
440 sub wait_for_soepkiptng() {
441 local *ST;
443 $starttime = time;
444 while((time - $starttime) < 15) {
445 if(open ST, $conf{statusfile}) {
446 chop((undef, undef, undef, $cdrplaypid, $killhost, $killport) = <ST>);
447 close ST;
448 last if $cdrplaypid && $killhost && $killport;
450 sleep 1;
455 ### MAIN ###
457 $SIG{__WARN__} = sub {
458 my $msg = $_[0];
459 $msg =~ s/\n/\r\n/g;
460 print STDERR $msg;
463 $mypath = "/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin:$progdir:$progdir/bin";
464 warn "path=$mypath\n";
466 warn "volume\n";
467 setvol($conf{volume_init}, 1) if $conf{volume_init};
469 if($$ == 1) {
470 warn "fork\n";
471 if(fork == 0) {
472 setpgrp;
473 $ENV{'PATH'} = $mypath;
474 open LOGERR, ">>/tmp/soepkiptngd_mobile.out";
475 $SIG{__WARN__} = sub {
476 print STDERR $_[0];
477 printf LOGERR "%s %s",
478 scalar localtime, $_[0];
481 warn "hdparm -u1 -m16 -X66 -d1 /dev/hda\n";
482 system "hdparm -u1 -m16 -X66 -d1 /dev/hda";
483 warn "ifconfig lo\n";
484 system "ifconfig lo 127.0.0.1";
485 warn "hostname -F /etc/hostname\n";
486 system "hostname -F /etc/hostname";
488 warn "unlink $conf{statusfile}\n";
489 unlink $conf{statusfile};
491 warn "starting soepkiptngd\n";
492 exec 'sched', 'soepkiptngd', '-d';
493 warn "sched: $!\n";
494 exec 'soepkiptngd', '-d';
495 warn "soepkiptngd: $!\n";
496 exec "$progdir/soepkiptngd", '-d';
497 die "$progdir/soepkiptngd: $!\n";
500 warn "wait_for_soepkiptng\n";
501 wait_for_soepkiptng;
502 } else {
503 local *ST;
504 if(open ST, $conf{statusfile}) {
505 chop((undef, undef, undef, $cdrplaypid, $killhost, $killport) = <ST>);
506 close ST;
509 warn "killhost=$killhost:$killport\n";
511 warn "funkeyd\n";
512 funkeyd;
514 # for testing
515 exit if $$ != 1;
517 sleep 1;
519 exec "/sbin/init";
520 warn "/sbin/init: $!\n";
522 exec "/bin/sh";
523 warn "/bin/sh: $!\n";
525 # nothing else we can do
526 for(;;) { }