package Gtk2::URxvt; # I tried to write this with Tk, as it uses less memory and is # more widely available. Alas, Tk is rather broken with respect to # embedding (and many other things, too). =head1 NAME Gtk2::URxvt - widget for embedding rxvt-unicode instances =head1 SYNOPSIS use Gtk2::URxvt; my $term = new Gtk2::URxvt; $window->add ($term); # passing arguments is also possible my $term = new Gtk2::URxvt args => [ -fg => "black", -bg => "green", ... ]; =head1 DESCRIPTION This module implements a gtk+ widget that embeds a urxvt (rxvt-unicode) terminal window. It starts - on demand - a single urxvtd (daemon) and uses it for all terminal widget. Any option valid for urxvtc/urxvt can be used, including C<-pty-fd> to pass your own pty, but excluding C<-embed>, for obvious reasons. The actual terminal process is started as soon as the widget is realized. =over 4 =cut use Carp; use File::Temp; our $VERSION = '0.1'; my ($urxvtd_dir, $urxvtd_socket, $urxvtd_pid, $urxvtd_fh); my %event_cb; # $wid => $cb use Glib::Object::Subclass Gtk2::Socket => signals => { title_changed => { param_types => [Glib::String], }, }, properties => [ Glib::ParamSpec->string ( 'urxvt_basename', 'URxvt command basename', 'The base name of the urxvtd and urxvtc commands (without the trailing \'c\' or \'d\'). Default: $ENV{URXVT_BASENAME} || "urxvt". Only used the first time a URxvt object is created.', $ENV{URXVT_BASENAME} || 'urxvt', [qw(construct-only readable writable)], ), Glib::ParamSpec->scalar ( 'args', 'URxvt commandline arguments', 'An arrayref containing the commandline arguments', [qw(construct-only readable writable)], ), ]; END { kill TERM => $urxvtd_pid if $urxvtd_pid; close $urxvtd_fh if $urxvtd_fh; undef $urxvtd_dir; } sub wm_normal_hints { my ($self) = @_; $self->mapped or return; my ($type, $format, @data) = $self->{plugged}->property_get ( Gtk2::Gdk::Atom->intern ("WM_NORMAL_HINTS", 0), Gtk2::Gdk::Atom->intern ("WM_SIZE_HINTS", 0), 0, 17*4, 0 ); my ($width_inc, $height_inc, $base_width, $base_height) = @data[9,10,15,16]; my $hints = new Gtk2::Gdk::Geometry; $hints->min_width ($base_width + $width_inc); $hints->min_height ($base_height + $height_inc); $hints->base_width ($base_width); $hints->base_height ($base_height); $hints->width_inc ($width_inc); $hints->height_inc ($height_inc); # from gnome-terminal: # # This set_size_request hack is because the extra size above base # size should only include the width of widgets that intersect the # term vertically and the height of widgets that intersect the term # horizontally. It works around a GTK bug, GTK should handle # this case. The size request can be huge without hosing # anything because we set the MIN_SIZE geometry hint. $self->set_size_request (20000, 20000); $self->size_request; $self->get_toplevel->size_request; $self->get_toplevel->set_geometry_hints ($self, $hints, [qw(min-size base-size resize-inc)]); } sub INIT_INSTANCE { my ($self) = @_; $self->can_focus (1); $self->signal_connect_after (destroy => sub { delete $event_cb{$self->{plugged}} if $self->{plugged}; }); $self->signal_connect_after (realize => sub { my ($self) = @_; unless ($urxvtd_socket) { $urxvtd_dir = File::Temp::tempdir "rxvt-unicode-tabbed-XXXX", TMPDIR => 1, CLEANUP => 1; $urxvtd_socket = "$dir/serv"; $ENV{RXVT_SOCKET} = $urxvtd_socket; $urxvtd_pid = open $urxvtd_fh, "-|", "$self->{urxvt_basename}d", "-o" or croak "$self->{urxvt_basename}d: $!"; my $output = readline $urxvtd_fh; $output =~ /^rxvt-unicode daemon listening/ or die "$self->{urxvt_basename}d: got output '$output'"; } my @args = @{ $self->{args} || [] }; $ENV{RXVT_SOCKET} = $urxvtd_socket; system "$self->{urxvt_basename}c", -embed => $self->window->get_xid, @args; 0 }); $self->signal_connect_after (map_event => sub { $_[0]->wm_normal_hints; 0 }); $self->signal_connect_after (plug_added => sub { my ($self) = @_; $self->{plugged} = ($self->window->get_children)[0]; $self->{plugged}->set_events ($self->{plugged}->get_events + ["property-change-mask"]); $self->wm_normal_hints; $event_cb{$self->{plugged}} = sub { my ($event) = @_; my $window = $event->window; if (Gtk2::Gdk::Event::Configure:: eq ref $event) { $self->wm_normal_hints; } elsif (Gtk2::Gdk::Event::Property:: eq ref $event) { my $atom = $event->atom; my $name = $atom->name; return if $event->state; # GDK_PROPERTY_NEW_VALUE == 0 if ($name eq "_NET_WM_NAME") { my ($type, $format, $data) = $window->property_get ( $atom, Gtk2::Gdk::Atom->intern ("UTF8_STRING", 0), 0, 128, 0 ); utf8::encode $data; $self->signal_emit (title_changed => $data); $self->wm_normal_hints; # hack, we don't get a notify for WM_NORMAL_HINTS } elsif ($name eq "WM_NORMAL_HINTS") { $self->wm_normal_hints; } } 0 }; 0 }); } # ugly, but gdk_window_filters are not available in perl Gtk2::Gdk::Event->handler_set (sub { my ($event) = @_; my $window = $event->window; ($event_cb{$window} and $event_cb{$window}->($event)) or Gtk2->main_do_event ($event); }); =back =head1 PROPERTIES =over 4 =item C (arrayref : construct-only / readable) An arrayref containing the arguments passed to C. Any argument valid for C/C can be used. =item C (string : construct-only / readable) URxvt command basename. The base name of the C and C commands (without the trailing C or C). Default: C<$ENV{URXVT_BASENAME} || "urxvt">. The value must be the same for all instances created. The mechanism might change in the future. =back =head1 SIGNALS =over 4 =item B (string) Emitted whenever the underlying urxvt window changes the title. =back =head1 EXAMPLE The following code snipped implements a small telnet-like application using Gtk2::URxvt as a vt100 terminal widget. By default, it connects to and displays the blinkenlights.nl starwars movie. It's the same example as F in the distribution. use Gtk2 -init; use Gtk2::URxvt; use IO::Socket::INET; use IO::Pty; use Fcntl; my $peerhost = $ARGV[0] || "towel.blinkenlights.nl"; my $peerport = $ARGV[1] || 22; Allocate/create a new pty: my $pty = new IO::Pty; fcntl $pty, F_SETFD, 0; # clear close-on-exec Create a new terminal window: my $window = new Gtk2::Window 'toplevel'; $window->set_default_size (80*9, 25*15); my $term = new Gtk2::URxvt args => [-fn => "9x15bold", "-pty-fd" => fileno $pty]; $window->add ($term); The call to C is important, as this forces the urxvt process to be created. Before, closing the pty will result in, well, closing the pty, I the subprocess can get hold of it. $term->realize; # force start of urxvt, so we can close the socket close $pty; my $tty = $pty->slave; Connect the socket to the remote host: my $socket = new IO::Socket::INET PeerHost => $peerhost, PeerPort => $peerport or die "$peerhost:$peerport: $!"; And these two IO watchers simply copy teween socket and terminal: add_watch Glib::IO fileno $socket, in => sub { sysread $socket, my $buf, 4096 or main_quit Gtk2; print $tty $buf; 1 }; add_watch Glib::IO fileno $tty, in => sub { sysread $tty, my $buf, 128; print $socket $buf; 1 }; Display, and enter mainloop: $window->show_all; main Gtk2; =head1 AUTHOR Marc Lehmann http://home.schmorp.de/ =cut 1