# This script replaces the default prompt status-bar item with one capable # of displaying additional information, under either user control or via # scripts. # # INSTALL: # # Place script in ~/.irssi/scripts/ and potentially symlink into autorun/ # to ensure it starts at irssi startup. # # If not using autorun, manually load the script via: # # /script load uberprompt.pl # # If you have a custom prompt format, you may need to copy it to the # uberprompt_format setting. See below for details. # # USAGE: # # Although the script is designed primarily for other scripts to set # status information into the prompt, the following commands are available: # # TODO: Document positional settings. # # /prompt set - sets the prompt to the given argument. $p in the argument will # be replaced by the original prompt content. # A parameter corresponding to the UP_* constants listed below # is required, in the format `/prompt set -inner Hello!' # # /prompt set [-inner|-pre|-post|only] # # /prompt clear - clears the additional data provided to the prompt. # /prompt on - enables the uberprompt (things may get confused if this is used # whilst the prompt is already enabled) # /prompt off - restore the original irssi prompt and prompt_empty statusbars. # unloading the script has the same effect. # # /help prompt - show help for uberprompt commands # # Additionally, the format for the prompt can be set via: # # UBERPROMPT FORMAT: # # /set uberprompt_format # # The default is [$*$uber], which is the same as the default provided in # default.theme. $uber is a placeholder variable to contain your additions # to the prompt when using the -inner mode. # Changing this setting will update the prompt immediately, unlike editing your theme. # # An additional variable available within this format is '$uber', which expands to # the content of prompt data provided with the UP_INNER placement argument. For all # other placement arguments, it will expand to the empty string ''. # # NOTE: this setting completely overrides the prompt="..." line in your .theme # file, and may cause unexpected behaviour if your theme wishes to set a # different form of prompt. It can be simply copied from the theme file into # the above setting. # # Usage from other Scripts: signal 'change prompt' => 'string' => position # # eg: # # signal_emit 'change prompt' 'some_string', UberPrompt::UP_INNER; # # will set the prompt to include that content, by default '[$* some_string]' # # The possible position arguments are the following strings: # # UP_PRE - place the provided string before the prompt -- $string$prompt # UP_INNER - place the provided string inside the prompt -- {prompt $* $string} # UP_POST - place the provided string after the prompt -- $prompt$string # UP_ONLY - replace the prompt with the provided string -- $string # # All strings may use the special variable '$prompt' to include the prompt # verbatim at that position in the string. It is probably only useful for # the UP_ONLY mode however. '$prompt_nt' will include the prompt, minus any # trailing whitespace. # # NOTIFICATIONS: # # You can also be notified when the prompt changes in response to the previous # signal or manual commands via: # # signal_add 'prompt changed', sub { my ($text, $len) = @_; ... do something ... }; # # # NOTES FOR SCRIPT WRITERS: # # The following code snippet can be used within your own script as a preamble # to ensure that uberprompt is loaded before your script to avoid # any issues with load order. It first checks if uberprompt is loaded, and # if not, attempts to load it. If the load fails, the script will die # with an error message, otherwise it will call your app_init() function. # # ---- start of snippet ---- # my $DEBUG_ENABLED = 0; # sub DEBUG () { $DEBUG_ENABLED } # # # check we have uberprompt loaded. # # sub script_is_loaded { # return exists($Irssi::Script::{$_[0] . '::'}); # } # # if (not script_is_loaded('uberprompt')) { # # print "This script requires 'uberprompt.pl' in order to work. " # . "Attempting to load it now..."; # # Irssi::signal_add('script error', 'load_uberprompt_failed'); # Irssi::command("script load uberprompt.pl"); # # unless(script_is_loaded('uberprompt')) { # load_uberprompt_failed("File does not exist"); # } # app_init(); # } else { # app_init(); # } # # sub load_uberprompt_failed { # Irssi::signal_remove('script error', 'load_prompt_failed'); # # print "Script could not be loaded. Script cannot continue. " # . "Check you have uberprompt.pl installed in your path and " # . "try again."; # # die "Script Load Failed: " . join(" ", @_); # } # # ---- end of snippet ---- # # # # Bugs: # # * Resizing the terminal rapidly whilst using this script in debug mode # may cause irssi to crash. See bug report at # http://bugs.irssi.org/index.php?do=details&task_id=772 for details. # # TODO: # # * report failure (somehow) to clients if hte prompt is disabled. # * fix issue at autorun startup with sbar item doesn't exist. # # # # # LICENCE: # # Copyright (c) 2010 Tom Feist # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. use strict; use warnings; use Irssi; use Irssi::TextUI; use Data::Dumper; { package Irssi::Nick } our $VERSION = "0.2"; our %IRSSI = ( authors => "shabble", contact => 'shabble+irssi@metavore.org, shabble@#irssi/Freenode', name => "uberprompt", description => "Helper script for dynamically adding text " . "into the input-bar prompt.", license => "MIT", changed => "24/7/2010" ); my $DEBUG_ENABLED = 0; sub DEBUG { $DEBUG_ENABLED } my $prompt_data = ''; my $prompt_data_pos = 'UP_INNER'; my $prompt_last = ''; my $prompt_format = ''; # flag to indicate whether rendering of hte prompt should allow the replaces # theme formats to be applied to the content. my $use_replaces = 0; my $emit_request = 0; my $expando_refresh_timer; my $expando_vars = {}; pre_init(); sub pre_init { Irssi::command('statusbar prompt reset'); init(); } sub prompt_subcmd_handler { my ($data, $server, $item) = @_; #$data =~ s/\s+$//g; # strip trailing whitespace. Irssi::command_runsub('prompt', $data, $server, $item); } sub _error($) { my ($msg) = @_; Irssi::active_win->print($msg, Irssi::MSGLEVEL_CLIENTERROR); } sub _debug_print($) { return unless DEBUG; my ($msg) = @_; Irssi::active_win->print($msg, Irssi::MSGLEVEL_CLIENTCRAP); } sub _print_help { my ($args) = @_; if ($args =~ m/^\s*prompt/i) { my @help_lines = ( "", "PROMPT ON", "PROMPT OFF", "PROMPT CLEAR", "PROMPT SET { -pre | -post | -only | -inner } ", "", "Commands for manipulating the UberPrompt.", "", "/PROMPT ON enables uberprompt, replacing the existing prompt ", " statusbar-item", "/PROMPT OFF disables it, and restores the original prompt item", "/PROMPT CLEAR resets the value of any additional data set by /PROMPT SET", " or a script", "/PROMPT SET changes the contents of the prompt, according to the mode", " and content provided.", " { -inner sets the value of the \$uber psuedo-variable in the", " /set uberprompt_format setting.", " | -pre places the content before the current prompt string", " | -post places the content after the prompt string", " | -only replaces the entire prompt contents with the given string }", "", "See Also:", '', '/SET uberprompt_format -- defaults to [$*$uber]', "/SET uberprompt_autostart -- determines whether /PROMPT ON is run", " automatically when the script loads", "/set uberprompt_use_replaces -- toggles the use of the current theme", " \"replaces\" setting. Especially", " noticeable on brackets \"[ ]\" ", "", ); Irssi::print($_, Irssi::MSGLEVEL_CLIENTCRAP) for @help_lines; Irssi::signal_stop; } } sub UNLOAD { deinit(); } sub exp_lbrace() { '{' } sub exp_rbrace() { '}' } sub deinit { Irssi::expando_destroy('lbrace'); Irssi::expando_destroy('rbrace'); # remove uberprompt and return the original ones. print "Removing uberprompt and restoring original"; restore_prompt_items(); } sub init { Irssi::statusbar_item_register('uberprompt', 0, 'uberprompt_draw'); # TODO: flags to prevent these from being recomputed? Irssi::expando_create('lbrace', \&exp_lbrace, {}); Irssi::expando_create('rbrace', \&exp_rbrace, {}); Irssi::settings_add_str ('uberprompt', 'uberprompt_format', '[$*$uber] '); Irssi::settings_add_bool('uberprompt', 'uberprompt_debug', 0); Irssi::settings_add_bool('uberprompt', 'uberprompt_autostart', 1); Irssi::settings_add_bool('uberprompt', 'uberprompt_use_replaces', 0); Irssi::command_bind("prompt", \&prompt_subcmd_handler); Irssi::command_bind('prompt on', \&replace_prompt_items); Irssi::command_bind('prompt off', \&restore_prompt_items); Irssi::command_bind('prompt set', \&cmd_prompt_set); Irssi::command_bind('prompt clear', sub { Irssi::signal_emit 'change prompt', '$p', 'UP_POST'; }); my $prompt_set_args_format = "inner pre post only"; Irssi::command_set_options('prompt set', $prompt_set_args_format); Irssi::command_bind('help', \&_print_help); Irssi::signal_add('setup changed', \&reload_settings); # intialise the prompt format. reload_settings(); # make sure we redraw when necessary. Irssi::signal_add('window changed', \&uberprompt_refresh); Irssi::signal_add('window name changed', \&uberprompt_refresh); Irssi::signal_add('window changed automatic', \&uberprompt_refresh); Irssi::signal_add('window item changed', \&uberprompt_refresh); Irssi::signal_add('window item server changed', \&uberprompt_refresh); Irssi::signal_add('window server changed', \&uberprompt_refresh); Irssi::signal_add('server nick changed', \&uberprompt_refresh); Irssi::signal_add('nick mode changed', \&refresh_if_me); # install our statusbars if required. if (Irssi::settings_get_bool('uberprompt_autostart')) { replace_prompt_items(); } # API signals Irssi::signal_register({'change prompt' => [qw/string string/]}); Irssi::signal_add('change prompt' => \&change_prompt_handler); # other scripts (specifically overlay/visual) can subscribe to # this event to be notified when the prompt changes. # arguments are new contents (string), new length (int) Irssi::signal_register({'prompt changed' => [qw/string int/]}); Irssi::signal_register({'prompt length request' => []}); Irssi::signal_add('prompt length request', \&length_request_handler); } sub cmd_prompt_set { my $args = shift; my @options_list = Irssi::command_parse_options "prompt set", $args; if (@options_list) { my ($options, $rest) = @options_list; my @opt_modes = keys %$options; if (@opt_modes != 1) { _error '%_/prompt set%_ must specify exactly one mode of' . ' {-inner, -only, -pre, -post}'; return; } my $mode = 'UP_' . uc($opt_modes[0]); Irssi::signal_emit 'change prompt', $rest, $mode; } else { _error ('%_/prompt set%_ must specify a mode {-inner, -only, -pre, -post}'); } } sub refresh_if_me { my ($channel, $nick) = @_; return unless $channel and $nick; my $server = Irssi::active_server; my $window = Irssi::active_win; return unless $server and $window; my $my_chan = $window->{active}->{name}; my $my_nick = $server->parse_special('$N'); return unless $my_chan and $my_nick; _debug_print "Chan: $channel->{name}, " . "nick: $nick->{nick}, " . "me: $my_nick, chan: $my_chan"; if ($my_chan eq $channel->{name} and $my_nick eq $nick->{nick}) { uberprompt_refresh(); } } sub length_request_handler { $emit_request = 1; uberprompt_render_prompt(); $emit_request = 0; } sub reload_settings { $use_replaces = Irssi::settings_get_bool('uberprompt_use_replaces'); $DEBUG_ENABLED = Irssi::settings_get_bool('uberprompt_debug'); if (DEBUG) { Irssi::signal_add 'prompt changed', 'debug_prompt_changed'; } else { Irssi::signal_remove 'prompt changed', 'debug_prompt_changed'; } my $new = Irssi::settings_get_str('uberprompt_format'); if ($prompt_format ne $new) { _debug_print("Updated prompt format"); $prompt_format = $new; $prompt_format =~ s/\$uber/\$\$uber/; Irssi::abstracts_register(['uberprompt', $prompt_format]); $expando_vars = {}; # TODO: something clever here to check if we need to schedule # an update timer or something, rather than just refreshing on # every possible activity in init() while ($prompt_format =~ m/(?{$var_name} = Irssi::parse_special($1); } } } sub debug_prompt_changed { my ($text, $len) = @_; $text =~ s/%/%%/g; print "DEBUG_HANDLER: Prompt Changed to: \"$text\", length: $len"; } sub change_prompt_handler { my ($text, $pos) = @_; # fix for people who used prompt_info and hence the signal won't # pass the second argument. $pos = 'UP_INNER' unless defined $pos; _debug_print("Got prompt change signal with: $text, $pos"); my ($changed_text, $changed_pos); $changed_text = defined $prompt_data ? $prompt_data ne $text : 1; $changed_pos = defined $prompt_data_pos ? $prompt_data_pos ne $pos : 1; $prompt_data = $text; $prompt_data_pos = $pos; if ($changed_text || $changed_pos) { _debug_print("Redrawing prompt"); uberprompt_refresh(); } } sub _escape_prompt_special { my $str = shift; $str =~ s/\$/\$\$/g; $str =~ s/\\/\\\\/g; #$str =~ s/%/%%/g; $str =~ s/{/\${lbrace}/g; $str =~ s/}/\${rbrace}/g; return $str; } sub uberprompt_render_prompt { my $window = Irssi::active_win; my $prompt_arg = ''; # default prompt sbar arguments (from config) if (scalar( () = $window->items )) { $prompt_arg = '$[.15]{itemname}'; } else { $prompt_arg = '${winname}'; } my $prompt = ''; # rendered content of the prompt. my $theme = Irssi::current_theme; my $arg = $use_replaces ? 0 : Irssi::EXPAND_FLAG_IGNORE_REPLACES; $prompt = $theme->format_expand("{uberprompt $prompt_arg}", $arg); if ($prompt_data_pos eq 'UP_ONLY') { $prompt =~ s/\$\$uber//; # no need for recursive prompting, I hope. # TODO: only compute this if necessary? my $prompt_nt = $prompt; $prompt_nt =~ s/\s+$//; my $pdata_copy = $prompt_data; $pdata_copy =~ s/\$prompt_nt/$prompt_nt/; $pdata_copy =~ s/\$prompt/$prompt/; $prompt = $pdata_copy; } elsif ($prompt_data_pos eq 'UP_INNER' && defined $prompt_data) { my $esc = _escape_prompt_special($prompt_data); $prompt =~ s/\$\$uber/$esc/; } else { # remove the $uber marker $prompt =~ s/\$\$uber//; # and add the additional text at the appropriate position. if ($prompt_data_pos eq 'UP_PRE') { $prompt = $prompt_data . $prompt; } elsif ($prompt_data_pos eq 'UP_POST') { $prompt .= $prompt_data; } } _debug_print("rendering with: $prompt"); if (($prompt ne $prompt_last) or $emit_request) { # _debug_print("Emitting prompt changed signal"); # my $exp = Irssi::current_theme()->format_expand($text, 0); my $ps = Irssi::parse_special($prompt); Irssi::signal_emit('prompt changed', $ps, length($ps)); $prompt_last = $prompt; } return $prompt; } sub uberprompt_draw { my ($sb_item, $get_size_only) = @_; my $prompt = uberprompt_render_prompt(); my $ret = $sb_item->default_handler($get_size_only, $prompt, '', 0); _debug_print("redrawing with: $prompt"); return $ret; } sub uberprompt_refresh { Irssi::statusbar_items_redraw('uberprompt'); } sub replace_prompt_items { # remove existing ones. _debug_print("Removing original prompt"); _sbar_command('prompt', 'remove', 'prompt'); _sbar_command('prompt', 'remove', 'prompt_empty'); # add the new one. _sbar_command('prompt', 'add', 'uberprompt', qw/-alignment left -before input -priority '-1'/); _sbar_command('prompt', 'position', '100'); } sub restore_prompt_items { _sbar_command('prompt', 'remove', 'uberprompt'); _debug_print("Restoring original prompt"); _sbar_command('prompt', 'reset'); } sub _sbar_command { my ($bar, $cmd, $item, @args) = @_; my $args_str = join ' ', @args; $args_str .= ' ' if length $args_str && defined $item; my $command = sprintf 'STATUSBAR %s %s %s%s', $bar, $cmd, $args_str, defined $item ? $item : ''; _debug_print("Running command: $command"); Irssi::command($command); }