#! perl sub refresh { my ($self) = @_; my $cmd = "\x1b[H\x1b[7m\x1b[K"; my $txt; my @ofs = (0); for my $tab (@{ $self->{tabs} }) { if ($tab == $self->{cur}) { $txt = " [$tab->{name}] "; } else { $txt = " $tab->{name} "; } $cmd .= $txt; push @ofs, $ofs[-1] + length $txt; } $self->{tabofs} = \@ofs; $self->cmd_parse ($self->locale_encode ($cmd)); } sub new_tab { my ($self) = @_; my $offset = $self->fheight; # save a backlink to us, make sure tabbed is inactive push @urxvt::TERM_INIT, sub { my ($term) = @_; $term->{parent} = $self; $term->resource ($_->[0] => $_->[1]) for @{ $self->{resource} || [] }; $term->resource (perl_ext_2 => $term->resource ("perl_ext_2") . ",-tabbed"); }; push @urxvt::TERM_EXT, urxvt::ext::tabbed::tab::; my $term = new urxvt::term $self->env, $urxvt::RXVTNAME, -embed => $self->parent, ; } sub configure { my ($self) = @_; $self->{cur}->XMoveResizeWindow ( $self->{cur}->parent, 0, $self->{tabheight}, $self->width, $self->height - $self->{tabheight} ); } sub make_current { my ($self, $tab) = @_; if (my $cur = $self->{cur}) { $cur->XUnmapWindow ($cur->parent) if $cur->mapped; } $self->{cur} = $tab; $self->configure; my $wm_normal_hints = $self->XInternAtom ("WM_NORMAL_HINTS"); for my $atom ($tab->XListProperties ($tab->parent)) { my ($type, $format, $items) = $self->XGetWindowProperty ($tab->parent, $atom); if ($atom == $wm_normal_hints) { my (@hints) = unpack "l!*", $items; $hints[ 4] += $self->{tabheight}; $hints[16] += $self->{tabheight}; $items = pack "l!*", @hints; } $self->XChangeWindowProperty ($self->parent, $atom, $type, $format, $items); } $tab->XMapWindow ($tab->parent); $self->refresh; () } sub on_button_press { 1 } sub on_button_release { my ($self, $event) = @_; my $ofs = $self->{tabofs}; if ($event->{row} == 0) { for my $i (0 .. @$ofs - 2) { if ($event->{col} >= $ofs->[$i] && $event->{col} < $ofs->[$i+1]) { $self->make_current ($self->{tabs}[$i]); } } } 1 } sub on_motion_notify { 1 } sub on_init { my ($self) = @_; for (qw(name perl_ext_1 perl_ext_2)) { my $val = $self->resource ($_); push @{ $self->{resource} }, [$_ => $val] if defined $val; } $self->resource (int_bwidth => 0); $self->resource (name => "URxvt.tab"); $self->resource (pty_fd => -1); $self->option ($urxvt::OPTION{scrollBar}, 0); () } sub on_start { my ($self) = @_; $self->{tabheight} = $self->int_bwidth + $self->fheight + $self->lineSpace; $self->cmd_parse ("\x1b[?25l\x1b[?7l"); $self->new_tab; $self->new_tab; $self->new_tab; () } sub on_configure_notify { my ($self, $event) = @_; $self->configure; () } sub on_wm_delete_window { my ($self) = @_; $_->destroy for @{ $self->{tabs} }; 1 } sub tab_start { my ($self, $tab) = @_; push @{ $self->{tabs} }, $tab; $tab->{name} ||= scalar @{ $self->{tabs} }; $self->make_current ($tab); () } sub tab_destroy { my ($self, $tab) = @_; $self->{tabs} = [ grep $_ != $tab, @{ $self->{tabs} } ]; if (@{ $self->{tabs} }) { if ($self->{cur} == $tab) { delete $self->{cur}; $self->make_current ($self->{tabs}[-1]); } } else { # delay destruction a tiny bit $self->{destroy} = urxvt::iw->new->start->cb (sub { $self->destroy }); } () } package urxvt::ext::tabbed::tab; # helper extension implementing the subwindows of a tabbed terminal. # simply proxies all interesting calls back to the tabbed class. { for my $hook qw(start destroy) { eval qq{ sub on_$hook { my \$parent = \$_[0]{term}{parent} or return; \$parent->tab_$hook (\@_) } }; die if $@; } }