#!/usr/bin/perl -w # Script to run games on a separate X session Version 2.0.2 # © Laurens Buhler 2003-2004 under the terms of the GPL-2, special thanks go to: the ppl in #gtk-perl on irc.gimp.org and Troy Henderson # For help use: xgame-gtk2 --help use strict; our (@choice, @list, @options, @ARGV, @target_table, $choice, $window, $table, $scrolled_window, $gamelist, $tooltips, $cmd_entry, $cmdoption); # Grabbing the data we need from the source file &list; # Checking is user wants the gui or not if ($ARGV[0] && $ARGV[0] =~/-([nNidD])/){ &console; } elsif ($ARGV[0] && $ARGV[0] eq "--help"){ &help; } elsif (eval "use Gtk2; 1"){ use Gtk2 -init; set_locale Gtk2; use constant FALSE => 0; use constant TRUE => 1; # Starting the gui &drawxgame; main Gtk2; exit(0); } else { die "Sorry, no gtk2-perl module was found, exiting...\n"; } # Here come the subroutines sub drawxgame { # Code needed for dnd @target_table = ({target => "STRING", flags => [], info => FALSE}, {target => "text/plain", flags => [], info => FALSE}, {target => "application/x-rootwin-drop", flags => [], info => TRUE},); # Create the window $window = Gtk2::Window->new("toplevel"); $window->signal_connect("delete_event", \&quit); $window->set_title("Xgame 2.0"); # Creating a VBox to arrange the games my $vbox = new Gtk2::VBox(FALSE, 5); $vbox->set_border_width(5); $window->add($vbox); $vbox->show(); # This is the scrolled window to put the List widget inside $scrolled_window = new Gtk2::ScrolledWindow(undef, undef); $scrolled_window->set_size_request(250, 400); $scrolled_window->set_policy('automatic', 'automatic'); $vbox->add($scrolled_window); $scrolled_window->show(); &gamelist; # Create a hbox to fit the cmdline opt. entry my $hbox_ent = new Gtk2::HBox(FALSE, 0); $vbox->pack_start($hbox_ent, FALSE, TRUE, 0); $hbox_ent->show(); # Create the entry and tooltips $tooltips = Gtk2::Tooltips->new; my $label = Gtk2::Label->new ("Cmdline arg: "); $cmd_entry = new Gtk2::Entry(); $tooltips->set_tip ($cmd_entry, "Command line arguments to go with the game command"); $hbox_ent->pack_start ($label, FALSE, FALSE, 0); $hbox_ent->pack_start ($cmd_entry, TRUE, TRUE, 0); # Create another hbox to fit the buttons my $hbox = new Gtk2::HBox(FALSE, 0); $vbox->pack_start($hbox, FALSE, TRUE, 0); $hbox->show(); # Creating the control buttons for Xgame for (["gtk-new", "Add a new game", \&add_edit_dialog, "add"],["gtk-preferences", "Xgame Preferences", \&pref_dialog, ""],["gtk-quit", "Exit Xgame", \&quit, ""]){ my $button = Gtk2::Button->new_from_stock ("$_->[0]"); $tooltips->set_tip ($button, "$_->[1]"); $button->signal_connect("clicked", $_->[2], $_->[3]); $hbox->pack_start($button, TRUE, TRUE, 0); } $tooltips->enable; $window->show_all(); } sub gamelist { $table->destroy if $table; $_ = @list; my $table_height = sprintf ("%.f",($_)); $table = new Gtk2::Table($table_height, 6, TRUE); $scrolled_window->add_with_viewport($table); my $start_height = 0; my $stop_height = 1; for (@list){ @_ = split/\t/; my $game_btn = new Gtk2::Button("$_[0]"); $game_btn->signal_connect(button_press_event =>\&right_click, "$start_height"); $game_btn->signal_connect("clicked", \&checkopts, "$start_height"); $game_btn->drag_dest_set('all', ['copy', 'move'], @target_table); $game_btn->signal_connect("drag_data_received", \&drag_data_received, $start_height); $game_btn->drag_source_set (['button1_mask', 'button3_mask'], ['copy', 'move'], @target_table); $game_btn->signal_connect ("drag_data_get", \&source_drag_data_get, "Xgame $start_height"); $table->attach_defaults($game_btn, 0, 6, $start_height++, $stop_height++); } $table->show_all; } sub list { # Checking if the gamelist exists $gamelist = "$ENV{HOME}/.xgamelist"; write_out($gamelist, "") if !-f "$gamelist"; # Here we grab the list of games @list = grep {!/^\s*$/} cat($gamelist); if(grep (/\[options\]/, @list)){ for (0 .. 3){ unshift (@options, (pop @list)); } } else { @options = ("[options]\t\n", "xconfig\t\n", "xinitops\t\n", "default\t\n"); } } sub checkbin { # Checking for a commandline option to pass on (my $input = $_[1])++; my $command = "$0 -i $input &"; if ($cmdoption){ undef $cmdoption; for (0, 0, 0){ splice @ARGV, $_, 1; } $command =~s/^(.*)&$/$1-c @ARGV &/; } # And this is where we process the choice @choice = split/\t/,$list[--$input]; # Writing files down before starting the program write_out($gamelist, @list, @options); # Here we make sure the exe is correct and fire up if so my @exe = split/ /,$choice[1]; chomp $exe[0]; if ($exe[0] =~/\//){ chomp (my $executable = qx|echo $exe[0]|); if (-f "$executable"){ system "$command"; } else { warn_dialog("Invalid executable!"); } } elsif (qx|which $exe[0] 2>&1| =~/which\: no $exe[0] in/){ warn_dialog("Invalid executable!"); } else { system "$command"; } } sub startgame { print ("Starting $choice[0]...\n"); # Checking for another instance of xgame chomp (my $xinit = (qx|ls ~/.xinitrc.* 2>&1|)[0]); $xinit = "$ENV{HOME}/.xinitrc" if $xinit !~ /\.xinitrc\.\w{6,6}/; # Backing up existing .xinitrc to random temp file chomp (my $tmpxinit = qx|mktemp ~/.xinitrc.XXXXXXX|); if (-f $xinit){ write_out($tmpxinit, cat($xinit)); } else { write_out($tmpxinit, ""); } # Creating new .xinitrc from input and defined options if ($cmdoption){ undef $cmdoption; for (0, 0, 0){ splice @ARGV, $_, 1; } $choice[1] =~s/^(.*)$/$1 @ARGV/; } chomp $choice[1]; # The new .xinitrc get's written (my $xinitopts = (split/\t/,$options[2])[1]) =~s/\n//; write_out("$ENV{HOME}/.xinitrc", "$xinitopts\n", "exec $choice[1]"); # Now we fire up the X server with the specified game (qx|ls /tmp/.X*lock|)[-1] =~ /(\d+)/; (my $xnumber = $1)++; (my $xconfig = (split/\t/,$options[1])[1]) =~s/\n//; $xconfig =~s/(.*)/-xf86config $1/ if $xconfig ne ""; system "startx -- :$xnumber $xconfig"; # Placing the backup back to where it belongs unlink $xinit; if (cat($tmpxinit) ne ""){ rename ($tmpxinit, "$ENV{HOME}/.xinitrc") or die "Error moving file: $!\n"; } else { unlink $tmpxinit; } } sub checkopts { my $cmd_text = $cmd_entry->get_text(); if ($cmd_text ne ""){ $cmdoption = 1; @ARGV = ("", "", "", $cmd_text); } checkbin("", $_[-1]); } sub add_edit_dialog { if ($_[1] =~/add/){ ask_data("", "", "Add"); } else { my @data = split/\t/,$list[$choice]; ask_data($data[0], $data[1], "Edit"); } } sub right_click { $choice = $_[-1]; my ($widget, $event) = @_; return 0 unless 3 == $event->button; my @items = ({ path => '/Properties', item_type => '', extra_data => 'gtk-properties', callback => \&add_edit_dialog }, { path => '/Delete', callback => \&ask_delete, item_type => '', extra_data => 'gtk-delete' }); my $factory = Gtk2::ItemFactory->new('Gtk2::Menu', '
', undef); $factory->create_items('foo', @items); my $menu = $factory->get_widget('
'); $menu->popup (undef, undef, undef, undef, $event->button, $event->time); } sub ask_data { my ($name, $switch, $command, $mode); ($mode, $name) = ($_[2], $_[0]); if ($_[1]=~/(.*?)\s+(.*)/){ $command = $1; $switch = $2; } else { ($command, $switch) = ("", ""); } my $dialog = Gtk2::Dialog->new("$mode a game", $window, ['destroy-with-parent'], 'gtk-close', 'close'); $dialog->set_border_width(5); my (@hbox, @entry); my $n = 0; for (["Name", $name, "The name for your entry"], ["Command", $command, "The command for your entry"], ["Switch", $switch, "Additional switches to go after the command"]){ $hbox[$n] = Gtk2::HBox->new (FALSE, 0); my $label = Gtk2::Label->new ($_->[0].":\t"); $hbox[$n]->pack_start ($label, FALSE, FALSE, 0); $entry[$n] = new Gtk2::Entry(); $tooltips->set_tip ($entry[$n], $_->[2]); $entry[$n]->set_text($_->[1]); $hbox[$n]->pack_start ($entry[$n++], FALSE, FALSE, 0); } for (@hbox){ $dialog->vbox->pack_start ($_, FALSE, FALSE, 0); } $dialog->show_all; my $response = $dialog->run; ($name, $command, $switch) = ($entry[0]->get_text, $entry[1]->get_text, $entry[2]->get_text); if ($command ne "" && $name ne ""){ if ($mode eq "Add"){ push @list, "$name\t$command $switch\n"; } else { $list[$choice] = "$name\t$command $switch\n"; } &gamelist; } $dialog->destroy; } sub ask_delete { my $game = (split/\t/,$list[$choice])[0]; my $dialog = Gtk2::Dialog->new("Delete Game", $window, ['destroy-with-parent'], 'gtk-ok', 'ok', 'gtk-cancel', 'cancel'); my $hbox = Gtk2::HBox->new (FALSE, 0); $hbox->set_border_width (5); $dialog->vbox->pack_start ($hbox, FALSE, FALSE, 0); my $label = new Gtk2::Label("Really delete \"$game\"?"); my $image = Gtk2::Image->new_from_stock ('gtk-dialog-question', 'dialog'); $hbox->pack_start($image, FALSE, FALSE, 0); $hbox->pack_start($label, FALSE, FALSE, 0); $dialog->show_all(); my $response = $dialog->run; if ($response eq 'ok'){ splice @list, $choice, 1; &gamelist; } $dialog->destroy; } sub pref_dialog { my $dialog = Gtk2::Dialog->new("Preferences", $window, ['destroy-with-parent'], 'gtk-close', 'close'); my (@hbox, @entry); my @items = ({}, {label=>"X config file", tooltip => "The X server config file to be used"}, {label=>"Start Command", tooltip => "Extra command to be executed by default"}, {label=>"Default Game", tooltip => "Pick your default game"}, {button=>"Sort Gamelist", tooltip => "Sort the gamelist alphabetically"}); for (1..4){ $hbox[$_] = Gtk2::HBox->new (FALSE, 0); $dialog->vbox->pack_start ($hbox[$_], FALSE, FALSE, 0); if ($_ ne "4"){ my $label = new Gtk2::Label($items[$_]->{label}.":\t "); $hbox[$_]->pack_start ($label, FALSE, FALSE, 0); } else { my $button = Gtk2::Button->new ($items[$_]->{button}); $tooltips->set_tip ($button, $items[$_]->{tooltip}); $hbox[$_]->pack_start($button, TRUE, TRUE, 0); $button->signal_connect (clicked => sub { @list = sort {uc($a) cmp uc($b)} @list; &gamelist; }); } if ($_ eq "3"){ my $opt = Gtk2::OptionMenu->new; my $menu = Gtk2::Menu->new; my $n = 0; for ("none", @list){ my @x = split/\t/,$_; my $item = Gtk2::MenuItem->new ($x[0]); if ($n ne "0"){ $item->signal_connect (activate => sub { $options[3] =~s/(.*?\t).*/$1$_[-1]/; }, $n); } else { $item->signal_connect (activate => sub { $options[3] =~s/(.*?\t).*/$1/; }); } $item->show; $menu->append ($item); $n++; } $opt->set_menu ($menu); (my $default = (split/\t/,$options[3])[1]) =~s/\n//; if ($default =~/(\d+)/){ $opt->set_history ($1); } else { $opt->set_history (0); } $tooltips->set_tip ($opt, $items[$_]->{tooltip}); $hbox[$_]->pack_start ($opt, FALSE, FALSE, 0); } elsif ($_ eq "1" || $_ eq "2"){ (my $entry = (split/\t/,$options[$_])[1]) =~s/\n//; $entry[$_] = new Gtk2::Entry(); $tooltips->set_tip ($entry[$_], $items[$_]->{tooltip}); $entry[$_]->set_text($entry); $hbox[$_]->pack_start ($entry[$_], TRUE, TRUE, 0); } } $dialog->show_all(); my $response = $dialog->run; for (1..2){ my $newpref = $entry[$_]->get_text; $options[$_] =~s/(.*?\t).*/$1$newpref/; } $dialog->destroy; } sub warn_dialog { if ($window){ my $dialog = Gtk2::Dialog->new("Error", $window, ['destroy-with-parent'], 'gtk-close', 'close'); my $hbox = Gtk2::HBox->new (FALSE, 0); $hbox->set_border_width (5); $dialog->vbox->pack_start ($hbox, FALSE, FALSE, 0); my $label = new Gtk2::Label("$_[-1]"); my $image = Gtk2::Image->new_from_stock ('gtk-dialog-warning', 'dialog'); $hbox->pack_start($image, FALSE, FALSE, 0); $hbox->pack_start($label, FALSE, FALSE, 0); $dialog->show_all(); my $response = $dialog->run; $dialog->destroy; } else { die "$_[-1]\n"; } } sub source_drag_data_get { my $data = $_[2]; $data->set ($data->target, 8, $_[-1]) if $_[3] ne TRUE; } sub drag_data_received { my $data = $_[4]; if (($data->length >= 0) && ($data->format == 8)){ my $drag = sprintf ("%s", $data->data); if ($drag ne "Xgame $_[-1]" && $drag =~/Xgame\s(\d+)/){ my $moving = $list[$1]; splice @list, $1, 1; splice @list, $_[-1], 0, $moving; &gamelist; } } } sub console { $_ = @list; if ($1 eq "i"){ $cmdoption = 1 if $ARGV[2] && $ARGV[2]=~/-c/; @choice = split/\t/,$list[--$ARGV[1]]; &startgame; } elsif ($1 eq "n"){ if (!$ARGV[1] || $ARGV[1] !~/^\d+$/ || $ARGV[1] > $_ || $ARGV[1] < 1){ die ("Invalid number, terminating...\n"); } else { $cmdoption = 1 if $ARGV[2] && $ARGV[2]=~/-c/; checkbin("", --$ARGV[1]); } } elsif ($1 eq "d"){ (my $input = (split/\t/,$options[3])[1]) =~s/\n//; if ($input !~/^\d+$/ || $input > $_ || $input < 1){ die ("Invalid default game, terminating...\n"); } else { $cmdoption = 1 if $ARGV[1] && $ARGV[1]=~/-c/; splice @ARGV, 1, 0, ""; &checkbin("", --$input); } } } sub help { print ("Usage: xgame-gtk2 [OPTION] or: xgame-gtk2 \n"); print ("Options:\n\t-d,\t\t use the specified default game\n"); print ("\t-n,\t\t instantly run the choosen game (-n )\n"); print ("\t-n/-d <> -c <>,\t run the chosen/default game with commandline arguments (-c )\n"); print ("\t--help,\t\t this message\n"); print ("Gamelist specific:\n"); print ("\tThe gamelist is located at ~/.xgamelist.\n"); print ("\tIt must be edited and maitained at all times through Xgame.\n"); print ("\tNot doing this could result in failure!\n"); } sub write_out { $_ = shift @_; open WRITE, ">", $_ or die "Error writing '$_': $!\n"; print WRITE @_; close WRITE; } sub cat { open MYFILE, $_[0] or die "$!"; @_ = ; close MYFILE; return (wantarray) ? @_ : join("", @_); } sub quit { write_out($gamelist, @list, @options); Gtk2->main_quit; return FALSE; }