changeset 148:4e92ca6c779a

add irssi conf
author zegervdv <zegervdv@me.com>
date Sat, 18 Oct 2014 10:06:58 +0200
parents 0d420021bd5d
children 45c9af0c9017
files irssi/config irssi/pbrisbin.theme irssi/scripts/adv_windowlist.pl irssi/scripts/autorun/adv_windowlist.pl irssi/scripts/autorun/cap_sasl.pl irssi/scripts/autorun/hilightwin.pl irssi/scripts/autorun/nicklist.pl irssi/scripts/autorun/nm.pl irssi/scripts/autorun/queryresume.pl irssi/scripts/autorun/trackbar.pl irssi/scripts/bitlbee_typing_notice.pl irssi/scripts/cap_sasl.pl irssi/scripts/hilightwin.pl irssi/scripts/nicklist.pl irssi/scripts/nm.pl irssi/scripts/queryresume.pl irssi/scripts/trackbar.pl
diffstat 17 files changed, 5072 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/config	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,208 @@
+# vim:ft=config
+
+servers = (
+  {
+    address = "irc.freenode.net";
+    chatnet = "freenode";
+    port = "6697";
+    use_ssl = "yes";
+    ssl_verify = "yes";
+    ssl_capath = "/etc/ssl/certs/";
+    autoconnect = "yes";
+  },
+
+  { address = "localhost"; chatnet = "bitlbee"; autoconnect = "yes"; }
+);
+
+chatnets = {
+  freenode = { type = "IRC"; nick = "zegervdv"; };
+  bitlbee = { type = "IRC"; };
+};
+
+channels = (
+  { name = "#vim"; chatnet = "freenode"; autojoin = "yes"; },
+  { name = "#raspberrypi"; chatnet = "freenode"; autojoin = "yes"; }
+);
+
+aliases = {
+  J = "join";
+  LEAVE = "part";
+  E = "exec - runz";
+  EO = "exec - -o runz";
+  EXIT = "quit";
+  LL = "lastlog";
+  W = "window";
+  M = "mark";
+  WC = "window close";
+  WK = "window kill";
+  WN = "window new hide";
+  WA = "window goto active";
+  RUN = "SCRIPT LOAD";
+  Q = "QUERY";
+};
+
+statusbar = {
+  items = {
+    time = "{sb $Z}       ";
+    window = ":: {sb [$winref] $tag $itemname} ";
+    window_empty = ":: {sb [$winref] $tag $itemname} ";
+
+    prompt = "{prompt}";
+    prompt_empty = "{prompt}";
+  };
+
+  default = {
+    window = {
+      type = "window";
+      placement = "top";
+      visible = "active";
+      items = { window = { }; window_empty = { }; typing_notice = { }; };
+    };
+
+    window_inact = {
+      type = "window";
+      placement = "top";
+      visible = "inactive";
+      items = { window = { }; window_empty = { }; };
+    };
+
+    prompt = {
+      type = "root";
+      placement = "bottom";
+      position = "100";
+      visible = "always";
+
+      items = {
+        time = { };
+        user = { };
+        prompt = { priority = "-1"; };
+        prompt_empty = { priority = "-1"; };
+
+        input = { priority = "10"; };
+      };
+    };
+
+    topic = {
+      type = "root";
+      placement = "bottom";
+      position = "1";
+      visible = "always";
+      items = { topic = { }; topic_empty = { }; };
+    };
+
+    awl_0 = {
+      items = {
+        barstart = { priority = "100"; };
+        awl_0 = { };
+        barend = { priority = "100"; alignment = "right"; };
+      };
+    };
+  };
+};
+
+settings = {
+  core = {
+    real_name = "Zeger Van de Vannet";
+    user_name = "zegervdv";
+    nick = "zegervdv";
+    awaylog_file = "/dev/null";
+    recode_autodetect_utf8 = "yes";
+    recode_fallback = "CP1252";
+  };
+
+  "fe-text" = { actlist_sort = "refnum"; };
+
+  "fe-common/core" = {
+    emphasis = "OFF";
+    theme = "pbrisbin";
+    beep_msg_level = "NOTICE MSGS HILIGHT";
+    bell_beeps = "no";
+    hilight_nick_matches = "no";
+    autolog_path = "~/.irssi/logs/$tag/$0.log";
+    autolog = "yes";
+  };
+
+  "perl/core/scripts" = {
+    neat_maxlength = "13";
+    awl_display_key = "%w$N.$H$C$S ";
+    awl_position = "0";
+    awl_sbar_maxlength = "no";
+    awl_maxlines = "3";
+    screen_away_message = "away";
+    hilightwin_showprivmsg = "no";
+    bitlbee_send_typing = "yes";
+    lt_in_queries = "yes";
+    neat_left_actions = "no";
+    awl_shared_sbar = "OFF";
+  };
+};
+
+ignores = (
+  { level = "JOINS PARTS QUITS NICKS"; },
+  { level = "MODES"; channels = ( "&bitlbee", "&facebook" ); }
+);
+
+hilights = (
+  { text = "zegervdv"; nick = "no"; word = "yes"; fullword = "yes"; },
+  { text = "@zegervdv"; nick = "no"; word = "yes"; fullword = "yes"; }
+);
+
+logs = { };
+windows = {
+  1 = { immortal = "yes"; name = "(status)"; level = "ALL"; };
+  2 = { name = "hilight"; sticky = "yes"; };
+  3 = {
+    items = (
+      {
+        type = "CHANNEL";
+        chat_type = "IRC";
+        name = "&bitlbee";
+        tag = "bitlbee";
+      }
+    );
+  };
+  4 = {
+    items = (
+      {
+        type = "CHANNEL";
+        chat_type = "IRC";
+        name = "#twitter_zegervdv";
+        tag = "bitlbee";
+      }
+    );
+  };
+  5 = {
+    items = (
+      {
+        type = "CHANNEL";
+        chat_type = "IRC";
+        name = "&facebook";
+        tag = "bitlbee";
+      }
+    );
+  };
+  6 = {
+    items = (
+      {
+        type = "CHANNEL";
+        chat_type = "IRC";
+        name = "#vim";
+        tag = "freenode";
+      }
+    );
+  };
+  7 = {
+    items = (
+      {
+        type = "CHANNEL";
+        chat_type = "IRC";
+        name = "#raspberrypi";
+        tag = "freenode";
+      }
+    );
+  };
+};
+mainwindows = {
+  1 = { first_line = "11"; lines = "44"; };
+  2 = { first_line = "1"; lines = "10"; };
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/pbrisbin.theme	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,174 @@
+##
+## pbrisbin's irssi theme.
+##
+
+default_color = "-1";
+info_eol = "false";
+replaces = { "[]=" = "%K$*%n"; };
+
+# Only overrides are shown, defaults are commented.
+abstracts = {
+  ##
+  ## Generic
+  ##
+  line_start = "%c::%n ";
+  timestamp = "%K$0-%n";
+  hilight = "%W$*%W";
+
+  #error = "%R$*%n";
+  #channel = "%_$*%_";
+  #nick = "%_$*%_";
+  #nickhost = "[$*]";
+  #server = "%_$*%_";
+  #comment = "[$*]";
+  #reason = "{comment $*}";
+  #mode = "{comment $*}";
+
+  # channel specific messages
+  #channick_hilight = "%C$*%n";
+  #chanhost_hilight = "{nickhost %c$*%n}";
+  #channick = "%c$*%n";
+  #chanhost = "{nickhost $*}";
+  #channelhilight = "%c$*%n";
+  #ban = "%c$*%n";
+
+  ##
+  ## Messages
+  ##
+  msgnick = "$0$1-%c:%n %|";
+
+  #ownmsgnick = "{msgnick $0 $1-}";
+  #ownnick = "%W$*%n";
+  #pubmsgnick = "{msgnick $0 $1-}";
+  #pubnick = "%N$*%n";
+  #pubmsgmenick = "{msgnick $0 $1-}";
+  #menick = "%Y$*%n";
+  #pubmsghinick = "{msgnick $1 $0$2-%n}";
+  #msgchannel = "%K:%c$*%n";
+  #privmsg = "[%R$0%K(%r$1-%K)%n] ";
+  #ownprivmsg = "[%r$0%K(%R$1-%K)%n] ";
+  #ownprivmsgnick = "{msgnick  $*}";
+  #ownprivnick = "%W$*%n";
+  #privmsgnick = "{msgnick  %R$*%n}";
+
+  ##
+  ## Action
+  ##
+  action_core = "%M$*";
+  action = "{action_core $*}";
+  pvtaction = "{action_core $*}";
+  ownaction_target = "{action_core $*}";
+
+  #ownaction = "{action $*}";
+  pubaction = "{action $*} ";
+  # fix neatlength inconsistency, sigh
+  #pvtaction_query = "{action $*}";
+
+  ##
+  ## whois
+  ##
+  #whois = "%# $[8]0 : $1-";
+
+  ##
+  ## Notices
+  ##
+  #ownnotice = "[%r$0%K(%R$1-%K)]%n ";
+  #notice = "%K-%M$*%K-%n ";
+  #pubnotice_channel = "%K:%m$*";
+  #pvtnotice_host = "%K(%m$*%K)";
+  #servernotice = "%g!$*%n ";
+
+  ##
+  ## CTCP
+  ##
+  #ownctcp = "[%r$0%K(%R$1-%K)] ";
+  #ctcp = "%g$*%n";
+
+  ##
+  ## Wallops
+  ##
+  #wallop = "%W$*%n: ";
+  #wallop_nick = "%n$*";
+  #wallop_action = "%W * $*%n ";
+
+  ##
+  ## Netsplits
+  ##
+  #netsplit = "%R$*%n";
+  #netjoin = "%C$*%n";
+
+  ##
+  ## Names
+  ##
+  #names_prefix = "";
+  #names_nick = "[%_$0%_$1-] ";
+  #names_nick_op = "{names_nick $*}";
+  #names_nick_halfop = "{names_nick $*}";
+  #names_nick_voice = "{names_nick $*}";
+  #names_users = "[%g$*%n]";
+  #names_channel = "%G$*%n";
+
+  ##
+  ## DCC
+  ##
+  #dcc = "%g$*%n";
+  #dccfile = "%_$*%_";
+  #dccownmsg = "[%r$0%K($1-%K)%n] ";
+  #dccownnick = "%R$*%n";
+  #dccownquerynick = "%W$*%n";
+  #dccownaction = "{action $*}";
+  #dccownaction_target = "{action_core $0}%K:%c$1%n ";
+  #dccmsg = "[%G$1-%K(%g$0%K)%n] ";
+  #dccquerynick = "%G$*%n";
+  #dccaction = "%W (*dcc*) $*%n %|";
+
+  ##
+  ## Statusbar
+  ##
+  sb_background = "%n%0";
+  sbstart = " ";
+  sbend = " ";
+  prompt = "   %c:%n ";
+  sb = "%W$*";
+  sbmode = "";
+  sbaway = " (%Gaway%n)";
+
+  #sb_prompt_bg = "%n";
+  #sb_info_bg = "%8";
+  #sbstart = "";
+  #sbend = " ";
+  #topicsbstart = "{sbstart $*}";
+  #topicsbend = "{sbend $*}";
+  #prompt = "[$*] ";
+  #sb = " %c[%n$*%c]%n";
+  #sbmode = "(%c+%n$*)";
+  #sbaway = " (%GzZzZ%n)";
+  #sbservertag = ":$0 (change with ^X)";
+  #sbnickmode = "$0";
+  #sb_act_sep = "%c$*";
+  #sb_act_text = "%c$*";
+  #sb_act_msg = "%W$*";
+  #sb_act_hilight = "%M$*";
+  #sb_act_hilight_color = "$0$1-%n";
+};
+
+formats = {
+  "fe-common/core" = {
+    own_msg = "{ownmsgnick {ownnick $[-13]0$2}}$1";
+    own_msg_channel = "{ownmsgnick {ownnick $[-13]0$3}{msgchannel $1}}$2";
+    own_msg_private_query = "{ownprivmsgnick {ownprivnick $[-13]2}}$1";
+    pubmsg_me = "{pubmsgmenick {menick $[-13]0}$2}$1";
+    pubmsg_me_channel = "{pubmsgmenick {menick $[-13]0$3}{msgchannel $1}}$2";
+    pubmsg_hilight = "{pubmsghinick $0 $0 $[-13]1$3%n}$2";
+    pubmsg_hilight_channel = "{pubmsghinick $0 $[-13]1$4{msgchannel $2}}$3";
+    pubmsg = "{pubmsgnick {pubnick $[-13]0$2}}$1";
+    pubmsg_channel = "{pubmsgnick {pubnick $[-13]0$2}}$1";
+    msg_private_query = "{privmsgnick $[-13]0}$2";
+  };
+  "fe-common/irc" = {
+    own_action = "{ownaction $[-11]0} $1";
+    action_private = "{pvtaction $[-11]0}$1";
+    action_private_query = "{pvtaction_query $[-11]0} $2";
+    action_public = "{pubaction $[-11]0}$1";
+  };
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/scripts/adv_windowlist.pl	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,2478 @@
+use strict; # use warnings;
+
+# {{{ debug
+
+#BEGIN {
+#	open STDERR, '>', '/home/ailin/wlstatwarnings';
+#};
+
+# FIXME COULD SOMEONE PLEASE TELL ME HOW TO SHUT UP
+#
+# ...
+# Variable "*" will not stay shared at (eval *) line *.
+# Variable "*" will not stay shared at (eval *) line *.
+# ...
+# Can't locate package Irssi::Nick for @Irssi::Irc::Nick::ISA at (eval *) line *.
+# ...
+#
+# THANKS
+
+# }}}
+
+# if you don't know how to operate folds, type zn
+
+# {{{ header
+
+use Irssi (); # which is the minimum required version of Irssi ?
+use Irssi::TextUI;
+
+use vars qw($VERSION %IRSSI);
+
+$VERSION = '0.6ca';
+%IRSSI = (
+	original_authors => q(BC-bd,        Veli,          Timo Sirainen, ).
+	                    q(Wouter Coekaerts,    Jean-Yves Lefort), # (decadix)
+	original_contact => q([email protected], [email protected], [email protected], ).
+	                    q([email protected], [email protected]),
+	authors          => q(Nei),
+	contact          => q(Nei @ [email protected]),
+	url              =>  "http://anti.teamidiot.de/",
+	name             => q(awl),
+	description      => q(Adds a permanent advanced window list on the right or ).
+	                    q(in a statusbar.),
+	description2     => q(Based on chanact.pl which was apparently based on ).
+	                    q(lightbar.c and nicklist.pl with various other ideas ).
+	                    q(from random scripts.),
+	license          => q(GNU GPLv2 or later),
+);
+
+# }}}
+
+# {{{ *** D O C U M E N T A T I O N ***
+
+# adapted by Nei
+
+###############
+# {{{ original comment
+# ###########
+# # Adds new powerful and customizable [Act: ...] item (chanelnames,modes,alias).
+# # Lets you give alias characters to windows so that you can select those with
+# # meta-<char>.
+# #
+# # for irssi 0.8.2 by [email protected]
+# #
+# # inspired by chanlist.pl by '[email protected]'
+# #
+# #########
+# # {{{ Contributors
+# #########
+# #
+# # [email protected]   /window_alias code
+# # [email protected]  chanact_abbreviate_names
+# # [email protected]      Extra chanact_show_mode and chanact_chop_status
+# # }}}
+# }}}
+# 
+# {{{ FURTHER THANKS TO
+# ############
+# # buu, fxn, Somni, Khisanth, integral, tybalt89   for much support in any aspect perl
+# # and the channel in general ( #perl @ freenode ) and especially the ir_* functions
+# #
+# # Valentin 'senneth' Batz ( [email protected] ) for the pointer to grep.pl, continuous support
+# #                                         and help in digging up ir_strip_codes
+# #
+# # OnetrixNET technology networks for the debian environment
+# #
+# # Monkey-Pirate.com / Spaceman Spiff for the webspace
+# #
+# }}}
+
+######
+# {{{ M A I N    P R O B L E M
+#####
+#
+# It is impossible to place the awl on a statusbar together with other items,
+# because I do not know how to calculate the size that it is going to get
+# granted, and therefore I cannot do the linebreaks properly.
+# This is what is missing to make a nice script out of awl.
+# If you have any ideas, please contact me ASAP :).
+# }}}
+######
+
+######
+# {{{ UTF-8 PROBLEM
+#####
+#
+# Please help me find a solution to this:
+# this be your statusbar, it is using up the maximum term size
+# [[1=1]#abc [2=2]#defghi]
+# 
+# now consider this example:i
+# "ascii" characters are marked with ., utf-8 characters with *
+# [[1=1]#... [2=2]#...***]
+#
+# you should think that this is how it would be displayed? WRONG!
+# [[1=1]#... [2=2]#...***   ]
+#
+# this is what Irssi does.. I believe my length calculating code to be correct,
+# however, I'd love to be proven wrong (or receive any other fix, too, of
+# course!)
+# }}}
+######
+
+#########
+# {{{ USAGE
+###
+#
+# copy the script to ~/.irssi/scripts/
+#
+# In irssi:
+#
+#		/script load awl
+#
+#
+# Hint: to get rid of the old [Act:] display
+#     /statusbar window remove act
+#
+# to get it back:
+#     /statusbar window add -after lag -priority 10 act
+# }}}
+##########
+# {{{ OPTIONS
+########
+#
+# {{{ /set awl_display_nokey <string>
+#     /set awl_display_key <string>
+#     /set awl_display_nokey_active <string>
+#     /set awl_display_key_active <string>
+#     * string : Format String for one window. The following $'s are expanded:
+#         $C : Name
+#         $N : Number of the Window
+#         $Q : meta-Keymap
+#         $H : Start highlighting
+#         $S : Stop highlighting
+#             /+++++++++++++++++++++++++++++++++,
+#            | ****  I M P O R T A N T :  ****  |
+#            |                                  |
+#            | don't forget  to use  $S  if you |
+#            | used $H before!                  |
+#            |                                  |
+#            '+++++++++++++++++++++++++++++++++/
+#       XXX NOTE ON *_active: there is a BUG somewhere in the length
+#       XXX calculation. currently it's best to NOT remove $H/$S from those
+#       XXX settings if you use it in the non-active settings.
+# }}}
+# {{{ /set awl_separator <string>
+#     * string : Charater to use between the channel entries
+#     you'll need to escape " " space and "$" like this:
+#     "/set awl_separator \ "
+#     "/set awl_separator \$"
+#     and {}% like this:
+#     "/set awl_separator %{"
+#     "/set awl_separator %}"
+#     "/set awl_separator %%"
+#     (reason being, that the separator is used inside a {format })
+# }}}
+# {{{ /set awl_prefer_name <ON|OFF>
+#     * this setting decides whether awl will use the active_name (OFF) or the
+#       window name as the name/caption in awl_display_*.
+#       That way you can rename windows using /window name myownname.
+# }}}
+# {{{ /set awl_hide_data <num>
+#     * num : hide the window if its data_level is below num
+#     set it to 0 to basically disable this feature,
+#               1 if you don't want windows without activity to be shown
+#               2 to show only those windows with channel text or hilight
+#               3 to show only windows with hilight
+# }}}
+# {{{ /set awl_maxlines <num>
+#     * num : number of lines to use for the window list (0 to disable, negative
+#       lock)
+# }}}
+# {{{ /set awl_columns <num>
+#     * num : number of columns to use in screen mode (0 for unlimited)
+# }}}
+# {{{ /set awl_block <num>
+#     * num : width of a column in screen mode (negative values = block display)
+#             /+++++++++++++++++++++++++++++++++,
+#            | ******  W A R N I N G !  ******  |
+#            |                                  |
+#            | If  your  block  display  looks  |
+#            | DISTORTED,  you need to add the  |
+#            | following  line to your  .theme  |
+#            | file under                       |
+#            |     abstracts = {             :  |
+#            |                                  |
+#            |       sb_act_none = "%n$*";      |
+#            |                                  |
+#            '+++++++++++++++++++++++++++++++++/
+#.02:08:26. < shi>  Irssi::current_theme()->get_format <.. can this be used?
+# }}}
+# {{{ /set awl_sbar_maxlength <ON|OFF>
+#     * if you enable the maxlength setting, the block width will be used as a
+#       maximum length for the non-block statusbar mode too.
+# }}}
+# {{{ /set awl_height_adjust <num>
+#     * num : how many lines to leave empty in screen mode
+# }}}
+# {{{ /set awl_sort <-data_level|-last_line|refnum>
+#     * you can change the window sort order with this variable
+#         -data_level : sort windows with hilight first
+#         -last_line  : sort windows in order of activity
+#         refnum      : sort windows by window number
+# }}}
+# {{{ /set awl_placement <top|bottom>
+#     /set awl_position <num>
+#     * these settings correspond to /statusbar because awl will create
+#       statusbars for you
+#     (see /help statusbar to learn more)
+# }}}
+# {{{ /set awl_all_disable <ON|OFF>
+#     * if you set awl_all_disable to ON, awl will also remove the
+#       last statusbar it created if it is empty.
+#       As you might guess, this only makes sense with awl_hide_data > 0 ;)
+# }}}
+# {{{ /set awl_automode <sbar|screen|emulate_lightbar>
+#     * this setting defines whether the window list is shown in statusbars or
+#       whether the screen hack is used (from nicklist.pl)
+# }}}
+# }}}
+##########
+# {{{ COMMANDS
+########
+# {{{ /awl paste <ON|OFF|TOGGLE>
+#     * enables or disables the screen hack windowlist. This is useful when you
+#       want to mark & copy text that you want to paste somewhere (hence the
+#       name). (ON means AWL disabled!)
+#       This is nicely bound to a function key for example.
+# }}}
+# {{{ /awl redraw
+#     * redraws the screen hack windowlist. There are many occasions where the
+#       screen hack windowlist can get destroyed so you can use this command to
+#       fix it.
+# }}}
+# }}}
+###
+# {{{ WISHES
+####
+#
+# if you fiddle with my mess, provide me with your fixes so I can benefit as well
+#
+# Nei =^.^= ( [email protected] )
+# }}}
+
+# }}}
+
+# {{{ modules
+
+#use Class::Classless;
+#use Term::Info;
+
+# }}}
+
+# {{{ global variables
+
+my $replaces = '[=]'; # AARGH!!! (chars that are always surrounded by weird
+                      # colour codes by Irssi)
+
+my $actString = [];   # statusbar texts
+my $currentLines = 0;
+my $resetNeeded;      # layout/screen has changed, redo everything
+my $needRemake;       # "normal" changes
+#my $callcount = 0;
+sub GLOB_QUEUE_TIMER () { 100 }
+my $globTime = undef; # timer to limit remake() calls
+
+
+my $SCREEN_MODE;
+my $DISABLE_SCREEN_TEMP;
+my $currentColumns = 0;
+my $screenResizing;
+my ($screenHeight, $screenWidth);
+my $screenansi = bless {
+	NAME => 'Screen::ANSI',
+	PARENTS => [],
+	METHODS => {
+		dcs   => sub { "\033P" },
+		st    => sub { "\033\\"},
+	}
+}, 'Class::Classless::X';
+#my $terminfo = new Term::Info 'xterm'; # xterm here, make this modular
+# {{{{{{{{{{{{{{{
+my $terminfo = bless { # xterm here, make this modular
+	NAME => 'Term::Info::xterm',
+	PARENTS => [],
+	METHODS => {
+ # 	civis=\E[?25l,
+		civis => sub { "\033[?25l" },
+ # 	sc=\E7,
+		sc    => sub { "\0337" },
+ # 	cup=\E[%i%p1%d;%p2%dH,
+		cup   => sub { shift;shift; "\033[" . ($_[0] + 1) . ';' . ($_[1] + 1) . 'H' },
+ # 	el=\E[K,
+		el    => sub { "\033[K" },
+ # 	rc=\E8,
+		rc    => sub { "\0338" },
+ # 	cnorm=\E[?25h,
+		cnorm => sub { "\033[?25h" },
+ # 	setab=\E[4%p1%dm,
+		setab => sub { shift;shift; "\033[4" . $_[0] . 'm' },
+ # 	setaf=\E[3%p1%dm,
+		setaf => sub { shift;shift; "\033[3" . $_[0] . 'm' },
+ # 	bold=\E[1m,
+		bold  => sub { "\033[1m" },
+ # 	blink=\E[5m,
+		blink => sub { "\033[5m" },
+ # 	rev=\E[7m,
+		rev   => sub { "\033[7m" },
+ # 	op=\E[39;49m,
+		op    => sub { "\033[39;49m" },
+	}
+}, 'Class::Classless::X';
+# }}}}}}}}}}}}}}}
+
+
+sub setc () {
+	$IRSSI{'name'}
+}
+sub set ($) {
+	setc . '_' . shift
+}
+
+# }}}
+
+
+# {{{ sbar mode
+
+my %statusbars;       # currently active statusbars
+
+# maybe I should just tie the array ?
+sub add_statusbar {
+	for (@_) {
+		# add subs
+		for my $l ($_) { {
+			no strict 'refs'; # :P
+			*{set$l} = sub { awl($l, @_) };
+		}; }
+		Irssi::command('statusbar ' . (set$_) . ' reset');
+		Irssi::command('statusbar ' . (set$_) . ' enable');
+		if (lc Irssi::settings_get_str(set 'placement') eq 'top') {
+			Irssi::command('statusbar ' . (set$_) . ' placement top');
+		}
+		if ((my $x = int Irssi::settings_get_int(set 'position')) != 0) {
+			Irssi::command('statusbar ' . (set$_) . ' position ' . $x);
+		}
+		Irssi::command('statusbar ' . (set$_) . ' add -priority 100 -alignment left barstart');
+		Irssi::command('statusbar ' . (set$_) . ' add ' . (set$_));
+		Irssi::command('statusbar ' . (set$_) . ' add -priority 100 -alignment right barend');
+		Irssi::command('statusbar ' . (set$_) . ' disable');
+		Irssi::statusbar_item_register(set$_, '$0', set$_);
+		$statusbars{$_} = {};
+	}
+}
+
+sub remove_statusbar {
+	for (@_) {
+		Irssi::command('statusbar ' . (set$_) . ' reset');
+		Irssi::statusbar_item_unregister(set$_); # XXX does this actually work ?
+		# DO NOT REMOVE the sub before you have unregistered it :))
+		for my $l ($_) { {
+			no strict 'refs';
+			undef &{set$l};
+		}; }
+		delete $statusbars{$_};
+	}
+}
+
+sub syncLines {
+	my $temp = $currentLines;
+	$currentLines = @$actString;
+	#Irssi::print("current lines: $temp new lines: $currentLines");
+	my $currMaxLines = Irssi::settings_get_int(set 'maxlines');
+	if ($currMaxLines > 0 and @$actString > $currMaxLines) {
+		$currentLines = $currMaxLines;
+	}
+	elsif ($currMaxLines < 0) {
+		$currentLines = abs($currMaxLines);
+	}
+	return if ($temp == $currentLines);
+	if ($currentLines > $temp) {
+		for ($temp .. ($currentLines - 1)) {
+			add_statusbar($_);
+			Irssi::command('statusbar ' . (set$_) . ' enable');
+		}
+	}
+	else {
+		for ($_ = ($temp - 1); $_ >= $currentLines; $_--) {
+			Irssi::command('statusbar ' . (set$_) . ' disable');
+			remove_statusbar($_);
+		}
+	}
+}
+
+# FIXME implement $get_size_only check, and user $item->{min|max-size} ??
+sub awl {
+	my ($line, $item, $get_size_only) = @_;
+
+	if ($needRemake) {
+		$needRemake = undef;
+		remake();
+	}
+
+	my $text = $actString->[$line];  # DO NOT set the actual $actString->[$line] to '' here or
+	$text = '' unless defined $text; # you'll screw up the statusbar counter ($currentLines)
+	$item->default_handler($get_size_only, $text, '', 1);
+}
+
+# remove old statusbars
+my %killBar;
+sub get_old_status {
+	my ($textDest, $cont, $cont_stripped) = @_;
+	if ($textDest->{'level'} == 524288 and $textDest->{'target'} eq ''
+			and !defined($textDest->{'server'})
+	) {
+		my $name = quotemeta(set '');
+		if ($cont_stripped =~ m/^$name(\d+)\s/) { $killBar{$1} = {}; }
+		Irssi::signal_stop();
+	}
+}
+sub killOldStatus {
+	%killBar = ();
+	Irssi::signal_add_first('print text' => 'get_old_status');
+	Irssi::command('statusbar');
+	Irssi::signal_remove('print text' => 'get_old_status');
+	remove_statusbar(keys %killBar);
+}
+#killOldStatus();
+
+# end sbar mode }}}
+
+
+# {{{ keymaps
+
+my %keymap;
+
+sub get_keymap {
+	my ($textDest, undef, $cont_stripped) = @_;
+	if ($textDest->{'level'} == 524288 and $textDest->{'target'} eq ''
+			and !defined($textDest->{'server'})
+	) {
+		if ($cont_stripped =~ m/((?:meta-)+)(.)\s+change_window (\d+)/) {
+			my ($level, $key, $window) = ($1, $2, $3);
+			my $numlevel = ($level =~ y/-//) - 1;
+			$keymap{$window} = ('-' x $numlevel) . "$key";
+		}
+		Irssi::signal_stop();
+	}
+}
+
+sub update_keymap {
+	%keymap = ();
+	Irssi::signal_remove('command bind' => 'watch_keymap');
+	Irssi::signal_add_first('print text' => 'get_keymap');
+	Irssi::command('bind'); # stolen from grep
+	Irssi::signal_remove('print text' => 'get_keymap');
+	Irssi::signal_add('command bind' => 'watch_keymap');
+	Irssi::timeout_add_once(100, 'eventChanged', undef);
+}
+
+# watch keymap changes
+sub watch_keymap {
+	Irssi::timeout_add_once(1000, 'update_keymap', undef);
+}
+
+update_keymap();
+
+# end keymaps }}}
+
+# {{{ format handling
+
+# a bad way do do expansions but who cares
+sub expand {
+	my ($string, %format) = @_;
+	my ($exp, $repl);
+	$string =~ s/\$$exp/$repl/g while (($exp, $repl) = each(%format));
+	return $string;
+}
+
+my %strip_table = (
+	# fe-common::core::formats.c:format_expand_styles
+	#      delete                format_backs  format_fores bold_fores   other stuff
+	(map { $_ => '' } (split //, '04261537' .  'kbgcrmyw' . 'KBGCRMYW' . 'U9_8:|FnN>#[')),
+	#      escape
+	(map { $_ => $_ } (split //, '{}%')),
+);
+sub ir_strip_codes { # strip %codes
+	my $o = shift;
+	$o =~ s/(%(.))/exists $strip_table{$2} ? $strip_table{$2} : $1/gex;
+	$o
+}
+
+sub ir_parse_special {
+	my $o; my $i = shift;
+	#if ($_[0]) { # for the future?!?
+	#	eval {
+	#		$o = $_[0]->parse_special($i);
+	#	};
+	#	unless ($@) {
+	#		return $o;
+	#	}
+	#}
+	my $win = shift || Irssi::active_win();
+	my $server = Irssi::active_server();
+	if (ref $win and ref $win->{'active'}) {
+		$o = $win->{'active'}->parse_special($i);
+	}
+	elsif (ref $win and ref $win->{'active_server'}) {
+		$o = $win->{'active_server'}->parse_special($i);
+	}
+	elsif (ref $server) {
+		$o =  $server->parse_special($i);
+	}
+	else {
+		$o = Irssi::parse_special($i);
+	}
+	$o
+}
+sub ir_parse_special_protected {
+	my $o; my $i = shift;
+	$i =~ s/
+		( \\. ) | # skip over escapes (maybe)
+		( \$[^% $\]+ ) # catch special variables
+	/
+		if ($1) { $1 }
+		elsif ($2) { my $i2 = $2; ir_fe(ir_parse_special($i2, @_)) }
+		else { $& }
+	/gex;
+	$i
+}
+
+
+sub sb_ctfe { # Irssi::current_theme->format_expand wrapper
+	Irssi::current_theme->format_expand(
+		shift,
+		(
+			Irssi::EXPAND_FLAG_IGNORE_REPLACES
+				|
+			($_[0]?0:Irssi::EXPAND_FLAG_IGNORE_EMPTY)
+		)
+	)
+}
+sub sb_expand { # expand {format }s (and apply parse_special for $vars)
+	ir_parse_special(
+		sb_ctfe(shift)
+	)
+}
+sub sb_strip {
+	ir_strip_codes(
+		sb_expand(shift)
+	); # does this get us the actual length of that s*ty bar :P ?
+}
+sub sb_length {
+	# unicode cludge, d*mn broken Irssi
+	# screw it, this will fail from broken joining anyway (and cause warnings)
+	my $term_type = 'term_type';
+	if (Irssi::version > 20040819) { # this is probably wrong, but I don't know
+		                              # when the setting name got changed
+		$term_type = 'term_charset';
+	}
+	#if (lc Irssi::settings_get_str($term_type) eq '8bit'
+	#		or Irssi::settings_get_str($term_type) =~ /^iso/i
+	#) {
+	#	length(sb_strip(shift))
+	#}
+	#else {
+	my $temp = sb_strip(shift);
+	# try to get the displayed width
+	my $length;
+	eval {
+		require Text::CharWidth;
+		$length = Text::CharWidth::mbswidth($temp);
+	};
+	unless ($@) {
+		return $length;
+	}
+	else {
+		if (lc Irssi::settings_get_str($term_type) eq 'utf-8') {
+			# try to switch on utf8
+			eval {
+				no warnings;
+				require Encode;
+				#$temp = Encode::decode_utf8($temp); # thanks for the hint, but I have my
+				#                                    # reasons for _utf8_on
+				Encode::_utf8_on($temp);
+			};
+		}
+		# there is nothing more I can do
+		length($temp)
+	}
+	#}
+}
+
+# !!! G*DD*MN Irssi is adding an additional layer of backslashitis per { } layer
+# !!! AND I still don't know what I need to escape.
+# !!! and NOONE else seems to know or care either.
+# !!! f*ck open source. I mean it.
+# XXX any Irssi::print debug statement leads to SEGFAULT - why ?
+
+# major parts of the idea by buu (#perl @ freenode)
+# thanks to fxn and Somni for debugging
+#	while ($_[0] =~ /(.)/g) {
+#		my $c = $1; # XXX sooo... goto kills $1
+#		if ($q eq '%') { goto ESC; }
+
+## <freenode:#perl:tybalt89> s/%(.)|(\{)|(\})|(\\|\$)/$1?$1:$2?($level++,$2):$3?($level>$min_level&&$level--,$3):'\\'x(2**$level-1).$4/ge;  # untested...
+sub ir_escape {
+	my $min_level = $_[1] || 0; my $level = $min_level;
+	my $o = shift;
+	$o =~ s/
+		(	%.	)	| # $1
+		(	\{	)	| # $2
+		(	\}	)	| # $3
+		(	\\	)	| # $4
+		(	\$(?=[^\\])	)	| # $5
+		(	\$	) # $6
+	/
+		if ($1) { $1 } # %. escape
+		elsif ($2) { $level++; $2 } # { nesting start
+		elsif ($3) { if ($level > $min_level) { $level--; } $3 } # } nesting end
+		elsif ($4) { '\\'x(2**$level) } # \ needs \\escaping
+		elsif ($5) { '\\'x(2**$level-1) . '$' . '\\'x(2**$level-1) } # and $ needs even more because of "parse_special"
+		else { '\\'x(2**$level-1) . '$' } # $ needs \$ escaping
+	/gex;
+	$o
+}
+#sub ir_escape {
+#	my $min_level = $_[1] || 0; my $level = $min_level;
+#	my $o = shift;
+#	$o =~ s/
+#		(	%.	)	| # $1
+#		(	\{	)	| # $2
+#		(	\}	)	| # $3
+#		(	\\	|	\$	)	# $4
+#	/
+#		if ($1) { $1 } # %. escape
+#		elsif ($2) { $level++; $2 } # { nesting start
+#		elsif ($3) { if ($level > $min_level) { $level--; } $3 } # } nesting end
+#		else { '\\'x(2**($level-1)-1) . $4 } # \ or $ needs \\escaping
+#	/gex;
+#	$o
+#}
+
+sub ir_fe { # try to fix format stuff
+	my $x = shift;
+	# XXX why do I have to use two/four % here instead of one/two ??
+	# answer: you screwed up in ir_escape
+	$x =~ s/([%{}])/%$1/g;
+	#$x =~ s/(\\|\$|[ ])/\\$1/g; # XXX HOW CAN I HANDLE THE SPACES CORRECTLY XXX
+	$x =~ s/(\\|\$)/\\$1/g;
+	#$x =~ s/(\$(?=.))|(\$)/$1?"\\\$\\":"\\\$"/ge; # I think this should be here
+	#                                              # (logic), but it doesn't work
+	#                                              # that way :P
+	#$x =~ s/\\/\\\\/g; # that's right, escape escapes
+	$x
+}
+sub ir_ve { # escapes special vars but leave colours alone
+	my $x = shift;
+	#$x =~ s/([%{}])/%$1/g;
+	$x =~ s/(\\|\$|[ ])/\\$1/g;
+	$x
+}
+
+my %ansi_table;
+{
+	my ($i, $j, $k) = (0, 0, 0);
+	%ansi_table = (
+		# fe-common::core::formats.c:format_expand_styles
+		#      do                                              format_backs
+		(map { $_ => $terminfo->setab($i++) } (split //, '01234567' )),
+		#      do                                              format_fores
+		(map { $_ => $terminfo->setaf($j++) } (split //, 'krgybmcw' )),
+		#      do                                              bold_fores
+		(map { $_ => $terminfo->bold() .
+		             $terminfo->setaf($k++) } (split //, 'KRGYBMCW')),
+		# reset
+		#(map { $_ => $terminfo->op() } (split //, 'nN')),
+		(map { $_ => $terminfo->op() } (split //, 'n')),
+		(map { $_ => "\033[0m" } (split //, 'N')), # XXX quick and DIRTY
+		# flash/bright
+		F => $terminfo->blink(),
+		# reverse
+		8 => $terminfo->rev(),
+		# bold
+		(map { $_ => $terminfo->bold() } (split //, '9_')),
+		#      delete                other stuff
+		(map { $_ => '' } (split //, ':|>#[')),
+		#      escape
+		(map { $_ => $_ } (split //, '{}%')),
+	)
+}
+sub formats_to_ansi_basic {
+	my $o = shift;
+	$o =~ s/(%(.))/exists $ansi_table{$2} ? $ansi_table{$2} : $1/gex;
+	$o
+}
+
+sub lc1459 ($) { my $x = shift; $x =~ y/A-Z][\^/a-z}{|~/; $x }
+Irssi::settings_add_str(setc, 'banned_channels', '');
+Irssi::settings_add_bool(setc, 'banned_channels_on', 0);
+my %banned_channels = map { lc1459($_) => undef }
+split ' ', Irssi::settings_get_str('banned_channels');
+Irssi::settings_add_str(setc, 'fancy_abbrev', 'fancy');
+
+# }}}
+
+# {{{ main
+
+sub remake () {
+	#$callcount++;
+	#my $xx = $callcount; Irssi::print("starting remake [ $xx ]");
+	my ($hilight, $number, $display);
+	my $separator = '{sb_act_sep ' . Irssi::settings_get_str(set 'separator') .
+		'}';
+	my $custSort = Irssi::settings_get_str(set 'sort');
+	my $custSortDir = 1;
+	if ($custSort =~ /^[-!](.*)/) {
+		$custSortDir = -1;
+		$custSort = $1;
+	}
+
+	my @wins = 
+		sort {
+			(
+				( (int($a->{$custSort}) <=> int($b->{$custSort})) * $custSortDir )
+					||
+				($a->{'refnum'} <=> $b->{'refnum'})
+			)
+		} Irssi::windows;
+	my $block = Irssi::settings_get_int(set 'block');
+	my $columns = $currentColumns;
+	my $oldActString = $actString if $SCREEN_MODE;
+	$actString = $SCREEN_MODE ? ['   A W L'] : [];
+	my $line = $SCREEN_MODE ? 1 : 0;
+	my $width = $SCREEN_MODE
+			?
+		$screenWidth - abs($block)*$columns + 1
+			:
+		([Irssi::windows]->[0]{'width'} - sb_length('{sb x}'));
+	my $height = $screenHeight - abs(Irssi::settings_get_int(set
+			'height_adjust'));
+	my ($numPad, $keyPad) = (0, 0);
+	my %abbrevList;
+	if ($SCREEN_MODE or Irssi::settings_get_bool(set 'sbar_maxlength')
+			or ($block < 0)
+	) {
+		%abbrevList = ();
+		if (Irssi::settings_get_str('fancy_abbrev') !~ /^(no|off|head)/i) {
+			my @nameList = map { ref $_ ? $_->get_active_name : '' } @wins;
+			for (my $i = 0; $i < @nameList - 1; ++$i) {
+				my ($x, $y) = ($nameList[$i], $nameList[$i + 1]);
+				for ($x, $y) { s/^[+#!=]// }
+				my $res = Algorithm::LCSS::LCSS($x, $y);
+				if (defined $res) {
+					#Irssi::print("common pattern $x $y : $res");
+					#Irssi::print("found at $nameList[$i] ".index($nameList[$i],
+					#		$res));
+					$abbrevList{$nameList[$i]} = int (index($nameList[$i], $res) +
+						(length($res) / 2));
+					#Irssi::print("found at ".$nameList[$i+1]." ".index($nameList[$i+1],
+					#		$res));
+					$abbrevList{$nameList[$i+1]} = int (index($nameList[$i+1], $res) +
+						(length($res) / 2));
+				}
+			}
+		}
+		if ($SCREEN_MODE or ($block < 0)) {
+			$numPad = length((sort { length($b) <=> length($a) } keys %keymap)[0]);
+			$keyPad = length((sort { length($b) <=> length($a) } values %keymap)[0]);
+		}
+	}
+	if ($SCREEN_MODE) {
+		print STDERR $screenansi->dcs().
+		             $terminfo->civis().
+						 $terminfo->sc().
+						 $screenansi->st();
+		if (@$oldActString < 1) {
+			print STDERR $screenansi->dcs().
+							 $terminfo->cup(0, $width).
+			             $actString->[0].
+							 $terminfo->el().
+			             $screenansi->st();
+		}
+	}
+	foreach my $win (@wins) {
+		unless ($SCREEN_MODE) {
+			$actString->[$line] = '' unless defined $actString->[$line]
+					or Irssi::settings_get_bool(set 'all_disable');
+		}
+
+		# all stolen from chanact, what does this code do and why do we need it ?
+		!ref($win) && next;
+
+		my $name = $win->get_active_name;
+		$name = '*' if (Irssi::settings_get_bool('banned_channels_on') and exists
+			$banned_channels{lc1459($name)});
+		$name = $win->{'name'} if $name ne '*' and $win->{'name'} ne ''
+			and Irssi::settings_get_bool(set 'prefer_name');
+		my $active = $win->{'active'};
+		my $colour = $win->{'hilight_color'};
+		if (!defined $colour) { $colour = ''; }
+
+		if ($win->{'data_level'} < Irssi::settings_get_int(set 'hide_data')) {
+			next; } # for Geert
+		if    ($win->{'data_level'} == 0) { $hilight = '{sb_act_none '; }
+		elsif ($win->{'data_level'} == 1) { $hilight = '{sb_act_text '; }
+		elsif ($win->{'data_level'} == 2) { $hilight = '{sb_act_msg '; }
+		elsif ($colour             ne '') { $hilight = "{sb_act_hilight_color $colour "; }
+		elsif ($win->{'data_level'} == 3) { $hilight = '{sb_act_hilight '; }
+		else                              { $hilight = '{sb_act_special '; }
+
+		$number = $win->{'refnum'};
+		my @display = ('display_nokey');
+		if (defined $keymap{$number} and $keymap{$number} ne '') {
+			unshift @display, map { (my $cpy = $_) =~ s/_no/_/; $cpy } @display;
+		}
+		if (Irssi::active_win->{'refnum'} == $number) {
+			unshift @display, map { my $cpy = $_; $cpy .= '_active'; $cpy } @display;
+		}
+		#Irssi::print("win $number [@display]: " . join '.', split //, join '<<', map {
+			#		Irssi::settings_get_str(set $_) } @display);
+		$display = (grep { $_ }
+			map { Irssi::settings_get_str(set $_) }
+			@display)[0];
+			#Irssi::print("win $number : " . join '.', split //, $display);
+
+		if ($SCREEN_MODE or Irssi::settings_get_bool(set 'sbar_maxlength')
+				or ($block < 0)
+		) {
+			my $baseLength = sb_length(ir_escape(ir_ve(ir_parse_special_protected(sb_ctfe(
+				'{sb_background}' . expand($display,
+				C => ir_fe('x'),
+				N => $number . (' 'x($numPad - length($number))),
+				Q => ir_fe((' 'x($keyPad - length($keymap{$number}))) . $keymap{$number}),
+				H => $hilight,
+				S => '}{sb_background}'
+			), 1), $win)))) - 1;
+			my $diff = abs($block) - (length($name) + $baseLength);
+			if ($diff < 0) { # too long
+				if (abs($diff) >= length($name)) { $name = '' } # forget it
+				elsif (abs($diff) + 1 >= length($name)) { $name = substr($name,
+						0, 1); }
+				else {
+					my $middle = exists $abbrevList{$name} ?
+					(($abbrevList{$name} + (2*(length($name) / 2)))/3) :
+						((Irssi::settings_get_str('fancy_abbrev') =~ /^head/i) ?
+								length($name) :
+						(length($name) / 2));
+					my $cut = int($middle - (abs($diff) / 2) + .55); 
+					$cut = 1 if $cut < 1;
+					$cut = length($name) - abs($diff) - 1 if $cut > (length($name) -
+						abs($diff) - 1);
+					$name = substr($name, 0, $cut) . '~' . substr($name, $cut +
+						abs($diff) + 1);
+				}
+			}
+			elsif ($SCREEN_MODE or ($block < 0)) {
+				$name .= (' ' x $diff);
+			}
+		}
+
+		my $add = ir_ve(ir_parse_special_protected(sb_ctfe('{sb_background}' . expand($display,
+			C => ir_fe($name),
+			N => $number . (' 'x($numPad - length($number))),
+			Q => ir_fe((' 'x($keyPad - length($keymap{$number}))) . $keymap{$number}),
+			H => $hilight,
+			S => '}{sb_background}'
+		), 1), $win));
+		if ($SCREEN_MODE) {
+			$actString->[$line] = $add;
+			if ((!defined $oldActString->[$line]
+					or $oldActString->[$line] ne $actString->[$line])
+					and
+				$line <= ($columns * $height)
+			) {
+				print STDERR $screenansi->dcs().
+								 $terminfo->cup(($line-1) % $height+1, $width + (
+									 abs($block) * int(($line-1) / $height))).
+				formats_to_ansi_basic(sb_expand(ir_escape($actString->[$line]))).
+								#$terminfo->el().
+								 $screenansi->st();
+			}
+			$line++;
+		}
+		else {
+			#$temp =~ s/\{\S+?(?:\s(.*?))?\}/$1/g;
+			#$temp =~ s/\\\\\\\\/\\/g; # XXX I'm actually guessing here, someone point me
+			#                          # XXX to docs please
+			$actString->[$line] = '' unless defined $actString->[$line];
+
+			# XXX how can I check whether the content still fits in the bar? this would
+			# XXX allow awlstatus to reside on a statusbar together with other items...
+			if (sb_length(ir_escape($actString->[$line] . $add)) >= $width) {
+				# XXX doesn't correctly handle utf-8 multibyte ... help !!?
+				$actString->[$line] .= ' ' x ($width - sb_length(ir_escape(
+					$actString->[$line])));
+				$line++;
+			}
+			$actString->[$line] .= $add . $separator;
+			# XXX if I use these prints, output layout gets screwed up... why ?
+			#Irssi::print("line $line: ".$actString->[$line]);
+			#Irssi::print("temp $line: ".$temp);
+		}
+	}
+
+	if ($SCREEN_MODE) {
+		while ($line <= ($columns * $height)) {
+			print STDERR $screenansi->dcs().
+							 $terminfo->cup(($line-1) % $height+1, $width + (
+								 abs($block) * int(($line-1) / $height))).
+							 $terminfo->el().
+							 $screenansi->st();
+			$line++;
+		}
+		print STDERR $screenansi->dcs().
+						 $terminfo->rc().
+		             $terminfo->cnorm().
+						 $screenansi->st();
+	}
+	else {
+		# XXX the Irssi::print statements lead to the MOST WEIRD results
+		# e.g.: the loop gets executed TWICE for p > 0 ?!?
+		for (my $p = 0; $p < @$actString; $p++) { # wrap each line in {sb }, escape it
+			my $x = $actString->[$p];              # properly, etc.
+			$x =~ s/\Q$separator\E([ ]*)$/$1/;
+			#Irssi::print("[$p]".'current:'.join'.',split//,sb_strip(ir_escape($x,0)));
+			#Irssi::print("assumed length before:".sb_length(ir_escape($x,0)));
+			$x = "{sb $x}";
+			#Irssi::print("[$p]".'new:'.join'.',split//,sb_expand(ir_escape($x,0)));
+			#Irssi::print("[$p]".'new:'.join'.',split//,ir_escape($x,0));
+			#Irssi::print("assumed length after:".sb_length(ir_escape($x,0)));
+			$x = ir_escape($x);
+			#Irssi::print("[$p]".'REALnew:'.join'.',split//,sb_strip($x));
+			$actString->[$p] = $x;
+			# XXX any Irssi::print debug statement leads to SEGFAULT (sometimes) - why ?
+		}
+	}
+	#Irssi::print("remake [ $xx ] finished");
+}
+
+sub awlHasChanged () {
+	$globTime = undef;
+	my $temp = ($SCREEN_MODE ?
+		"\\\n" . Irssi::settings_get_int(set 'block').
+		Irssi::settings_get_int(set 'height_adjust')
+		: "!\n" . Irssi::settings_get_str(set 'placement').
+		Irssi::settings_get_int(set 'position')).
+		Irssi::settings_get_str(set 'automode');
+	if ($temp ne $resetNeeded) { wlreset(); return; }
+	#Irssi::print("awl has changed, calls to remake so far: $callcount");
+	$needRemake = 1;
+
+	#remake();
+	if (
+		($SCREEN_MODE and !$DISABLE_SCREEN_TEMP)
+			or
+		($needRemake and Irssi::settings_get_bool(set 'all_disable'))
+			or
+		(!Irssi::settings_get_bool(set 'all_disable') and $currentLines < 1)
+	) {
+		$needRemake = undef;
+		remake();
+	}
+
+	unless ($SCREEN_MODE) {
+		# XXX Irssi crashes if I try to do this without timer, why ? What's the minimum
+		# XXX delay I need to use in the timer ?
+		Irssi::timeout_add_once(100, 'syncLines', undef);
+
+		for (keys %statusbars) {
+			Irssi::statusbar_items_redraw(set$_);
+		}
+	}
+	else {
+		Irssi::timeout_add_once(100, 'syncColumns', undef);
+	}
+}
+
+sub eventChanged () { # Implement a change queue/blocker -.-)
+	if (defined $globTime) {
+		Irssi::timeout_remove($globTime);
+	} # delay the update further
+	$globTime = Irssi::timeout_add_once(GLOB_QUEUE_TIMER, 'awlHasChanged', undef);
+}
+
+# }}}
+
+
+# {{{ screen mode
+
+sub screenFullRedraw {
+	my ($window) = @_;
+	if (!ref $window or $window->{'refnum'} == Irssi::active_win->{'refnum'}) {
+		$actString = [];
+		eventChanged();
+	}
+}
+
+sub screenSize { # from nicklist.pl
+	$screenResizing = 1;
+	# fit screen
+	system 'screen -x '.$ENV{'STY'}.' -X fit';
+	# get size
+	my ($row, $col) = split ' ', `stty size`;
+	# set screen width
+	$screenWidth = $col-1;
+	$screenHeight = $row-1;
+	
+	# on some recent systems, "screen -X fit; screen -X width -w 50" doesn't work, needs a sleep in between the 2 commands
+	# so we wait a second before setting the width
+	Irssi::timeout_add_once(100, sub {
+		my ($new_irssi_width) = @_;
+		$new_irssi_width -= abs(Irssi::settings_get_int(set
+				'block'))*$currentColumns - 1;
+		system 'screen -x '.$ENV{'STY'}.' -X width -w ' . $new_irssi_width;
+		# and then we wait another second for the resizing, and then redraw.
+		Irssi::timeout_add_once(10,sub {$screenResizing = 0; screenFullRedraw()}, []);
+	}, $screenWidth);
+}
+
+sub screenOff {
+	my ($unloadMode) = @_;
+	Irssi::signal_remove('gui print text finished' => 'screenFullRedraw');
+	Irssi::signal_remove('gui page scrolled' => 'screenFullRedraw');
+	Irssi::signal_remove('window changed' => 'screenFullRedraw');
+	Irssi::signal_remove('window changed automatic' => 'screenFullRedraw');
+	if ($unloadMode) {
+		Irssi::signal_remove('terminal resized' => 'resizeTerm');
+	}
+	system 'screen -x '.$ENV{'STY'}.' -X fit';
+}
+
+sub syncColumns {
+	return if (@$actString == 0);
+	my $temp = $currentColumns;
+	#Irssi::print("current columns $temp");
+	my $height = $screenHeight - abs(Irssi::settings_get_int(set
+			'height_adjust'));
+	$currentColumns = int(($#$actString-1) / $height) + 1;
+	#Irssi::print("objects in actstring:".scalar(@$actString).", screen height:".
+	#	$height);
+	my $currMaxColumns = Irssi::settings_get_int(set 'columns');
+	if ($currMaxColumns > 0 and $currentColumns > $currMaxColumns) {
+		$currentColumns = $currMaxColumns;
+	}
+	elsif ($currMaxColumns < 0) {
+		$currentColumns = abs($currMaxColumns);
+	}
+	return if ($temp == $currentColumns);
+	screenSize();
+}
+
+#$needRemake = 1;
+sub resizeTerm () {
+	if ($SCREEN_MODE and !$screenResizing) {
+		$screenResizing = 1;
+		Irssi::timeout_add_once(10, 'screenSize', undef);
+	}
+	Irssi::timeout_add_once(100, 'eventChanged', undef);
+}
+
+# }}}
+
+
+# {{{ settings add
+
+Irssi::settings_add_str(setc, set 'display_nokey', '[$N]$H$C$S');
+Irssi::settings_add_str(setc, set 'display_key', '[$Q=$N]$H$C$S');
+Irssi::settings_add_str(setc, set 'display_nokey_active', '');
+Irssi::settings_add_str(setc, set 'display_key_active', '');
+Irssi::settings_add_str(setc, set 'separator', "\\ ");
+Irssi::settings_add_bool(setc, set 'prefer_name', 0);
+Irssi::settings_add_int(setc, set 'hide_data', 0);
+Irssi::settings_add_int(setc, set 'maxlines', 9);
+Irssi::settings_add_int(setc, set 'columns', 1);
+Irssi::settings_add_int(setc, set 'block', 20);
+Irssi::settings_add_bool(setc, set 'sbar_maxlength', 0);
+Irssi::settings_add_int(setc, set 'height_adjust', 2);
+Irssi::settings_add_str(setc, set 'sort', 'refnum');
+Irssi::settings_add_str(setc, set 'placement', 'bottom');
+Irssi::settings_add_int(setc, set 'position', 0);
+Irssi::settings_add_bool(setc, set 'all_disable', 0);
+Irssi::settings_add_str(setc, set 'automode', 'sbar');
+
+# }}}
+
+
+# {{{ init
+
+sub wlreset {
+	$actString = [];
+	$currentLines = 0; # 1; # mhmmmm .. we actually enable one line down there so
+	                        # let's try this.
+	#update_keymap();
+	killOldStatus();
+	# Register statusbar
+	#add_statusbar(0);
+	#Irssi::command('statusbar wl0 enable');
+	my $was_screen_mode = $SCREEN_MODE;
+	if ($SCREEN_MODE = (Irssi::settings_get_str(set 'automode') =~ /screen/i)
+			and
+		!$was_screen_mode
+	) {
+		if (!defined $ENV{'STY'}) {
+			Irssi::print('Screen mode can only be used in GNU screen but no '.
+				'screen was found.', MSGLEVEL_CLIENTERROR);
+			$SCREEN_MODE = undef;
+		}
+		else {
+			Irssi::signal_add_last('gui print text finished' => 'screenFullRedraw');
+			Irssi::signal_add_last('gui page scrolled' => 'screenFullRedraw');
+			Irssi::signal_add('window changed' => 'screenFullRedraw');
+			Irssi::signal_add('window changed automatic' => 'screenFullRedraw');
+		}
+	}
+	elsif ($was_screen_mode and !$SCREEN_MODE) {
+		screenOff();
+	}
+	$resetNeeded = ($SCREEN_MODE ?
+		"\\\n" . Irssi::settings_get_int(set 'block').
+		Irssi::settings_get_int(set 'height_adjust')
+		: "!\n" . Irssi::settings_get_str(set 'placement').
+		Irssi::settings_get_int(set 'position')).
+		Irssi::settings_get_str(set 'automode');
+	resizeTerm();
+}
+
+wlreset();
+
+# }}}
+
+
+# {{{ unload/deinit
+
+my $Unload;
+sub unload ($$$) {
+	$Unload = 1;
+	# pretend we didn't do anything ASAP
+	Irssi::timeout_add_once(10, sub { $Unload = undef; }, undef);
+}
+# last try to catch a sigsegv
+Irssi::signal_add_first('gui exit' => sub { $Unload = undef; });
+sub UNLOAD {
+	# this might well crash Irssi... try /eval /script unload someotherscript ;
+	# /quit (= SEGFAULT !)
+	if ($Unload) {
+		$actString = ['']; # syncLines(); # XXX Irssi crashes when trying to disable
+		killOldStatus();                  # XXX all statusbars ?
+		if ($SCREEN_MODE) {
+			screenOff('unload mode');
+		}
+	}
+}
+
+# }}}
+
+
+# {{{ signals
+
+sub addPrintTextHook { # update on print text
+	return if $_[0]->{'level'} == 262144 and $_[0]->{'target'} eq ''
+			and !defined($_[0]->{'server'});
+	if (Irssi::settings_get_str(set 'sort') =~ /^[-!]?last_line$/) {
+		Irssi::timeout_add_once(100, 'eventChanged', undef);
+	}
+}
+
+#sub _x { my ($x, $y) = @_; ($x, sub { Irssi::print('-->signal '.$x); eval "$y();"; }) }
+#sub _x { @_ }
+Irssi::signal_add_first(
+	'command script unload' => 'unload'
+);
+Irssi::signal_add_last({
+	'setup changed' => 'eventChanged',
+	'print text' => 'addPrintTextHook',
+	'terminal resized' => 'resizeTerm',
+	'setup reread' => 'wlreset',
+	'window hilight' => 'eventChanged',
+});
+Irssi::signal_add({
+	'window created' => 'eventChanged',
+	'window destroyed' => 'eventChanged',
+	'window name changed' => 'eventChanged',
+	'window refnum changed' => 'eventChanged',
+	'window changed' => 'eventChanged',
+	'window changed automatic' => 'eventChanged',
+});
+
+#Irssi::signal_add('nick mode changed', 'chanactHasChanged'); # relicts
+
+# }}}
+
+# {{{ commands
+
+
+sub runsub {
+	my ($cmd) = @_;
+	sub {
+		my ($data, $server, $item) = @_;
+		Irssi::command_runsub($cmd, $data, $server, $item);
+	};
+}
+Irssi::command_bind( setc() => runsub(setc()) );
+Irssi::command_bind( setc() . ' paste' => runsub(setc() . ' paste') );
+Irssi::command_bind(
+	setc() . ' paste on' => sub {
+		return unless $SCREEN_MODE;
+		my $was_disabled = $DISABLE_SCREEN_TEMP;
+		$DISABLE_SCREEN_TEMP = 1;
+		Irssi::print('Paste mode is now ON, '.uc(setc()).' is temporarily '.
+		             'disabled.');
+		if (!$was_disabled) {
+			$screenResizing = 1;
+			screenOff();
+		}
+	}
+);
+Irssi::command_bind(
+	setc() . ' paste off' => sub {
+		return unless $SCREEN_MODE;
+		my $was_disabled = $DISABLE_SCREEN_TEMP;
+		$DISABLE_SCREEN_TEMP = undef;
+		Irssi::print('Paste mode is now OFF, '.uc(setc()).' is enabled.');
+		if ($was_disabled) {
+			$SCREEN_MODE = undef;
+			$screenResizing = 0;
+			wlreset();
+		}
+	}
+);
+Irssi::command_bind(
+	setc() . ' paste toggle' => sub {
+		if ($DISABLE_SCREEN_TEMP) {
+			Irssi::command(setc() . ' paste off');
+		}
+		else {
+			Irssi::command(setc() . ' paste on');
+		}
+	}
+);
+Irssi::command_bind(
+	setc() . ' redraw' => sub {
+		return unless $SCREEN_MODE;
+		screenFullRedraw();
+	}
+);
+		
+
+# }}}
+
+# {{{ Algorithm::LCSS module
+{
+	package Algorithm::Diff;
+	# Skip to first "=head" line for documentation.
+	use strict;
+
+	use integer;    # see below in _replaceNextLargerWith() for mod to make
+						 # if you don't use this
+
+	# McIlroy-Hunt diff algorithm
+	# Adapted from the Smalltalk code of Mario I. Wolczko, <[email protected]>
+	# by Ned Konz, [email protected]
+	# Updates by Tye McQueen, http://perlmonks.org/?node=tye
+
+	# Create a hash that maps each element of $aCollection to the set of
+	# positions it occupies in $aCollection, restricted to the elements
+	# within the range of indexes specified by $start and $end.
+	# The fourth parameter is a subroutine reference that will be called to
+	# generate a string to use as a key.
+	# Additional parameters, if any, will be passed to this subroutine.
+	#
+	# my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen );
+
+	sub _withPositionsOfInInterval
+	{
+		 my $aCollection = shift;    # array ref
+		 my $start       = shift;
+		 my $end         = shift;
+		 my $keyGen      = shift;
+		 my %d;
+		 my $index;
+		 for ( $index = $start ; $index <= $end ; $index++ )
+		 {
+			  my $element = $aCollection->[$index];
+			  my $key = &$keyGen( $element, @_ );
+			  if ( exists( $d{$key} ) )
+			  {
+					unshift ( @{ $d{$key} }, $index );
+			  }
+			  else
+			  {
+					$d{$key} = [$index];
+			  }
+		 }
+		 return wantarray ? %d : \%d;
+	}
+
+	# Find the place at which aValue would normally be inserted into the
+	# array. If that place is already occupied by aValue, do nothing, and
+	# return undef. If the place does not exist (i.e., it is off the end of
+	# the array), add it to the end, otherwise replace the element at that
+	# point with aValue.  It is assumed that the array's values are numeric.
+	# This is where the bulk (75%) of the time is spent in this module, so
+	# try to make it fast!
+
+	sub _replaceNextLargerWith
+	{
+		 my ( $array, $aValue, $high ) = @_;
+		 $high ||= $#$array;
+
+		 # off the end?
+		 if ( $high == -1 || $aValue > $array->[-1] )
+		 {
+			  push ( @$array, $aValue );
+			  return $high + 1;
+		 }
+
+		 # binary search for insertion point...
+		 my $low = 0;
+		 my $index;
+		 my $found;
+		 while ( $low <= $high )
+		 {
+			  $index = ( $high + $low ) / 2;
+
+			  # $index = int(( $high + $low ) / 2);  # without 'use integer'
+			  $found = $array->[$index];
+
+			  if ( $aValue == $found )
+			  {
+					return undef;
+			  }
+			  elsif ( $aValue > $found )
+			  {
+					$low = $index + 1;
+			  }
+			  else
+			  {
+					$high = $index - 1;
+			  }
+		 }
+
+		 # now insertion point is in $low.
+		 $array->[$low] = $aValue;    # overwrite next larger
+		 return $low;
+	}
+
+	# This method computes the longest common subsequence in $a and $b.
+
+	# Result is array or ref, whose contents is such that
+	#   $a->[ $i ] == $b->[ $result[ $i ] ]
+	# foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined.
+
+	# An additional argument may be passed; this is a hash or key generating
+	# function that should return a string that uniquely identifies the given
+	# element.  It should be the case that if the key is the same, the elements
+	# will compare the same. If this parameter is undef or missing, the key
+	# will be the element as a string.
+
+	# By default, comparisons will use "eq" and elements will be turned into keys
+	# using the default stringizing operator '""'.
+
+	# Additional parameters, if any, will be passed to the key generation
+	# routine.
+
+	sub _longestCommonSubsequence
+	{
+		 my $a        = shift;    # array ref or hash ref
+		 my $b        = shift;    # array ref or hash ref
+		 my $counting = shift;    # scalar
+		 my $keyGen   = shift;    # code ref
+		 my $compare;             # code ref
+
+		 if ( ref($a) eq 'HASH' )
+		 {                        # prepared hash must be in $b
+			  my $tmp = $b;
+			  $b = $a;
+			  $a = $tmp;
+		 }
+
+		 # Check for bogus (non-ref) argument values
+		 if ( !ref($a) || !ref($b) )
+		 {
+			  my @callerInfo = caller(1);
+			  die 'error: must pass array or hash references to ' . $callerInfo[3];
+		 }
+
+		 # set up code refs
+		 # Note that these are optimized.
+		 if ( !defined($keyGen) )    # optimize for strings
+		 {
+			  $keyGen = sub { $_[0] };
+			  $compare = sub { my ( $a, $b ) = @_; $a eq $b };
+		 }
+		 else
+		 {
+			  $compare = sub {
+					my $a = shift;
+					my $b = shift;
+					&$keyGen( $a, @_ ) eq &$keyGen( $b, @_ );
+			  };
+		 }
+
+		 my ( $aStart, $aFinish, $matchVector ) = ( 0, $#$a, [] );
+		 my ( $prunedCount, $bMatches ) = ( 0, {} );
+
+		 if ( ref($b) eq 'HASH' )    # was $bMatches prepared for us?
+		 {
+			  $bMatches = $b;
+		 }
+		 else
+		 {
+			  my ( $bStart, $bFinish ) = ( 0, $#$b );
+
+			  # First we prune off any common elements at the beginning
+			  while ( $aStart <= $aFinish
+					and $bStart <= $bFinish
+					and &$compare( $a->[$aStart], $b->[$bStart], @_ ) )
+			  {
+					$matchVector->[ $aStart++ ] = $bStart++;
+					$prunedCount++;
+			  }
+
+			  # now the end
+			  while ( $aStart <= $aFinish
+					and $bStart <= $bFinish
+					and &$compare( $a->[$aFinish], $b->[$bFinish], @_ ) )
+			  {
+					$matchVector->[ $aFinish-- ] = $bFinish--;
+					$prunedCount++;
+			  }
+
+			  # Now compute the equivalence classes of positions of elements
+			  $bMatches =
+				 _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ );
+		 }
+		 my $thresh = [];
+		 my $links  = [];
+
+		 my ( $i, $ai, $j, $k );
+		 for ( $i = $aStart ; $i <= $aFinish ; $i++ )
+		 {
+			  $ai = &$keyGen( $a->[$i], @_ );
+			  if ( exists( $bMatches->{$ai} ) )
+			  {
+					$k = 0;
+					for $j ( @{ $bMatches->{$ai} } )
+					{
+
+						 # optimization: most of the time this will be true
+						 if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j )
+						 {
+							  $thresh->[$k] = $j;
+						 }
+						 else
+						 {
+							  $k = _replaceNextLargerWith( $thresh, $j, $k );
+						 }
+
+						 # oddly, it's faster to always test this (CPU cache?).
+						 if ( defined($k) )
+						 {
+							  $links->[$k] =
+								 [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ];
+						 }
+					}
+			  }
+		 }
+
+		 if (@$thresh)
+		 {
+			  return $prunedCount + @$thresh if $counting;
+			  for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] )
+			  {
+					$matchVector->[ $link->[1] ] = $link->[2];
+			  }
+		 }
+		 elsif ($counting)
+		 {
+			  return $prunedCount;
+		 }
+
+		 return wantarray ? @$matchVector : $matchVector;
+	}
+
+	sub traverse_sequences
+	{
+		 my $a                 = shift;          # array ref
+		 my $b                 = shift;          # array ref
+		 my $callbacks         = shift || {};
+		 my $keyGen            = shift;
+		 my $matchCallback     = $callbacks->{'MATCH'} || sub { };
+		 my $discardACallback  = $callbacks->{'DISCARD_A'} || sub { };
+		 my $finishedACallback = $callbacks->{'A_FINISHED'};
+		 my $discardBCallback  = $callbacks->{'DISCARD_B'} || sub { };
+		 my $finishedBCallback = $callbacks->{'B_FINISHED'};
+		 my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ );
+
+		 # Process all the lines in @$matchVector
+		 my $lastA = $#$a;
+		 my $lastB = $#$b;
+		 my $bi    = 0;
+		 my $ai;
+
+		 for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ )
+		 {
+			  my $bLine = $matchVector->[$ai];
+			  if ( defined($bLine) )    # matched
+			  {
+					&$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine;
+					&$matchCallback( $ai,    $bi++, @_ );
+			  }
+			  else
+			  {
+					&$discardACallback( $ai, $bi, @_ );
+			  }
+		 }
+
+		 # The last entry (if any) processed was a match.
+		 # $ai and $bi point just past the last matching lines in their sequences.
+
+		 while ( $ai <= $lastA or $bi <= $lastB )
+		 {
+
+			  # last A?
+			  if ( $ai == $lastA + 1 and $bi <= $lastB )
+			  {
+					if ( defined($finishedACallback) )
+					{
+						 &$finishedACallback( $lastA, @_ );
+						 $finishedACallback = undef;
+					}
+					else
+					{
+						 &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB;
+					}
+			  }
+
+			  # last B?
+			  if ( $bi == $lastB + 1 and $ai <= $lastA )
+			  {
+					if ( defined($finishedBCallback) )
+					{
+						 &$finishedBCallback( $lastB, @_ );
+						 $finishedBCallback = undef;
+					}
+					else
+					{
+						 &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA;
+					}
+			  }
+
+			  &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA;
+			  &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB;
+		 }
+
+		 return 1;
+	}
+
+	sub traverse_balanced
+	{
+		 my $a                 = shift;              # array ref
+		 my $b                 = shift;              # array ref
+		 my $callbacks         = shift || {};
+		 my $keyGen            = shift;
+		 my $matchCallback     = $callbacks->{'MATCH'} || sub { };
+		 my $discardACallback  = $callbacks->{'DISCARD_A'} || sub { };
+		 my $discardBCallback  = $callbacks->{'DISCARD_B'} || sub { };
+		 my $changeCallback    = $callbacks->{'CHANGE'};
+		 my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ );
+
+		 # Process all the lines in match vector
+		 my $lastA = $#$a;
+		 my $lastB = $#$b;
+		 my $bi    = 0;
+		 my $ai    = 0;
+		 my $ma    = -1;
+		 my $mb;
+
+		 while (1)
+		 {
+
+			  # Find next match indices $ma and $mb
+			  do {
+					$ma++;
+			  } while(
+						 $ma <= $#$matchVector
+					&&  !defined $matchVector->[$ma]
+			  );
+
+			  last if $ma > $#$matchVector;    # end of matchVector?
+			  $mb = $matchVector->[$ma];
+
+			  # Proceed with discard a/b or change events until
+			  # next match
+			  while ( $ai < $ma || $bi < $mb )
+			  {
+
+					if ( $ai < $ma && $bi < $mb )
+					{
+
+						 # Change
+						 if ( defined $changeCallback )
+						 {
+							  &$changeCallback( $ai++, $bi++, @_ );
+						 }
+						 else
+						 {
+							  &$discardACallback( $ai++, $bi, @_ );
+							  &$discardBCallback( $ai, $bi++, @_ );
+						 }
+					}
+					elsif ( $ai < $ma )
+					{
+						 &$discardACallback( $ai++, $bi, @_ );
+					}
+					else
+					{
+
+						 # $bi < $mb
+						 &$discardBCallback( $ai, $bi++, @_ );
+					}
+			  }
+
+			  # Match
+			  &$matchCallback( $ai++, $bi++, @_ );
+		 }
+
+		 while ( $ai <= $lastA || $bi <= $lastB )
+		 {
+			  if ( $ai <= $lastA && $bi <= $lastB )
+			  {
+
+					# Change
+					if ( defined $changeCallback )
+					{
+						 &$changeCallback( $ai++, $bi++, @_ );
+					}
+					else
+					{
+						 &$discardACallback( $ai++, $bi, @_ );
+						 &$discardBCallback( $ai, $bi++, @_ );
+					}
+			  }
+			  elsif ( $ai <= $lastA )
+			  {
+					&$discardACallback( $ai++, $bi, @_ );
+			  }
+			  else
+			  {
+
+					# $bi <= $lastB
+					&$discardBCallback( $ai, $bi++, @_ );
+			  }
+		 }
+
+		 return 1;
+	}
+
+	sub prepare
+	{
+		 my $a       = shift;    # array ref
+		 my $keyGen  = shift;    # code ref
+
+		 # set up code ref
+		 $keyGen = sub { $_[0] } unless defined($keyGen);
+
+		 return scalar _withPositionsOfInInterval( $a, 0, $#$a, $keyGen, @_ );
+	}
+
+	sub LCS
+	{
+		 my $a = shift;                  # array ref
+		 my $b = shift;                  # array ref or hash ref
+		 my $matchVector = _longestCommonSubsequence( $a, $b, 0, @_ );
+		 my @retval;
+		 my $i;
+		 for ( $i = 0 ; $i <= $#$matchVector ; $i++ )
+		 {
+			  if ( defined( $matchVector->[$i] ) )
+			  {
+					push ( @retval, $a->[$i] );
+			  }
+		 }
+		 return wantarray ? @retval : \@retval;
+	}
+
+	sub LCS_length
+	{
+		 my $a = shift;                          # array ref
+		 my $b = shift;                          # array ref or hash ref
+		 return _longestCommonSubsequence( $a, $b, 1, @_ );
+	}
+
+	sub LCSidx
+	{
+		 my $a= shift @_;
+		 my $b= shift @_;
+		 my $match= _longestCommonSubsequence( $a, $b, 0, @_ );
+		 my @am= grep defined $match->[$_], 0..$#$match;
+		 my @bm= @{$match}[@am];
+		 return \@am, \@bm;
+	}
+
+	sub compact_diff
+	{
+		 my $a= shift @_;
+		 my $b= shift @_;
+		 my( $am, $bm )= LCSidx( $a, $b, @_ );
+		 my @cdiff;
+		 my( $ai, $bi )= ( 0, 0 );
+		 push @cdiff, $ai, $bi;
+		 while( 1 ) {
+			  while(  @$am  &&  $ai == $am->[0]  &&  $bi == $bm->[0]  ) {
+					shift @$am;
+					shift @$bm;
+					++$ai, ++$bi;
+			  }
+			  push @cdiff, $ai, $bi;
+			  last   if  ! @$am;
+			  $ai = $am->[0];
+			  $bi = $bm->[0];
+			  push @cdiff, $ai, $bi;
+		 }
+		 push @cdiff, 0+@$a, 0+@$b
+			  if  $ai < @$a || $bi < @$b;
+		 return wantarray ? @cdiff : \@cdiff;
+	}
+
+	sub diff
+	{
+		 my $a      = shift;    # array ref
+		 my $b      = shift;    # array ref
+		 my $retval = [];
+		 my $hunk   = [];
+		 my $discard = sub {
+			  push @$hunk, [ '-', $_[0], $a->[ $_[0] ] ];
+		 };
+		 my $add = sub {
+			  push @$hunk, [ '+', $_[1], $b->[ $_[1] ] ];
+		 };
+		 my $match = sub {
+			  push @$retval, $hunk
+					if 0 < @$hunk;
+			  $hunk = []
+		 };
+		 traverse_sequences( $a, $b,
+			  { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ );
+		 &$match();
+		 return wantarray ? @$retval : $retval;
+	}
+
+	sub sdiff
+	{
+		 my $a      = shift;    # array ref
+		 my $b      = shift;    # array ref
+		 my $retval = [];
+		 my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) };
+		 my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) };
+		 my $change = sub {
+			  push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] );
+		 };
+		 my $match = sub {
+			  push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] );
+		 };
+		 traverse_balanced(
+			  $a,
+			  $b,
+			  {
+					MATCH     => $match,
+					DISCARD_A => $discard,
+					DISCARD_B => $add,
+					CHANGE    => $change,
+			  },
+			  @_
+		 );
+		 return wantarray ? @$retval : $retval;
+	}
+
+	########################################
+	my $Root= __PACKAGE__;
+	package Algorithm::Diff::_impl;
+	use strict;
+
+	sub _Idx()  { 0 } # $me->[_Idx]: Ref to array of hunk indices
+					# 1   # $me->[1]: Ref to first sequence
+					# 2   # $me->[2]: Ref to second sequence
+	sub _End()  { 3 } # $me->[_End]: Diff between forward and reverse pos
+	sub _Same() { 4 } # $me->[_Same]: 1 if pos 1 contains unchanged items
+	sub _Base() { 5 } # $me->[_Base]: Added to range's min and max
+	sub _Pos()  { 6 } # $me->[_Pos]: Which hunk is currently selected
+	sub _Off()  { 7 } # $me->[_Off]: Offset into _Idx for current position
+	sub _Min() { -2 } # Added to _Off to get min instead of max+1
+
+	sub Die
+	{
+		 require Carp;
+		 Carp::confess( @_ );
+	}
+
+	sub _ChkPos
+	{
+		 my( $me )= @_;
+		 return   if  $me->[_Pos];
+		 my $meth= ( caller(1) )[3];
+		 Die( "Called $meth on 'reset' object" );
+	}
+
+	sub _ChkSeq
+	{
+		 my( $me, $seq )= @_;
+		 return $seq + $me->[_Off]
+			  if  1 == $seq  ||  2 == $seq;
+		 my $meth= ( caller(1) )[3];
+		 Die( "$meth: Invalid sequence number ($seq); must be 1 or 2" );
+	}
+
+	sub getObjPkg
+	{
+		 my( $us )= @_;
+		 return ref $us   if  ref $us;
+		 return $us . "::_obj";
+	}
+
+	sub new
+	{
+		 my( $us, $seq1, $seq2, $opts ) = @_;
+		 my @args;
+		 for( $opts->{keyGen} ) {
+			  push @args, $_   if  $_;
+		 }
+		 for( $opts->{keyGenArgs} ) {
+			  push @args, @$_   if  $_;
+		 }
+		 my $cdif= Algorithm::Diff::compact_diff( $seq1, $seq2, @args );
+		 my $same= 1;
+		 if(  0 == $cdif->[2]  &&  0 == $cdif->[3]  ) {
+			  $same= 0;
+			  splice @$cdif, 0, 2;
+		 }
+		 my @obj= ( $cdif, $seq1, $seq2 );
+		 $obj[_End] = (1+@$cdif)/2;
+		 $obj[_Same] = $same;
+		 $obj[_Base] = 0;
+		 my $me = bless \@obj, $us->getObjPkg();
+		 $me->Reset( 0 );
+		 return $me;
+	}
+
+	sub Reset
+	{
+		 my( $me, $pos )= @_;
+		 $pos= int( $pos || 0 );
+		 $pos += $me->[_End]
+			  if  $pos < 0;
+		 $pos= 0
+			  if  $pos < 0  ||  $me->[_End] <= $pos;
+		 $me->[_Pos]= $pos || !1;
+		 $me->[_Off]= 2*$pos - 1;
+		 return $me;
+	}
+
+	sub Base
+	{
+		 my( $me, $base )= @_;
+		 my $oldBase= $me->[_Base];
+		 $me->[_Base]= 0+$base   if  defined $base;
+		 return $oldBase;
+	}
+
+	sub Copy
+	{
+		 my( $me, $pos, $base )= @_;
+		 my @obj= @$me;
+		 my $you= bless \@obj, ref($me);
+		 $you->Reset( $pos )   if  defined $pos;
+		 $you->Base( $base );
+		 return $you;
+	}
+
+	sub Next {
+		 my( $me, $steps )= @_;
+		 $steps= 1   if  ! defined $steps;
+		 if( $steps ) {
+			  my $pos= $me->[_Pos];
+			  my $new= $pos + $steps;
+			  $new= 0   if  $pos  &&  $new < 0;
+			  $me->Reset( $new )
+		 }
+		 return $me->[_Pos];
+	}
+
+	sub Prev {
+		 my( $me, $steps )= @_;
+		 $steps= 1   if  ! defined $steps;
+		 my $pos= $me->Next(-$steps);
+		 $pos -= $me->[_End]   if  $pos;
+		 return $pos;
+	}
+
+	sub Diff {
+		 my( $me )= @_;
+		 $me->_ChkPos();
+		 return 0   if  $me->[_Same] == ( 1 & $me->[_Pos] );
+		 my $ret= 0;
+		 my $off= $me->[_Off];
+		 for my $seq ( 1, 2 ) {
+			  $ret |= $seq
+					if  $me->[_Idx][ $off + $seq + _Min ]
+					<   $me->[_Idx][ $off + $seq ];
+		 }
+		 return $ret;
+	}
+
+	sub Min {
+		 my( $me, $seq, $base )= @_;
+		 $me->_ChkPos();
+		 my $off= $me->_ChkSeq($seq);
+		 $base= $me->[_Base] if !defined $base;
+		 return $base + $me->[_Idx][ $off + _Min ];
+	}
+
+	sub Max {
+		 my( $me, $seq, $base )= @_;
+		 $me->_ChkPos();
+		 my $off= $me->_ChkSeq($seq);
+		 $base= $me->[_Base] if !defined $base;
+		 return $base + $me->[_Idx][ $off ] -1;
+	}
+
+	sub Range {
+		 my( $me, $seq, $base )= @_;
+		 $me->_ChkPos();
+		 my $off = $me->_ChkSeq($seq);
+		 if( !wantarray ) {
+			  return  $me->[_Idx][ $off ]
+					-   $me->[_Idx][ $off + _Min ];
+		 }
+		 $base= $me->[_Base] if !defined $base;
+		 return  ( $base + $me->[_Idx][ $off + _Min ] )
+			  ..  ( $base + $me->[_Idx][ $off ] - 1 );
+	}
+
+	sub Items {
+		 my( $me, $seq )= @_;
+		 $me->_ChkPos();
+		 my $off = $me->_ChkSeq($seq);
+		 if( !wantarray ) {
+			  return  $me->[_Idx][ $off ]
+					-   $me->[_Idx][ $off + _Min ];
+		 }
+		 return
+			  @{$me->[$seq]}[
+						 $me->[_Idx][ $off + _Min ]
+					..  ( $me->[_Idx][ $off ] - 1 )
+			  ];
+	}
+
+	sub Same {
+		 my( $me )= @_;
+		 $me->_ChkPos();
+		 return wantarray ? () : 0
+			  if  $me->[_Same] != ( 1 & $me->[_Pos] );
+		 return $me->Items(1);
+	}
+
+	my %getName;
+		 %getName= (
+			  same => \&Same,
+			  diff => \&Diff,
+			  base => \&Base,
+			  min  => \&Min,
+			  max  => \&Max,
+			  range=> \&Range,
+			  items=> \&Items, # same thing
+		 );
+
+	sub Get
+	{
+		 my $me= shift @_;
+		 $me->_ChkPos();
+		 my @value;
+		 for my $arg (  @_  ) {
+			  for my $word (  split ' ', $arg  ) {
+					my $meth;
+					if(     $word !~ /^(-?\d+)?([a-zA-Z]+)([12])?$/
+						 ||  not  $meth= $getName{ lc $2 }
+					) {
+						 Die( $Root, ", Get: Invalid request ($word)" );
+					}
+					my( $base, $name, $seq )= ( $1, $2, $3 );
+					push @value, scalar(
+						 4 == length($name)
+							  ? $meth->( $me )
+							  : $meth->( $me, $seq, $base )
+					);
+			  }
+		 }
+		 if(  wantarray  ) {
+			  return @value;
+		 } elsif(  1 == @value  ) {
+			  return $value[0];
+		 }
+		 Die( 0+@value, " values requested from ",
+			  $Root, "'s Get in scalar context" );
+	}
+
+
+	my $Obj= getObjPkg($Root);
+	no strict 'refs';
+
+	for my $meth (  qw( new getObjPkg )  ) {
+		 *{$Root."::".$meth} = \&{$meth};
+		 *{$Obj ."::".$meth} = \&{$meth};
+	}
+	for my $meth (  qw(
+		 Next Prev Reset Copy Base Diff
+		 Same Items Range Min Max Get
+		 _ChkPos _ChkSeq
+	)  ) {
+		 *{$Obj."::".$meth} = \&{$meth};
+	}
+
+};
+{
+	package Algorithm::LCSS;
+
+	use strict;
+	{
+		no strict 'refs';
+		*traverse_sequences = \&Algorithm::Diff::traverse_sequences;
+	}
+
+	sub _tokenize { [split //, $_[0]] }
+
+	sub CSS {
+		 my $is_array = ref $_[0] eq 'ARRAY' ? 1 : 0;
+		 my ( $seq1, $seq2, @match, $from_match );
+		 my $i = 0;
+		 if ( $is_array ) {
+			  $seq1 = $_[0];
+			  $seq2 = $_[1];
+			  traverse_sequences( $seq1, $seq2, {
+					MATCH => sub { push @{$match[$i]}, $seq1->[$_[0]]; $from_match = 1 },
+					DISCARD_A => sub { do{$i++; $from_match = 0} if $from_match },
+					DISCARD_B => sub { do{$i++; $from_match = 0} if $from_match },
+			  });
+		 }
+		 else {
+			  $seq1 = _tokenize($_[0]);
+			  $seq2 = _tokenize($_[1]);
+			  traverse_sequences( $seq1, $seq2, {
+					MATCH => sub { $match[$i] .= $seq1->[$_[0]]; $from_match = 1 },
+					DISCARD_A => sub { do{$i++; $from_match = 0} if $from_match },
+					DISCARD_B => sub { do{$i++; $from_match = 0} if $from_match },
+			  });
+		 }
+	  return \@match;
+	}
+
+	sub CSS_Sorted {
+		 my $match = CSS(@_);
+		 if ( ref $_[0] eq 'ARRAY' ) {
+			 @$match = map{$_->[0]}sort{$b->[1]<=>$a->[1]}map{[$_,scalar(@$_)]}@$match
+		 }
+		 else {
+			 @$match = map{$_->[0]}sort{$b->[1]<=>$a->[1]}map{[$_,length($_)]}@$match
+		 }
+	  return $match;
+	}
+
+	sub LCSS {
+		 my $is_array = ref $_[0] eq 'ARRAY' ? 1 : 0;
+		 my $css = CSS(@_);
+		 my $index;
+		 my $length = 0;
+		 if ( $is_array ) {
+			  for( my $i = 0; $i < @$css; $i++ ) {
+					next unless @{$css->[$i]}>$length;
+					$index = $i;
+					$length = @{$css->[$i]};
+			  }
+		 }
+		 else {
+			  for( my $i = 0; $i < @$css; $i++ ) {
+					next unless length($css->[$i])>$length;
+					$index = $i;
+					$length = length($css->[$i]);
+			  }
+		 }
+	  return $css->[$index];
+	}
+
+};
+# }}}
+#{{{ Class::Classless module
+{
+	package Class::Classless;
+	use strict;
+	use vars qw(@ISA);
+	use Carp;
+
+	@ISA = ();
+
+	###########################################################################
+
+	@Class::Classless::X::ISA = ();
+
+	###########################################################################
+	###########################################################################
+
+	sub Class::Classless::X::AUTOLOAD {
+	  # This's the big dispatcher.
+	  
+	  my $it = shift @_;
+	  my $m =  ($Class::Classless::X::AUTOLOAD =~ m/([^:]+)$/s ) 
+					 ? $1 : $Class::Classless::X::AUTOLOAD;
+
+	  croak "Can't call Class::Classless methods (like $m) without an object"
+		 unless ref $it;  # sanity, basically.
+
+	  my $prevstate;
+	  $prevstate = ${shift @_}
+		if scalar(@_) && defined($_[0]) &&
+			ref($_[0]) eq 'Class::Classless::CALLSTATE::SHIMMY'
+	  ;   # A shim!  we were called via $callstate->NEXT
+
+	  my $no_fail = $prevstate ? $prevstate->[3] : undef;
+	  my $i       = $prevstate ? ($prevstate->[1] + 1) : 0;
+		# where to start scanning
+	  my $lineage;
+
+	  # Get the linearization of the ISA tree
+	  if($prevstate) {
+		 $lineage = $prevstate->[2];
+	  } elsif(defined $it->{'ISA_CACHE'} and ref $it->{'ISA_CACHE'} ){
+		 $lineage = $it->{'ISA_CACHE'};
+	  } else {
+		 $lineage = [ &Class::Classless::X::ISA_TREE($it) ];
+	  }
+
+	  # Was:
+	  #my @lineage =
+	  #  $prevstate ? @{$prevstate->[2]}
+	  #             : &Class::Classless::X::ISA_TREE($it);
+	  # # Get the linearization of the ISA tree
+	  # # ISA-memoization happens in the ISA_TREE function.
+	  
+	  for(; $i < @$lineage; ++$i) {
+
+		 if( !defined($no_fail) and exists($lineage->[$i]{'NO_FAIL'}) ) {
+			$no_fail = ($lineage->[$i]{'NO_FAIL'} || 0);
+			# so the first NO_FAIL sets it
+		 }
+
+		 if(     ref($lineage->[$i]{'METHODS'}     || 0)  # sanity
+			&& exists($lineage->[$i]{'METHODS'}{$m})
+		 ){
+			# We found what we were after.  Now see what to do with it.
+			my $v = $lineage->[$i]{'METHODS'}{$m};
+			return $v unless defined $v and ref $v;
+
+			if(ref($v) eq 'CODE') { # normal case, I expect!
+			  # Used to have copying of the arglist here.
+			  #  But it was apparently useless, so I deleted it
+			  unshift @_, 
+				 $it,                   # $_[0]    -- target object
+				 # a NEW callstate
+				 bless([$m, $i, $lineage, $no_fail, $prevstate ? 1 : 0],
+						 'Class::Classless::CALLSTATE'
+						),                # $_[1]    -- the callstate
+			  ;
+			  goto &{ $v }; # yes, magic goto!  bimskalabim!
+			}
+			return @$v if ref($v) eq '_deref_array';
+			return $$v if ref($v) eq '_deref_scalar';
+			return $v; # fallthru
+		 }
+	  }
+
+	  if($m eq 'DESTROY') { # mitigate DESTROY-lookup failure at global destruction
+		 # should be impossible
+	  } else {
+		 if($no_fail || 0) {
+			return;
+		 }
+		 croak "Can't find ", $prevstate ? 'NEXT method' : 'method',
+				 " $m in ", $it->{'NAME'} || $it,
+				 " or any ancestors\n";
+	  }
+	}
+
+	###########################################################################
+	###########################################################################
+
+	sub Class::Classless::X::DESTROY {
+	  # noop
+	}
+
+	###########################################################################
+	sub Class::Classless::X::ISA_TREE {
+	  # The linearizer!
+	  # Returns the search path for $_[0], starting with $_[0]
+	  # Possibly memoized.
+
+	  # I stopped being able to understand this algorithm about five
+	  #  minutes after I wrote it.
+	  use strict;
+	  
+	  my $set_cache = 0; # flag to set the cache on the way out
+	  
+	  if(exists($_[0]{'ISA_CACHE'})) {
+		 return    @{$_[0]{'ISA_CACHE'}}
+		  if defined $_[0]{'ISA_CACHE'}
+			  and ref $_[0]{'ISA_CACHE'};
+		  
+		 # Otherwise, if exists but is not a ref, it's a signal that it should
+		 #  be replaced at the earliest, with a listref
+		 $set_cache = 1;
+	  }
+	  
+	  my $has_mi = 0; # set to 0 on the first node we see with 2 parents!
+	  # First, just figure out what's in the tree.
+	  my %last_child = ($_[0] => 1); # as if already seen
+
+	  # if $last_child{$x} == $y, that means:
+	  #  1) incidentally, we've passed the node $x before.
+	  #  2) $x is the last child of $y,
+	  #     so that means that $y can be pushed to the stack only after
+	  #      we've pushed $x to the stack.
+	  
+	  my @tree_nodes;
+	  {
+		 my $current;
+		 my @in_stack = ($_[0]);
+		 while(@in_stack) {
+			next unless
+			 defined($current = shift @in_stack)
+			 && ref($current) # sanity
+			 && ref($current->{'PARENTS'} || 0) # sanity
+			;
+
+			push @tree_nodes, $current;
+
+			$has_mi = 1 if @{$current->{'PARENTS'}} > 1;
+			unshift
+			  @in_stack,
+			  map {
+				 if(exists $last_child{$_}) { # seen before!
+					$last_child{$_} = $current;
+					(); # seen -- don't re-explore
+				 } else { # first time seen
+					$last_child{$_} = $current;
+					$_; # first time seen -- explore now
+				 }
+			  }
+			  @{$current->{'PARENTS'}}
+			;
+		 }
+
+		 # If there was no MI, then that first scan was sufficient.
+		 unless($has_mi) {
+			$_[0]{'ISA_CACHE'} = \@tree_nodes if $set_cache;
+			return @tree_nodes;
+		 }
+
+		 # Otherwise, toss this list and rescan, consulting %last_child
+	  }
+
+	  # $last_child{$parent} holds the last (or only) child of $parent
+	  # in this tree.  When walking the tree this time, only that
+	  # child is authorized to put its parent on the @in_stack.
+	  # And that's the only way a node can get added to @in_stack,
+	  # except for $_[0] (the start node) being there at the beginning.
+
+	  # Now, walk again, but this time exploring parents the LAST
+	  # time seen in the tree, not the first.
+
+	  my @out;
+	  {
+		 my $current;
+		 my @in_stack = ($_[0]);
+		 while(@in_stack) {
+			next unless defined($current = shift @in_stack) && ref($current);
+			push @out, $current; # finally.
+			unshift
+			  @in_stack,
+			  grep(
+				 (
+					defined($_) # sanity
+					&& ref($_)  # sanity
+					&& $last_child{$_} eq $current,
+				 ),
+				 # I'm lastborn (or onlyborn) of this parent
+				 # so it's OK to explore now
+				 @{$current->{'PARENTS'}}
+			  )
+			 if ref($current->{'PARENTS'} || 0) # sanity
+			;
+		 }
+
+		 unless(scalar(@out) == scalar(keys(%last_child))) {
+			# the counts should be equal
+			my %good_ones;
+			@good_ones{@out} = ();
+			croak
+			  "ISA tree for " .
+			  ($_[0]{'NAME'} || $_[0]) .
+			  " is apparently cyclic, probably involving the nodes " .
+			  nodelist( grep { ref($_) && !exists $good_ones{$_} }
+				 values(%last_child) )
+			  . "\n";
+		 }
+	  }
+	  #print "Contents of out: ", nodelist(@out), "\n";
+	  
+	  $_[0]{'ISA_CACHE'} = \@out if $set_cache;
+	  return @out;
+	}
+
+	###########################################################################
+
+	sub Class::Classless::X::can { # NOT like UNIVERSAL::can ...
+	  # return 1 if $it is capable of the method given -- otherwise 0
+	  my($it, $m) = @_[0,1];
+	  return undef unless ref $it;
+
+	  croak "undef is not a valid method name"       unless defined($m);
+	  croak "null-string is not a valid method name" unless length($m);
+
+	  foreach my $o (&Class::Classless::X::ISA_TREE($it)) {
+		 return 1
+		  if  ref($o->{'METHODS'} || 0)   # sanity
+			&& exists $o->{'METHODS'}{$m};
+	  }
+
+	  return 0;
+	}
+
+
+	###########################################################################
+
+	sub Class::Classless::X::isa { # Like UNIVERSAL::isa
+	  # Returns true for $X->isa($Y) iff $Y is $X or is an ancestor of $X.
+
+	  return unless ref($_[0]) && ref($_[1]);
+	  return scalar(grep {$_ eq $_[1]} &Class::Classless::X::ISA_TREE($_[0])); 
+	}
+
+	###########################################################################
+
+	sub nodelist { join ', ', map { "" . ($_->{'NAME'} || $_) . ""} @_ }
+
+	###########################################################################
+	###########################################################################
+	###########################################################################
+	# Methods for the CALLSTATE class.
+	#  Basically, CALLSTATE objects represent the state of the dispatcher,
+	#   frozen at the moment when the method call was dispatched to the
+	#   appropriate sub.
+	#  In the grand scheme of things, this needn't be a class -- I could
+	#   have just made the callstate data-object be a hash with documented
+	#   keys, or a closure that responded to only certain parameters,
+	#   etc.  But I like it this way.  And I like being able to say simply
+	#   $cs->NEXT
+	#  Yes, these are a bit cryptically written, but it's behoovy for
+	#   them to be very very efficient.
+
+	@Class::Classless::ISA = ();
+	sub Class::Classless::CALLSTATE::found_name { $_[0][0] }
+		#  the method name called and found
+	sub Class::Classless::CALLSTATE::found_depth { $_[0][1] }
+		#  my depth in the lineage
+	sub Class::Classless::CALLSTATE::lineage { @{$_[0][2]} }
+		#  my lineage
+	sub Class::Classless::CALLSTATE::target { $_[0][2][  0          ] }
+		#  the object that's the target -- same as $_[0] for the method called
+	sub Class::Classless::CALLSTATE::home   { $_[0][2][  $_[0][1]   ] }
+		#  the object I was found in
+	sub Class::Classless::CALLSTATE::sub_found {
+	  $_[0][2][  $_[0][1]   ]{'METHODS'}{ $_[0][0] }
+	}  #  the routine called
+
+	sub Class::Classless::CALLSTATE::no_fail          {  $_[0][3]         }
+	sub Class::Classless::CALLSTATE::set_no_fail_true {  $_[0][3] = 1     }
+	sub Class::Classless::CALLSTATE::set_fail_false   {  $_[0][3] = 0     }
+	sub Class::Classless::CALLSTATE::set_fail_undef   {  $_[0][3] = undef }
+
+	sub Class::Classless::CALLSTATE::via_next         {  $_[0][4] }
+
+	sub Class::Classless::CALLSTATE::NEXT {
+	  #croak "NEXT needs at least one argument: \$cs->NEXT('method'...)"
+	  # unless @_ > 1;
+		# no longer true.
+	  my $cs = shift @_;
+	  my $m  = shift @_; # which may be (or come out) undef...
+	  $m = $cs->[0] unless defined $m; #  the method name called and found
+
+	  ($cs->[2][0])->$m(
+		 bless( \$cs, 'Class::Classless::CALLSTATE::SHIMMY' ),
+		 @_
+	  );
+	}
+
+	###########################################################################
+};
+#}}}
+
+###############
+###
+#
+# {{{ *** C h a n g e l o g ***
+#
+# 0.6ca
+# - add screen support (from nicklist.pl)
+# - rename to adv_windowlist.pl (advanced window list) since it isn't just a
+#   window list status bar (wlstat) anymore
+# - names can now have a max length and window names can be used
+# - fixed a bug with block display in screen mode and statusbar mode
+# - added space handling to ir_fe and removed it again
+# - now handling formats on my own
+# - added warning about missing sb_act_none abstract leading to
+# - display*active settings
+# - added warning about the bug in awl_display_(no)key_active settings
+#
+# 0.5d
+# - add setting to also hide the last statusbar if empty (awl_all_disable)
+# - reverted to old utf8 code to also calculate broken utf8 length correctly
+# - simplified dealing with statusbars in wlreset
+# - added a little tweak for the renamed term_type somewhere after Irssi 0.8.9
+# - fixed bug in handling channel #$$
+# - typo on line 200 spotted by f0rked
+# - reset background colour at the beginning of an entry
+# 
+# 0.4d
+# - fixed order of disabling statusbars
+# - several attempts at special chars, without any real success
+#   and much more weird new bugs caused by this
+# - setting to specify sort order
+# - reduced timeout values
+# - added awl_hide_data for Geert Hauwaerts ( [email protected] ) :)
+# - make it so the dynamic sub is actually deleted
+# - fix a bug with removing of the last separator
+# - take into consideration parse_special
+# 
+# 0.3b
+# - automatically kill old statusbars
+# - reset on /reload
+# - position/placement settings
+#
+# 0.2
+# - automated retrieval of key bindings (thanks grep.pl authors)
+# - improved removing of statusbars
+# - got rid of status chop
+#
+# 0.1
+# - rewritten to suit my needs
+# - based on chanact 0.5.5
+# }}}
+# vim: se fdm=marker tw=80 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/scripts/autorun/adv_windowlist.pl	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,1 @@
+../adv_windowlist.pl
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/scripts/autorun/cap_sasl.pl	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,1 @@
+../cap_sasl.pl
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/scripts/autorun/hilightwin.pl	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,1 @@
+../hilightwin.pl
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/scripts/autorun/nicklist.pl	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,1 @@
+../nicklist.pl
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/scripts/autorun/nm.pl	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,1 @@
+../nm.pl
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/scripts/autorun/queryresume.pl	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,1 @@
+../queryresume.pl
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/scripts/autorun/trackbar.pl	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,1 @@
+../trackbar.pl
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/scripts/bitlbee_typing_notice.pl	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,319 @@
+# INSTALLATION
+# [&bitlbee] set typing_notice true
+# <@root> typing_notice = `true'
+# AND
+# /statusbar window add typing_notice
+#
+# SETTINGS
+# [bitlbee]
+# bitlbee_send_typing = ON
+#   -> send typing messages to buddies
+# bitlbee_typing_allwin = OFF
+#   -> show typing notifications in all windows
+#
+# 
+# Changelog:
+#
+# 2010-08-09 (version 1.7.1)
+# * Multiple control channels supported by checking chanmodes
+#
+# 2010-07-27 (version 1.7)
+# * Using new server detection for latest BitlBee support
+#
+# 2010-07-26 (version 1.6.3)
+# * Removed checking if nicks exists in &bitlbee channel, this because BitlBee 
+#   can be used without control channel from this date
+#
+# 2007-03-03 (version 1.6.2)
+# * Fix: timers weren't deleted correctly. This resulted in huge mem usage.
+#
+# 2006-11-02 (version 1.6.1)
+# * Sending typing works again.
+#
+# 2006-10-27 (version 1.6)
+# * 'channel sync' re-implemented.
+# * bitlbee_send_typing was a string setting, It's a boolean now, like it should.
+#
+# 2006-10-24 (version 1.5)
+# * Sending notices to online users only. ( removed this again at 2010-07-26, see above )
+# * Using the new get_channel function;
+#
+# 2005-12-15 (version 1.42):
+# * Fixed small bug with typing notices disappearing under certain circumstances
+#   in channels
+# * Fixed bug that caused outgoing notifications not to work 
+# * root cares not about our typing status.
+#
+# 2005-12-04 (version 1.41):
+# * Implemented stale states in statusbar (shows "(stale)" for OSCAR connections) 
+# * Introduced bitlbee_typing_allwin (default OFF). Set this to ON to make
+#   typing notifications visible in all windows.
+#
+# 2005-12-03 (version 1.4):
+# * Major code cleanups and rewrites for bitlbee 1.0 with the updated typing
+#   scheme. TYPING 0, TYPING 1, and TYPING 2 are now supported from the server.
+# * Stale states (where user has typed in text but has stopped typing) are now
+#   recognized.
+# * Bug where user thinks you are still typing if you close the window after
+#   typing something and then erasing it quickly.. fixed.
+# * If a user signs off while they are still typing, the notification is removed
+# This update by Matt "f0rked" Sparks
+#
+# 2005-08-26:
+# Some fixes for AIM, Thanks to Dracula.
+#
+# 2005-08-16:
+# AIM supported, for sending notices, using CTCP TYPING 0. (Use the AIM patch from Hanji http://get.bitlbee.org/patches/)
+# 
+# 2004-10-31:
+# Sends typing notice to the bitlbee server when typing a message in irssi. bitlbee > 0.92
+#
+# 2004-06-11:
+# shows [typing: ] in &bitlbee with multiple users.
+#
+use strict;
+use Irssi::TextUI;
+use Data::Dumper;
+
+use vars qw($VERSION %IRSSI);
+
+$VERSION = '1.7.1';
+%IRSSI = (
+	authors	 	=> 'Tijmen "timing" Ruizendaal, Matt "f0rked" Sparks',
+	contact		=> '[email protected], [email protected]',
+	name		=> 'BitlBee_typing_notice',
+	description	=> '1. Adds an item to the status bar wich shows [typing] when someone is typing a message on the supported IM-networks	2. Sends typing notices to the supported IM networks (the other way arround). (For bitlbee 3.0+)',
+	license	 	=> 'GPLv2',
+	url		=> 'http://the-timing.nl/stuff/irssi-bitlbee, http://f0rked.com',
+	changed	 	=> '2010-08-09',
+);
+
+my $bitlbee_server; # server object
+my @control_channels; # mostly: &bitlbee, &facebook etc.
+init();
+
+sub init { # if script is loaded after connect
+	my @servers = Irssi::servers();
+	foreach my $server(@servers) {
+		if( $server->isupport('NETWORK') eq 'BitlBee' ){
+			$bitlbee_server = $server;
+			my @channels = $server->channels();
+			foreach my $channel(@channels) {
+				if( $channel->{mode} =~ /C/ ){
+					push @control_channels, $channel->{name} unless (grep $_ eq $channel->{name}, @control_channels);
+				}
+			}
+		}
+	}
+}
+# if connect after script is loaded
+Irssi::signal_add_last('event 005' => sub {
+	my( $server ) = @_;
+	if( $server->isupport('NETWORK') eq 'BitlBee' ){
+		$bitlbee_server = $server;
+	}
+});
+# if new control channel is synced after script is loaded
+Irssi::signal_add_last('channel sync' => sub {
+	my( $channel ) = @_;
+	if( $channel->{mode} =~ /C/ && $channel->{server}->{tag} eq $bitlbee_server->{tag} ){
+		push @control_channels, $channel->{name} unless (grep $_ eq $channel->{name}, @control_channels);
+	}
+});
+
+# How often to check if we are typing, or on msn,
+# how long to keep the typing notice up, or check
+# if the other user is still typing...
+my $KEEP_TYPING_TIMEOUT = 1;
+my $STOP_TYPING_TIMEOUT = 7;
+
+my %timer_tag;
+
+my %typing;
+my %tag;
+my $line;
+my %out_typing;
+my $lastkey;
+my $keylog_active = 1;
+my $command_char = Irssi::settings_get_str('cmdchars'); # mostly: /
+my $to_char = Irssi::settings_get_str("completion_char"); # mostly: :
+
+sub event_ctcp_msg {
+	my ($server, $msg, $from, $address) = @_;
+	return if $server->{tag} ne $bitlbee_server->{tag};
+	if ( my($type) = $msg =~ "TYPING ([0-9])" ){
+		Irssi::signal_stop();
+		if( $type == 0 ){
+			unset_typing($from);
+		} elsif( $type == 1 ){
+			$typing{$from}=1;
+			if( $address !~ /\@login\.oscar\.aol\.com/ and $address !~ /\@YAHOO/ and $address !~ /\@login\.icq\.com/ ){
+				Irssi::timeout_remove($tag{$from});
+				delete($tag{$from});
+				$tag{$from}=Irssi::timeout_add_once($STOP_TYPING_TIMEOUT*1000,"unset_typing",$from);
+			}
+			redraw($from);
+		} elsif( $type == 2 ){
+			stale_typing($from);
+		}
+	}
+}
+
+sub unset_typing {
+	my($from,$no_redraw)=@_;
+	delete $typing{$from} if $typing{$from};
+	Irssi::timeout_remove($tag{$from});
+	delete($tag{$from});
+	redraw($from) if !$no_redraw;
+}
+
+sub stale_typing {
+	my($from)=@_;
+	$typing{$from}=2;
+	redraw($from);
+}
+
+sub redraw {
+	my($from)=@_;
+	my $window = Irssi::active_win();
+	my $name = $window->get_active_name();
+	
+	# only redraw if current window equals to the typing person, is a control channel or if allwin is set
+	if( $from eq $name || (grep $_ eq $name, @control_channels) || Irssi::settings_get_bool("bitlbee_typing_allwin") ){
+		Irssi::statusbar_items_redraw('typing_notice');
+	}
+}	
+
+sub event_msg {
+	my ($server,$data,$from,$address,$target) = @_;
+	return if $server->{tag} ne $bitlbee_server->{tag};
+	my $channel=Irssi::active_win()->get_active_name();
+	unset_typing $from, "no redraw";
+	unset_typing $channel;
+}
+
+sub event_quit {
+	my $server = shift;
+	return if $server->{tag} ne $bitlbee_server->{tag};
+	my $nick = shift;
+	unset_typing $nick;
+}
+
+sub typing_notice {
+	my ($item, $get_size_only) = @_;
+	my $window = Irssi::active_win();
+	my $channel = $window->get_active_name();
+	
+	if (exists($typing{$channel})) {
+		my $append=$typing{$channel}==2 ? " (stale)" : "";
+		$item->default_handler($get_size_only, "{sb typing$append}", 0, 1);
+	} else {
+		$item->default_handler($get_size_only, "", 0, 1);
+		Irssi::timeout_remove($tag{$channel});
+		delete($tag{$channel});
+	}
+	# we check for correct windows again, because the statusbar item is redrawn after window change too.
+	if( (grep $_ eq $channel, @control_channels) || Irssi::settings_get_bool("bitlbee_typing_allwin")) {
+		foreach my $key (keys(%typing)) {
+			$line .= " ".$key;
+			if ($typing{$key}==2) { $line .= " (stale)"; }
+		}
+		if ($line ne "") {
+			$item->default_handler($get_size_only, "{sb typing:$line}", 0, 1);
+			$line = "";
+		}
+	} 
+}
+
+sub window_change {
+	Irssi::statusbar_items_redraw('typing_notice');
+	my $win = !Irssi::active_win() ? undef : Irssi::active_win()->{active};
+	if (ref $win && ($win->{server}->{tag} eq $bitlbee_server->{tag})) {
+		if (!$keylog_active) {
+			$keylog_active = 1;
+			Irssi::signal_add_last('gui key pressed', 'key_pressed');
+		}
+	} else {
+		if ($keylog_active) {
+			$keylog_active = 0;
+			Irssi::signal_remove('gui key pressed', 'key_pressed');
+		}
+	}
+}
+
+sub key_pressed {
+	return if !Irssi::settings_get_bool("bitlbee_send_typing");
+	my $key = shift;
+	if ($key != 9 && $key != 10 && $lastkey != 27 && $key != 27 
+	   && $lastkey != 91 && $key != 126 && $key != 127) 
+	{
+		my $server = Irssi::active_server();
+		my $window = Irssi::active_win();
+		my $nick = $window->get_active_name();
+
+		if ($server->{tag} eq $bitlbee_server->{tag} && $nick ne "(status)" && $nick ne "root") {
+			if( grep $_ eq $nick, @control_channels ){ # send typing if in control channel
+				my $input = Irssi::parse_special("\$L");
+				my ($first_word) = split(/ /,$input);
+				if ($input !~ /^$command_char.*/ && $first_word =~ s/$to_char$//){
+					send_typing($first_word);
+				}
+			} else { # or any other channels / query
+				my $input = Irssi::parse_special("\$L");
+				if ($input !~ /^$command_char.*/ && length($input) > 0){
+					send_typing($nick);
+				}
+			}
+		}
+	}
+	$lastkey = $key;
+}
+
+sub out_empty {
+	my ($a) = @_;
+	my($nick,$tag)=@{$a};
+	delete($out_typing{$nick});
+	Irssi::timeout_remove($timer_tag{$nick});
+	delete($timer_tag{$nick});
+	$bitlbee_server->command("^CTCP $nick TYPING 0");
+}
+
+sub send_typing {
+	my $nick = shift;
+	if (!exists($out_typing{$nick}) || time - $out_typing{$nick} > $KEEP_TYPING_TIMEOUT) {
+		$bitlbee_server->command("^CTCP $nick TYPING 1");
+		$out_typing{$nick} = time;
+		### Reset 'stop-typing' timer
+		Irssi::timeout_remove($timer_tag{$nick});
+		delete($timer_tag{$nick});
+
+		### create new timer
+		$timer_tag{$nick} = Irssi::timeout_add_once($STOP_TYPING_TIMEOUT*1000, 'out_empty', ["$nick", $bitlbee_server->{tag}]);
+	}
+}
+
+#README: Delete the old bitlbee_send_typing string from ~/.irssi/config. A boolean is better.
+
+sub db_typing { 
+	print "Detected channels: ";
+	print Dumper(@control_channels);
+	print "Detected server tag: ".$bitlbee_server->{tag};
+	print "Tag: ".Dumper(%tag);	
+	print "Timer Tag: ".Dumper(%timer_tag);	
+	print "Typing: ".Dumper(%typing);	
+	print "Out Typing: ".Dumper(%out_typing);	
+}
+
+Irssi::command_bind('db_typing','db_typing');
+
+Irssi::settings_add_bool("bitlbee","bitlbee_send_typing",1);
+Irssi::settings_add_bool("bitlbee","bitlbee_typing_allwin",0);
+
+Irssi::signal_add("ctcp msg", "event_ctcp_msg");
+Irssi::signal_add("message private", "event_msg");
+Irssi::signal_add("message public", "event_msg");
+Irssi::signal_add("message quit", "event_quit");
+Irssi::signal_add_last('window changed', 'window_change');
+Irssi::signal_add_last('gui key pressed', 'key_pressed');
+Irssi::statusbar_item_register('typing_notice', undef, 'typing_notice');
+Irssi::statusbars_recreate_items();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/scripts/cap_sasl.pl	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,272 @@
+use strict;
+use Irssi;
+use vars qw($VERSION %IRSSI);
+# $Id$
+
+use MIME::Base64;
+
+$VERSION = "1.1";
+
+%IRSSI = (
+    authors     => 'Michael Tharp and Jilles Tjoelker',
+    contact     => '[email protected]',
+    name        => 'cap_sasl.pl',
+    description => 'Implements PLAIN SASL authentication mechanism for use with charybdis ircds, and enables CAP MULTI-PREFIX',
+    license     => 'GNU General Public License',
+    url         => 'http://sasl.charybdis.be/',
+);
+
+my %sasl_auth = ();
+my %mech = ();
+
+sub timeout;
+
+sub server_connected {
+	my $server = shift;
+	$server->send_raw_now("CAP LS");
+}
+
+sub event_cap {
+	my ($server, $args, $nick, $address) = @_;
+	my ($subcmd, $caps, $tosend);
+
+	$tosend = '';
+	if ($args =~ /^\S+ (\S+) :(.*)$/) {
+		$subcmd = uc $1;
+		$caps = ' '.$2.' ';
+		if ($subcmd eq 'LS') {
+			$tosend .= ' multi-prefix' if $caps =~ / multi-prefix /i;
+			$tosend .= ' sasl' if $caps =~ / sasl /i && defined($sasl_auth{$server->{tag}});
+			$tosend =~ s/^ //;
+			$server->print('', "CLICAP: supported by server:$caps");
+			if (!$server->{connected}) {
+				if ($tosend eq '') {
+					$server->send_raw_now("CAP END");
+				} else {
+					$server->print('', "CLICAP: requesting: $tosend");
+					$server->send_raw_now("CAP REQ :$tosend");
+				}
+			}
+			Irssi::signal_stop();
+		} elsif ($subcmd eq 'ACK') {
+			$server->print('', "CLICAP: now enabled:$caps");
+			if ($caps =~ / sasl /i) {
+				$sasl_auth{$server->{tag}}{buffer} = '';
+				if($mech{$sasl_auth{$server->{tag}}{mech}}) {
+					$server->send_raw_now("AUTHENTICATE " . $sasl_auth{$server->{tag}}{mech});
+					Irssi::timeout_add_once(5000, \&timeout, $server->{tag});
+				}else{
+					$server->print('', 'SASL: attempted to start unknown mechanism "' . $sasl_auth{$server->{tag}}{mech} . '"');
+				}
+			}
+			elsif (!$server->{connected}) {
+				$server->send_raw_now("CAP END");
+			}
+			Irssi::signal_stop();
+		} elsif ($subcmd eq 'NAK') {
+			$server->print('', "CLICAP: refused:$caps");
+			if (!$server->{connected}) {
+				$server->send_raw_now("CAP END");
+			}
+			Irssi::signal_stop();
+		} elsif ($subcmd eq 'LIST') {
+			$server->print('', "CLICAP: currently enabled:$caps");
+			Irssi::signal_stop();
+		}
+	}
+}
+
+sub event_authenticate {
+	my ($server, $args, $nick, $address) = @_;
+	my $sasl = $sasl_auth{$server->{tag}};
+	return unless $sasl && $mech{$sasl->{mech}};
+
+	$sasl->{buffer} .= $args;
+	return if length($args) == 400;
+
+	my $data = $sasl->{buffer} eq '+' ? '' : decode_base64($sasl->{buffer});
+	my $out = $mech{$sasl->{mech}}($sasl, $data);
+	$out = '' unless defined $out;
+	$out = $out eq '' ? '+' : encode_base64($out, '');
+
+	while(length $out >= 400) {
+		my $subout = substr($out, 0, 400, '');
+		$server->send_raw_now("AUTHENTICATE $subout");
+	}
+	if(length $out) {
+		$server->send_raw_now("AUTHENTICATE $out");
+	}else{ # Last piece was exactly 400 bytes, we have to send some padding to indicate we're done
+		$server->send_raw_now("AUTHENTICATE +");
+	}
+
+	$sasl->{buffer} = '';
+	Irssi::signal_stop();
+}
+
+sub event_saslend {
+	my ($server, $args, $nick, $address) = @_;
+
+	my $data = $args;
+	$data =~ s/^\S+ :?//;
+	# need this to see it, ?? -- jilles
+	$server->print('', $data);
+	if (!$server->{connected}) {
+		$server->send_raw_now("CAP END");
+	}
+}
+
+sub timeout {
+	my $tag = shift;
+	my $server = Irssi::server_find_tag($tag);
+	if(!$server->{connected}) {
+		$server->print('', "SASL: authentication timed out");
+		$server->send_raw_now("CAP END");
+	}
+}
+
+sub cmd_sasl {
+	my ($data, $server, $item) = @_;
+
+	if ($data ne '') {
+		Irssi::command_runsub ('sasl', $data, $server, $item);
+	} else {
+		cmd_sasl_show(@_);
+	}
+}
+
+sub cmd_sasl_set {
+	my ($data, $server, $item) = @_;
+
+	if (my($net, $u, $p, $m) = $data =~ /^(\S+) (\S+) (\S+) (\S+)$/) {
+		if($mech{uc $m}) {
+			$sasl_auth{$net}{user} = $u;
+			$sasl_auth{$net}{password} = $p;
+			$sasl_auth{$net}{mech} = uc $m;
+			Irssi::print("SASL: added $net: [$m] $sasl_auth{$net}{user} *");
+		}else{
+			Irssi::print("SASL: unknown mechanism $m");
+		}
+	} elsif ($data =~ /^(\S+)$/) {
+		$net = $1;
+		if (defined($sasl_auth{$net})) {
+			delete $sasl_auth{$net};
+			Irssi::print("SASL: deleted $net");
+		} else {
+			Irssi::print("SASL: no entry for $net");
+		}
+	} else {
+		Irssi::print("SASL: usage: /sasl set <net> <user> <password or keyfile> <mechanism>");
+	}
+}
+
+sub cmd_sasl_show {
+	#my ($data, $server, $item) = @_;
+	my $net;
+	my $count = 0;
+
+	foreach $net (keys %sasl_auth) {
+		Irssi::print("SASL: $net: [$sasl_auth{$net}{mech}] $sasl_auth{$net}{user} *");
+		$count++;
+	}
+	Irssi::print("SASL: no networks defined") if !$count;
+}
+
+sub cmd_sasl_save {
+	#my ($data, $server, $item) = @_;
+	my $file = Irssi::get_irssi_dir()."/sasl.auth";
+	open FILE, "> $file" or return;
+	foreach my $net (keys %sasl_auth) {
+		printf FILE ("%s\t%s\t%s\t%s\n", $net, $sasl_auth{$net}{user}, $sasl_auth{$net}{password}, $sasl_auth{$net}{mech});
+	}
+	close FILE;
+	Irssi::print("SASL: auth saved to $file");
+}
+
+sub cmd_sasl_load {
+	#my ($data, $server, $item) = @_;
+	my $file = Irssi::get_irssi_dir()."/sasl.auth";
+
+	open FILE, "< $file" or return;
+	%sasl_auth = ();
+	while (<FILE>) {
+		chomp;
+		my ($net, $u, $p, $m) = split (/\t/, $_, 4);
+		$m ||= "PLAIN";
+		if($mech{uc $m}) {
+			$sasl_auth{$net}{user} = $u;
+			$sasl_auth{$net}{password} = $p;
+			$sasl_auth{$net}{mech} = uc $m;
+		}else{
+			Irssi::print("SASL: unknown mechanism $m");
+		}
+	}
+	close FILE;
+	Irssi::print("SASL: auth loaded from $file");
+}
+
+sub cmd_sasl_mechanisms {
+	Irssi::print("SASL: mechanisms supported: " . join(" ", keys %mech));
+}
+
+Irssi::signal_add_first('server connected', \&server_connected);
+Irssi::signal_add('event cap', \&event_cap);
+Irssi::signal_add('event authenticate', \&event_authenticate);
+Irssi::signal_add('event 903', 'event_saslend');
+Irssi::signal_add('event 904', 'event_saslend');
+Irssi::signal_add('event 905', 'event_saslend');
+Irssi::signal_add('event 906', 'event_saslend');
+Irssi::signal_add('event 907', 'event_saslend');
+
+Irssi::command_bind('sasl', \&cmd_sasl);
+Irssi::command_bind('sasl load', \&cmd_sasl_load);
+Irssi::command_bind('sasl save', \&cmd_sasl_save);
+Irssi::command_bind('sasl set', \&cmd_sasl_set);
+Irssi::command_bind('sasl show', \&cmd_sasl_show);
+Irssi::command_bind('sasl mechanisms', \&cmd_sasl_mechanisms);
+
+$mech{PLAIN} = sub {
+	my($sasl, $data) = @_;
+	my $u = $sasl->{user};
+	my $p = $sasl->{password};
+
+	join("\0", $u, $u, $p);
+};
+
+eval {
+	use Crypt::OpenSSL::Bignum;
+	use Crypt::DH;
+	use Crypt::Blowfish;
+	use Math::BigInt;
+	sub bin2bi { return Crypt::OpenSSL::Bignum->new_from_bin(shift)->to_decimal } # binary to BigInt
+	sub bi2bin { return Crypt::OpenSSL::Bignum->new_from_decimal((shift)->bstr)->to_bin } # BigInt to binary
+	$mech{'DH-BLOWFISH'} = sub {
+		my($sasl, $data) = @_;
+		my $u = $sasl->{user};
+		my $pass = $sasl->{password};
+
+		# Generate private key and compute secret key
+		my($p, $g, $y) = unpack("(n/a*)3", $data);
+		my $dh = Crypt::DH->new(p => bin2bi($p), g => bin2bi($g));
+		$dh->generate_keys;
+
+		my $secret = bi2bin($dh->compute_secret(bin2bi($y)));
+		my $pubkey = bi2bin($dh->pub_key);
+
+		# Pad the password to the nearest multiple of blocksize and encrypt
+		$pass .= "\0";
+		$pass .= chr(rand(256)) while length($pass) % 8;
+
+		my $cipher = Crypt::Blowfish->new($secret);
+		my $crypted = '';
+		while(length $pass) {
+			my $clear = substr($pass, 0, 8, '');
+			$crypted .= $cipher->encrypt($clear);
+		}
+
+		pack("n/a*Z*a*", $pubkey, $u, $crypted);
+	};
+};
+
+cmd_sasl_load();
+
+# vim: ts=4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/scripts/hilightwin.pl	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,57 @@
+#
+# Print hilighted messages & private messages to window named "hilight" for
+# irssi 0.7.99 by Timo Sirainen
+#
+# Modded a tiny bit by znx to stop private messages entering the hilighted
+# window (can be toggled) and to put up a timestamp.
+#
+
+use Irssi;
+use POSIX;
+use vars qw($VERSION %IRSSI); 
+
+$VERSION = "0.02";
+%IRSSI = (
+    authors     => "Timo \'cras\' Sirainen, Mark \'znx\' Sangster",
+    contact     => "tss\@iki.fi, znxster\@gmail.com", 
+    name        => "hilightwin",
+    description => "Print hilighted messages to window named \"hilight\"",
+    license     => "Public Domain",
+    url         => "http://irssi.org/",
+    changed     => "Sun May 25 18:59:57 BST 2008"
+);
+
+sub sig_printtext {
+    my ($dest, $text, $stripped) = @_;
+
+    my $opt = MSGLEVEL_HILIGHT;
+
+    if(Irssi::settings_get_bool('hilightwin_showprivmsg')) {
+        $opt = MSGLEVEL_HILIGHT|MSGLEVEL_MSGS;
+    }
+    
+    if(
+        ($dest->{level} & ($opt)) &&
+        ($dest->{level} & MSGLEVEL_NOHILIGHT) == 0
+    ) {
+        $window = Irssi::window_find_name('hilight');
+        
+        if ($dest->{level} & MSGLEVEL_PUBLIC) {
+            $text = $dest->{target}.": ".$text;
+        }
+        $text = strftime(
+            Irssi::settings_get_str('timestamp_format')." ",
+            localtime
+        ).$text;
+        $window->print($text, MSGLEVEL_NEVER) if ($window);
+    }
+}
+
+$window = Irssi::window_find_name('hilight');
+Irssi::print("Create a window named 'hilight'") if (!$window);
+
+Irssi::settings_add_bool('hilightwin','hilightwin_showprivmsg',1);
+
+Irssi::signal_add('print text', 'sig_printtext');
+
+# vim:set ts=4 sw=4 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/scripts/nicklist.pl	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,611 @@
+# for documentation: see http://wouter.coekaerts.be/site/irssi/nicklist
+
+use Irssi;
+use strict;
+use IO::Handle; # for (auto)flush
+use Fcntl; # for sysopen
+use vars qw($VERSION %IRSSI);
+$VERSION = '0.4.6';
+%IRSSI = (
+	authors     => 'Wouter Coekaerts',
+	contact     => '[email protected]',
+	name        => 'nicklist',
+	description => 'draws a nicklist to another terminal, or at the right of your irssi in the same terminal',
+	license     => 'GPLv2',
+	url         => 'http://wouter.coekaerts.be/irssi',
+	changed     => '29/06/2004'
+);
+
+sub cmd_help {
+	print ( <<EOF
+Commands:
+NICKLIST HELP
+NICKLIST SCROLL <nr of lines>
+NICKLIST SCREEN
+NICKLIST FIFO
+NICKLIST OFF
+NICKLIST UPDATE
+
+For help see: http://wouter.coekaerts.be/site/irssi/nicklist
+
+in short:
+
+1. FIFO MODE
+- in irssi: /NICKLIST FIFO (only the first time, to create the fifo)
+- in a shell, in a window where you want the nicklist: cat ~/.irssi/nicklistfifo
+- back in irssi:
+    /SET nicklist_heigth <height of nicklist>
+    /SET nicklist_width <width of nicklist>
+    /NICKLIST FIFO
+
+2. SCREEN MODE
+- start irssi inside screen ("screen irssi")
+- /NICKLIST SCREEN
+EOF
+    );
+}
+
+my $prev_lines = 0;                  # number of lines in previous written nicklist
+my $scroll_pos = 0;                  # scrolling position
+my $cursor_line;                     # line the cursor is currently on
+my ($OFF, $SCREEN, $FIFO) = (0,1,2); # modes
+my $mode = $OFF;                     # current mode
+my $need_redraw = 0;                 # nicklist needs redrawing
+my $screen_resizing = 0;             # terminal is being resized
+my $active_channel;                  # (REC)
+
+my @nicklist=();                     # array of hashes, containing the internal nicklist of the active channel
+	# nick => realnick
+	# mode =>
+	my ($MODE_OP, $MODE_HALFOP, $MODE_VOICE, $MODE_NORMAL) = (0,1,2,3);
+	# status =>
+	my ($STATUS_NORMAL, $STATUS_JOINING, $STATUS_PARTING, $STATUS_QUITING, $STATUS_KICKED, $STATUS_SPLIT) = (0,1,2,3,4,5);
+	# text => text to be printed
+	# cmp => text used to compare (sort) nicks
+
+
+# 'cached' settings
+my ($screen_prefix, $irssi_width, @prefix_mode, @prefix_status, $height, $nicklist_width);
+
+sub read_settings {
+	($screen_prefix = Irssi::settings_get_str('nicklist_screen_prefix')) =~ s/\\e/\033/g;
+
+	($prefix_mode[$MODE_OP] = Irssi::settings_get_str('nicklist_prefix_mode_op')) =~ s/\\e/\033/g;
+	($prefix_mode[$MODE_HALFOP] = Irssi::settings_get_str('nicklist_prefix_mode_halfop')) =~ s/\\e/\033/g;
+	($prefix_mode[$MODE_VOICE] = Irssi::settings_get_str('nicklist_prefix_mode_voice')) =~ s/\\e/\033/g;
+	($prefix_mode[$MODE_NORMAL] = Irssi::settings_get_str('nicklist_prefix_mode_normal')) =~ s/\\e/\033/g;
+	
+	if ($mode != $SCREEN) {
+		$height = Irssi::settings_get_int('nicklist_height');
+	}
+	my $new_nicklist_width = Irssi::settings_get_int('nicklist_width');
+	if ($new_nicklist_width != $nicklist_width && $mode == $SCREEN) {
+		sig_terminal_resized();
+	}
+	$nicklist_width = $new_nicklist_width;
+}
+
+sub update {
+	read_settings();
+	make_nicklist();
+}
+
+##################
+##### OUTPUT #####
+##################
+
+### off ###
+
+sub cmd_off {
+	if ($mode == $SCREEN) {
+		screen_stop();
+	} elsif ($mode == $FIFO) {
+		fifo_stop();
+	}
+}
+
+### fifo ###
+
+sub cmd_fifo_start {
+	read_settings();
+	my $path = Irssi::settings_get_str('nicklist_fifo_path');
+	unless (-p $path) { # not a pipe
+	    if (-e _) { # but a something else
+	        die "$0: $path exists and is not a pipe, please remove it\n";
+	    } else {
+	        require POSIX;
+	        POSIX::mkfifo($path, 0666) or die "can\'t mkfifo $path: $!";
+		Irssi::print("Fifo created. Start reading it (\"cat $path\") and try again.");
+		return;
+	    }
+	}
+	if (!sysopen(FIFO, $path, O_WRONLY | O_NONBLOCK)) { # or die "can't write $path: $!";
+		Irssi::print("Couldn\'t write to the fifo ($!). Please start reading the fifo (\"cat $path\") and try again.");
+		return;
+	}
+	FIFO->autoflush(1);
+	print FIFO "\033[2J\033[1;1H"; # erase screen & jump to 0,0
+	$cursor_line = 0;
+	if ($mode == $SCREEN) {
+		screen_stop();
+	}
+	$mode = $FIFO;
+	make_nicklist();
+}
+
+sub fifo_stop {
+	close FIFO;
+	$mode = $OFF;
+	Irssi::print("Fifo closed.");
+}
+
+### screen ###
+
+sub cmd_screen_start {
+	if (!defined($ENV{'STY'})) {
+		Irssi::print 'screen not detected, screen mode only works inside screen';
+		return;
+	}
+	read_settings();
+	if ($mode == $SCREEN) {return;}
+	if ($mode == $FIFO) {
+		fifo_stop();
+	}
+	$mode = $SCREEN;
+	Irssi::signal_add_last('gui print text finished', \&sig_gui_print_text_finished);
+	Irssi::signal_add_last('gui page scrolled', \&sig_page_scrolled);
+	Irssi::signal_add('terminal resized', \&sig_terminal_resized);
+	screen_size();
+	make_nicklist();
+}
+
+sub screen_stop {
+	$mode = $OFF;
+	Irssi::signal_remove('gui print text finished', \&sig_gui_print_text_finished);
+	Irssi::signal_remove('gui page scrolled', \&sig_page_scrolled);
+	Irssi::signal_remove('terminal resized', \&sig_terminal_resized);
+	system 'screen -x '.$ENV{'STY'}.' -X fit';
+}
+
+sub screen_size {
+	if ($mode != $SCREEN) {
+		return;
+	}
+	$screen_resizing = 1;
+	# fit screen
+	system 'screen -x '.$ENV{'STY'}.' -X fit';
+	# get size (from perldoc -q size)
+	my ($winsize, $row, $col, $xpixel, $ypixel);
+	eval 'use Term::ReadKey; ($col, $row, $xpixel, $ypixel) = GetTerminalSize';
+	#	require Term::ReadKey 'GetTerminalSize';
+	#	($col, $row, $xpixel, $ypixel) = Term::ReadKey::GetTerminalSize;
+	#};
+	if ($@) { # no Term::ReadKey, try the ugly way
+		eval {
+			require 'sys/ioctl.ph';
+			# without this reloading doesn't work. workaround for some unknown bug
+			do 'asm/ioctls.ph';
+		};
+		
+		# ugly way not working, let's try something uglier, the dg-hack(tm) (constant for linux only?)
+		if($@) { no strict 'refs'; *TIOCGWINSZ = sub { return 0x5413 } }
+		
+		unless (defined &TIOCGWINSZ) {
+			die "Term::ReadKey not found, and ioctl 'workaround' failed. Install the Term::ReadKey perl module to use screen mode.\n";
+		}
+		open(TTY, "+</dev/tty") or die "No tty: $!";
+		unless (ioctl(TTY, &TIOCGWINSZ, $winsize='')) {
+			die "Term::ReadKey not found, and ioctl 'workaround' failed ($!). Install the Term::ReadKey perl module to use screen mode.\n";
+		}
+		close(TTY);
+		($row, $col, $xpixel, $ypixel) = unpack('S4', $winsize);
+	}
+	
+	# set screen width
+	$irssi_width = $col-$nicklist_width-1;
+	$height = $row-1;
+	
+	# on some recent systems, "screen -X fit; screen -X width -w 50" doesn't work, needs a sleep in between the 2 commands
+	# so we wait a second before setting the width
+	Irssi::timeout_add_once(1000, sub {
+		my ($new_irssi_width) = @_;
+		system 'screen -x '.$ENV{'STY'}.' -X width -w ' . $new_irssi_width;
+		# and then we wait another second for the resizing, and then redraw.
+		Irssi::timeout_add_once(1000,sub {$screen_resizing = 0; redraw()}, []);
+	}, $irssi_width);
+}
+
+sub sig_terminal_resized {
+	if ($screen_resizing) {
+		return;
+	}
+	$screen_resizing = 1;
+	Irssi::timeout_add_once(1000,\&screen_size,[]);
+}
+
+
+### both ###
+
+sub nicklist_write_start {
+	if ($mode == $SCREEN) {
+		print STDERR "\033P\033[s\033\\"; # save cursor
+	}
+}
+
+sub nicklist_write_end {
+	if ($mode == $SCREEN) {
+		print STDERR "\033P\033[u\033\\"; # restore cursor
+	}
+}
+
+sub nicklist_write_line {
+	my ($line, $data) = @_;
+	if ($mode == $SCREEN) {
+		print STDERR "\033P\033[" . ($line+1) . ';'. ($irssi_width+1) .'H'. $screen_prefix . $data . "\033\\";
+	} elsif ($mode == $FIFO) {
+		$data = "\033[m$data"; # reset color
+		if ($line == $cursor_line+1) {
+			$data = "\n$data"; # next line
+		} elsif ($line == $cursor_line) {
+			$data = "\033[1G".$data; # back to beginning of line
+		} else {
+			$data = "\033[".($line+1).";0H".$data; # jump
+		}
+		$cursor_line=$line;
+		print(FIFO $data) or fifo_stop();
+	}
+}
+
+# recalc the text of the nicklist item
+sub calc_text {
+	my ($nick) = @_;
+	my $tmp = $nicklist_width-3;
+	(my $text = $nick->{'nick'}) =~ s/^(.{$tmp})..+$/$1\033[34m~\033[m/;
+	$nick->{'text'} = $prefix_mode[$nick->{'mode'}] . $text . (' ' x ($nicklist_width-length($nick->{'nick'})-1));
+	$nick->{'cmp'} = $nick->{'mode'}.lc($nick->{'nick'});
+}
+
+# redraw the given nick (nr) if it is visible
+sub redraw_nick_nr {
+	my ($nr) = @_;
+	my $line = $nr - $scroll_pos;
+	if ($line >= 0 && $line < $height) {
+		nicklist_write_line($line, $nicklist[$nr]->{'text'});
+	}
+}
+
+# nick was inserted, redraw area if necessary
+sub draw_insert_nick_nr {
+	my ($nr) = @_;
+	my $line = $nr - $scroll_pos;
+	if ($line < 0) { # nick is inserted above visible area
+		$scroll_pos++; # 'scroll' down :)
+	} elsif ($line < $height) { # line is visible
+		if ($mode == $SCREEN) {
+			need_redraw();
+		} elsif ($mode == $FIFO) {
+			my $data = "\033[m\033[L". $nicklist[$nr]->{'text'}; # reset color & insert line & write nick
+			if ($line == $cursor_line) {
+				$data = "\033[1G".$data; # back to beginning of line
+			} else {
+				$data = "\033[".($line+1).";1H".$data; # jump
+			}
+			$cursor_line=$line;
+			print(FIFO $data) or fifo_stop();
+			if ($prev_lines < $height) {
+				$prev_lines++; # the nicklist has one line more
+			}
+		}
+	}
+}
+
+sub draw_remove_nick_nr {
+	my ($nr) = @_;
+	my $line = $nr - $scroll_pos;
+	if ($line < 0) { # nick removed above visible area
+		$scroll_pos--; # 'scroll' up :)
+	} elsif ($line < $height) { # line is visible
+		if ($mode == $SCREEN) {
+			need_redraw();
+		} elsif ($mode == $FIFO) {
+			#my $data = "\033[m\033[L[i$line]". $nicklist[$nr]->{'text'}; # reset color & insert line & write nick
+			my $data = "\033[M"; # delete line
+			if ($line != $cursor_line) {
+				$data = "\033[".($line+1)."d".$data; # jump
+			}
+			$cursor_line=$line;
+			print(FIFO $data) or fifo_stop();
+			if (@nicklist-$scroll_pos >= $height) {
+				redraw_nick_nr($scroll_pos+$height-1);
+			}
+		}
+	}
+}
+
+# redraw the whole nicklist
+sub redraw {
+	$need_redraw = 0;
+	#make_nicklist();
+	nicklist_write_start();
+	my $line = 0;
+	### draw nicklist ###
+	for (my $i=$scroll_pos;$line < $height && $i < @nicklist; $i++) {
+		nicklist_write_line($line++, $nicklist[$i]->{'text'});
+	}
+
+	### clean up other lines ###
+	my $real_lines = $line;
+	while($line < $prev_lines) {
+		nicklist_write_line($line++,' ' x $nicklist_width);
+	}
+	$prev_lines = $real_lines;
+	nicklist_write_end();
+}
+
+# redraw (with little delay to avoid redrawing to much)
+sub need_redraw {
+	if(!$need_redraw) {
+		$need_redraw = 1;
+		Irssi::timeout_add_once(10,\&redraw,[]);
+	}
+}
+
+sub sig_page_scrolled {
+	$prev_lines = $height; # we'll need to redraw everything if he scrolled up
+	need_redraw;
+}
+
+# redraw (with delay) if the window is visible (only in screen mode)
+sub sig_gui_print_text_finished {
+	if ($need_redraw) { # there's already a redraw 'queued'
+		return;
+	}
+	my $window = @_[0];
+	if ($window->{'refnum'} == Irssi::active_win->{'refnum'} || Irssi::settings_get_str('nicklist_screen_split_windows') eq '*') {
+		need_redraw;
+		return;
+	}
+	foreach my $win (split(/[ ,]/, Irssi::settings_get_str('nicklist_screen_split_windows'))) {
+		if ($window->{'refnum'} == $win || $window->{'name'} eq $win) {
+			need_redraw;
+			return;
+		}
+	}
+}
+
+####################
+##### NICKLIST #####
+####################
+
+# returns the position of the given nick(as string) in the (internal) nicklist
+sub find_nick {
+	my ($nick) = @_;
+	for (my $i=0;$i < @nicklist; $i++) {
+		if ($nicklist[$i]->{'nick'} eq $nick) {
+			return $i;
+		}
+	}
+	return -1;
+}
+
+# find position where nick should be inserted into the list
+sub find_insert_pos {
+	my ($cmp)= @_;
+	for (my $i=0;$i < @nicklist; $i++) {
+		if ($nicklist[$i]->{'cmp'} gt $cmp) {
+			return $i;
+		}
+	}
+	return scalar(@nicklist); #last
+}
+
+# make the (internal) nicklist (@nicklist)
+sub make_nicklist {
+	@nicklist = ();
+	$scroll_pos = 0;
+
+	### get & check channel ###
+	my $channel = Irssi::active_win->{active};
+
+	if (!$channel || (ref($channel) ne 'Irssi::Irc::Channel' && ref($channel) ne 'Irssi::Silc::Channel' && ref($channel) ne 'Irssi::Xmpp::Channel') || $channel->{'type'} ne 'CHANNEL' || ($channel->{chat_type} ne 'SILC' && !$channel->{'names_got'}) ) {
+		$active_channel = undef;
+		# no nicklist
+	} else {
+		$active_channel = $channel;
+		### make nicklist ###
+		my $thisnick;
+		foreach my $nick (sort {(($a->{'op'}?'1':$a->{'halfop'}?'2':$a->{'voice'}?'3':'4').lc($a->{'nick'}))
+		                    cmp (($b->{'op'}?'1':$b->{'halfop'}?'2':$b->{'voice'}?'3':'4').lc($b->{'nick'}))} $channel->nicks()) {
+			$thisnick = {'nick' => $nick->{'nick'}, 'mode' => ($nick->{'op'}?$MODE_OP:$nick->{'halfop'}?$MODE_HALFOP:$nick->{'voice'}?$MODE_VOICE:$MODE_NORMAL)};
+			calc_text($thisnick);
+			push @nicklist, $thisnick;
+		}
+	}
+	need_redraw();
+}
+
+# insert nick(as hash) into nicklist
+# pre: cmp has to be calculated
+sub insert_nick {
+	my ($nick) = @_;
+	my $nr = find_insert_pos($nick->{'cmp'});
+	splice @nicklist, $nr, 0, $nick;
+	draw_insert_nick_nr($nr);
+}
+
+# remove nick(as nr) from nicklist
+sub remove_nick {
+	my ($nr) = @_;
+	splice @nicklist, $nr, 1;
+	draw_remove_nick_nr($nr);
+}
+
+###################
+##### ACTIONS #####
+###################
+
+# scroll the nicklist, arg = number of lines to scroll, positive = down, negative = up
+sub cmd_scroll {
+	if (!$active_channel) { # not a channel active
+		return;
+	}
+	my @nicks=Irssi::active_win->{active}->nicks;
+	my $nick_count = scalar(@nicks)+0;
+	my $channel = Irssi::active_win->{active};
+	if (!$channel || $channel->{type} ne 'CHANNEL' || !$channel->{names_got} || $nick_count <= Irssi::settings_get_int('nicklist_height')) {
+		return;
+	}
+	$scroll_pos += @_[0];
+
+	if ($scroll_pos > $nick_count - $height) {
+		$scroll_pos = $nick_count - $height;
+	}
+	if ($scroll_pos <= 0) {
+		$scroll_pos = 0;
+	}
+	need_redraw();
+}
+
+sub is_active_channel {
+	my ($server,$channel) = @_; # (channel as string)
+	return ($server && $server->{'tag'} eq $active_channel->{'server'}->{'tag'} && $server->channel_find($channel) && $active_channel && $server->channel_find($channel)->{'name'} eq $active_channel->{'name'});
+}
+
+sub sig_channel_wholist { # this is actualy a little late, when the names are received would be better
+	my ($channel) = @_;
+	if (Irssi::active_win->{'active'} && Irssi::active_win->{'active'}->{'name'} eq $channel->{'name'}) { # the channel joined is active
+		make_nicklist
+	}
+}
+
+sub sig_join {
+	my ($server,$channel,$nick,$address) = @_;
+	if (!is_active_channel($server,$channel)) {
+		return;
+	}
+	my $newnick = {'nick' => $nick, 'mode' => $MODE_NORMAL};
+	calc_text($newnick);
+	insert_nick($newnick);
+}
+
+sub sig_kick {
+	my ($server, $channel, $nick, $kicker, $address, $reason) = @_;
+	if (!is_active_channel($server,$channel)) {
+		return;
+	}
+	my $nr = find_nick($nick);
+	if ($nr == -1) {
+		Irssi::print("nicklist warning: $nick was kicked from $channel, but not found in nicklist");
+	} else {
+		remove_nick($nr);
+	}
+}
+
+sub sig_part {
+	my ($server,$channel,$nick,$address, $reason) = @_;
+	if (!is_active_channel($server,$channel)) {
+		return;
+	}
+	my $nr = find_nick($nick);
+	if ($nr == -1) {
+		Irssi::print("nicklist warning: $nick has parted $channel, but was not found in nicklist");
+	} else {
+		remove_nick($nr);
+	}
+
+}
+
+sub sig_quit {
+	my ($server,$nick,$address, $reason) = @_;
+	if ($server->{'tag'} ne $active_channel->{'server'}->{'tag'}) {
+		return;
+	}
+	my $nr = find_nick($nick);
+	if ($nr != -1) {
+		remove_nick($nr);
+	}
+}
+
+sub sig_nick {
+	my ($server, $newnick, $oldnick, $address) = @_;
+	if ($server->{'tag'} ne $active_channel->{'server'}->{'tag'}) {
+		return;
+	}
+	my $nr = find_nick($oldnick);
+	if ($nr != -1) { # if nick was found (nickchange is in current channel)
+		my $nick = $nicklist[$nr];
+		remove_nick($nr);
+		$nick->{'nick'} = $newnick;
+		calc_text($nick);
+		insert_nick($nick);
+	}
+}
+
+sub sig_mode {
+	my ($channel, $nick, $setby, $mode, $type) = @_; # (nick and channel as rec)
+	if ($channel->{'server'}->{'tag'} ne $active_channel->{'server'}->{'tag'} || $channel->{'name'} ne $active_channel->{'name'}) {
+		return;
+	}
+	my $nr = find_nick($nick->{'nick'});
+	if ($nr == -1) {
+		Irssi::print("nicklist warning: $nick->{'nick'} had mode set on $channel->{'name'}, but was not found in nicklist");
+	} else {
+		my $nicklist_item = $nicklist[$nr];
+		remove_nick($nr);
+		$nicklist_item->{'mode'} = ($nick->{'op'}?$MODE_OP:$nick->{'halfop'}?$MODE_HALFOP:$nick->{'voice'}?$MODE_VOICE:$MODE_NORMAL);
+		calc_text($nicklist_item);
+		insert_nick($nicklist_item);
+	}
+}
+
+##### command binds #####
+Irssi::command_bind 'nicklist' => sub {
+    my ( $data, $server, $item ) = @_;
+    $data =~ s/\s+$//g;
+    Irssi::command_runsub ('nicklist', $data, $server, $item ) ;
+};
+Irssi::signal_add_first 'default command nicklist' => sub {
+	# gets triggered if called with unknown subcommand
+	cmd_help();
+};
+Irssi::command_bind('nicklist update',\&update);
+Irssi::command_bind('nicklist help',\&cmd_help);
+Irssi::command_bind('nicklist scroll',\&cmd_scroll);
+Irssi::command_bind('nicklist fifo',\&cmd_fifo_start);
+Irssi::command_bind('nicklist screen',\&cmd_screen_start);
+Irssi::command_bind('nicklist screensize',\&screen_size);
+Irssi::command_bind('nicklist off',\&cmd_off);
+
+##### signals #####
+Irssi::signal_add_last('window item changed', \&make_nicklist);
+Irssi::signal_add_last('window changed', \&make_nicklist);
+Irssi::signal_add_last('channel wholist', \&sig_channel_wholist);
+Irssi::signal_add_first('message join', \&sig_join); # first, to be before ignores
+Irssi::signal_add_first('message part', \&sig_part);
+Irssi::signal_add_first('message kick', \&sig_kick);
+Irssi::signal_add_first('message quit', \&sig_quit);
+Irssi::signal_add_first('message nick', \&sig_nick);
+Irssi::signal_add_first('message own_nick', \&sig_nick);
+Irssi::signal_add_first('nick mode changed', \&sig_mode);
+
+Irssi::signal_add('setup changed', \&read_settings);
+
+##### settings #####
+Irssi::settings_add_str('nicklist', 'nicklist_screen_prefix', '\e[m ');
+Irssi::settings_add_str('nicklist', 'nicklist_prefix_mode_op', '\e[32m@\e[39m');
+Irssi::settings_add_str('nicklist', 'nicklist_prefix_mode_halfop', '\e[34m%\e[39m');
+Irssi::settings_add_str('nicklist', 'nicklist_prefix_mode_voice', '\e[33m+\e[39m');
+Irssi::settings_add_str('nicklist', 'nicklist_prefix_mode_normal', ' ');
+
+Irssi::settings_add_int('nicklist', 'nicklist_width',11);
+Irssi::settings_add_int('nicklist', 'nicklist_height',24);
+Irssi::settings_add_str('nicklist', 'nicklist_fifo_path', Irssi::get_irssi_dir . '/nicklistfifo');
+Irssi::settings_add_str('nicklist', 'nicklist_screen_split_windows', '');
+Irssi::settings_add_str('nicklist', 'nicklist_automode', '');
+
+read_settings();
+if (uc(Irssi::settings_get_str('nicklist_automode')) eq 'SCREEN') {
+	cmd_screen_start();
+} elsif (uc(Irssi::settings_get_str('nicklist_automode')) eq 'FIFO') {
+	cmd_fifo_start();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/scripts/nm.pl	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,693 @@
+use Irssi;
+use strict;
+
+use vars qw($VERSION %IRSSI);
+
+$VERSION="0.3.10";
+%IRSSI = (
+	authors=> 'BC-bd',
+	contact=> '[email protected]',
+	name=> 'nm',
+	description=> 'right aligned nicks depending on longest nick',
+	license=> 'GPL v2',
+	url=> 'http://bc-bd.org/blog/irssi/',
+);
+
+# $Id: 9cb009e8b7e6f5ce60294334faf88715ef01413e $
+# nm.pl
+# for irssi 0.8.4 by [email protected]
+#
+# right aligned nicks depending on longest nick
+#
+# inspired by neatmsg.pl from kodgehopper <[email protected]
+# formats taken from www.irssi.de
+# thanks to adrianel <[email protected]> for some hints
+# thanks to Eric Wald <[email protected]> for the left alignment patch
+# inspired by nickcolor.pl by Timo Sirainen and Ian Peters
+# thanks to And1 <[email protected]> for a small patch
+# thanks to [email protected] for the save/load patch
+# thanks to Dennis Heimbert <[email protected]> for a bug report/patch
+# thanks to Roy Sigurd Karlsbakk <[email protected]> for an autosave patch
+#
+#########
+# USAGE
+###
+# 
+# use
+# 
+# 	/neatcolor help
+#
+# for help on available commands
+#
+#########
+# OPTIONS
+#########
+
+my $help = "
+/set neat_colorize <ON|OFF>
+    * ON  : colorize nicks
+    * OFF : do not colorize nicks
+
+/set neat_colors <string>
+    Use these colors when colorizing nicks, eg:
+
+        /set neat_colors yYrR
+
+    See the file formats.txt on an explanation of what colors are
+    available.
+
+/set neat_left_actions <ON|OFF>
+    * ON  : print nicks left-aligned on actions
+    * OFF : print nicks right-aligned on actions
+
+/set neat_left_messages <ON|OFF>
+    * ON  : print nicks left-aligned on messages
+    * OFF : print nicks right-aligned on messages
+
+/set neat_right_mode <ON|OFF>
+    * ON  : print the mode of the nick e.g @%+ after the nick
+    * OFF : print it left of the nick 
+
+/set neat_maxlength <number>
+    * number : Maximum length of Nicks to display. Longer nicks are truncated.
+    * 0      : Do not truncate nicks.
+
+/set neat_melength <number>
+    * number : number of spaces to substract from /me padding
+
+/set neat_ignorechars <str>
+    * str : regular expression used to filter out unwanted characters in
+            nicks. this can be used to assign the same color for similar
+            nicks, e.g. foo and foo_:
+
+                /set neat_ignorechars [_]
+
+/set neat_allow_shrinking <ON|OFF>
+    * ON  : shrink padding when longest nick disappears
+    * OFF : do not shrink, only allow growing
+
+/set neat_autosave <number>
+    * number : autosave after <number> seconds, defaults to 60. Set to 0 to
+               disable.
+";
+
+#
+###
+################
+###
+#
+# Changelog
+#
+# Version 0.3.11
+#  - added autosave, idea from Roy Sigurd Karlsbakk
+#
+# Version 0.3.10
+#  - fix losing of saved color when changing nick shares more than one channel
+#    with you
+#
+# Version 0.3.9
+#  - fix longest nick calculation for nicks shorter than the current longest
+#    nick
+#  - updated url
+#
+# Version 0.3.8
+#  - fixed error in the nickchange tracking code, reported by Kevin Ballard
+#  - added --all switch to reset command
+#  - skip broken lines in saved_colors
+#
+# Version 0.3.7
+#  - fixed crash when calling /neatcolor without parameters
+#  - fixed url
+#
+# Version 0.3.6
+#  - added option to ignore certain characters from color hash building, see
+#    https://bc-bd.org/trac/irssi/ticket/22
+#  - added option to save and specify colors for nicks, see
+#    https://bc-bd.org/trac/irssi/ticket/23
+#  - added option to disallow shrinking, see
+#    https://bc-bd.org/trac/irssi/ticket/12
+#
+# Version 0.3.5
+#  - now also aligning own messages in queries
+#
+# Version 0.3.4
+#  - fxed off by one error in nick_to_color, patch by jrib, see
+#  https://bc-bd.org/trac/irssi/ticket/24
+#
+# Version 0.3.3
+#  - added support for alignment in queries, see
+#    https://bc-bd.org/trac/irssi/ticket/21
+#
+# Version 0.3.2
+#  - integrated left alignment patch from Eric Wald <[email protected]>, see
+#    https://bc-bd.org/trac/irssi/ticket/18
+#
+# Version 0.3.1
+#  - /me padding, see https://bc-bd.org/trac/irssi/ticket/17
+#
+# Version 0.3.0
+#  - integrate nick coloring support
+#
+# Version 0.2.1
+#  - moved neat_maxlength check to reformat() (thx to Jerome De Greef <[email protected]>)
+#
+# Version 0.2.0
+#  - by adrianel <[email protected]>
+#     * reformat after setup reload
+#     * maximum length of nicks
+#
+# Version 0.1.0
+#  - got lost somewhere
+#
+# Version 0.0.2
+#  - ugly typo fixed
+#  
+# Version 0.0.1
+#  - initial release
+#
+###
+################
+###
+#
+# BUGS
+#
+# Empty nicks, eg "<> message"
+# 	This seems to be triggered by some themes. As of now there is no known
+# 	fix other than changing themes, see
+# 	https://bc-bd.org/trac/irssi/ticket/19
+#
+# Well, it's a feature: due to the lacking support of extendable themes
+# from irssi it is not possible to just change some formats per window.
+# This means that right now all windows are aligned with the same nick
+# length, which can be somewhat annoying.
+# If irssi supports extendable themes, I will include per-server indenting
+# and a setting where you can specify servers you don't want to be indented
+#
+###
+################
+
+my ($longestNick, %saved_colors, @colors, $alignment, $sign, %commands,);
+my ($pending_save);
+
+my $colorize = -1;
+
+sub reformat() {
+	my $max = Irssi::settings_get_int('neat_maxlength');
+	my $actsign = Irssi::settings_get_bool('neat_left_actions')? '': '-';
+	$sign = Irssi::settings_get_bool('neat_left_messages')? '': '-';
+
+	if ($max && $max < $longestNick) {
+		$longestNick = $max;
+	}
+
+	my $me = $longestNick - Irssi::settings_get_int('neat_melength');
+	$me = 0 if ($me < 0);
+
+	Irssi::command('^format own_action {ownaction $['.$actsign.$me.']0} $1');
+	Irssi::command('^format action_public {pubaction $['.$actsign.$me.']0}$1');
+	Irssi::command('^format action_private {pvtaction $['.$actsign.$me.']0}$1');
+	Irssi::command('^format action_private_query {pvtaction_query $['.$actsign.$me.']0} $2');
+
+	my $length = $sign . $longestNick;
+	if (Irssi::settings_get_bool('neat_right_mode') == 0) {
+		Irssi::command('^format own_msg {ownmsgnick $2 {ownnick $['.$length.']0}}$1');
+		Irssi::command('^format own_msg_channel {ownmsgnick $3 {ownnick $['.$length.']0}{msgchannel $1}}$2');
+		Irssi::command('^format pubmsg_me {pubmsgmenick $2 {menick $['.$length.']0}}$1');
+		Irssi::command('^format pubmsg_me_channel {pubmsgmenick $3 {menick $['.$length.']0}{msgchannel $1}}$2');
+		Irssi::command('^format pubmsg_hilight {pubmsghinick $0 $3 $['.$length.']1%n}$2');
+		Irssi::command('^format pubmsg_hilight_channel {pubmsghinick $0 $4 $['.$length.']1{msgchannel $2}}$3');
+		Irssi::command('^format pubmsg {pubmsgnick $2 {pubnick $['.$length.']0}}$1');
+		Irssi::command('^format pubmsg_channel {pubmsgnick $2 {pubnick $['.$length.']0}}$1');
+	} else {
+		Irssi::command('^format own_msg {ownmsgnick {ownnick $['.$length.']0$2}}$1');
+		Irssi::command('^format own_msg_channel {ownmsgnick {ownnick $['.$length.']0$3}{msgchannel $1}}$2');
+		Irssi::command('^format pubmsg_me {pubmsgmenick {menick $['.$length.']0}$2}$1');
+		Irssi::command('^format pubmsg_me_channel {pubmsgmenick {menick $['.$length.']0$3}{msgchannel $1}}$2');
+		Irssi::command('^format pubmsg_hilight {pubmsghinick $0 $0 $['.$length.']1$3%n}$2');
+		Irssi::command('^format pubmsg_hilight_channel {pubmsghinick $0 $['.$length.']1$4{msgchannel $2}}$3');
+		Irssi::command('^format pubmsg {pubmsgnick {pubnick $['.$length.']0$2}}$1');
+		Irssi::command('^format pubmsg_channel {pubmsgnick {pubnick $['.$length.']0$2}}$1');
+	}
+
+	# format queries
+	Irssi::command('^format own_msg_private_query {ownprivmsgnick {ownprivnick $['.$length.']2}}$1');
+	Irssi::command('^format msg_private_query {privmsgnick $['.$length.']0}$2');
+};
+
+sub findLongestNick {
+	$longestNick = 0;
+
+	# get own nick length
+	map {
+		my $len = length($_->{nick});
+
+		$longestNick = $len if ($len > $longestNick);
+	} Irssi::servers();
+
+	# find longest other nick
+	foreach (Irssi::channels()) {
+		foreach ($_->nicks()) {
+			my $len = length($_->{nick});
+
+			$longestNick = $len if ($len > $longestNick);
+		}
+	}
+
+	reformat();
+}
+
+sub delayed_save
+{
+	# skip if we have already a save pending. we don't reset the timeout
+	# here, else you could end up with changes never being automatically
+	# saved if they happen more often than <neat_autosave> seconds
+	return if $pending_save;
+
+	return unless Irssi::settings_get_int('neat_autosave');
+
+	Irssi::timeout_add_once(Irssi::settings_get_int('neat_autosave') * 1000,
+		\&save_colors, undef);
+}
+
+# a new nick was created
+sub sig_newNick
+{
+	my ($channel, $nick) = @_;
+
+	my $len = length($nick->{nick});
+
+	if ($len > $longestNick) {
+		$longestNick = $len;
+		reformat();
+	}
+
+	return if (exists($saved_colors{$nick->{nick}}));
+
+	$saved_colors{$nick->{nick}} = "%".nick_to_color($nick->{nick});
+	delayed_save();
+}
+
+# something changed
+sub sig_changeNick
+{
+	my ($channel, $nick, $old_nick) = @_;
+
+	# if no saved color exists, we already handled this nickchange. irssi
+	# generates one signal per channel the nick is in, so if you share more
+	# than one channel with this nick, you'd lose the coloring.
+	return unless exists($saved_colors{$old_nick});
+
+	# we need to update the saved colorors hash independent of nick lenght
+	$saved_colors{$nick->{nick}} = $saved_colors{$old_nick};
+	delete $saved_colors{$old_nick};
+	delayed_save();
+
+	my $new = length($nick->{nick});
+
+	# in case the new nick is longer than the old one, simply remember this
+	# as the new longest nick and reformat.
+	#
+	# if the new nick is as long as the known longest nick nothing has to be
+	# done
+	#
+	# if the new nick is shorter than the current longest one and if the
+	# user allows us to shrink, find new longest nick and reformat.
+	if ($new > $longestNick) {
+		$longestNick = $new;
+	} elsif ($new == $longestNick) {
+		return;
+	} else {
+		return unless Irssi::settings_get_bool('neat_allow_shrinking');
+		findLongestNick();
+	}
+
+	reformat();
+}
+
+sub sig_removeNick
+{
+	my ($channel, $nick) = @_;
+
+	my $thisLen = length($nick->{nick});
+
+	# we only need to recalculate if this was the longest nick and we are
+	# allowed to shrink
+	if ($thisLen == $longestNick && Irssi::settings_get_bool('neat_allow_shrinking')) {
+		findLongestNick();
+		reformat();
+	}
+
+	# we do not remove a known color for a gone nick, as they may return
+}
+
+# based on simple_hash from nickcolor.pl
+sub nick_to_color($) {
+	my ($string) = @_;
+	chomp $string;
+
+	my $ignore = Irssi::settings_get_str("neat_ignorechars");
+	$string =~ s/$ignore//g;
+
+	my $counter;
+	foreach my $char (split(//, $string)) {
+		$counter += ord $char;
+	}
+
+	return $colors[$counter % ($#colors + 1)];
+}
+
+sub color_left($) {
+	Irssi::command('^format pubmsg {pubmsgnick $2 {pubnick '.$_[0].'$['.$sign.$longestNick.']0}}$1');
+	Irssi::command('^format pubmsg_channel {pubmsgnick $2 {pubnick '.$_[0].'$['.$sign.$longestNick.']0}}$1');
+}
+
+sub color_right($) {
+	Irssi::command('^format pubmsg {pubmsgnick {pubnick '.$_[0].'$['.$sign.$longestNick.']0}$2}$1');
+	Irssi::command('^format pubmsg_channel {pubmsgnick {pubnick '.$_[0].'$['.$sign.$longestNick.']0}$2}$1');
+}
+
+sub sig_public {
+	my ($server, $msg, $nick, $address, $target) = @_;
+
+	&$alignment($saved_colors{$nick});
+}
+
+sub sig_setup {
+	@colors = split(//, Irssi::settings_get_str('neat_colors'));
+
+	# check left or right alignment
+	if (Irssi::settings_get_bool('neat_right_mode') == 0) {
+		$alignment = \&color_left;
+	} else {
+		$alignment = \&color_right;
+	}
+	
+	# check if we switched coloring on or off
+	my $new = Irssi::settings_get_bool('neat_colorize');
+	if ($new != $colorize) {
+		if ($new) {
+			Irssi::signal_add('message public', 'sig_public');
+		} else {
+			if ($colorize >= 0) {
+				Irssi::signal_remove('message public', 'sig_public');
+			}
+		}
+	}
+	$colorize = $new;
+
+	reformat();
+	&$alignment('%w');
+}
+
+# make sure that every nick has an assigned color
+sub assert_colors() {
+	foreach (Irssi::channels()) {
+		foreach ($_->nicks()) {
+			next if (exists($saved_colors{$_->{nick}}));
+
+			$saved_colors{$_->{nick}} = "%".nick_to_color($_->{nick});
+			delayed_save();
+		}
+	}
+}
+
+# load colors from file
+sub load_colors() {
+	open(FID, "<".$ENV{HOME}."/.irssi/saved_colors") || return;
+
+	while (<FID>) {
+		chomp;
+		my ($k, $v) = split(/:/);
+
+		# skip broken lines, those may have been introduced by nm.pl
+		# version 0.3.7 and earlier
+		if ($k eq '' || $v eq '') {
+			neat_log(Irssi::active_win(), "Warning, broken line in saved_colors file, skipping '$k:$v'");
+			next;
+		}
+
+		$saved_colors{$k} = $v;
+	}
+
+	close(FID);
+}
+
+# save colors to file
+sub save_colors() {
+	open(FID, ">".$ENV{HOME}."/.irssi/saved_colors");
+
+	print FID $_.":".$saved_colors{$_}."\n" foreach (keys(%saved_colors));
+
+	close(FID);
+
+	# clear possible pending save.
+	Irssi::timeout_remove($pending_save) if $pending_save;
+	$pending_save = undef;
+}
+
+# log a line to a window item
+sub neat_log($@) {
+	my ($witem, @text) = @_;
+
+	$witem->print("nm.pl: ".$_) foreach(@text);
+}
+
+# show available colors
+sub cmd_neatcolor_colors($) {
+	my ($witem, undef, undef) = @_;
+
+	neat_log($witem, "Available colors: ".join("", map { "%".$_.$_ } @colors));
+}
+
+# display the configured color for a nick
+sub cmd_neatcolor_get() {
+	my ($witem, $nick, undef) = @_;
+
+	if (!exists($saved_colors{$nick})) {
+		neat_log($witem, "Error: no such nick '$nick'");
+		return;
+	}
+
+	neat_log($witem, "Color for ".$saved_colors{$nick}.$nick);
+}
+
+# display help
+sub cmd_neatcolor_help() {
+	my ($witem, $cmd, undef) = @_;
+
+	if ($cmd) {
+		if (!exists($commands{$cmd})) {
+			neat_log($witem, "Error: no such command '$cmd'");
+			return;
+		}
+
+		if (!exists($commands{$cmd}{verbose})) {
+			neat_log($witem, "No additional help for '$cmd' available");
+			return;
+		}
+
+		neat_log($witem, ( "", "Help for ".uc($cmd), "" ) );
+		neat_log($witem, @{$commands{$cmd}{verbose}});
+		return;
+	}
+
+	neat_log($witem, split(/\n/, $help));
+	neat_log($witem, "Available options for /neatcolor");
+	neat_log($witem, "    ".$_.": ".$commands{$_}{text}) foreach(sort(keys(%commands)));
+
+	my @verbose;
+	foreach (sort(keys(%commands))) {
+		push(@verbose, $_) if exists($commands{$_}{verbose});
+	}
+
+	neat_log($witem, "Verbose help available for: '".join(", ", @verbose)."'");
+}
+
+# list configured nicks
+sub cmd_neatcolor_list() {
+	my ($witem, undef, undef) = @_;
+
+	neat_log($witem, "Configured nicks: ".join(", ", map { $saved_colors{$_}.$_ } sort(keys(%saved_colors))));
+}
+
+# reset a nick to its default color
+sub cmd_neatcolor_reset() {
+	my ($witem, $nick, undef) = @_;
+
+	if ($nick eq '--all') {
+		%saved_colors = ();
+		assert_colors();
+		neat_log($witem, "Reset all colors");
+		return;
+	}
+
+	if (!exists($saved_colors{$nick})) {
+		neat_log($witem, "Error: no such nick '$nick'");
+		return;
+	}
+
+	$saved_colors{$nick} = "%".nick_to_color($nick);
+	delayed_save();
+	neat_log($witem, "Reset color for ".$saved_colors{$nick}.$nick);
+}
+
+# save configured colors to disk
+sub cmd_neatcolor_save() {
+	my ($witem, undef, undef) = @_;
+
+	save_colors();
+
+	neat_log($witem, "color information saved");
+}
+
+# set a color for a nick
+sub cmd_neatcolor_set() {
+	my ($witem, $nick, $color) = @_;
+
+	my @found = grep(/$color/, @colors);
+	if ($#found) {
+		neat_log($witem, "Error: trying to set unknown color '%$color$color%n'");
+		cmd_neatcolor_colors($witem);
+		return;
+	}
+
+	if ($witem->{type} ne "CHANNEL" && $witem->{type} ne "QUERY") {
+		neat_log($witem, "Warning: not a Channel/Query, can not check nick!");
+		neat_log($witem, "Remember, nicks are case sensitive to nm.pl");
+	} else {
+		my @nicks = grep(/^$nick$/i, map { $_->{nick} } ($witem->nicks()));
+
+		if ($#nicks < 0) {
+			neat_log($witem, "Warning: could not find nick '$nick' here");
+		} else {
+			if ($nicks[0] ne $nick) {
+				neat_log($witem, "Warning: using '$nicks[0]' instead of '$nick'");
+				$nick = $nicks[0];
+			}
+		}
+	}
+
+	$saved_colors{$nick} = "%".$color;
+	delayed_save();
+	neat_log($witem, "Set color for $saved_colors{$nick}$nick");
+}
+
+%commands = (
+	colors => {
+		text => "show available colors",
+		verbose => [
+			"COLORS",
+			"",
+			"displays all available colors",
+			"",
+			"You can restrict/define the list of available colors ".
+			"with the help of the neat_colors setting"
+		],
+		func => \&cmd_neatcolor_colors,
+	},
+	get => {
+		text => "retrieve color for a nick",
+		verbose => [
+			"GET <nick>",
+			"",
+			"displays color used for <nick>"
+		],
+		func => \&cmd_neatcolor_get,
+	},
+	help => {
+		text => "print this help message",
+		func => \&cmd_neatcolor_help,
+	},
+	list => {
+		text => "list configured nick/color pairs",
+		func => \&cmd_neatcolor_list,
+	},
+	reset => {
+		text => "reset color to default",
+		verbose => [
+			"RESET --all|<nick>",
+			"",
+			"resets the color used for all nicks or for <nick> to ",
+			"its internal default",
+		],
+		func => \&cmd_neatcolor_reset,
+	},
+	save => {
+		text => "save color information to disk",
+		verbose => [
+			"SAVE",
+			"",
+			"saves color information to disk, so that it survives ".
+			"an irssi restart.",
+			"",
+			"Color information will be automatically saved on /quit",
+		],
+		func => \&cmd_neatcolor_save,
+	},
+	set => {
+		text => "set a specific color for a nick",
+		verbose => [
+			"SET <nick> <color>",
+			"",
+			"use <color> for <nick>",
+			"",
+			"This command will perform a couple of sanity checks, ".
+			"when called from a CHANNEL/QUERY window",
+			"",
+			"EXAMPLE:",
+			"  /neatcolor set bc-bd r",
+			"",
+			"use /neatcolor COLORS to see available colors"
+		],
+		func => \&cmd_neatcolor_set,
+	},
+);
+
+# the main command callback that gets called for all neatcolor commands
+sub cmd_neatcolor() {
+	my ($data, $server, $witem) = @_;
+	my ($cmd, $nick, $color) = split (/ /, $data);
+
+	$cmd = lc($cmd);
+
+	# make sure we have a valid witem to print text to
+	$witem = Irssi::active_win() unless ($witem);
+
+	if (!exists($commands{$cmd})) {
+		neat_log($witem, "Error: unknown command '$cmd'");
+		&{$commands{"help"}{"func"}}($witem) if (exists($commands{"help"}));
+		return;
+	}
+
+	&{$commands{$cmd}{"func"}}($witem, $nick, $color);
+}
+
+Irssi::settings_add_bool('misc', 'neat_left_messages', 0);
+Irssi::settings_add_bool('misc', 'neat_left_actions', 0);
+Irssi::settings_add_bool('misc', 'neat_right_mode', 1);
+Irssi::settings_add_int('misc', 'neat_maxlength', 0);
+Irssi::settings_add_int('misc', 'neat_melength', 2);
+Irssi::settings_add_bool('misc', 'neat_colorize', 1);
+Irssi::settings_add_str('misc', 'neat_colors', 'rRgGyYbBmMcC');
+Irssi::settings_add_str('misc', 'neat_ignorechars', '');
+Irssi::settings_add_bool('misc', 'neat_allow_shrinking', 1);
+Irssi::settings_add_int('misc', 'neat_autosave', 60);
+
+Irssi::command_bind('neatcolor', 'cmd_neatcolor');
+
+Irssi::signal_add('nicklist new', 'sig_newNick');
+Irssi::signal_add('nicklist changed', 'sig_changeNick');
+Irssi::signal_add('nicklist remove', 'sig_removeNick');
+
+Irssi::signal_add('setup changed', 'sig_setup');
+Irssi::signal_add_last('setup reread', 'sig_setup');
+
+findLongestNick();
+sig_setup;
+
+load_colors();
+assert_colors();
+
+# we need to add this signal _after_ the colors have been loaded, to make sure
+# no race condition exists wrt color saving
+Irssi::signal_add('gui exit', 'save_colors');
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/scripts/queryresume.pl	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,64 @@
+# QueryResume by Stefan Tomanek <[email protected]>
+#
+use strict;
+
+use vars qw($VERSION %IRSSI);
+$VERSION = '2003021201';
+%IRSSI = (
+    authors     => 'Stefan \'tommie\' Tomanek',
+    contact     => '[email protected]',
+    name        => 'QueryResume',
+    description => 'restores the last lines of a query on re-creation',
+    license     => 'GPLv2',
+    modules     => 'Date::Format File::Glob',
+    changed     => $VERSION,
+);  
+
+use Irssi 20020324;
+use Date::Format;
+use File::Glob ':glob';
+
+sub draw_box ($$$$) {
+    my ($title, $text, $footer, $colour) = @_;
+    my $box = '';
+    $box .= '%R,--[%n%9%U'.$title.'%U%9%R]%n'."\n";
+    foreach (split(/\n/, $text)) {
+        $box .= '%R|%n '.$_."\n";
+    }
+    $box .= '%R`--<%n'.$footer.'%R>->%n';
+    $box =~ s/%.//g unless $colour;
+    return $box;
+}
+
+sub sig_window_item_new ($$) {
+    my ($win, $witem) = @_;
+    return unless (ref $witem && $witem->{type} eq 'QUERY');
+    my @data;
+    my $filename = Irssi::settings_get_str('autolog_path');
+    my $servertag = $witem->{server}->{tag};
+    my $name = lc $witem->{name};
+    $filename =~ s/(\$tag|\$1)/$servertag/g;
+    $filename =~ s/\$0/$name/g;
+    my @lt = localtime(time);
+    my $zone;
+    $filename = strftime($filename, @lt, $zone);
+    $filename =~ s/(\[|\])/\\$1/g;
+    local *F;
+    open(F, "<".bsd_glob($filename));
+    my $lines = Irssi::settings_get_int('queryresume_lines');
+    foreach (<F>) {
+	unless (/^--- Log/) {
+	    push(@data, $_);
+	    shift(@data) if (@data > $lines);
+	}
+    }
+    my $text;
+    $text .= $_ foreach @data;
+    $text =~ s/%/%%/g;
+    $witem->print(draw_box('QueryResume', $text, $filename, 1), MSGLEVEL_CLIENTCRAP) if $text;
+}
+
+Irssi::settings_add_int($IRSSI{name}, 'queryresume_lines', 10);
+
+Irssi::signal_add('window item new', 'sig_window_item_new');
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irssi/scripts/trackbar.pl	Sat Oct 18 10:06:58 2014 +0200
@@ -0,0 +1,189 @@
+# trackbar.pl
+# 
+# This little script will do just one thing: it will draw a line each time you
+# switch away from a window. This way, you always know just upto where you've
+# been reading that window :) It also removes the previous drawn line, so you
+# don't see double lines.
+#
+# Usage: 
+#
+#     The script works right out of the box, but if you want you can change
+#     the working by /set'ing the following variables:
+#
+#     trackbar_string       The characters to repeat to draw the bar
+#     trackbar_style        The style for the bar, %r is red for example
+#                           See formats.txt that came with irssi
+#
+#     /mark is a command that will redraw the line at the bottom.  However!  This
+#     requires irssi version after 20021228.  otherwise you'll get the error
+#     redraw: unknown command, and your screen is all goofed up :)
+#
+#     /upgrade & buf.pl notice: This version tries to remove the trackbars before 
+#     the upgrade is done, so buf.pl does not restore them, as they are not removeable
+#     afterwards by trackbar.  Unfortiounatly, to make this work, trackbar and buf.pl
+#     need to be loaded in a specific order.  Please experiment to see which order works
+#     for you (strangely, it differs from configuration to configuration, something I will
+#     try to fix in a next version) 
+#
+# Authors:
+#   - Main maintainer & author: Peter 'kinlo' Leurs
+#   - Many thanks to Timo 'cras' Sirainen for placing me on my way
+#   - on-upgrade-remove-line patch by Uwe Dudenhoeffer
+#
+# Version history:
+#  1.4: - Changed our's by my's so the irssi script header is valid
+#       - Removed utf-8 support.  In theory, the script should work w/o any
+#         problems for utf-8, just set trackbar_string to a valid utf-8 character
+#         and everything *should* work.  However, this script is being plagued by
+#         irssi internal bugs.  The function Irssi::settings_get_str does NOT handle
+#         unicode strings properly, hence you will notice problems when setting the bar
+#         to a unicode char.  For changing your bar to utf-8 symbols, read the line sub.
+#  1.3: - Upgrade now removes the trackbars. 
+#       - Some code cleanups, other defaults
+#       - /mark sets the line to the bottom
+#  1.2: - Support for utf-8
+#       - How the bar looks can now be configured with trackbar_string 
+#         and trackbar_style
+#  1.1: - Fixed bug when closing window
+#  1.0: - Initial release
+#
+#
+#  Call for help!
+#
+#  There is a trackbar version 2.0 that properly handles resizes and immediate config change
+#  activation.  However, there is/are some bug(s) in irssi's main buffer/window code that causes
+#  irssi to 'forget' lines, which is ofcourse completly unaccepteable.  I haven't found the time
+#  nor do I know the irssi's internals enough to find and fix this bug, if you want to help, please
+#  contact me, I'll give you a copy of the 2.0 version that will immediatly show you the problems.
+#
+# Known bugs:
+#  - if you /clear a window, it will be uncleared when returning to the window
+#  - UTF-8 characters in the trackbar_string doesnt work.  This is an irssi bug.
+#  - if you resize your irssi (in xterm or so) the bar is not resized 
+#  - changing the trackbar style is only visible after returning to a window
+#  however, changing style/resize takes in effect after you left the window.
+#
+# Whishlist/todo:
+#  - instead of drawing a line, just invert timestamp or something, 
+#    to save a line (but I don't think this is possible with current irssi)
+#  - some pageup keybinding possibility, to scroll up upto the trackbar
+#  - <@coekie> kinlo: if i switch to another window, in another split window, i 
+#              want the trackbar to go down in the previouswindow in  that splitwindow :)
+#  - < bob_2> anyway to clear the line once the window is read?
+#  - < elho> kinlo: wishlist item: a string that gets prepended to the repeating pattern
+#  - < elho> an option to still have the timestamp in front of the bar
+#  - < elho> oh and an option to not draw it in the status window :P
+#
+# BTW: when you have feature requests, mailing a patch that works is the fastest way
+# to get it added :p
+
+use strict;
+use 5.6.1;
+use Irssi;
+use Irssi::TextUI;
+
+my $VERSION = "1.4";
+
+my %IRSSI = (
+    authors     => "Peter 'kinlo' Leurs",
+    contact     => "peter\@pfoe.be",
+    name        => "trackbar",
+    description => "Shows a bar where you've last read a window",
+    license     => "GPLv2",
+    url         => "http://www.pfoe.be/~peter/trackbar/",
+    changed     => "Thu Feb 20 16:18:08 2003",
+);
+
+my %config;
+
+Irssi::settings_add_str('trackbar', 'trackbar_string' => '-');
+$config{'trackbar_string'} = Irssi::settings_get_str('trackbar_string');
+
+Irssi::settings_add_str('trackbar', 'trackbar_style' => '%K');
+$config{'trackbar_style'} = Irssi::settings_get_str('trackbar_style');
+
+Irssi::signal_add(
+    'setup changed' => sub {
+        $config{'trackbar_string'} = Irssi::settings_get_str('trackbar_string');
+        $config{'trackbar_style'}  = Irssi::settings_get_str('trackbar_style');
+        if ($config{'trackbar_style'} =~ /(?<!%)[^%]|%%|%$/) {
+            Irssi::print(
+                "trackbar: %RWarning!%n 'trackbar_style' seems to contain "
+                . "printable characters. Only use format codes (read "
+                . "formats.txt).", MSGLEVEL_CLIENTERROR);
+        }
+    }
+);
+
+Irssi::signal_add(
+    'window changed' => sub {
+        my (undef, $oldwindow) = @_;
+
+        if ($oldwindow) {
+            my $line = $oldwindow->view()->get_bookmark('trackbar');
+            $oldwindow->view()->remove_line($line) if defined $line;
+            $oldwindow->print(line($oldwindow->{'width'}), MSGLEVEL_NEVER);
+            $oldwindow->view()->set_bookmark_bottom('trackbar');
+        }
+    }
+);
+
+sub line {
+    my $width  = shift;
+    my $string = $config{'trackbar_string'};
+    $string = '-' unless defined $string;
+
+    # There is a bug in irssi's utf-8 handling on config file settings, as you 
+    # can reproduce/see yourself by the following code sniplet:
+    #
+    #   my $quake = pack 'U*', 8364;    # EUR symbol
+    #   Irssi::settings_add_str 'temp', 'temp_foo' => $quake;
+    #   Irssi::print length $quake;
+    #       # prints 1
+    #   Irssi::print length Irssi::settings_get_str 'temp_foo';
+    #       # prints 3
+    #
+    #
+    # Trackbar used to have a workaround, but on recent versions of perl/irssi
+    # it does no longer work.  Therefore, if you want your trackbar to contain
+    # unicode characters, uncomment the line below for a nice full line, or set
+    # the string to whatever char you want.
+
+    # $string = pack('U*', 0x2500);
+
+
+    my $length = length $string;
+
+    if ($length == 0) {
+        $string = '-';
+        $length = 1;
+    }
+
+    my $times = $width / $length;
+    $times = int(1 + $times) if $times != int($times);
+    $string =~ s/%/%%/g;
+    return $config{'trackbar_style'} . substr($string x $times, 0, $width);
+}
+
+# Remove trackbars on upgrade - but this doesn't really work if the scripts are not loaded in the correct order... watch out!
+
+Irssi::signal_add_first( 'session save' => sub {
+	    for my $window (Irssi::windows) {	
+		next unless defined $window;
+		my $line = $window->view()->get_bookmark('trackbar');
+		$window->view()->remove_line($line) if defined $line;
+	    }
+	}
+);
+
+sub cmd_mark {
+    my $window = Irssi::active_win();
+#    return unless defined $window;
+    my $line = $window->view()->get_bookmark('trackbar');
+    $window->view()->remove_line($line) if defined $line;
+    $window->print(line($window->{'width'}), MSGLEVEL_NEVER);
+    $window->view()->set_bookmark_bottom('trackbar');
+    Irssi::command("redraw");    
+}
+
+Irssi::command_bind('mark',   'cmd_mark');