aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES11
-rw-r--r--Makefile65
-rw-r--r--NOTES31
-rw-r--r--TODO13
-rw-r--r--ansicode.txt779
-rw-r--r--array.h91
-rw-r--r--buffer-poll.c57
-rw-r--r--buffer.c181
-rw-r--r--command.c165
-rw-r--r--input.c825
-rw-r--r--local.c690
-rw-r--r--log.c218
-rw-r--r--screen.c827
-rw-r--r--server.c999
-rw-r--r--session.c189
-rw-r--r--tmux.c514
-rw-r--r--tmux.h583
-rw-r--r--window.c317
-rw-r--r--xmalloc.c243
19 files changed, 6798 insertions, 0 deletions
diff --git a/CHANGES b/CHANGES
new file mode 100644
index 00000000..236836bf
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,11 @@
+09 July 2007
+
+* Initial import to CVS. Basic functions are working, albeit with a couple of
+ showstopper memory bugs and many missing features. Detaching, reattaching,
+ creating new sessions, listing sessions work acceptably for using with shells.
+ Simple curses programs (top, systat, tetris) and more complicated ones (mutt,
+ emacs) that don't require scrolling regions (ESC[r) mostly work fine
+ (including mutt, emacs). No status bar yet and no key remapping or other
+ customisation.
+
+
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..60f38ac3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,65 @@
+# $Id: Makefile,v 1.1.1.1 2007-07-09 19:03:33 nicm Exp $
+
+.SUFFIXES: .c .o .y .l .h
+.PHONY: clean
+
+PROG= tmux
+VERSION= 0.1
+
+OS!= uname
+REL!= uname -r
+DATE!= date +%Y%m%d-%H%M
+
+SRCS= tmux.c server.c buffer.c buffer-poll.c xmalloc.c input.c screen.c \
+ window.c session.c local.c log.c command.c
+HDRS= tmux.h
+
+LEX= lex
+YACC= yacc -d
+
+CC= cc
+INCDIRS+= -I. -I- -I/usr/local/include
+CFLAGS+= -DBUILD="\"$(VERSION) ($(DATE))\""
+CFLAGS+= -g -ggdb -DDEBUG
+#CFLAGS+= -pedantic -std=c99
+#CFLAGS+= -Wredundant-decls -Wdisabled-optimization -Wendif-labels
+CFLAGS+= -Wno-long-long -Wall -W -Wnested-externs -Wformat=2
+CFLAGS+= -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations
+CFLAGS+= -Wwrite-strings -Wshadow -Wpointer-arith -Wcast-qual -Wsign-compare
+CFLAGS+= -Wundef -Wshadow -Wbad-function-cast -Winline -Wcast-align
+
+PREFIX?= /usr/local
+INSTALLBIN= install -g bin -o root -m 555
+INSTALLMAN= install -g bin -o root -m 444
+
+LDFLAGS+= -L/usr/local/lib
+LIBS+= -lutil -lncurses
+
+OBJS= ${SRCS:S/.c/.o/:S/.y/.o/:S/.l/.o/}
+
+CLEANFILES= ${PROG} *.o .depend *~ ${PROG}.core *.log
+
+.c.o:
+ ${CC} ${CFLAGS} ${INCDIRS} -c ${.IMPSRC} -o ${.TARGET}
+
+.l.o:
+ ${LEX} ${.IMPSRC}
+ ${CC} ${CFLAGS} ${INCDIRS} -c lex.yy.c -o ${.TARGET}
+
+.y.o:
+ ${YACC} ${.IMPSRC}
+ ${CC} ${CFLAGS} ${INCDIRS} -c y.tab.c -o ${.TARGET}
+
+all: .depend ${PROG}
+
+${PROG}: ${OBJS}
+ ${CC} ${LDFLAGS} -o ${PROG} ${LIBS} ${OBJS}
+
+.depend: ${HDRS}
+ -mkdep ${CFLAGS} ${INCDIRS} ${SRCS:M*.c}
+
+depend:
+ mkdep ${CFLAGS} ${INCDIRS} ${SRCS:M*.c}
+
+clean:
+ rm -f ${CLEANFILES}
diff --git a/NOTES b/NOTES
new file mode 100644
index 00000000..ff4904ed
--- /dev/null
+++ b/NOTES
@@ -0,0 +1,31 @@
+Command prefix is C-b.
+
+Commands: d detach
+ c create new terminal
+ n next terminal
+ p previous terminal
+ r refresh screen
+ t set window name
+ 0-9 select window
+
+There is one default server process per user which puts its socket in
+/tmp/tmux-UID. It is created the first time tmux is run and subsequent
+invocations will connect to the same server. The server holds multiple
+sessions, call tmux with "-n <session name>" to create a session or attach to
+an existing session. All the sessions may be listed with -l, or the windows of
+a single session with "-ln <session name>". Sessions are destroyed when no
+windows remain attached to them.
+
+Another server process can be used by specifying an alternative socket path with
+"-s <path>" but it shouldn't normally be required.
+
+You can set the window title (listed in -l), using the \e] escape sequence. For
+example:
+
+ $ echo -n \\033]0;My Title\\007
+
+There is currently no method for setting the window name (what will eventually
+be shown in the status bar).
+
+You might get message "couldn't find server" after a crash, in this case you
+must remove the /tmp/tmux-`id -u` file manually.
diff --git a/TODO b/TODO
new file mode 100644
index 00000000..eb02c350
--- /dev/null
+++ b/TODO
@@ -0,0 +1,13 @@
+- key remapping
+- decide if TIOCPKT is necessary and either handle it or remove the code
+- it would be nice if there wasn't so much copying buffers about, audit uses
+- window names
+- status bar
+- useful env vars like WINDOW
+- lots of scripting love: add, remove, move around windows, status bar
+- sort out who controls the buffers in local.c a bit
+- better checking/emulation for missing term requirements
+- alt charset, borders etc (terminfo(5)/Line Graphics)
+- use default shell rather than fixed
+- wrap windows with forward/back
+- new window command prompt
diff --git a/ansicode.txt b/ansicode.txt
new file mode 100644
index 00000000..8767b9e7
--- /dev/null
+++ b/ansicode.txt
@@ -0,0 +1,779 @@
+Summary of ANSI standards for ASCII terminals Joe Smith, 18-May-84
+
+Contents:
+ 1. Overview and Definitions
+ 2. General rules for interpreting an ESCape Sequence
+ 3. General rules for interpreting a Control Sequence
+ 4. C0 and C1 control codes in numeric order
+ 5. Two and three-character ESCape Sequences in numeric order
+ 6. Control Sequences in numeric order
+ 7. VT100 emulation requirements
+
+The VT100 USER GUIDE and ANSI standard X3.64-1979 both list the ANSI ESCape
+sequences in alphabetic order by mnemonic, but do not have a have a cross
+reference in order by ASCII code. This paper lists the combination of all
+definitions from the three ANSI standards in numeric order. For a description
+of the advantages of using these standards, see the article "Toward
+Standardized Video Terminals" in the April-1984 issue of BYTE magazine.
+
+ANSI X3.4-1977 defines the 7-bit ASCII character set (C0 and G0). It was
+written in 1968, revised in 1977, and explains the decisions made in laying out
+the ASCII code. In particular, it explains why ANSI chose to make ASCII
+incompatible with EBCDIC in order to make it self-consistant.
+
+ANSI X3.41-1974 introduces the idea of an 8-bit ASCII character set (C1 and G1
+in addition to the existing C0 and G0). It describes how to use the 8-bit
+features in a 7-bit environment. X3.41 defines the format of all ESCape
+sequences, but defines only the 3-character ones with a parameter character
+in the middle. These instruct the terminal how to interpret the C0, G0, C1,
+and G1 characters (such as by selecting different character-set ROMs).
+
+ Note: NAPLPS does videotex graphics by redefining the C1 set and
+ selecting alternate G0, G1, G2, and G3 sets.
+ See the February 1983 issue of BYTE magazine for details.
+
+ANSI X3.64-1979 defines the remaining ESCape sequences. It defines all the C1
+control characters, and specifies that certain two-character ESCape sequences
+in the 7-bit environment are to act exactly like the 8-bit C1 control set.
+X3.64 introduces the idea of a Control-Sequence, which starts with CSI
+character, has an indefinite length, and is terminated by an alphabetic
+character. The VT100 was one of the first terminals to implement this
+standard.
+
+Definitions:
+
+ Control Character - A single character with an ASCII code with the range
+ of 000 to 037 and 200 to 237 octal, 00 to 1F and 80 to 9F hex.
+
+ Escape Sequence - A two or three character string staring with ESCape.
+ (Four or more character strings are allowed but not defined.)
+
+ Control Sequence - A string starting with CSI (233 octal, 9B hex) or
+ with ESCape Left-Bracket, and terminated by an alphabetic character.
+ Any number of parameter characters (digits 0 to 9, semicolon, and
+ question mark) may appear within the Control Sequence. The terminating
+ character may be preceded by an intermediate character (such as space).
+ Character classifications:
+
+C0 Control 000-037 octal, 00-1F hex (G0 is 041-176 octal, 21-7E hex)
+SPACE 040+240 octal, 20+A0 hex Always and everywhere a blank space
+Intermediate 040-057 octal, 20-2F hex !"#$%&'()*+,-./
+Parameters 060-077 octal, 30-3F hex 0123456789:;<=>?
+Uppercase 100-137 octal, 40-5F hex @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
+Lowercase 140-176 octal, 60-7E hex `abcdefghijlkmnopqrstuvwxyz{|}~
+Alphabetic 100-176 octal, 40-7E hex (all of upper and lower case)
+Delete 177 octal, 7F hex Always and everywhere ignored
+C1 Control 200-237 octal, 80-9F hex 32 additional control characters
+G1 Displayable 241-376 octal, A1-FE hex 94 additional displayable characters
+Special 240+377 octal, A0+FF hex Same as SPACE and DELETE
+
+Note that in this paper, the terms uppercase, lowercase, and alphabetics
+include more characters than just A to Z.
+
+------------------------------------------------------------------------------
+
+General rules for interpreting an ESCape Sequence:
+
+ An ESCape Sequence starts with the ESC character (033 octal, 1B hex).
+The length of the ESCape Sequence depends on the character that immediately
+follows the ESCape.
+
+If the next character is
+ C0 control: Interpret it first, then resume processing ESCape sequence.
+ Example: CR, LF, XON, and XOFF work as normal within an ESCape sequence.
+ Intermediate: Expect zero or more intermediates, a parameter terminates
+ a private function, an alphabetic terminates a standard sequence.
+ Example: ESC ( A defines standard character set, ESC ( 0 a DEC set.
+ Parameter: End of a private 2-character escape sequence.
+ Example: ESC = sets special keypad mode, ESC > clears it.
+ Uppercase: Translate it into a C1 control character and act on it.
+ Example: ESC D does indexes down, ESC M indexes up. (CSI is special)
+ Lowercase: End of a standard 2-character escape sequence.
+ Example: ESC c resets the terminal.
+ Delete: Ignore it, and continue interpreting the ESCape sequence
+ C1 and G1: Treat the same as their 7-bit counterparts
+
+ Note that CSI is the two-character sequence ESCape left-bracket or the 8-bit
+C1 code of 233 octal, 9B hex. CSI introduces a Control Sequence, which
+continues until an alphabetic character is received.
+
+General rules for interpreting a Control Sequence:
+
+1) It starts with CSI, the Control Sequence Introducer.
+2) It contains any number of parameter characters (0123456789:;<=>?).
+3) It terminates with an alphabetic character.
+4) Intermediate characters (if any) immediately precede the terminator.
+
+If the first character after CSI is one of "<=>?" (074-077 octal, 3C-3F hex),
+then Control Sequence is to be interpreted according to private standards (such
+as setting and resetting modes not defined by ANSI). The terminal should
+expect any number of numeric parameters, separated by semicolons (073 octal,
+3B hex). Only after the terminating alphabetic character is received should
+the terminal act on the Control Sequence.
+
+=============================================================================
+ C0 set of 7-bit control characters (from ANSI X3.4-1977).
+
+Oct Hex Name * (* marks function used in DEC VT series or LA series terminals)
+--- -- - --- - --------------------------------------------------------------
+000 00 @ NUL * Null filler, terminal should ignore this character
+001 01 A SOH Start of Header
+002 02 B STX Start of Text, implied end of header
+003 03 C ETX End of Text, causes some terminal to respond with ACK or NAK
+004 04 D EOT End of Transmission
+005 05 E ENQ * Enquiry, causes terminal to send ANSWER-BACK ID
+006 06 F ACK Acknowledge, usually sent by terminal in response to ETX
+007 07 G BEL * Bell, triggers the bell, buzzer, or beeper on the terminal
+010 08 H BS * Backspace, can be used to define overstruck characters
+011 09 I HT * Horizontal Tabulation, move to next predetermined position
+012 0A J LF * Linefeed, move to same position on next line (see also NL)
+013 0B K VT * Vertical Tabulation, move to next predetermined line
+014 0C L FF * Form Feed, move to next form or page
+015 0D M CR * Carriage Return, move to first character of current line
+016 0E N SO * Shift Out, switch to G1 (other half of character set)
+017 0F O SI * Shift In, switch to G0 (normal half of character set)
+020 10 P DLE Data Link Escape, interpret next control character specially
+021 11 Q XON * (DC1) Terminal is allowed to resume transmitting
+022 12 R DC2 Device Control 2, causes ASR-33 to activate paper-tape reader
+023 13 S XOFF* (DC2) Terminal must pause and refrain from transmitting
+024 14 T DC4 Device Control 4, causes ASR-33 to deactivate paper-tape reader
+025 15 U NAK Negative Acknowledge, used sometimes with ETX and ACK
+026 16 V SYN Synchronous Idle, used to maintain timing in Sync communication
+027 17 W ETB End of Transmission block
+030 18 X CAN * Cancel (makes VT100 abort current escape sequence if any)
+031 19 Y EM End of Medium
+032 1A Z SUB * Substitute (VT100 uses this to display parity errors)
+033 1B [ ESC * Prefix to an ESCape sequence
+034 1C \ FS File Separator
+035 1D ] GS Group Separator
+036 1E ^ RS * Record Separator (sent by VT132 in block-transfer mode)
+037 1F _ US Unit Separator
+
+040 20 SP * Space (should never be defined to be otherwise)
+177 7F DEL * Delete, should be ignored by terminal
+
+==============================================================================
+ C1 set of 8-bit control characters (from ANSI X3.64-1979)
+
+Oct Hex Name * (* marks function used in DEC VT series or LA series terminals)
+--- -- - --- - --------------------------------------------------------------
+200 80 @ Reserved for future standardization
+201 81 A Reserved
+202 82 B Reserved
+203 83 C Reserved
+204 84 D IND * Index, moves down one line same column regardless of NL
+205 85 E NEL * NEw Line, moves done one line and to first column (CR+LF)
+206 86 F SSA Start of Selected Area to be sent to auxiliary output device
+207 87 G ESA End of Selected Area to be sent to auxiliary output device
+210 88 H HTS * Horizontal Tabulation Set at current position
+211 89 I HTJ Hor Tab Justify, moves string to next tab position
+212 8A J VTS Vertical Tabulation Set at current line
+213 8B K PLD Partial Line Down (subscript)
+214 8C L PLU Partial Line Up (superscript)
+215 8D M RI * Reverse Index, go up one line, reverse scroll if necessary
+216 8E N SS2 * Single Shift to G2
+217 8F O SS3 * Single Shift to G3 (VT100 uses this for sending PF keys)
+220 90 P DCS * Device Control String, terminated by ST (VT125 enters graphics)
+221 91 Q PU1 Private Use 1
+222 92 R PU2 Private Use 2
+223 93 S STS Set Transmit State
+224 94 T CCH Cancel CHaracter, ignore previous character
+225 95 U MW Message Waiting, turns on an indicator on the terminal
+226 96 V SPA Start of Protected Area
+227 97 W EPA End of Protected Area
+230 98 X Reserved for for future standard
+231 99 Y Reserved
+232 9A Z * Reserved, but causes DEC terminals to respond with DA codes
+233 9B [ CSI * Control Sequence Introducer (described in a seperate table)
+234 9C \ ST * String Terminator (VT125 exits graphics)
+235 9D ] OSC Operating System Command (reprograms intelligent terminal)
+236 9E ^ PM Privacy Message (password verification), terminated by ST
+237 9F _ APC Application Program Command (to word processor), term by ST
+
+==============================================================================
+ Character set selection sequences (from ANSI X3.41-1974)
+ All are 3 characters long (including the ESCape). Alphabetic characters
+ as 3rd character are defined by ANSI, parameter characters as 3rd character
+ may be interpreted differently by each terminal manufacturer.
+
+Oct Hex * (* marks function used in DEC VT series or LA series terminals)
+--- -- -- - ------------------------------------------------------------------
+040 20 ANNOUNCER - Determines whether to use 7-bit or 8-bit ASCII
+ A G0 only will be used. Ignore SI, SO, and G1.
+ B G0 and G1 used internally. SI and SO affect G0, G1 is ignored.
+ C G0 and G1 in an 8-bit only environment. SI and SO are ignored.
+ D G0 and G1 are used, SI and SO affect G0.
+ E
+ F * 7-bit transmission, VT240/PRO350 sends CSI as two characters ESC [
+ G * 8-bit transmission, VT240/PRO350 sends CSI as single 8-bit character
+041 21 ! Select C0 control set (choice of 63 standard, 16 private)
+042 22 " Select C1 control set (choice of 63 standard, 16 private)
+043 23 # Translate next character to a special single character
+ #3 * DECDHL1 - Double height line, top half
+ #4 * DECDHL2 - Double height line, bottom half
+ #5 * DECSWL - Single width line
+ #6 * DECDWL - Double width line
+ #7 * DECHCP - Make a hardcopy of the graphics screen (GIGI,VT125,VT241)
+ #8 * DECALN - Alignment display, fill screen with "E" to adjust focus
+044 24 $ MULTIBYTE CHARACTERS - Displayable characters require 2-bytes each
+045 25 % SPECIAL INTERPRETATION - Such as 9-bit data
+046 26 & Reserved for future standardization
+047 27 ' Reserved for future standardization
+050 28 ( * SCS - Select G0 character set (choice of 63 standard, 16 private)
+ (0 * DEC VT100 line drawing set (affects lowercase characters)
+ (1 * DEC Alternate character ROM set (RAM set on GIGI and VT220)
+ (2 * DEC Alternate character ROM set with line drawing
+ (5 * DEC Finnish on LA100
+ (6 * DEC Norwegian/Danish on LA100
+ (7 * DEC Swedish on LA100
+ (9 * DEC French Canadian
+ (< * DEC supplemental graphics (everything not in USASCII)
+ (A * UKASCII (British pound sign)
+ (B * USASCII (American pound sign)
+ (C * ISO Finnish on LA120
+ (E * ISO Norwegian/Danish on LA120
+ (H * ISO Swedish on LA120
+ (K * ISO German on LA100,LA120
+ (R * ISO French on LA100,LA120
+ (Y * ISO Italian on LA100
+ (Z * ISO Spanish on LA100
+051 29 ) * SCS - Select G1 character set (choice of 63 standard, 16 private)
+ * (same character sets as listed under G0)
+052 2A * * SCS - Select G2 character set
+ * (same character sets as listed under G0)
+053 2B + * SCS - Select G3 character set
+ * (same character sets as listed under G0)
+054 2C , SCS - Select G0 character set (additional 63+16 sets)
+055 2D - SCS - Select G1 character set (additional 63+16 sets)
+056 2E . SCS - Select G2 character set
+057 2F / SCS - Select G3 character set
+
+==============================================================================
+ Private two-character escape sequences (allowed by ANSI X3.41-1974)
+ These can be defined differently by each terminal manufacturer.
+
+Oct Hex * (* marks function used in DEC VT series or LA series terminals)
+--- -- - - ------------------------------------------------------------------
+060 30 0
+061 31 1 DECGON graphics on for VT105, DECHTS horiz tab set for LA34/LA120
+062 32 2 DECGOFF graphics off VT105, DECCAHT clear all horz tabs LA34/LA120
+063 33 3 DECVTS - set vertical tab for LA34/LA120
+064 34 4 DECCAVT - clear all vertical tabs for LA34/LA120
+065 35 5 * DECXMT - Host requests that VT132 transmit as if ENTER were pressed
+066 36 6
+067 37 7 * DECSC - Save cursor position and character attributes
+070 38 8 * DECRC - Restore cursor and attributes to previously saved position
+071 39 9
+072 3A :
+073 3B ;
+074 3C < * DECANSI - Switch from VT52 mode to VT100 mode
+075 3D = * DECKPAM - Set keypad to applications mode (ESCape instead of digits)
+076 3E > * DECKPNM - Set keypad to numeric mode (digits intead of ESCape seq)
+077 3F ?
+
+ DCS Device Control Strings used by DEC terminals (ends with ST)
+
+Pp = Start ReGIS graphics (VT125, GIGI, VT240, PRO350)
+Pq = Start SIXEL graphics (screen dump to LA34, LA100, screen load to VT125)
+Pr = SET-UP data for GIGI, $PrVC0$\ disables both visible cursors.
+Ps = Reprogram keys on the GIGI, $P0sDIR<CR>$\ makes keypad 0 send "DIR<CR>"
+ 0-9=digits on keypad, 10=ENTER, 11=minus, 12=comma, 13=period,
+ 14-17=PF1-PF4, 18-21=cursor keys. Enabled by $[?23h (PK1).
+Pt = Start VT105 graphics on a VT125
+
+==============================================================================
+
+ Standard two-character escape sequences (defined by ANSI X3.64-1979)
+
+100 40 @ See description of C1 control characters
+ An ESCape followed by one of these uppercase characters is translated
+ to an 8-bit C1 control character before being interpreted.
+220 90 P DCS - Device Control String, terminated by ST - see table above.
+133 5B [ CSI - Control Sequence Introducer - see table below.
+137 5F _ See description of C1 control characters
+
+==============================================================================
+
+ Indepenent control functions (from Appendix E of X3.64-1977).
+ These four controls have the same meaning regardless of the current
+ definition of the C0 and C1 control sets. Each control is a two-character
+ ESCape sequence, the 2nd character is lowercase.
+
+Oct Hex * (* marks function used in DEC VT series or LA series terminals)
+--- -- - - --------------------------------------------------------------------
+140 60 ` DMI - Disable Manual Input
+141 61 a INT - INTerrupt the terminal and do special action
+142 62 b EMI - Enable Manual Input
+143 63 c * RIS - Reset to Initial State (VT100 does a power-on reset)
+ ... The remaining lowercase characters are reserved by ANSI.
+153 6B k NAPLPS lock-shift G1 to GR
+154 6C l NAPLPS lock-shift G2 to GR
+155 6D m NAPLPS lock-shift G3 to GR
+156 6E n * LS2 - Shift G2 to GL (extension of SI) VT240,NAPLPS
+157 6F o * LS3 - Shift G3 to GL (extension of SO) VT240,NAPLPS
+ ... The remaining lowercase characters are reserved by ANSI.
+174 7C | * LS3R - VT240 lock-shift G3 to GR
+175 7D } * LS2R - VT240 lock-shift G2 to GR
+176 7E ~ * LS1R - VT240 lock-shift G1 to GR
+
+==============================================================================
+ Control Sequences (defined by ANSI X3.64-1979)
+
+Control Sequences are started by either ESC [ or CSI and are terminated by an
+"alphabetic" character (100 to 176 octal, 40 to 7E hex). Intermediate
+characters are space through slash (40 to 57 octal, 20 to 2F hex) and parameter
+characters are zero through question mark (60 to 77 octal, 30 to 3F hex,
+including digits and semicolon). Parameters consist of zero or more decimal
+numbers separated by semicolons. Leading zeros are optional, leading blanks
+are not allowed. If no digits precede the final character, the default
+parameter is used. Many functions treat a parameter of 0 as if it were 1.
+
+Oct Hex * (* marks function used in DEC VT series or LA series terminals)
+--- -- - - --------------------------------------------------------------------
+100 40 @ ICH - Insert CHaracter
+ [10@ = Make room for 10 characters at current position
+101 41 A * CUU - CUrsor Up
+ * [A = Move up one line, stop at top of screen, [9A = move up 9
+102 42 B * CUD - CUrsor Down
+ * [B = Move down one line, stop at bottom of screen
+103 43 C * CUF - CUrsor Forward
+ * [C = Move forward one position, stop at right edge of screen
+104 44 D * CUB - CUrsor Backward
+ * [D = Same as BackSpace, stop at left edge of screen
+105 45 E CNL - Cursor to Next Line
+ [5E = Move to first position of 5th line down
+106 46 F CPL - Cursor to Previous Line
+ [5F = Move to first position of 5th line previous
+107 47 G CHA - Cursor Horizontal position Absolute
+ [40G = Move to column 40 of current line
+110 48 H * CUP - CUrsor Position
+ * [H = Home, [24;80H = Row 24, Column 80
+111 49 I CHT - Cursor Horizontal Tabulation
+ [I = Same as HT (Control-I), [3I = Go forward 3 tabs
+112 4A J * ED - Erase in Display (cursor does not move)
+ * [J = [0J = Erase from current position to end (inclusive)
+ * [1J = Erase from beginning to current position (inclusive)
+ * [2J = Erase entire display
+ * [?0J = Selective erase in display ([?1J, [?2J similar)
+113 4B K * EL - Erase in Line (cursor does not move)
+ * [K = [0K = Erase from current position to end (inclusive)
+ * [1K = Erase from beginning to current position
+ * [2K = Erase entire current line
+ * [?0K = Selective erase to end of line ([?1K, [?2K similar)
+114 4C L * IL - Insert Line, current line moves down (VT102 series)
+ [3L = Insert 3 lines if currently in scrolling region
+115 4D M * DL - Delete Line, lines below current move up (VT102 series)
+ [2M = Delete 2 lines if currently in scrolling region
+116 4E N EF - Erase in Field (as bounded by protected fields)
+ [0N, [1N, [2N act like [L but within currend field
+117 4F O EA - Erase in qualified Area (defined by DAQ)
+ [0O, [1O, [2O act like [J but within current area
+120 50 P * DCH - Delete Character, from current position to end of field
+ [4P = Delete 4 characters, VT102 series
+121 51 Q SEM - Set Editing extent Mode (limits ICH and DCH)
+ [0Q = [Q = Insert/delete character affects rest of display
+ [1Q = ICH/DCH affect the current line only
+ [2Q = ICH/DCH affect current field (between tab stops) only
+ [3Q = ICH/DCH affect qualified area (between protected fields)
+122 52 R * CPR - Cursor Position Report (from terminal to host)
+ * [24;80R = Cursor is positioned at line 24 column 80
+123 53 S SU - Scroll up, entire display is moved up, new lines at bottom
+ [3S = Move everything up 3 lines, bring in 3 new lines
+124 54 T SD - Scroll down, new lines inserted at top of screen
+ [4T = Scroll down 4, bring previous lines back into view
+125 55 U NP - Next Page (if terminal has more than 1 page of memory)
+ [2U = Scroll forward 2 pages
+126 56 V PP - Previous Page (if terminal remembers lines scrolled off top)
+ [1V = Scroll backward 1 page
+127 57 W CTC - Cursor Tabulation Control
+ [0W = Set horizontal tab for current line at current position
+ [1W = Set vertical tab stop for current line of current page
+ [2W = Clear horiz tab stop at current position of current line
+ [3W = Clear vert tab stop at current line of current page
+ [4W = Clear all horiz tab stops on current line only
+ [5W = Clear all horiz tab stops for the entire terminal
+ [6W = Clear all vert tabs stops for the entire terminal
+130 58 X ECH - Erase CHaracter
+ [4X = Change next 4 characters to "erased" state
+131 59 Y CVT - Cursor Vertical Tab
+ [2Y = Move forward to 2nd following vertical tab stop
+132 5A Z CBT - Cursor Back Tab
+ [3Z = Move backwards to 3rd previous horizontal tab stop
+133 5B [ Reserved for future standardization left bracket
+134 5C \ Reserved reverse slant
+135 5D ] Reserved right bracket
+136 5E ^ Reserved circumflex
+137 5F _ Reserved underscore
+140 60 ` * HPA - Horizontal Position Absolute (depends on PUM)
+ [720` = Move to 720 decipoints (1 inch) from left margin
+ * [80` = Move to column 80 on LA120
+141 61 a * HPR - Horizontal Position Relative (depends on PUM)
+ [360a = Move 360 decipoints (1/2 inch) from current position
+ * [40a = Move 40 columns to right of current position on LA120
+142 62 b REP - REPeat previous displayable character
+ [80b = Repeat character 80 times
+143 63 c * DA - Device Attributes
+ * [c = Terminal will identify itself
+ * [?1;2c = Terminal is saying it is a VT100 with AVO
+ * [>0c = Secondary DA request (distinguishes VT240 from VT220)
+144 64 d * VPA - Vertical Position Absolute (depends on PUM)
+ [90d = Move to 90 decipoints (1/8 inch) from top margin
+ * [10d = Move to line 10 if before that else line 10 next page
+145 65 e * VPR - Vertical Position Relative (depends on PUM)
+ [720e = Move 720 decipoints (1 inch) down from current position
+ * [6e = Advance 6 lines forward on LA120
+146 66 f * HVP - Horizontal and Vertical Position (depends on PUM)
+ [720,1440f = Move to 1 inch down and 2 inches over (decipoints)
+ * [24;80f = Move to row 24 column 80 if PUM is set to character
+147 67 g * TBC - Tabulation Clear
+ * [0g = Clear horizontal tab stop at current position
+ * [1g = Clear vertical tab stop at current line (LA120)
+ * [2g = Clear all horizontal tab stops on current line only LA120
+ * [3g = Clear all horizontal tab stops in the terminal
+150 68 h * SM - Set Mode (. means permanently set on VT100)
+ [0h = Error, this command is ignored
+ * [1h = GATM - Guarded Area Transmit Mode, send all (VT132)
+ [2h = KAM - Keyboard Action Mode, disable keyboard input
+ [3h = CRM - Control Representation Mode, show all control chars
+ * [4h = IRM - Insertion/Replacement Mode, set insert mode (VT102)
+ [5h = SRTM - Status Report Transfer Mode, report after DCS
+ * [6h = ERM - ERasure Mode, erase protected and unprotected
+ [7h = VEM - Vertical Editing Mode, IL/DL affect previous lines
+ [8h, [9h are reserved
+ [10h = HEM - Horizontal Editing mode, ICH/DCH/IRM go backwards
+ [11h = PUM - Positioning Unit Mode, use decipoints for HVP/etc
+ . [12h = SRM - Send Receive Mode, transmit without local echo
+ [13h = FEAM - Format Effector Action Mode, FE's are stored
+ [14h = FETM - Format Effector Transfer Mode, send only if stored
+ [15h = MATM - Multiple Area Transfer Mode, send all areas
+ * [16h = TTM - Transmit Termination Mode, send scrolling region
+ [17h = SATM - Send Area Transmit Mode, send entire buffer
+ [18h = TSM - Tabulation Stop Mode, lines are independent
+ [19h = EBM - Editing Boundry Mode, all of memory affected
+ * [20h = LNM - Linefeed Newline Mode, LF interpreted as CR LF
+ * [?1h = DECCKM - Cursor Keys Mode, send ESC O A for cursor up
+ * [?2h = DECANM - ANSI Mode, use ESC < to switch VT52 to ANSI
+ * [?3h = DECCOLM - COLumn mode, 132 characters per line
+ * [?4h = DECSCLM - SCrolL Mode, smooth scrolling
+ * [?5h = DECSCNM - SCreeN Mode, black on white background
+ * [?6h = DECOM - Origin Mode, line 1 is relative to scroll region
+ * [?7h = DECAWM - AutoWrap Mode, start newline after column 80
+ * [?8h = DECARM - Auto Repeat Mode, key will autorepeat
+ * [?9h = DECINLM - INterLace Mode, interlaced for taking photos
+ * [?10h = DECEDM - EDit Mode, VT132 is in EDIT mode
+ * [?11h = DECLTM - Line Transmit Mode, ignore TTM, send line
+ [?12h = ?
+ * [?13h = DECSCFDM - Space Compression/Field Delimiting on,
+ * [?14h = DECTEM - Transmit Execution Mode, transmit on ENTER
+ [?15h = ?
+ * [?16h = DECEKEM - Edit Key Execution Mode, EDIT key is local
+ [?17h = ?
+ * [?18h = DECPFF - Print FormFeed mode, send FF after printscreen
+ * [?19h = DECPEXT - Print Extent mode, print entire screen
+ * [?20h = OV1 - Overstrike, overlay characters on GIGI
+ * [?21h = BA1 - Local BASIC, GIGI to keyboard and screen
+ * [?22h = BA2 - Host BASIC, GIGI to host computer
+ * [?23h = PK1 - GIGI numeric keypad sends reprogrammable sequences
+ * [?24h = AH1 - Autohardcopy before erasing or rolling GIGI screen
+ * [?29h = - Use only the proper pitch for the LA100 font
+ * [?38h = DECTEK - TEKtronix mode graphics
+151 69 i * MC - Media Copy (printer port on VT102)
+ * [0i = Send contents of text screen to printer
+ [1i = Fill screen from auxiliary input (printer's keyboard)
+ [2i = Send screen to secondary output device
+ [3i = Fill screen from secondary input device
+ * [4i = Turn on copying received data to primary output (VT125)
+ * [4i = Received data goes to VT102 screen, not to its printer
+ * [5i = Turn off copying received data to primary output (VT125)
+ * [5i = Received data goes to VT102's printer, not its screen
+ * [6i = Turn off copying received data to secondary output (VT125)
+ * [7i = Turn on copying received data to secondary output (VT125)
+ * [?0i = Graphics screen dump goes to graphics printer VT125,VT240
+ * [?1i = Print cursor line, terminated by CR LF
+ * [?2i = Graphics screen dump goes to host computer VT125,VT240
+ * [?4i = Disable auto print
+ * [?5i = Auto print, send a line at a time when linefeed received
+152 6A j Reserved for future standardization
+153 6B k Reserved for future standardization
+154 6C l * RM - Reset Mode (. means permanently reset on VT100)
+ * [1l = GATM - Transmit only unprotected characters (VT132)
+ . [2l = KAM - Enable input from keyboard
+ . [3l = CRM - Control characters are not displayable characters
+ * [4l = IRM - Reset to replacement mode (VT102)
+ . [5l = SRTM - Report only on command (DSR)
+ * [6l = ERM - Erase only unprotected fields
+ . [7l = VEM - IL/DL affect lines after current line
+ [8l, [9l are reserved
+ . [10l = HEM - ICH and IRM shove characters forward, DCH pulls
+ . [11l = PUM - Use character positions for HPA/HPR/VPA/VPR/HVP
+ [12l = SRM - Local echo - input from keyboard sent to screen
+ . [13l = FEAM - HPA/VPA/SGR/etc are acted upon when received
+ . [14l = FETM - Format Effectors are sent to the printer
+ [15l = MATM - Send only current area if SATM is reset
+ * [16l = TTM - Transmit partial page, up to cursor position
+ [17l = SATM - Transmit areas bounded by SSA/ESA/DAQ
+ . [18l = TSM - Setting a tab stop on one line affects all lines
+ . [19l = EBM - Insert does not overflow to next page
+ * [20l = LNM - Linefeed does not change horizontal position
+ * [?1l = DECCKM - Cursor keys send ANSI cursor position commands
+ * [?2l = DECANM - Use VT52 emulation instead of ANSI mode
+ * [?3l = DECCOLM - 80 characters per line (erases screen)
+ * [?4l = DECSCLM - Jump scrolling
+ * [?5l = DECSCNM - Normal screen (white on black background)
+ * [?6l = DECOM - Line numbers are independent of scrolling region
+ * [?7l = DECAWM - Cursor remains at end of line after column 80
+ * [?8l = DECARM - Keys do not repeat when held down
+ * [?9l = DECINLM - Display is not interlaced to avoid flicker
+ * [?10l = DECEDM - VT132 transmits all key presses
+ * [?11l = DECLTM - Send page or partial page depending on TTM
+ [?12l = ?
+ * [?13l = DECSCFDM - Don't suppress trailing spaces on transmit
+ * [?14l = DECTEM - ENTER sends ESC S (STS) a request to send
+ [?15l = ?
+ * [?16l = DECEKEM - EDIT key transmits either $[10h or $[10l
+ [?17l = ?
+ * [?18l = DECPFF - Don't send a formfeed after printing screen
+ * [?19l = DECPEXT - Print only the lines within the scroll region
+ * [?20l = OV0 - Space is destructive, replace not overstrike, GIGI
+ * [?21l = BA0 - No BASIC, GIGI is On-Line or Local
+ * [?22l = BA0 - No BASIC, GIGI is On-Line or Local
+ * [?23l = PK0 - Ignore reprogramming on GIGI keypad and cursors
+ * [?24l = AH0 - No auto-hardcopy when GIGI screen erased
+ * [?29l = Allow all character pitches on the LA100
+ * [?38l = DECTEK - Ignore TEKtronix graphics commands
+155 6D m * SGR - Set Graphics Rendition (affects character attributes)
+ * [0m = Clear all special attributes
+ * [1m = Bold or increased intensity
+ * [2m = Dim or secondary color on GIGI (superscript on XXXXXX)
+ [3m = Italic (subscript on XXXXXX)
+ * [4m = Underscore, [0;4m = Clear, then set underline only
+ * [5m = Slow blink
+ [6m = Fast blink (overscore on XXXXXX)
+ * [7m = Negative image, [0;1;7m = Bold + Inverse
+ [8m = Concealed (do not display character echoed locally)
+ [9m = Reserved for future standardization
+ * [10m = Select primary font (LA100)
+ * [11m - [19m = Selete alternate font (LA100 has 11 thru 14)
+ [20m = FRAKTUR (whatever that means)
+ * [22m = Cancel bold or dim attribute only (VT220)
+ * [24m = Cancel underline attribute only (VT220)
+ * [25m = Cancel fast or slow blink attribute only (VT220)
+ * [27m = Cancel negative image attribute only (VT220)
+ * [30m = Write with black, [40m = Set background to black (GIGI)
+ * [31m = Write with red, [41m = Set background to red
+ * [32m = Write with green, [42m = Set background to green
+ * [33m = Write with yellow, [43m = Set background to yellow
+ * [34m = Write with blue, [44m = Set background to blue
+ * [35m = Write with magenta, [45m = Set background to magenta
+ * [36m = Write with cyan, [46m = Set background to cyan
+ * [37m = Write with white, [47m = Set background to white
+ [38m, [39m, [48m, [49m are reserved
+156 6E n * DSR - Device Status Report
+ * [0n = Terminal is ready, no malfunctions detected
+ [1n = Terminal is busy, retry later
+ [2n = Terminal is busy, it will send DSR when ready
+ * [3n = Malfunction, please try again
+ [4n = Malfunction, terminal will send DSR when ready
+ * [5n = Command to terminal to report its status
+ * [6n = Command to terminal requesting cursor position (CPR)
+ * [?15n = Command to terminal requesting printer status, returns
+ [?10n = OK, [?11n = not OK, [?13n = no printer.
+ * [?25n = "Are User Defined Keys Locked?" (VT220)
+157 6F o DAQ - Define Area Qualification starting at current position
+ [0o = Accept all input, transmit on request
+ [1o = Protected and guarded, accept no input, do not transmit
+ [2o = Accept any printing character in this field
+ [3o = Numeric only field
+ [4o = Alphabetic (A-Z and a-z) only
+ [5o = Right justify in area
+ [3;6o = Zero fill in area
+ [7o = Set horizontal tab stop, this is the start of the field
+ [8o = Protected and unguarded, accept no input, do transmit
+ [9o = Space fill in area
+
+==============================================================================
+
+ Private Control Sequences (allowed by ANSI X3.41-1974).
+ These take parameter strings and terminate with the last half of lowercase.
+
+Oct Hex * (* marks function used in DEC VT series or LA series terminals)
+--- -- - - --------------------------------------------------------------------
+160 70 p * DECSTR - Soft Terminal Reset
+ [!p = Soft Terminal Reset
+161 71 q * DECLL - Load LEDs
+ [0q = Turn off all, [?1;4q turns on L1 and L4, etc
+ [154;155;157q = VT100 goes bonkers
+ [2;23!q = Partial screen dump from GIGI to graphics printer
+ [0"q = DECSCA Select Character Attributes off
+ [1"q = DECSCA - designate set as non-erasable
+ [2"q = DECSCA - designate set as erasable
+162 72 r * DECSTBM - Set top and bottom margins (scroll region on VT100)
+ [4;20r = Set top margin at line 4 and bottom at line 20
+163 73 s * DECSTRM - Set left and right margins on LA100,LA120
+ [5;130s = Set left margin at column 5 and right at column 130
+164 74 t * DECSLPP - Set physical lines per page
+ [66t = Paper has 66 lines (11 inches at 6 per inch)
+165 75 u * DECSHTS - Set many horizontal tab stops at once on LA100
+ [9;17;25;33;41;49;57;65;73;81u = Set standard tab stops
+166 76 v * DECSVTS - Set many vertical tab stops at once on LA100
+ [1;16;31;45v = Set vert tabs every 15 lines
+167 77 w * DECSHORP - Set horizontal pitch on LAxxx printers
+ [1w = 10 characters per inch, [2w = 12 characters per inch
+ [0w=10, [3w=13.2, [4w=16.5, [5w=5, [6w=6, [7w=6.6, [8w=8.25
+170 78 x * DECREQTPARM - Request terminal parameters
+ [3;5;2;64;64;1;0x = Report, 7 bit Even, 1200 baud, 1200 baud
+171 79 y * DECTST - Invoke confidence test
+ [2;1y = Power-up test on VT100 series (and VT100 part of VT125)
+ [3;1y = Power-up test on GIGI (VK100)
+ [4;1y = Power-up test on graphics portion of VT125
+172 7A z * DECVERP - Set vertical pitch on LA100
+ [1z = 6 lines per inch, [2z = 8 lines per inch
+ [0z=6, [3z=12, [4z=3, [5z=3, [6z=4
+173 7B { Private
+174 7C | * DECTTC - Transmit Termination Character
+ [0| = No extra characters, [1| = terminate with FF
+175 7D } * DECPRO - Define protected field on VT132
+ [0} = No protection, [1;4;5;7} = Any attribute is protected
+ [254} = Characters with no attributes are protected
+176 7E ~ * DECKEYS - Sent by special function keys
+ [1~=FIND, [2~=INSERT, [3~=REMOVE, [4~=SELECT, [5~=PREV, [6~=NEXT
+ [17~=F6...[34~=F20 ([23~=ESC,[24~=BS,[25~=LF,[28~=HELP,[29~=DO)
+177 7F DELETE is always ignored
+
+==============================================================================
+ Control Sequences with intermediate characters (from ANSI X3.64-1979).
+ Note that there is a SPACE character before the terminating alphabetic.
+
+Oct Hex * (* marks function used in DEC VT series or LA series terminals)
+--- -- - - --------------------------------------------------------------------
+100 40 @ SL - Scroll Left
+ [4 @ = Move everything over 4 columns, 4 new columns at right
+101 41 A SR - Scroll Right
+ [2 A = Move everything over 2 columns, 2 new columns at left
+102 42 B GSM - Graphic Size Modification
+ [110;50 B = Make 110% high, 50% wide
+103 43 C GSS - Graphic Size Selection
+ [120 C = Make characters 120 decipoints (1/6 inch) high
+104 44 D FNT - FoNT selection (used by SGR, [10m thru [19m)
+ [0;23 D = Make primary font be registered font #23
+105 45 E TSS - Thin Space Specification
+ [36 E = Define a thin space to be 36 decipoints (1/20 inch)
+106 46 F JFY - JustiFY, done by the terminal/printer
+ [0 E = No justification
+ [1 E = Fill, bringing words up from next line if necessary
+ [2 E = Interword spacing, adjust spaces between words
+ [3 E = Letter spacing, adjust width of each letter
+ [4 E = Use hyphenation
+ [5 E = Flush left margin
+ [6 E = Center following text between margins (until [0 E)
+ [7 E = Flush right margin
+ [8 E = Italian form (underscore instead of hyphen)
+107 47 G SPI - SPacing Increment (in decipoints)
+ [120;72 G = 6 per inch vertical, 10 per inch horizontal
+110 48 H QUAD- Do quadding on current line of text (typography)
+ [0 H = Flush left, [1 H = Flush left and fill with leader
+ [2 H = Center, [3 H = Center and fill with leader
+ [4 H = Flush right, [5 H = Flush right and fill with leader
+111 49 I Reserved for future standardization
+157 67 o Reserved for future standardization
+160 70 p Private use
+ ... May be defined by the printer manufacturer
+176 7E ~ Private use
+177 7F DELETE is always ignored
+
+==============================================================================
+ Minimum requirements for VT100 emulation:
+
+1) To act as a passive display, implement the 4 cursor commands, the 2 erase
+ commands, direct cursor addressing, and at least inverse characters.
+ The software should be capable of handling strings with 16 numeric parameters
+ with values in the range of 0 to 255.
+
+ [A Move cursor up one row, stop if a top of screen
+ [B Move cursor down one row, stop if at bottom of screen
+ [C Move cursor forward one column, stop if at right edge of screen
+ [D Move cursor backward one column, stop if at left edge of screen
+ [H Home to row 1 column 1 (also [1;1H)
+ [J Clear from current position to bottom of screen
+ [K Clear from current position to end of line
+ [24;80H Position to line 24 column 80 (any line 1 to 24, any column 1 to 132)
+ [0m Clear attributes to normal characters
+ [7m Add the inverse video attribute to succeeding characters
+ [0;7m Set character attributes to inverse video only
+
+2) To enter data in VT100 mode, implement the 4 cursor keys and the 4 PF keys.
+ It must be possible to enter ESC, TAB, BS, DEL, and LF from the keyboard.
+
+ [A Sent by the up-cursor key (alternately ESC O A)
+ [B Sent by the down-cursor key (alternately ESC O B)
+ [C Sent by the right-cursor key (alternately ESC O C)
+ [D Sent by the left-cursor key (alternately ESC O D)
+ OP PF1 key sends ESC O P
+ OQ PF2 key sends ESC O Q
+ OR PF3 key sends ESC O R
+ OS PF3 key sends ESC O S
+ [c Request for the terminal to identify itself
+ [?1;0c VT100 with memory for 24 by 80, inverse video character attribute
+ [?1;2c VT100 capable of 132 column mode, with bold+blink+underline+inverse
+
+3) When doing full-screen editing on a VT100, implement directed erase, the
+ numeric keypad in applications mode, and the limited scrolling region.
+ The latter is needed to do insert/delete line functions without rewriting
+ the screen.
+
+ [0J Erase from current position to bottom of screen inclusive
+ [1J Erase from top of screen to current position inclusive
+ [2J Erase entire screen (without moving the cursor)
+ [0K Erase from current position to end of line inclusive
+ [1K Erase from beginning of line to current position inclusive
+ [2K Erase entire line (without moving cursor)
+ [12;24r Set scrolling region to lines 12 thru 24. If a linefeed or an
+ INDex is received while on line 24, the former line 12 is deleted
+ and rows 13-24 move up. If a RI (reverse Index) is received while
+ on line 12, a blank line is inserted there as rows 12-13 move down.
+ All VT100 compatible terminals (except GIGI) have this feature.
+ ESC = Set numeric keypad to applications mode
+ ESC > Set numeric keypad to numbers mode
+ OA Up-cursor key sends ESC O A after ESC = ESC [ ? 1 h
+ OB Down-cursor key sends ESC O B " " "
+ OC Right-cursor key sends ESC O B " " "
+ OB Left-cursor key sends ESC O B " " "
+ OM ENTER key sends ESC O M after ESC =
+ Ol COMMA on keypad sends ESC O l " " (that's lowercase L)
+ Om MINUS on keypad sends ESC O m " "
+ Op ZERO on keypad sends ESC O p " "
+ Oq ONE on keypad sends ESC O q " "
+ Or TWO on keypad sends ESC O r " "
+ Os THREE on keypad sends ESC O s " "
+ Ot FOUR on keypad sends ESC O t " "
+ Ou FIVE on keypad sends ESC O u " "
+ Ov SIX on keypad sends ESC O v " "
+ Ow SEVEN on keypad sends ESC O w " "
+ Ox EIGHT on keypad sends ESC O x " "
+ Oy NINE on keypad sends ESC O y " "
+
+4) If the hardware is capable of double width/double height:
+
+ #3 Top half of a double-width double-height line
+ #4 Bottom half of a double-width double-height line
+ #5 Make line single-width (lines are set this way when cleared by ESC [ J)
+ #6 Make line double-width normal height (40 or 66 characters)
+
+5) If the terminal emulator is capable of insert/delete characters,
+insert/delete lines, insert/replace mode, and can do a full-screen dump to
+the printer (in text mode), then it should identify itself as a VT102
+
+ [c Request for the terminal to identify itself
+ [?6c VT102 (printer port, 132 column mode, and ins/del standard)
+ [1@ Insert a blank character position (shift line to the right)
+ [1P Delete a character position (shift line to the left)
+ [1L Insert blank line at current row (shift screen down)
+ [1M Delete the current line (shift screen up)
+ [4h Set insert mode, new characters shove existing ones to the right
+ [4l Reset insert mode, new characters replace existing ones
+ [0i Print screen (all 24 lines) to the printer
+ [4i All received data goes to the printer (nothing to the screen)
+ [5i All received data goes to the screen (nothing to the printer)
+
+
+[End of ANSICODE.TXT]
diff --git a/array.h b/array.h
new file mode 100644
index 00000000..369bb636
--- /dev/null
+++ b/array.h
@@ -0,0 +1,91 @@
+/* $Id: array.h,v 1.1.1.1 2007-07-09 19:03:30 nicm Exp $ */
+
+/*
+ * Copyright (c) 2006 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef ARRAY_H
+#define ARRAY_H
+
+#define ARRAY_DECL(n, c) \
+ struct n { \
+ c *list; \
+ u_int num; \
+ size_t space; \
+ }
+
+#define ARRAY_ITEM(a, i) ((a)->list[i])
+#define ARRAY_ITEMSIZE(a) (sizeof *(a)->list)
+
+#define ARRAY_EMPTY(a) ((a) == NULL || (a)->num == 0)
+#define ARRAY_LENGTH(a) ((a)->num)
+
+#define ARRAY_FIRST(a) ARRAY_ITEM(a, 0)
+#define ARRAY_LAST(a) ARRAY_ITEM(a, (a)->num - 1)
+
+#define ARRAY_INIT(a) do { \
+ (a)->num = 0; \
+ (a)->list = NULL; \
+ (a)->space = 0; \
+} while (0)
+
+#define ARRAY_SET(a, i, s) do { \
+ (a)->list[i] = s; \
+} while (0)
+
+#define ARRAY_ADD(a, s) do { \
+ ENSURE_SIZE2((a)->list, (a)->space, (a)->num + 1, ARRAY_ITEMSIZE(a)); \
+ (a)->list[(a)->num] = s; \
+ (a)->num++; \
+} while (0)
+#define ARRAY_REMOVE(a, i) do { \
+ if (i < (a)->num - 1) { \
+ memmove((a)->list + (i), (a)->list + (i) + 1, \
+ ARRAY_ITEMSIZE(a) * ((a)->num - (i) - 1)); \
+ } \
+ (a)->num--; \
+ if ((a)->num == 0) \
+ ARRAY_FREE(a); \
+} while (0)
+
+#define ARRAY_EXPAND(a, n) do { \
+ ENSURE_SIZE2((a)->list, (a)->space, (a)->num + n, ARRAY_ITEMSIZE(a)); \
+ (a)->num += n; \
+} while (0)
+#define ARRAY_TRUNC(a, n) do { \
+ if ((a)->num > n) \
+ (a)->num -= n; \
+ else \
+ ARRAY_FREE(a); \
+} while (0)
+
+#define ARRAY_CONCAT(a, b) do { \
+ ENSURE_SIZE2((a)->list, (a)->space, (a)->num + (b)->num, \
+ ARRAY_ITEMSIZE(a)); \
+ memcpy((a)->list + (a)->num, (b)->list, (b)->num * ARRAY_ITEMSIZE(a)) \
+ (a)->num += (b)->num; \
+} while (0)
+
+#define ARRAY_FREE(a) do { \
+ if ((a)->list != NULL) \
+ xfree((a)->list); \
+ ARRAY_INIT(a); \
+} while (0)
+#define ARRAY_FREEALL(a) do { \
+ ARRAY_FREE(a); \
+ xfree(a); \
+} while (0)
+
+#endif
diff --git a/buffer-poll.c b/buffer-poll.c
new file mode 100644
index 00000000..e3c64832
--- /dev/null
+++ b/buffer-poll.c
@@ -0,0 +1,57 @@
+/* $Id: buffer-poll.c,v 1.1.1.1 2007-07-09 19:04:12 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <poll.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/* Fill buffers from socket based on poll results. */
+int
+buffer_poll(struct pollfd *pfd, struct buffer *in, struct buffer *out)
+{
+ ssize_t n;
+
+ if (pfd->revents & (POLLERR|POLLNVAL|POLLHUP))
+ return (-1);
+ if (pfd->revents & POLLIN) {
+ buffer_ensure(in, BUFSIZ);
+ n = read(pfd->fd, BUFFER_IN(in), BUFFER_FREE(in));
+ if (n == 0)
+ return (-1);
+ if (n == -1) {
+ if (errno != EINTR && errno != EAGAIN)
+ return (-1);
+ return (0);
+ }
+ buffer_add(in, n);
+ }
+ if (BUFFER_USED(out) > 0 && pfd->revents & POLLOUT) {
+ n = write(pfd->fd, BUFFER_OUT(out), BUFFER_USED(out));
+ if (n == -1) {
+ if (errno != EINTR && errno != EAGAIN)
+ return (-1);
+ return (0);
+ }
+ buffer_remove(out, n);
+ }
+ return (0);
+}
diff --git a/buffer.c b/buffer.c
new file mode 100644
index 00000000..3166088c
--- /dev/null
+++ b/buffer.c
@@ -0,0 +1,181 @@
+/* $Id: buffer.c,v 1.1.1.1 2007-07-09 19:03:33 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <string.h>
+
+#include "tmux.h"
+
+/* Create a buffer. */
+struct buffer *
+buffer_create(size_t size)
+{
+ struct buffer *b;
+
+ if (size == 0)
+ log_fatalx("buffer_create: zero size");
+
+ b = xcalloc(1, sizeof *b);
+
+ b->base = xmalloc(size);
+ b->space = size;
+
+ return (b);
+}
+
+/* Destroy a buffer. */
+void
+buffer_destroy(struct buffer *b)
+{
+ xfree(b->base);
+ xfree(b);
+}
+
+/* Empty a buffer. */
+void
+buffer_clear(struct buffer *b)
+{
+ b->size = 0;
+ b->off = 0;
+}
+
+/* Ensure free space for size in buffer. */
+void
+buffer_ensure(struct buffer *b, size_t size)
+{
+ if (size == 0)
+ log_fatalx("buffer_ensure: zero size");
+
+ if (BUFFER_FREE(b) >= size)
+ return;
+
+ if (b->off > 0) {
+ if (b->size > 0)
+ memmove(b->base, b->base + b->off, b->size);
+ b->off = 0;
+ }
+
+ ENSURE_FOR(b->base, b->space, b->size, size);
+}
+
+/* Adjust buffer after data appended. */
+void
+buffer_add(struct buffer *b, size_t size)
+{
+ if (size == 0)
+ log_fatalx("buffer_add: zero size");
+ if (size > b->space - b->size)
+ log_fatalx("buffer_add: overflow");
+
+ b->size += size;
+}
+
+/* Reverse buffer add. */
+void
+buffer_reverse_add(struct buffer *b, size_t size)
+{
+ if (size == 0)
+ log_fatalx("buffer_reverse_add: zero size");
+ if (size > b->size)
+ log_fatalx("buffer_reverse_add: underflow");
+
+ b->size -= size;
+}
+
+/* Adjust buffer after data removed. */
+void
+buffer_remove(struct buffer *b, size_t size)
+{
+ if (size == 0)
+ log_fatalx("buffer_remove: zero size");
+ if (size > b->size)
+ log_fatalx("buffer_remove: underflow");
+
+ b->size -= size;
+ b->off += size;
+}
+
+/* Reverse buffer remove. */
+void
+buffer_reverse_remove(struct buffer *b, size_t size)
+{
+ if (size == 0)
+ log_fatalx("buffer_reverse_remove: zero size");
+ if (size > b->off)
+ log_fatalx("buffer_reverse_remove: overflow");
+
+ b->size += size;
+ b->off -= size;
+}
+
+/* Insert a section into the buffer. */
+void
+buffer_insert_range(struct buffer *b, size_t base, size_t size)
+{
+ if (size == 0)
+ log_fatalx("buffer_insert_range: zero size");
+ if (base > b->size)
+ log_fatalx("buffer_insert_range: range overflows buffer");
+
+ buffer_ensure(b, size);
+ memmove(b->base + b->off + base + size,
+ b->base + b->off + base, b->size - base);
+ b->size += size;
+}
+
+/* Delete a section from the buffer. */
+void
+buffer_delete_range(struct buffer *b, size_t base, size_t size)
+{
+ if (size == 0)
+ log_fatalx("buffer_delete_range: zero size");
+ if (size > b->size)
+ log_fatalx("buffer_delete_range: size too big");
+ if (base + size > b->size)
+ log_fatalx("buffer_delete_range: range overflows buffer");
+
+ memmove(b->base + b->off + base,
+ b->base + b->off + base + size, b->size - base - size);
+ b->size -= size;
+}
+
+/* Copy data into a buffer. */
+void
+buffer_write(struct buffer *b, const void *data, size_t size)
+{
+ if (size == 0)
+ log_fatalx("buffer_write: zero size");
+
+ buffer_ensure(b, size);
+ memcpy(BUFFER_IN(b), data, size);
+ buffer_add(b, size);
+}
+
+/* Copy data out of a buffer. */
+void
+buffer_read(struct buffer *b, void *data, size_t size)
+{
+ if (size == 0)
+ log_fatalx("buffer_read: zero size");
+ if (size > b->size)
+ log_fatalx("buffer_read: underflow");
+
+ memcpy(data, BUFFER_OUT(b), size);
+ buffer_remove(b, size);
+}
diff --git a/command.c b/command.c
new file mode 100644
index 00000000..05a7e726
--- /dev/null
+++ b/command.c
@@ -0,0 +1,165 @@
+/* $Id: command.c,v 1.1.1.1 2007-07-09 19:03:50 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include "tmux.h"
+
+int cmd_prefix = '\002'; /* C-b */
+
+int cmd_fn_select(struct buffer *, int);
+int cmd_fn_create(struct buffer *, int);
+int cmd_fn_detach(struct buffer *, int);
+int cmd_fn_next(struct buffer *, int);
+int cmd_fn_previous(struct buffer *, int);
+int cmd_fn_refresh(struct buffer *, int);
+int cmd_fn_rename(struct buffer *, int);
+
+struct cmd {
+ int key;
+ int (*fn)(struct buffer *, int);
+ int arg;
+};
+
+struct cmd cmd_table[] = {
+ { '0', cmd_fn_select, 0 },
+ { '1', cmd_fn_select, 1 },
+ { '2', cmd_fn_select, 2 },
+ { '3', cmd_fn_select, 3 },
+ { '4', cmd_fn_select, 4 },
+ { '5', cmd_fn_select, 5 },
+ { '6', cmd_fn_select, 6 },
+ { '7', cmd_fn_select, 7 },
+ { '8', cmd_fn_select, 8 },
+ { '9', cmd_fn_select, 9 },
+ { 'C', cmd_fn_create, 0 },
+ { 'c', cmd_fn_create, 0 },
+ { 'D', cmd_fn_detach, 0 },
+ { 'd', cmd_fn_detach, 0 },
+ { 'N', cmd_fn_next, 0 },
+ { 'n', cmd_fn_next, 0 },
+ { 'P', cmd_fn_previous, 0 },
+ { 'p', cmd_fn_previous, 0 },
+ { 'R', cmd_fn_refresh, 0 },
+ { 'r', cmd_fn_refresh, 0 },
+ { 'T', cmd_fn_rename, 0 },
+ { 't', cmd_fn_rename, 0 }
+};
+
+/* Dispatch to a command. */
+int
+cmd_execute(int key, struct buffer *srv_out)
+{
+ struct cmd *cmd;
+ u_int i;
+
+ for (i = 0; i < (sizeof cmd_table / sizeof cmd_table[0]); i++) {
+ cmd = cmd_table + i;
+ if (cmd->key == key)
+ return (cmd->fn(srv_out, cmd->arg));
+ }
+ return (0);
+}
+
+/* Handle select command. */
+int
+cmd_fn_select(unused struct buffer *srv_out, int arg)
+{
+ struct hdr hdr;
+ struct select_data data;
+
+ hdr.code = MSG_SELECT;
+ hdr.size = sizeof data;
+ buffer_write(srv_out, &hdr, sizeof hdr);
+ data.idx = arg;
+ buffer_write(srv_out, &data, sizeof data);
+
+ return (0);
+}
+
+/* Handle create command. */
+int
+cmd_fn_create(unused struct buffer *srv_out, unused int arg)
+{
+ struct hdr hdr;
+
+ hdr.code = MSG_CREATE;
+ hdr.size = 0;
+ buffer_write(srv_out, &hdr, sizeof hdr);
+
+ return (0);
+}
+
+/* Handle detach command. */
+int
+cmd_fn_detach(unused struct buffer *srv_out, unused int arg)
+{
+ return (-1);
+}
+
+/* Handle next command. */
+int
+cmd_fn_next(unused struct buffer *srv_out, unused int arg)
+{
+ struct hdr hdr;
+
+ hdr.code = MSG_NEXT;
+ hdr.size = 0;
+ buffer_write(srv_out, &hdr, sizeof hdr);
+
+ return (0);
+}
+
+/* Handle previous command. */
+int
+cmd_fn_previous(unused struct buffer *srv_out, unused int arg)
+{
+ struct hdr hdr;
+
+ hdr.code = MSG_PREVIOUS;
+ hdr.size = 0;
+ buffer_write(srv_out, &hdr, sizeof hdr);
+
+ return (0);
+}
+
+/* Handle refresh command. */
+int
+cmd_fn_refresh(unused struct buffer *srv_out, unused int arg)
+{
+ struct hdr hdr;
+
+ hdr.code = MSG_REFRESH;
+ hdr.size = 0;
+ buffer_write(srv_out, &hdr, sizeof hdr);
+
+ return (0);
+}
+
+/* Handle rename command. */
+int
+cmd_fn_rename(struct buffer *srv_out, unused int arg)
+{
+ struct hdr hdr;
+
+ hdr.code = MSG_RENAME;
+ hdr.size = 0;
+ buffer_write(srv_out, &hdr, sizeof hdr);
+
+ return (0);
+}
diff --git a/input.c b/input.c
new file mode 100644
index 00000000..663ef2a3
--- /dev/null
+++ b/input.c
@@ -0,0 +1,825 @@
+/* $Id: input.c,v 1.1.1.1 2007-07-09 19:03:45 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+size_t input_sequence(
+ u_char *, size_t, u_char *, u_char *, uint16_t **, u_int *);
+int input_control(
+ u_char **, size_t *, struct buffer *, struct screen *, u_char);
+int input_pair_private(
+ u_char **, size_t *, struct buffer *, struct screen *, u_char);
+int input_pair_standard(
+ u_char **, size_t *, struct buffer *, struct screen *, u_char);
+int input_pair_control(
+ u_char **, size_t *, struct buffer *, struct screen *, u_char);
+int input_control_sequence(
+ u_char **, size_t *, struct buffer *, struct screen *);
+int input_check_one(uint16_t *, u_int, uint16_t *, uint16_t);
+int input_check_one2(
+ uint16_t *, u_int, uint16_t *, uint16_t, uint16_t, uint16_t);
+int input_check_two(
+ uint16_t *, u_int, uint16_t *, uint16_t, uint16_t *, uint16_t);
+
+struct input_key {
+ int key;
+ const char *string;
+};
+
+struct input_key input_keys[] = {
+ { KEYC_BACKSPACE, "" },
+ { KEYC_DC, "[3~" },
+ { KEYC_DOWN, "OB" },
+ { KEYC_F1, "OP" },
+ { KEYC_F10, "[21~" },
+ { KEYC_F11, "[23~" },
+ { KEYC_F12, "[24~" },
+ { KEYC_F2, "OQ" },
+ { KEYC_F3, "OR" },
+ { KEYC_F4, "OS" },
+ { KEYC_F5, "[15~" },
+ { KEYC_F6, "[17~" },
+ { KEYC_F7, "[18~" },
+ { KEYC_F8, "[19~" },
+ { KEYC_F9, "[20~" },
+ { KEYC_HOME, "[1~" },
+ { KEYC_IC, "[2~" },
+ { KEYC_LEFT, "OD" },
+ { KEYC_LL, "[4~" },
+ { KEYC_NPAGE, "[6~" },
+ { KEYC_PPAGE, "[5~" },
+ { KEYC_RIGHT, "OC" },
+ { KEYC_UP, "OA" }
+};
+
+/*
+ * This parses CSI escape sequences into a code and a block of uint16_t
+ * arguments. buf must be the next byte after the \e[ and len the remaining
+ * data.
+ */
+size_t
+input_sequence(u_char *buf, size_t len,
+ u_char *code, u_char *private, uint16_t **argv, u_int *argc)
+{
+ char ch;
+ u_char *ptr, *saved;
+ const char *errstr;
+
+ *code = 0;
+
+ *argc = 0;
+ *argv = NULL;
+
+ if (len == 0)
+ return (0);
+ saved = buf;
+
+ /*
+ * 0x3c (<) to 0x3f (?) mark private sequences when appear as the first
+ * character.
+ */
+ *private = '\0';
+ if (*buf >= '<' && *buf <= '?') {
+ *private = *buf;
+ buf++; len--;
+ } else if (*buf < '0' || *buf > ';')
+ goto complete;
+
+ while (len > 0) {
+ /*
+ * Every parameter substring is bytes from 0x30 (0) to 0x3a (:),
+ * terminated by 0x3b (;). 0x3a is an internal seperator.
+ */
+
+ /* Find the end of the substring. */
+ ptr = buf;
+ while (len != 0 && *ptr >= '0' && *ptr <= '9') {
+ ptr++;
+ len--;
+ }
+ if (len == 0)
+ break;
+
+ /* An 0x3a is unsupported. */
+ if (*ptr == ':')
+ goto invalid;
+
+ /* Create a new argument. */
+ (*argc)++;
+ *argv = xrealloc(*argv, *argc, sizeof **argv);
+
+ /* Fill in argument value. */
+ errstr = NULL;
+ if (ptr == buf)
+ (*argv)[*argc - 1] = UINT16_MAX;
+ else {
+ ch = *ptr; *ptr = '\0';
+ (*argv)[*argc - 1] =
+ strtonum(buf, 0, UINT16_MAX - 1, &errstr);
+ *ptr = ch;
+ }
+ buf = ptr;
+
+ /* If the conversion had errors, abort now. */
+ if (errstr != NULL)
+ goto invalid;
+
+ /* Break for any non-terminator. */
+ if (*buf != ';')
+ goto complete;
+ buf++; len--;
+ }
+ if (len == 0)
+ goto incomplete;
+
+complete:
+ /* Valid final characters are 0x40 (@) to 0x7e (~). */
+ if (*buf < '@' || *buf > '~')
+ goto invalid;
+
+ *code = *buf;
+ return (buf - saved + 1);
+
+invalid:
+ if (*argv != NULL) {
+ xfree(*argv);
+ *argv = NULL;
+ }
+ *argc = 0;
+
+ /* Invalid. Consume until a valid terminator. */
+ while (len > 0) {
+ if (*buf >= '@' && *buf <= '~')
+ break;
+ buf++; len--;
+ }
+ if (len == 0)
+ goto incomplete;
+
+ *code = '\0';
+ return (buf - saved + 1);
+
+incomplete:
+ if (*argv != NULL) {
+ xfree(*argv);
+ *argv = NULL;
+ }
+ *argc = 0;
+
+ *code = '\0';
+ return (0);
+}
+
+/* Translate a key code into an output key sequence. */
+void
+input_key(struct buffer *b, int key)
+{
+ struct input_key *ak;
+ u_int i;
+
+ log_debug("writing key %d", key);
+ if (key != KEYC_NONE && key >= 0) {
+ input_store8(b, key);
+ return;
+ }
+
+ for (i = 0; i < (sizeof input_keys / sizeof input_keys[0]); i++) {
+ ak = input_keys + i;
+ if (ak->key == key) {
+ log_debug("found key %d: \"%s\"", key, ak->string);
+ buffer_write(b, ak->string, strlen(ak->string));
+ return;
+ }
+ }
+}
+
+/*
+ * Parse a block of data and normalise escape sequences into a \e, a single
+ * character code and the correct number of arguments. This includes adding
+ * missing arguments and default values, and enforcing limits. Returns the
+ * number of bytes consumed. The screen is updated with the data and used
+ * to fill in current cursor positions and sizes.
+ */
+size_t
+input_parse(u_char *buf, size_t len, struct buffer *b, struct screen *s)
+{
+ u_char *saved, ch;
+ size_t size;
+ FILE *f;
+
+ saved = buf;
+
+ if (debug_level > 1) {
+ f = fopen("tmux-in.log", "a+");
+ fwrite(buf, len, 1, f);
+ fclose(f);
+ }
+
+ while (len > 0) {
+ ch = *buf++; len--;
+
+ /* Handle control characters. */
+ if (ch != '\e') {
+ if (ch < ' ') {
+ if (input_control(&buf, &len, b, s, ch) == 1) {
+ *--buf = ch;
+ break;
+ }
+ } else {
+ log_debug("character: %c (%hhu)", ch, ch);
+ screen_character(s, ch);
+ input_store8(b, ch);
+ }
+ continue;
+ }
+ if (len == 0) {
+ *--buf = '\e';
+ break;
+ }
+
+ /* Read the first character. */
+ ch = *buf++; len--;
+
+ /* Ignore delete. */
+ if (ch == '\177') {
+ if (len == 0) {
+ *--buf = '\e';
+ break;
+ }
+ ch = *buf++; len--;
+ }
+
+ /* Interpret C0 immediately. */
+ if (ch < ' ') {
+ if (input_control(&buf, &len, b, s, ch) == 1) {
+ *--buf = ch;
+ break;
+ }
+
+ if (len == 0) {
+ *--buf = '\e';
+ break;
+ }
+ ch = *buf++; len--;
+ }
+
+ /*
+ * Save used size to work out how much to pass to
+ * screen_sequence later.
+ */
+ size = BUFFER_USED(b);
+
+ /* Skip until the end of intermediate strings. */
+ if (ch >= ' ' && ch <= '/') {
+ while (len != 0) {
+ if (ch >= 0x30 && ch <= 0x3f)
+ break;
+ if (ch >= 0x40 && ch <= 0x5f)
+ break;
+ ch = *buf++; len--;
+ }
+ continue;
+ }
+
+ /* Handle two-character sequences. */
+ if (ch >= '0' && ch <= '?') {
+ if (input_pair_private(&buf, &len, b, s, ch) == 1)
+ goto incomplete;
+ goto next;
+ }
+ if (ch >= '`' && ch <= '~') {
+ if (input_pair_standard(&buf, &len, b, s, ch) == 1)
+ goto incomplete;
+ goto next;
+ }
+ if (ch >= '@' && ch <= '_' && ch != '[') {
+ if (input_pair_control(&buf, &len, b, s, ch) == 1)
+ goto incomplete;
+ goto next;
+ }
+
+ /* If not CSI at this point, invalid. */
+ if (ch != '[')
+ continue;
+
+ if (input_control_sequence(&buf, &len, b, s) == 1)
+ goto incomplete;
+
+ next:
+ size = BUFFER_USED(b) - size;
+ log_debug("output is %zu bytes", size);
+ if (size > 0) /* XXX only one command? */
+ screen_sequence(s, BUFFER_IN(b) - size);
+ log_debug("remaining data %zu bytes", len);
+ }
+
+ return (buf - saved);
+
+incomplete:
+ *--buf = ch;
+ *--buf = '\e';
+ return (buf - saved);
+}
+
+/* Handle single control characters. */
+int
+input_control(unused u_char **buf, unused size_t *len,
+ struct buffer *b, struct screen *s, u_char ch)
+{
+ switch (ch) {
+ case '\0': /* NUL */
+ break;
+ case '\n': /* LF */
+ case '\r': /* CR */
+ case '\010': /* BS */
+ log_debug("control: %hhu", ch);
+ screen_character(s, ch);
+ input_store8(b, ch);
+ break;
+ default:
+ log_debug("unknown control: %c (%hhu)", ch, ch);
+ break;
+ }
+
+ return (0);
+}
+
+/* Translate a private two-character sequence. */
+int
+input_pair_private(unused u_char **buf, unused size_t *len,
+ unused struct buffer *b, unused struct screen *s, unused u_char ch)
+{
+ log_debug("private2: %c (%hhu)", ch, ch);
+
+ switch (ch) {
+ case '=': /* DECKPAM */
+ input_store_zero(b, CODE_KKEYPADON);
+ break;
+ case '>': /* DECKPNM*/
+ input_store_zero(b, CODE_KKEYPADOFF);
+ break;
+ default:
+ log_debug("unknown private2: %c (%hhu)", ch, ch);
+ break;
+ }
+
+ return (0);
+}
+
+/* Translate a standard two-character sequence. */
+int
+input_pair_standard(unused u_char **buf, unused size_t *len,
+ unused struct buffer *b, unused struct screen *s, u_char ch)
+{
+ log_debug("unknown standard2: %c (%hhu)", ch, ch);
+
+ return (0);
+}
+
+/* Translate a control two-character sequence. */
+int
+input_pair_control(u_char **buf, size_t *len,
+ struct buffer *b, unused struct screen *s, u_char ch)
+{
+ u_char *ptr;
+ size_t size;
+
+ log_debug("control2: %c (%hhu)", ch, ch);
+
+ switch (ch) {
+ case ']': /* window title */
+ if (*len < 3)
+ return (1);
+ ch = *(*buf)++; (*len)--;
+
+ /*
+ * Search MAXTITLELEN + 1 to allow space for the ;. The
+ * \007 is also included, but space is needed for a \0 so
+ * it doesn't need to be compensated for.
+ */
+ size = *len > MAXTITLELEN + 1 ? MAXTITLELEN + 1 : *len;
+ if ((ptr = memchr(*buf, '\007', size)) == NULL) {
+ log_debug("title not found in %zu bytes", size);
+ if (*len >= MAXTITLELEN + 1)
+ break;
+ (*buf)--; (*len)++;
+ return (1);
+ }
+ size = ptr - *buf;
+
+ /* A zero size means no ;, just skip the \007 and return. */
+ if (size == 0) {
+ (*buf)++; (*len)--;
+ break;
+ }
+
+ /* Set the title if appropriate. */
+ if (**buf == ';' && (ch == '0' || ch == '1')) {
+ log_debug("title found, length %zu bytes: %.*s",
+ size - 1, (int) size - 1, *buf + 1);
+ input_store_one(b, CODE_TITLE, size - 1);
+ buffer_write(b, *buf + 1, size - 1);
+ }
+
+ /* Skip the title; add one for the \007. */
+ (*buf) += size + 1;
+ (*len) -= size + 1;
+ break;
+ case 'M': /* RI */
+ input_store_one(b, CODE_CURSORUPSCROLL, 1);
+ break;
+ default:
+ log_debug("unknown control2: %c (%hhu)", ch, ch);
+ break;
+ }
+
+ return (0);
+}
+
+/* Translate a control sequence. */
+int
+input_control_sequence(
+ u_char **buf, size_t *len, struct buffer *b, struct screen *s)
+{
+ u_char code, private;
+ size_t used;
+ uint16_t *argv, ua, ub;
+ u_int argc, i;
+
+ used = input_sequence(*buf, *len, &code, &private, &argv, &argc);
+ if (used == 0) /* incomplete */
+ return (1);
+
+ (*buf) += used;
+ (*len) -= used;
+
+ if (code == '\0') /* invalid */
+ return (-1);
+
+ log_debug(
+ "sequence: %c (%hhu) [%c] [cx %u, cy %u, sx %u, sy %u]: %u", code,
+ code, private, s->cx + 1, s->cy + 1, s->sx + 1, s->sy + 1, argc);
+ for (i = 0; i < argc; i++)
+ log_debug("\targument %u: %u", i, argv[i]);
+
+ switch (code) {
+ case 'A': /* CUU */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_CURSORUP, ua);
+ break;
+ case 'B': /* CUD */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_CURSORDOWN, ua);
+ break;
+ case 'C': /* CUF */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_CURSORRIGHT, ua);
+ break;
+ case 'D': /* CUB */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_CURSORLEFT, ua);
+ break;
+ case 'P': /* DCH */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_DELETECHARACTER, ua);
+ break;
+ case 'M': /* DL */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_DELETELINE, ua);
+ break;
+ case '@': /* ICH */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_INSERTCHARACTER, ua);
+ break;
+ case 'L': /* IL */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_INSERTLINE, ua);
+ break;
+ case 'd': /* VPA */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_two(b, CODE_CURSORMOVE, ua, s->cx + 1);
+ break;
+ case 'G': /* HPA */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_two(b, CODE_CURSORMOVE, s->cy + 1, ua);
+ break;
+ case 'H': /* CUP */
+ case 'f': /* HVP */
+ if (private != '\0')
+ break;
+ if (input_check_two(argv, argc, &ua, 1, &ub, 1) != 0)
+ break;
+ if (ua == 0 || ub == 0)
+ break;
+ input_store_two(b, CODE_CURSORMOVE, ua, ub);
+ break;
+ case 'J': /* ED */
+ if (private != '\0')
+ break;
+ if (input_check_one2(argv, argc, &ua, 0, 0, 2) != 0)
+ break;
+ switch (ua) {
+ case 0:
+ input_store_zero(b, CODE_CLEARENDOFSCREEN);
+ break;
+ case 2:
+ input_store_zero(b, CODE_CLEARSCREEN);
+ break;
+ }
+ break;
+ case 'K': /* EL */
+ if (private != '\0')
+ break;
+ if (input_check_one2(argv, argc, &ua, 0, 0, 2) != 0)
+ break;
+ switch (ua) {
+ case 0:
+ input_store_zero(b, CODE_CLEARENDOFLINE);
+ break;
+ case 1:
+ input_store_zero(b, CODE_CLEARSTARTOFLINE);
+ break;
+ case 2:
+ input_store_zero(b, CODE_CLEARLINE);
+ break;
+ }
+ break;
+ case 'h': /* SM */
+ if (input_check_one(argv, argc, &ua, 0) != 0)
+ break;
+ switch (private) {
+ case '?':
+ switch (ua) {
+ case 1: /* GATM */
+ input_store_zero(b, CODE_KCURSORON);
+ break;
+ case 25: /* TCEM */
+ input_store_zero(b, CODE_CURSORON);
+ break;
+ default:
+ log_debug("unknown SM [%d]: %u", private, ua);
+ }
+ break;
+ case '\0':
+ switch (ua) {
+ case 4: /* IRM */
+ input_store_zero(b, CODE_INSERTON);
+ break;
+ case 34:
+ /* Cursor high visibility not supported. */
+ break;
+ default:
+ log_debug("unknown SM [%d]: %u", private, ua);
+ break;
+ }
+ break;
+ }
+ break;
+ case 'l': /* RM */
+ if (input_check_one(argv, argc, &ua, 0) != 0)
+ break;
+ switch (private) {
+ case '?':
+ switch (ua) {
+ case 1: /* GATM */
+ input_store_zero(b, CODE_KCURSOROFF);
+ break;
+ case 25: /* TCEM */
+ input_store_zero(b, CODE_CURSOROFF);
+ break;
+ default:
+ log_debug("unknown RM [%d]: %u", private, ua);
+ }
+ break;
+ case '\0':
+ switch (ua) {
+ case 4: /* IRM */
+ input_store_zero(b, CODE_INSERTOFF);
+ break;
+ case 34:
+ /* Cursor high visibility not supported. */
+ break;
+ default:
+ log_debug("unknown RM [%d]: %u", private, ua);
+ break;
+ }
+ break;
+ }
+ break;
+ case 'r': /* DECSTBM */
+ if (private != '\0')
+ break;
+ if (input_check_two(argv, argc,
+ &ua, s->ry_upper + 1, &ub, s->ry_lower + 1) != 0)
+ break;
+ if (ua == 0 || ub == 0 || ub < ua)
+ break;
+ input_store_two(b, CODE_SCROLLREGION, ua, ub);
+ break;
+ case 'm': /* SGR */
+ input_store_zero(b, CODE_ATTRIBUTES);
+ if (argc == 0) {
+ input_store16(b, 1);
+ input_store16(b, 0);
+ } else {
+ input_store16(b, argc);
+ for (i = 0; i < argc; i++) {
+ if (argv[i] == UINT16_MAX)
+ argv[i] = 0;
+ input_store16(b, argv[i]);
+ }
+ }
+ break;
+ default:
+ log_debug("unknown sequence: %c (%hhu)", code, code);
+ break;
+ }
+
+ if (argv != NULL) {
+ xfree(argv);
+ argv = NULL;
+ }
+
+ return (0);
+}
+
+/* Check for one argument. */
+int
+input_check_one(uint16_t *argv, u_int argc, uint16_t *a, uint16_t ad)
+{
+ *a = ad;
+ if (argc == 1) {
+ if (argv[0] != UINT16_MAX)
+ *a = argv[0];
+ } else if (argc != 0)
+ return (-1);
+ return (0);
+}
+
+/* Check for one argument with limits. */
+int
+input_check_one2(uint16_t *argv, u_int argc,
+ uint16_t *a, uint16_t ad, uint16_t al, uint16_t au)
+{
+ *a = ad;
+ if (argc == 1) {
+ if (argv[0] != UINT16_MAX)
+ *a = argv[0];
+ } else if (argc != 0)
+ return (-1);
+ if (*a < al || *a > au)
+ return (-1);
+ return (0);
+}
+
+/* Check for two arguments. */
+int
+input_check_two(uint16_t *argv, u_int argc,
+ uint16_t *a, uint16_t ad, uint16_t *b, uint16_t bd)
+{
+ *a = ad;
+ *b = bd;
+ if (argc == 1) {
+ if (argv[0] != UINT16_MAX)
+ *a = argv[0];
+ } else if (argc == 2) {
+ if (argv[0] != UINT16_MAX)
+ *a = argv[0];
+ if (argv[1] != UINT16_MAX)
+ *b = argv[1];
+ } else if (argc != 0)
+ return (-1);
+ return (0);
+}
+
+/* Store a code without arguments. */
+void
+input_store_zero(struct buffer *b, u_char code)
+{
+ input_store8(b, '\e');
+ input_store8(b, code);
+}
+
+/* Store a code with a single argument. */
+void
+input_store_one(struct buffer *b, u_char code, uint16_t ua)
+{
+ input_store8(b, '\e');
+ input_store8(b, code);
+ input_store16(b, ua);
+}
+
+/* Store a code with two arguments. */
+void
+input_store_two(struct buffer *b, u_char code, uint16_t ua, uint16_t ub)
+{
+ input_store8(b, '\e');
+ input_store8(b, code);
+ input_store16(b, ua);
+ input_store16(b, ub);
+}
+
+/* Write an 8-bit quantity to a buffer. */
+void
+input_store8(struct buffer *b, uint8_t n)
+{
+ buffer_write(b, &n, sizeof n);
+}
+
+/* Write a 16-bit argument to a buffer. */
+void
+input_store16(struct buffer *b, uint16_t n)
+{
+ buffer_write(b, &n, sizeof n);
+}
+
+/* Extract an 8-bit quantity from a buffer. */
+uint8_t
+input_extract8(struct buffer *b)
+{
+ uint8_t n;
+
+ buffer_read(b, &n, sizeof n);
+ return (n);
+}
+
+/* Extract a 16-bit argument from a pointer. */
+uint16_t
+input_extract16(struct buffer *b)
+{
+ uint16_t n;
+
+ buffer_read(b, &n, sizeof n);
+ return (n);
+}
diff --git a/local.c b/local.c
new file mode 100644
index 00000000..eeadb659
--- /dev/null
+++ b/local.c
@@ -0,0 +1,690 @@
+/* $Id: local.c,v 1.1.1.1 2007-07-09 19:03:30 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <curses.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <term.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Functions to translate input and write output to the local client terminal.
+ * This file should probably be called tty or terminal.c.
+ */
+
+int local_cmp(const void *, const void *);
+int local_putc(int);
+void local_putp(const char *);
+
+/* Local key types and key codes. */
+struct local_key {
+ const char *name;
+ char *string;
+ size_t size;
+ int code;
+};
+struct local_key local_keys[] = {
+ { "ka1", NULL, 0, KEYC_A1 },
+ { "ka3", NULL, 0, KEYC_A3 },
+ { "kb2", NULL, 0, KEYC_B2 },
+ { "kbs", NULL, 0, KEYC_BACKSPACE },
+ { "kbeg", NULL, 0, KEYC_BEG },
+ { "kcbt", NULL, 0, KEYC_BTAB },
+ { "kc1", NULL, 0, KEYC_C1 },
+ { "kc3", NULL, 0, KEYC_C3 },
+ { "kcan", NULL, 0, KEYC_CANCEL },
+ { "ktbc", NULL, 0, KEYC_CATAB },
+ { "kclr", NULL, 0, KEYC_CLEAR },
+ { "kclo", NULL, 0, KEYC_CLOSE },
+ { "kcmd", NULL, 0, KEYC_COMMAND },
+ { "kcpy", NULL, 0, KEYC_COPY },
+ { "kcrt", NULL, 0, KEYC_CREATE },
+ { "kctab", NULL, 0, KEYC_CTAB },
+ { "kdch1", NULL, 0, KEYC_DC },
+ { "kdl1", NULL, 0, KEYC_DL },
+ { "kcud1", NULL, 0, KEYC_DOWN },
+ { "krmir", NULL, 0, KEYC_EIC },
+ { "kend", NULL, 0, KEYC_END },
+ { "kent", NULL, 0, KEYC_ENTER },
+ { "kel", NULL, 0, KEYC_EOL },
+ { "ked", NULL, 0, KEYC_EOS },
+ { "kext", NULL, 0, KEYC_EXIT },
+ { "kf0", NULL, 0, KEYC_F0 },
+ { "kf1", NULL, 0, KEYC_F1 },
+ { "kf10", NULL, 0, KEYC_F10 },
+ { "kf11", NULL, 0, KEYC_F11 },
+ { "kf12", NULL, 0, KEYC_F12 },
+ { "kf13", NULL, 0, KEYC_F13 },
+ { "kf14", NULL, 0, KEYC_F14 },
+ { "kf15", NULL, 0, KEYC_F15 },
+ { "kf16", NULL, 0, KEYC_F16 },
+ { "kf17", NULL, 0, KEYC_F17 },
+ { "kf18", NULL, 0, KEYC_F18 },
+ { "kf19", NULL, 0, KEYC_F19 },
+ { "kf2", NULL, 0, KEYC_F2 },
+ { "kf20", NULL, 0, KEYC_F20 },
+ { "kf21", NULL, 0, KEYC_F21 },
+ { "kf22", NULL, 0, KEYC_F22 },
+ { "kf23", NULL, 0, KEYC_F23 },
+ { "kf24", NULL, 0, KEYC_F24 },
+ { "kf25", NULL, 0, KEYC_F25 },
+ { "kf26", NULL, 0, KEYC_F26 },
+ { "kf27", NULL, 0, KEYC_F27 },
+ { "kf28", NULL, 0, KEYC_F28 },
+ { "kf29", NULL, 0, KEYC_F29 },
+ { "kf3", NULL, 0, KEYC_F3 },
+ { "kf30", NULL, 0, KEYC_F30 },
+ { "kf31", NULL, 0, KEYC_F31 },
+ { "kf32", NULL, 0, KEYC_F32 },
+ { "kf33", NULL, 0, KEYC_F33 },
+ { "kf34", NULL, 0, KEYC_F34 },
+ { "kf35", NULL, 0, KEYC_F35 },
+ { "kf36", NULL, 0, KEYC_F36 },
+ { "kf37", NULL, 0, KEYC_F37 },
+ { "kf38", NULL, 0, KEYC_F38 },
+ { "kf39", NULL, 0, KEYC_F39 },
+ { "kf4", NULL, 0, KEYC_F4 },
+ { "kf40", NULL, 0, KEYC_F40 },
+ { "kf41", NULL, 0, KEYC_F41 },
+ { "kf42", NULL, 0, KEYC_F42 },
+ { "kf43", NULL, 0, KEYC_F43 },
+ { "kf44", NULL, 0, KEYC_F44 },
+ { "kf45", NULL, 0, KEYC_F45 },
+ { "kf46", NULL, 0, KEYC_F46 },
+ { "kf47", NULL, 0, KEYC_F47 },
+ { "kf48", NULL, 0, KEYC_F48 },
+ { "kf49", NULL, 0, KEYC_F49 },
+ { "kf5", NULL, 0, KEYC_F5 },
+ { "kf50", NULL, 0, KEYC_F50 },
+ { "kf51", NULL, 0, KEYC_F51 },
+ { "kf52", NULL, 0, KEYC_F52 },
+ { "kf53", NULL, 0, KEYC_F53 },
+ { "kf54", NULL, 0, KEYC_F54 },
+ { "kf55", NULL, 0, KEYC_F55 },
+ { "kf56", NULL, 0, KEYC_F56 },
+ { "kf57", NULL, 0, KEYC_F57 },
+ { "kf58", NULL, 0, KEYC_F58 },
+ { "kf59", NULL, 0, KEYC_F59 },
+ { "kf6", NULL, 0, KEYC_F6 },
+ { "kf60", NULL, 0, KEYC_F60 },
+ { "kf61", NULL, 0, KEYC_F61 },
+ { "kf62", NULL, 0, KEYC_F62 },
+ { "kf63", NULL, 0, KEYC_F63 },
+ { "kf7", NULL, 0, KEYC_F7 },
+ { "kf8", NULL, 0, KEYC_F8 },
+ { "kf9", NULL, 0, KEYC_F9 },
+ { "kfnd", NULL, 0, KEYC_FIND },
+ { "khlp", NULL, 0, KEYC_HELP },
+ { "khome", NULL, 0, KEYC_HOME },
+ { "kich1", NULL, 0, KEYC_IC },
+ { "kil1", NULL, 0, KEYC_IL },
+ { "kcub1", NULL, 0, KEYC_LEFT },
+ { "kll", NULL, 0, KEYC_LL },
+ { "kmrk", NULL, 0, KEYC_MARK },
+ { "kmsg", NULL, 0, KEYC_MESSAGE },
+ { "kmov", NULL, 0, KEYC_MOVE },
+ { "knxt", NULL, 0, KEYC_NEXT },
+ { "knp", NULL, 0, KEYC_NPAGE },
+ { "kopn", NULL, 0, KEYC_OPEN },
+ { "kopt", NULL, 0, KEYC_OPTIONS },
+ { "kpp", NULL, 0, KEYC_PPAGE },
+ { "kprv", NULL, 0, KEYC_PREVIOUS },
+ { "kprt", NULL, 0, KEYC_PRINT },
+ { "krdo", NULL, 0, KEYC_REDO },
+ { "kref", NULL, 0, KEYC_REFERENCE },
+ { "krfr", NULL, 0, KEYC_REFRESH },
+ { "krpl", NULL, 0, KEYC_REPLACE },
+ { "krst", NULL, 0, KEYC_RESTART },
+ { "kres", NULL, 0, KEYC_RESUME },
+ { "kcuf1", NULL, 0, KEYC_RIGHT },
+ { "ksav", NULL, 0, KEYC_SAVE },
+ { "kBEG", NULL, 0, KEYC_SBEG },
+ { "kCAN", NULL, 0, KEYC_SCANCEL },
+ { "kCMD", NULL, 0, KEYC_SCOMMAND },
+ { "kCPY", NULL, 0, KEYC_SCOPY },
+ { "kCRT", NULL, 0, KEYC_SCREATE },
+ { "kDC", NULL, 0, KEYC_SDC },
+ { "kDL", NULL, 0, KEYC_SDL },
+ { "kslt", NULL, 0, KEYC_SELECT },
+ { "kEND", NULL, 0, KEYC_SEND },
+ { "kEOL", NULL, 0, KEYC_SEOL },
+ { "kEXT", NULL, 0, KEYC_SEXIT },
+ { "kind", NULL, 0, KEYC_SF },
+ { "kFND", NULL, 0, KEYC_SFIND },
+ { "kHLP", NULL, 0, KEYC_SHELP },
+ { "kHOM", NULL, 0, KEYC_SHOME },
+ { "kIC", NULL, 0, KEYC_SIC },
+ { "kLFT", NULL, 0, KEYC_SLEFT },
+ { "kMSG", NULL, 0, KEYC_SMESSAGE },
+ { "kMOV", NULL, 0, KEYC_SMOVE },
+ { "kNXT", NULL, 0, KEYC_SNEXT },
+ { "kOPT", NULL, 0, KEYC_SOPTIONS },
+ { "kPRV", NULL, 0, KEYC_SPREVIOUS },
+ { "kPRT", NULL, 0, KEYC_SPRINT },
+ { "kri", NULL, 0, KEYC_SR },
+ { "kRDO", NULL, 0, KEYC_SREDO },
+ { "kRPL", NULL, 0, KEYC_SREPLACE },
+ { "kRIT", NULL, 0, KEYC_SRIGHT },
+ { "kRES", NULL, 0, KEYC_SRSUME },
+ { "kSAV", NULL, 0, KEYC_SSAVE },
+ { "kSPD", NULL, 0, KEYC_SSUSPEND },
+ { "khts", NULL, 0, KEYC_STAB },
+ { "kUND", NULL, 0, KEYC_SUNDO },
+ { "kspd", NULL, 0, KEYC_SUSPEND },
+ { "kund", NULL, 0, KEYC_UNDO },
+ { "kcuu1", NULL, 0, KEYC_UP },
+ { "pmous", NULL, 0, KEYC_MOUSE },
+ { NULL, NULL, 0, KEYC_NONE }
+};
+
+/* tty file descriptor and local terminal buffers. */
+int local_fd;
+struct buffer *local_in;
+struct buffer *local_out;
+struct termios local_tio;
+
+/* Initialise local terminal. */
+int
+local_init(struct buffer **in, struct buffer **out)
+{
+ char *tty;
+ int mode;
+ struct termios tio;
+ struct local_key *lk;
+
+ if ((tty = ttyname(STDOUT_FILENO)) == NULL)
+ log_fatal("ttyname");
+ if ((local_fd = open(tty, O_RDWR)) == -1)
+ log_fatal("open(\"%s\")", tty);
+ if ((mode = fcntl(local_fd, F_GETFL)) == -1)
+ log_fatal("fcntl");
+ if (fcntl(local_fd, F_SETFL, mode|O_NONBLOCK) == -1)
+ log_fatal("fcntl");
+
+ *in = local_in = buffer_create(BUFSIZ);
+ *out = local_out = buffer_create(BUFSIZ);
+
+ setupterm(NULL, STDOUT_FILENO, NULL);
+
+ if (tcgetattr(local_fd, &local_tio) != 0)
+ log_fatal("tcgetattr");
+ memcpy(&tio, &local_tio, sizeof tio);
+ tio.c_iflag &= ~(IXON|IXOFF|ICRNL|INLCR);
+ tio.c_oflag &= ~(OPOST|ONLCR|OCRNL|ONLRET);
+ tio.c_lflag &= ~(IEXTEN|ICANON|ECHO|ECHOE|ECHOKE|ECHOCTL|ISIG);
+ if (tcsetattr(local_fd, TCSANOW, &tio) != 0)
+ log_fatal("tcsetattr");
+
+ if (enter_ca_mode)
+ local_putp(enter_ca_mode);
+ local_putp(keypad_xmit);
+ local_putp(clear_screen);
+
+ for (lk = local_keys; lk->name != NULL; lk++) {
+ lk->string = tigetstr(lk->name);
+ if (lk->string == (char *) -1 || lk->string == (char *) 0)
+ lk->string = NULL;
+ else {
+ lk->size = strlen(lk->string);
+ log_debug("string for %s (%d): \"%s\", length %zu",
+ lk->name, lk->code, lk->string, lk->size);
+ }
+ }
+ qsort(local_keys, sizeof local_keys /
+ sizeof local_keys[0], sizeof local_keys[0], local_cmp);
+
+ return (local_fd);
+}
+
+/* Compare keys. */
+int
+local_cmp(const void *ptr1, const void *ptr2)
+{
+ const struct local_key *lk1 = ptr1, *lk2 = ptr2;
+
+ if (lk1->string == NULL && lk2->string == NULL)
+ return (0);
+ if (lk1->string == NULL)
+ return (1);
+ if (lk2->string == NULL)
+ return (-1);
+ return (lk2->size - lk1->size);
+}
+
+/* Tidy up and reset local terminal. */
+void
+local_done(void)
+{
+ xfree(local_in);
+ xfree(local_out);
+
+ if (tcsetattr(local_fd, TCSANOW, &local_tio) != 0)
+ log_fatal("tcsetattr");
+ close(local_fd);
+
+ putp(keypad_local); /* not local_putp */
+ if (exit_ca_mode)
+ putp(exit_ca_mode);
+ putp(clear_screen);
+ putp(cursor_normal);
+ putp(exit_attribute_mode);
+}
+
+/* Put a character. Used as parameter to tputs. */
+int
+local_putc(int c)
+{
+ FILE *f;
+ u_char ch = c;
+
+ if (c < 0 || c > (int) UCHAR_MAX)
+ log_fatalx("local_putc: invalid character");
+
+ if (debug_level > 1) {
+ f = fopen("tmux-out.log", "a+");
+ fprintf(f, "%c", ch);
+ fclose(f);
+ }
+
+ buffer_write(local_out, &ch, 1);
+ return (c);
+}
+
+/* Put terminfo string. */
+void
+local_putp(const char *s)
+{
+ if (s == NULL)
+ log_fatalx("local_putp: null pointer");
+
+ tputs(s, 1, local_putc);
+}
+
+/* Return waiting keys if any. */
+int
+local_key(size_t *used)
+{
+ struct local_key *lk;
+ u_int i;
+ size_t size;
+
+ size = BUFFER_USED(local_in);
+ if (size == 0)
+ return (KEYC_NONE);
+
+ i = 0;
+ lk = local_keys;
+ while (lk->string != NULL) {
+ if (strncmp(BUFFER_OUT(local_in), lk->string, size) == 0) {
+ if (size < lk->size)
+ return (KEYC_NONE);
+ log_debug("got key: %s %d", lk->name, lk->code);
+ buffer_remove(local_in, lk->size);
+ if (used != NULL)
+ *used = lk->size;
+ return (lk->code);
+ }
+
+ i++;
+ lk = local_keys + i;
+ }
+
+ if (used != NULL)
+ *used = 1;
+ return (input_extract8(local_in));
+}
+
+/* Display output data. */
+void
+local_output(struct buffer *b, size_t size)
+{
+ u_char ch;
+ uint16_t ua, ub;
+
+ while (size != 0) {
+ if (size < 1)
+ break;
+ size--;
+ ch = input_extract8(b);
+ if (ch != '\e') {
+ switch (ch) {
+ case '\n': /* LF */
+ if (cursor_down) {
+ local_putp(cursor_down);
+ break;
+ }
+ log_fatalx("local_output: cursor_down");
+ case '\r': /* CR */
+ if (carriage_return) {
+ local_putp(carriage_return);
+ break;
+ }
+ log_fatalx("local_output: carriage_return");
+ case '\010': /* BS */
+ if (cursor_left) {
+ local_putp(cursor_left);
+ break;
+ }
+ log_fatalx("local_output: cursor_left");
+ break;
+ default:
+ local_putc(ch);
+ break;
+ }
+ continue;
+ }
+
+ if (size < 1)
+ log_fatalx("local_output: underflow");
+ size--;
+ ch = input_extract8(b);
+
+ log_debug("received code %hhu", ch);
+ switch (ch) {
+ case CODE_CURSORUP:
+ if (size < 2)
+ log_fatalx("local_output: underflow");
+ size -= 2;
+ ua = input_extract16(b);
+ if (!parm_up_cursor)
+ log_fatalx("local_output: parm_up_cursor");
+ local_putp(tparm(parm_up_cursor, ua));
+ break;
+ case CODE_CURSORDOWN:
+ if (size < 2)
+ log_fatalx("local_output: underflow");
+ size -= 2;
+ ua = input_extract16(b);
+ if (!parm_down_cursor)
+ log_fatalx("local_output: parm_down_cursor");
+ local_putp(tparm(parm_down_cursor, ua));
+ break;
+ case CODE_CURSORRIGHT:
+ if (size < 2)
+ log_fatalx("local_output: underflow");
+ size -= 2;
+ ua = input_extract16(b);
+ if (!parm_right_cursor)
+ log_fatalx("local_output: parm_right_cursor");
+ local_putp(tparm(parm_right_cursor, ua));
+ break;
+ case CODE_CURSORLEFT:
+ if (size < 2)
+ log_fatalx("local_output: underflow");
+ size -= 2;
+ ua = input_extract16(b);
+ if (!parm_left_cursor)
+ log_fatalx("local_output: parm_left_cursor");
+ local_putp(tparm(parm_left_cursor, ua));
+ break;
+ case CODE_CURSORMOVE:
+ if (size < 4)
+ log_fatalx("local_output: underflow");
+ size -= 4;
+ ua = input_extract16(b);
+ ub = input_extract16(b);
+ if (!cursor_address)
+ log_fatalx("local_output: cursor_address");
+ local_putp(tparm(cursor_address, ua - 1, ub - 1));
+ break;
+ case CODE_CLEARENDOFSCREEN:
+ if (!clr_eos)
+ log_fatalx("local_output: clr_eos");
+ local_putp(clr_eos);
+ break;
+ case CODE_CLEARSCREEN:
+ if (!clear_screen)
+ log_fatalx("local_output: clear_screen");
+ local_putp(clear_screen);
+ break;
+ case CODE_CLEARENDOFLINE:
+ if (!clr_eol)
+ log_fatalx("local_output: clr_eol");
+ local_putp(clr_eol);
+ break;
+ case CODE_CLEARSTARTOFLINE:
+ if (!clr_bol)
+ log_fatalx("local_output: clr_bol");
+ local_putp(clr_bol);
+ break;
+ case CODE_CLEARLINE:
+ if (!clr_eol)
+ log_fatalx("local_output: clr_eol");
+ local_putp(clr_eol); /* XXX */
+ break;
+ case CODE_INSERTLINE:
+ if (size < 2)
+ log_fatalx("local_output: underflow");
+ size -= 2;
+ ua = input_extract16(b);
+ if (!parm_insert_line)
+ log_fatalx("local_output: parm_insert_line");
+ local_putp(tparm(parm_insert_line, ua));
+ break;
+ case CODE_DELETELINE:
+ if (size < 2)
+ log_fatalx("local_output: underflow");
+ size -= 2;
+ ua = input_extract16(b);
+ if (!parm_delete_line)
+ log_fatalx("local_output: parm_delete");
+ local_putp(tparm(parm_delete_line, ua));
+ break;
+ case CODE_INSERTCHARACTER:
+ if (size < 2)
+ log_fatalx("local_output: underflow");
+ size -= 2;
+ ua = input_extract16(b);
+ if (!parm_ich)
+ log_fatalx("local_output: parm_ich");
+ local_putp(tparm(parm_ich, ua));
+ break;
+ case CODE_DELETECHARACTER:
+ if (size < 2)
+ log_fatalx("local_output: underflow");
+ size -= 2;
+ ua = input_extract16(b);
+ if (!parm_dch)
+ log_fatalx("local_output: parm_dch");
+ local_putp(tparm(parm_dch, ua));
+ break;
+ case CODE_CURSORON:
+ if (!cursor_normal)
+ log_fatalx("local_output: cursor_normal");
+ local_putp(cursor_normal);
+ break;
+ case CODE_CURSOROFF:
+ if (!cursor_invisible)
+ log_fatalx("local_output: cursor_invisible");
+ local_putp(cursor_invisible);
+ break;
+ case CODE_CURSORUPSCROLL:
+ if (!scroll_reverse)
+ log_fatalx("local_output: scroll_reverse");
+ local_putp(scroll_reverse);
+ break;
+ case CODE_CURSORDOWNSCROLL:
+ if (!scroll_forward)
+ log_fatalx("local_output: scroll_forward");
+ local_putp(scroll_forward);
+ break;
+ case CODE_SCROLLREGION:
+ if (size < 4)
+ log_fatalx("local_output: underflow");
+ size -= 4;
+ ua = input_extract16(b);
+ ub = input_extract16(b);
+ if (!change_scroll_region) {
+ log_fatalx(
+ "local_output: change_scroll_region");
+ }
+ local_putp(tparm(change_scroll_region, ua - 1, ub - 1));
+ break;
+ case CODE_INSERTON:
+ if (!enter_insert_mode)
+ log_fatalx("local_output: enter_insert_mode");
+ local_putp(enter_insert_mode);
+ break;
+ case CODE_INSERTOFF:
+ if (!exit_insert_mode)
+ log_fatalx("local_output: exit_insert_mode");
+ local_putp(exit_insert_mode);
+ break;
+ case CODE_KCURSOROFF:
+ /*t = tigetstr("CE");
+ if (t != (char *) 0 && t != (char *) -1)
+ local_putp(t);*/
+ break;
+ case CODE_KCURSORON:
+ /*t = tigetstr("CS");
+ if (t != (char *) 0 && t != (char *) -1)
+ local_putp(t);*/
+ break;
+ case CODE_KKEYPADOFF:/*
+ if (!keypad_local)
+ log_fatalx("local_output: keypad_local");
+ local_putp(keypad_local);*/
+ break;
+ case CODE_KKEYPADON:/*
+ if (!keypad_xmit)
+ log_fatalx("local_output: keypad_xmit");
+ local_putp(keypad_xmit);*/
+ break;
+ case CODE_TITLE:
+ if (size < 2)
+ log_fatalx("local_output: underflow");
+ size -= 2;
+ ua = input_extract16(b);
+
+ if (size < ua)
+ log_fatalx("local_output: underflow");
+ size -= ua;
+ buffer_remove(b, ua);
+ break;
+ case CODE_ATTRIBUTES:
+ if (size < 2)
+ log_fatalx("local_output: underflow");
+ size -= 2;
+ ua = input_extract16(b);
+
+ if (!exit_attribute_mode)
+ log_fatalx("local_output: exit_attribute_mode");
+ if (ua == 0) {
+ if (exit_attribute_mode)
+ local_putp(exit_attribute_mode);
+ break;
+ }
+
+ while (ua-- != 0) {
+ if (size < 2)
+ log_fatalx("local_output: underflow");
+ size -= 2;
+ ub = input_extract16(b);
+
+ switch (ub) {
+ case 0:
+ case 10:
+ if (exit_attribute_mode)
+ local_putp(exit_attribute_mode);
+ break;
+ case 1:
+ if (enter_bold_mode)
+ local_putp(enter_bold_mode);
+ break;
+ case 2:
+ if (enter_dim_mode)
+ local_putp(enter_dim_mode);
+ break;
+ case 3:
+ if (enter_standout_mode)
+ local_putp(enter_standout_mode);
+ break;
+ case 4:
+ if (enter_underline_mode)
+ local_putp(enter_underline_mode);
+ break;
+ case 5:
+ if (enter_blink_mode)
+ local_putp(enter_blink_mode);
+ break;
+ case 7:
+ if (enter_reverse_mode)
+ local_putp(enter_reverse_mode);
+ break;
+ case 8:
+ if (enter_secure_mode)
+ local_putp(enter_secure_mode);
+ break;
+ case 23:
+ if (exit_standout_mode)
+ local_putp(exit_standout_mode);
+ break;
+ case 24:
+ if (exit_underline_mode)
+ local_putp(exit_underline_mode);
+ break;
+ case 30:
+ case 31:
+ case 32:
+ case 33:
+ case 34:
+ case 35:
+ case 36:
+ case 37:
+ local_putp(
+ tparm(set_a_foreground, ub - 30));
+ break;
+ case 39:
+ if (tigetflag("AX") == TRUE)
+ local_putp("\e[39m");
+ else {
+ local_putp(
+ tparm(set_a_background, 0));
+ }
+ break;
+ case 40:
+ case 41:
+ case 42:
+ case 43:
+ case 44:
+ case 45:
+ case 46:
+ case 47:
+ local_putp(
+ tparm(set_a_background, ub - 40));
+ break;
+ case 49:
+ if (tigetflag("AX") == TRUE)
+ local_putp("\e[49m");
+ else {
+ local_putp(
+ tparm(set_a_background, 0));
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+}
diff --git a/log.c b/log.c
new file mode 100644
index 00000000..476eb189
--- /dev/null
+++ b/log.c
@@ -0,0 +1,218 @@
+/* $Id: log.c,v 1.1.1.1 2007-07-09 19:03:33 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "tmux.h"
+
+/* Logging enabled. */
+int log_enabled;
+
+/* Log stream or NULL to use syslog. */
+FILE *log_stream;
+
+/* Debug level. */
+int log_level;
+
+/* Open log. */
+void
+log_open(FILE *f, int facility, int level)
+{
+ log_stream = f;
+ log_level = level;
+
+ if (f == NULL)
+ openlog(__progname, LOG_PID|LOG_NDELAY, facility);
+ tzset();
+
+ log_enabled = 1;
+}
+
+/* Close logging. */
+void
+log_close(void)
+{
+ if (log_stream != NULL)
+ fclose(log_stream);
+
+ log_enabled = 0;
+}
+
+/* Write a log message. */
+void
+log_write(FILE *f, int priority, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ log_vwrite(f, priority, fmt, ap);
+ va_end(ap);
+}
+
+/* Write a log message. */
+void
+log_vwrite(FILE *f, int priority, const char *fmt, va_list ap)
+{
+ if (!log_enabled)
+ return;
+
+ if (f == NULL) {
+ vsyslog(priority, fmt, ap);
+ return;
+ }
+
+ if (vfprintf(f, fmt, ap) == -1)
+ exit(1);
+ if (fputc('\n', f) == EOF)
+ exit(1);
+ fflush(log_stream);
+}
+
+/* Log a warning with error string. */
+void printflike1
+log_warn(const char *msg, ...)
+{
+ va_list ap;
+ char *fmt;
+
+ if (!log_enabled)
+ return;
+
+ va_start(ap, msg);
+ if (asprintf(&fmt, "%s: %s", msg, strerror(errno)) == -1)
+ exit(1);
+ log_vwrite(log_stream, LOG_CRIT, fmt, ap);
+ xfree(fmt);
+ va_end(ap);
+}
+
+/* Log a warning. */
+void printflike1
+log_warnx(const char *msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ log_vwrite(log_stream, LOG_CRIT, msg, ap);
+ va_end(ap);
+}
+
+/* Log an informational message. */
+void printflike1
+log_info(const char *msg, ...)
+{
+ va_list ap;
+
+ if (log_level > -1) {
+ va_start(ap, msg);
+ if (log_stream == stderr)
+ log_vwrite(stdout, LOG_INFO, msg, ap);
+ else
+ log_vwrite(log_stream, LOG_INFO, msg, ap);
+ va_end(ap);
+ }
+}
+
+/* Log a debug message. */
+void printflike1
+log_debug(const char *msg, ...)
+{
+ va_list ap;
+
+ if (log_level > 0) {
+ va_start(ap, msg);
+ log_vwrite(log_stream, LOG_DEBUG, msg, ap);
+ va_end(ap);
+ }
+}
+
+/* Log a debug message at level 2. */
+void printflike1
+log_debug2(const char *msg, ...)
+{
+ va_list ap;
+
+ if (log_level > 1) {
+ va_start(ap, msg);
+ log_vwrite(log_stream, LOG_DEBUG, msg, ap);
+ va_end(ap);
+ }
+}
+
+/* Log a debug message at level 3. */
+void printflike1
+log_debug3(const char *msg, ...)
+{
+ va_list ap;
+
+ if (log_level > 2) {
+ va_start(ap, msg);
+ log_vwrite(log_stream, LOG_DEBUG, msg, ap);
+ va_end(ap);
+ }
+}
+
+/* Log a critical error, with error string if necessary, and die. */
+__dead void
+log_vfatal(const char *msg, va_list ap)
+{
+ char *fmt;
+
+ if (!log_enabled)
+ exit(1);
+
+ if (errno != 0) {
+ if (asprintf(&fmt, "fatal: %s: %s", msg, strerror(errno)) == -1)
+ exit(1);
+ log_vwrite(log_stream, LOG_CRIT, fmt, ap);
+ } else {
+ if (asprintf(&fmt, "fatal: %s", msg) == -1)
+ exit(1);
+ log_vwrite(log_stream, LOG_CRIT, fmt, ap);
+ }
+ xfree(fmt);
+
+ exit(1);
+}
+
+/* Log a critical error, with error string, and die. */
+__dead void
+log_fatal(const char *msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ log_vfatal(msg, ap);
+}
+
+/* Log a critical error and die. */
+__dead void
+log_fatalx(const char *msg, ...)
+{
+ va_list ap;
+
+ errno = 0;
+ va_start(ap, msg);
+ log_vfatal(msg, ap);
+}
diff --git a/screen.c b/screen.c
new file mode 100644
index 00000000..d997cb66
--- /dev/null
+++ b/screen.c
@@ -0,0 +1,827 @@
+/* $Id: screen.c,v 1.1.1.1 2007-07-09 19:03:54 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <string.h>
+
+#include "tmux.h"
+
+/*
+ * Virtual screen and basic ANSI terminal emulator.
+ */
+
+size_t screen_store_attributes(struct buffer *, u_char);
+size_t screen_store_colours(struct buffer *, u_char);
+void screen_free_lines(struct screen *, u_int, u_int);
+void screen_make_lines(struct screen *, u_int, u_int);
+void screen_move_lines(struct screen *, u_int, u_int, u_int);
+void screen_fill_lines(
+ struct screen *, u_int, u_int, u_char, u_char, u_char);
+uint16_t screen_extract(u_char *);
+void screen_write_character(struct screen *, u_char);
+void screen_cursor_up_scroll(struct screen *, u_int);
+void screen_cursor_down_scroll(struct screen *, u_int);
+void screen_scroll_up(struct screen *, u_int);
+void screen_scroll_down(struct screen *, u_int);
+void screen_fill_screen(struct screen *, u_char, u_char, u_char);
+void screen_fill_line(struct screen *, u_int, u_char, u_char, u_char);
+void screen_fill_end_of_screen(
+ struct screen *, u_int, u_int, u_char, u_char, u_char);
+void screen_fill_end_of_line(
+ struct screen *, u_int, u_int, u_char, u_char, u_char);
+void screen_fill_start_of_line(
+ struct screen *, u_int, u_int, u_char, u_char, u_char);
+void screen_insert_lines(struct screen *, u_int, u_int);
+void screen_delete_lines(struct screen *, u_int, u_int);
+void screen_insert_characters(struct screen *, u_int, u_int, u_int);
+void screen_delete_characters(struct screen *, u_int, u_int, u_int);
+
+#define SCREEN_DEFDATA ' '
+#define SCREEN_DEFATTR 0
+#define SCREEN_DEFCOLR 0x88
+
+#define screen_last_y(s) ((s)->sy - 1)
+#define screen_last_x(s) ((s)->sx - 1)
+
+/* Create a new screen. */
+void
+screen_create(struct screen *s, u_int sx, u_int sy)
+{
+ s->sx = sx;
+ s->sy = sy;
+ s->cx = 0;
+ s->cy = 0;
+
+ s->ry_upper = 0;
+ s->ry_lower = sy - 1;
+
+ s->attr = SCREEN_DEFATTR;
+ s->colr = SCREEN_DEFCOLR;
+
+ s->mode = MODE_CURSOR;
+ *s->title = '\0';
+
+ s->grid_data = xmalloc(sy * (sizeof *s->grid_data));
+ s->grid_attr = xmalloc(sy * (sizeof *s->grid_attr));
+ s->grid_colr = xmalloc(sy * (sizeof *s->grid_colr));
+ screen_make_lines(s, 0, screen_last_y(s));
+ screen_fill_screen(s, SCREEN_DEFDATA, 0, SCREEN_DEFCOLR);
+}
+
+/* Resize screen. */
+void
+screen_resize(struct screen *s, u_int sx, u_int sy)
+{
+ u_int i, ox, oy, ny;
+
+ if (sx < 1)
+ sx = 1;
+ if (sy < 1)
+ sy = 1;
+
+ ox = s->sx;
+ oy = s->sy;
+ s->sx = sx;
+ s->sy = sy;
+
+ log_debug("resizing screen (%u, %u) -> (%u, %u)", ox, oy, sx, sy);
+
+ if (sy < oy) {
+ ny = oy - sy;
+ if (ny > s->cy)
+ ny = s->cy;
+
+ if (ny != 0) {
+ log_debug("removing %u lines from top", ny);
+ for (i = 0; i < ny; i++) {
+ log_debug("freeing line %u", i);
+ xfree(s->grid_data[i]);
+ xfree(s->grid_attr[i]);
+ xfree(s->grid_colr[i]);
+ }
+ memmove(s->grid_data, s->grid_data + ny,
+ (oy - ny) * (sizeof *s->grid_data));
+ memmove(s->grid_attr, s->grid_attr + ny,
+ (oy - ny) * (sizeof *s->grid_attr));
+ memmove(s->grid_colr, s->grid_colr + ny,
+ (oy - ny) * (sizeof *s->grid_colr));
+ s->cy -= ny;
+ }
+ if (ny < oy - sy) {
+ log_debug(
+ "removing %u lines from bottom", oy - sy - ny);
+ for (i = sy; i < oy - ny; i++) {
+ log_debug("freeing line %u", i);
+ xfree(s->grid_data[i]);
+ xfree(s->grid_attr[i]);
+ xfree(s->grid_colr[i]);
+ }
+ if (s->cy >= sy)
+ s->cy = sy - 1;
+ }
+ }
+ if (sy != oy) {
+ s->grid_data = xrealloc(s->grid_data, sy, sizeof *s->grid_data);
+ s->grid_attr = xrealloc(s->grid_attr, sy, sizeof *s->grid_attr);
+ s->grid_colr = xrealloc(s->grid_colr, sy, sizeof *s->grid_colr);
+ }
+ if (sy > oy) {
+ for (i = oy; i < sy; i++) {
+ log_debug("allocating line %u", i);
+ s->grid_data[i] = xmalloc(sx);
+ s->grid_attr[i] = xmalloc(sx);
+ s->grid_colr[i] = xmalloc(sx);
+ screen_fill_line(s, i,
+ SCREEN_DEFDATA, SCREEN_DEFATTR, SCREEN_DEFCOLR);
+ }
+ sy = oy;
+ }
+
+ if (sx != ox) {
+ for (i = 0; i < sy; i++) {
+ log_debug("adjusting line %u to %u", i, sx);
+ s->grid_data[i] = xrealloc(s->grid_data[i], sx, 1);
+ s->grid_attr[i] = xrealloc(s->grid_attr[i], sx, 1);
+ s->grid_colr[i] = xrealloc(s->grid_colr[i], sx, 1);
+ if (sx > ox) {
+ screen_fill_end_of_line(s, ox, i,
+ SCREEN_DEFDATA, SCREEN_DEFATTR,
+ SCREEN_DEFCOLR);
+ }
+ }
+ if (s->cx >= sx)
+ s->cx = sx - 1;
+ }
+}
+
+/* Draw a set of lines on the screen. */
+void
+screen_draw(struct screen *s, struct buffer *b, u_int py_upper, u_int py_lower)
+{
+ u_char attr, colr;
+ size_t size;
+ u_int i, j;
+ uint16_t n;
+
+ if (py_upper > s->sy || py_lower > s->sy || py_upper > py_lower) {
+ log_fatalx(
+ "screen_draw_lines: invalid, %u to %u", py_upper, py_lower);
+ }
+
+ /* XXX. This is naive and rough right now. */
+ attr = 0;
+ colr = SCREEN_DEFCOLR;
+
+ input_store_zero(b, CODE_CURSOROFF);
+
+ input_store_one(b, CODE_ATTRIBUTES, 0);
+
+ for (j = py_upper; j <= py_lower; j++) {
+ input_store_two(b, CODE_CURSORMOVE, j + 1, 1);
+
+ for (i = 0; i < s->sx; i++) {
+
+ size = BUFFER_USED(b);
+ input_store_one(b, CODE_ATTRIBUTES, 0);
+
+ n = 0;
+ if (s->grid_attr[j][i] != attr) {
+ attr = s->grid_attr[j][i];
+ n += screen_store_attributes(b, attr);
+ if (attr == 0)
+ colr = SCREEN_DEFCOLR;
+ }
+ if (s->grid_colr[j][i] != colr) {
+ colr = s->grid_colr[j][i];
+ n += screen_store_colours(b, colr);
+ }
+ if (n == 0)
+ buffer_reverse_add(b, 4);
+ else {
+ size = BUFFER_USED(b) - size;
+ memcpy(BUFFER_IN(b) - size + 2, &n, 2);
+ }
+
+ input_store8(b, s->grid_data[j][i]);
+ }
+ }
+
+ size = BUFFER_USED(b);
+ input_store_one(b, CODE_ATTRIBUTES, 0);
+ n = screen_store_attributes(b, s->attr);
+ n += screen_store_colours(b, s->colr);
+ size = BUFFER_USED(b) - size;
+ memcpy(BUFFER_IN(b) - size + 2, &n, 2);
+
+ input_store_two(b, CODE_CURSORMOVE, s->cy + 1, s->cx + 1);
+
+ if (s->mode & MODE_CURSOR)
+ input_store_zero(b, CODE_CURSORON);
+}
+
+/* Store screen atttributes in buffer. */
+size_t
+screen_store_attributes(struct buffer *b, u_char attr)
+{
+ size_t n;
+
+ if (attr == 0) {
+ input_store16(b, 0);
+ return (1);
+ }
+
+ n = 0;
+ if (attr & ATTR_BRIGHT) {
+ input_store16(b, 1);
+ n++;
+ }
+ if (attr & ATTR_DIM) {
+ input_store16(b, 2);
+ n++;
+ }
+ if (attr & ATTR_ITALICS) {
+ input_store16(b, 3);
+ n++;
+ }
+ if (attr & ATTR_UNDERSCORE) {
+ input_store16(b, 4);
+ n++;
+ }
+ if (attr & ATTR_BLINK) {
+ input_store16(b, 5);
+ n++;
+ }
+ if (attr & ATTR_REVERSE) {
+ input_store16(b, 7);
+ n++;
+ }
+ if (attr & ATTR_HIDDEN) {
+ input_store16(b, 8);
+ n++;
+ }
+ return (n);
+}
+
+/* Store screen colours in buffer. */
+size_t
+screen_store_colours(struct buffer *b, u_char colr)
+{
+ uint16_t v;
+
+ v = colr >> 4;
+ if (v == 8)
+ v = 9;
+ input_store16(b, 30 + v);
+ v = colr & 0xf;
+ if (v == 8)
+ v = 9;
+ input_store16(b, 40 + v);
+
+ return (2);
+}
+
+/* Make a range of lines. */
+void
+screen_make_lines(struct screen *s, u_int uy, u_int ly)
+{
+ u_int i;
+
+ log_debug("making lines %u:%u", uy, ly);
+
+ if (uy > screen_last_y(s) || ly > screen_last_y(s) || ly < uy)
+ log_fatalx("screen_make_lines: bad range");
+
+ for (i = uy; i <= ly; i++) {
+ s->grid_data[i] = xmalloc(s->sx);
+ s->grid_attr[i] = xmalloc(s->sx);
+ s->grid_colr[i] = xmalloc(s->sx);
+ }
+}
+
+/* Free a range of lines. */
+void
+screen_free_lines(struct screen *s, u_int uy, u_int ly)
+{
+ u_int i;
+
+ log_debug("freeing lines %u:%u", uy, ly);
+
+ if (uy > screen_last_y(s) || ly > screen_last_y(s) || ly < uy)
+ log_fatalx("screen_free_lines: bad range");
+
+ for (i = uy; i <= ly; i++) {
+ xfree(s->grid_data[i]);
+ s->grid_data[i] = (u_char *) 0xabcdabcd;
+ xfree(s->grid_attr[i]);
+ s->grid_attr[i] = (u_char *) 0xabcdabcd;
+ xfree(s->grid_colr[i]);
+ s->grid_colr[i] = (u_char *) 0xabcdabcd;
+ }
+}
+
+/* Move a range of lines. */
+void
+screen_move_lines(struct screen *s, u_int dy, u_int uy, u_int ly)
+{
+ u_int ny;
+
+ log_debug("moving lines %u:%u to %u", uy, ly, dy);
+
+ if (uy > screen_last_y(s) || ly > screen_last_y(s) || ly < uy)
+ log_fatalx("screen_move_lines: bad range");
+ if (dy > screen_last_y(s))
+ log_fatalx("screen_move_lines: bad destination");
+
+ ny = (ly - uy) + 1;
+ memmove(
+ s->grid_data + dy, s->grid_data + uy, ny * (sizeof *s->grid_data));
+ memmove(
+ s->grid_attr + dy, s->grid_attr + uy, ny * (sizeof *s->grid_attr));
+ memmove(
+ s->grid_colr + dy, s->grid_colr + uy, ny * (sizeof *s->grid_colr));
+}
+
+/* Fill a range of lines. */
+void
+screen_fill_lines(
+ struct screen *s, u_int uy, u_int ly, u_char data, u_char attr, u_char colr)
+{
+ u_int i;
+
+ log_debug("filling lines %u:%u", uy, ly);
+
+ if (uy > screen_last_y(s) || ly > screen_last_y(s) || ly < uy)
+ log_fatalx("screen_fill_lines: bad range");
+
+ for (i = uy; i <= ly; i++)
+ screen_fill_line(s, i, data, attr, colr);
+}
+
+/* Update screen with character. */
+void
+screen_character(struct screen *s, u_char ch)
+{
+ switch (ch) {
+ case '\n': /* LF */
+ screen_cursor_down_scroll(s, 1);
+ break;
+ case '\r': /* CR */
+ s->cx = 0;
+ break;
+ case '\010': /* BS */
+ if (s->cx > 0)
+ s->cx--;
+ break;
+ default:
+ if (ch < ' ')
+ log_fatalx("screen_character: bad control: %hhu", ch);
+ screen_write_character(s, ch);
+ break;
+ }
+}
+
+/* Extract 16-bit value from pointer. */
+uint16_t
+screen_extract(u_char *ptr)
+{
+ uint16_t n;
+
+ memcpy(&n, ptr, sizeof n);
+ return (n);
+}
+
+/* Update screen with escape sequence. */
+void
+screen_sequence(struct screen *s, u_char *ptr)
+{
+ uint16_t ua, ub;
+
+ ptr++;
+ log_debug("processing code %hhu", *ptr);
+ switch (*ptr++) {
+ case CODE_CURSORUP:
+ ua = screen_extract(ptr);
+ if (ua > s->cy)
+ ua = s->cy;
+ s->cy -= ua;
+ break;
+ case CODE_CURSORDOWN:
+ ua = screen_extract(ptr);
+ if (s->cy + ua >= s->sy)
+ ua = s->sy - 1 - s->cy;
+ s->cy += ua;
+ break;
+ case CODE_CURSORLEFT:
+ ua = screen_extract(ptr);
+ if (ua > s->cx)
+ ua = s->cx;
+ s->cx -= ua;
+ break;
+ case CODE_CURSORRIGHT:
+ ua = screen_extract(ptr);
+ if (s->cx + ua >= s->sx)
+ ua = s->sx - 1 - s->cx;
+ s->cx += ua;
+ break;
+ case CODE_CURSORMOVE:
+ ua = screen_extract(ptr);
+ ptr += 2;
+ ub = screen_extract(ptr);
+ if (ub > s->sx)
+ ub = s->sx;
+ s->cx = ub - 1;
+ if (ua > s->sy)
+ ua = s->sy;
+ s->cy = ua - 1;
+ break;
+ case CODE_CLEARENDOFSCREEN:
+ screen_fill_end_of_screen(
+ s, s->cx, s->cy, SCREEN_DEFDATA, s->attr, s->colr);
+ break;
+ case CODE_CLEARSCREEN:
+ screen_fill_screen(s, SCREEN_DEFDATA, s->attr, s->colr);
+ break;
+ case CODE_CLEARENDOFLINE:
+ screen_fill_end_of_line(
+ s, s->cx, s->cy, SCREEN_DEFDATA, s->attr, s->colr);
+ break;
+ case CODE_CLEARSTARTOFLINE:
+ screen_fill_start_of_line(
+ s, s->cx, s->cy, SCREEN_DEFDATA, s->attr, s->colr);
+ break;
+ case CODE_CLEARLINE:
+ screen_fill_line(s, s->cy, SCREEN_DEFDATA, s->attr, s->colr);
+ break;
+ case CODE_INSERTLINE:
+ ua = screen_extract(ptr);
+ screen_insert_lines(s, s->cy, ua);
+ break;
+ case CODE_DELETELINE:
+ ua = screen_extract(ptr);
+ screen_delete_lines(s, s->cy, ua);
+ break;
+ case CODE_INSERTCHARACTER:
+ ua = screen_extract(ptr);
+ screen_insert_characters(s, s->cx, s->cy, ua);
+ break;
+ case CODE_DELETECHARACTER:
+ ua = screen_extract(ptr);
+ screen_delete_characters(s, s->cx, s->cy, ua);
+ break;
+ case CODE_CURSORON:
+ s->mode |= MODE_CURSOR;
+ break;
+ case CODE_CURSOROFF:
+ s->mode &= ~MODE_CURSOR;
+ break;
+ case CODE_CURSORDOWNSCROLL:
+ ua = screen_extract(ptr);
+ screen_cursor_down_scroll(s, ua);
+ break;
+ case CODE_CURSORUPSCROLL:
+ ua = screen_extract(ptr);
+ screen_cursor_up_scroll(s, ua);
+ break;
+ case CODE_SCROLLREGION:
+ ua = screen_extract(ptr);
+ ptr += 2;
+ ub = screen_extract(ptr);
+ if (ua > s->sy)
+ ua = s->sy;
+ s->ry_upper = ua - 1;
+ if (ub > s->sy)
+ ub = s->sy;
+ s->ry_lower = ub - 1;
+ break;
+ case CODE_INSERTOFF:
+ s->mode &= ~MODE_INSERT;
+ break;
+ case CODE_INSERTON:
+ s->mode |= MODE_INSERT;
+ break;
+ case CODE_KCURSOROFF:
+ s->mode &= ~MODE_KCURSOR;
+ break;
+ case CODE_KCURSORON:
+ s->mode |= MODE_KCURSOR;
+ break;
+ case CODE_KKEYPADOFF:
+ s->mode &= ~MODE_KKEYPAD;
+ break;
+ case CODE_KKEYPADON:
+ s->mode |= MODE_KKEYPAD;
+ break;
+ case CODE_TITLE:
+ ua = screen_extract(ptr);
+ ptr += 2;
+ log_debug("new title: %u:%.*s", ua, (int) ua, ptr);
+ if (ua > MAXTITLELEN - 1)
+ ua = MAXTITLELEN - 1;
+ memcpy(s->title, ptr, ua);
+ s->title[ua] = '\0';
+ break;
+ case CODE_ATTRIBUTES:
+ ua = screen_extract(ptr);
+ if (ua == 0) {
+ s->attr = 0;
+ s->colr = SCREEN_DEFCOLR;
+ break;
+ }
+
+ while (ua-- > 0) {
+ ptr += 2;
+ ub = screen_extract(ptr);
+ switch (ub) {
+ case 0:
+ case 10:
+ s->attr = 0;
+ s->colr = SCREEN_DEFCOLR;
+ break;
+ case 1:
+ s->attr |= ATTR_BRIGHT;
+ break;
+ case 2:
+ s->attr |= ATTR_DIM;
+ break;
+ case 3:
+ s->attr |= ATTR_ITALICS;
+ break;
+ case 4:
+ s->attr |= ATTR_UNDERSCORE;
+ break;
+ case 5:
+ s->attr |= ATTR_BLINK;
+ break;
+ case 7:
+ s->attr |= ATTR_REVERSE;
+ break;
+ case 8:
+ s->attr |= ATTR_HIDDEN;
+ break;
+ case 23:
+ s->attr &= ~ATTR_ITALICS;
+ break;
+ case 24:
+ s->attr &= ~ATTR_UNDERSCORE;
+ break;
+ case 30:
+ case 31:
+ case 32:
+ case 33:
+ case 34:
+ case 35:
+ case 36:
+ case 37:
+ s->colr &= 0x0f;
+ s->colr |= (ub - 30) << 4;
+ break;
+ case 39:
+ s->colr &= 0x0f;
+ s->colr |= 0x80;
+ break;
+ case 40:
+ case 41:
+ case 42:
+ case 43:
+ case 44:
+ case 45:
+ case 46:
+ case 47:
+ s->colr &= 0xf0;
+ s->colr |= ub - 40;
+ break;
+ case 49:
+ s->colr &= 0xf0;
+ s->colr |= 0x08;
+ break;
+ }
+ }
+ }
+}
+
+/* Write a single character to the screen at the cursor and move forward. */
+void
+screen_write_character(struct screen *s, u_char ch)
+{
+ s->grid_data[s->cy][s->cx] = ch;
+ s->grid_attr[s->cy][s->cx] = s->attr;
+ s->grid_colr[s->cy][s->cx] = s->colr;
+
+ s->cx++;
+ if (s->cx >= s->sx) {
+ s->cx = 0;
+ screen_cursor_down_scroll(s, 1);
+ }
+}
+
+/* Move cursor up and scroll if necessary. */
+void
+screen_cursor_up_scroll(struct screen *s, u_int ny)
+{
+ if (s->cy < ny) {
+ screen_scroll_down(s, ny - s->cy);
+ s->cy = 0;
+ } else
+ s->cy -= ny;
+}
+
+/* Move cursor down and scroll if necessary. */
+void
+screen_cursor_down_scroll(struct screen *s, u_int ny)
+{
+ if (s->sy - 1 - s->cy < ny) {
+ screen_scroll_up(s, ny - (s->sy - 1 - s->cy));
+ s->cy = s->sy - 1;
+ } else
+ s->cy += ny;
+}
+
+/* Scroll screen up. */
+void
+screen_scroll_up(struct screen *s, u_int ny)
+{
+ if (s->ry_upper == 0 && s->ry_lower == s->sy - 1) {
+ screen_delete_lines(s, 0, ny);
+ return;
+ }
+
+ log_fatalx("screen_scroll_up: %u %u", s->ry_upper, s->ry_lower);
+}
+
+/* Scroll screen down. */
+void
+screen_scroll_down(struct screen *s, u_int ny)
+{
+ if (s->ry_upper == 0 && s->ry_lower == s->sy - 1) {
+ screen_insert_lines(s, 0, ny);
+ return;
+ }
+
+ log_fatalx("screen_scroll_down: %u %u", s->ry_upper, s->ry_lower);
+}
+
+/* Fill entire screen. */
+void
+screen_fill_screen(struct screen *s, u_char data, u_char attr, u_char colr)
+{
+ screen_fill_end_of_screen(s, 0, 0, data, attr, colr);
+}
+
+/* Fill single line. */
+void
+screen_fill_line(
+ struct screen *s, u_int py, u_char data, u_char attr, u_char colr)
+{
+ screen_fill_end_of_line(s, 0, py, data, attr, colr);
+}
+
+/* Fill to end of screen. */
+void
+screen_fill_end_of_screen(
+ struct screen *s, u_int px, u_int py, u_char data, u_char attr, u_char colr)
+{
+ if (py >= s->sy)
+ return;
+
+ if (px != 0) {
+ screen_fill_end_of_line(s, px, py, data, attr, colr);
+ if (py++ >= s->sy)
+ return;
+ }
+
+ while (py++ < s->sy)
+ screen_fill_line(s, py - 1, data, attr, colr);
+}
+
+/* Fill to end of line. */
+void
+screen_fill_end_of_line(
+ struct screen *s, u_int px, u_int py, u_char data, u_char attr, u_char colr)
+{
+ if (px >= s->sx)
+ return;
+ if (py >= s->sy)
+ return;
+
+ memset(s->grid_data[py] + px, data, s->sx - px);
+ memset(s->grid_attr[py] + px, attr, s->sx - px);
+ memset(s->grid_colr[py] + px, colr, s->sx - px);
+}
+
+/* Fill to start of line. */
+void
+screen_fill_start_of_line(
+ struct screen *s, u_int px, u_int py, u_char data, u_char attr, u_char colr)
+{
+ if (px >= s->sx)
+ return;
+ if (py >= s->sy)
+ return;
+
+ memset(s->grid_data[py], data, px);
+ memset(s->grid_attr[py], attr, px);
+ memset(s->grid_colr[py], colr, px);
+}
+
+/* Insert lines. */
+void
+screen_insert_lines(struct screen *s, u_int py, u_int ny)
+{
+ if (py > screen_last_y(s))
+ return;
+
+ if (py + ny > screen_last_y(s))
+ ny = screen_last_y(s) - py;
+ log_debug("inserting lines: %u,%u", py, ny);
+
+ screen_free_lines(s, (screen_last_y(s) - ny) + 1, screen_last_y(s));
+
+ if (py != screen_last_y(s))
+ screen_move_lines(s, py + ny, py, screen_last_y(s) - ny);
+
+ screen_fill_lines(
+ s, py, py + ny - 1, SCREEN_DEFDATA, SCREEN_DEFATTR, SCREEN_DEFCOLR);
+}
+
+/* Delete lines. */
+void
+screen_delete_lines(struct screen *s, u_int py, u_int ny)
+{
+ if (py > screen_last_y(s))
+ return;
+
+ if (py + ny > screen_last_y(s))
+ ny = screen_last_y(s) - py;
+ log_debug("deleting lines: %u,%u", py, ny);
+
+ screen_free_lines(s, py, py + ny - 1);
+
+ if (py != screen_last_y(s))
+ screen_move_lines(s, py, py + ny, screen_last_y(s));
+
+ screen_make_lines(s, (screen_last_y(s) - ny) + 1, screen_last_y(s));
+ screen_fill_lines(s, (screen_last_y(s) - ny) + 1,
+ screen_last_y(s), SCREEN_DEFDATA, SCREEN_DEFATTR, SCREEN_DEFCOLR);
+}
+
+/* Insert characters. */
+void
+screen_insert_characters(struct screen *s, u_int px, u_int py, u_int nx)
+{
+ if (px >= s->sx || py >= s->sy)
+ return;
+
+ if (px + nx > s->sx)
+ nx = s->sx - px;
+
+ if (px - nx != s->sx) {
+ memmove(s->grid_data[py] + px + nx,
+ s->grid_data[py] + px, s->sx - px - nx);
+ memmove(s->grid_attr[py] + px + nx,
+ s->grid_attr[py] + px, s->sx - px - nx);
+ memmove(s->grid_colr[py] + px + nx,
+ s->grid_colr[py] + px, s->sx - px - nx);
+ }
+ memset(s->grid_data[py] + px, SCREEN_DEFDATA, nx);
+ memset(s->grid_attr[py] + px, SCREEN_DEFATTR, nx);
+ memset(s->grid_colr[py] + px, SCREEN_DEFCOLR, nx);
+}
+
+/* Delete characters. */
+void
+screen_delete_characters(struct screen *s, u_int px, u_int py, u_int nx)
+{
+ if (px >= s->sx || py >= s->sy)
+ return;
+
+ if (px + nx > s->sx)
+ nx = s->sx - px;
+
+ if (px - nx != s->sx) {
+ memmove(s->grid_data[py] + px,
+ s->grid_data[py] + px + nx, s->sx - px - nx);
+ memmove(s->grid_attr[py] + px,
+ s->grid_attr[py] + px + nx, s->sx - px - nx);
+ memmove(s->grid_colr[py] + px,
+ s->grid_colr[py] + px + nx, s->sx - px - nx);
+ }
+ memset(s->grid_data[py] + px + nx, SCREEN_DEFDATA, s->sx - px - nx);
+ memset(s->grid_attr[py] + px + nx, SCREEN_DEFATTR, s->sx - px - nx);
+ memset(s->grid_colr[py] + px + nx, SCREEN_DEFCOLR, s->sx - px - nx);
+}
diff --git a/server.c b/server.c
new file mode 100644
index 00000000..5d6204dd
--- /dev/null
+++ b/server.c
@@ -0,0 +1,999 @@
+/* $Id: server.c,v 1.1.1.1 2007-07-09 19:03:42 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <termios.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "tmux.h"
+
+/*
+ * Main server functions.
+ */
+
+/* Client list. */
+struct clients clients;
+
+int server_main(int);
+void fill_windows(struct pollfd **);
+void handle_windows(struct pollfd **);
+void fill_clients(struct pollfd **);
+void handle_clients(struct pollfd **);
+struct client *accept_client(int);
+void lost_client(struct client *);
+void user_start(struct client *, const char *, const char *,
+ size_t, void (*)(struct client *, const char *));
+void user_input(struct client *, size_t);
+void write_message(struct client *, const char *, ...);
+void write_client(struct client *, u_int, void *, size_t);
+void write_client2(
+ struct client *, u_int, void *, size_t, void *, size_t);
+void write_clients(struct window *, u_int, void *, size_t);
+void new_window(struct window *);
+void lost_window(struct window *);
+void changed_window(struct client *);
+void draw_client(struct client *, u_int, u_int);
+void process_client(struct client *);
+void process_identify_msg(struct client *, struct hdr *);
+void process_create_msg(struct client *, struct hdr *);
+void process_next_msg(struct client *, struct hdr *);
+void process_previous_msg(struct client *, struct hdr *);
+void process_select_msg(struct client *, struct hdr *);
+void process_size_msg(struct client *, struct hdr *);
+void process_input_msg(struct client *, struct hdr *);
+void process_refresh_msg(struct client *, struct hdr *);
+void process_sessions_msg(struct client *, struct hdr *);
+void process_windows_msg(struct client *, struct hdr *);
+void process_rename_msg(struct client *, struct hdr *);
+void rename_callback(struct client *, const char *);
+
+/* Fork and start server process. */
+int
+server_start(void)
+{
+ mode_t mode;
+ int fd;
+ struct sockaddr_un sa;
+ size_t sz;
+ pid_t pid;
+ FILE *f;
+ char *path;
+
+ /* Fork the server process. */
+ switch (pid = fork()) {
+ case -1:
+ return (-1);
+ case 0:
+ break;
+ default:
+ return (0);
+ }
+
+ /* Start logging to file. */
+ if (debug_level > 0) {
+ xasprintf(&path,
+ "%s-server-%ld.log", __progname, (long) getpid());
+ f = fopen(path, "w");
+ log_open(f, LOG_DAEMON, debug_level);
+ xfree(path);
+ }
+ log_debug("server started, pid %ld", (long) getpid());
+
+ /* Create the socket. */
+ memset(&sa, 0, sizeof sa);
+ sa.sun_family = AF_UNIX;
+ sz = strlcpy(sa.sun_path, socket_path, sizeof sa.sun_path);
+ if (sz >= sizeof sa.sun_path) {
+ errno = ENAMETOOLONG;
+ log_fatal("socket");
+ }
+ unlink(sa.sun_path);
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ log_fatal("socket");
+
+ mode = umask(S_IXUSR|S_IRWXG|S_IRWXO);
+ if (bind(fd, (struct sockaddr *) &sa, SUN_LEN(&sa)) == -1)
+ log_fatal("bind");
+ umask(mode);
+
+ if (listen(fd, 16) == -1)
+ log_fatal("listen");
+
+ /*
+ * Detach into the background. This means the PID changes which will
+ * have to be fixed in some way at some point... XXX
+ */
+ if (daemon(1, 1) != 0)
+ log_fatal("daemon");
+ log_debug("server daemonised, pid now %ld", (long) getpid());
+
+ setproctitle("server (%s)", socket_path);
+ exit(server_main(fd));
+}
+
+/* Main server loop. */
+int
+server_main(int srv_fd)
+{
+ struct pollfd *pfds, *pfd;
+ int nfds, mode;
+ struct sigaction act;
+
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_RESTART;
+
+ act.sa_handler = SIG_IGN;
+ if (sigaction(SIGPIPE, &act, NULL) != 0)
+ log_fatal("sigaction");
+ if (sigaction(SIGUSR1, &act, NULL) != 0)
+ log_fatal("sigaction");
+ if (sigaction(SIGUSR2, &act, NULL) != 0)
+ log_fatal("sigaction");
+ if (sigaction(SIGINT, &act, NULL) != 0)
+ log_fatal("sigaction");
+ if (sigaction(SIGQUIT, &act, NULL) != 0)
+ log_fatal("sigaction");
+
+ ARRAY_INIT(&windows);
+ ARRAY_INIT(&clients);
+ ARRAY_INIT(&sessions);
+
+ if ((mode = fcntl(srv_fd, F_GETFL)) == -1)
+ log_fatal("fcntl");
+ if (fcntl(srv_fd, F_SETFL, mode|O_NONBLOCK) == -1)
+ log_fatal("fcntl");
+
+ pfds = NULL;
+ while (!sigterm) {
+ /* Initialise pollfd array. */
+ nfds = 1 + ARRAY_LENGTH(&windows) + ARRAY_LENGTH(&clients);
+ pfds = xrealloc(pfds, nfds, sizeof *pfds);
+ pfd = pfds;
+
+ /* Fill server socket. */
+ pfd->fd = srv_fd;
+ pfd->events = POLLIN;
+ pfd++;
+
+ /* Fill window and client sockets. */
+ fill_windows(&pfd);
+ fill_clients(&pfd);
+
+ /* Do the poll. */
+ if (poll(pfds, nfds, INFTIM) == -1) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ log_fatal("poll");
+ }
+ pfd = pfds;
+
+ /* Handle server socket. */
+ if (pfd->revents & (POLLERR|POLLNVAL|POLLHUP))
+ log_fatalx("lost server socket");
+ if (pfd->revents & POLLIN) {
+ accept_client(srv_fd);
+ continue;
+ }
+ pfd++;
+
+ /*
+ * Handle window and client sockets. Clients can create
+ * windows, so windows must come first to avoid messing up by
+ * increasing the array size.
+ */
+ handle_windows(&pfd);
+ handle_clients(&pfd);
+ }
+
+ close(srv_fd);
+ unlink(socket_path);
+
+ return (0);
+}
+
+/* Fill window pollfds. */
+void
+fill_windows(struct pollfd **pfd)
+{
+ struct window *w;
+ u_int i;
+
+ for (i = 0; i < ARRAY_LENGTH(&windows); i++) {
+ if ((w = ARRAY_ITEM(&windows, i)) == NULL)
+ (*pfd)->fd = -1;
+ else {
+ (*pfd)->fd = w->fd;
+ (*pfd)->events = POLLIN;
+ if (BUFFER_USED(w->out) > 0)
+ (*pfd)->events |= POLLOUT;
+ }
+ (*pfd)++;
+ }
+}
+
+/* Handle window pollfds. */
+void
+handle_windows(struct pollfd **pfd)
+{
+ struct window *w;
+ u_int i;
+ struct buffer *b;
+
+ for (i = 0; i < ARRAY_LENGTH(&windows); i++) {
+ if ((w = ARRAY_ITEM(&windows, i)) != NULL) {
+ if (window_poll(w, *pfd) != 0)
+ lost_window(w);
+ else {
+ b = buffer_create(BUFSIZ);
+ window_output(w, b);
+ if (BUFFER_USED(b) != 0) {
+ write_clients(w, MSG_OUTPUT,
+ BUFFER_OUT(b), BUFFER_USED(b));
+ }
+ buffer_destroy(b);
+ }
+ }
+ (*pfd)++;
+ }
+}
+
+/* Fill client pollfds. */
+void
+fill_clients(struct pollfd **pfd)
+{
+ struct client *c;
+ u_int i;
+
+ for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
+ if ((c = ARRAY_ITEM(&clients, i)) == NULL)
+ (*pfd)->fd = -1;
+ else {
+ (*pfd)->fd = c->fd;
+ (*pfd)->events = POLLIN;
+ if (BUFFER_USED(c->out) > 0)
+ (*pfd)->events |= POLLOUT;
+ }
+ (*pfd)++;
+ }
+}
+
+/* Handle client pollfds. */
+void
+handle_clients(struct pollfd *(*pfd))
+{
+ struct client *c;
+ u_int i;
+
+ for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
+ if ((c = ARRAY_ITEM(&clients, i)) != NULL) {
+ if (buffer_poll((*pfd), c->in, c->out) != 0)
+ lost_client(c);
+ else
+ process_client(c);
+ }
+ (*pfd)++;
+ }
+}
+
+/* accept(2) and create new client. */
+struct client *
+accept_client(int srv_fd)
+{
+ struct client *c;
+ struct sockaddr_storage sa;
+ socklen_t slen = sizeof sa;
+ int client_fd, mode;
+ u_int i;
+
+ client_fd = accept(srv_fd, (struct sockaddr *) &sa, &slen);
+ if (client_fd == -1) {
+ if (errno == EAGAIN || errno == EINTR || errno == ECONNABORTED)
+ return (NULL);
+ log_fatal("accept");
+ }
+ if ((mode = fcntl(client_fd, F_GETFL)) == -1)
+ log_fatal("fcntl");
+ if (fcntl(client_fd, F_SETFL, mode|O_NONBLOCK) == -1)
+ log_fatal("fcntl");
+
+ c = xmalloc(sizeof *c);
+ c->fd = client_fd;
+ c->in = buffer_create(BUFSIZ);
+ c->out = buffer_create(BUFSIZ);
+ c->session = NULL;
+ c->prompt = NULL;
+
+ for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
+ if (ARRAY_ITEM(&clients, i) == NULL) {
+ ARRAY_SET(&clients, i, c);
+ return (c);
+ }
+ }
+ ARRAY_ADD(&clients, c);
+ return (c);
+}
+
+/* Lost a client. */
+void
+lost_client(struct client *c)
+{
+ u_int i;
+
+ for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
+ if (ARRAY_ITEM(&clients, i) == c)
+ ARRAY_SET(&clients, i, NULL);
+ }
+
+ close(c->fd);
+ buffer_destroy(c->in);
+ buffer_destroy(c->out);
+ xfree(c);
+}
+
+/* Write message command to a client. */
+void
+write_message(struct client *c, const char *fmt, ...)
+{
+ struct hdr hdr;
+ va_list ap;
+ char *msg;
+ size_t size;
+ u_int i;
+
+ buffer_ensure(c->out, sizeof hdr);
+ buffer_add(c->out, sizeof hdr);
+ size = BUFFER_USED(c->out);
+
+ input_store_zero(c->out, CODE_CURSOROFF);
+ input_store_two(c->out, CODE_CURSORMOVE, c->sy, 1);
+ input_store_one(c->out, CODE_ATTRIBUTES, 2);
+ input_store16(c->out, 0);
+ input_store16(c->out, 7);
+ va_start(ap, fmt);
+ xvasprintf(&msg, fmt, ap);
+ buffer_write(c->out, msg, strlen(msg));
+ xfree(msg);
+ va_end(ap);
+ for (i = strlen(msg); i < c->sx; i++)
+ input_store8(c->out, ' ');
+
+ size = BUFFER_USED(c->out) - size;
+ hdr.code = MSG_OUTPUT;
+ hdr.size = size;
+ memcpy(BUFFER_IN(c->out) - size - sizeof hdr, &hdr, sizeof hdr);
+
+ hdr.code = MSG_PAUSE;
+ hdr.size = 0;
+ buffer_write(c->out, &hdr, sizeof hdr);
+
+ buffer_ensure(c->out, sizeof hdr);
+ buffer_add(c->out, sizeof hdr);
+ size = BUFFER_USED(c->out);
+
+ screen_draw(&c->session->window->screen, c->out, c->sy - 1, c->sy - 1);
+
+ size = BUFFER_USED(c->out) - size;
+ hdr.code = MSG_OUTPUT;
+ hdr.size = size;
+ memcpy(BUFFER_IN(c->out) - size - sizeof hdr, &hdr, sizeof hdr);
+}
+
+/* Start user input. */
+void
+user_start(struct client *c, const char *prompt, const char *now,
+ size_t len, void (*callback)(struct client *, const char *))
+{
+ struct hdr hdr;
+ size_t size;
+ u_int i;
+
+ c->callback = callback;
+ c->prompt = prompt;
+
+ c->len = len;
+ if (c->len > c->sx - strlen(c->prompt))
+ c->len = c->sx - strlen(c->prompt);
+ c->buf = xmalloc(c->len + 1);
+ strlcpy(c->buf, now, c->len + 1);
+ c->idx = strlen(c->buf);
+
+ buffer_ensure(c->out, sizeof hdr);
+ buffer_add(c->out, sizeof hdr);
+ size = BUFFER_USED(c->out);
+
+ input_store_zero(c->out, CODE_CURSOROFF);
+ input_store_two(c->out, CODE_CURSORMOVE, c->sy, 1);
+ input_store_one(c->out, CODE_ATTRIBUTES, 2);
+ input_store16(c->out, 0);
+ input_store16(c->out, 7);
+
+ i = 0;
+ buffer_write(c->out, c->prompt, strlen(c->prompt));
+ i += strlen(c->prompt);
+ if (*c->buf != '\0') {
+ buffer_write(c->out, c->buf, strlen(c->buf));
+ i += strlen(c->buf);
+ }
+ for (; i < c->sx; i++)
+ input_store8(c->out, ' ');
+
+ input_store_two(c->out,
+ CODE_CURSORMOVE, c->sy, 1 + strlen(c->prompt) + c->idx);
+ input_store_zero(c->out, CODE_CURSORON);
+
+ size = BUFFER_USED(c->out) - size;
+ hdr.code = MSG_OUTPUT;
+ hdr.size = size;
+ memcpy(BUFFER_IN(c->out) - size - sizeof hdr, &hdr, sizeof hdr);
+}
+
+/* Handle user input. */
+void
+user_input(struct client *c, size_t in)
+{
+ struct hdr hdr;
+ size_t size;
+ int key;
+ u_int i;
+
+ buffer_ensure(c->out, sizeof hdr);
+ buffer_add(c->out, sizeof hdr);
+ size = BUFFER_USED(c->out);
+
+ while (in != 0) {
+ if (in < 1)
+ break;
+ in--;
+ key = input_extract8(c->in);
+ if (key == '\e') {
+ if (in < 2)
+ log_fatalx("user_input: underflow");
+ in -= 2;
+ key = (int16_t) input_extract16(c->in);
+ }
+
+ again:
+ if (key == '\r') {
+ screen_draw(&c->session->window->screen,
+ c->out, c->sy - 1, c->sy - 1);
+
+ c->callback(c, c->buf);
+ c->prompt = NULL;
+ xfree(c->buf);
+ break;
+ }
+
+ switch (key) {
+ case KEYC_LEFT:
+ if (c->idx > 0)
+ c->idx--;
+ input_store_two(c->out, CODE_CURSORMOVE,
+ c->sy, 1 + strlen(c->prompt) + c->idx);
+ break;
+ case KEYC_RIGHT:
+ if (c->idx < strlen(c->buf))
+ c->idx++;
+ input_store_two(c->out, CODE_CURSORMOVE,
+ c->sy, 1 + strlen(c->prompt) + c->idx);
+ break;
+ case KEYC_HOME:
+ c->idx = 0;
+ input_store_two(c->out, CODE_CURSORMOVE,
+ c->sy, 1 + strlen(c->prompt) + c->idx);
+ break;
+ case KEYC_LL:
+ c->idx = strlen(c->buf);
+ input_store_two(c->out, CODE_CURSORMOVE,
+ c->sy, 1 + strlen(c->prompt) + c->idx);
+ break;
+ case KEYC_BACKSPACE:
+ if (c->idx == 0)
+ break;
+ if (strlen(c->buf) == 0)
+ break;
+ if (c->idx == strlen(c->buf))
+ c->buf[c->idx - 1] = '\0';
+ else {
+ memmove(c->buf + c->idx - 1,
+ c->buf + c->idx, c->len - c->idx);
+ }
+ c->idx--;
+ input_store_one(c->out, CODE_CURSORLEFT, 1);
+ input_store_one(c->out, CODE_DELETECHARACTER, 1);
+ input_store_zero(c->out, CODE_CURSOROFF);
+ input_store_two(c->out, CODE_CURSORMOVE, c->sy, c->sx);
+ input_store8(c->out, ' ');
+ input_store_two(c->out, CODE_CURSORMOVE,
+ c->sy, 1 + strlen(c->prompt) + c->idx);
+ input_store_zero(c->out, CODE_CURSORON);
+ break;
+ case KEYC_DC:
+ if (strlen(c->buf) == 0)
+ break;
+ if (c->idx == strlen(c->buf))
+ break;
+ memmove(c->buf + c->idx,
+ c->buf + c->idx + 1, c->len - c->idx - 1);
+ input_store_one(c->out, CODE_DELETECHARACTER, 1);
+ input_store_zero(c->out, CODE_CURSOROFF);
+ input_store_two(c->out,CODE_CURSORMOVE, c->sy, c->sx);
+ input_store8(c->out, ' ');
+ input_store_two(c->out, CODE_CURSORMOVE,
+ c->sy, 1 + strlen(c->prompt) + c->idx);
+ input_store_zero(c->out, CODE_CURSORON);
+ break;
+ default:
+ if (key >= ' ' && key != '\177') {
+ if (c->idx == c->len)
+ break;
+ if (strlen(c->buf) == c->len)
+ break;
+ memmove(c->buf + c->idx + 1,
+ c->buf + c->idx, c->len - c->idx);
+ c->buf[c->idx++] = key;
+ input_store_one(
+ c->out, CODE_INSERTCHARACTER, 1);
+ input_store8(c->out, key);
+ break;
+ }
+ switch (key) {
+ case '\001':
+ key = KEYC_HOME;
+ goto again;
+ case '\005':
+ key = KEYC_LL;
+ goto again;
+ case '\013':
+ c->buf[c->idx + 1] = '\0';
+ input_store_zero(c->out, CODE_CURSOROFF);
+ i = 1 + strlen(c->prompt) + c->idx;
+ for (; i < c->sx; i++)
+ input_store8(c->out, ' ');
+ input_store_two(c->out, CODE_CURSORMOVE,
+ c->sy, 1 + strlen(c->prompt) + c->idx);
+ input_store_zero(c->out, CODE_CURSORON);
+ break;
+ }
+ }
+ }
+
+ size = BUFFER_USED(c->out) - size;
+ if (size != 0) {
+ hdr.code = MSG_OUTPUT;
+ hdr.size = size;
+ memcpy(BUFFER_IN(c->out) - size - sizeof hdr, &hdr, sizeof hdr);
+ } else
+ buffer_reverse_add(c->out, sizeof hdr);
+}
+
+/* Write command to a client. */
+void
+write_client(struct client *c, u_int cmd, void *buf, size_t len)
+{
+ struct hdr hdr;
+
+ hdr.code = cmd;
+ hdr.size = len;
+
+ buffer_write(c->out, &hdr, sizeof hdr);
+ if (buf != NULL)
+ buffer_write(c->out, buf, len);
+}
+
+/* Write command to a client with two buffers. */
+void
+write_client2(struct client *c,
+ u_int cmd, void *buf1, size_t len1, void *buf2, size_t len2)
+{
+ struct hdr hdr;
+
+ hdr.code = cmd;
+ hdr.size = len1 + len2;
+
+ buffer_write(c->out, &hdr, sizeof hdr);
+ if (buf1 != NULL)
+ buffer_write(c->out, buf1, len1);
+ if (buf2 != NULL)
+ buffer_write(c->out, buf2, len2);
+}
+
+/* Write command to all clients attached to a specific window. */
+void
+write_clients(struct window *w, u_int cmd, void *buf, size_t len)
+{
+ struct client *c;
+ struct hdr hdr;
+ u_int i;
+
+ hdr.code = cmd;
+ hdr.size = len;
+
+ for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
+ c = ARRAY_ITEM(&clients, i);
+ if (c != NULL && c->session != NULL) {
+ if (c->session->window == w) {
+ buffer_write(c->out, &hdr, sizeof hdr);
+ if (buf != NULL)
+ buffer_write(c->out, buf, len);
+ }
+ }
+ }
+}
+
+/* Lost window: move clients on to next window. */
+void
+lost_window(struct window *w)
+{
+ struct client *c;
+ u_int i;
+
+ for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
+ c = ARRAY_ITEM(&clients, i);
+ if (c != NULL && c->session != NULL) {
+ if (session_has(c->session, w)) {
+ if (session_detach(c->session, w) != 0)
+ write_client(c, MSG_EXIT, NULL, 0);
+ changed_window(c);
+ }
+ }
+ }
+}
+
+/* Changed client window. */
+void
+changed_window(struct client *c)
+{
+ struct window *w;
+
+ w = c->session->window;
+ if (c->sx != w->screen.sx || c->sy != w->screen.sy)
+ window_resize(w, c->sx, c->sy);
+ draw_client(c, 0, c->sy - 1);
+}
+
+/* Draw window on client. */
+void
+draw_client(struct client *c, u_int py_upper, u_int py_lower)
+{
+ struct hdr hdr;
+ size_t size;
+
+ buffer_ensure(c->out, sizeof hdr);
+ buffer_add(c->out, sizeof hdr);
+ size = BUFFER_USED(c->out);
+
+ screen_draw(&c->session->window->screen, c->out, py_upper, py_lower);
+
+ size = BUFFER_USED(c->out) - size;
+ log_debug("redrawing screen, %zu bytes", size);
+ if (size != 0) {
+ hdr.code = MSG_OUTPUT;
+ hdr.size = size;
+ memcpy(
+ BUFFER_IN(c->out) - size - sizeof hdr, &hdr, sizeof hdr);
+ } else
+ buffer_reverse_add(c->out, sizeof hdr);
+}
+
+/* Process a command from the client. */
+void
+process_client(struct client *c)
+{
+ struct hdr hdr;
+
+ if (BUFFER_USED(c->in) < sizeof hdr)
+ return;
+ memcpy(&hdr, BUFFER_OUT(c->in), sizeof hdr);
+ if (BUFFER_USED(c->in) < (sizeof hdr) + hdr.size)
+ return;
+ buffer_remove(c->in, sizeof hdr);
+
+ switch (hdr.code) {
+ case MSG_IDENTIFY:
+ process_identify_msg(c, &hdr);
+ break;
+ case MSG_CREATE:
+ process_create_msg(c, &hdr);
+ break;
+ case MSG_NEXT:
+ process_next_msg(c, &hdr);
+ break;
+ case MSG_PREVIOUS:
+ process_previous_msg(c, &hdr);
+ break;
+ case MSG_SIZE:
+ process_size_msg(c, &hdr);
+ break;
+ case MSG_INPUT:
+ process_input_msg(c, &hdr);
+ break;
+ case MSG_REFRESH:
+ process_refresh_msg(c, &hdr);
+ break;
+ case MSG_SELECT:
+ process_select_msg(c, &hdr);
+ break;
+ case MSG_SESSIONS:
+ process_sessions_msg(c, &hdr);
+ break;
+ case MSG_WINDOWS:
+ process_windows_msg(c, &hdr);
+ break;
+ case MSG_RENAME:
+ process_rename_msg(c, &hdr);
+ break;
+ }
+}
+
+/* Identify message from client. */
+void
+process_identify_msg(struct client *c, struct hdr *hdr)
+{
+ struct identify_data data;
+
+ if (hdr->size != sizeof data)
+ log_fatalx("bad MSG_IDENTIFY size");
+ buffer_read(c->in, &data, hdr->size);
+
+ c->sx = data.sx;
+ if (c->sx == 0)
+ c->sx = 80;
+ c->sy = data.sy;
+ if (c->sy == 0)
+ c->sy = 25;
+
+ /* Try and find session or create if not found. */
+ c->session = session_find(data.name);
+ if (c->session == NULL) {
+ c->session =
+ session_create(data.name, "/bin/ksh -l", c->sx, c->sy);
+ }
+ if (c->session == NULL)
+ log_fatalx("process_identify_msg: session_create failed");
+
+ draw_client(c, 0, c->sy - 1);
+}
+
+/* Create message from client. */
+void
+process_create_msg(struct client *c, struct hdr *hdr)
+{
+ if (c->session == NULL)
+ log_fatalx("MSG_CREATE before identified");
+ if (hdr->size != 0)
+ log_fatalx("bad MSG_CREATE size");
+
+ if (session_new(c->session, "/bin/ksh -l", c->sx, c->sy) != 0)
+ log_fatalx("process_create_msg: session_new failed");
+
+ draw_client(c, 0, c->sy - 1);
+}
+
+/* Next message from client. */
+void
+process_next_msg(struct client *c, struct hdr *hdr)
+{
+ if (c->session == NULL)
+ log_fatalx("MSG_NEXT before identified");
+ if (hdr->size != 0)
+ log_fatalx("bad MSG_NEXT size");
+
+ if (session_next(c->session) == 0)
+ changed_window(c);
+ else
+ write_message(c, "No next window");
+}
+
+/* Previous message from client. */
+void
+process_previous_msg(struct client *c, struct hdr *hdr)
+{
+ if (c->session == NULL)
+ log_fatalx("MSG_PREVIOUS before identified");
+ if (hdr->size != 0)
+ log_fatalx("bad MSG_PREVIOUS size");
+
+ if (session_previous(c->session) == 0)
+ changed_window(c);
+ else
+ write_message(c, "No previous window");
+}
+
+/* Size message from client. */
+void
+process_size_msg(struct client *c, struct hdr *hdr)
+{
+ struct size_data data;
+
+ if (c->session == NULL)
+ log_fatalx("MSG_SIZE before identified");
+ if (hdr->size != sizeof data)
+ log_fatalx("bad MSG_SIZE size");
+ buffer_read(c->in, &data, hdr->size);
+
+ c->sx = data.sx;
+ if (c->sx == 0)
+ c->sx = 80;
+ c->sy = data.sy;
+ if (c->sy == 0)
+ c->sy = 25;
+
+ if (window_resize(c->session->window, c->sx, c->sy) != 0)
+ draw_client(c, 0, c->sy - 1);
+}
+
+/* Input message from client. */
+void
+process_input_msg(struct client *c, struct hdr *hdr)
+{
+ if (c->session == NULL)
+ log_fatalx("MSG_INPUT before identified");
+
+ if (c->prompt == NULL)
+ window_input(c->session->window, c->in, hdr->size);
+ else
+ user_input(c, hdr->size);
+}
+
+/* Refresh message from client. */
+void
+process_refresh_msg(struct client *c, struct hdr *hdr)
+{
+ struct refresh_data data;
+
+ if (c->session == NULL)
+ log_fatalx("MSG_REFRESH before identified");
+ if (hdr->size != 0 && hdr->size != sizeof data)
+ log_fatalx("bad MSG_REFRESH size");
+
+ draw_client(c, 0, c->sy - 1);
+}
+
+/* Select message from client. */
+void
+process_select_msg(struct client *c, struct hdr *hdr)
+{
+ struct select_data data;
+
+ if (c->session == NULL)
+ log_fatalx("MSG_SELECT before identified");
+ if (hdr->size != sizeof data)
+ log_fatalx("bad MSG_SELECT size");
+ buffer_read(c->in, &data, hdr->size);
+
+ if (c->session == NULL)
+ return;
+ if (session_select(c->session, data.idx) == 0)
+ changed_window(c);
+ else
+ write_message(c, "Window %u not present", data.idx);
+}
+
+/* Sessions message from client. */
+void
+process_sessions_msg(struct client *c, struct hdr *hdr)
+{
+ struct sessions_data data;
+ struct sessions_entry entry;
+ struct session *s;
+ u_int i, j;
+
+ if (hdr->size != sizeof data)
+ log_fatalx("bad MSG_SESSIONS size");
+ buffer_read(c->in, &data, hdr->size);
+
+ data.sessions = 0;
+ for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
+ if (ARRAY_ITEM(&sessions, i) != NULL)
+ data.sessions++;
+ }
+ write_client2(c, MSG_SESSIONS,
+ &data, sizeof data, NULL, data.sessions * sizeof entry);
+
+ for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
+ s = ARRAY_ITEM(&sessions, i);
+ if (s == NULL)
+ continue;
+ strlcpy(entry.name, s->name, sizeof entry.name);
+ entry.tim = s->tim;
+ entry.windows = 0;
+ for (j = 0; j < ARRAY_LENGTH(&s->windows); j++) {
+ if (ARRAY_ITEM(&s->windows, j) != NULL)
+ entry.windows++;
+ }
+ buffer_write(c->out, &entry, sizeof entry);
+ }
+}
+
+/* Windows message from client. */
+void
+process_windows_msg(struct client *c, struct hdr *hdr)
+{
+ struct windows_data data;
+ struct windows_entry entry;
+ struct session *s;
+ struct window *w;
+ u_int i;
+
+ if (hdr->size != sizeof data)
+ log_fatalx("bad MSG_WINDOWS size");
+ buffer_read(c->in, &data, hdr->size);
+
+ s = session_find(data.name);
+ if (s == NULL) {
+ data.windows = 0;
+ write_client(c, MSG_WINDOWS, &data, sizeof data);
+ return;
+ }
+
+ data.windows = 0;
+ for (i = 0; i < ARRAY_LENGTH(&s->windows); i++) {
+ if (ARRAY_ITEM(&windows, i) != NULL)
+ data.windows++;
+ }
+ write_client2(c, MSG_WINDOWS,
+ &data, sizeof data, NULL, data.windows * sizeof entry);
+
+ for (i = 0; i < ARRAY_LENGTH(&windows); i++) {
+ w = ARRAY_ITEM(&windows, i);
+ if (w == NULL)
+ continue;
+ entry.idx = i;
+ strlcpy(entry.name, w->name, sizeof entry.name);
+ strlcpy(entry.title, w->screen.title, sizeof entry.title);
+ if (ttyname_r(w->fd, entry.tty, sizeof entry.tty) != 0)
+ *entry.tty = '\0';
+ buffer_write(c->out, &entry, sizeof entry);
+ }
+}
+
+/* Rename message from client. */
+void
+process_rename_msg(struct client *c, struct hdr *hdr)
+{
+ if (c->session == NULL)
+ log_fatalx("MSG_RENAME before identified");
+ if (hdr->size != 0)
+ log_fatalx("bad MSG_RENAME size");
+
+ user_start(c, "Window name: ",
+ c->session->window->name, MAXNAMELEN, rename_callback);
+}
+
+/* Callback for rename. */
+void
+rename_callback(struct client *c, const char *string)
+{
+ strlcpy(
+ c->session->window->name, string, sizeof c->session->window->name);
+}
diff --git a/session.c b/session.c
new file mode 100644
index 00000000..31b80154
--- /dev/null
+++ b/session.c
@@ -0,0 +1,189 @@
+/* $Id: session.c,v 1.1.1.1 2007-07-09 19:04:12 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <string.h>
+
+#include "tmux.h"
+
+/* Global session list. */
+struct sessions sessions;
+
+/* Find session by name. */
+struct session *
+session_find(const char *name)
+{
+ struct session *s;
+ u_int i;
+
+ for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
+ s = ARRAY_ITEM(&sessions, i);
+ if (s != NULL && strcmp(s->name, name) == 0)
+ return (s);
+ }
+
+ return (NULL);
+}
+
+/* Create a new session. */
+struct session *
+session_create(const char *name, const char *cmd, u_int sx, u_int sy)
+{
+ struct session *s;
+ u_int i;
+
+ s = xmalloc(sizeof *s);
+ s->tim = time(NULL);
+ strlcpy(s->name, name, sizeof s->name);
+ ARRAY_INIT(&s->windows);
+
+ if (session_new(s, cmd, sx, sy) != 0) {
+ xfree(s);
+ return (NULL);
+ }
+
+ for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
+ s = ARRAY_ITEM(&sessions, i);
+ if (s == NULL) {
+ ARRAY_SET(&sessions, i, s);
+ return (s);
+ }
+ }
+ ARRAY_ADD(&sessions, s);
+ return (s);
+}
+
+/* Destroy a session. */
+void
+session_destroy(struct session *s)
+{
+ u_int i;
+
+ if (session_index(s, &i) != 0)
+ log_fatalx("session not found");
+ ARRAY_REMOVE(&sessions, i);
+
+ while (!ARRAY_EMPTY(&s->windows))
+ window_remove(&s->windows, ARRAY_FIRST(&s->windows));
+
+ xfree(s);
+}
+
+/* Find session index. */
+int
+session_index(struct session *s, u_int *i)
+{
+ for (*i = 0; *i < ARRAY_LENGTH(&sessions); (*i)++) {
+ if (s == ARRAY_ITEM(&sessions, *i))
+ return (0);
+ }
+ return (-1);
+}
+
+/* Create a new window on a session. */
+int
+session_new(struct session *s, const char *cmd, u_int sx, u_int sy)
+{
+ struct window *w;
+
+ if ((w = window_create(cmd, sx, sy)) == NULL)
+ return (-1);
+ session_attach(s, w);
+
+ s->window = w;
+ return (0);
+}
+
+/* Attach a window to a session. */
+void
+session_attach(struct session *s, struct window *w)
+{
+ window_add(&s->windows, w);
+}
+
+/* Detach a window from a session. */
+int
+session_detach(struct session *s, struct window *w)
+{
+ if (s->window == w) {
+ if (session_next(s) != 0)
+ session_previous(s);
+ }
+
+ window_remove(&s->windows, w);
+ if (ARRAY_EMPTY(&s->windows)) {
+ session_destroy(s);
+ return (1);
+ }
+ return (0);
+}
+
+/* Return if session has window. */
+int
+session_has(struct session *s, struct window *w)
+{
+ u_int i;
+
+ return (window_index(&s->windows, w, &i) == 0);
+}
+
+/* Move session to next window. */
+int
+session_next(struct session *s)
+{
+ struct window *w;
+
+ if (s->window == NULL)
+ return (-1);
+
+ w = window_next(&s->windows, s->window);
+ if (w == NULL)
+ return (-1);
+ s->window = w;
+ return (0);
+}
+
+/* Move session to previous window. */
+int
+session_previous(struct session *s)
+{
+ struct window *w;
+
+ if (s->window == NULL)
+ return (-1);
+
+ w = window_previous(&s->windows, s->window);
+ if (w == NULL)
+ return (-1);
+ s->window = w;
+ return (0);
+}
+
+/* Move session to specific window. */
+int
+session_select(struct session *s, u_int i)
+{
+ struct window *w;
+
+ w = window_at(&s->windows, i);
+ if (w == NULL)
+ return (-1);
+ s->window = w;
+ return (0);
+}
diff --git a/tmux.c b/tmux.c
new file mode 100644
index 00000000..a5965b22
--- /dev/null
+++ b/tmux.c
@@ -0,0 +1,514 @@
+/* $Id: tmux.c,v 1.1.1.1 2007-07-09 19:03:33 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+#ifdef DEBUG
+const char *malloc_options = "AFGJPX";
+#endif
+
+int connect_server(void);
+int process_server(struct buffer *);
+int process_local(struct buffer *, struct buffer *);
+void sighandler(int);
+__dead void usage(void);
+__dead void main_list(char *);
+void process_list(struct buffer *, const char *);
+
+/* SIGWINCH received flag. */
+volatile sig_atomic_t sigwinch;
+
+/* SIGTERM received flag. */
+volatile sig_atomic_t sigterm;
+
+/* Debug output level. */
+int debug_level;
+
+/* Path to server socket. */
+char socket_path[MAXPATHLEN];
+
+__dead void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: %s [-v] [-n name] [-s path]\n", __progname);
+ exit(1);
+}
+
+void
+sighandler(int sig)
+{
+ switch (sig) {
+ case SIGWINCH:
+ sigwinch = 1;
+ break;
+ case SIGTERM:
+ sigterm = 1;
+ break;
+ case SIGCHLD:
+ waitpid(WAIT_ANY, NULL, WNOHANG);
+ break;
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ int opt, srv_fd, loc_fd, mode, listf, n;
+ char *path, name[MAXNAMELEN];
+ FILE *f;
+ struct buffer *srv_in, *srv_out, *loc_in, *loc_out;
+ struct pollfd pfds[2];
+ struct hdr hdr;
+ struct identify_data id;
+ struct size_data sd;
+ struct winsize ws;
+ struct sigaction act;
+ struct stat sb;
+
+ *name = '\0';
+ path = NULL;
+ listf = 0;
+
+ while ((opt = getopt(argc, argv, "ln:s:v?")) != EOF) {
+ switch (opt) {
+ case 'l':
+ listf = 1;
+ break;
+ case 'n':
+ if (strlcpy(name, optarg, sizeof name) >= sizeof name)
+ errx(1, "name too long");
+ break;
+ case 's':
+ path = xstrdup(optarg);
+ break;
+ case 'v':
+ debug_level++;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 0)
+ usage();
+
+ /* Sort out socket path. */
+ if (path == NULL) {
+ xasprintf(&path,
+ "%s/%s-%lu", _PATH_TMP, __progname, (u_long) getuid());
+ }
+ if (realpath(path, socket_path) == NULL)
+ err(1, "realpath");
+ xfree(path);
+
+ /* Skip to list function if listing. */
+ if (listf) {
+ if (*name == '\0')
+ main_list(NULL);
+ else
+ main_list(name);
+ }
+
+ /* And fill name. */
+ if (*name == '\0')
+ xsnprintf(name, sizeof name, "s-%lu", (u_long) getpid());
+
+ /* Check stdin/stdout. */
+ if (!isatty(STDIN_FILENO))
+ errx(1, "stdin is not a tty");
+ if (!isatty(STDOUT_FILENO))
+ errx(1, "stdout is not a tty");
+
+ /* Set up signal handlers. */
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_RESTART;
+
+ act.sa_handler = SIG_IGN;
+ if (sigaction(SIGPIPE, &act, NULL) != 0)
+ err(1, "sigaction");
+ if (sigaction(SIGUSR1, &act, NULL) != 0)
+ err(1, "sigaction");
+ if (sigaction(SIGUSR2, &act, NULL) != 0)
+ err(1, "sigaction");
+ if (sigaction(SIGINT, &act, NULL) != 0)
+ err(1, "sigaction");
+ if (sigaction(SIGTSTP, &act, NULL) != 0)
+ err(1, "sigaction");
+ if (sigaction(SIGQUIT, &act, NULL) != 0)
+ err(1, "sigaction");
+
+ act.sa_handler = sighandler;
+ if (sigaction(SIGWINCH, &act, NULL) != 0)
+ err(1, "sigaction");
+ if (sigaction(SIGTERM, &act, NULL) != 0)
+ err(1, "sigaction");
+ if (sigaction(SIGCHLD, &act, NULL) != 0)
+ err(1, "sigaction");
+
+ /* Start server if necessary. */
+ if (stat(socket_path, &sb) != 0) {
+ if (errno != ENOENT)
+ err(1, "%s", socket_path);
+ else {
+ if (server_start() != 0)
+ errx(1, "couldn't start server");
+ sleep(1);
+ }
+ } else {
+ if (!S_ISSOCK(sb.st_mode))
+ errx(1, "%s: not a socket", socket_path);
+ }
+
+ /* Connect to server. */
+ if ((srv_fd = connect_server()) == -1)
+ errx(1, "couldn't find server");
+ if ((mode = fcntl(srv_fd, F_GETFL)) == -1)
+ err(1, "fcntl");
+ if (fcntl(srv_fd, F_SETFL, mode|O_NONBLOCK) == -1)
+ err(1, "fcntl");
+ srv_in = buffer_create(BUFSIZ);
+ srv_out = buffer_create(BUFSIZ);
+
+ /* Find window size. */
+ if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1)
+ err(1, "ioctl(TIOCGWINSZ)");
+
+ /* Send initial data. */
+ hdr.code = MSG_IDENTIFY;
+ hdr.size = sizeof id;
+ buffer_write(srv_out, &hdr, sizeof hdr);
+ strlcpy(id.name, name, sizeof id.name);
+ id.sx = ws.ws_col;
+ id.sy = ws.ws_row;
+ buffer_write(srv_out, &id, hdr.size);
+
+ /* Start logging to file. */
+ if (debug_level > 0) {
+ xasprintf(&path,
+ "%s-client-%ld.log", __progname, (long) getpid());
+ f = fopen(path, "w");
+ log_open(f, LOG_USER, debug_level);
+ xfree(path);
+ }
+
+ /* Initialise terminal. */
+ loc_fd = local_init(&loc_in, &loc_out);
+ setproctitle("client (%s)", name);
+
+ /* Main loop. */
+ n = 0;
+ while (!sigterm) {
+ /* Handle SIGWINCH if necessary. */
+ if (sigwinch) {
+ if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1)
+ log_fatal("ioctl(TIOCGWINSZ)");
+
+ hdr.code = MSG_SIZE;
+ hdr.size = sizeof sd;
+ buffer_write(srv_out, &hdr, sizeof hdr);
+ sd.sx = ws.ws_col;
+ sd.sy = ws.ws_row;
+ buffer_write(srv_out, &sd, hdr.size);
+
+ sigwinch = 0;
+ }
+
+ /* Set up pollfds. */
+ pfds[0].fd = srv_fd;
+ pfds[0].events = POLLIN;
+ if (BUFFER_USED(srv_out) > 0)
+ pfds[0].events |= POLLOUT;
+ pfds[1].fd = loc_fd;
+ pfds[1].events = POLLIN;
+ if (BUFFER_USED(loc_out) > 0)
+ pfds[1].events |= POLLOUT;
+
+ /* Do the poll. */
+ if (poll(pfds, 2, INFTIM) == -1) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ log_fatal("poll");
+ }
+
+ /* Read/write from sockets. */
+ if (buffer_poll(&pfds[0], srv_in, srv_out) != 0)
+ goto server_dead;
+ if (buffer_poll(&pfds[1], loc_in, loc_out) != 0)
+ log_fatalx("lost local socket");
+
+ /* Output flushed; pause if requested. */
+ if (n)
+ usleep(750000);
+
+ /* Process any data. */
+ if ((n = process_server(srv_in)) == -1)
+ break;
+ if (process_local(loc_in, srv_out) == -1)
+ break;
+ }
+
+ local_done();
+
+ if (!sigterm)
+ printf("[detached]\n");
+ else
+ printf("[terminated]\n");
+ exit(0);
+
+server_dead:
+ local_done();
+
+ printf("[lost server]\n");
+ exit(1);
+}
+
+/* Connect to server socket from PID. */
+int
+connect_server(void)
+{
+ int fd;
+ struct sockaddr_un sa;
+ size_t sz;
+
+ memset(&sa, 0, sizeof sa);
+ sa.sun_family = AF_UNIX;
+ sz = strlcpy(sa.sun_path, socket_path, sizeof sa.sun_path);
+ if (sz >= sizeof sa.sun_path) {
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ return (-1);
+ if (connect(fd, (struct sockaddr *) &sa, SUN_LEN(&sa)) == -1)
+ return (-1);
+ return (fd);
+}
+
+/* Handle data from server. */
+int
+process_server(struct buffer *srv_in)
+{
+ struct hdr hdr;
+
+ for (;;) {
+ if (BUFFER_USED(srv_in) < sizeof hdr)
+ break;
+ memcpy(&hdr, BUFFER_OUT(srv_in), sizeof hdr);
+ if (BUFFER_USED(srv_in) < (sizeof hdr) + hdr.size)
+ break;
+ buffer_remove(srv_in, sizeof hdr);
+
+ switch (hdr.code) {
+ case MSG_OUTPUT:
+ local_output(srv_in, hdr.size);
+ break;
+ case MSG_PAUSE:
+ if (hdr.size != 0)
+ log_fatalx("bad MSG_PAUSE size");
+ return (1);
+ case MSG_EXIT:
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+/* Handle data from local terminal. */
+int
+process_local(struct buffer *loc_in, struct buffer *srv_out)
+{
+ struct buffer *b;
+ struct hdr hdr;
+ size_t size;
+ int n, key;
+
+ n = 0;
+ b = buffer_create(BUFSIZ);
+
+ while ((key = local_key(&size)) != KEYC_NONE) {
+ log_debug("key code: %d", key);
+
+ if (key == cmd_prefix) {
+ if ((key = local_key(NULL)) == KEYC_NONE) {
+ buffer_reverse_remove(loc_in, size);
+ break;
+ }
+ n = cmd_execute(key, srv_out);
+ break;
+ }
+
+ input_store8(b, '\e');
+ input_store16(b, (uint16_t) key);
+ }
+
+ if (BUFFER_USED(b) == 0) {
+ buffer_destroy(b);
+ return (n);
+ }
+ log_debug("transmitting %zu bytes of input", BUFFER_USED(b));
+
+ hdr.code = MSG_INPUT;
+ hdr.size = BUFFER_USED(b);
+ buffer_write(srv_out, &hdr, sizeof hdr);
+ buffer_write(srv_out, BUFFER_OUT(b), BUFFER_USED(b));
+
+ buffer_destroy(b);
+ return (n);
+}
+
+/* List sessions or windows. */
+__dead void
+main_list(char *name)
+{
+ struct sessions_data sd;
+ struct windows_data wd;
+ int srv_fd, mode;
+ struct buffer *srv_in, *srv_out;
+ struct pollfd pfd;
+ struct hdr hdr;
+
+ /* Connect to server. */
+ if ((srv_fd = connect_server()) == -1)
+ errx(1, "couldn't find server");
+ if ((mode = fcntl(srv_fd, F_GETFL)) == -1)
+ err(1, "fcntl");
+ if (fcntl(srv_fd, F_SETFL, mode|O_NONBLOCK) == -1)
+ err(1, "fcntl");
+ srv_in = buffer_create(BUFSIZ);
+ srv_out = buffer_create(BUFSIZ);
+
+ /* Send query data. */
+ if (name == NULL) {
+ hdr.code = MSG_SESSIONS;
+ hdr.size = sizeof sd;
+ buffer_write(srv_out, &hdr, sizeof hdr);
+ buffer_write(srv_out, &sd, hdr.size);
+ } else {
+ hdr.code = MSG_WINDOWS;
+ hdr.size = sizeof wd;
+ buffer_write(srv_out, &hdr, sizeof hdr);
+ strlcpy(wd.name, name, sizeof wd.name);
+ buffer_write(srv_out, &wd, hdr.size);
+ }
+
+ /* Main loop. */
+ for (;;) {
+ /* Set up pollfd. */
+ pfd.fd = srv_fd;
+ pfd.events = POLLIN;
+ if (BUFFER_USED(srv_out) > 0)
+ pfd.events |= POLLOUT;
+
+ /* Do the poll. */
+ if (poll(&pfd, 1, INFTIM) == -1) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ err(1, "poll");
+ }
+
+ /* Read/write from sockets. */
+ if (buffer_poll(&pfd, srv_in, srv_out) != 0)
+ errx(1, "lost server");
+
+ /* Process data. */
+ process_list(srv_in, name);
+ }
+}
+
+void
+process_list(struct buffer *srv_in, const char *name)
+{
+ struct sessions_data sd;
+ struct sessions_entry se;
+ struct windows_data wd;
+ struct windows_entry we;
+ struct hdr hdr;
+ char *tim;
+
+ for (;;) {
+ if (BUFFER_USED(srv_in) < sizeof hdr)
+ break;
+ memcpy(&hdr, BUFFER_OUT(srv_in), sizeof hdr);
+ if (BUFFER_USED(srv_in) < (sizeof hdr) + hdr.size)
+ break;
+ buffer_remove(srv_in, sizeof hdr);
+
+ switch (hdr.code) {
+ case MSG_SESSIONS:
+ if (hdr.size < sizeof sd)
+ errx(1, "bad MSG_SESSIONS size");
+ buffer_read(srv_in, &sd, sizeof sd);
+ hdr.size -= sizeof sd;
+ if (sd.sessions == 0 && hdr.size == 0)
+ exit(0);
+ if (hdr.size < sd.sessions * sizeof se)
+ errx(1, "bad MSG_SESSIONS size");
+ while (sd.sessions-- > 0) {
+ buffer_read(srv_in, &se, sizeof se);
+ tim = ctime(&se.tim);
+ *strchr(tim, '\n') = '\0';
+ printf("%s: %u windows (created %s)\n",
+ se.name, se.windows, tim);
+ }
+ exit(0);
+ case MSG_WINDOWS:
+ if (hdr.size < sizeof wd)
+ errx(1, "bad MSG_WINDOWS size");
+ buffer_read(srv_in, &wd, sizeof wd);
+ hdr.size -= sizeof wd;
+ if (wd.windows == 0 && hdr.size == 0)
+ errx(1, "session \"%s\" not found", name);
+ if (hdr.size < wd.windows * sizeof we)
+ errx(1, "bad MSG_WINDOWS size");
+ while (wd.windows-- > 0) {
+ buffer_read(srv_in, &we, sizeof we);
+ if (*we.title != '\0') {
+ printf("%u: %s \"%s\" (%s)\n",
+ we.idx, we.name, we.title, we.tty);
+ } else {
+ printf("%u: %s (%s)\n",
+ we.idx, we.name, we.tty);
+ }
+ }
+ exit(0);
+ }
+ }
+}
diff --git a/tmux.h b/tmux.h
new file mode 100644
index 00000000..962ed98c
--- /dev/null
+++ b/tmux.h
@@ -0,0 +1,583 @@
+/* $Id: tmux.h,v 1.1.1.1 2007-07-09 19:03:50 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef NSCR_H
+#define NSCR_H
+
+#include <sys/param.h>
+#include <sys/queue.h>
+
+#include <poll.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "array.h"
+
+extern char *__progname;
+
+#define MAXNAMELEN 32
+#define MAXTITLELEN 192
+
+/* Definition to shut gcc up about unused arguments. */
+#define unused __attribute__ ((unused))
+
+/* Attribute to make gcc check printf-like arguments. */
+#define printflike1 __attribute__ ((format (printf, 1, 2)))
+#define printflike2 __attribute__ ((format (printf, 2, 3)))
+#define printflike3 __attribute__ ((format (printf, 3, 4)))
+#define printflike4 __attribute__ ((format (printf, 4, 5)))
+
+/* Ensure buffer size. */
+#define ENSURE_SIZE(buf, len, size) do { \
+ (buf) = ensure_size(buf, &(len), 1, size); \
+} while (0)
+#define ENSURE_SIZE2(buf, len, nmemb, size) do { \
+ (buf) = ensure_size(buf, &(len), nmemb, size); \
+} while (0)
+#define ENSURE_FOR(buf, len, size, adj) do { \
+ (buf) = ensure_for(buf, &(len), size, adj); \
+} while (0)
+
+/* Buffer macros. */
+#define BUFFER_USED(b) ((b)->size)
+#define BUFFER_FREE(b) ((b)->space - (b)->off - (b)->size)
+#define BUFFER_IN(b) ((b)->base + (b)->off + (b)->size)
+#define BUFFER_OUT(b) ((b)->base + (b)->off)
+
+/* Buffer structure. */
+struct buffer {
+ u_char *base; /* buffer start */
+ size_t space; /* total size of buffer */
+
+ size_t size; /* size of data in buffer */
+ size_t off; /* offset of data in buffer */
+};
+
+/* Key codes. ncurses defines KEY_*. Grrr. */
+#define KEYC_NONE 256
+#define KEYC_A1 -1
+#define KEYC_A3 -2
+#define KEYC_B2 -3
+#define KEYC_BACKSPACE -4
+#define KEYC_BEG -5
+#define KEYC_BTAB -6
+#define KEYC_C1 -7
+#define KEYC_C3 -8
+#define KEYC_CANCEL -9
+#define KEYC_CATAB -10
+#define KEYC_CLEAR -11
+#define KEYC_CLOSE -12
+#define KEYC_COMMAND -13
+#define KEYC_COPY -14
+#define KEYC_CREATE -15
+#define KEYC_CTAB -16
+#define KEYC_DC -17
+#define KEYC_DL -18
+#define KEYC_DOWN -19
+#define KEYC_EIC -20
+#define KEYC_END -21
+#define KEYC_ENTER -22
+#define KEYC_EOL -23
+#define KEYC_EOS -24
+#define KEYC_EXIT -25
+#define KEYC_F0 -26
+#define KEYC_F1 -27
+#define KEYC_F10 -28
+#define KEYC_F11 -29
+#define KEYC_F12 -30
+#define KEYC_F13 -31
+#define KEYC_F14 -32
+#define KEYC_F15 -33
+#define KEYC_F16 -34
+#define KEYC_F17 -35
+#define KEYC_F18 -36
+#define KEYC_F19 -37
+#define KEYC_F2 -38
+#define KEYC_F20 -39
+#define KEYC_F21 -40
+#define KEYC_F22 -41
+#define KEYC_F23 -42
+#define KEYC_F24 -43
+#define KEYC_F25 -44
+#define KEYC_F26 -45
+#define KEYC_F27 -46
+#define KEYC_F28 -47
+#define KEYC_F29 -48
+#define KEYC_F3 -49
+#define KEYC_F30 -50
+#define KEYC_F31 -51
+#define KEYC_F32 -52
+#define KEYC_F33 -53
+#define KEYC_F34 -54
+#define KEYC_F35 -55
+#define KEYC_F36 -56
+#define KEYC_F37 -57
+#define KEYC_F38 -58
+#define KEYC_F39 -59
+#define KEYC_F4 -60
+#define KEYC_F40 -61
+#define KEYC_F41 -62
+#define KEYC_F42 -63
+#define KEYC_F43 -64
+#define KEYC_F44 -65
+#define KEYC_F45 -66
+#define KEYC_F46 -67
+#define KEYC_F47 -68
+#define KEYC_F48 -69
+#define KEYC_F49 -70
+#define KEYC_F5 -71
+#define KEYC_F50 -72
+#define KEYC_F51 -73
+#define KEYC_F52 -74
+#define KEYC_F53 -75
+#define KEYC_F54 -76
+#define KEYC_F55 -77
+#define KEYC_F56 -78
+#define KEYC_F57 -79
+#define KEYC_F58 -80
+#define KEYC_F59 -81
+#define KEYC_F6 -82
+#define KEYC_F60 -83
+#define KEYC_F61 -84
+#define KEYC_F62 -85
+#define KEYC_F63 -86
+#define KEYC_F7 -87
+#define KEYC_F8 -88
+#define KEYC_F9 -89
+#define KEYC_FIND -90
+#define KEYC_HELP -91
+#define KEYC_HOME -92
+#define KEYC_IC -93
+#define KEYC_IL -94
+#define KEYC_LEFT -95
+#define KEYC_LL -96
+#define KEYC_MARK -97
+#define KEYC_MESSAGE -98
+#define KEYC_MOVE -99
+#define KEYC_NEXT -100
+#define KEYC_NPAGE -101
+#define KEYC_OPEN -102
+#define KEYC_OPTIONS -103
+#define KEYC_PPAGE -104
+#define KEYC_PREVIOUS -105
+#define KEYC_PRINT -106
+#define KEYC_REDO -107
+#define KEYC_REFERENCE -108
+#define KEYC_REFRESH -109
+#define KEYC_REPLACE -110
+#define KEYC_RESTART -111
+#define KEYC_RESUME -112
+#define KEYC_RIGHT -113
+#define KEYC_SAVE -114
+#define KEYC_SBEG -115
+#define KEYC_SCANCEL -116
+#define KEYC_SCOMMAND -117
+#define KEYC_SCOPY -118
+#define KEYC_SCREATE -119
+#define KEYC_SDC -120
+#define KEYC_SDL -121
+#define KEYC_SELECT -122
+#define KEYC_SEND -123
+#define KEYC_SEOL -124
+#define KEYC_SEXIT -125
+#define KEYC_SF -126
+#define KEYC_SFIND -127
+#define KEYC_SHELP -128
+#define KEYC_SHOME -129
+#define KEYC_SIC -130
+#define KEYC_SLEFT -131
+#define KEYC_SMESSAGE -132
+#define KEYC_SMOVE -133
+#define KEYC_SNEXT -134
+#define KEYC_SOPTIONS -135
+#define KEYC_SPREVIOUS -136
+#define KEYC_SPRINT -137
+#define KEYC_SR -138
+#define KEYC_SREDO -139
+#define KEYC_SREPLACE -140
+#define KEYC_SRIGHT -141
+#define KEYC_SRSUME -142
+#define KEYC_SSAVE -143
+#define KEYC_SSUSPEND -144
+#define KEYC_STAB -145
+#define KEYC_SUNDO -146
+#define KEYC_SUSPEND -147
+#define KEYC_UNDO -148
+#define KEYC_UP -149
+#define KEYC_MOUSE -150
+
+/* Escape codes. */
+/*
+ AL=\E[%dL parm_insert_line
+ DC=\E[%dP parm_dch
+ DL=\E[%dM parm_delete_line
+ DO=\E[%dB parm_down_cursor
+ IC=\E[%d@ parm_ich
+ Km=\E[M key_mouse
+ LE=\E[%dD parm_left_cursor
+ RI=\E[%dC parm_right_cursor
+ UP=\E[%dA parm_up_cursor
+ ac=++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~
+ acs_chars
+ ae=^O exit_alt_charset_mode
+ al=\E[L insert_line
+ as=^N enter_alt_charset_mode
+ bl=^G bell
+ bt=\E[Z back_tab
+ cb=\E[1K clear_bol
+ cd=\E[J clear_eos
+ ce=\E[K clear_eol
+ cl=\E[H\E[J clear_screen
+ cm=\E[%i%d;%dH cursor_address
+ cr=^M carriage_return
+ cs=\E[%i%d;%dr change_scroll_region
+ ct=\E[3g clear_all_tabs
+ dc=\E[P delete_character
+ dl=\E[M delete_line
+ do=^J cursor_down
+ eA=\E(B\E)0 ena_acs
+ ei=\E[4l exit_insert_mode
+ ho=\E[H cursor_home
+ im=\E[4h enter_insert_mode
+ is=\E)0 init_2string
+ le=^H cursor_left
+ mb=\E[5m enter_blink_mode
+ md=\E[1m enter_bold_mode
+ me=\E[m exit_attrbute_mode
+ mr=\E[7m enter_reverse_mode
+ nd=\E[C cursor_right
+ nw=\EE newline
+ rc=\E8 restore_cursor
+ rs=\Ec reset_string
+ sc=\E7 save_cursor
+ se=\E[23m exit_standout_mode
+ sf=^J scroll_forward
+ so=\E[3m enter_standout_mode
+ sr=\EM scroll_reverse
+ st=\EH set_tab
+ ta=^I tab
+ ue=\E[24m exit_underline_mode
+ up=\EM cursor_up
+ s=\E[4m
+ vb=\Eg flash_screen
+ ve=\E[34h\E[?25h
+ cursor_normal
+ vi=\E[?25l cursor_invisible
+ vs=\E[34l cursor_visible
+ E0=\E(B
+ S0=\E(%p1%c
+ */
+
+/* Translated escape codes. */
+#define CODE_CURSORUP 0
+#define CODE_CURSORDOWN 1
+#define CODE_CURSORRIGHT 2
+#define CODE_CURSORLEFT 3
+#define CODE_INSERTCHARACTER 4
+#define CODE_DELETECHARACTER 5
+#define CODE_INSERTLINE 6
+#define CODE_DELETELINE 7
+#define CODE_CLEARLINE 8
+#define CODE_CLEARSCREEN 9
+#define CODE_CLEARENDOFLINE 10
+#define CODE_CLEARENDOFSCREEN 11
+#define CODE_CLEARSTARTOFLINE 12
+#define CODE_CURSORMOVE 13
+#define CODE_ATTRIBUTES 14
+#define CODE_CURSOROFF 15
+#define CODE_CURSORON 16
+#define CODE_CURSORUPSCROLL 17
+#define CODE_CURSORDOWNSCROLL 18
+#define CODE_SCROLLREGION 19
+#define CODE_INSERTON 20
+#define CODE_INSERTOFF 21
+#define CODE_KCURSOROFF 22
+#define CODE_KCURSORON 23
+#define CODE_KKEYPADOFF 24
+#define CODE_KKEYPADON 25
+#define CODE_TITLE 26
+
+/* Message codes. */
+#define MSG_IDENTIFY 0
+#define MSG_CREATE 1
+#define MSG_EXIT 2
+#define MSG_SIZE 3
+#define MSG_NEXT 4
+#define MSG_PREVIOUS 5
+#define MSG_INPUT 6 /* input from client to server */
+#define MSG_OUTPUT 7 /* output from server to client */
+#define MSG_REFRESH 8
+#define MSG_SELECT 9
+#define MSG_SESSIONS 10
+#define MSG_WINDOWS 11
+#define MSG_PAUSE 12
+#define MSG_RENAME 13
+
+struct identify_data {
+ char name[MAXNAMELEN];
+
+ u_int sx;
+ u_int sy;
+};
+
+struct sessions_data {
+ u_int sessions;
+};
+
+struct sessions_entry {
+ char name[MAXNAMELEN];
+ time_t tim;
+ u_int windows;
+};
+
+struct windows_data {
+ char name[MAXNAMELEN];
+ u_int windows;
+};
+
+struct windows_entry {
+ u_int idx;
+ char tty[TTY_NAME_MAX];
+
+ char name[MAXNAMELEN];
+ char title[MAXTITLELEN];
+};
+
+struct size_data {
+ u_int sx;
+ u_int sy;
+};
+
+struct select_data {
+ u_int idx;
+};
+
+struct refresh_data {
+ u_int py_upper;
+ u_int py_lower;
+};
+
+/* Message header structure. */
+struct hdr {
+ u_int code;
+ size_t size;
+};
+
+/* Attributes. */
+#define ATTR_BRIGHT 0x1
+#define ATTR_DIM 0x2
+#define ATTR_UNDERSCORE 0x4
+#define ATTR_BLINK 0x8
+#define ATTR_REVERSE 0x10
+#define ATTR_HIDDEN 0x20
+#define ATTR_ITALICS 0x40
+
+/* Modes. */
+#define MODE_CURSOR 0x1
+#define MODE_INSERT 0x2
+#define MODE_KCURSOR 0x4
+#define MODE_KKEYPAD 0x8
+
+/*
+ * Virtual screen. This is stored as three blocks of 8-bit values, one for
+ * the actual characters, one for attributes and one for colours. Three
+ * seperate blocks means memset and friends can be used.
+ *
+ * Each block is y by x in size, row then column order. Sizes are 0-based.
+ */
+struct screen {
+ char title[MAXTITLELEN];
+
+ u_char **grid_data;
+ u_char **grid_attr;
+ u_char **grid_colr;
+
+ u_int sx; /* size x */
+ u_int sy; /* size y */
+
+ u_int cx; /* cursor x */
+ u_int cy; /* cursor y */
+
+ u_int ry_upper; /* scroll region top */
+ u_int ry_lower; /* scroll region bottom */
+
+ u_char attr;
+ u_char colr; /* fg:bg */
+
+ int mode;
+};
+
+/* Window structure. */
+struct window {
+ char name[MAXNAMELEN];
+
+ int fd;
+ struct buffer *in;
+ struct buffer *out;
+
+ u_int references;
+
+ struct screen screen;
+};
+ARRAY_DECL(windows, struct window *);
+
+/* Client session. */
+struct session {
+ char name[MAXNAMELEN];
+ time_t tim;
+
+ struct window *window;
+ struct windows windows;
+};
+ARRAY_DECL(sessions, struct session *);
+
+/* Client connection. */
+struct client {
+ int fd;
+ struct buffer *in;
+ struct buffer *out;
+
+ u_int sx;
+ u_int sy;
+
+ struct session *session;
+
+ /* User input. */
+ const char *prompt;
+ char *buf;
+ size_t len;
+ size_t idx;
+ void (*callback)(struct client *, const char *);
+
+};
+ARRAY_DECL(clients, struct client *);
+
+/* tmux.c */
+volatile sig_atomic_t sigterm;
+extern int debug_level;
+extern char socket_path[MAXPATHLEN];
+
+/* server.c */
+int server_start(void);
+
+/* ansi.c */
+void input_key(struct buffer *, int);
+size_t input_parse(u_char *, size_t, struct buffer *, struct screen *);
+uint8_t input_extract8(struct buffer *);
+uint16_t input_extract16(struct buffer *);
+void input_store8(struct buffer *, uint8_t);
+void input_store16(struct buffer *, uint16_t);
+void input_store_zero(struct buffer *, u_char);
+void input_store_one(struct buffer *, u_char, uint16_t);
+void input_store_two(struct buffer *, u_char, uint16_t, uint16_t);
+
+/* screen.c */
+void screen_create(struct screen *, u_int, u_int);
+void screen_resize(struct screen *, u_int, u_int);
+void screen_draw(struct screen *, struct buffer *, u_int, u_int);
+void screen_character(struct screen *, u_char);
+void screen_sequence(struct screen *, u_char *);
+
+/* local.c */
+int local_init(struct buffer **, struct buffer **);
+void local_done(void);
+int local_key(size_t *);
+void local_output(struct buffer *, size_t);
+
+/* command.c */
+extern int cmd_prefix;
+int cmd_execute(int, struct buffer *);
+
+/* window.c */
+extern struct windows windows;
+struct window *window_create(const char *, u_int, u_int);
+int window_index(struct windows *, struct window *, u_int *);
+void window_add(struct windows *, struct window *);
+void window_remove(struct windows *, struct window *);
+void window_destroy(struct window *);
+struct window *window_next(struct windows *, struct window *);
+struct window *window_previous(struct windows *, struct window *);
+struct window *window_at(struct windows *, u_int);
+int window_resize(struct window *, u_int, u_int);
+int window_poll(struct window *, struct pollfd *);
+void window_input(struct window *, struct buffer *, size_t);
+void window_output(struct window *, struct buffer *);
+
+/* session.c */
+extern struct sessions sessions;
+struct session *session_find(const char *);
+struct session *session_create(const char *, const char *, u_int, u_int);
+void session_destroy(struct session *);
+int session_index(struct session *, u_int *);
+int session_new(struct session *, const char *, u_int, u_int);
+void session_attach(struct session *, struct window *);
+int session_detach(struct session *, struct window *);
+int session_has(struct session *, struct window *);
+int session_next(struct session *);
+int session_previous(struct session *);
+int session_select(struct session *, u_int);
+
+/* buffer.c */
+struct buffer *buffer_create(size_t);
+void buffer_destroy(struct buffer *);
+void buffer_clear(struct buffer *);
+void buffer_ensure(struct buffer *, size_t);
+void buffer_add(struct buffer *, size_t);
+void buffer_reverse_add(struct buffer *, size_t);
+void buffer_remove(struct buffer *, size_t);
+void buffer_reverse_remove(struct buffer *, size_t);
+void buffer_insert_range(struct buffer *, size_t, size_t);
+void buffer_delete_range(struct buffer *, size_t, size_t);
+void buffer_write(struct buffer *, const void *, size_t);
+void buffer_read(struct buffer *, void *, size_t);
+
+/* buffer-poll.c */
+int buffer_poll(struct pollfd *, struct buffer *, struct buffer *);
+
+/* log.c */
+void log_open(FILE *, int, int);
+void log_close(void);
+void log_vwrite(FILE *, int, const char *, va_list);
+void log_write(FILE *, int, const char *, ...);
+void printflike1 log_warn(const char *, ...);
+void printflike1 log_warnx(const char *, ...);
+void printflike1 log_info(const char *, ...);
+void printflike1 log_debug(const char *, ...);
+void printflike1 log_debug2(const char *, ...);
+void printflike1 log_debug3(const char *, ...);
+__dead void log_vfatal(const char *, va_list);
+__dead void log_fatal(const char *, ...);
+__dead void log_fatalx(const char *, ...);
+
+/* xmalloc.c */
+void *ensure_size(void *, size_t *, size_t, size_t);
+void *ensure_for(void *, size_t *, size_t, size_t);
+char *xstrdup(const char *);
+void *xcalloc(size_t, size_t);
+void *xmalloc(size_t);
+void *xrealloc(void *, size_t, size_t);
+void xfree(void *);
+int printflike2 xasprintf(char **, const char *, ...);
+int xvasprintf(char **, const char *, va_list);
+int printflike3 xsnprintf(char *, size_t, const char *, ...);
+int xvsnprintf(char *, size_t, const char *, va_list);
+int printflike3 printpath(char *, size_t, const char *, ...);
+char *xdirname(const char *);
+char *xbasename(const char *);
+
+#endif
diff --git a/window.c b/window.c
new file mode 100644
index 00000000..47d9e08d
--- /dev/null
+++ b/window.c
@@ -0,0 +1,317 @@
+/* $Id: window.c,v 1.1.1.1 2007-07-09 19:04:12 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <fcntl.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#define TTYDEFCHARS
+#include <termios.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "tmux.h"
+
+/*
+ * Each window is attached to a pty. This file contains code to handle them.
+ *
+ * A window has two buffers attached, these are filled and emptied by the main
+ * server poll loop. Output data is received from pty's in ANSI format,
+ * translated and returned as a series of ANSI escape sequences and strings.
+ * Input data is received in ANSI format and written directly to the pty.
+ *
+ * Each window also has a "virtual" screen (screen.c) which contains the
+ * current state and is redisplayed when the window is reattached to a client.
+ *
+ * A global list of windows is maintained, and a window may also be a member
+ * of any number of sessions. A reference count is maintained and a window
+ * removed from the global list and destroyed when it reaches zero.
+ */
+
+/* Global window list. */
+struct windows windows;
+
+/* Create a new window. */
+struct window *
+window_create(const char *cmd, u_int sx, u_int sy)
+{
+ struct window *w;
+ struct winsize ws;
+ struct termios tio;
+ int fd, mode;
+ char pid[16], *ptr, *name;
+
+ memset(&ws, 0, sizeof ws);
+ ws.ws_col = sx;
+ ws.ws_row = sy;
+
+ memset(&tio, 0, sizeof tio);
+ tio.c_iflag = TTYDEF_IFLAG;
+ tio.c_oflag = TTYDEF_OFLAG;
+ tio.c_lflag = TTYDEF_LFLAG;
+ tio.c_cflag = TTYDEF_CFLAG;
+ memcpy(&tio.c_cc, ttydefchars, sizeof tio.c_cc);
+ cfsetspeed(&tio, TTYDEF_SPEED);
+
+ xsnprintf(pid, sizeof pid, "%ld", (long) getpid());
+ switch (forkpty(&fd, NULL, &tio, &ws)) {
+ case -1:
+ return (NULL);
+ case 0:
+ if (setenv("TMUX", pid, 1) != 0)
+ log_fatal("setenv");
+ if (setenv("TERM", "screen", 1) != 0)
+ log_fatal("setenv");
+ log_close();
+
+ execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL);
+ log_fatal("execl");
+ }
+
+ if ((mode = fcntl(fd, F_GETFL)) == -1)
+ log_fatal("fcntl");
+ if (fcntl(fd, F_SETFL, mode|O_NONBLOCK) == -1)
+ log_fatal("fcntl");
+
+ mode = 1;
+ if (ioctl(fd, TIOCPKT, &mode) == -1)
+ log_fatal("ioctl(TIOCPKT)");
+
+ w = xmalloc(sizeof *w);
+ w->fd = fd;
+ w->in = buffer_create(BUFSIZ);
+ w->out = buffer_create(BUFSIZ);
+ screen_create(&w->screen, sx, sy);
+
+ name = xstrdup(cmd);
+ if ((ptr = strchr(name, ' ')) != NULL) {
+ if (ptr != name && ptr[-1] != '\\')
+ *ptr = '\0';
+ else {
+ while ((ptr = strchr(ptr + 1, ' ')) != NULL) {
+ if (ptr[-1] != '\\') {
+ *ptr = '\0';
+ break;
+ }
+ }
+ }
+ }
+ strlcpy(w->name, xbasename(name), sizeof w->name);
+ xfree(name);
+
+ window_add(&windows, w);
+ w->references = 0;
+
+ return (w);
+}
+
+/* Find window index in list. */
+int
+window_index(struct windows *ww, struct window *w, u_int *i)
+{
+ for (*i = 0; *i < ARRAY_LENGTH(ww); (*i)++) {
+ if (w == ARRAY_ITEM(ww, *i))
+ return (0);
+ }
+ return (-1);
+}
+
+/* Add a window to a list. */
+void
+window_add(struct windows *ww, struct window *w)
+{
+ u_int i;
+
+ if (window_index(ww, NULL, &i) != 0)
+ ARRAY_ADD(ww, w);
+ else
+ ARRAY_SET(ww, i, w);
+
+ w->references++;
+}
+
+/* Remove a window from a list. */
+void
+window_remove(struct windows *ww, struct window *w)
+{
+ u_int i;
+
+ if (window_index(ww, w, &i) != 0)
+ log_fatalx("window_remove: window not found");
+ ARRAY_REMOVE(ww, i);
+
+ w->references--;
+ if (w->references == 0) {
+ window_destroy(w);
+ window_remove(&windows, w);
+ }
+}
+
+/* Destroy a window. */
+void
+window_destroy(struct window *w)
+{
+ close(w->fd);
+
+ buffer_destroy(w->in);
+ buffer_destroy(w->out);
+ xfree(w);
+}
+
+/* Locate next window in list. */
+struct window *
+window_next(struct windows *ww, struct window *w)
+{
+ u_int i;
+
+ if (window_index(ww, w, &i) != 0)
+ log_fatalx("window_next: window not found");
+
+ if (i == ARRAY_LENGTH(ww) - 1)
+ return (NULL);
+ do {
+ i++;
+ w = window_at(ww, i);
+ if (w != NULL)
+ return (w);
+ } while (i != ARRAY_LENGTH(ww) - 1);
+ return (NULL);
+}
+
+/* Locate previous window in list. */
+struct window *
+window_previous(struct windows *ww, struct window *w)
+{
+ u_int i;
+
+ if (window_index(ww, w, &i) != 0)
+ log_fatalx("window_previous: window not found");
+ if (i == 0)
+ return (NULL);
+ do {
+ i--;
+ w = window_at(ww, i);
+ if (w != NULL)
+ return (w);
+ } while (i != 0);
+ return (NULL);
+}
+
+/* Locate window at specific position in list. */
+struct window *
+window_at(struct windows *ww, u_int i)
+{
+ if (i >= ARRAY_LENGTH(ww))
+ return (NULL);
+ return (ARRAY_ITEM(ww, i));
+}
+
+/* Resize a window. */
+int
+window_resize(struct window *w, u_int sx, u_int sy)
+{
+ struct winsize ws;
+
+ if (sx == w->screen.sx && sy == w->screen.sy)
+ return (-1);
+
+ memset(&ws, 0, sizeof ws);
+ ws.ws_col = sx;
+ ws.ws_row = sy;
+
+ screen_resize(&w->screen, sx, sy);
+
+ if (ioctl(w->fd, TIOCSWINSZ, &ws) == -1)
+ log_fatal("ioctl(TIOCSWINSZ)");
+ return (0);
+}
+
+/* Handle window poll results. This is special because of TIOCPKT. */
+int
+window_poll(struct window *w, struct pollfd *pfd)
+{
+ struct termios tio;
+ size_t size;
+ u_char *ptr;
+
+ size = BUFFER_USED(w->in);
+ if (buffer_poll(pfd, w->in, w->out) != 0)
+ return (-1);
+
+ if (BUFFER_USED(w->in) == size)
+ return (0);
+ ptr = BUFFER_IN(w->in) - (BUFFER_USED(w->in) - size);
+
+ log_debug("window packet: %hhu", *ptr);
+ switch (*ptr) {
+ case TIOCPKT_DATA:
+ case TIOCPKT_FLUSHREAD:
+ case TIOCPKT_FLUSHWRITE:
+ case TIOCPKT_STOP:
+ case TIOCPKT_START:
+ case TIOCPKT_DOSTOP:
+ case TIOCPKT_NOSTOP:
+ buffer_delete_range(w->in, size, 1);
+ break;
+ case TIOCPKT_IOCTL:
+ buffer_delete_range(w->in, size, 1 + sizeof tio);
+ break;
+ }
+
+ return (0);
+}
+
+/* Process window input. */
+void
+window_input(struct window *w, struct buffer *b, size_t size)
+{
+ int key;
+
+ while (size != 0) {
+ if (size < 1)
+ break;
+ size--;
+ key = input_extract8(b);
+ if (key == '\e') {
+ if (size < 2)
+ log_fatalx("window_input: underflow");
+ size -= 2;
+ key = (int16_t) input_extract16(b);
+ }
+ input_key(w->out, key);
+ }
+}
+
+/*
+ * Process window output. Output is translated into a series of ANSI escape
+ * sequences and strings and returned.
+ */
+void
+window_output(struct window *w, struct buffer *b)
+{
+ size_t used;
+
+ used = input_parse(
+ BUFFER_OUT(w->in), BUFFER_USED(w->in), b, &w->screen);
+ if (used != 0)
+ buffer_remove(w->in, used);
+}
diff --git a/xmalloc.c b/xmalloc.c
new file mode 100644
index 00000000..228e4381
--- /dev/null
+++ b/xmalloc.c
@@ -0,0 +1,243 @@
+/* $Id: xmalloc.c,v 1.1.1.1 2007-07-09 19:03:33 nicm Exp $ */
+
+/*
+ * Copyright (c) 2004 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+void *
+ensure_for(void *buf, size_t *len, size_t size, size_t adj)
+{
+ if (adj == 0)
+ log_fatalx("ensure_for: zero adj");
+
+ if (SIZE_MAX - size < adj)
+ log_fatalx("ensure_for: size + adj > SIZE_MAX");
+ size += adj;
+
+ if (*len == 0) {
+ *len = BUFSIZ;
+ buf = xmalloc(*len);
+ }
+
+ while (*len <= size) {
+ buf = xrealloc(buf, 2, *len);
+ *len *= 2;
+ }
+
+ return (buf);
+}
+
+void *
+ensure_size(void *buf, size_t *len, size_t nmemb, size_t size)
+{
+ if (nmemb == 0 || size == 0)
+ log_fatalx("ensure_size: zero size");
+ if (SIZE_MAX / nmemb < size)
+ log_fatalx("ensure_size: nmemb * size > SIZE_MAX");
+
+ if (*len == 0) {
+ *len = BUFSIZ;
+ buf = xmalloc(*len);
+ }
+
+ while (*len <= nmemb * size) {
+ buf = xrealloc(buf, 2, *len);
+ *len *= 2;
+ }
+
+ return (buf);
+}
+
+char *
+xstrdup(const char *s)
+{
+ void *ptr;
+ size_t len;
+
+ len = strlen(s) + 1;
+ ptr = xmalloc(len);
+
+ return (strncpy(ptr, s, len));
+}
+
+void *
+xcalloc(size_t nmemb, size_t size)
+{
+ void *ptr;
+
+ if (size == 0 || nmemb == 0)
+ log_fatalx("xcalloc: zero size");
+ if (SIZE_MAX / nmemb < size)
+ log_fatalx("xcalloc: nmemb * size > SIZE_MAX");
+ if ((ptr = calloc(nmemb, size)) == NULL)
+ log_fatal("xcalloc");
+
+ return (ptr);
+}
+
+void *
+xmalloc(size_t size)
+{
+ void *ptr;
+
+ if (size == 0)
+ log_fatalx("xmalloc: zero size");
+ if ((ptr = malloc(size)) == NULL)
+ log_fatal("xmalloc");
+
+ return (ptr);
+}
+
+void *
+xrealloc(void *oldptr, size_t nmemb, size_t size)
+{
+ size_t newsize = nmemb * size;
+ void *newptr;
+
+ if (newsize == 0)
+ log_fatalx("xrealloc: zero size");
+ if (SIZE_MAX / nmemb < size)
+ log_fatal("xrealloc: nmemb * size > SIZE_MAX");
+ if ((newptr = realloc(oldptr, newsize)) == NULL)
+ log_fatal("xrealloc");
+
+ return (newptr);
+}
+
+void
+xfree(void *ptr)
+{
+ if (ptr == NULL)
+ log_fatalx("xfree: null pointer");
+ free(ptr);
+}
+
+int printflike2
+xasprintf(char **ret, const char *fmt, ...)
+{
+ va_list ap;
+ int i;
+
+ va_start(ap, fmt);
+ i = xvasprintf(ret, fmt, ap);
+ va_end(ap);
+
+ return (i);
+}
+
+int
+xvasprintf(char **ret, const char *fmt, va_list ap)
+{
+ int i;
+
+ i = vasprintf(ret, fmt, ap);
+
+ if (i < 0 || *ret == NULL)
+ log_fatal("xvasprintf");
+
+ return (i);
+}
+
+int printflike3
+xsnprintf(char *buf, size_t len, const char *fmt, ...)
+{
+ va_list ap;
+ int i;
+
+ va_start(ap, fmt);
+ i = xvsnprintf(buf, len, fmt, ap);
+ va_end(ap);
+
+ return (i);
+}
+
+int
+xvsnprintf(char *buf, size_t len, const char *fmt, va_list ap)
+{
+ int i;
+
+ if (len > INT_MAX) {
+ errno = EINVAL;
+ log_fatal("xvsnprintf");
+ }
+
+ i = vsnprintf(buf, len, fmt, ap);
+
+ if (i < 0)
+ log_fatal("xvsnprintf");
+
+ return (i);
+}
+
+/*
+ * Print a path. Same as xsnprintf, but return ENAMETOOLONG on truncation.
+ */
+int printflike3
+printpath(char *buf, size_t len, const char *fmt, ...)
+{
+ va_list ap;
+ int n;
+
+ if (len > INT_MAX) {
+ errno = ENAMETOOLONG;
+ return (1);
+ }
+
+ va_start(ap, fmt);
+ n = xvsnprintf(buf, len, fmt, ap);
+ va_end(ap);
+
+ if ((size_t) n > len) {
+ errno = ENAMETOOLONG;
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * Some system modify the path in place. This function and xbasename below
+ * avoid that by using a temporary buffer.
+ */
+char *
+xdirname(const char *src)
+{
+ char dst[MAXPATHLEN];
+
+ strlcpy(dst, src, sizeof dst);
+ return (dirname(dst));
+}
+
+char *
+xbasename(const char *src)
+{
+ char dst[MAXPATHLEN];
+
+ strlcpy(dst, src, sizeof dst);
+ return (basename(dst));
+}