ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Gtk2-URxvt/URxvt.pm
Revision: 1.5
Committed: Sun Dec 25 17:03:58 2005 UTC (18 years, 4 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.4: +7 -5 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 package Gtk2::URxvt;
2
3 # I tried to write this with Tk, as it uses less memory and is
4 # more widely available. Alas, Tk is rather broken with respect to
5 # embedding (and many other things, too).
6
7 =head1 NAME
8
9 Gtk2::URxvt - widget for embedding rxvt-unicode instances
10
11 =head1 SYNOPSIS
12
13 use Gtk2::URxvt;
14
15 my $term = new Gtk2::URxvt;
16 $window->add ($term);
17
18 # passing arguments is also possible
19 my $term = new Gtk2::URxvt args => [ -fg => "black", -bg => "green", ... ];
20
21 =head1 DESCRIPTION
22
23 This module implements a gtk+ widget that embeds a urxvt (rxvt-unicode)
24 terminal window. It starts - on demand - a single urxvtd (daemon) and uses
25 it for all terminal widget.
26
27 Any option valid for urxvtc/urxvt can be used, including C<-pty-fd> to
28 pass your own pty, but excluding C<-embed>, for obvious reasons.
29
30 The actual terminal process is started as soon as the widget is realized.
31
32 =over 4
33
34 =cut
35
36 use Carp;
37 use File::Temp;
38
39 our $VERSION = '0.1';
40
41 my ($urxvtd_dir, $urxvtd_socket, $urxvtd_pid, $urxvtd_fh);
42 my %event_cb; # $wid => $cb
43
44 use Glib::Object::Subclass
45 Gtk2::Socket =>
46 signals => {
47 title_changed => {
48 param_types => [Glib::String],
49 },
50 },
51 properties => [
52 Glib::ParamSpec->string (
53 'urxvt_basename',
54 'URxvt command basename',
55 '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.',
56 $ENV{URXVT_BASENAME} || 'urxvt',
57 [qw(construct-only readable writable)],
58 ),
59 Glib::ParamSpec->scalar (
60 'args',
61 'URxvt commandline arguments',
62 'An arrayref containing the commandline arguments',
63 [qw(construct-only readable writable)],
64 ),
65 ];
66
67 END {
68 kill TERM => $urxvtd_pid if $urxvtd_pid;
69 close $urxvtd_fh if $urxvtd_fh;
70 undef $urxvtd_dir;
71 }
72
73 sub wm_normal_hints {
74 my ($self) = @_;
75
76 $self->mapped
77 or return;
78
79 my ($type, $format, @data)
80 = $self->{plugged}->property_get (
81 Gtk2::Gdk::Atom->intern ("WM_NORMAL_HINTS", 0),
82 Gtk2::Gdk::Atom->intern ("WM_SIZE_HINTS", 0),
83 0, 17*4, 0
84 );
85 my ($width_inc, $height_inc, $base_width, $base_height) = @data[9,10,15,16];
86
87 my $hints = new Gtk2::Gdk::Geometry;
88 $hints->min_width ($base_width + $width_inc); $hints->min_height ($base_height + $height_inc);
89 $hints->base_width ($base_width); $hints->base_height ($base_height);
90 $hints->width_inc ($width_inc); $hints->height_inc ($height_inc);
91
92 # from gnome-terminal:
93 #
94 # This set_size_request hack is because the extra size above base
95 # size should only include the width of widgets that intersect the
96 # term vertically and the height of widgets that intersect the term
97 # horizontally. It works around a GTK bug, GTK should handle
98 # this case. The size request can be huge without hosing
99 # anything because we set the MIN_SIZE geometry hint.
100 $self->set_size_request (20000, 20000);
101 $self->size_request;
102 $self->get_toplevel->size_request;
103
104 $self->get_toplevel->set_geometry_hints ($self, $hints, [qw(min-size base-size resize-inc)]);
105 }
106
107 sub INIT_INSTANCE {
108 my ($self) = @_;
109
110 $self->can_focus (1);
111
112 $self->signal_connect_after (destroy => sub {
113 delete $event_cb{$self->{plugged}} if $self->{plugged};
114 });
115
116 $self->signal_connect_after (realize => sub {
117 my ($self) = @_;
118
119 unless ($urxvtd_socket) {
120 $urxvtd_dir = File::Temp::tempdir "rxvt-unicode-tabbed-XXXX", TMPDIR => 1, CLEANUP => 1;
121 $urxvtd_socket = "$dir/serv";
122
123 $ENV{RXVT_SOCKET} = $urxvtd_socket;
124 $urxvtd_pid = open $urxvtd_fh, "-|", "$self->{urxvt_basename}d", "-o"
125 or croak "$self->{urxvt_basename}d: $!";
126
127 my $output = readline $urxvtd_fh;
128
129 $output =~ /^rxvt-unicode daemon listening/
130 or die "$self->{urxvt_basename}d: got output '$output'";
131 }
132
133 my @args = @{ $self->{args} || [] };
134
135 $ENV{RXVT_SOCKET} = $urxvtd_socket;
136 system "$self->{urxvt_basename}c", -embed => $self->window->get_xid, @args;
137
138 0
139 });
140
141 $self->signal_connect_after (map_event => sub {
142 $_[0]->wm_normal_hints;
143 0
144 });
145
146 $self->signal_connect_after (plug_added => sub {
147 my ($self) = @_;
148
149 $self->{plugged} = ($self->window->get_children)[0];
150 $self->{plugged}->set_events ($self->{plugged}->get_events + ["property-change-mask"]);
151
152 $self->wm_normal_hints;
153
154 $event_cb{$self->{plugged}} = sub {
155 my ($event) = @_;
156 my $window = $event->window;
157
158 if (Gtk2::Gdk::Event::Configure:: eq ref $event) {
159 $self->wm_normal_hints;
160 } elsif (Gtk2::Gdk::Event::Property:: eq ref $event) {
161 my $atom = $event->atom;
162 my $name = $atom->name;
163
164 return if $event->state; # GDK_PROPERTY_NEW_VALUE == 0
165
166 if ($name eq "_NET_WM_NAME") {
167 my ($type, $format, $data)
168 = $window->property_get (
169 $atom,
170 Gtk2::Gdk::Atom->intern ("UTF8_STRING", 0),
171 0, 128, 0
172 );
173
174 utf8::encode $data;
175
176 $self->signal_emit (title_changed => $data);
177
178 $self->wm_normal_hints; # hack, we don't get a notify for WM_NORMAL_HINTS
179 } elsif ($name eq "WM_NORMAL_HINTS") {
180 $self->wm_normal_hints;
181 }
182 }
183
184 0
185 };
186
187 0
188 });
189 }
190
191 # ugly, but gdk_window_filters are not available in perl
192
193 Gtk2::Gdk::Event->handler_set (sub {
194 my ($event) = @_;
195 my $window = $event->window;
196
197 ($event_cb{$window} and $event_cb{$window}->($event))
198 or Gtk2->main_do_event ($event);
199 });
200
201 =back
202
203 =head1 PROPERTIES
204
205 =over 4
206
207 =item C<args> (arrayref : construct-only / readable)
208
209 An arrayref containing the arguments passed to C<urxvtc>. Any argument
210 valid for C<urxvt>/C<urxvtc> can be used.
211
212 =item C<urxvt_basename> (string : construct-only / readable)
213
214 URxvt command basename. The base name of the C<urxvtd>
215 and C<urxvtc> commands (without the trailing C<c> or
216 C<d>). Default: C<$ENV{URXVT_BASENAME} || "urxvt">. The value must be the
217 same for all instances created. The mechanism might change in the future.
218
219 =back
220
221 =head1 SIGNALS
222
223 =over 4
224
225 =item B<title_changed> (string)
226
227 Emitted whenever the underlying urxvt window changes the title.
228
229 =back
230
231 =head1 EXAMPLE
232
233 The following code snipped implements a small telnet-like application
234 using Gtk2::URxvt as a vt100 terminal widget. By default, it connects to
235 and displays the blinkenlights.nl starwars movie. It's the same example as
236 F<eg/xtelnet> in the distribution.
237
238 use Gtk2 -init;
239 use Gtk2::URxvt;
240 use IO::Socket::INET;
241 use IO::Pty;
242 use Fcntl;
243
244 my $peerhost = $ARGV[0] || "towel.blinkenlights.nl";
245 my $peerport = $ARGV[1] || 22;
246
247 Allocate/create a new pty:
248
249 my $pty = new IO::Pty;
250 fcntl $pty, F_SETFD, 0; # clear close-on-exec
251
252 Create a new terminal window:
253
254 my $window = new Gtk2::Window 'toplevel';
255 $window->set_default_size (80*9, 25*15);
256 my $term = new Gtk2::URxvt args => [-fn => "9x15bold", "-pty-fd" => fileno $pty];
257 $window->add ($term);
258
259 The call to C<realize> is important, as this forces the urxvt process to
260 be created. Before, closing the pty will result in, well, closing the pty,
261 I<before> the subprocess can get hold of it.
262
263 $term->realize; # force start of urxvt, so we can close the socket
264 close $pty;
265
266 my $tty = $pty->slave;
267
268 Connect the socket to the remote host:
269
270 my $socket = new IO::Socket::INET PeerHost => $peerhost, PeerPort => $peerport
271 or die "$peerhost:$peerport: $!";
272
273 And these two IO watchers simply copy teween socket and terminal:
274
275 add_watch Glib::IO fileno $socket, in => sub {
276 sysread $socket, my $buf, 4096
277 or main_quit Gtk2;
278 print $tty $buf;
279
280 1
281 };
282
283 add_watch Glib::IO fileno $tty, in => sub {
284 sysread $tty, my $buf, 128;
285 print $socket $buf;
286
287 1
288 };
289
290 Display, and enter mainloop:
291
292 $window->show_all;
293 main Gtk2;
294
295 =head1 AUTHOR
296
297 Marc Lehmann <schmorp@schmorp.de>
298 http://home.schmorp.de/
299
300 =cut
301
302 1