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 " 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 ". Sessions are destroyed when no +windows remain attached to them. + +Another server process can be used by specifying an alternative socket path with +"-s " 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$\ makes keypad 0 send "DIR" + 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 + * + * 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 + * + * 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 + +#include +#include +#include + +#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 + * + * 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 + +#include + +#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 + * + * 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 + +#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 + * + * 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 + +#include +#include +#include + +#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 + * + * 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 + +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 + +#include +#include +#include +#include +#include + +#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 + * + * 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 + +#include + +#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 + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 + +#include + +#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 + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 +#include + +#include +#include +#include + +#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 + * + * 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 +#include + +#include +#include +#include +#include +#include +#define TTYDEFCHARS +#include +#include +#include + +#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 + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#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)); +}