Path: blob/master/roles/users/files/irssi/scripts/adv_windowlist.pl
245 views
use strict;1use warnings;23our $VERSION = '1.8'; # 63feb35d8b0a8b64our %IRSSI = (5authors => 'Nei',6contact => 'Nei @ [email protected]',7url => "http://anti.teamidiot.de/",8name => 'adv_windowlist',9description => 'Adds a permanent advanced window list on the right or in a status bar.',10sbitems => 'awl_shared',11license => 'GNU GPLv2 or later',12);1314# UPGRADE NOTE15# ============16# for users of 0.7 or earlier series, please note that appearance17# settings have moved to /format, i.e. inside your theme!18# the fifo (screen) has been replaced by an external viewer script1920# Usage21# =====22# copy the script to ~/.irssi/scripts/23#24# In irssi:25#26# /run adv_windowlist27#28# In your shell (for example a tmux split):29#30# perl ~/.irssi/scripts/adv_windowlist.pl31#32# To use sbar mode instead:33#34# /toggle awl_viewer35#36# Hint: to get rid of the old [Act:] display37# /statusbar window remove act38#39# to get it back:40# /statusbar window add -after lag -priority 10 act4142# Options43# =======44# formats can be cleared with /format -delete45#46# /format awl_display_(no)key(_active|_visible) <string>47# * string : Format String for one window. The following $'s are expanded:48# $C : Name49# $N : Number of the Window50# $Q : meta-Keymap51# $H : Start hilighting52# $S : Stop hilighting53# /+++++++++++++++++++++++++++++++++,54# | **** I M P O R T A N T : **** |55# | |56# | don't forget to use $S if you |57# | used $H before! |58# | |59# '+++++++++++++++++++++++++++++++++/60# key : a key binding that goes to this window could be detected in /bind61# nokey : no such key binding was detected62# active : window would receive the input you are currently typing63# visible : window is also visible on screen but not active (a split window)64#65# /format awl_name_display <string>66# * string : Format String for window names67# $0 : name as formatted by the settings68#69# /format awl_display_header <string>70# * string : Format String for this header line. The following $'s are expanded:71# $C : network tag72#73# /format awl_separator(2) <string>74# * string : Character to use between the channel entries75# variant 2 can be used for alternating separators (only in status bar76# without block display)77#78# /format awl_abbrev_chars <string>79# * string : Character to use when shortening long names. The second character80# will be used if two blocks need to be filled.81#82# /format awl_title <string>83# * string : Text to display in the title string or title bar84#85# /format awl_viewer_item_bg <string>86# * string : Format String specifying the viewer's item background colour87#88# /set awl_prefer_name <ON|OFF>89# * this setting decides whether awl will use the active_name (OFF) or the90# window name as the name/caption in awl_display_*.91# That way you can rename windows using /window name myownname.92#93# /set awl_hide_empty <num>94# * if visible windows without items should be hidden from the window list95# set it to 0 to show all windows96# 1 to hide visible windows without items (negative exempt97# active window)98#99# /set awl_detach <list>100# * list of windows that should be hidden from the window list. you101# can also use /awl detach and /awl attach to manage this102# setting. an optional data_level can be specified with ",num"103#104# /set awl_detach_data <num>105# * num : hide the detached window if its data_level is below num106#107# /set awl_detach_aht <ON|OFF>108# * if enabled, also detach all windows listed in the109# activity_hide_targets setting110#111# /set awl_hide_data <num>112# * num : hide the window if its data_level is below num113# set it to 0 to basically disable this feature,114# 1 if you don't want windows without activity to be shown115# 2 to show only those windows with channel text or hilight116# 3 to show only windows with hilight (negative exempt active window)117#118# /set awl_hide_name_data <num>119# * num : hide the name of the window if its data_level is below num120# (only works in status bar without block display)121# you will want to change your formats to add $H...$S around $Q or $N122# if you plan to use this123#124# /set awl_maxlines <num>125# * num : number of lines to use for the window list (0 to disable, negative126# lock)127#128# /set awl_maxcolumns <num>129# * num : number of columns to use for the window list when using the130# tmux integration (0 to disable)131#132# /set awl_block <num>133# * num : width of a column in viewer mode (negative values = block134# display in status bar mode)135# /+++++++++++++++++++++++++++++++++,136# | ****** W A R N I N G ! ****** |137# | |138# | If your block display looks |139# | DISTORTED, you need to add the |140# | following line to your .theme |141# | file under |142# | abstracts = { : |143# | |144# | sb_act_none = "%K$*"; |145# | |146# '+++++++++++++++++++++++++++++++++/147#148# /set awl_sbar_maxlength <ON|OFF>149# * if you enable the maxlength setting, the block width will be used as a150# maximum length for the non-block status bar mode too.151#152# /set awl_height_adjust <num>153# * num : how many lines to leave empty in viewer mode154#155# /set awl_sort <-data_level|-last_line|refnum>156# * you can change the window sort order with this variable157# -data_level : sort windows with hilight first158# -last_line : sort windows in order of activity159# refnum : sort windows by window number160# active/server/tag : sort by server name161# lru : sort windows with the last recently used last162# "-" reverses the sort order163# typechecks are supported via ::, e.g. active::Query or active::Irc::Query164# undefinedness can be checked with ~, e.g. ~active165# string comparison can be done with =, e.g. name=(status)166# to make sort case insensitive, use #i, e.g. name#i167# any key in the window hash can be tested, e.g. active/chat_type=XMPP168# multiple criteria can be separated with , or +, e.g. -data_level+-last_line169#170# /set awl_placement <top|bottom>171# /set awl_position <num>172# * these settings correspond to /statusbar because awl will create173# status bars for you174# (see /help statusbar to learn more)175#176# /set awl_all_disable <ON|OFF>177# * if you set awl_all_disable to ON, awl will also remove the178# last status bar it created if it is empty.179# As you might guess, this only makes sense with awl_hide_data > 0 ;)180#181# /set awl_viewer <ON|OFF>182# * enable the external viewer script183#184# /set awl_viewer_launch <ON|OFF>185# * try to auto-launch the viewer under tmux or with a shell command186# /awl restart is required all auto-launch related settings to take187# effect188#189# /set awl_viewer_tmux_position <left|top|right|bottom|custom>190# * try to split in this direction when using tmux for the viewer191# custom : use custom_command setting192#193# /set awl_viewer_xwin_command <shell command>194# * custom command to run in order to start the viewer when irssi is195# running under X196# %A - gets replaced by the command to run the viewer197# %qA - additionally quote the command198#199# /set awl_viewer_custom_command <shell command>200# * custom command to run in order to start the viewer201#202# /set awl_viewer_launch_env <string>203# * specific environment settings for use on viewer auto-launch,204# without the AWL_ prefix205#206# /set awl_shared_sbar <left<right|OFF>207# * share a status bar for the first awl item, you will need to manually208# /statusbar window add -after lag -priority 10 awl_shared209# left : space in cells occupied on the left of status bar210# right : space occupied on the right211# Note: you need to replace "left" AND "right" with the appropriate numbers!212#213# /set awl_path <path>214# * path to the file which the viewer script reads215#216# /set fancy_abbrev <no|head|strict|fancy>217# * how to shorten too long names218# no : shorten in the middle219# head : always cut off the ends220# strict : shorten repeating substrings221# fancy : combination of no+strict222#223# /set awl_custom_xform <perl code>224# * specify a custom routine to transform window names225# example: s/^#// remove the #-mark of IRC channels226# the special flags $CHANNEL / $TAG / $QUERY / $NAME can be227# tested in conditionals228#229# /set awl_last_line_shade <timeout>230# * set timeout to shade activity base colours, to enable231# you also need to add +-last_line to awl_sort232# (requires 256 colour support)233#234# /set awl_no_mode_hint <ON|OFF>235# * whether to show the hint of running the viewer script in the236# status bar237#238# /set awl_mouse <ON|OFF>239# * enable the terminal mouse in irssi240# (use the awl-patched mouse.pl for gestures and commands if you need241# them and disable mouse_escape)242#243# /set awl_mouse_offset <num>244# * specifies where on the screen is the awl status bar245# (0 = on top/bottom, 1 = one additional line in between,246# e.g. prompt)247# you MUST set this correctly otherwise the mouse coordinates will248# be off249#250# /set mouse_scroll <num>251# * how many lines the mouse wheel scrolls252#253# /set mouse_escape <num>254# * seconds to disable the mouse, when not clicked on the windowlist255#256257# Commands258# ========259# /awl detach <num>260# * hide the current window from the window list. num specifies the261# data_level (optional)262#263# /awl attach264# * unhide the current window from the window list265#266# /awl ack267# * change to the next window with activity, ignoring detached windows268#269# /awl redraw270# * redraws the windowlist. There may be occasions where the271# windowlist can get destroyed so you can use this command to272# force a redraw.273#274# /awl restart275# * restart the connection to the viewer script.276277# Viewer script278# =============279# When run from the command line, adv_windowlist acts as the viewer280# script to be used together with the irssi script to display the281# window list in a sidebar/terminal of its own.282#283# One optional parameter is accepted, the awl_path284#285# The viewer can be configured by three environment variables:286#287# AWL_HI9=1288# * interpret %9 as high-intensity toggle instead of bold. This had289# been the default prior to version 0.9b8290#291# AWL_AUTOFOCUS=0292# * disable auto-focus behaviour when activating a window293#294# AWL_NOTITLE=1295# * disable the title bar296297# Nei =^.^= ( [email protected] )298299no warnings 'redefine';300use constant IN_IRSSI => __PACKAGE__ ne 'main' || $ENV{IRSSI_MOCK};301use constant SCRIPT_FILE => __FILE__;302no if !IN_IRSSI, strict => (qw(subs refs));303use if IN_IRSSI, Irssi => ();304use if IN_IRSSI, 'Irssi::TextUI' => ();305use v5.10;306use Encode;307use Storable ();308use IO::Socket::UNIX;309use List::Util qw(min max reduce);310use Hash::Util qw(lock_keys);311use Text::ParseWords qw(shellwords);312313BEGIN {314if ($] < 5.012) {315*CORE::GLOBAL::length = *CORE::GLOBAL::length = sub (_) {316defined $_[0] ? CORE::length($_[0]) : undef317};318}319*Irssi::active_win = {}; # hide incorrect warning320}321322unless (IN_IRSSI) {323local *_ = \@ARGV;324&AwlViewer::main;325exit;326}327328329use constant GLOB_QUEUE_TIMER => 100;330331our $BLOCK_ALL; # localized blocker332my @actString; # status bar texts333my @win_items;334my $currentLines = 0;335my %awins;336my $globTime; # timer to limit remake calls337338my %CHANGED;339my $VIEWER_MODE;340my $MOUSE_ON;341my %mouse_coords;342my %statusbars;343my %S; # settings344my $settings_str = '1';345my $window_sort_func;346my $custom_xform;347my ($sb_base_width, $sb_base_width_pre, $sb_base_width_post);348my $print_text_activity;349my $shade_line_timer;350my ($screenHeight, $screenWidth);351my %viewer;352353my (%keymap, %nummap, %wnmap, %specialmap, %wnmap_exp, %custom_key_map);354my %banned_channels;355my %detach_map;356my %abbrev_cache;357358use constant setc => 'awl';359360sub set ($) {361setc . '_' . $_[0]362}363364sub add_statusbar {365for (@_) {366# add subs367my $l = set $_;368{369my $close = $_;370no strict 'refs';371*{$l} = sub { awl($close, @_) };372}373Irssi::command("^statusbar $l reset");374Irssi::command("statusbar $l enable");375if (lc $S{placement} eq 'top') {376Irssi::command("statusbar $l placement top");377}378if (my $x = $S{position}) {379Irssi::command("statusbar $l position $x");380}381Irssi::command("statusbar $l add -priority 100 -alignment left barstart");382Irssi::command("statusbar $l add $l");383Irssi::command("statusbar $l add -priority 100 -alignment right barend");384Irssi::command("statusbar $l disable");385Irssi::statusbar_item_register($l, '$0', $l);386$statusbars{$_} = 1;387Irssi::command("statusbar $l enable");388}389}390391sub remove_statusbar {392for (@_) {393my $l = set $_;394Irssi::command("statusbar $l disable");395Irssi::command("statusbar $l reset");396Irssi::statusbar_item_unregister($l);397{398no strict 'refs';399undef &{$l};400}401delete $statusbars{$_};402}403}404405my $awl_shared_empty = sub {406return if $BLOCK_ALL;407my ($item, $get_size_only) = @_;408$item->default_handler($get_size_only, '', '', 0);409};410411sub syncLines {412my $maxLines = $S{maxlines};413my $newLines = ($maxLines > 0 and @actString > $maxLines) ?414$maxLines :415($maxLines < 0) ?416-$maxLines :417@actString;418$currentLines = 1 if !$currentLines && $S{shared_sbar};419if ($S{shared_sbar} && !$statusbars{shared}) {420my $l = set 'shared';421{422no strict 'refs';423*{$l} = sub {424return if $BLOCK_ALL;425my ($item, $get_size_only) = @_;426427my $text = $actString[0];428my $title = _get_format(set 'title');429if (length $title) {430$title =~ s{\\(.)|(.)}{431defined $2 ? quotemeta $2432: $1 eq 'V' ? '\u'433: $1 eq ':' ? quotemeta ':%n'434: $1 =~ /^[uUFQE]$/ ? "\\$1"435: quotemeta "\\$1"436}sge;437$title = eval qq{"$title"};438$title .= ' ';439}440my $pat = defined $text ? "{sb $title\$*}" : '{sb }';441$text //= '';442$item->default_handler($get_size_only, $pat, $text, 0);443};444}445$statusbars{shared} = 1;446remove_statusbar (0) if $statusbars{0};447}448elsif ($statusbars{shared} && !$S{shared_sbar}) {449add_statusbar (0) if $currentLines && $newLines;450delete $statusbars{shared};451my $l = set 'shared';452{453no strict 'refs';454*{$l} = $awl_shared_empty;455}456}457if ($currentLines == $newLines) { return; }458elsif ($newLines > $currentLines) {459add_statusbar ($currentLines .. ($newLines - 1));460}461else {462remove_statusbar (reverse ($newLines .. ($currentLines - 1)));463}464$currentLines = $newLines;465}466467sub awl {468return if $BLOCK_ALL;469my ($line, $item, $get_size_only) = @_;470471my $text = $actString[$line];472my $pat = defined $text ? '{sb $*}' : '{sb }';473$text //= '';474$item->default_handler($get_size_only, $pat, $text, 0);475}476477# remove old statusbars478{ my %killBar;479sub get_old_status {480my ($textDest, $cont, $cont_stripped) = @_;481if ($textDest->{level} == 524288 and $textDest->{target} eq '' and !defined $textDest->{server}) {482my $name = quotemeta(set '');483if ($cont_stripped =~ m/^$name(\d+)\s/) { $killBar{$1} = 1; }484Irssi::signal_stop;485}486}487sub killOldStatus {488%killBar = ();489Irssi::signal_add_first('print text' => 'get_old_status');490Irssi::command('statusbar');491Irssi::signal_remove('print text' => 'get_old_status');492remove_statusbar(keys %killBar);493}494}495496sub _add_map {497my ($type, $target, $map) = @_;498($type->{$target}) = sort { length $a <=> length $b || $a cmp $b }499$map, exists $type->{$target} ? $type->{$target} : ();500}501502sub get_keymap {503my ($textDest, undef, $cont_stripped) = @_;504if ($textDest->{level} == 524288 and $textDest->{target} eq '' and !defined $textDest->{server}) {505my $one_meta_or_ctrl_key = qr/((?:meta-)*?)(?:(meta-|\^)(\S)|(\w+))/;506$cont_stripped = as_uni($cont_stripped);507if ($cont_stripped =~ m/((?:$one_meta_or_ctrl_key-)*$one_meta_or_ctrl_key)\s+(.*)$/) {508my ($combo, $command) = ($1, $10);509my $map = '';510while ($combo =~ s/(?:-|^)$one_meta_or_ctrl_key$//) {511my ($level, $ctl, $key, $nkey) = ($1, $2, $3, $4);512my $numlevel = ($level =~ y/-//);513$ctl = '' if !$ctl || $ctl ne '^';514$map = ('-' x ($numlevel%2)) . ('+' x ($numlevel/2)) .515$ctl . (defined $key ? $key : "\01$nkey\01") . $map;516}517for ($command) {518last unless length $map;519if (/^change_window (\d+)/i) {520_add_map(\%nummap, $1, $map);521}522elsif (/^(?:command window goto|change_window) (\S+)/i) {523my $window = $1;524if ($window !~ /\D/) {525_add_map(\%nummap, $window, $map);526}527elsif (lc $window eq 'active') {528_add_map(\%specialmap, '_active', $map);529}530else {531_add_map(\%wnmap, $window, $map);532}533}534elsif (/^(?:active_window|command ((awl )?ack))/i) {535_add_map(\%specialmap, '_active', $map);536$viewer{use_ack} = $1;537}538elsif (/^command window last/i) {539_add_map(\%specialmap, '_last', $map);540}541elsif (/^(?:upper_window|command window up)/i) {542_add_map(\%specialmap, '_up', $map);543}544elsif (/^(?:lower_window|command window down)/i) {545_add_map(\%specialmap, '_down', $map);546}547elsif (/^key\s+(\w+)/i) {548$custom_key_map{$1} = $map;549}550}551}552Irssi::signal_stop;553}554}555556sub update_keymap {557%nummap = %wnmap = %specialmap = %custom_key_map = ();558Irssi::signal_remove('command bind' => 'watch_keymap');559Irssi::signal_add_first('print text' => 'get_keymap');560Irssi::command('bind');561Irssi::signal_remove('print text' => 'get_keymap');562for (keys %custom_key_map) {563if (exists $custom_key_map{$_} &&564$custom_key_map{$_} =~ s/\01(\w+)\01/exists $custom_key_map{$1} ? $custom_key_map{$1} : "\02"/ge) {565if ($custom_key_map{$_} =~ /\02/) {566delete $custom_key_map{$_};567}568else {569redo;570}571}572}573for my $keymap (\(%specialmap, %wnmap, %nummap)) {574for (keys %$keymap) {575if ($keymap->{$_} =~ s/\01(\w+)\01/exists $custom_key_map{$1} ? $custom_key_map{$1} : "\02"/ge) {576if ($keymap->{$_} =~ /\02/) {577delete $keymap->{$_};578}579}580}581}582Irssi::signal_add('command bind' => 'watch_keymap');583delete $viewer{client_keymap};584&wl_changed;585}586587# watch keymap changes588sub watch_keymap {589Irssi::timeout_add_once(1000, 'update_keymap', undef);590}591592{ my %strip_table = (593# fe-common::core::formats.c:format_expand_styles594# delete format_backs format_fores bold_fores other stuff595(map { $_ => '' } (split //, '04261537' . 'kbgcrmyw' . 'KBGCRMYW' . 'U9_8I:|FnN>#[' . 'pP')),596# escape597(map { $_ => $_ } (split //, '{}%')),598);599sub ir_strip_codes { # strip %codes600my $o = shift;601$o =~ s/(%(%|Z.{6}|z.{6}|X..|x..|.))/exists $strip_table{$2} ? $strip_table{$2} :602$2 =~ m{x(?:0[a-f]|[1-6][0-9a-z]|7[a-x])|z[0-9a-f]{6}}i ? '' : $1/gex;603$o604}605}606## ir_parse_special -- wrapper around parse_special607## $i - input format608## $args - array ref of arguments to format609## $win - different target window (default current window)610## $flags - different kind of escape flags (default 4|8)611## returns formatted str612sub ir_parse_special {613my $o;614my $i = shift;615my $args = shift // [];616y/ /\177/ for @$args; # hack to escape spaces617my $win = shift || Irssi::active_win;618my $flags = shift // 0x4|0x8;619my @cmd_args = ($i, (join ' ', @$args), $flags);620my $server = Irssi::active_server();621if (ref $win and ref $win->{active}) {622$o = $win->{active}->parse_special(@cmd_args);623}624elsif (ref $win and ref $win->{active_server}) {625$o = $win->{active_server}->parse_special(@cmd_args);626}627elsif (ref $server) {628$o = $server->parse_special(@cmd_args);629}630else {631$o = &Irssi::parse_special(@cmd_args);632}633$o =~ y/\177/ /;634$o635}636637sub sb_format_expand { # Irssi::current_theme->format_expand wrapper638Irssi::current_theme->format_expand(639$_[0],640(641Irssi::EXPAND_FLAG_IGNORE_REPLACES642|643($_[1] ? 0 : Irssi::EXPAND_FLAG_IGNORE_EMPTY)644)645)646}647648{ my $term_type = Irssi::version > 20040819 ? 'term_charset' : 'term_type';649if (Irssi->can('string_width')) {650*screen_length = sub { Irssi::string_width($_[0]) };651}652else {653local $@;654eval { require Text::CharWidth; };655unless ($@) {656*screen_length = sub { Text::CharWidth::mbswidth($_[0]) };657}658else {659my $err = $@; chomp $err; $err =~ s/\sat .* line \d+\.$//;660#Irssi::print("%_$IRSSI{name}: warning:%_ Text::CharWidth module failed to load. Length calculation may be off! Error was:");661print "%_$IRSSI{name}:%_ $err";662*screen_length = sub {663my $temp = shift;664if (lc Irssi::settings_get_str($term_type) eq 'utf-8') {665Encode::_utf8_on($temp);666}667length($temp)668};669}670}671sub as_uni {672no warnings 'utf8';673Encode::decode(Irssi::settings_get_str($term_type), $_[0], 0)674}675sub as_tc {676Encode::encode(Irssi::settings_get_str($term_type), $_[0], 0)677}678}679680sub sb_length {681screen_length(ir_strip_codes($_[0]))682}683684sub run_custom_xform {685local $@;686eval {687$custom_xform->()688};689if ($@) {690$@ =~ /^(.*)/;691print '%_'.(set 'custom_xform').'%_ died (disabling): '.$1;692$custom_xform = undef;693}694}695696sub remove_uniform {697my $o = shift;698$o =~ s/^xmpp:(.*?[%@]).+\.[^.]+$/$1/ or699$o =~ s#^psyc://.+\.[^.]+/([@~].*)$#$1#;700if ($custom_xform) {701run_custom_xform() for $o;702}703$o704}705706sub remove_uniform_vars {707my $win = shift;708my $name = __PACKAGE__ . '::custom_xform::' . $win->{active}{type}709if ref $win->{active} && $win->{active}{type};710no strict 'refs';711local ${$name} = 1 if $name;712remove_uniform(+shift);713}714715sub lc1459 {716my $x = shift;717$x =~ y/][\\^/}{|~/;718lc $x719}720721sub window_list {722my $i = 0;723map { $_->[1] } sort $window_sort_func map { [ $i++, $_ ] } Irssi::windows;724}725726sub _calculate_abbrev {727my ($wins, $abbrevList) = @_;728if ($S{fancy_abbrev} !~ /^(no|off|head)/i) {729my @nameList = map { ref $_ ? remove_uniform_vars($_, as_uni($_->get_active_name) // '') : '' } @$wins;730for (my $i = 0; $i < @nameList - 1; ++$i) {731my ($x, $y) = ($nameList[$i], $nameList[$i + 1]);732s/^[+#!=]// for $x, $y;733my $res = exists $abbrev_cache{$x}{$y} ? $abbrev_cache{$x}{$y}734: $abbrev_cache{$x}{$y} = string_LCSS($x, $y);735if (defined $res) {736for ($nameList[$i], $nameList[$i + 1]) {737$abbrevList->{$_} //= int((index $_, $res) + (length $res) / 2);738}739}740}741}742}743744my %act_last_line_shades = (745r => [qw[ 50 40 30 20 ]],746g => [qw[ 1O 1I 1C 16 ]],747y => [qw[ 5O 4I 3C 26 ]],748b => [qw[ 15 14 13 12 ]],749m => [qw[ 54 43 32 21 ]],750c => [qw[ 1S 1L 1E 17 ]],751w => [qw[ 7W 7T 7Q 3E ]],752K => [qw[ 7M 7K 27 7H ]],753R => [qw[ 60 50 40 30 ]],754G => [qw[ 1U 1O 1I 1C ]],755Y => [qw[ 6U 5O 4I 3C ]],756B => [qw[ 2B 2A 29 28 ]],757M => [qw[ 65 54 43 32 ]],758C => [qw[ 1Z 1S 1L 1E ]],759W => [qw[ 6Z 5S 7R 7O ]],760);761762sub _format_display {763my (undef, $format, $cformat, $hilight, $name, $number, $key, $win) = @_;764if ($print_text_activity && $S{line_shade}) {765my @hilight_code = split /\177/, sb_format_expand("{$hilight \177}"), 2;766my $max_time = max(1, log($S{line_shade}) - log(1000));767my $time_delta = min(3, min($max_time, log(max(1, time - $win->{last_line}))) / $max_time * 3);768if ($hilight_code[0] =~ /%(.)/ && exists $act_last_line_shades{$1}) {769$hilight = 'sb_act_hilight_color %X'.$act_last_line_shades{$1}[$time_delta];770}771}772$cformat = '$0' unless length $cformat;773my %map = ('$C' => $cformat, '$N' => '$1', '$Q' => '$2');774$format =~ s<(\$.)><$map{$1}//$1>ge;775$format =~ s<\$H((?:\$.|[^\$])*?)\$S><{$hilight $1%n}>g;776my @ret = ir_parse_special(sb_format_expand($format), [$name, $number, $key], $win);777@ret778}779780sub _get_format {781Irssi::current_theme->get_format(__PACKAGE__, @_)782}783784sub _is_detached {785my ($win, $active_number) = @_;786my $level = $win->{data_level} // 0;787my $number = $win->{refnum};788my $name = lc1459( as_uni($win->{name}) );789my $active = lc1459( as_uni($win->get_active_name) // '' );790my $tag = $win->{active} && $win->{active}{server} ? lc1459( as_uni($win->{active}{server}{tag}) // '' ) : '';791my @cond = ($number);792push @cond, "$name" if length $name;793push @cond, "$tag/$active" if length $tag && length $active;794push @cond, "$active" if length $active;795push @cond, "$tag/*", "$tag/::all" if length $tag;796push @cond, "*", "::all";797for my $cond (@cond) {798if (exists $detach_map{ $cond }) {799my $dd = $detach_map{ $cond } // $S{detach_data};800return $win->{data_level} < abs $dd801&& ($number != $active_number || 0 <= $dd);802}803}804return;805}806807sub _calculate_items {808my ($wins, $abbrevList) = @_;809810my $display_header = _get_format(set 'display_header');811my $name_format = _get_format(set 'name_display');812my $abbrev_chars = as_uni(_get_format(set 'abbrev_chars'));813814my %displays;815816my $active = Irssi::active_win;817@win_items = ();818%keymap = (%nummap, %wnmap_exp);819820my ($numPad, $keyPad) = (0, 0);821if ($VIEWER_MODE or $S{block} < 0) {822$numPad = length((sort { length $b <=> length $a } keys %keymap)[0]) // 0;823$keyPad = length((sort { length $b <=> length $a } values %keymap)[0]) // 0;824}825my $last_net;826my ($abbrev1, $abbrev2) = $abbrev_chars =~ /(\X)(.*)/;827my @abbrev_chars = ('~', "\x{301c}");828unless (defined $abbrev1 && screen_length(as_tc($abbrev1)) == 1) { $abbrev1 = $abbrev_chars[0] }829unless (length $abbrev2) {830$abbrev2 = $abbrev1;831if ($abbrev1 eq $abbrev_chars[0]) {832$abbrev2 = $abbrev_chars[1];833}834else {835$abbrev2 = $abbrev1;836}837}838if (screen_length(as_tc($abbrev2)) == 1) {839$abbrev2 x= 2;840}841while (screen_length(as_tc($abbrev2)) > 2) {842chop $abbrev2;843}844unless (screen_length(as_tc($abbrev2)) == 2) {845$abbrev2 = $abbrev_chars[1];846}847for my $win (@$wins) {848my $global_tag_header_mode;849850next unless ref $win;851852my $backup_win = Storable::dclone($win);853delete $backup_win->{active} unless ref $backup_win->{active};854855$global_tag_header_mode =856$display_header && ($last_net // '') ne ($backup_win->{active}{server}{tag} // '');857858if ($win->{data_level} < abs $S{hide_data}859&& ($win->{refnum} != $active->{refnum} || 0 <= $S{hide_data})) {860next; }861elsif (exists $awins{$win->{refnum}} && $S{hide_empty} && !$win->items862&& ($win->{refnum} != $active->{refnum} || 0 <= $S{hide_empty})) {863next; }864elsif (_is_detached($win, $active->{refnum})) {865next; }866867my $colour = $win->{hilight_color} // '';868my $hilight = do {869if ($win->{data_level} == 0) { 'sb_act_none'; }870elsif ($win->{data_level} == 1) { 'sb_act_text'; }871elsif ($win->{data_level} == 2) { 'sb_act_msg'; }872elsif ($colour ne '') { "sb_act_hilight_color $colour"; }873elsif ($win->{data_level} == 3) { 'sb_act_hilight'; }874else { 'sb_act_special'; }875};876my $number = $win->{refnum};877878my ($name, $display, $cdisplay);879if ($global_tag_header_mode) {880$display = $display_header;881$name = as_uni($backup_win->{active}{server}{tag}) // '';882if ($custom_xform) {883no strict 'refs';884local ${ __PACKAGE__ . '::custom_xform::TAG' } = 1;885run_custom_xform() for $name;886}887}888else {889my @display = ('display_nokey');890if (defined $keymap{$number} and $keymap{$number} ne '') {891unshift @display, map { (my $cpy = $_) =~ s/_no/_/; $cpy } @display;892}893if (exists $awins{$number}) {894unshift @display, map { my $cpy = $_; $cpy .= '_visible'; $cpy } @display;895}896if ($active->{refnum} == $number) {897unshift @display, map { my $cpy = $_; $cpy .= '_active'; $cpy }898grep { !/_visible$/ } @display;899}900$display = (grep { length $_ }901map { $displays{$_} //= _get_format(set $_) }902@display)[0];903$cdisplay = $name_format;904$name = as_uni($win->get_active_name) // '';905$name = '*' if $S{banned_on} and exists $banned_channels{lc1459($name)};906$name = remove_uniform_vars($win, $name) if $name ne '*';907if ($name ne '*' and $win->{name} ne '' and $S{prefer_name}) {908$name = as_uni($win->{name});909if ($custom_xform) {910no strict 'refs';911local ${ __PACKAGE__ . '::custom_xform::NAME' } = 1;912run_custom_xform() for $name;913}914}915916if (!$VIEWER_MODE && $S{block} >= 0 && $S{hide_name}917&& $win->{data_level} < abs $S{hide_name}918&& ($win->{refnum} != $active->{refnum} || 0 <= $S{hide_name})) {919$name = '';920$cdisplay = '';921}922}923924$display = "$display%n";925my $num_ent = (' 'x max(0,$numPad - length $number)) . $number;926my $key_ent = exists $keymap{$number} ? ((' 'x max(0,$keyPad - length $keymap{$number})) . $keymap{$number}) : ' 'x$keyPad;927if ($VIEWER_MODE or $S{sbar_maxlen} or $S{block} < 0) {928my $baseLength = sb_length(_format_display(929'', $display, $cdisplay, $hilight,930'x', # placeholder931$num_ent,932$key_ent,933$win)) - 1;934my $diff = (abs $S{block}) - (screen_length(as_tc($name)) + $baseLength);935if ($diff < 0) { # too long936my $screen_length = screen_length(as_tc($name));937if ((abs $diff) >= $screen_length) { $name = '' } # forget it938elsif ((abs $diff) + screen_length(as_tc(substr($name, 0, 1))) >= $screen_length) { $name = substr($name, 0, 1); }939else {940my $ulen = length $name;941my $middle2 = exists $abbrevList->{$name} ?942($S{fancy_strict}) ?9432* $abbrevList->{$name} :944(2*($abbrevList->{$name} + $ulen) / 3) :945($S{fancy_head}) ?9462*$ulen :947$ulen;948my $first = 1;949while (length $name > 1) {950my $cp = $middle2 >= 0 ? $middle2/2 : -1; # clearing position951my $rm = 2;952# if character at end is wider than 1 cell -> replace it with ~953if (screen_length(as_tc(substr $name, $cp, 1)) > 1) {954if ($first || $cp < 0) {955$rm = 1;956$first = undef;957}958}959elsif ($cp < 0) { # elsif at end -> replace last 2 characters960--$cp;961}962(substr $name, $cp, $rm) = $abbrev1;963if ($cp > -1 && $rm > 1) {964--$middle2;965}966my $sl = screen_length(as_tc($name));967if ($sl + $baseLength < abs $S{block}) {968(substr $name, ($middle2+1)/2, 1) = $abbrev2;969last;970}971elsif ($sl + $baseLength == abs $S{block}) {972last;973}974}975}976}977elsif ($VIEWER_MODE or $S{block} < 0) {978$name .= (' ' x $diff);979}980}981982push @win_items, _format_display(983'', $display, $cdisplay, $hilight,984as_tc($name),985$num_ent,986as_tc($key_ent),987$win);988989if ($global_tag_header_mode) {990$last_net = $backup_win->{active}{server}{tag};991redo;992}993994$mouse_coords{refnum}{$#win_items} = $number;995}996}997998sub _spread_items {999my $width = $screenWidth - $sb_base_width - 1;1000my @separator = _get_format(set 'separator');1001if ($S{block} >= 0) {1002my $sep2 = _get_format(set 'separator2');1003push @separator, $sep2 if length $sep2 && $sep2 ne $separator[0];1004}1005$separator[0] .= '%n';1006my @sepLen = map { sb_length($_) } @separator;10071008@actString = ();1009my $curLine;1010my $curLen = 0;1011if ($S{shared_sbar}) {1012$curLen += $S{shared_sbar}[0] + 2;1013$width -= $S{shared_sbar}[2];1014}1015my $mouse_header_check = 0;1016for my $it (@win_items) {1017my $itemLen = sb_length($it);1018if ($curLen) {1019if ($curLen + $itemLen + $sepLen[$mouse_header_check % @sepLen] > $width) {1020$width += $S{shared_sbar}[2]1021if !@actString && $S{shared_sbar};1022push @actString, $curLine;1023$curLine = undef;1024$curLen = 0;1025}1026elsif (defined $curLine) {1027$curLine .= $separator[$mouse_header_check % @separator];1028$curLen += $sepLen[$mouse_header_check % @sepLen];1029}1030}1031$curLine .= $it;1032if (exists $mouse_coords{refnum}{$mouse_header_check}) {1033$mouse_coords{scalar @actString}{ $_ } = $mouse_coords{refnum}{$mouse_header_check}1034for $curLen .. $curLen + $itemLen - 1;1035}1036$curLen += $itemLen;1037}1038continue {1039++$mouse_header_check;1040}1041$curLen -= $S{shared_sbar}[0]1042if !@actString && $S{shared_sbar};1043push @actString, $curLine if $curLen;1044}10451046sub remake {1047my %abbrevList;1048my @wins = window_list();1049if ($VIEWER_MODE or $S{sbar_maxlen} or $S{block} < 0) {1050_calculate_abbrev(\@wins, \%abbrevList);1051}10521053%mouse_coords = ( refnum => +{} );1054_calculate_items(\@wins, \%abbrevList);10551056unless ($VIEWER_MODE) {1057_spread_items();10581059push @actString, undef unless @actString || $S{all_disable};1060}1061}10621063sub update_wl {1064return if $BLOCK_ALL;1065remake();10661067Irssi::statusbar_items_redraw(set $_) for keys %statusbars;10681069unless ($VIEWER_MODE) {1070Irssi::timeout_add_once(100, 'syncLines', undef);1071}1072else {1073syncViewer();1074}1075}10761077sub screenFullRedraw {1078my ($window) = @_;1079if (!ref $window or $window->{refnum} == Irssi::active_win->{refnum}) {1080$viewer{fullRedraw} = 1 if $viewer{client};1081$settings_str = '';1082&setup_changed;1083}1084}10851086sub restartViewerServer {1087if ($VIEWER_MODE) {1088stop_viewer();1089start_viewer();1090}1091}10921093sub _simple_quote {1094my @r = map {1095my $x = $_;1096$x =~ s/'/'"'"'/g;1097$x = "'$x'";1098} @_;1099wantarray ? @r : shift @r1100}11011102sub _viewer_command_replace_format {1103my ($ecmd, @args) = @_;1104my $file = _simple_quote(SCRIPT_FILE());1105my $path = _simple_quote($viewer{path});1106my @env;1107for my $env (shellwords($S{viewer_launch_env})) {1108if ($env =~ /^(\w+)(?:=(.*))$/) {1109push @env, "AWL_$1=$2"1110}1111}1112my $cmd = join ' ',1113(@env ? ('env', _simple_quote(@env)) : ()),1114'perl', $file, '-1', _simple_quote(@args), $path;1115$ecmd =~ s{%(%|\w+)}{1116my $sub = $1;1117if ($sub eq '%') {1118'%'1119}1120elsif ($sub =~ /^(q*)A(.*)/) {1121my $ret = $cmd;1122for (1..length $1) {1123$ret = _simple_quote($ret);1124}1125"$ret$2"1126}1127else {1128"%$sub"1129}1130}gex;1131$ecmd1132}11331134sub start_viewer {1135unlink $viewer{path} if -S $viewer{path} || -p _;11361137$viewer{server} = IO::Socket::UNIX->new(1138Type => SOCK_STREAM,1139Local => $viewer{path},1140Listen => 11141);1142unless ($viewer{server}) {1143$viewer{msg} = "Viewer: $!";1144$viewer{retry} = Irssi::timeout_add_once(5000, 'retry_viewer', 1);1145return;1146}1147$viewer{server}->blocking(0);1148set_viewer_mode_hint();1149$viewer{server_tag} = Irssi::input_add($viewer{server}->fileno, INPUT_READ, 'vi_connected', undef);11501151if ($S{viewer_launch}) {1152if (length $ENV{TMUX_PANE} && length $ENV{TMUX} && lc $S{viewer_tmux_position} ne 'custom') {1153my $cmd = _viewer_command_replace_format('%qA', '-p', lc $S{viewer_tmux_position});1154Irssi::command("exec - tmux neww -d $cmd 2>&1 &");1155}1156elsif (length $ENV{WINDOWID} && length $ENV{DISPLAY} && length $S{viewer_xwin_command} && $S{viewer_xwin_command} =~ /\S/) {1157my $cmd = _viewer_command_replace_format($S{viewer_xwin_command});1158Irssi::command("exec - $cmd 2>&1 &");1159}1160elsif (length $S{viewer_custom_command} && $S{viewer_custom_command} =~ /\S/) {1161my $cmd = _viewer_command_replace_format($S{viewer_custom_command});1162Irssi::command("exec - $cmd 2>&1 &");1163}1164}1165}11661167sub set_viewer_mode_hint {1168return unless $viewer{server};1169if ($S{no_mode_hint}) {1170$viewer{msg} = undef;1171}1172else {1173my ($name) = __PACKAGE__ =~ /::([^:]+)$/;1174$viewer{msg} = "Run $name from the shell or switch to sbar mode";1175}1176}11771178sub retry_viewer {1179start_viewer();1180}11811182sub vi_close_client {1183Irssi::input_remove(delete $viewer{client_tag}) if exists $viewer{client_tag};1184$viewer{client}->close if $viewer{client};1185delete $viewer{client};1186delete $viewer{client_keymap};1187delete $viewer{client_settings};1188delete $viewer{client_env};1189delete $viewer{fullRedraw};1190}11911192sub vi_connected {1193vi_close_client();1194$viewer{client} = $viewer{server}->accept or return;1195$viewer{client}->blocking(0);1196$viewer{client_tag} = Irssi::input_add($viewer{client}->fileno, INPUT_READ, 'vi_clientinput', undef);1197syncViewer();1198}11991200use constant VIEWER_BLOCK_SIZE => 1024;1201sub vi_clientinput {1202if ($viewer{client}->read(my $buf, VIEWER_BLOCK_SIZE)) {1203$viewer{rcvbuf} .= $buf;1204if ($viewer{rcvbuf} =~ s/^(?:(active|\d+)|(last|up|down))\n//igm) {1205if (defined $2) {1206Irssi::command("window $2");1207}1208elsif (lc $1 eq 'active' && $viewer{use_ack}) {1209Irssi::command($viewer{use_ack});1210}1211else {1212Irssi::command("window goto $1");1213}1214}1215}1216else {1217vi_close_client();1218Irssi::timeout_add_once(100, 'syncViewer', undef);1219}1220}12211222sub stop_viewer {1223Irssi::timeout_remove(delete $viewer{retry}) if exists $viewer{retry};1224vi_close_client();1225Irssi::input_remove(delete $viewer{server_tag}) if exists $viewer{server_tag};1226return unless $viewer{server};1227$viewer{server}->close;1228delete $viewer{server};1229}1230sub _encode_var {1231my $str;1232while (@_) {1233my ($name, $var) = splice @_, 0, 2;1234my $type = ref $var ? $var =~ /HASH/ ? 'map' : $var =~ /ARRAY/ ? 'list' : '' : '';1235$str .= "\n\U$name$type\_begin\n";1236if ($type eq 'map') {1237no warnings 'numeric';1238$str .= " $_\n ${$var}{$_}\n" for sort { $a <=> $b || $a cmp $b } keys %$var;1239}1240elsif ($type eq 'list') {1241$str .= " $_\n" for @$var;1242}1243else {1244$str .= " $var\n";1245}1246$str .= "\U$name$type\_end\n";1247}1248$str1249}1250sub syncViewer {1251if ($viewer{client}) {1252@actString = ();1253if ($currentLines) {1254killOldStatus();1255$currentLines = 0;1256}1257my $str;1258unless ($viewer{client_keymap}) {1259$str .= _encode_var('key', +{ %nummap, %specialmap });1260$viewer{client_keymap} = 1;1261}1262unless ($viewer{client_settings}) {1263$str .= _encode_var(1264block => $S{block},1265ha => $S{height_adjust},1266mc => $S{maxcolumns},1267ml => $S{maxlines},1268);1269$viewer{client_settings} = 1;1270}1271unless ($viewer{client_env}) {1272$str .= _encode_var(irssienv => +{1273length $ENV{TMUX_PANE} && length $ENV{TMUX} ?1274(tmux_pane => $ENV{TMUX_PANE},1275tmux_srv => $ENV{TMUX}) : (),1276length $ENV{WINDOWID} ?1277(xwinid => $ENV{WINDOWID}) : (),1278});1279$viewer{client_env} = 1;1280}1281my $separator = _get_format(set 'separator');1282my $sepLen = sb_length($separator);1283my $item_bg = _get_format(set 'viewer_item_bg');1284my $title = _get_format(set 'title');1285if (length $title) {1286$title =~ s{\\(.)|(.)}{1287defined $2 ? quotemeta $21288: $1 eq 'V' ? '\U'1289: $1 eq ':' ? quotemeta '%N'1290: $1 =~ /^[uUFQE]$/ ? "\\$1"1291: quotemeta "\\$1"1292}sge;1293$title = eval qq{"$title"};1294}1295$str .= _encode_var(redraw => 1) if delete $viewer{fullRedraw};1296$str .= _encode_var(separator => $separator,1297seplen => $sepLen,1298itembg => $item_bg,1299title => $title,1300mouse => $mouse_coords{refnum},1301key2 => \%wnmap_exp,1302win => \@win_items);13031304my $was = $viewer{client}->blocking(1);1305$viewer{client}->print($str);1306$viewer{client}->blocking($was);1307}1308elsif ($viewer{server}) {1309if (defined $viewer{msg}) {1310@actString = ((uc setc()).": $viewer{msg}");1311}1312else {1313@actString = ();1314}1315}1316elsif (defined $viewer{msg}) {1317@actString = ((uc setc()).": $viewer{msg}");1318}1319if (@actString) {1320Irssi::timeout_add_once(100, 'syncLines', undef);1321}1322elsif ($currentLines) {1323killOldStatus();1324$currentLines = 0;1325}1326}13271328sub reset_awl {1329Irssi::timeout_remove($shade_line_timer) if $shade_line_timer; $shade_line_timer = undef;1330my $was_sort = $S{sort} // '';1331my $was_xform = $S{xform} // '';1332my $was_shared = $S{shared_sbar};1333my $was_no_hint = $S{no_mode_hint};1334%S = (1335sort => Irssi::settings_get_str( set 'sort'),1336fancy_abbrev => Irssi::settings_get_str('fancy_abbrev'),1337xform => Irssi::settings_get_str( set 'custom_xform'),1338block => Irssi::settings_get_int( set 'block'),1339banned_on => Irssi::settings_get_bool('banned_channels_on'),1340prefer_name => Irssi::settings_get_bool(set 'prefer_name'),1341hide_data => Irssi::settings_get_int( set 'hide_data'),1342hide_name => Irssi::settings_get_int( set 'hide_name_data'),1343hide_empty => Irssi::settings_get_int( set 'hide_empty'),1344detach => Irssi::settings_get_str( set 'detach'),1345detach_data => Irssi::settings_get_int( set 'detach_data'),1346detach_aht => Irssi::settings_get_bool(set 'detach_aht'),1347sbar_maxlen => Irssi::settings_get_bool(set 'sbar_maxlength'),1348placement => Irssi::settings_get_str( set 'placement'),1349position => Irssi::settings_get_int( set 'position'),1350maxlines => Irssi::settings_get_int( set 'maxlines'),1351maxcolumns => Irssi::settings_get_int( set 'maxcolumns'),1352all_disable => Irssi::settings_get_bool(set 'all_disable'),1353height_adjust => Irssi::settings_get_int( set 'height_adjust'),1354mouse_offset => Irssi::settings_get_int( set 'mouse_offset'),1355mouse_scroll => Irssi::settings_get_int( 'mouse_scroll'),1356mouse_escape => Irssi::settings_get_int( 'mouse_escape'),1357line_shade => Irssi::settings_get_time(set 'last_line_shade'),1358no_mode_hint => Irssi::settings_get_bool(set 'no_mode_hint'),1359viewer_launch => Irssi::settings_get_bool(set 'viewer_launch'),1360viewer_launch_env => Irssi::settings_get_str(set 'viewer_launch_env'),1361viewer_xwin_command => Irssi::settings_get_str(set 'viewer_xwin_command'),1362viewer_custom_command => Irssi::settings_get_str(set 'viewer_custom_command'),1363viewer_tmux_position => Irssi::settings_get_str(set 'viewer_tmux_position'),1364);1365$S{fancy_strict} = $S{fancy_abbrev} =~ /^strict/i;1366$S{fancy_head} = $S{fancy_abbrev} =~ /^head/i;1367my $shared = Irssi::settings_get_str(set 'shared_sbar');1368if ($shared =~ /^(\d+)([<])(\d+)$/) {1369$S{shared_sbar} = [$1, $2, $3];1370}1371else {1372Irssi::settings_set_str(set 'shared_sbar', 'OFF');1373$S{shared_sbar} = undef;1374}1375lock_keys(%S);1376if ($was_sort ne $S{sort}) {1377$print_text_activity = undef;1378my @sort_order = grep { @$_ > 4 } map {1379s/^\s*//;1380my $reverse = s/^\W*\K[-!]//;1381my $undef_check = s/^\W*\K~// ? 1 : undef;1382my $equal_check = s/=(.*)\s?$// ? $1 : undef;1383s/\s*$//;1384my $ignore_case = s/#i$// ? 1 : undef;13851386$print_text_activity = 1 if $_ eq 'last_line';13871388my @path = split '/';1389my $class_check = @path && $path[-1] =~ s/(::.*)$// ? $1 : undef;1390my $lru = "@path" eq 'lru';13911392[ $reverse ? -1 : 1, $undef_check, $equal_check, $class_check, $ignore_case, $lru, @path ]1393} "$S{sort}," =~ /([^+,]*|[^+,]*=[^,]*?\s(?=\+)|[^+,]*=[^,]*)[+,]/g;1394$window_sort_func = sub {1395no warnings qw(numeric uninitialized);1396for my $so (@sort_order) {1397my @x = map {1398my $ret = 0;1399$_ = lc1459($_) if defined $_ && !ref $_ && $so->[4];1400$ret = $_ eq ($so->[4] ? lc1459($so->[2]) : $so->[2]) ? 1 : -1 if defined $so->[2];1401$ret = defined $_ ? ($ret || -3) : 3 if $so->[1];1402$ret = ref $_ && $_->isa('Irssi'.$so->[3]) ? 2 : ($ret || -2) if $so->[3];1403-$ret || $_1404}1405map {1406$so->[5] ? $_->[0] : reduce { return unless ref $a; $a->{$b} } $_->[1], @{$so}[6..$#$so]1407} $a, $b;1408return ((($x[0] <=> $x[1] || $x[0] cmp $x[1]) * $so->[0]) || next);1409}1410return ($a->[1]{refnum} <=> $b->[1]{refnum});1411};1412}1413if ($was_xform ne $S{xform}) {1414if ($S{xform} !~ /\S/) {1415$custom_xform = undef;1416}1417else {1418my $script_pkg = __PACKAGE__ . '::custom_xform';1419local $@;1420$custom_xform = eval qq{1421package $script_pkg;1422use strict;1423no warnings;1424our (\$QUERY, \$CHANNEL, \$TAG, \$NAME);1425return sub {1426# line 1 @{[ set 'custom_xform' ]}\n$S{xform}\n}};1427if ($@) {1428$@ =~ /^(.*)/;1429print '%_'.(set 'custom_xform').'%_ did not compile: '.$1;1430}1431}1432}14331434my $new_settings = join "\n", $VIEWER_MODE1435? ("\\", $S{block}, $S{height_adjust}, $S{maxlines}, $S{maxcolumns})1436: ("!", $S{placement}, $S{position});14371438my $first_viewer = $settings_str eq '1';1439if ($settings_str ne $new_settings) {1440@actString = ();1441%abbrev_cache = ();1442$currentLines = 0;1443killOldStatus();1444delete $viewer{client_settings};1445$settings_str = $new_settings;1446}14471448my $was_mouse_mode = $MOUSE_ON;1449if ($MOUSE_ON = Irssi::settings_get_bool(set 'mouse') and !$was_mouse_mode) {1450install_mouse();1451}1452elsif ($was_mouse_mode and !$MOUSE_ON) {1453uninstall_mouse();1454}14551456unless ($first_viewer) {1457my $path = Irssi::settings_get_str(set 'path');1458my $was_viewer_mode = $VIEWER_MODE;1459if ($was_viewer_mode &&1460defined $viewer{path} && $viewer{path} ne $path) {1461stop_viewer();1462$was_viewer_mode = 0;1463}1464elsif ($was_viewer_mode && $S{no_mode_hint} != $was_no_hint + 0) {1465set_viewer_mode_hint();1466}1467$viewer{path} = $path;1468if ($VIEWER_MODE = Irssi::settings_get_bool(set 'viewer') and !$was_viewer_mode) {1469start_viewer();1470}1471elsif ($was_viewer_mode and !$VIEWER_MODE) {1472stop_viewer();1473}1474}14751476%banned_channels = map { lc1459(as_uni($_)) => undef }1477split ' ', Irssi::settings_get_str('banned_channels');14781479%detach_map = ($S{detach_aht}1480? (map { ( lc1459(as_uni($_)) => undef ) }1481split ' ', Irssi::settings_get_str('activity_hide_targets')) : (),1482(map { my ($k, $v) = (split /(?:,(-?\d+))$/, $_)[0, 1];1483( lc1459(as_uni($k)) => $v ) }1484split ' ', $S{detach}));14851486my @sb_base = split /\177/, sb_format_expand("{sbstart}{sb \177}{sbend}"), 2;1487$sb_base_width_pre = sb_length($sb_base[0]);1488$sb_base_width_post = max 0, sb_length($sb_base[1])-1;1489$sb_base_width = $sb_base_width_pre + $sb_base_width_post;14901491if ($print_text_activity && $S{line_shade}) {1492$shade_line_timer = Irssi::timeout_add(max(10 * GLOB_QUEUE_TIMER, 100*$S{line_shade}**(1/3)), 'wl_changed', undef);1493}14941495$CHANGED{AWINS} = 1;1496}14971498sub hide_window {1499my ($data) = @_;1500my $ent;15011502$data =~ s/\s*$//;1503my $win = Irssi::active_win;1504my $number = $win->{refnum};1505my $name = as_uni($win->{name});1506my $active = as_uni($win->get_active_name) // '';1507my $tag = $win->{active} && $win->{active}{server} ? as_uni($win->{active}{server}{tag}) // '' : '';1508if (length $name) {1509$ent = "$name";1510}1511elsif (length $tag && length $active) {1512$ent = "$tag/$active";1513}1514else {1515$ent = "$number";1516}15171518my $found = 0;1519my @setting;1520for my $s (split ' ', $S{detach}) {1521my ($k, $v) = (split /(?:,(-?\d+))$/, $s)[0, 1];1522if (lc1459(as_uni($k)) eq lc1459($ent)) {1523unless ($found) {1524if ($data =~ /^(-?\d+)$/) {1525$ent .= ",$1";1526}1527if (defined $v && 0 == abs $v) {1528$win->print("Hiding window $ent");1529}1530push @setting, as_tc($ent);1531$found = 1;1532}1533}1534else {1535push @setting, defined $v ? "$k,$v" : $k;1536}1537}1538unless ($found) {1539$win->print("Hiding window $ent");1540if ($data =~ /^(-?\d+)$/) {1541$ent .= ",$1";1542}1543push @setting, as_tc($ent);1544}15451546if (@setting) {1547Irssi::command("^set ".(set 'detach')." @setting");1548} else {1549Irssi::command("^set -clear ".(set 'detach'));1550}1551}15521553sub unhide_window {1554my ($data, $server, $witem) = @_;1555my $win = Irssi::active_win;1556my $number = $win->{refnum};1557my $name = as_uni($win->{name});1558my $active = as_uni($win->get_active_name) // '';1559my $tag = $win->{active} && $win->{active}{server} ? as_uni($win->{active}{server}{tag}) // '' : '';15601561my %detach_aht;1562if ($S{detach_aht}) {1563%detach_aht = (map { ( lc1459(as_uni($_)) => undef ) }1564split ' ', Irssi::settings_get_str('activity_hide_targets'));1565}1566my @setting;1567my @kills = (length $name ? $name : undef,1568length $tag && length $active ? "$tag/$active" : undef,1569length $active ? $active : undef,1570$number);1571my @was_unhidden = (0) x @kills;1572for my $s (split ' ', $S{detach}) {1573my ($k, $v) = (split /(?:,(-?\d+))$/, $s)[0, 1];1574my $k2 = lc1459(as_uni($k));1575my $kill;1576for my $ki (0..$#kills) {1577if (defined $kills[$ki] && $k2 eq lc1459($kills[$ki])) {1578$kill = $ki;1579}1580}15811582if (defined $kill) {1583if (defined $v && 0 == abs $v) {1584$was_unhidden[$kill] = 1;1585push @setting, defined $v ? "$k,$v" : $k;1586} else {1587$win->print("Unhiding window $kills[$kill]");1588}1589}1590else {1591push @setting, defined $v ? "$k,$v" : $k;1592}1593}1594my @is_hidden = (defined $kills[0] && (exists $detach_map{"*"} || exists $detach_map{"::all"}),1595defined $kills[1] && (exists $detach_map{lc1459("$tag/*")} || exists $detach_map{lc1459("$tag/::all")}1596|| exists $detach_map{"*"} || exists $detach_map{"::all"}),1597defined $kills[2] && (exists $detach_map{"*"} || exists $detach_map{"::all"}),1598(exists $detach_map{"*"} || exists $detach_map{"::all"})1599);1600for my $ki (1, 2, 0, 3) {1601if ($is_hidden[$ki]) {1602unless ($was_unhidden[$ki]) {1603$win->print("Unhiding window $kills[$ki]");1604push @setting, "$kills[$ki],0";1605$was_unhidden[$ki] = 1;1606}1607last;1608}1609}1610my @is_hidden_aht = (defined $kills[0] && (exists $detach_aht{lc1459($name)}1611|| exists $detach_aht{"*"} || exists $detach_aht{"::all"}),1612defined $kills[1] && (exists $detach_aht{lc1459("$tag/$active")}1613|| exists $detach_aht{lc1459($active)}1614|| exists $detach_aht{lc1459("$tag/*")} || exists $detach_aht{lc1459("$tag/::all")}1615|| exists $detach_aht{"*"} || exists $detach_aht{"::all"}),1616defined $kills[2] && (exists $detach_aht{lc1459($active)}1617|| exists $detach_aht{"*"} || exists $detach_aht{"::all"}),1618(exists $detach_aht{$number} || exists $detach_aht{"*"} || exists $detach_aht{"::all"})1619);1620for my $ki (1, 2, 0, 3) {1621if ($is_hidden_aht[$ki]) {1622unless ($was_unhidden[$ki]) {1623$win->print("Unhiding window $kills[$ki], it is hidden because ".(set 'detach_aht')." is ON");1624push @setting, "$kills[$ki],0";1625$was_unhidden[$ki] = 1;1626}1627last;1628}1629}16301631if (@setting) {1632Irssi::command("^set ".(set 'detach')." @setting");1633} else {1634Irssi::command("^set -clear ".(set 'detach'));1635}1636}16371638sub ack_window {1639my ($data, $server, $witem) = @_;1640my $win = Irssi::active_win;1641my $number = $win->{refnum};1642if (grep { $_->{cmd} eq 'ack' } Irssi::commands) {1643my $Orig_Irssi_windows = \&Irssi::windows;1644local *Irssi::windows = sub () { grep { !_is_detached($_, $number) } $Orig_Irssi_windows->() };1645Irssi::command("ack" . (length $data ? " $data" : ""));1646} else {1647my $ignore_refnum = Irssi::settings_get_bool('active_window_ignore_refnum');1648my $max_win;1649my $max_act = 0;1650my $max_ref = 0;1651for my $rec (Irssi::windows) {1652next if _is_detached($rec, $number);16531654# ignore refnum1655if ($ignore_refnum &&1656$rec->{data_level} > 0 && $max_act < $rec->{data_level}) {1657$max_act = $rec->{data_level};1658$max_win = $rec;1659}16601661# windows with lower refnums break ties1662elsif (!$ignore_refnum &&1663$rec->{data_level} > 0 &&1664($rec->{data_level} > $max_act ||1665($rec->{data_level} == $max_act && $rec->{refnum} < $max_ref))) {1666$max_act = $rec->{data_level};1667$max_win = $rec;1668$max_ref = $rec->{refnum};1669}1670}1671$max_win->set_active if defined $max_win;1672}1673}16741675sub refnum_changed {1676my ($win, $old_refnum) = @_;1677my @old_setting = split ' ', $S{detach};1678my @setting = map {1679my ($k, $v) = (split /(?:,(-?\d+))$/, $_)[0, 1];1680if ($k eq $old_refnum) {1681$win->{refnum} . (defined $v ? ",$v" : "")1682}1683else {1684$_1685}1686} @old_setting;1687if ("@old_setting" ne "@setting") {1688$S{detach} = "@setting";1689Irssi::settings_set_str(set 'detach', "@setting");1690&setup_changed;1691}1692else {1693&wl_changed;1694}1695}16961697sub window_destroyed {1698my ($win) = @_;1699my @old_setting = split ' ', $S{detach};1700my @setting = grep {1701my ($k, $v) = (split /(?:,(-?\d+))$/, $_)[0, 1];1702if ($k eq $win->{refnum}) {17030;1704}1705else {17061;1707}1708} @old_setting;1709if ("@old_setting" ne "@setting") {1710$S{detach} = "@setting";1711Irssi::settings_set_str(set 'detach', "@setting");1712&setup_changed;1713}1714else {1715&awins_changed;1716}1717}17181719sub stop_mouse_tracking {1720print STDERR "\e[?1005l\e[?1000l";1721}1722sub start_mouse_tracking {1723print STDERR "\e[?1000h\e[?1005h";1724}1725sub install_mouse {1726Irssi::command_bind('mouse_xterm' => 'mouse_xterm');1727Irssi::command('^bind meta-[M command mouse_xterm');1728Irssi::signal_add_first('gui key pressed' => 'mouse_key_hook');1729start_mouse_tracking();1730}1731sub uninstall_mouse {1732stop_mouse_tracking();1733Irssi::signal_remove('gui key pressed' => 'mouse_key_hook');1734Irssi::command('^bind -delete meta-[M');1735Irssi::command_unbind('mouse_xterm' => 'mouse_xterm');1736}17371738sub awl_mouse_event {1739return if $VIEWER_MODE;1740if ((($_[0] == 3 and $_[3] == 0)1741|| $_[0] == 64 || $_[0] == 65) and1742$_[1] == $_[4] and $_[2] == $_[5]) {1743my $top = lc $S{placement} eq 'top';1744my ($pos, $line) = @_[1 .. 2];1745unless ($top) {1746$line -= $screenHeight;1747$line += $currentLines;1748$line += $S{mouse_offset};1749}1750else {1751$line -= $S{mouse_offset};1752}1753$pos -= $sb_base_width_pre;1754return if $line < 0 || $line >= $currentLines;1755if ($_[0] == 64) {1756Irssi::command('window up');1757}1758elsif ($_[0] == 65) {1759Irssi::command('window down');1760}1761elsif (exists $mouse_coords{$line}{$pos}) {1762my $win = $mouse_coords{$line}{$pos};1763Irssi::command('window ' . $win);1764}1765Irssi::signal_stop;1766}1767}17681769sub mouse_scroll_event {1770return unless $S{mouse_scroll};1771if (($_[3] == 64 or $_[3] == 65) and1772$_[0] == $_[3] and $_[1] == $_[4] and $_[2] == $_[5]) {1773my $cmd = 'scrollback goto ' . ($_[3] == 64 ? '-' : '+') . $S{mouse_scroll};1774Irssi::active_win->command($cmd);1775Irssi::signal_stop;1776}1777elsif ($_[0] == 64 or $_[0] == 65) {1778Irssi::signal_stop;1779}1780}17811782sub mouse_escape {1783return unless $S{mouse_escape} > 0;1784if ($_[0] == 3) {1785my $tm = $S{mouse_escape};1786$tm *= 1000 if $tm < 1000;1787stop_mouse_tracking();1788Irssi::timeout_add_once($tm, 'start_mouse_tracking', undef);1789Irssi::signal_stop;1790}1791}17921793sub UNLOAD {1794@actString = ();1795killOldStatus();1796stop_viewer() if $VIEWER_MODE;1797uninstall_mouse() if $MOUSE_ON;1798}17991800sub addPrintTextHook { # update on print text1801return unless defined $^S;1802return if $BLOCK_ALL;1803return unless $print_text_activity;1804return if $_[0]->{level} == 262144 and $_[0]->{target} eq ''1805and !defined($_[0]->{server});1806&wl_changed;1807}18081809sub block_event_window_change {1810Irssi::signal_stop;1811}18121813sub update_awins {1814my @wins = Irssi::windows;1815local $BLOCK_ALL = 1;1816Irssi::signal_add_first('window changed' => 'block_event_window_change');1817my $bwin =1818my $awin = Irssi::active_win;1819my $lwin;1820my $defer_irssi_broken_last;1821unless ($wins[0]{refnum} == $awin->{refnum}) {1822# special case: more than 1 last win, so /win last;1823# /win last doesn't come back to the current window. eg. after1824# connect & autojoin; we can't handle this situation, bail out1825$defer_irssi_broken_last = 1;1826}1827else {1828$awin->command('window last');1829$lwin = Irssi::active_win;1830$lwin->command('window last');1831$defer_irssi_broken_last = $lwin->{refnum} == $bwin->{refnum};1832}1833my $awin_counter = 0;1834Irssi::signal_remove('window changed' => 'block_event_window_change');1835unless ($defer_irssi_broken_last) {1836# we need to keep the fe-windows code running here1837Irssi::signal_add_priority('window changed' => 'block_event_window_change', -99);1838%awins = %wnmap_exp = ();1839do {1840Irssi::active_win->command('window up');1841$awin = Irssi::active_win;1842$awins{$awin->{refnum}} = undef;1843++$awin_counter;1844} until ($awin->{refnum} == $bwin->{refnum} || $awin_counter >= @wins);1845Irssi::signal_remove('window changed' => 'block_event_window_change');18461847Irssi::signal_add_first('window changed' => 'block_event_window_change');1848for my $key (keys %wnmap) {1849next unless Irssi::window_find_name($key) || Irssi::window_find_item($key);1850$awin->command("window goto $key");1851my $cwin = Irssi::active_win;1852$wnmap_exp{ $cwin->{refnum} } = $wnmap{$key};1853$cwin->command('window last')1854if $cwin->{refnum} != $awin->{refnum};1855}1856for my $win (reverse @wins) { # restore original window order1857Irssi::active_win->command('window '.$win->{refnum});1858}1859$awin->command('window '.$lwin->{refnum}); # restore last win1860Irssi::active_win->command('window last');1861Irssi::signal_remove('window changed' => 'block_event_window_change');1862}1863$CHANGED{WL} = 1;1864}18651866sub resizeTerm {1867if (defined (my $r = `stty size 2>/dev/null`)) {1868($screenHeight, $screenWidth) = split ' ', $r;1869$CHANGED{SETUP} = 1;1870}1871else {1872$CHANGED{SIZE} = 1;1873}1874}18751876sub awl_refresh {1877$globTime = undef;1878resizeTerm() if delete $CHANGED{SIZE};1879reset_awl() if delete $CHANGED{SETUP};1880update_awins() if delete $CHANGED{AWINS};1881update_wl() if delete $CHANGED{WL};1882}18831884sub termsize_changed { $CHANGED{SIZE} = 1; &queue_refresh; }1885sub setup_changed { $CHANGED{SETUP} = 1; &queue_refresh; }1886sub awins_changed { $CHANGED{AWINS} = 1; &queue_refresh; }1887sub wl_changed { $CHANGED{WL} = 1; &queue_refresh; }18881889sub window_changed {1890&awins_changed if $_[1];1891}18921893sub queue_refresh {1894return if $BLOCK_ALL;1895Irssi::timeout_remove($globTime)1896if defined $globTime; # delay the update further1897$globTime = Irssi::timeout_add_once(GLOB_QUEUE_TIMER, 'awl_refresh', undef);1898}18991900sub awl_init {1901termsize_changed();1902setup_changed();1903update_keymap();1904Irssi::timeout_remove($globTime)1905if defined $globTime;1906awl_refresh();1907termsize_changed();1908}19091910sub runsub {1911my $cmd = shift;1912sub {1913my ($data, $server, $item) = @_;1914Irssi::command_runsub($cmd, $data, $server, $item);1915};1916}19171918Irssi::signal_register({1919'gui mouse' => [qw/int int int int int int/],1920});1921{ my $broken_expandos = (Irssi::version >= 20081128 && Irssi::version < 20110210)1922? sub { my $x = shift; $x =~ s/\$\{cumode_space\}/ /; $x } : undef;1923Irssi::theme_register([1924map { $broken_expandos ? $broken_expandos->($_) : $_ }1925set 'display_nokey' => '$N${cumode_space}$H$C$S',1926set 'display_key' => '$Q${cumode_space}$H$C$S',1927set 'display_nokey_visible' => '%2$N${cumode_space}$H$C$S',1928set 'display_key_visible' => '%2$Q${cumode_space}$H$C$S',1929set 'display_nokey_active' => '%1$N${cumode_space}$H$C$S',1930set 'display_key_active' => '%1$Q${cumode_space}$H$C$S',1931set 'display_header' => '%8$C|${N}',1932set 'name_display' => '$0',1933set 'separator' => ' ',1934set 'separator2' => '',1935set 'abbrev_chars' => "~\x{301c}",1936set 'viewer_item_bg' => sb_format_expand('{sb_background}'),1937set 'title' => '\V'.setc().'\:',1938]);1939}1940Irssi::settings_add_bool(setc, set 'prefer_name', 0); #1941Irssi::settings_add_int( setc, set 'hide_empty', 0); #1942Irssi::settings_add_int( setc, set 'hide_data', 0); #1943Irssi::settings_add_str( setc, set 'detach', ''); #1944Irssi::settings_add_int( setc, set 'detach_data', -3); #1945Irssi::settings_add_bool(setc, set 'detach_aht', 0); #1946Irssi::settings_add_int( setc, set 'hide_name_data', 0); #1947Irssi::settings_add_int( setc, set 'maxlines', 9); #1948Irssi::settings_add_int( setc, set 'maxcolumns', 4); #1949Irssi::settings_add_int( setc, set 'block', 15); #1950Irssi::settings_add_bool(setc, set 'sbar_maxlength', 1); #1951Irssi::settings_add_int( setc, set 'height_adjust', 2); #1952Irssi::settings_add_str( setc, set 'sort', 'refnum'); #1953Irssi::settings_add_str( setc, set 'placement', 'bottom'); #1954Irssi::settings_add_int( setc, set 'position', 0); #1955Irssi::settings_add_bool(setc, set 'all_disable', 1); #1956Irssi::settings_add_bool(setc, set 'viewer', 1); #1957Irssi::settings_add_str( setc, set 'shared_sbar', 'OFF'); #1958Irssi::settings_add_bool(setc, set 'mouse', 0); #1959Irssi::settings_add_str( setc, set 'path', Irssi::get_irssi_dir . '/_windowlist'); #1960Irssi::settings_add_str( setc, set 'custom_xform', ''); #1961Irssi::settings_add_time(setc, set 'last_line_shade', '0'); #1962Irssi::settings_add_int( setc, set 'mouse_offset', 1); #1963Irssi::settings_add_int( setc, 'mouse_scroll', 3); #1964Irssi::settings_add_int( setc, 'mouse_escape', 1); #1965Irssi::settings_add_str( setc, 'banned_channels', '');1966Irssi::settings_add_bool(setc, 'banned_channels_on', 1);1967Irssi::settings_add_str( setc, 'fancy_abbrev', 'fancy'); #1968Irssi::settings_add_bool(setc, set 'no_mode_hint', 0); #1969Irssi::settings_add_bool(setc, set 'viewer_launch', 1); #1970Irssi::settings_add_str( setc, set 'viewer_launch_env', ''); #1971Irssi::settings_add_str( setc, set 'viewer_tmux_position', 'left'); #1972Irssi::settings_add_str( setc, set 'viewer_xwin_command', 'xterm +sb -e %A'); #1973Irssi::settings_add_str( setc, set 'viewer_custom_command', ''); #19741975Irssi::signal_add_last({1976'setup changed' => 'setup_changed',1977'print text' => 'addPrintTextHook',1978'terminal resized' => 'termsize_changed',1979'setup reread' => 'screenFullRedraw',1980'window hilight' => 'wl_changed',1981'command format' => 'wl_changed',1982});1983Irssi::signal_add({1984'window changed' => 'window_changed',1985'window item changed' => 'wl_changed',1986'window changed automatic' => 'window_changed',1987'window created' => 'awins_changed',1988'window destroyed' => 'window_destroyed',1989'window name changed' => 'wl_changed',1990'window refnum changed' => 'refnum_changed',1991});1992Irssi::signal_add_last('gui mouse' => 'mouse_escape');1993Irssi::signal_add_last('gui mouse' => 'mouse_scroll_event');1994Irssi::signal_add_last('gui mouse' => 'awl_mouse_event');1995Irssi::command_bind( setc() => runsub(setc()) );1996Irssi::command_bind( setc() . ' redraw' => 'screenFullRedraw' );1997Irssi::command_bind( setc() . ' restart' => 'restartViewerServer' );1998Irssi::command_bind( setc() . ' attach' => 'unhide_window' );1999Irssi::command_bind( setc() . ' detach' => 'hide_window' );2000Irssi::command_bind( setc() . ' ack' => 'ack_window' );20012002{2003my $l = set 'shared';2004{2005no strict 'refs';2006*{$l} = $awl_shared_empty;2007}2008Irssi::statusbar_item_register($l, '$0', $l);2009}20102011awl_init();20122013# Mouse script based on irssi mouse patch by mirage2014{ my $mouse_status = -1; # -1:off 0,1,2:filling mouse_combo2015my @mouse_combo; # 0:button 1:x 2:y2016my @mouse_previous; # previous contents of mouse_combo20172018sub mouse_xterm_off {2019$mouse_status = -1;2020}2021sub mouse_xterm {2022$mouse_status = 0;2023Irssi::timeout_add_once(10, 'mouse_xterm_off', undef);2024}20252026sub mouse_key_hook {2027my ($key) = @_;2028if ($mouse_status != -1) {2029if ($mouse_status == 0) {2030@mouse_previous = @mouse_combo;2031#if @mouse_combo && $mouse_combo[0] < 64;2032}2033$mouse_combo[$mouse_status] = $key - 32;2034$mouse_status++;2035if ($mouse_status == 3) {2036$mouse_status = -1;2037# match screen coordinates2038$mouse_combo[1]--;2039$mouse_combo[2]--;2040Irssi::signal_emit('gui mouse', @mouse_combo[0 .. 2], @mouse_previous[0 .. 2]);2041}2042Irssi::signal_stop;2043}2044}2045}20462047sub string_LCSS {2048my $str = join "\0", @_;2049(sort { length $b <=> length $a } $str =~ /(?=(.+).*\0.*\1)/g)[0]2050}20512052# workaround for issue #2712053{ package Irssi::Nick }20542055# workaround for issue #5722056@Irssi::UI::Exec::ISA = 'Irssi::Windowitem'2057if Irssi::version >= 20140822 && Irssi::version <= 20161101 && !@Irssi::UI::Exec::ISA;20582059UNITCHECK2060{ package AwlViewer;2061use strict;2062use warnings;2063no warnings 'redefine';2064use Encode;2065use IO::Socket::UNIX;2066use IO::Select;2067use List::Util qw(max);2068use constant BLOCK_SIZE => 1024;2069use constant RECONNECT_TIME => 5;20702071my $sockpath;20722073our $VERSION = '0.8';20742075our ($got_int, $resized, $timeout);20762077my %vars;2078my (%c2w, @seqlist);2079my %mouse_coords;2080my (@mouse, @last_mouse);2081my ($err, $sock, $loop);2082my ($keybuf, $rcvbuf);2083my @screen;2084my ($screenHeight, $screenWidth);2085my ($disp_update, $fs_open, $one_shot_integration, $one_shot_resize);2086my $integration_position;2087my $show_title_bar;20882089sub connect_it {2090$sock = IO::Socket::UNIX->new(2091Type => SOCK_STREAM,2092Peer => $sockpath,2093);2094unless ($sock) {2095$err = $!;2096return;2097}2098$sock->blocking(0);2099$loop->add($sock);2100}21012102sub remove_conn {2103my $fh = shift;2104$loop->remove($fh);2105$fh->close;2106$sock = undef;2107%vars = ();2108@screen = ();2109}21102111{ package Terminfo; # xterm2112sub civis { "\e[?25l" }2113sub sc { "\e7" }2114sub cup { "\e[" . ($_[0] + 1) . ';' . ($_[1] + 1) . 'H' }2115sub el { "\e[K" }2116sub rc { "\e8" }2117sub cnorm { "\e[?25h" }2118sub setab { "\e[4" . $_[0] . 'm' }2119sub setaf { "\e[3" . $_[0] . 'm' }2120sub setaf16 { "\e[9" . $_[0] . 'm' }2121sub setab16 { "\e[10" . $_[0] . 'm' }2122sub setaf256 { "\e[38;5;" . $_[0] . 'm' }2123sub setab256 { "\e[48;5;" . $_[0] . 'm' }2124sub sgr0 { "\e[0m" }2125sub bold { "\e[1m" }2126sub it { "\e[3m" }2127sub ul { "\e[4m" }2128sub blink { "\e[5m" }2129sub rev { "\e[7m" }2130sub op { "\e[39;49m" }2131sub exit_bold { "\e[22m" }2132sub exit_it { "\e[23m" }2133sub exit_ul { "\e[24m" }2134sub exit_blink { "\e[25m" }2135sub exit_rev { "\e[27m" }2136sub smcup { "\e[?1049h" }2137sub rmcup { "\e[?1049l" }2138sub smmouse { "\e[?1000h\e[?1005h" }2139sub rmmouse { "\e[?1005l\e[?1000l" }2140}21412142sub init {2143$sockpath = shift // "$ENV{HOME}/.irssi/_windowlist";2144STDOUT->autoflush(1);2145printf "\r%swaiting for %s...", Terminfo::sc, $::IRSSI{name};21462147`stty -icanon -echo`;21482149$loop = IO::Select->new;2150STDIN->blocking(0);2151$loop->add(\*STDIN);21522153$SIG{INT} = sub {2154$got_int = 12155};2156$SIG{WINCH} = sub {2157$resized = 12158};21592160$resized = 3;21612162$disp_update = 2;21632164$show_title_bar = 1;2165}21662167sub enter_fs {2168return if $fs_open;2169safe_print(Terminfo::rc, Terminfo::smcup, Terminfo::civis, Terminfo::smmouse);2170$fs_open = 1;2171}21722173sub leave_fs {2174return unless $fs_open;2175safe_print(Terminfo::rmmouse, Terminfo::cnorm, Terminfo::rmcup);2176safe_print(sprintf "\r%swaiting for %s...", Terminfo::sc, $::IRSSI{name}) if $_[0];21772178$fs_open = 0;2179}21802181sub end_prog {2182leave_fs();2183STDIN->blocking(1);2184`stty sane`;2185printf "\r%s%sthanks for using %s\n", Terminfo::rc, Terminfo::el, $::IRSSI{name};2186}21872188sub safe_print {2189my $st = STDIN->blocking(1);2190print @_;2191STDIN->blocking($st);2192}21932194sub safe_qx {2195my $st = STDIN->blocking(1);2196my $ret = `$_[0]`;2197STDIN->blocking($st);2198$ret2199}22002201sub safe_print_sock {2202return unless $sock;2203my $was = $sock->blocking(1);2204$sock->print(@_);2205$sock->blocking($was);2206}22072208sub process_recv {2209my $need = 0;2210while ($rcvbuf =~ s/\n(.+)_BEGIN\n((?: .*\n)*)\1_END\n//) {2211my $var = lc $1;2212my $data = $2;2213my @data = split "\n ", "\n$data ", -1;2214shift @data; pop @data;2215my $itembg = $vars{itembg};2216if ($var =~ s/list$//) {2217$vars{$var} = \@data;2218}2219elsif ($var =~ s/map$//) {2220$vars{$var} = +{ @data };2221}2222else {2223$vars{$var} = join "\n", @data;2224}2225$need = 1 if $var eq 'win';2226$need = 1 if $var eq 'redraw' && $vars{$var};2227if (($itembg//'') ne ($vars{itembg}//'')) {2228$need = $vars{redraw} = 1;2229}2230_build_keymap() if $var eq 'key2';2231}2232$need2233}22342235{ my %ansi_table;2236my ($i, $j, $k) = (0, 0, 0);2237my %term_state;2238sub reset_term_state { my %old_term = %term_state; %term_state = (); %old_term }2239sub set_term_state { my %old_term = %term_state; %term_state = @_; %old_term }2240%ansi_table = (2241# fe-common::core::formats.c:format_expand_styles2242(map { my $t = $i++; ($_ => sub { my $n = $term_state{hicolor} ? \&Terminfo::setab16 : \&Terminfo::setab;2243$n->($t) }) } (split //, '01234567' )),2244(map { my $t = $j++; ($_ => sub { my $n = $term_state{hicolor} ? \&Terminfo::setaf16 : \&Terminfo::setaf;2245$n->($t) }) } (split //, 'krgybmcw' )),2246(map { my $t = $k++; ($_ => sub { my $n = $term_state{hicolor} ? \&Terminfo::setaf : \&Terminfo::setaf16;2247$n->($t) }) } (split //, 'KRGYBMCW')),2248# reset2249n => sub { $term_state{hicolor} = 0; my $r = Terminfo::op;2250for (qw(blink rev bold)) {2251$r .= Terminfo->can("exit_$_")->() if delete $term_state{$_};2252}2253{2254local $ansi_table{n} = $ansi_table{N};2255$r .= formats_to_ansi_basic($vars{itembg});2256}2257$r2258},2259N => sub { reset_term_state(); Terminfo::sgr0 },2260# flash/bright2261F => sub { my $n = 'blink'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },2262# reverse22638 => sub { my $n = 'rev'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },2264# bold2265"_" => sub { my $n = 'bold'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },2266# underline2267U => sub { my $n = 'ul'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },2268# italic2269I => sub { my $n = 'it'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },2270# bold, used as colour modifier if AWL_HI9 is set22719 => $ENV{AWL_HI9} ? sub { $term_state{hicolor} ^= 1; '' }2272: sub { my $n = 'bold'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },2273# delete other stuff2274(map { $_ => sub { '' } } (split //, ':|>#[')),2275# escape2276(map { my $close = $_; $_ => sub { $close } } (split //, '{}%')),2277);2278for my $base (0 .. 15) {2279my $close = $base;2280my $idx = ($close&8) | ($close&4)>>2 | ($close&2) | ($close&1)<<2;2281$ansi_table{ (sprintf "x0%x", $close) } =2282$ansi_table{ (sprintf "x0%X", $close) } =2283sub { Terminfo::setab256($idx) };2284$ansi_table{ (sprintf "X0%x", $close) } =2285$ansi_table{ (sprintf "X0%X", $close) } =2286sub { Terminfo::setaf256($idx) };2287}2288for my $plane (1 .. 6) {2289for my $coord (0 .. 35) {2290my $close = 16 + ($plane-1) * 36 + $coord;2291my $ch = $coord < 10 ? $coord : chr( $coord - 10 + ord 'a' );2292$ansi_table{ "x$plane$ch" } =2293$ansi_table{ "x$plane\U$ch" } =2294sub { Terminfo::setab256($close) };2295$ansi_table{ "X$plane$ch" } =2296$ansi_table{ "X$plane\U$ch" } =2297sub { Terminfo::setaf256($close) };2298}2299}2300for my $gray (0 .. 23) {2301my $close = 232 + $gray;2302my $ch = chr( $gray + ord 'a' );2303$ansi_table{ "x7$ch" } =2304$ansi_table{ "x7\U$ch" } =2305sub { Terminfo::setab256($close) };2306$ansi_table{ "X7$ch" } =2307$ansi_table{ "X7\U$ch" } =2308sub { Terminfo::setaf256($close) };2309}2310sub formats_to_ansi_basic {2311my $o = shift;2312$o =~ s/(%(X..|x..|.))/exists $ansi_table{$2} ? $ansi_table{$2}->() : $1/gex;2313$o2314}2315}23162317sub _header {2318my $str = $vars{title} // uc ::setc();2319my $ccs = qr/%(?:X(?:[1-6][0-9A-Z]|7[A-X])|[0-9BCFGIKMNRUWY_])/i;2320(my $stripstr = $str) =~ s/($ccs)//g;2321my $space = int( ((abs $vars{block}) - length $stripstr) / (1 + length $stripstr));2322if ($space > 0) {2323my $ss = ' ' x $space;2324my @x = $str =~ /((?:$ccs)*\X(?:(?:$ccs)*$)?)/g;2325$str = join $ss, '', @x, '';2326}2327($stripstr = $str) =~ s/($ccs)//g;2328my $pad = max 0, (abs $vars{block}) - length $stripstr;2329$str = ' ' x ($pad/2) . $str . ' ' x ($pad/2 + $pad%2);2330$str2331}23322333sub _add_item {2334my ($i, $j, $c, $wi, $screen, $mouse) = @_;2335$screen->[$i][$j] = "%N%n$wi";2336if (exists $vars{mouse}{$c - 1}) {2337$mouse->[$i][$j] = $vars{mouse}{$c - 1};2338}2339}2340sub update_screen {2341$disp_update = 0;2342unless ($sock && exists $vars{seplen} && exists $vars{block}) {2343leave_fs(1);2344return;2345}2346enter_fs();2347@screen = () if delete $vars{redraw};2348%mouse_coords = ();2349my $ncols = ($vars{seplen} + abs $vars{block}) ?2350int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ) : 0;2351my $xenl = ($vars{seplen} + abs $vars{block})2352&& $ncols > int( ($screenWidth + $vars{seplen} - 1) / ($vars{seplen} + abs $vars{block}) );2353my $nrows = $screenHeight - $vars{ha};2354my @wi = @{$vars{win}//[]};2355my $max_items = $ncols * $nrows;2356my $c = $show_title_bar ? 1 : 0;2357my $items = @wi + $c;2358my $titems = $items > $max_items ? $max_items : $items;2359my $i = 0;2360my $j = 0;2361my @new_screen;2362my @new_mouse;2363$new_screen[0][0] = _header() #. ' ' x $vars{seplen}2364if $show_title_bar;2365unless ($nrows > $ncols) { # line layout2366++$j if $show_title_bar;2367for my $wi (@wi) {2368if ($j >= $ncols) {2369$j = 0;2370++$i;2371}2372last if $i >= $nrows;2373_add_item($i, $j, $show_title_bar ? $c : $c + 1,2374$wi, \@new_screen, \@new_mouse);2375if ($c + 1 < $titems && $j + 1 < $ncols) {2376$new_screen[$i][$j] .= $vars{separator};2377}2378++$j;2379++$c;2380}2381}2382else { # column layout2383++$i if $show_title_bar;2384for my $wi (@wi) {2385if ($i >= $nrows) {2386$i = 0;2387++$j;2388}2389last if $j >= $ncols;2390_add_item($i, $j, $show_title_bar ? $c : $c + 1,2391$wi, \@new_screen, \@new_mouse);2392if ($c + $nrows < $titems) {2393$new_screen[$i][$j] .= $vars{separator};2394}2395++$i;2396++$c;2397}2398}2399my $step = $vars{seplen} + abs $vars{block};2400$i = 0;2401my $str = Terminfo::sc . Terminfo::sgr0;2402for (my $i = 0; $i < @new_screen; ++$i) {2403for (my $j = 0; $j < @{$new_screen[$i]}; ++$j) {2404if (defined $new_mouse[$i] && defined $new_mouse[$i][$j]) {2405my $from = $j * $step;2406$mouse_coords{$i}{$_} = $new_mouse[$i][$j]2407for $from .. $from + abs $vars{block};2408}2409next if defined $screen[$i] && defined $screen[$i][$j]2410&& $screen[$i][$j] eq $new_screen[$i][$j];2411$str .= Terminfo::cup($i, $j * $step)2412. formats_to_ansi_basic($new_screen[$i][$j])2413. Terminfo::sgr0;2414$str .= Terminfo::el if $j == $#{$new_screen[$i]} && (!$xenl || $j + 1 != $ncols);2415}2416}2417for (@new_screen .. $screenHeight - 1) {2418if (!@screen || defined $screen[$_]) {2419$str .= Terminfo::cup($_, 0) . Terminfo::sgr0 . Terminfo::el;2420}2421}2422$str .= Terminfo::rc;2423safe_print $str;2424@screen = @new_screen;2425}24262427sub handle_resize {2428if (defined (my $r = safe_qx('stty size'))) {2429($screenHeight, $screenWidth) = split ' ', $r;2430$resized = 0;2431@screen = ();2432$disp_update = 1;2433if ($one_shot_integration == 2) {2434$one_shot_resize--;2435}2436}2437else {2438}2439}24402441sub _build_keymap {2442%c2w = reverse( %{$vars{key}}, %{$vars{key2}} );2443if (!grep { /^[+-]./ } keys %c2w) {2444%c2w = (%c2w, map { ("-$_" => $c2w{$_}) } grep { !/^\^./ } keys %c2w);2445}2446%c2w = map {2447my $key = $_;2448s{^(-)?(\+)?(\^)?(.)}{2449join '', (2450($1 ? "\e" : ''),2451($2 ? "\e\e" : ''),2452($3 ? "$4"^"@" : $4)2453)2454}e;2455$_ => $c2w{$key}2456} keys %c2w;2457@seqlist = sort { length $b <=> length $a } keys %c2w;2458}24592460sub _match_tmux {2461length $ENV{TMUX} && exists $vars{irssienv}{tmux_srv} && length $vars{irssienv}{tmux_pane}2462&& $ENV{TMUX} eq $vars{irssienv}{tmux_srv}2463}24642465sub process_keys {2466Encode::_utf8_on($keybuf);2467my $win;2468my $use_mouse;2469my $maybe;2470KEY: while (length $keybuf && !$maybe) {2471$maybe = 0;2472if ($keybuf =~ s/^\e\[M(.)(.)(.)//) {2473@last_mouse = @mouse;# if @mouse && $mouse[0] < 64;2474@mouse = map { -32 + ord } ($1, $2, $3);2475$use_mouse = 1;2476next KEY;2477}2478for my $s (@seqlist) {2479if ($keybuf =~ s/^\Q$s//) {2480$win = $c2w{$s};2481$use_mouse = 0;2482next KEY;2483}2484elsif (length $keybuf < length $s && $s =~ /^\Q$keybuf/) {2485$maybe = 1;2486}2487}2488unless ($maybe) {2489substr $keybuf, 0, 1, '';2490}2491}2492if ($use_mouse && @mouse && @last_mouse &&2493$mouse[2] == $last_mouse[2] &&2494$mouse[1] == $last_mouse[1] &&2495($mouse[0] == 3 || $mouse[0] == 64 || $mouse[0] == 65)) {2496if ($mouse[0] == 64) {2497$win = 'up';2498}2499elsif ($mouse[0] == 65) {2500$win = 'down';2501}2502elsif (exists $mouse_coords{$mouse[2] - 1}{$mouse[1] - 1}) {2503$win = $mouse_coords{$mouse[2] - 1}{$mouse[1] - 1};2504}2505elsif ($mouse[2] == 1 && $mouse[1] <= abs $vars{block}) {2506$win = $last_mouse[0] != 0 ? 'last' : 'active';2507}2508else {2509}2510}2511if (defined $win) {2512$win =~ s/^_//;2513safe_print_sock("$win\n");2514if (!exists $ENV{AWL_AUTOFOCUS} || $ENV{AWL_AUTOFOCUS}) {2515if (_match_tmux()) {2516safe_qx("tmux selectp -t $vars{irssienv}{tmux_pane} 2>&1");2517}2518elsif (exists $vars{irssienv}{xwinid}) {2519safe_qx("wmctrl -ia $vars{irssienv}{xwinid} 2>/dev/null");2520}2521}2522}2523Encode::_utf8_off($keybuf);2524}25252526sub check_integration {2527return unless $vars{irssienv};2528return unless $sock && exists $vars{seplen} && exists $vars{block};2529if ($one_shot_integration == 1) {2530my $nrows = $screenHeight - $vars{ha};2531my $ncols = ($vars{seplen} + abs $vars{block}) ? int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ) : 0;2532my $items = ($show_title_bar ? 1 : 0) + @{$vars{win}//[]};2533my $dcols_required = $nrows ? int($items/$nrows) + !!($items%$nrows) : 0;2534my $rows_required = $ncols ? int($items/$ncols) + !!($items%$ncols) : 0;2535$rows_required = abs $vars{ml}2536if ($vars{ml} < 0 || ($vars{ml} > 0 && $rows_required > $vars{ml}));2537$dcols_required = abs $vars{mc}2538if ($vars{mc} < 0 || ($vars{mc} > 0 && $dcols_required > $vars{mc}));2539my $rows = $rows_required + $vars{ha};2540my $cols = ($dcols_required * ($vars{seplen} + abs $vars{block})) - $vars{seplen};2541if (_match_tmux()) {2542# int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) );2543my ($pos_flag, $before);2544if ($integration_position eq 'left') {2545$pos_flag = 'h';2546$before = 1;2547}2548elsif ($integration_position eq 'top') {2549$pos_flag = 'v';2550$before = 1;2551}2552elsif ($integration_position eq 'right') {2553$pos_flag = 'h';2554}2555else {2556$pos_flag = 'v';2557}2558my @cmd = "joinp -d$pos_flag -s $ENV{TMUX_PANE} -t $vars{irssienv}{tmux_pane}";2559push @cmd, "swapp -d -t $ENV{TMUX_PANE} -s $vars{irssienv}{tmux_pane}"2560if $before;2561$cols = max($cols, 2);2562$rows = max($rows, 2);25632564safe_qx("tmux " . (join " \\\; ", @cmd) . " 2>&1");2565}2566else {2567$resized = 1;2568#safe_qx("resize -s $screenHeight $cols 2>&1")2569# if $cols > 0;2570}2571$one_shot_integration++;2572if ($resized == 1) {2573handle_resize();2574resize_integration();2575}2576}2577elsif ($one_shot_integration == 2) {2578resize_integration(1);2579}2580}25812582sub resize_integration {2583return unless $one_shot_integration;2584return unless ($one_shot_resize//0) < 0 || shift;2585return if ($one_shot_resize//0) > 0;25862587my $nrows = $screenHeight - $vars{ha};2588my $ncols = ($vars{seplen} + abs $vars{block}) ? int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ) : 0;2589my $items = ($show_title_bar ? 1 : 0) + @{$vars{win}//[]};2590my $dcols_required = $nrows ? (int($items/$nrows) + !!($items%$nrows)) : 0;2591my $rows_required = $ncols ? int($items/$ncols) + !!($items%$ncols) : 0;2592$rows_required = abs $vars{ml}2593if ($vars{ml} < 0 || ($vars{ml} > 0 && $rows_required > $vars{ml}));2594$dcols_required = abs $vars{mc}2595if ($vars{mc} < 0 || ($vars{mc} > 0 && $dcols_required > $vars{mc}));2596my $rows = $rows_required + $vars{ha};2597my $cols = ($dcols_required * ($vars{seplen} + abs $vars{block})) - $vars{seplen};2598if (_match_tmux()) {2599my $pos_flag;2600my $before = 0;2601if ($integration_position eq 'left') {2602$pos_flag = 'h';2603$before = 1;2604}2605elsif ($integration_position eq 'top') {2606$pos_flag = 'v';2607$before = 1;2608}2609elsif ($integration_position eq 'right') {2610$pos_flag = 'h';2611}2612else {2613$pos_flag = 'v';2614}2615my @cmd;2616# hard tmux limits2617$cols = max($cols, 2);2618$rows = max($rows, 2);2619if ($pos_flag eq 'h' && $cols != $screenWidth) {2620my $change = $screenWidth - $cols;2621my $dir = ($before ^ ($change<0)) ? 'L' : 'R';2622push @cmd, "resizep -$dir -t $ENV{TMUX_PANE} @{[abs $change]}";2623#push @cmd, "resizep -x $cols -t $ENV{TMUX_PANE}";2624$one_shot_resize = 1;2625}2626if ($pos_flag eq 'v' && $rows != $screenHeight) {2627#push @cmd, "resizep -y $rows -t $ENV{TMUX_PANE}";2628my $change = $screenHeight - $rows;2629my $dir = ($before ^ ($change<0)) ? 'U' : 'D';2630push @cmd, "resizep -$dir -t $ENV{TMUX_PANE} @{[abs $change]}";2631$one_shot_resize = 1;2632}26332634safe_qx("tmux " . (join " \\\; ", @cmd) . " 2>&1")2635if @cmd;2636}2637else {2638$cols = max($cols, 1);2639$rows = max($rows, 1);2640unless ($nrows > $ncols) { # line layout2641if ($rows != $screenHeight) {2642safe_qx("resize -s $rows $screenWidth 2>&1");2643$one_shot_resize = 1;2644}2645}2646else {2647if ($cols != $screenWidth) {2648safe_qx("resize -s $screenHeight $cols 2>&1");2649$one_shot_resize = 1;2650}2651}2652}2653if ($resized == 1) {2654handle_resize();2655}2656}26572658sub init_integration {2659return unless $one_shot_integration;2660if (_match_tmux()) {2661}2662else {2663}2664safe_print("\e]2;".(uc ::setc())."\e\\");2665}26662667sub main {2668require Getopt::Std;2669my %opts;2670Getopt::Std::getopts('1p:', \%opts);2671my $one_shot = $opts{1};2672$integration_position = $opts{p};2673$one_shot_integration = 0+!!$one_shot;2674#shift if @_ && $_[0] eq '--';2675&init;2676$show_title_bar = 0 if $ENV{AWL_NOTITLE};2677init_integration();2678until ($got_int) {2679$timeout = undef;2680if ($resized) {2681if ($resized == 1) {2682$timeout = 1;2683$resized++;2684}2685else {2686handle_resize();2687resize_integration();2688}2689}2690unless ($sock || $timeout) {2691connect_it();2692}2693$timeout ||= RECONNECT_TIME unless $sock;2694update_screen() if $disp_update;2695SELECT: while (my @read = $loop->can_read($timeout)) {2696for my $fh (@read) {2697if ($fh == \*STDIN) {2698if (read STDIN, my $buf, BLOCK_SIZE) {2699do {2700$keybuf .= $buf;2701} while read STDIN, $buf, BLOCK_SIZE;2702}2703else {2704$got_int = 1;2705last SELECT;2706}2707}2708else {2709if ($fh->read(my $buf, BLOCK_SIZE)) {2710do {2711$rcvbuf .= $buf;2712} while $fh->read($buf, BLOCK_SIZE);2713}2714else {2715$disp_update = 1;2716remove_conn($fh);2717if ($one_shot) {2718$got_int = 1;2719last SELECT;2720}2721$timeout ||= RECONNECT_TIME;2722}2723}2724}2725$disp_update |= process_recv() if length $rcvbuf;2726process_keys() if length $keybuf;2727check_integration() if $one_shot;2728update_screen() if $disp_update;2729}2730continue {2731}2732}2733end_prog();2734}2735}273627371;27382739# Changelog2740# =========2741# 1.82742# - use string_width in Irssi 1.2.02743#2744# 1.72745# - fix crash on invalid /set awl_sort, introduced in 1.6, reported by2746# tpetazzoni2747# - delay viewer initialisation2748# - improve race condition on tmux resize integration2749#2750# 1.62751# - add detach setting to hide windows2752# - fix race condition when loading the script, reported by madduck2753# - improve compatibility with irssi 1.22754# - add special value lru to awl_sort to sort windows by usage2755#2756# 1.52757# - improve compat. with sideways splits2758#2759# 1.42760# - fix line wrapping in some themes, reported by justanotherbody2761# - fix named window key detection, reported by madduck2762# - make title (in viewer and shared_sbar) configurable2763#2764# 1.32765# - workaround for irssi issue #5722766#2767# 1.22768# - new format to choose abbreviation character2769#2770# 1.12771# - infinite loop on shortening certain window names reported by Kalan2772#2773# 1.02774# - new awl_viewer_launch setting and an array of related settings2775# - fixed regression bug /exec -interactive2776# - fixed some warnings in perl 5.10 reported by kl32777# - workaround for crash due to infinite recursion in irssi's Perl2778# error handling2779#2780# 0.92781# - fix endless loop in awin detection code!2782# - correct colour swap in awl_viewer2783# - fix passing of alternate socket path to the viewer2784# - potential undefinedness in mouse refnum hinted at by Canopus2785# - fixed regression bug /exec -interactive2786# - add case-insensitive modifier to awl_sort2787# - run custom_xform on awl_prefer_name also2788# - avoid inconsistent active window state after awin detection2789# reported by ss2790# - revert %9-hack in the viewer prompted by discussion with pierrot2791# - fix new warning in perl 5.222792#2793# 0.82794# - replace fifo mode with external viewer script2795# - remove bundled cpan modules2796# - work around bogus irssi warning2797# - improve mouse support2798# - workaround for broken cumode in irssi 0.8.152799# - fix handling of non-meta windows (uninitialized warning)2800# - add 256 colour support, strip true colour codes2801# - fix totally bogus $N padding reported by Ed S.2802# - make /window goto #name mappings work but ignore non-existant ones2803# - improve incomplete reads reported by bcode2804# - fix single % in awl_viewer reported by bcode2805# - add support for key bindings by nike and ferret2806# - coerce utf8 key binds2807# - add settings: custom_xform, last_line_shade, hide_name_data2808# - abbreviations were broken in some cases2809# - fix some misuse of / as cmdchar in mouse script reported by bcode2810# - add shared status bar mode2811# - ${type} variables for custom_xform setting2812# - crash if custom_xform had runtime error2813# - update sorting documentation2814# - fix odd case in size calculation noted by lasers2815# - add missing font styles to the viewer reported by ishanyx2816# - add italic2817#2818# 0.7g2819# - remove screen support and replace it with fifo support2820# - add double-width support to the shortener2821# - correct documentation regarding $T vs. display_header2822# - add missing refresh for window item changed (thanks vague)2823# - add visible windows2824# - add exemptions for active window2825# - workaround for hiding the window changes from trackbar2826# - hack to force 16colours in screen mode2827# - remember last window (reported by earthnative)2828# - wrong window focus on new queries (reported by emsid)2829# - dataloss bug on trying to remember last window2830#2831# 0.6d+2832# - add support for network headers2833# - fixed regression bug /exec -interactive2834#2835# 0.6ca+2836# - add screen support (from nicklist.pl)2837# - names can now have a max length and window names can be used2838# - fixed a bug with block display in screen mode and status bar mode2839# - added space handling to ir_fe and removed it again2840# - now handling formats on my own2841# - started to work on $tag display2842# - added warning about missing sb_act_none abstract leading to2843# - display*active settings2844# - added warning about the bug in awl_display_(no)key_active settings2845# - mouse hack2846#2847# 0.5d2848# - add setting to also hide the last status bar if empty (awl_all_disable)2849# - reverted to old utf8 code to also calculate broken utf8 length correctly2850# - simplified dealing with status bars in wlreset2851# - added a little tweak for the renamed term_type somewhere after Irssi 0.8.92852# - fixed bug in handling channel #$$2853# - reset background colour at the beginning of an entry2854#2855# 0.4d2856# - fixed order of disabling status bars2857# - several attempts at special chars, without any real success2858# and much more weird new bugs caused by this2859# - setting to specify sort order2860# - reduced timeout values2861# - added awl_hide_data2862# - make it so the dynamic sub is actually deleted2863# - fix a bug with removing of the last separator2864# - take into consideration parse_special2865#2866# 0.3b2867# - automatically kill old status bars2868# - reset on /reload2869# - position/placement settings2870#2871# 0.22872# - automated retrieval of key bindings (thanks grep.pl authors)2873# - improved removing of status bars2874# - got rid of status chop2875#2876# 0.12877# - Based on chanact.pl which was apparently based on lightbar.c and2878# nicklist.pl with various other ideas from random scripts.287928802881