From 124562ddd4613041d5a2db9547ab59aa426b098a Mon Sep 17 00:00:00 2001 From: Antoine Ginies Date: Thu, 22 Jul 2004 02:33:39 +0000 Subject: new tool to configure PXE default file --- drakpxelinux.pl | 638 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 638 insertions(+) create mode 100644 drakpxelinux.pl diff --git a/drakpxelinux.pl b/drakpxelinux.pl new file mode 100644 index 0000000..8bdd24d --- /dev/null +++ b/drakpxelinux.pl @@ -0,0 +1,638 @@ +#!/usr/bin/perl +# +# Copyright (C) 2004 by Mandrakesoft aginies _ateuh_ mandrakesoft.com +# +# 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, 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. + +# Quick configuration of PXE menu parameters +# release 0.5 +# use with care developement release.... +# thx R1 for test and some debug + +use lib qw(/usr/lib/libDrakX); +use standalone; +use strict; +use common; +use MDK::Common; +use ugtk2 qw(:ask :helpers :wrappers :create :dialogs); + + +use constant FALSE => 0; +use constant TRUE => 1; + +# ie of entry menu in PXE: +#label linux +# KERNEL images/vmlinuz +# APPEND initrd=images/all.rdz automatic=method:http,interface:eth0,network:dhcp,server:10.0.1.33,directory:/install/ ramdisk_size=64000 root=/dev/ram3 rw vga=788 display=:0 + +my $conf = "/var/lib/tftpboot/X86PC/linux/pxelinux.cfg/default"; +#my $conf = "/tmp/tr"; + +my $help = " +PXE Label: the name displayed in PXE menu (a word/number) +Server: IP address of server, which contains installation directory +Kernel: memdisk or vmlinuz +Initrd: network boot image (network.img ) or all.rdz +Interface: network interface used for the installation process +Network: DHCP or an IP address +Directory: full path to MDK install server directory +Method: installation method: choose NFS or HTTP +Ramsize: ramsize parameter on boot disk +Display: export display on another computer (ie: 10.0.1.33:0) +VGA: if you encounter any problem with VGA, please adjust +"; + +my $date = chomp_(`date +%d-%m-20%y`); + +if (!-f $conf) { + err_dialog("Error !", "missing $conf\n\nPlease install pxe package.") and die; +} else { + cp_af($conf, "$conf-$date"); +} + +my @listpxe; +my @list_method = qw(nfs http); +my @list_ram = qw(48000 32000 64000 1280000); +my @list_vga = qw(788 normal 785 791 792 text); +my @list_eth = qw(eth0 eth1 eth2); + +use constant COLUMN_LABEL => 0; +use constant COLUMN_INFO => 1; +use constant COLUMN_KERNEL => 2; +use constant COLUMN_INITRD => 3; +use constant COLUMN_AUTOMATIC => 4; +use constant COLUMN_INTERFACE => 5; +use constant COLUMN_NETWORK => 6; +use constant COLUMN_SERVER => 7; +use constant COLUMN_DIRECTORY => 8; +use constant COLUMN_RAMDISK => 9; +use constant COLUMN_VGA => 10; +use constant COLUMN_DISPLAY => 11; +use constant COLUMN_OPTION => 12; +use constant NUM_COLUMNS => 13; + + +sub get_items() { + my $info; my $label; my $kernel; my $initrd; + foreach (cat_($conf)) { + if (any { /^label/ } cat_($conf)) { + if (/^label/) { ($label) = /^label\s(.*)/ } + if (/KERNEL/) { ($kernel) = /KERNEL\s(.*)/ } + my ($initrd, $automatic, $interface, $network, $server, $directory, $ramdisk, $vga, $display) = m!\s*APPEND\sinitrd=(.*?rdz)\s\bautomatic=method\b:(nfs|http),interface:(eth0|eth1|eth2),network:(dhcp|10.0.1.33),server:(.*?),directory:(.*?)\s\bramdisk_size\b=(.*?)\sroot=/dev/ram3\srw\svga=(.*?)\sdisplay=(.*?)\s!; + # in case of parameter not on this order (previous pxe default file) + $initrd or my ($init) = m!\s*APPEND\s\binitrd\b=(.*?)\s!; + $ramdisk or my ($ramdis) = m!\bramdisk_size\b=(\d+)\s!; + $automatic or my ($automati) = m!\bautomatic\b=method:(nfs|http)!; + $vga or my ($vg) = m!\bvga\b=(.*?)\s!; + $display or my ($displa) = m!\bdisplay\b=(.*?)\s!; + $interface or my ($interfac) = m!\binterface\b:(eth0|eth1|eth2)!; + $network or my ($networ) = m!\bnetwork\b:(dhcp|10.0.1.33)!; + $server or my ($serve) = m!\bserver\b:(.*?),!; + $directory or my ($director) = m!\bdirectory\b:(.*?)\s!; + my $information = "$label $automati"; + my $initall; my $ramdiskall; my $autoall; my $vgaall; my $displayall; my $interfaceall; my $networkall; my $serverall; my $directoryall; + if ($initrd) { $initall = $initrd } else { $initall = $init } + if ($ramdisk) { $ramdiskall = $ramdisk } else { $ramdiskall = $ramdis } + if ($automatic) { $autoall = $automatic } else { $autoall = $automati } + if ($vga) { $vgaall = $vga } else { $vgaall = $vg } + if ($display) { $displayall = $display } else { $displayall = $displa } + if ($interface) { $interfaceall = $interface } else { $interfaceall = $interfac } + if ($network) { $networkall = $network } else { $networkall = $networ } + if ($server) { $serverall = $server } else { $serverall = $serve } + if ($directory) { $directoryall = $directory } else { $directoryall = $director } + my ($allopt) = m!\s*APPEND\s(.*)!; + my ($autocomp) = m!\bautomatic\b=(.*?)\s!; + my ($devram) = m!root=/dev/ram3\srw!; + my $optionsall = join(' ', grep { ! /$devram/ and ! /initrd=$initall/ and ! /display=$displayall/ and ! /ramdisk_size=$ramdiskall/ and ! /automatic=$autocomp/ and ! /vga=$vgaall/ } split(' ', $allopt)); + + if ($label and $kernel and $initall) { + push @listpxe, { + label => $label, + info => $information, + kernel => $kernel, + initrd => $initall, + automatic => $autoall, + interface => $interfaceall, + network => $networkall, + server => $serverall, + directory => $directoryall, + ramdisk => $ramdiskall, + vga => $vgaall, + display => $displayall, + option => $optionsall, + },; + } + } + } +} + +sub list_label_pxe() { + my @labels; + foreach (cat_($conf)) { + my ($label) = /label\s(.*)/; + $label and push @labels, $label; + } + @labels; +} + +sub set_new_default { + my ($default) = @_; + substInFile { + s/^DEFAULT.*/DEFAULT $default/; + } $conf; +} + +sub get_default_pxe { + my ($defaultpxe) = cat_($conf) =~ /DEFAULT\s+(\S+)/; + return $defaultpxe; +} + +sub get_default_prompt() { + my ($prompt) = cat_($conf) =~ /^PROMPT\s(\d+)/; + return $prompt; +} + +sub get_default_timeout() { + my ($time) = cat_($conf) =~ /^TIMEOUT\s(\d+)/; + return $time; +} + +sub write_conf_pxe { + my $default = get_default_pxe; + my $prompt = get_default_prompt; + my $time = get_default_timeout; + output($conf, <{interface}) { + append_to_file($conf, " +label $a->{label} + KERNEL $a->{kernel} + APPEND initrd=$a->{initrd} automatic=method:$a->{automatic},interface:$a->{interface},network:$a->{network},server:$a->{server},directory:$a->{directory} ramdisk_size=$a->{ramdisk} root=/dev/ram3 rw vga=$a->{vga} display=$a->{display} +"); + } else { + append_to_file($conf, " +label $a->{label} + KERNEL $a->{kernel} + APPEND initrd=$a->{initrd} +"); + } + } +} + +sub create_model { + get_items(); + my $model = Gtk2::ListStore->new(("Glib::String") x13); + foreach my $a (@listpxe) { + my $iter = $model->append; + $model->set($iter, + COLUMN_LABEL, $a->{label}, + COLUMN_INFO, $a->{info}, + COLUMN_KERNEL, $a->{kernel}, + COLUMN_INITRD, $a->{initrd}, + COLUMN_AUTOMATIC, $a->{automatic}, + COLUMN_INTERFACE, $a->{interface}, + COLUMN_NETWORK, $a->{network}, + COLUMN_SERVER, $a->{server}, + COLUMN_DIRECTORY, $a->{directory}, + COLUMN_RAMDISK, $a->{ramdisk}, + COLUMN_VGA, $a->{vga}, + COLUMN_DISPLAY, $a->{display}, + COLUMN_OPTION, $a->{option}, + ), + } + return $model; +} + +sub add_item { + my ($button, $model) = @_; + my @a; + push @a, { + label => "cooker", + info => "install cooker", + kernel => "/images/cooker", + initrd => "cooker.rdz", + automatic => "http", + interface => "eth0", + network => "dhcp", + server => "10.0.1.33", + directory => "/install/cooker", + ramdisk => "64000", + vga => "788", + display => ":0", + option => "acpi=ht", + }; + + my $iter = $model->append; + $model->set($iter, + COLUMN_LABEL, $a[-1]{label}, + COLUMN_INFO, $a[-1]{info}, + COLUMN_KERNEL, $a[-1]{kernel}, + COLUMN_INITRD, $a[-1]{initrd}, + COLUMN_AUTOMATIC, $a[-1]{automatic}, + COLUMN_INTERFACE, $a[-1]{interface}, + COLUMN_NETWORK, $a[-1]{network}, + COLUMN_SERVER, $a[-1]{server}, + COLUMN_DIRECTORY, $a[-1]{directory}, + COLUMN_RAMDISK, $a[-1]{ramdisk}, + COLUMN_VGA, $a[-1]{vga}, + COLUMN_DISPLAY, $a[-1]{display}, + COLUMN_OPTION, $a[-1]{option}, + ); +# write_conf_pxe; +} + +sub remove_item { + my ($widget, $treeview) = @_; + my $model = $treeview->get_model; + my $selection = $treeview->get_selection; + my $iter = $selection->get_selected; + if ($iter) { + my $path = $model->get_path($iter); + my $i = ($path->get_indices)[0]; + ask_okcancel("Remove $listpxe[$i]{label} PXE entry ?") or return; + $model->remove($iter); + splice @listpxe, $i, 1; + } + write_conf_pxe; +} + +sub edit_box_item { + my ($widget, $treeview) = @_; + my $model = $treeview->get_model; + my $selection = $treeview->get_selection; + + my $iter = $selection->get_selected; + if ($iter) { + my $path = $model->get_path($iter); + my $i = ($path->get_indices)[0]; + + my $dialog = new Gtk2::Dialog(); + $dialog->set_modal(0); + + my $label = Gtk2::Entry->new; + $label->set_text($listpxe[$i]{label}); + + my $info = Gtk2::Entry->new; + $info->set_text($listpxe[$i]{info}); + + my $kernel = Gtk2::Entry->new; + $kernel->set_text($listpxe[$i]{kernel}); + # file selection for kernel + my $file_dialogk = new Gtk2::FileSelection("File Selection"); + $file_dialogk->set_modal(TRUE); + $file_dialogk->signal_connect("destroy", sub { Gtk2->exit }); + $file_dialogk->ok_button->signal_connect(clicked => sub { + my $file = $file_dialogk->get_filename; + $kernel->set_text($file); + $file_dialogk->hide; + }, $file_dialogk); + $file_dialogk->cancel_button->signal_connect(clicked => sub { $file_dialogk->hide }); + # button kernel + my $buttonkernel = Gtk2::Button->new("Select kernel"); + $buttonkernel->signal_connect(clicked => sub { $file_dialogk->show }); + + my $initrd = Gtk2::Entry->new; + $initrd->set_text($listpxe[$i]{initrd}); + # file selection for initrd + my $file_dialog = new Gtk2::FileSelection("File Selection"); + $file_dialog->set_modal(TRUE); + $file_dialog->signal_connect("destroy", sub { Gtk2->exit }); + $file_dialog->ok_button->signal_connect(clicked => sub { + my $filei = $file_dialog->get_filename; + $initrd->set_text($filei); + $file_dialog->hide; + }, $file_dialog); + my $buttoninitrd = Gtk2::Button->new("Select initrd"); + $buttoninitrd->signal_connect(clicked => sub { $file_dialog->show }); + + # combo box to pop down automatic installation + my $automatic = Gtk2::Combo->new; + $automatic->set_popdown_strings(@list_method); + $automatic->entry->set_text($listpxe[$i]{automatic}); + + # combo box to pop down list of network interface + my $interface = new Gtk2::Combo(); + $interface->set_popdown_strings(@list_eth); + $interface->entry->set_text($listpxe[$i]{interface}); + + my $network = Gtk2::Entry->new; + $network->set_text($listpxe[$i]{network}); + + my $server = Gtk2::Entry->new; + $server->set_text($listpxe[$i]{server}); + + my $directory = Gtk2::Entry->new; + $directory->set_text($listpxe[$i]{directory}); + + my $ramdisk = new Gtk2::Combo(); + $ramdisk->set_popdown_strings(@list_ram); + $ramdisk->entry->set_text($listpxe[$i]{ramdisk}); + + my $vga = new Gtk2::Combo(); + $vga->set_popdown_strings(@list_vga); + $vga->entry->set_text($listpxe[$i]{vga}); + + my $display = Gtk2::Entry->new; + $display->set_text($listpxe[$i]{display}); + + my $option = Gtk2::Entry->new; + $option->set_text($listpxe[$i]{option}); + + gtkpack_($dialog->vbox, + 0, gtkadd(Gtk2::Frame->new(("Description")), + gtkpack(gtkset_border_width(Gtk2::VBox->new, 2), + gtkadd(Gtk2::HBox->new, + Gtk2::Label->new("Label"), + $label, + ), + gtkadd(Gtk2::HBox->new, + Gtk2::Label->new("Info"), + $info, + ), + ), + ), + 0, gtkadd(Gtk2::Frame->new(("Boot image")), + gtkadd(gtkset_border_width(Gtk2::VBox->new, 2), + gtkadd(Gtk2::HBox->new, + Gtk2::Label->new("Kernel"), + $kernel, + $buttonkernel, + ), + gtkadd(Gtk2::HBox->new, + Gtk2::Label->new("Initrd"), + $initrd, + $buttoninitrd, + ), + ), + ), + 0, gtkadd(Gtk2::Frame->new(("Automatic Options")), + gtkadd(gtkset_border_width(Gtk2::VBox->new, 2), + gtkadd(Gtk2::HBox->new, + Gtk2::Label->new("Automatic"), + $automatic, + ), + gtkadd(Gtk2::HBox->new, + Gtk2::Label->new("Interface"), + $interface, + ), + gtkadd(Gtk2::HBox->new, + Gtk2::Label->new("Network"), + $network, + ), + gtkadd(Gtk2::HBox->new, + Gtk2::Label->new("Server"), + $server, + ), + gtkadd(Gtk2::HBox->new, + Gtk2::Label->new("Directory"), + $directory), + ), + ), + 0, gtkadd(Gtk2::Frame->new(("Display")), + gtkadd(gtkset_border_width(Gtk2::VBox->new, 2), + gtkadd(Gtk2::HBox->new, + Gtk2::Label->new("Vga"), + $vga, + ), + gtkadd(Gtk2::HBox->new, + Gtk2::Label->new("Display"), + $display, + ), + ), + ), + 0, gtkadd(Gtk2::Frame->new(("Other options")), + gtkadd(gtkset_border_width(Gtk2::VBox->new, 2), + gtkadd(Gtk2::HBox->new, + Gtk2::Label->new("Ramdisk"), + $ramdisk, + ), + gtkadd(Gtk2::HBox->new, + Gtk2::Label->new("Option"), + $option, + ), + ), + ), + 0, create_okcancel({ + cancel_clicked => sub { $dialog->destroy }, + ok_clicked => sub { + #ask_okcancel("are you sure you want to update all those values ?"); + $listpxe[$i]{label} = $label->get_text; + $listpxe[$i]{info} = $info->get_text; + $listpxe[$i]{kernel} = $kernel->get_text; + $listpxe[$i]{initrd} = $initrd->get_text; + $listpxe[$i]{automatic} = $automatic->entry->get_text; + $listpxe[$i]{interface} = $interface->entry->get_text; + $listpxe[$i]{network} = $network->get_text; + $listpxe[$i]{server} = $server->get_text; + $listpxe[$i]{directory} = $directory->get_text; + $listpxe[$i]{ramdisk} = $ramdisk->entry->get_text; + $listpxe[$i]{vga} = $vga->entry->get_text; + $listpxe[$i]{display} = $display->get_text; + $listpxe[$i]{option} = $option->get_text; + + # update value in cells + $model->set($iter, + COLUMN_LABEL, $listpxe[$i]{label}, + COLUMN_INFO, $listpxe[$i]{info}, + COLUMN_KERNEL, $listpxe[$i]{kernel}, + COLUMN_INITRD, $listpxe[$i]{initrd}, + COLUMN_AUTOMATIC, $listpxe[$i]{automatic}, + COLUMN_INTERFACE, $listpxe[$i]{interface}, + COLUMN_NETWORK, $listpxe[$i]{network}, + COLUMN_SERVER, $listpxe[$i]{server}, + COLUMN_DIRECTORY, $listpxe[$i]{directory}, + COLUMN_RAMDISK, $listpxe[$i]{ramdisk}, + COLUMN_VGA, $listpxe[$i]{vga}, + COLUMN_DISPLAY, $listpxe[$i]{display}, + COLUMN_OPTION, $listpxe[$i]{option}, + ); + $dialog->destroy; + write_conf_pxe; + }, + }, + ), + ); + $dialog->show_all; + } +} + +sub cell_edited { + my ($cell, $path_string, $new_text, $model) = @_; + my $path = Gtk2::TreePath->new_from_string($path_string); + my $column = $cell->get_data("column"); + my $iter = $model->get_iter($path); + + if ($column == COLUMN_LABEL) { + my $i = ($path->get_indices)[0]; + $listpxe[$i]{label} = $new_text; + $model->set($iter, $column, $listpxe[$i]{label}); + } elsif ($column == COLUMN_INFO) { + my $i = ($path->get_indices)[0]; + $listpxe[$i]{info} = $new_text; + $model->set($iter, $column, $listpxe[$i]{info}); + } elsif ($column == COLUMN_KERNEL) { + my $i = ($path->get_indices)[0]; + if (-f $new_text) { + $listpxe[$i]{kernel} = $new_text; + $model->set($iter, $column, $listpxe[$i]{kernel}); + } + } elsif ($column == COLUMN_INITRD) { + my $i = ($path->get_indices)[0]; + if (-f $new_text) { + $listpxe[$i]{initrd} = $new_text; + $model->set($iter, $column, $listpxe[$i]{initrd}); + } + } elsif ($column == COLUMN_AUTOMATIC) { + my $i = ($path->get_indices)[0]; + if (!member($new_text, @list_method) or ($listpxe[$i]{kernel} =~ /memdisk/)) { + return; + } else { + $listpxe[$i]{automatic} = $new_text; + $model->set($iter, $column, $listpxe[$i]{automatic}); + } + } elsif ($column == COLUMN_INTERFACE) { + my $i = ($path->get_indices)[0]; + if (member($new_text, @list_eth) or ($listpxe[$i]{kernel} != /memdisk/)) { + $listpxe[$i]{interface} = $new_text; + $model->set($iter, $column, $listpxe[$i]{interface}); + } + } elsif ($column == COLUMN_NETWORK) { + my $i = ($path->get_indices)[0]; + if ($listpxe[$i]{kernel} != /memdisk/) { + $listpxe[$i]{network} = $new_text; + $model->set($iter, $column, $listpxe[$i]{network}); + } + } elsif ($column == COLUMN_SERVER) { + my $i = ($path->get_indices)[0]; + if ($listpxe[$i]{kernel} != /memdisk/) { + $listpxe[$i]{server} = $new_text; + $model->set($iter, $column, $listpxe[$i]{server}); + } + } elsif ($column == COLUMN_DIRECTORY) { + my $i = ($path->get_indices)[0]; + if (-d $new_text || ($listpxe[$i]{kernel} != /memdisk/)) { + $listpxe[$i]{directory} = $new_text; + $model->set($iter, $column, $listpxe[$i]{directory}); + } + } elsif ($column == COLUMN_RAMDISK) { + my $i = ($path->get_indices)[0]; + if (member($new_text, @list_ram) || ($listpxe[$i]{kernel} != /memdisk/)) { + $listpxe[$i]{ramdisk} = $new_text; + $model->set($iter, $column, $listpxe[$i]{ramdisk}); + } + } elsif ($column == COLUMN_VGA) { + my $i = ($path->get_indices)[0]; + if (member($new_text, @list_vga) || ($listpxe[$i]{kernel} != /memdisk/)) { + $listpxe[$i]{vga} = $new_text; + $model->set($iter, $column, $listpxe[$i]{vga}); + } + } elsif ($column == COLUMN_DISPLAY) { + my $i = ($path->get_indices)[0]; + if ($listpxe[$i]{kernel} != /memdisk/) { + $listpxe[$i]{display} = $new_text; + $model->set($iter, $column, $listpxe[$i]{display}); + } + } elsif ($column == COLUMN_OPTION) { + my $i = ($path->get_indices)[0]; + $listpxe[$i]{option} = $new_text; + $model->set($iter, $column, $listpxe[$i]{option}); + } + write_conf_pxe; +} + +sub show_help { info_dialog("Help", $help) } + +sub add_columns { + my $treeview = shift; + my $model = $treeview->get_model; + # horrible hack to avoid: GLib-GObject-CRITICAL **: file gobject.c: line 1019 (g_object_set_property): assertion `G_IS_VALUE (value)' failed at drakpxe2.pl line 640. + my $t = "0"; + my @j = qw/Label Info Kernel Initrd Method Interface Network Server Directory Ramdisk vga Display Extra Options/; + each_index { + my $renderer = Gtk2::CellRendererText->new; + $renderer->set(editable => 1); + $renderer->signal_connect(edited => \&cell_edited, $model); + $renderer->set_data(column => $::i); + $treeview->insert_column_with_attributes(-1, $j[$t], $renderer, 'text' => $::i); + $t++; + } (qw/x x x x x x x x x x x x x/); +#(("Label"), ("Info"), ("Kernel"), ("Initrd"), ("Method"), ("Interface"), ("Network"), ("Server"), ("Directory"), ("Ramdisk"), ("vga"), ("Display"), ("Extra"), ("Options")); +} + +# +# Main + +# create model +my $model = create_model(); + +my $window = ugtk2->new('Drakpxe2'); +$window->{rwindow}->set_size_request(800, 400) unless $::isEmbedded; +my $W = $window->{window}; +$W->signal_connect(delete_event => sub { ugtk2->exit }); + +my $treeview = Gtk2::TreeView->new_with_model($model); +$treeview->set_rules_hint(TRUE); +$treeview->get_selection->set_mode('single'); +add_columns($treeview); + +my $labels = new Gtk2::Combo(); +my @labels = list_label_pxe; +my $defaultlabel = get_default_pxe; +$labels->entry->set_text("$defaultlabel"); +$labels->entry->signal_connect("changed", sub { list_label_pxe; my $new_default = $labels->entry->get_text; + set_new_default($new_default); + } + ); + +$labels->set_popdown_strings(@labels); +$labels->entry->set_text("$defaultlabel"); + +$W->add(gtkpack_(Gtk2::VBox->new(0,0), + 0, Gtk2::Banner->new('wiz_default_up', ("DrakPXE default configuration file")), + 1, create_scrolled_window($treeview), + 0, gtkadd(gtkset_border_width(Gtk2::VBox->new, 0,0), + gtkadd(Gtk2::HBox->new(0,0), + $labels, + gtkpack(gtkset_layout(new Gtk2::HButtonBox, 'spread'), + 0, (map { + my ($label, $sub, $widg) = @$_; + gtksignal_connect(Gtk2::Button->new($label), clicked => sub { + $sub->($model, $widg); }) + } ([ ("Help"), \&show_help ], + [ ("Add a PXE entry"), \&add_item, $model ], + [ ("Remove PXE entry"), \&remove_item, $treeview ], + [ ("Edit a PXE entry"), \&edit_box_item, $treeview ], + ) + ), + ), + + ), + ), + ), + ); + +$W->show_all; +Gtk2->main; -- cgit v1.2.1