#!/usr/bin/perl use strict; use Getopt::Std; use XML::Simple; use Festival::Client; use Data::Dumper; # $Id: joymenu 74 2003-07-11 15:09:44Z steve $ # Copyright (C) 2003 Steve Pomeroy # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # $Log$ # Revision 1.4 2003/07/11 15:09:44 steve # added initial speed control # # Revision 1.3 2003/04/02 06:59:28 steve # *welds on a boiler-plate* # # load command-line options getopts("sc:d:h"); our( $opt_s, $opt_c, $opt_d, $opt_h ); usage() if $opt_h; #### Defaults ###### my $conffile = "$ENV{'HOME'}/.joymenurc.xml"; my $dev = "/dev/js0"; #### overrides ###### $conffile = $opt_c if $opt_c; $dev = $opt_d if $opt_d; #### globals ###### my %alias = (); my %alias_titles = (); my $buttons = 0; my $mainmenu; my @curmenu; my @globals = ({}); my $festival = Festival::Client->new(); my $speak = 1; my $speak_speed = 1; open JSIN, "<", $dev or die "cannot open device $dev for reading: $!"; binmode JSIN; # struct js_event { # __u32 time; /* event timestamp in milliseconds */ # __s16 value; /* value */ # __u8 type; /* event type */ # __u8 number; /* axis/button number */ # }; # 8 bytes long print "loading config\n"; my $config = load_config( $conffile ); print "loading aliases\n"; %alias = %{load_aliases( $config )}; $mainmenu = $config; push @curmenu, $mainmenu; #print Dumper $config; #exit; print "ready\n"; #### main loop ###### while( read JSIN, my $foo, 8 ){ # 8 bytes # break apart the struct my @data = unpack "LsCC", $foo; parse_event(@data); } ############################# subs ###################################### sub parse_event{ my @data = @_; return if $data[2] != 1; # ignore anything that isn't an actual button press ############### menus #################### if( exists $curmenu[$#curmenu]->{'menu'} && $data[1] && (my $newmenu = is_activated( $curmenu[$#curmenu]->{'menu'}, 2**$data[3] )) ){ if( exists $curmenu[$#curmenu]->{'menu'}->{$newmenu} ){ push @curmenu, $curmenu[$#curmenu]->{'menu'}->{$newmenu}; } message( "$newmenu menu"); my %local_globals = (); # load up any globals foreach my $type (keys %{$curmenu[$#curmenu]} ){ if( ref $curmenu[$#curmenu]->{$type} eq "HASH" ){ foreach my $child (keys %{$curmenu[$#curmenu]->{$type}} ){ my $menucmd = $curmenu[$#curmenu]->{$type}->{$child}; # add it to the top of the global stack if( ref $menucmd eq "HASH" && $menucmd->{'global'} eq "yes" ){ $local_globals{$child} = $menucmd; } } } } push @globals, \%local_globals; ############ normal commands #################### }elsif( exists $curmenu[$#curmenu]->{'command'} && $data[1] && (my $newmenu = is_activated( $curmenu[$#curmenu]->{'command'}, 2**$data[3] ))){ message( $newmenu ); if( exists $curmenu[$#curmenu]->{'command'}->{$newmenu}->{'content'} ){ my $command = $curmenu[$#curmenu]->{'command'}->{$newmenu}->{'content'}; my $output = `$command &`; $festival->say($output); } ############### menu commands ################### }elsif( exists $curmenu[$#curmenu]->{'menucmd'} && $data[1] && (my $newmenu = is_activated( $curmenu[$#curmenu]->{'menucmd'}, 2**$data[3] ))){ menucommand( $newmenu ); }elsif( $data[1] && (my $newmenu = is_activated( \@globals, 2**$data[3] ))){ menucommand( $newmenu ); }elsif( !$data[1] || $data[2] == 129 || $data[2] == 130 ){ # suck up key-up and load events for now }else{ # non-mapped buttons } } sub menucommand($){ my ($cmd) = @_; if( $cmd eq "back" ){ if( $#curmenu ){ pop @curmenu; pop @globals; message("back"); }else{ message("top menu"); } # describes the current menu }elsif( $cmd eq "describe" ){ message( $curmenu[$#curmenu]->{'title'}, 1 ); my $msg = ""; foreach my $type ( keys %{$curmenu[$#curmenu]} ){ if( ref $curmenu[$#curmenu]->{$type} eq "HASH" ){ foreach my $child (keys %{$curmenu[$#curmenu]->{$type}} ){ my $menucmd = $curmenu[$#curmenu]->{$type}->{$child}; if( ref $menucmd eq "HASH" ){ my $blah = "for"; $blah = "to" if ($type eq "command" || $type eq "menucmd"); $msg .= "Press " . describe_button($menucmd->{'button'}). " $blah " . describe($menucmd, $child) . ". "; } } } } message( $msg, 1 ); # toggles speaking of the menus }elsif( $cmd eq "speak" ){ $speak = !$speak; message( "menu speaking is now ".($speak ? "on" : "off") ); }elsif( $cmd eq "faster" ){ $speak_speed += 0.5; message( "Increasing speaking speed." ); }elsif( $cmd eq "slower" ){ $speak_speed -= 0.5; message( "Increasing speaking speed." ); # goes back to the top level menu }elsif( $cmd eq "top" ){ splice @curmenu, 0, 1; splice @globals, 0, 1; }else{ message("Unknown menu command: $cmd"); } } sub describe_button($){ my ( $button ) = @_; if( exists $alias_titles{$button} ){ return $alias_titles{$button}; }else{ return $button; } } sub describe($){ my( $what, $name ) = @_; if( exists $what->{'title'} ){ return $what->{'title'}; }else{ return $name; } } sub is_activated($$){ my ($menu, $button) = @_; my $found = ""; # deal with globals if( ref $menu eq "ARRAY" ){ for( my $a = $#{$menu}; $a >= 0 && !$found; $a-- ){ $found = is_activated($menu->[$a], $button); } } return $found if $found; foreach my $item (keys %{$menu}){ if( ref $menu->{$item} eq "HASH" && exists $menu->{$item}->{'button'} && $button == parse_alias($menu->{$item}->{'button'})){ $found = $item; last; } } return $found; } sub parse_alias($$){ my ($button)= @_; if( exists $alias{$button} ){ return $alias{$button}; } return $button; } sub load_aliases($){ my ($config) = @_; my %aliases; foreach my $alias (keys %{$config->{'alias'}}){ $aliases{$alias} = $config->{'alias'}->{$alias}->{'button'}; $alias_titles{$alias} = $config->{'alias'}->{$alias}->{'title'} if exists $config->{'alias'}->{$alias}->{'title'}; } return \%aliases; } sub load_config($){ my ( $conffile ) = @_; my $xs = new XML::Simple(); my $xml = $xs->XMLin( $conffile, forcearray => ['menu', 'command'], keyattr => ['name'] ); return $xml; } sub message($$){ my( $message, $requested) = @_; print $message, "\n"; $festival->say($message) if ($requested || $speak); } sub usage(){ die< this code released under the GNU GPL USAGE }