#!/usr/bin/perl -w
#####################################################################
## lcdplaylist v1.0
##
## displays filelist, playlist and play info on a 20x4 LCD display
## reacts on keyborad & IR remote control, pilots a player
##
## author: Matthieu Weber (mweber@mit.jyu.fi)
##
## Requirements : dmms, lcdproc, lirc
##
#####################################################################
$DEBUG = 1; # other values: 0
#### Config #########################################################
my @mixer_channels = ('v', 'w'); # v = master w = PCM
my %mixer_init_level = $DEBUG ? ('v' => 0, 'w' => 0) : ('v' => 80, 'w' => 80);
my $cd_device="/dev/cdrom";
my $cd_root="/cdrom";
my $rejectlist = "\.txt\$|\.log\$|\.bmp\$|\.jpg\$|\.gif\$|\.png\$|\.cddb\$|md5sums";
my $info_scroll_line="title"; # other values: "none", "title", "album", "author"
my $playlist_displayed_info="title"; # other values: "full", "title", "album", "author"
my $scroll_speed=4;
my $startup_sound = "/hal_ok.ogg";
#my $startup_sound = "/home/mweber/linux/freevo/testfiles/Music/ThomasRusiak-Hiphopper.mp3";
my %ir_action=(
"OPEN/CLOSE" => {
"info" => {"action" => "TOGGLE_MOUNT"},
"playlist" => {"action" => "TOGGLE_MOUNT"},
"filelist" => {"action" => "TOGGLE_MOUNT"},
},
"MODE" => {
"info" => {"action" => "DISPLAY_MODE_SWITCH"},
"playlist" => {"action" => "DISPLAY_MODE_SWITCH"},
"filelist" => {"action" => "DISPLAY_MODE_SWITCH"},
},
"TEXT" => {
"playlist" => {"action" =>"PLAYLIST_TEXT_SWITCH"},
"info" => {"action" => "DISPLAY_TEXT_SWITCH"},
},
"2" => {
"playlist" => {"action" => "PLAYLIST_UP", "repeat" => 2},
"filelist" => {"action" => "FILELIST_UP", "repeat" => 2},
},
"4" => {
"playlist" => {"action" => "PLAYLIST_LEFT"},
"filelist" => {"action" => "FILELIST_LEFT"},
},
"6" => {
"playlist" => {"action" => "PLAYLIST_RIGHT"},
"filelist" => {"action" => "FILELIST_RIGHT"},
},
"8" => {
"playlist" => {"action" => "PLAYLIST_DOWN", "repeat" => 2},
"filelist" => {"action" => "FILELIST_DOWN", "repeat" => 2},
},
# "+" => {
# "playlist" => {"action" => "FILELIST_ADD"},
# "filelist" => {"action" => "PLAYLIST_PLAY"},
# },
# "0" => {
# "playlist" => {"action" => "PLAYLIST_CLEAR"},
# },
# "-" => {
# "playlist" => {"action" => "PLAYLIST_DEL"},
# },
# "A.F/P" => {
# "info" => {"action" => "VOLUME_UP 2", "repeat" => 1},
# "playlist" => {"action" => "VOLUME_UP 2", "repeat" => 1},
# "filelist" => {"action" => "VOLUME_UP 2", "repeat" => 1},
# },
# "B.-/--" => {
# "info" => {"action" => "VOLUME_DOWN 2", "repeat" => 1},
# "playlist" => {"action" => "VOLUME_DOWN 2", "repeat" => 1},
# "filelist" => {"action" => "VOLUME_DOWN 2", "repeat" => 1},
# },
# "MEMO" => {
# "info" => {"action" => "MUTE"},
# "playlist" => {"action" => "MUTE"},
# "filelist" => {"action" => "MUTE"},
# },
"TIME" => {
"info" => {"action" => "TIME"},
},
">" => {
"info" => {"action" => "PLAY"},
"playlist" => {"action" => "PLAYLIST_PLAY"},
"filelist" => {"action" => "FILELIST_ADD"},
},
"||" => {
"info" => {"action" => "PAUSE"},
"playlist" => {"action" => "PLAYLIST_DEL"},
},
"[]" => {
"info" => {"action" => "STOP"},
"playlist" => {"action" => "PLAYLIST_CLEAR"},
},
">>|" => {
"info" => {"action" => "NEXT", "repeat" => 2},
"playlist" => {"action" => "NEXT", "repeat" => 2},
},
"|<<" => {
"info" => {"action" => "PREV", "repeat" => 2},
"playlist" => {"action" => "PREV", "repeat" => 2},
},
">>" => {
"info" => {"action" => "FWD 3", "repeat" => 2},
},
"<<" => {
"info" => {"action" => "BWD 3", "repeat" => 2},
},
);
my %kbd_action = ();
if ($DEBUG) {
%kbd_action = (
"." => {
"info" => {"action" => "TOGGLE_MOUNT"},
"playlist" => {"action" => "TOGGLE_MOUNT"},
"filelist" => {"action" => "TOGGLE_MOUNT"},
},
"i" => {
"playlist" => {"action" =>"PLAYLIST_UP"},
"filelist" => {"action" =>"FILELIST_UP"},
"info" => {"action" => "PREV"},
},
"j" => {
"playlist" => {"action" =>"PLAYLIST_LEFT"},
"filelist" => {"action" =>"FILELIST_LEFT"},
"info" => {"action" => "BWD 5"},
},
"k" => {
"playlist" => {"action" =>"PLAYLIST_RIGHT"},
"filelist" => {"action" =>"FILELIST_RIGHT"},
"info" => {"action" => "FWD 5"},
},
"m" => {
"playlist" => {"action" =>"PLAYLIST_DOWN"},
"filelist" => {"action" =>"FILELIST_DOWN"},
"info" => {"action" => "NEXT"},
},
"+" => {
"playlist" => {"action" =>"PLAYLIST_PLAY"},
"filelist" => {"action" =>"FILELIST_ADD"},
"info" => {"action" => "PLAY"},
},
"0" => {
"playlist" => {"action" =>"PLAYLIST_CLEAR"},
"info" => {"action" => "STOP"},
},
"9" => {
"playlist" => {"action" =>"PLAYLIST_DEL"},
"info" => {"action" => "PAUSE"},
},
" " => {
"playlist" => {"action" =>"DISPLAY_MODE_SWITCH"},
"filelist" => {"action" =>"DISPLAY_MODE_SWITCH"},
"info" => {"action" =>"DISPLAY_MODE_SWITCH"},
},
"t" => {
"playlist" => {"action" =>"PLAYLIST_TEXT_SWITCH"},
"info" => {"action" =>"DISPLAY_TEXT_SWITCH"},
},
"e" => {
"info" => {"action" =>"TIME"},
},
);
}
else {
%kbd_action = (
"e" => {
"info" => {"action" => "TOGGLE_MOUNT"},
"playlist" => {"action" => "TOGGLE_MOUNT"},
"filelist" => {"action" => "TOGGLE_MOUNT"},
},
"t" => {
"playlist" => {"action" =>"PLAYLIST_UP"},
"filelist" => {"action" =>"FILELIST_UP"},
"info" => {"action" => "PREV"},
},
"h" => {
"playlist" => {"action" =>"PLAYLIST_LEFT"},
"filelist" => {"action" =>"FILELIST_LEFT"},
"info" => {"action" => "BWD 5"},
},
"k" => {
"playlist" => {"action" =>"PLAYLIST_RIGHT"},
"filelist" => {"action" =>"FILELIST_RIGHT"},
"info" => {"action" => "FWD 5"},
},
"f" => {
"playlist" => {"action" =>"PLAYLIST_DOWN"},
"filelist" => {"action" =>"FILELIST_DOWN"},
"info" => {"action" => "NEXT"},
},
"i" => {
"playlist" => {"action" =>"PLAYLIST_PLAY"},
"filelist" => {"action" =>"FILELIST_ADD"},
"info" => {"action" => "PLAY"},
},
"r" => {
"playlist" => {"action" =>"PLAYLIST_CLEAR"},
"info" => {"action" => "STOP"},
},
"y" => {
"playlist" => {"action" =>"PLAYLIST_DEL"},
"info" => {"action" => "PAUSE"},
},
"u" => {
"playlist" => {"action" =>"DISPLAY_MODE_SWITCH"},
"filelist" => {"action" =>"DISPLAY_MODE_SWITCH"},
"info" => {"action" =>"DISPLAY_MODE_SWITCH"},
},
);
}
#### End Config #####################################################
use IO::Socket;
use Fcntl;
use Term::ReadKey;
my %reset_priority_time;
my %reset_priority_value;
my $volume_before_mute = -1;
my ($lcd_width,$lcd_height,$cell_width,$cell_height);
my $filelist_first_line;
my $filelist_cursor_pos_min;
my $filelist_cursor_pos_max;
my $filelist_cursor_pos;
my $display_mode = "filelist"; # other value are "playlist" and "info"
my %files;
my $lock_file="/var/lock/lcdplaylist.lock";
my $playlist_pos = -1;
my $is_playing = 0;
my $is_paused = 0;
my $got_info = 0;
my $time_mode = 0; # 0 = elapesd 1 = remaining 2 = track total
my @async_queue=();
my $async_event_processed = 1;
my $cd_volume_label="";
my $playlist_edit_pos=-1;
my $playlist_is_empty=1;
my %titres_txt_db;
my @dir_pos_history = ();
my $cd_ready = 0;
my $cd_drive_closed_empty = 1;
my $tray_position = "closed"; # other value: "open"
$SIG{PIPE}=\&bp;
$SIG{TERM}=\&silent_bp;
$SIG{INT}=\&silent_bp;
$SIG{__DIE__}=\&bp;
## Mixer functions ################################################
sub set_mixer_value {
my ($channel,$param) = @_;
system("aumix -$channel $param"); }
sub get_mixer_value {
my $channel = $mixer_channels[0];
my $val=`aumix -$channel q`;
my ($l,$r) = ($val =~ /\w+ (\d+), (\d+)/);
}
sub mixer_init {
foreach $c (@mixer_channels) {
&set_mixer_value($c,$mixer_init_level{$c});
}
}
sub set_bar {
my($widget,$y,$value,$force) = @_;
my $maxbarwidth = ($lcd_width - 7) * $cell_width;
my $barwidth = int($value*$maxbarwidth/100); if($force || ($barwidth != $old_barwidth{$widget} && ($barwidth%5 == 0 || $barwidth%5 == 1)) ) {
&send_lcd("widget_set mixer $widget 7 $y $barwidth") ;
$old_barwidth{$widget} = $barwidth;
}
}
sub unmute {
if($volume_before_mute != -1) {
&set_mixer_value($mixer_channels[0],$volume_before_mute);
$volume_before_mute = -1;
}
}
## File list functions ##############################################
sub find {
my @l = @_;
my @out;
foreach $e (@l) {
next if $e =~ /^\./;
next if $e =~ $rejectlist;
if (-d "$e" && $depth != 0) {
}
else {
}
}
}
sub load_file_list {
$files{root} = $root;
$files{list
} = [ sort &find(1, $root)]; $files{nbelem} = @{$files{list}};
my $nblines = $filelist_cursor_pos_max - $filelist_cursor_pos_min + 1;
if ($files{nbelem} > $nblines &&
$files{pos} + $nblines > $files{nbelem
}) { $filelist_cursor_pos = $filelist_cursor_pos_max - $files{nbelem
} + $files{pos} + 1; }
else {
$filelist_cursor_pos = $filelist_cursor_pos_min;
}
&show_files();
}
sub show_files {
my %file_menu_items=("up" => "^", "down" => "v", "left" => "<", "right" => ">");
my $menu = "Browse: ";
my $display_line_amount = $lcd_height - $filelist_first_line + 1;
my $beg = $files{pos} - $filelist_cursor_pos; for($i=0; $i<$display_line_amount; $i++) {
my $left = 2;
my $top = $filelist_first_line + $i;
my $right = $lcd_width;
my $bot = $top;
my $txt = defined $files{list
}->[$beg+$i] ? &latin1_to_ascii(&get_last_path_part($files{list
}->[$beg+$i])) : " "; $txt = substr($txt, 0, $right - $left + 1) if $filelist_cursor_pos != $i; &send_lcd("widget_set filelist line$i $left $top $right $bot h $scroll_speed {$txt}");
}
&send_lcd("widget_set filelist cursor 1 ".($filelist_first_line + $filelist_cursor_pos)." {>}");
$menu .= $file_menu_items{"left"}." " if $files{root} ne $cd_root;
$menu .= $file_menu_items{"right"}." " if -d
$files{list
}->[$files{pos}]; $menu .= $file_menu_items{"up"}." " if $files{pos} > 0; $menu .= $file_menu_items{"down"}." " if $files{pos} < $files{nbelem
} -1; }
else {
$menu .= "mount";
}
&show_one_line_menu($menu);
}
sub clear_files {
my $display_line_amount;
#print "clear_files\n";
$display_line_amount = $lcd_height - $filelist_first_line + 1;
for($i=0; $i<$display_line_amount; $i++) {
my $left = 2;
my $top = $filelist_first_line + $i;
my $right = $lcd_width;
my $bot = $top;
&send_lcd("widget_set filelist line$i $left $top $right $bot h $scroll_speed { }");
}
&send_lcd("widget_set filelist cursor 1 ".($filelist_first_line)." { }");
&show_one_line_menu("Browse: mount ");
%files = ();
}
sub get_last_path_part {
my ($r) = ($p=~/\/([^\/]+)\/?$/);
}
sub show_one_line_menu {
#print "show_one_line_menu\n";
&send_lcd("widget_set filelist header 1 1 {$text}");
}
## Playlist functions ################################################
sub playlist_add {
my @l = @_;
my $pl_was_empty = $#playlist == -1 ? 1 : 0;
foreach $file (@l) {
push @playlist, {"title" => "", "length" => "", "file" => $file}; my $titres_txt = &get_titres_txt_path($file);
if ($titres_txt ne "") {
&playlist_get_info($#playlist); # File is the last pushed, which is at
# the end of the list
}
}
if($playlist_pos == -1) {
$playlist_pos = 0;
}
if($pl_was_empty == 1) {
# Just get the first element of the list displayed :)
&dmms_play() if !$DEBUG;
}
}
sub get_titres_txt_path {
$file =~ s/\/[^\/]+$//;
$titres_txt = "$file/titres.txt";
return -f
$titres_txt ? $titres_txt : ""; }
sub latin1_to_ascii {
$s =~ tr/àáâãäåçèéêëìíîïòóôöøñùúûüýÿ/aaaaaaceeeeiiiiooooonuuuuyy/;
$s =~ tr/ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÒÓÔÖØÑÙÚÛÜÝÿ/AAAAAACEEEEIIIIOOOOONUUUUYY/;
$s =~ s/æ/ae/g;
$s =~ s/Æ/AE/g;
$s =~ s/ß/ss/g;
}
sub get_album_from_titres_txt {
my $state="title";
my %album;
my $titres_txt = &get_titres_txt_path($file);
if ($titres_txt eq "") {
}
if (! exists $titres_txt_cb{$titres_txt}) { open(TITRES
, $titres_txt); while(<TITRES>) {
s/\r?\n//;
next if /^\s*$/ && $state eq "tracks";
if ($state eq "title") {
$album{title} = &latin1_to_ascii($_);
$state = "composer";
}
elsif ($state eq "composer") {
my $tmp = $_;
my ($comp, $year) = ($tmp =~ /(.*), (\d*)$/);
if ($comp eq "") {
$album{composer} = &latin1_to_ascii($tmp);
$album{year} = 0;
}
else {
$album{composer} = &latin1_to_ascii($comp);
$album{year} = $year;
}
$state = "tracks";
}
elsif ($state eq "tracks") {
my $l = $_;
next unless $l =~ /^ ?\d/;
my ($num, $ttitle) = ($l =~ /^( ?\d+)[\. ]?-? ?(.*)/);
next if $num > 999;
$num += 0;
$album{tracks
}->[$num] = sprintf("%02d - %s",$num,&latin1_to_ascii($ttitle)); }
}
if (defined $album{title
} && $album{title
} !~ /^\s*$/) { $titres_txt_cb{$titres_txt} = \%album;
}
else {
}
}
return %{$titres_txt_cb{$titres_txt}}; }
# Initializes an asynchrous request
sub playlist_get_info {
my $file = $playlist[$pos]->{file};
my %album = &get_album_from_titres_txt($file);
if (%album) {
my ($tracknum) = ($file =~ /(\d+)\./);
$tracknum += 0;
&playlist_get_info_cb ($pos, $album{tracks}->[$tracknum],
$album{composer}, $album{title}, 0);
&playlist_refresh_display();
}
else {
push(@async_queue, {"data" => $pos, "act" => \&playlist_get_info_act,
"cb" => \&playlist_get_info_cb});
}
}
# What to do when the asynchronous request is to be processed
sub playlist_get_info_act {
&send_dmms("INFO ".$playlist[$data]->{file});
}
# What to do with the result of the asynchronous request
sub playlist_get_info_cb {
my ($pos, $title, $author, $album, $length) = @_;
my $file = $playlist[$pos]->{file};
if ($length >= 0 && defined $title) { if (! defined $playlist[$pos]->{title
} || $playlist[$pos]->{title
} eq "") { $playlist[$pos]->{title} = &latin1_to_ascii(&clean_title($title));
}
$playlist[$pos]->{author
} = defined $author ? &latin1_to_ascii($author) : "" if ! defined $playlist[$pos]->{author
}; $playlist[$pos]->{album
} = defined $album ? &latin1_to_ascii($album) : "" if ! defined $playlist[$pos]->{album
}; $playlist[$pos]->{length} = $length; $playlist[$pos]->{valid} = 1;
&playlist_refresh_display();
}
else {
&playlist_delete($pos);
}
}
sub playlist_delete {
$playlist_pos -- if $pos < $playlist_pos;
$playlist_pos = 0 if $playlist_pos < 0 && $#playlist > -1;
$playlist_pos = $#playlist if $playlist_pos > $#playlist;
$playlist_edit_pos = $#playlist if $playlist_edit_pos > $#playlist;
&playlist_refresh_display();
}
sub playlist_get_pos {
}
sub clean_title {
$s =~ s/\....$//;
$s =~ s/_/ /g;
$s =~ s/%20/ /g;
}
sub playlist_get_album {
if($#playlist > -1 && $pos <= $#playlist) {
if(defined $playlist[$pos]->{valid
} && $playlist[$pos]->{valid
} == 1) { my $t = $playlist[$pos]->{album} ne "" ? $playlist[$pos]->{album} : "Unknown album";
$t = " " if $t eq "";
}
else {
}
}
else {
}
}
sub playlist_get_author {
if($#playlist > -1 && $pos <= $#playlist) {
if(defined $playlist[$pos]->{valid
} && $playlist[$pos]->{valid
} == 1) { my $t = $playlist[$pos]->{author} ne "" ? $playlist[$pos]->{author} : "Unknown artist";
$t = " " if $t eq "";
}
else {
}
}
else {
}
}
sub playlist_get_title {
if($#playlist > -1 && $pos <= $#playlist) {
if(defined $playlist[$pos]->{valid
} && $playlist[$pos]->{valid
} == 1) { my $t = $playlist[$pos]->{title} ne "" ? $playlist[$pos]->{title} : "Unknown title";
$t = " " if $t eq "";
}
else {
&playlist_get_info($pos);
return &clean_title(&get_last_path_part($playlist[$pos]->{file
})); }
}
else {
}
}
#sub playlist_get_title {
# my $pos = shift(@_);
#
# if($#playlist > -1 && $pos <= $#playlist) {
# if(defined $playlist[$pos]->{valid} && $playlist[$pos]->{valid} == 1) {
# my $t = "";
# if ($info_scroll_line eq "full") {
# $t .= $playlist[$pos]->{author} . ", " if $playlist[$pos]->{author} ne "";
# $t .= $playlist[$pos]->{album} . ": " if $playlist[$pos]->{album} ne "";
# $t .= $playlist[$pos]->{title};
# }
# elsif ($info_scroll_line eq "title") {
# $t .= $playlist[$pos]->{title} ne "" ? $playlist[$pos]->{title} : "Unknown";
# }
# elsif ($info_scroll_line eq "author") {
# $t .= $playlist[$pos]->{author} ne "" ? $playlist[$pos]->{author} : "Unknown";
# }
# elsif ($info_scroll_line eq "album") {
# $t .= $playlist[$pos]->{album} ne "" ? $playlist[$pos]->{album} : "Unknown";
# }
# $t = " " if $t eq "";
# return $t
# }
# else {
# &playlist_get_info($pos);
# return &clean_title(&get_last_path_part($playlist[$pos]->{file}));
# }
# }
# else {
# return "";
# }
#}
sub playlist_clear {
if($#playlist > -1) {
$playlist_pos = -1;
$playlist_edit_pos = -1;
$playlist_cursor_pos = $playlist_cursor_pos_min;
@playlist = ();
&playlist_refresh_display();
#&display_title("Empty playlist");
&display_title("CD: $cd_volume_label");
&display_info ("","",0);
}
}
sub playlist_refresh_display {
return if $display_mode ne "playlist"; my $display_line_amount = $lcd_height - $playlist_first_line + 1;
if ($#playlist > -1 ) {
$playlist_edit_pos = 0 if $playlist_edit_pos == -1;
&send_lcd("widget_set playlist plinfo 5 1 {".($playlist_edit_pos+1)."/".($#playlist+1)."}");
}
else {
&send_lcd("widget_set playlist plinfo 5 1 {Empty}");
}
my $beg = $playlist_edit_pos - $playlist_cursor_pos;
if ($beg < 0) {
$beg = 0;
$playlist_cursor_pos = $#playlist;
}
$playlist_cursor_pos = $playlist_cursor_pos_min if $playlist_cursor_pos < $playlist_cursor_pos_min;
&send_lcd("widget_set playlist editcursor 1 ".($playlist_first_line + $playlist_cursor_pos)." {-}") if $#playlist > -1;
&send_lcd("widget_set playlist playcursor $lcd_width 1 { }");
for($i=0; $i<$display_line_amount; $i++) {
my $left = 2;
my $top = $playlist_first_line + $i;
my $right = $lcd_width;
my $bot = $top;
my $txt = "";
if($playlist_displayed_info eq "title") {
$txt = &playlist_get_title($beg + $i);
}
elsif($playlist_displayed_info eq "album") {
$txt = &playlist_get_album($beg + $i);
}
elsif ($playlist_displayed_info eq "author") {
$txt = &playlist_get_author($beg + $i);
}
elsif ($playlist_displayed_info eq "full") {
$txt = &playlist_get_author($beg + $i).", ".
&playlist_get_album($beg + $i).": ".
&playlist_get_title($beg + $i);
}
$txt = substr($txt, 0, $right - $left + 1) if $playlist_cursor_pos != $i; $txt = " " if $txt eq "";
&send_lcd("widget_set playlist line$i $left $top $right $bot h $scroll_speed {$txt}");
if ($#playlist > -1 ) {
if ($playlist_pos == $beg + $i && $txt ne " ") {
if ($playlist_edit_pos != $playlist_pos) {
&send_lcd("widget_set playlist playcursor 1 ".($playlist_first_line + $i)." {|}");
}
else {
&send_lcd("widget_set playlist editcursor 1 ".($playlist_first_line + $i)." {+}");
}
}
}
else {
&send_lcd("widget_set playlist editcursor 1 ".($playlist_first_line + $i)." { }");
}
}
}
## Dmms Player functions ###############################################
sub dmms_play {
&send_dmms("PLAY ".$playlist[$playlist_pos]->{file}) if -f $playlist[$playlist_pos]->{file};
}
sub dmms_next {
if($playlist_pos < $#playlist && $playlist_pos > -1) {
#print "playlist_pos = $playlist_pos pl_length = ".$#playlist."\n";
$playlist_pos++;
&dmms_play();
}
else {
$is_playing = 0;
}
}
sub dmms_prev {
if ($playlist_pos > 0) {
$playlist_pos--;
&dmms_play();
}
}
sub dmms_pause {
&send_dmms("PAUSE");
}
sub dmms_stop {
&send_dmms("STOP");
}
sub dmms_fwd {
&send_dmms("FWD $t");
}
sub dmms_bwd {
&send_dmms("BWD $t");
}
## Info window functions ###############################################
sub status_to_symbol {
if ($status eq "STOP") {
}
elsif ($status eq "PLAY") {
}
elsif ($status eq "PAUSE") {
}
else {
}
}
sub display_time {
#my $x = $lcd_width > 16 ? $lcd_width - 10 : $lcd_width - 4;
my $x = $lcd_width - 5;
my $sign = " ";
if ($#playlist > -1) {
if ($time_mode == 1) {
$time = $playlist[$playlist_pos]->{length} - $time; $sign = "-";
}
elsif ($time_mode == 2) {
$time = $playlist[$playlist_pos]->{length}; }
}
&send_lcd(sprintf("widget_set info time $x 4 {$sign%02d:%02d}",$time/60,$time%60
)); }
sub display_pl_length {
my $pl_length = shift(@_);
if ($lcd_width > 16) {
if ($pl_length < 100) {
&send_lcd("widget_set info pl_length 6 2 {$pl_length}");
}
else {
&send_lcd("widget_set info pl_length 6 2 {}");
}
}
}
sub display_length {
my $length = $playlist[$playlist_pos]->{length}; my $x = $lcd_width - 4;
if ($lcd_width > 16 && $length < 6000000) {
&send_lcd(sprintf("widget_set info length $x 4 {%02d:%02d}", $length/60, $length%60
)); }
}
sub display_status {
&send_lcd("widget_set info status 1 4 {".&status_to_symbol($status)."}");
}
sub display_pos {
&send_lcd(sprintf("widget_set info pos 4 4 {%#2d}",$pos+1)); }
sub display_title {
my ($title) = @_;
if ($info_scroll_line ne "title") {
$title = substr($title,0,$lcd_width); }
&send_lcd("widget_set info title 1 1 $lcd_width 1 h $scroll_speed {$title}");
}
sub display_album {
my ($album) = @_;
if ($info_scroll_line ne "album") {
$album = substr($album,0,$lcd_width); }
&send_lcd("widget_set info album 1 2 $lcd_width 2 h $scroll_speed {$album}")
}
sub display_author {
my ($author) = @_;
if ($info_scroll_line ne "author") {
$author = substr($author,0,$lcd_width); }
&send_lcd("widget_set info author 1 3 $lcd_width 3 h $scroll_speed {$author}");
}
sub display_info {
my ($rate, $freq, $nch) = @_;
if ($rate ne "") {
$rate /= 1000;
}
$freq = int($freq / 1000) if $freq ne ""; if ($nch == 2 ) {
$nch = "oo";
}
elsif ($nch == 1 ) {
$nch = "o";
}
else {
$nch = "";
}
if($lcd_width < 16) {
&send_lcd("widget_set info info 4 4 {$rate $freq $nch}");
}
else {
&send_lcd("widget_set info info 5 4 {$rate $freq $nch}");
}
}
## LCD Management Functions ############################################
sub raise_screen {
my ($scr,$prio,$lower,$lower_prio) = @_;
#print "raise_screen\n";
&send_lcd("screen_set $scr name $scr priority $prio");
$reset_priority_time{$scr} = time()+$lower unless $lower eq "noauto"; $reset_priority_value{$scr} = $lower_prio unless $lower eq "noauto";
}
sub lower_screen {
my ($scr,$prio) = @_;
#print "lower_screen\n";
&send_lcd("screen_set $scr name $scr priority $prio");
$reset_priority_time{$scr} = 0;
}
sub show_message {
my ($text) = @_;
#print "show_message\n";
&send_lcd("widget_set message text 1 1 {$text}");
&raise_screen("message",16,5,224);
}
## CD-ROM drive functions #################################################
sub open_tray {
}
sub close_tray {
if ($DEBUG) {
print STDERR "3 seconds to close the tray...\n"; }
else {
system("eject -t $cd_device"); }
}
sub mount {
if (open(CDROM
,"$cd_device")) { &display_title("Mounting...");
}
else {
}
}
sub umount {
}
sub cd_is_mounted {
my $r = 0;
die "/proc not mounted" unless -e
"/proc/mounts"; open(PROC
,"/proc/mounts"); while(<PROC>) {
s/\r?\n//;
if($_ =~ $cd_device) {
$r = 1;
last;
}
}
}
## Acting Function ########################################################
sub act {
my ($act,$params) = ($t =~ /^(\S+)\s*(.*)/);
#print "$act\n";
if ($act eq "FILELIST_UP") {
if ($filelist_cursor_pos > $filelist_cursor_pos_min) {
$filelist_cursor_pos--;
}
&show_files();
}
}
elsif ($act eq "PLAYLIST_UP") {
if ($playlist_edit_pos > 0 && $#playlist > -1) {
if ($playlist_cursor_pos > $playlist_cursor_pos_min) {
$playlist_cursor_pos--;
}
$playlist_edit_pos--;
&playlist_refresh_display();
}
}
elsif ($act eq "FILELIST_DOWN") {
if ($files{pos} < $files{nbelem
}-1) { if ($filelist_cursor_pos < $filelist_cursor_pos_max) {
$filelist_cursor_pos++;
}
&show_files();
}
}
elsif ($act eq "PLAYLIST_DOWN") {
if ($playlist_edit_pos < $#playlist && $#playlist > -1) {
if ($playlist_cursor_pos < $playlist_cursor_pos_max) {
$playlist_cursor_pos++;
}
$playlist_edit_pos++;
&playlist_refresh_display();
}
}
elsif ($act eq "FILELIST_LEFT") {
my $p = $files{root};
if($p ne $cd_root) {
my ($np) = ($p=~/^(.*)\/[^\/]+\/?$/);
$files{pos} = pop(@dir_pos_history); &load_file_list($np);
}
}
elsif ($act eq "PLAYLIST_LEFT") {
if($playlist_edit_pos > 0) {
splice @playlist, $playlist_edit_pos-1, 2, ($playlist[$playlist_edit_pos], $playlist[$playlist_edit_pos-1]); if ($playlist_pos == $playlist_edit_pos) {
$playlist_pos--;
}
elsif ($playlist_pos == $playlist_edit_pos - 1) {
$playlist_pos++;
}
if ($playlist_cursor_pos > $playlist_cursor_pos_min) {
$playlist_cursor_pos--;
}
$playlist_edit_pos--;
&playlist_refresh_display();
}
}
elsif ($act eq "FILELIST_RIGHT") {
my $f = $files{list
}->[$files{pos}]; if(-d $f) {
push (@dir_pos_history, $files{pos}); &load_file_list($f);
}
}
elsif ($act eq "PLAYLIST_RIGHT") {
if($playlist_edit_pos < $#playlist) {
splice @playlist, $playlist_edit_pos, 2, ($playlist[$playlist_edit_pos+1], $playlist[$playlist_edit_pos]); if ($playlist_pos == $playlist_edit_pos) {
$playlist_pos++;
}
elsif ($playlist_pos == $playlist_edit_pos + 1) {
$playlist_pos--;
}
if ($playlist_cursor_pos < $playlist_cursor_pos_max) {
$playlist_cursor_pos++;
}
$playlist_edit_pos++;
&playlist_refresh_display();
}
}
elsif ($act eq "FILELIST_ADD") {
my $f = $files{list
}->[$files{pos}]; #print "f = $f\n";
if(-f $f) {
&playlist_add(($f));
}
elsif(-d $f) {
my @dircontent = sort &find(-1, $f); #print "adding :\n ".join("\n",@dircontent)."\n";
&playlist_add(@dircontent);
}
}
elsif ($act eq "PLAYLIST_PLAY") {
if($#playlist > -1) {
$playlist_pos = $playlist_edit_pos;
&playlist_refresh_display();
&dmms_stop();
&dmms_play();
}
}
elsif ($act eq "PLAYLIST_DEL") {
my $restart_play = 0;
if($playlist_pos == $playlist_edit_pos && $is_playing == 1) {
&dmms_stop();
$restart_play = 1;
}
&playlist_delete($playlist_edit_pos);
$playlist_pos = $#playlist if $#playlist < $playlist_pos;
if($restart_play == 1) {
&dmms_play();
}
else {
if ($#playlist > -1) {
&display_title(&playlist_get_title($playlist_pos));
&display_album(&playlist_get_album($playlist_pos));
&display_author(&playlist_get_author($playlist_pos));
}
else {
#&display_title("Empty playlist");
&display_title("CD: $cd_volume_label");
&display_album(" ");
&display_author(" ");
}
}
}
elsif ($act eq "PLAYLIST_CLEAR") {
&dmms_stop();
&playlist_clear();
$display_mode = "filelist";
&lower_screen("playlist",192);
&raise_screen("filelist",64,"noauto");
}
elsif ($act eq "DISPLAY_MODE_SWITCH") {
## filelist -> info -> playlist
## info is never raised/lowered
if($display_mode eq "filelist") {
&lower_screen("filelist",192);
$display_mode = "info";
}
elsif($display_mode eq "info") {
$display_mode = "playlist";
&playlist_refresh_display();
&raise_screen("playlist",64,"noauto");
}
elsif($display_mode eq "playlist") {
&lower_screen("playlist",192);
&raise_screen("filelist",64,"noauto");
$display_mode = "filelist";
}
}
elsif ($act eq "DISPLAY_TEXT_SWITCH") {
# switch sequence:
# title -> album -> author -> none -> title ...
if ($info_scroll_line eq "title") {
$info_scroll_line = "album";
}
elsif ($info_scroll_line eq "album") {
$info_scroll_line = "author";
}
elsif ($info_scroll_line eq "author") {
$info_scroll_line = "none";
}
elsif ($info_scroll_line eq "none") {
$info_scroll_line = "title";
}
# &playlist_refresh_display();
&display_title(&playlist_get_title($playlist_pos));
&display_author(&playlist_get_author($playlist_pos));
&display_album(&playlist_get_album($playlist_pos));
}
elsif ($act eq "PLAYLIST_TEXT_SWITCH") {
# switch sequence:
# title -> album -> author -> full -> title ...
if ($playlist_displayed_info eq "title") {
$playlist_displayed_info = "album";
}
elsif ($playlist_displayed_info eq "album") {
$playlist_displayed_info = "author";
}
elsif ($playlist_displayed_info eq "author") {
$playlist_displayed_info = "full";
}
elsif ($playlist_displayed_info eq "full") {
$playlist_displayed_info = "title";
}
&playlist_refresh_display();
}
elsif ($act eq "TOGGLE_MOUNT") {
my $res;
if(&cd_is_mounted() == 1) {
&dmms_stop() if $is_playing;
&clear_files();
&display_title("Eject...");
&display_author(" ");
&display_album(" ");
$res = &umount();
if ($res == 0) {
$cd_ready = 0;
&playlist_clear();
&lower_screen("filelist",192);
&lower_screen("playlist",192);
%titres_txt_db = ();
$display_mode = "info";
&open_tray();
&display_title("Open");
$tray_position = "open";
}
else {
&show_message("Umount error");
&display_title("No CD");
}
}
else {
&close_tray();
$res = &mount();
if ($res == 0) {
$cd_ready = 1;
$tray_position = "closed";
$cd_volume_label = &get_cd_volume_label();
&display_title("CD: $cd_volume_label");
&display_author(" ");
&display_album(" ");
&display_info ("","",0);
&load_file_list($cd_root);
&raise_screen("filelist",64,"noauto");
$display_mode = "filelist";
}
else {
# Mount error
&display_title("No CD");
if ($tray_position eq "closed") {
&open_tray();
&display_title("Open");
&display_author(" ");
&display_album(" ");
$tray_position = "open";
}
else {
$tray_position = "closed";
}
}
}
}
elsif ($act eq "VOLUME_UP") {
&raise_screen("mixer",32,2,192);
&unmute();
foreach $c (@mixer_channels) {
&get_mixer_value();
&set_mixer_value($c,"+$params");
}
&set_bar("volbar",1,&get_mixer_value($mixer_channels[0]));
}
elsif ($act eq "VOLUME_DOWN") {
&raise_screen("mixer",32,2,192);
&unmute();
foreach $c (@mixer_channels) {
&get_mixer_value();
&set_mixer_value($c,"-$params");
}
&set_bar("volbar",1,&get_mixer_value($mixer_channels[0]));
}
elsif ($act eq "MUTE") {
&raise_screen("mixer",32,1,192);
if($volume_before_mute == -1) {
$volume_before_mute = &get_mixer_value($mixer_channels[0]);
&send_lcd("widget_set mixer volmute 5 1 {X}");
&set_mixer_value($mixer_channels[0],"0");
}
else {
&set_mixer_value($mixer_channels[0],$volume_before_mute);
&send_lcd("widget_set mixer volmute 5 1 { }");
$volume_before_mute = -1;
}
}
elsif ($act eq "TIME") {
$time_mode = ($time_mode + 1) % 3 if $is_playing == 1;
}
elsif ($act eq "PLAY") {
&dmms_play();
}
elsif ($act eq "STOP") {
if ($is_playing == 1) {
&dmms_stop();
}
else {
$playlist_pos = 0;
&display_title(&playlist_get_title($playlist_pos));
&display_author(&playlist_get_author($playlist_pos));
&display_album(&playlist_get_album($playlist_pos));
&display_time("0");
&display_info("", "", 0);
}
}
elsif ($act eq "PAUSE") {
&dmms_pause();
}
elsif ($act eq "NEXT") {
&dmms_next();
&playlist_refresh_display();
}
elsif ($act eq "PREV") {
&dmms_prev();
&playlist_refresh_display();
}
elsif ($act eq "FWD") {
&dmms_fwd($params);
}
elsif ($act eq "BWD") {
&dmms_bwd($params);
}
}
## General Functions ###################################################
sub write_lock_file {
}
sub delete_lock_file {
}
sub check_lock_file {
if(-f "$lock_file") {
my $pid = <F>;
print STDERR "lcdmix is already running with PID $pid ($lock_file)\n"; }
else {
}
}
# Signal handler
sub bp {
&delete_lock_file();
ReadMode 0;
}
else {
}
}
# Silent signal handler
sub silent_bp {
&delete_lock_file();
ReadMode 0;
}
sub send_lcd {
#print STDERR "->LCD: $s\n";
}
sub send_dmms {
#print STDERR "->DMMS: $s\n";
}
sub get_ir_key {
my %key;
$key{repeat
} = hex($l[1]); $key{name} = $l[2];
$key{remote} = $l[3];
}
sub get_cd_volume_label {
my $cd_title;
$cd_title =~ s/ +$//;
}
#########################################################################
## Main #################################################################
#########################################################################
exit if &check_lock_file() == 0; &write_lock_file();
# Connect to the server...
$lcdsock = IO::Socket::INET->new(
Proto => "tcp",
PeerAddr => "localhost",
PeerPort => "13666",
)
|| die "Cannot connect to LCDproc port\n";
# Make sure our messages get there right away
$lcdsock->autoflush(1);
sleep 1; # Give server plenty of time to notice us... &send_lcd("hello");
$irsock = IO::Socket::UNIX->new(
Type => SOCK_STREAM,
Peer => "/dev/lircd",
)
|| die "Cannot connect to /dev/lircd\n";
$dmmssock = IO::Socket::UNIX->new(
Type => SOCK_STREAM,
Peer => "/tmp/dmms",
)
|| die "Cannot connect to /tmp/dmms\n";
# Turn off blocking mode...
fcntl($lcdsock, F_SETFL
, O_NONBLOCK
); fcntl($irsock, F_SETFL
, O_NONBLOCK
); fcntl($dmmssock, F_SETFL
, O_NONBLOCK
);
# Set initial mixer values
&mixer_init() if $DEBUG == 0;
&send_dmms("PLAY $startup_sound") if -e $startup_sound;
ReadMode 4;
while(1) {
if ($async_event_processed == 1 && $#async_queue > -1) {
&{$async_queue[0]->{act}}($async_queue[0]->{data});
$async_event_processed = 0;
}
while(defined ($lcdline=<$lcdsock>)) { #print STDERR "LCD: $lcdline\n";
if($lcdline =~ /^connect/) {
($lcd_width,$lcd_height,$cell_width,$cell_height) =
($lcdline=~/wid (\d+) hgt (\d+) cellwid (\d+) cellhgt (\d+)/);
$filelist_first_line = $lcd_height > 1 ? 2 : 1;
$filelist_cursor_pos_min = 0;
$filelist_cursor_pos_max = $lcd_height - $filelist_first_line;
$filelist_cursor_pos = $filelist_cursor_pos_min;
$playlist_first_line = $lcd_height > 1 ? 2 : 1;
$playlist_cursor_pos_min = 0;
$playlist_cursor_pos_max = $lcd_height - $playlist_first_line;
$playlist_cursor_pos = $playlist_cursor_pos_min;
# Set up some screen widgets...
&send_lcd("client_set name lcdplaylist");
&send_lcd("screen_add info");
&send_lcd("screen_set info name info heartbeat off priority 128");
&send_lcd("widget_add info time string");
&send_lcd("widget_add info pl_length string");
&send_lcd("widget_add info length string");
&send_lcd("widget_add info status string");
&send_lcd("widget_add info pos string");
&send_lcd("widget_add info title scroller");
&send_lcd("widget_add info album scroller");
&send_lcd("widget_add info author scroller");
&send_lcd("widget_add info info string");
&display_title("No CD");
&display_status("STOP");
&display_time("0");
&send_lcd("screen_add filelist");
&send_lcd("screen_set filelist name filelist heartbeat off priority 192");
&send_lcd("widget_add filelist header string");
&send_lcd("widget_add filelist line0 scroller");
&send_lcd("widget_add filelist line1 scroller");
&send_lcd("widget_add filelist line2 scroller");
&send_lcd("widget_add filelist cursor string");
&show_files();
&send_lcd("screen_add playlist");
&send_lcd("screen_set playlist name playlist heartbeat off priority 192");
&send_lcd("widget_add playlist header string");
&send_lcd("widget_add playlist plinfo string");
&send_lcd("widget_add playlist line0 scroller");
&send_lcd("widget_add playlist line1 scroller");
&send_lcd("widget_add playlist line2 scroller");
&send_lcd("widget_add playlist editcursor string");
&send_lcd("widget_add playlist playcursor string");
&send_lcd("widget_set playlist header 1 1 {PL:}");
&send_lcd("screen_add mixer");
&send_lcd("screen_set mixer name mixer heartbeat off priority 192");
&send_lcd("widget_add mixer vollbl string");
&send_lcd("widget_set mixer vollbl 1 1 {Vol}");
&send_lcd("widget_add mixer volmute string");
&send_lcd("widget_add mixer volbar hbar");
$old_barwidth{volbar}=0;
&set_bar("volbar",1,&get_mixer_value($mixer_channels[0]),1);
&send_lcd("screen_add message");
&send_lcd("screen_set message name message heartbeat off priority 224");
&send_lcd("widget_add message text string");
}
elsif($lcdline =~ /^success/) {
#print STDERR "LCD: $lcdline\n";
}
elsif($lcdline =~ /^listen|^ignore/) {
#print STDERR "LCD: $lcdline\n";
}
else {
print STDERR "LCD: $lcdline\n"; }
}
while(defined ($irline=<$irsock>)) { my %k=&get_ir_key($irline);
#print STDERR "IR: $k{name}\n";
if(exists $ir_action{$k{name
}}->{$display_mode}) { if(exists $ir_action{$k{name
}}->{$display_mode}->{"repeat"}) { &act($ir_action{$k{name}}->{$display_mode}->{"action"}) if $k{repeat} % $ir_action{$k{name}}->{$display_mode}->{"repeat"} == 0;
}
else {
&act($ir_action{$k{name}}->{$display_mode}->{"action"}) if $k{repeat} == 0;
}
}
}
while (defined ($key = ReadKey
(-1))) { #print "$key\n";
if ($key eq "q") {
&dmms_stop() if $is_playing == 1;
&silent_bp();
}
if(exists $kbd_action{$key}->{$display_mode}) { #print "$key\n";
#print "Doing ".$kbd_action{$key}->{$display_mode}->{"action"}."\n";
&act($kbd_action{$key}->{$display_mode}->{"action"});
}
}
while(defined ($dmmsline=<$dmmssock>)) { #print STDERR "dmms: $dmmsline\n";
if ($dmmsline =~ /^PLAY/) {
&display_status("PLAY");
$is_playing = 1;
$got_info = 0;
}
elsif ($dmmsline =~ /^STOP$/) {
&display_status("STOP");
$is_playing = 0;
$is_paused = 0;
&display_time("0");
}
elsif ($dmmsline =~ /^STOPPED$/) {
if ($playlist_pos == $#playlist) {
&dmms_stop();
&display_status("STOP");
$is_playing = 0;
$is_paused = 0;
&display_time("0");
}
else {
if ($got_info == 1) {
&dmms_next();
}
else {
# File is probably not handled by dmms or *very* short -> discard
&playlist_delete($playlist_pos);
## Replay, same pos, but actually file after the discarded one
&dmms_play();
}
}
&playlist_refresh_display();
}
elsif ($dmmsline =~ /^PAUSE/) {
if ($is_playing == 1) {
if ($is_paused == 0) {
&display_status("PAUSE");
}
else {
&display_status("PLAY");
}
$is_paused = ! $is_paused;
}
}
elsif ($dmmsline =~ /^TIME/) {
my ($t) = ($dmmsline =~ /^TIME (.*)$/);
&display_time($t);
}
elsif ($dmmsline =~ /^INFO/) {
my ($author, $album, $title);
my ($data, $length, $rate, $freq, $nch) =
($dmmsline =~ /^INFO ([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)$/);
if ($data =~ /\|/) {
($author, $album, $title) = split(/\|/, $data); }
else {
$title = $data;
$author = "";
$album = "";
}
if ($#playlist >= 0) {
&playlist_get_info_cb($playlist_pos, $title, $author, $album, $length);
&display_title(&playlist_get_title($playlist_pos));
&display_author(&playlist_get_author($playlist_pos));
&display_album(&playlist_get_album($playlist_pos));
&display_time("0") if $got_info == 0;
&display_info($rate, $freq, $nch);
$got_info = 1;
}
}
elsif ($dmmsline =~ /^TITLE/) {
my ($author, $album, $title);
my ($length, $data) = ($dmmsline =~ /^TITLE ([-\d.]+) (.*)/);
if ($data =~ /\|/) {
($author, $album, $title) = split(/\|/, $data); }
else {
$title = $data;
$author = "";
$album = "";
}
my $ev = shift(@async_queue); &{$ev->{cb}}($ev->{data}, $title, $author, $album, $length);
$async_event_processed = 1;
}
elsif ($dmmsline =~ /^HELLO/) {
#print STDERR "dmms: $dmmsline\n";
}
elsif ($dmmsline =~ /^ERR|^UNKN/) {
#print STDERR "dmms: $dmmsline\n";
}
}
foreach $screen (keys(%reset_priority_time)) { if(time() > $reset_priority_time{$screen} && $reset_priority_time{$screen} != 0) { &lower_screen($screen,$reset_priority_value{$screen});
}
}
}
ReadMode 0;