Skip to content
Tom Feist edited this page Apr 26, 2011 · 68 revisions

Some Notes on Irssi Internals.

home |
   Code Organisation
      Design Hierarchy
      Filesystem Layout
   Mainloop
   Signals, And Their Handling
      Adding Signals
      notes
   Curses and Terminals
   Input Processing (keybindings, etc)
   Window Management
   Formats, Abstracts, and Themes
   Perl Scripting Support
      Message Levels
   Hacking on the Perl API

Code Organisation

Design Hierarchy

sub1 sub2
   \ /
       xxx  IRC       COMMON ICQ  yyy
        |____|___________|____|____|
          |
         GUI (gtk/gnome, qt/kde, text, none)
          |
sub1 sub2 |
   \ /    |
       xxx  IRC    |  COMMON ICQ  yyy
        |____|_____|_____|____|____|
          |
      COMMON UI
          |
sub1 sub2 |
   \ /    |
       xxx  IRC    |    ICQ  yyy
        |____|_____|_____|____|
          |
        CORE
        /
        lib-config

    (IRC, ICQ, xxx and yyy are chat protocols ..)
    (sub1 and sub2 are submodules of IRC module, like DCC and flood protect)
    [[design.txt|http://irssi.org/documentation/design]]

Filesystem Layout

.
|-- core
|-- fe-common
|   |-- core
|   `-- irc
|       |-- dcc
|       `-- notifylist
|-- fe-none
|-- fe-text
|-- irc
|   |-- core
|   |-- dcc
|   |-- flood
|   |-- notifylist
|   `-- proxy
|-- lib-config
`-- perl
    |-- common
    |-- irc
    |-- textui
    `-- ui

Irssi is designed to be somewhat modular, so that different frontends can be attached to the IRC client bits for people who like shiney windows, and suchlike.

The code is organised into core, the really really essential bits, irc -- the bit that deals with talking to IRC servers, lib-config which handles the config file format, and perl, which implements the scripting functionality.

The other modules (cunningly prefixed with fe-*) contain code specific to various frontends, with fe-common looking after the shared code.

The rest of this document will probably only consider the fe-text frontend, because it's the most useful, and most widely used.

Mainloop

The fe-text/irssi.c contains the program entry-point, which consists of calling *_init() on just about every other module, parsing some commandline options, and then starting up the mainloop.

Irssi is built on GLib, and runs in an event-driven sort of way, using the GLib loop to handle IO Polling and timers.

[[MainLoop Docs|http://library.gnome.org/devel/glib/stable/glib-The-Main-Event-Loop.html]] The main-loop locks the terminal with a simple semaphore-like construct, executes one iteration of the GLib event loop, and then unlocks the terminal.

A SIGHUP handler is installed to re-read the config file, which is checked next.

Finally, [[dirty_check()|https://github.com/shabble/irssi/blob/links/src/fe-text/irssi.c#L112]] determines if the terminal has been resized, or if a full screen update is needed.

Signals, And Their Handling

Signals are at the core of Irssi's design. They are how modules, both internal and external, can communicate with one another, how scripts can change the behaviour of various functionality, and a whole lot more. Signals can be thought of as a type of the Publisher/Subscriber pattern.

A Simplified Description

Because Irssi is single-threaded, only a single signal can be truly "active" at any given time. However, since a handler for a given signal can cause the emission of another, a "call-stack" of signals is maintained.

When a signal is emitted, A list of subscribers to that signal is fetched, and the callback function for each subscriber is called, in order of a priority value.

The following pseudocode demonstrates how the stack-like nature of signals works. We have 3 signals, A, B, and C.

Register A, handler A1, priority 1;
Register A, handler A2, priority 2;
Register A, handler A3, priority 3;

Register B, handler B1, priority 1;

Register C, handler C1, priority 1;
Register C, handler C2, priority 2;

Example 1:

Emit A:
  Call A1;
  Call A2;
    A2 Emits B:
      Call B1;
  Call A3;
    A3 Emits C:
      Call C1;
      Call C2;

Example 2:

Emit A(args_1):
  Call A1(args_1);
    A1 modifies args_1 -> args_2, calls Continue;
  Call A2(args_2);
    A2 Emits C:
      Call C1;
        C1 calls Signal_Stop
      <C2 not called>
  Call A3;

TODO: Someone please clarify this.

The ability for signal handlers to both modify the arguments during the handler chain, as well as terminate the chain at a given point, provides great flexibility in data handling. The use of signals rather than subroutine calls also decouples the different modules, allowing additional handlers to be inserted into the chain and other modifications to be made without any recompilation.

The Gory Details

core/signals.c handles the bulk of the signal implementation. Adapters are implemented in perl/perl-signals.c to allow scripts to produce and consume signals.

At their heart, signals are just a bundle of up to 6 parameters wrapped around a unique signal ID (mapped via a hash table in the [[signals.h:signal_get_uniq_id()|https://github.com/shabble/irssi/blob/links/src/core/signals.h#L67]] macro which in turn calls [[modules.c:module_get_uniq_id_str()|https://github.com/shabble/irssi/blob/links/src/core/modules.c#L81]]).

The following structures are the primary signal data-structures.

typedef struct {
    int id; /* signal id */
    int refcount;

    int emitting;      /* signal is being emitted */
    int stop_emit;     /* this signal was stopped */
    int continue_emit; /* this signal emit was continued elsewhere */
    int remove_count;  /* number of invalid hooks in signal */

    SignalHook *hooks; /* head of linked-list, see above */
} Signal;

The SignalHooks are the structures containing the actual callback functions. They are stored in the *hooks member of the Signal struct as a linked list, and are iterated over and their callback functions executed.

typedef struct _SignalHook {
    struct _SignalHook *next; /* linked-list sibling */
    int priority;             /* typically -100 (HIGH) -- 100 (LOW) */
    const char *module;       /* where it came from */
    SIGNAL_FUNC func;         /* callback function pointer */
    void *user_data;          /* unused? */
} SignalHook;

The signals.c:signals hashtable stores Signal structures which implement a reference-counting system, flags to indicate continue or stop, and SignalHook *hooks, a singly-linked list of callback functions.

signals.c:signal_emit_real() is where most of the work happens. It marshalls the varargs, and then travels over the list of callback hooks. It stops when it hits the end of the list, or when the signal record's continue_emit differs from the

stop_emit_count comes from the signal record (from the ID hash), as does continue_count.

TODO: FIgure out the termination - emit_counts

As I understand it, the Signal structure knows how many handlers it expects to call, and increments a counter each time. signal_stop() works by setting that counter to the expected total, thus aborting the handler call loop.

TODO: Confirm

signal_stop() works by making the signal record->stop_emit count greater than the number it's already done, (rec->emitting)

<signal_continue()> works by stopping the current signal, and re-emitting it with new arguments, but only to the later items in the hooks list.

Any stale callbacks are removed via signal_hooks_clean() at the end of a signal emission, allowing for some grouped garbage collection.

Adding Signals

notes

Signals can be nested - that's why we need stop_by_name()

Any way to determine the current "signal stack"?

Curses and Terminals

  • mainwindow

  • drawing stuff

Input Processing (keybindings, etc)

Reading Keystrokes

fe-text/gui-readline is responsible for listening for keypresses, via a GLib input listener attached to STDIN.

When a keystroke is received, it calls the [[sig_input|https://github.com/shabble/irssi/blob/links/src/fe-text/gui-readline.c#L637]] function, which checks for a few things (e.g.: if it is likely part of a clipboard paste, due to the delay between characters), and then emits a "gui key pressed" signal.

The only default handler for "gui key pressed" also resides within gui-readline.c, and performs the following tasks:

  • Checks if a ENTRY_REDIRECT is in place, and if so, handles it.

  • Converts the numeric keystroke value into a string.

  • Passes the key-string to either [[keyboard.c:key_pressed()|https://github.com/shabble/irssi/blob/links/src/fe-common/core/keyboard.c#L556]] or [[gui-entry.c:gui_entry_insert_char()|https://github.com/shabble/irssi/blob/links/src/fe-text/gui-entry.c#L488]] depending on the value of escape_next_key

    Note: gui_entry_insert_char() gets called anyway, if key_pressed() returns > 0

Handling Key-Bindings.

Bindings are mostly handled by fe-common/core/keyboard.c.

History (inc. window level history)

Window Management

How windows are created/manipulated, drawn.

Stuff about split windows

Message levels

Highlights / Activity

Formats, Abstracts, and Themes

Internals of parsing/colours/layering of formats/theme abstracts, defaults.

Perl Scripting Support

Where datastructures are set
how they are made
.xs files where everything lives / code organisation

Some of these things are:

hilights

For a standard message, such as a PUBLIC:

"message public" handled by fe-common/core/fe-messages.c

Message Levels
Abstract Replacements
Theme formats
Module (/format) formats

Message Levels

Message levels are defined in src/core/levels.h and some support functions in src/core/levels.c

We have:

int level_get(str level)

Special cases: ALL, * both return MSGLEVEL_ALL, and NEVER returns 0. The remainder are looked up in a big static string array by name to find the appropriate numeric level. Partial matches are permitted as long as they are unambigious.

int level2bits(str level, int *errorp)
str bits2level(int bits)
int combine_level(int dest, str src)

Hacking on the Perl API

Notes:

create a new blah.xs file in src/perl/blah.

Ensure that you have the following entry:

MODULE = Irssi::blah::YourPackage PACKAGE = Irssi::Some::Namespace
PROTOTYPES: ENABLE

in your .xs file

Add the appropriate entry to src/perl/Makefile.am

Probably under blah_sources.

Edit the base .xs file for your package

Find the BOOT: entry, and add

irssi_boot(BLAH__YourPackage);
recompile

Running ./autogen.sh probably doesn't hurt. The configure script might regen all the makefiles, but it's hard to be sure.

See an example at https://github.com/shabble/irssi-scripts/blob/patches/patches/binding-api.patch

FILES

Full listing of all source files. (Minus some spurious makefiles and other cruft.) . |-- common.h |-- core | |-- args.c | |-- args.h | |-- channel-rec.h | |-- channel-setup-rec.h | |-- channels-setup.c | |-- channels-setup.h | |-- channels.c | |-- channels.h | |-- chat-commands.c | |-- chat-protocols.c | |-- chat-protocols.h | |-- chatnet-rec.h | |-- chatnets.c | |-- chatnets.h | |-- commands.c | |-- commands.h | |-- core.c | |-- core.h | |-- expandos.c | |-- expandos.h | |-- ignore.c | |-- ignore.h | |-- levels.c | |-- levels.h | |-- line-split.c | |-- line-split.h | |-- log-away.c | |-- log.c | |-- log.h | |-- masks.c | |-- masks.h | |-- misc.c | |-- misc.h | |-- module.h | |-- modules-load.c | |-- modules-load.h | |-- modules.c | |-- modules.h | |-- net-disconnect.c | |-- net-disconnect.h | |-- net-nonblock.c | |-- net-nonblock.h | |-- net-sendbuffer.c | |-- net-sendbuffer.h | |-- network-openssl.c | |-- network.c | |-- network.h | |-- nick-rec.h | |-- nicklist.c | |-- nicklist.h | |-- nickmatch-cache.c | |-- nickmatch-cache.h | |-- pidwait.c | |-- pidwait.h | |-- queries.c | |-- queries.h | |-- query-rec.h | |-- rawlog.c | |-- rawlog.h | |-- recode.c | |-- recode.h | |-- server-connect-rec.h | |-- server-rec.h | |-- server-setup-rec.h | |-- servers-reconnect.c | |-- servers-reconnect.h | |-- servers-setup.c | |-- servers-setup.h | |-- servers.c | |-- servers.h | |-- session.c | |-- session.h | |-- settings.c | |-- settings.h | |-- signals.c | |-- signals.h | |-- special-vars.c | |-- special-vars.h | |-- window-item-def.h | |-- window-item-rec.h | |-- write-buffer.c | `-- write-buffer.h |-- fe-common | |-- core | | |-- chat-completion.c | | |-- chat-completion.h | | |-- command-history.c | | |-- command-history.h | | |-- completion.c | | |-- completion.h | | |-- fe-channels.c | | |-- fe-channels.h | | |-- fe-common-core.c | | |-- fe-common-core.h | | |-- fe-core-commands.c | | |-- fe-core-commands.h | | |-- fe-exec.c | | |-- fe-exec.h | | |-- fe-expandos.c | | |-- fe-help.c | | |-- fe-ignore-messages.c | | |-- fe-ignore.c | | |-- fe-log.c | | |-- fe-messages.c | | |-- fe-messages.h | | |-- fe-modules.c | | |-- fe-queries.c | | |-- fe-queries.h | | |-- fe-recode.c | | |-- fe-recode.h | | |-- fe-server.c | | |-- fe-settings.c | | |-- fe-windows.c | | |-- fe-windows.h | | |-- formats.c | | |-- formats.h | | |-- hilight-text.c | | |-- hilight-text.h | | |-- keyboard.c | | |-- keyboard.h | | |-- module-formats.c | | |-- module-formats.h | | |-- module.h | | |-- printtext.c | | |-- printtext.h | | |-- themes.c | | |-- themes.h | | |-- utf8.c | | |-- utf8.h | | |-- wcwidth.c | | |-- window-activity.c | | |-- window-activity.h | | |-- window-commands.c | | |-- window-items.c | | |-- window-items.h | | |-- windows-layout.c | | `-- windows-layout.h | `-- irc | |-- dcc | | |-- fe-dcc-chat-messages.c | | |-- fe-dcc-chat.c | | |-- fe-dcc-get.c | | |-- fe-dcc-send.c | | |-- fe-dcc-server.c | | |-- fe-dcc.c | | |-- fe-dcc.h | | |-- module-formats.c | | |-- module-formats.h | | `-- module.h | |-- fe-common-irc.c | |-- fe-ctcp.c | |-- fe-events-numeric.c | |-- fe-events.c | |-- fe-irc-channels.c | |-- fe-irc-commands.c | |-- fe-irc-messages.c | |-- fe-irc-queries.c | |-- fe-irc-server.c | |-- fe-irc-server.h | |-- fe-ircnet.c | |-- fe-modes.c | |-- fe-netjoin.c | |-- fe-netsplit.c | |-- fe-whois.c | |-- irc-completion.c | |-- module-formats.c | |-- module-formats.h | |-- module.h | `-- notifylist | |-- fe-notifylist.c | |-- module-formats.c | |-- module-formats.h | `-- module.h |-- fe-none | |-- irssi.c | `-- module.h |-- fe-text | |-- gui-entry.c | |-- gui-entry.h | |-- gui-expandos.c | |-- gui-printtext.c | |-- gui-printtext.h | |-- gui-readline.c | |-- gui-readline.h | |-- gui-windows.c | |-- gui-windows.h | |-- irssi.c | |-- lastlog.c | |-- mainwindow-activity.c | |-- mainwindows-layout.c | |-- mainwindows.c | |-- mainwindows.h | |-- module-formats.c | |-- module-formats.h | |-- module.h | |-- statusbar-config.c | |-- statusbar-config.h | |-- statusbar-item.h | |-- statusbar-items.c | |-- statusbar.c | |-- statusbar.h | |-- term-curses.c | |-- term-dummy.c | |-- term-terminfo.c | |-- term.c | |-- term.h | |-- terminfo-core.c | |-- terminfo-core.h | |-- textbuffer-commands.c | |-- textbuffer-view.c | |-- textbuffer-view.h | |-- textbuffer.c | |-- textbuffer.h | `-- tparm.c |-- filelist.txt |-- irc | |-- core | | |-- bans.c | | |-- bans.h | | |-- channel-events.c | | |-- channel-rejoin.c | | |-- channel-rejoin.h | | |-- channels-query.c | | |-- ctcp.c | | |-- ctcp.h | | |-- irc-channels-setup.c | | |-- irc-channels.c | | |-- irc-channels.h | | |-- irc-chatnets.c | | |-- irc-chatnets.h | | |-- irc-commands.c | | |-- irc-commands.h | | |-- irc-core.c | | |-- irc-expandos.c | | |-- irc-masks.c | | |-- irc-masks.h | | |-- irc-nicklist.c | | |-- irc-nicklist.h | | |-- irc-queries.c | | |-- irc-queries.h | | |-- irc-servers-reconnect.c | | |-- irc-servers-setup.c | | |-- irc-servers-setup.h | | |-- irc-servers.c | | |-- irc-servers.h | | |-- irc-session.c | | |-- irc.c | | |-- irc.h | | |-- lag.c | | |-- massjoin.c | | |-- mode-lists.c | | |-- mode-lists.h | | |-- modes.c | | |-- modes.h | | |-- module.h | | |-- netsplit.c | | |-- netsplit.h | | |-- servers-idle.c | | |-- servers-idle.h | | |-- servers-redirect.c | | `-- servers-redirect.h | |-- dcc | | |-- dcc-autoget.c | | |-- dcc-chat.c | | |-- dcc-chat.h | | |-- dcc-file-rec.h | | |-- dcc-file.h | | |-- dcc-get.c | | |-- dcc-get.h | | |-- dcc-queue.c | | |-- dcc-queue.h | | |-- dcc-rec.h | | |-- dcc-resume.c | | |-- dcc-send.c | | |-- dcc-send.h | | |-- dcc-server.c | | |-- dcc-server.h | | |-- dcc.c | | |-- dcc.h | | `-- module.h | |-- flood | | |-- autoignore.c | | |-- autoignore.h | | |-- flood.c | | |-- flood.h | | `-- module.h | |-- notifylist | | |-- module.h | | |-- notify-commands.c | | |-- notify-ison.c | | |-- notify-setup.c | | |-- notify-setup.h | | |-- notify-whois.c | | |-- notifylist.c | | `-- notifylist.h | `-- proxy | |-- dump.c | |-- listen.c | |-- module.h | |-- proxy.c | `-- proxy.h |-- lib-config | |-- get.c | |-- iconfig.h | |-- module.h | |-- parse.c | |-- set.c | `-- write.c `-- perl |-- common | |-- Channel.xs | |-- Core.xs | |-- Expando.xs | |-- Ignore.xs | |-- Irssi.bs | |-- Irssi.pm | |-- Irssi.xs | |-- Log.xs | |-- Makefile.PL.in | |-- Masks.xs | |-- Query.xs | |-- Rawlog.xs | |-- Server.xs | |-- Settings.xs | |-- module.h | `-- typemap |-- get-signals.pl |-- irc | |-- Channel.xs | |-- Client.xs | |-- Ctcp.xs | |-- Dcc.xs | |-- Irc.bs | |-- Irc.pm | |-- Irc.xs | |-- Makefile.PL.in | |-- Modes.xs | |-- Netsplit.xs | |-- Notifylist.xs | |-- Query.xs | |-- Server.xs | |-- module.h | `-- typemap |-- irssi-core.pl |-- irssi-core.pl.h |-- module-fe.h |-- module-formats.c |-- module-formats.h |-- module.h |-- perl-common.c |-- perl-common.h |-- perl-core.c |-- perl-core.h |-- perl-fe.c |-- perl-signals-list.h |-- perl-signals.c |-- perl-signals.h |-- perl-sources.c |-- perl-sources.h |-- textui | |-- Makefile.PL.in | |-- Statusbar.xs | |-- TextBuffer.xs | |-- TextBufferView.xs | |-- TextUI.bs | |-- TextUI.pm | |-- TextUI.xs | |-- module.h | `-- typemap `-- ui |-- Formats.xs |-- Makefile.PL.in |-- Themes.xs |-- UI.bs |-- UI.pm |-- UI.xs |-- Window.xs |-- module.h `-- typemap

Clone this wiki locally