├── .gitignore ├── COPYING ├── Makefile ├── README ├── bits.c ├── bits.h ├── buffer.c ├── buffer.h ├── c_init.awk ├── cmd.c ├── cmd.h ├── colour.c ├── colour.def ├── colour.h ├── complete.c ├── complete.h ├── config.c ├── config.cdl ├── config.h ├── ctbuf.c ├── ctbuf.h ├── ctcp.c ├── ctcp.h ├── dist └── config.mak ├── distMakefile ├── gen_numerics.py ├── gen_numerics_text.py ├── genconfig.c ├── genkeymap.c ├── gitversion ├── input.c ├── input.h ├── irc.c ├── irc.h ├── keys ├── logging.c ├── logging.h ├── man.in ├── names.c ├── names.h ├── numeric.h ├── numeric_text.c ├── numeric_text.h ├── numerics.py ├── osconf.h ├── plans ├── quirc.c ├── readme.css ├── readme.htm ├── spec-cdl ├── strbuf.c ├── strbuf.h ├── text.h ├── ttyesc.c ├── ttyesc.h ├── ttyraw.c ├── ttyraw.h ├── tutorial.htm ├── types.c └── types.h /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | quirc 3 | *.o 4 | *.d 5 | *.tar 6 | *.tar.gz 7 | *.tgz 8 | genconfig 9 | genkeymap 10 | version.h 11 | quirc.1 12 | config_* 13 | c_init.c 14 | keymap.c 15 | keymod.h 16 | *.pyc 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for quIRC 2 | 3 | VERSION := `git describe --tags` 4 | 5 | include distMakefile 6 | 7 | FORCE: 8 | version.h: FORCE 9 | ./gitversion 10 | 11 | dist: all doc 12 | -mkdir quirc_$(VERSION) 13 | for p in $$(ls); do cp $$p quirc_$(VERSION)/$$p; done; 14 | cp -r dist quirc_$(VERSION)/dist 15 | -rm quirc_$(VERSION)/*.tar.gz 16 | mv quirc_$(VERSION)/distMakefile quirc_$(VERSION)/Makefile 17 | tar -czf quirc_$(VERSION).tar.gz quirc_$(VERSION)/ 18 | rm -r quirc_$(VERSION) 19 | 20 | dists: c_init.c config.c config.h version.h keymod.h keymap.c doc 21 | -mkdir quirc_$(VERSION).src 22 | for p in $$(ls); do cp $$p quirc_$(VERSION).src/$$p; done; 23 | cp -r dist quirc_$(VERSION).src/dist 24 | -rm quirc_$(VERSION).src/*.tar.gz 25 | rm quirc_$(VERSION).src/*.o 26 | rm quirc_$(VERSION).src/quirc 27 | rm quirc_$(VERSION).src/genconfig 28 | rm quirc_$(VERSION).src/genkeymap 29 | mv quirc_$(VERSION).src/distMakefile quirc_$(VERSION).src/Makefile 30 | tar -czf quirc_$(VERSION).src.tar.gz quirc_$(VERSION).src/ 31 | rm -r quirc_$(VERSION).src 32 | 33 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | ****** quIRC: readme ****** 3 | ** quIRC is a lightweight terminal-based IRC client. It is written in C. ** 4 | ***** Contents ***** 5 | * Foreword 6 | * Further_Help 7 | * Commands 8 | * Configuration 9 | * Input_Controls 10 | * Other_Controls 11 | * Keymapping 12 | * Tab_strip 13 | * Troubleshooting 14 | ***** Foreword ***** 15 | I like to engage with my user population. I welcome bug reports, feature 16 | requests, patches, and even undirected rambling musings about what direction 17 | quIRC should take in the future. If you use (or are considering using) quIRC, 18 | please drop into #quirc on irc.newnet.net for a chat. 19 | ***** Further Help ***** 20 | If this file doesn't answer your question, you can find help from several other 21 | places. 22 | First port of call is the website, http://jttlov.no-ip.org. 23 | Then there's the github page, http://github.com/ec429/quIRC. 24 | Or try our IRC channel, #quirc on irc.newnet.net (if your problem doesn't 25 | prevent you from joining us there!) 26 | ***** Commands ***** 27 | All commands are prefixed with a '/'. Anything else is a message to be sent to 28 | the channel (or nick if current tab is a private messaging tab). 29 | /help [cmd] 30 | Show on-line help for command /cmd. If cmd is omitted, list all commands. 31 | /server url 32 | Connects to the given server (will open in a new tab). 33 | /nick nickname 34 | Sets your nickname. Scope is server-wide (or, in (status) tab, sets 35 | default for new /servers). 36 | /join channel [key] 37 | Joins the given channel (will open in a new tab). 38 | /me action 39 | Sends an 'action' to the channel. 40 | /msg [-n] recipient [message] 41 | Private message; sends the message to the given recipient. Will open a 42 | private messaging tab if one is not already open for that recipient 43 | (suppress with -n). If message is omitted, just opens the tab. 44 | /amsg message 45 | Send message to all attached channels on this server. 46 | /ping [recipient] 47 | Send a CTCP ping to recipient. If used in a private messaging tab, 48 | recipient may be omitted. 49 | /topic [message] 50 | Sets or gets the channel topic. 51 | /mode [nick [{+|-}mode]] 52 | Without a {+|-}mode, queries your mode, or the mode of nick if given. 53 | /mode nick +mode sets mode mode on nick; /mode nick -mode unsets it. 54 | /away [msg] 55 | /unaway 56 | /away - 57 | Indicates that you are away, by storing an away message (msg) on the 58 | server; it will be sent to anyone who private-messages you. /away - or / 59 | unaway removes any away message you have set. 60 | /afk [msg] 61 | /afk - 62 | Indicates that you are 'away from keyboard' ('afk') by appending |afk (or 63 | |msg) to your nick. /afk - removes any such indications. 64 | /set option [value] 65 | Sets configuration options; the options are analogous to those in .quirc, 66 | with one or two extras. 67 | * The options 'height' and 'width' are used to tell quIRC how many 68 | rows and columns your terminal has (for cursor-positioning). quIRC 69 | will try to deduce these values (from environment variables $LINES 70 | and $COLUMNS, using defaults of 80x24 if these env vars are not 71 | found), but you can override those guesses here or on the command 72 | line. 73 | * The option 'conf' can be /set for each channel (the rc and cmdline 74 | versions control the default setting). In Conference Mode (/set 75 | conf +), joins, parts, quits, nick changes etc. will not be 76 | displayed. This can be useful in busy channels. 77 | * The options 'uname', 'fname' and 'pass' are analogous to the -- 78 | uname, --fname and --pass commandline options. 79 | For full details see config_ref.htm. 80 | /log logtype file 81 | /log - 82 | Starts logging the current buffer to file (relative to ~/.quirc/), using 83 | a format specified by logtype. This may be plain (human-readable output 84 | similar to normal display) or symbolic (machine-readable, though still 85 | textual, format). 86 | /log - disables logging for the current buffer. 87 | /ignore [-ipd] nick[!user[@host]] 88 | /ignore [-ipd] -r regex 89 | /ignore -l 90 | Adds a nick to your "ignore list", thus preventing messages from that 91 | nick from appearing. 92 | -i 93 | Match will be case-insensitive 94 | -p 95 | Will also ignore matching private messages 96 | -d 97 | Instead of adding a rule, remove all rules matching the given 98 | nick!user@host 99 | -r 100 | Supply a regular expression match. The usual form (without -r) 101 | generates the regular expression ^nick[_~]*!user@host$; if user or 102 | host are not given (or begin with *), they are replaced with [^!@]* 103 | resp. [^@] 104 | -l 105 | Instead of adding a rule, list rules which apply to this tab 106 | /rejoin [key] 107 | Rejoins a channel tab which is dead (eg after losing server connection). 108 | If key is not specified, the previous key (if any) will be used. 109 | /reconnect 110 | Reconnects to a server from which you have become disconnected. 111 | /realsname 112 | Displays the hostname of the server you are connected to. 113 | /grep [pattern] 114 | Searches backwards through the current tab's backscroll for the regular 115 | expression pattern, and scrolls to a match if found. If pattern is 116 | omitted, uses your nick (note that this may behave oddly if your nick 117 | contains regex metacharacters). Uses POSIX Extended Regular Expression 118 | syntax. 119 | /part channel 120 | /leave channel 121 | Leaves (departs) the given channel. 122 | /disconnect [message] 123 | Disconnects from the server, optionally sending a 'quit message'. 124 | /close 125 | Closes the current tab. In addition, has an effect which depends on the 126 | tab type: 127 | (status) 128 | Disconnects from all servers and quits quIRC 129 | {server} 130 | Disconnects from the server and all channels on that server 131 | [channel] 132 | Leaves the channel 133 | /quit [message] 134 | /exit [message] 135 | Quits quIRC, optionally sending a 'quit message' to the server. 136 | /cmd command 137 | Allows you to send a raw command to the server; not recommended. 138 | /tab n 139 | Switches to tab number n; (status) is tab 0. 140 | /left 141 | /right 142 | Swaps the current tab with the tab on its left (or right); tab 0 (status) 143 | cannot be moved. 144 | /sort 145 | Sorts the tab list into an intuitive order (grouping channels after their 146 | parent servers; maintaining the order of the servers and of each server's 147 | channels). 148 | //[msg] 149 | Sends /msg to the channel. 150 | ***** Configuration ***** 151 | quIRC can be configured through an "rc file" as follows. (For a compact 152 | reference table see config_ref.htm) 153 | In your home directory (/home/username), create a folder called ".quirc", a 154 | file within that folder called "rc", and open it in your editor. 155 | In this file you can set servers, nick and channels to automatically use: 156 | nick global-nickname 157 | pass global-password 158 | ignore -options global-ignore 159 | server url 160 | *chan channel-on-that-server 161 | >log logtype-for-that-channel logfile-for-that-channel 162 | *nick nick-on-that-server 163 | *pass password-for-that-server 164 | *ignore -options ignore-on-that-server 165 | where the options to [*]ignore are those for the /ignore command (except for l 166 | and d), and must not be separated by whitespace. You can however combine 167 | options, like -ipr. The options may not be omitted; if none are required, use - 168 | (dash). 169 | The logfile (for >log) is relative to ~/.quirc/, as with the /log command. 170 | Set the maximum length of nick that will be displayed, with lines 171 | mnln maxnicklen 172 | You can also set mirc-colour-compatibility, with 173 | mcc mcc-level 174 | where 0 doesn't scan for mirc-colours, 1 silently strips them out, and 2 175 | displays the appropriate colour. The default is mcc 1. 176 | The size of each scrollback buffer, in lines, can be set with 177 | buf buf-lines 178 | the default being 1024. Larger values will, of course, increase memory 179 | consumption. 180 | You can turn on a few display options too; 181 | fwc 182 | hts 183 | tsb 184 | quiet 185 | will turn on Full-Width-Colour (makes coloured backgrounds for lines (eg. /me) 186 | run all the way across the terminal), Highlight-Tab-Strip (gives the tab strip 187 | a magenta background, to make it more visible), Top-Status-Bar (uses the top 188 | line of the terminal for some status information), and Quiet Mode (many 189 | informational messages, including unrecognised IRC traffic, are suppressed). To 190 | turn them off prefix them with no-, like 191 | no-hts 192 | By default fwc, hts and quiet are turned off; tsb is turned on. 193 | You can control the timestamping settings: 194 | ts ts-level 195 | utc 196 | its 197 | The timestamping levels are: 198 | ts-level Meaning 199 | 0 No timestamps will be displayed 200 | 1 Display timestamps in the form [HH:MM] 201 | 2 Display timestamps in the form [HH:MM:SS] 202 | 3 Display timestamps in the form [HH:MM:SS +hhmm], where +hhmm is the 203 | time zone offset 204 | 4 Display timestamps in the form [Day. HH:MM:SS], where Day is the day 205 | of the week 206 | 5 Display timestamps in the form [Day. HH:MM:SS +hhmm] 207 | 6 Pointlessly, display timestamps as seconds since the Epoch 208 | If utc is enabled, timestamps will be displayed as UTC instead of local time, 209 | and +hhmm will be replaced by UTC. 210 | If its is enabled, a clock will be displayed at the left-hand end of the input 211 | line, using the format specified by the current ts setting. 212 | The default setting is ts-level 1, no-utc, no-its. 213 | These settings and others can be overridden at runtime with commandline 214 | options. For details run "quirc --help". 215 | You can also customise the colours quIRC uses. A custom colour line starts with 216 | '%', followed optionally by 'S' or 'R' (only use this colour when Sending or 217 | Receiving respectively), followed by an identifier, then space or tab, then 218 | four space-separated numbers. Like this: 219 | %[S|R]identfore back hi ul 220 | Fore and Back set the foreground and background colours (8 colours each, red=4 221 | green=2 blue=1, add for mixtures, eg white=7). Hi sets bright, Ul sets 222 | underline; both are true if nonzero. 223 | ident can be any of 224 | msg 225 | Ordinary messages 226 | notice 227 | Notices 228 | join 229 | Channel-Join notifications 230 | part 231 | Channel-Leave notifications 232 | quit 233 | Quit-messages 234 | nick 235 | Nick-change notifications 236 | act 237 | Actions ('/me does something') 238 | status 239 | status messages 240 | err 241 | error messages 242 | unk 243 | Unknown commands (splurged to output) 244 | unn 245 | Unknown numerics (splurged to output) 246 | ***** Input controls ***** 247 | These are based, broadly, on bash's controls. 248 | Up/Down/PgUp/PgDn 249 | Read lines from the input history. History is local to the current tab. 250 | Left/Right 251 | Move the cursor within the current input line. 252 | Home 253 | Ctrl-A 254 | Move the cursor to the beginning of the input line. 255 | End 256 | Ctrl-E 257 | Move the cursor to the end of the input line. 258 | Ctrl-C 259 | Clear the input line. 260 | Ctrl-X 261 | Clear everything to the left of the cursor. 262 | Ctrl-K 263 | Clear everything to the right of the cursor. 264 | Tab 265 | Ctrl-I 266 | Autocomplete the nickname or command to the left of the cursor. 267 | Ctrl-H 268 | Backspace 269 | Delete the character to the left of the cursor. 270 | Ctrl-W 271 | Delete the word to the left of the cursor. Words are delimited only by 272 | spaces. 273 | ***** Other controls ***** 274 | Ctrl-Up 275 | Ctrl-Down 276 | Scroll the current buffer a line at a time. 277 | Ctrl-PgUp 278 | Ctrl-PgDn 279 | Scroll the current buffer a page at a time. 280 | Ctrl-Home 281 | Scroll to the top of the current buffer. 282 | Ctrl-End 283 | Scroll to the bottom of the current buffer. 284 | Ctrl-left 285 | Ctrl-right 286 | Change tab. 287 | F1 through F12 288 | Equivalent to /tab #, except that F12 is /tab 0. 289 | ***** Keymapping ***** 290 | quIRC's mapping of function and other special keys is derived from terminfo, so 291 | it's important to set your $TERM correctly. For a few key chords that don't 292 | have terminfo bindings, it defaults to a VT100 mapping, such as used by xterm 293 | and rxvt. However, this mapping can be changed with a file /home/ 294 | username/.quirc/keys. 295 | To change key mappings, add lines of the form MODNAME hex. The MODNAMEs 296 | expected are those which appear in the "keys" file in the quIRC source 297 | directory. 298 | The prefix 'C' refers to Ctrl, 'S' to Shift, and 'A' to Alt. 299 | If, for instance, your terminal sends Ctrl-Left as ^[OD, you would add the 300 | following line: 301 | CLEFT 1b4f44 302 | Similarly, if your terminal sends backspace as ^H, you would add: 303 | BS 08 304 | You can find out what sequences your terminal sends for various keys by running 305 | a hex-dump program such as xxd or hd, pressing the key, and noting the output. 306 | (You will usually need to press first the key, then enter/return, then ^D) 307 | ***** Tab strip ***** 308 | The strip of tabs along the bottom of the screen has various indicators. 309 | The parens bracketing the name tell you what kind of tab it is: 310 | () 311 | status 312 | {} 313 | server 314 | [] 315 | channel 316 | <> 317 | nick (private messaging) 318 | The background colour is green for the current tab, and blue for the current 319 | tab's parent server (if applicable). 320 | The foreground colour of tabs other than the current tab will turn red to 321 | indicate that there are new messages on that tab (and flash cyan if a message 322 | contains your nick). If a tab is 'dead' (eg. disconnected from server), it 323 | turns yellow. 324 | ***** Troubleshooting ***** 325 | I get several copies of the status line scrolling up my screen! 326 | One possible cause of this is that your terminal size is not what quIRC 327 | thinks it is. You may need to /set height and width, or export the 328 | environment variables $LINES and $COLUMNS from your shell (typically you 329 | would also add this to your shell's .rc file), or as a last resort resize 330 | your terminal to 80x24. 331 | Another possibility is that your terminal emulator isn't recognising 332 | terminal escape sequences (which quIRC uses heavily for eg. cursor 333 | positioning). For details see your terminal emulator's documentation; it 334 | is known that xterm works out-of-the-box. In general quIRC relies on your 335 | system's terminfo database; if this is inaccurate, or your $TERM is set 336 | incorrectly, quIRC will be sending the wrong escape sequences. 337 | The control keys/cursor keys/function keys don't do anything! 338 | This probably means your terminal isn't sending VT100 (ANSI) escape 339 | sequences, but rather some other set of sequences. See the section 340 | "Keymapping", above. 341 | I can't build it! My libc doesn't support getaddrinfo_a()! 342 | You need to disable ASYNCH_NL; cp dist/config.mak . (unless you already 343 | have a config.mak) and then uncomment the relevant lines. 344 | This should disable asynchronous name lookups, instead using code which 345 | will block while performing the DNS lookup. 346 | I can't build it! My compiler complains about %jd and intmax_t! 347 | This is a known bug in some compiler/libc combinations; to work around it 348 | you need to define INTMAX_BUG to 1. cp dist/config.mak . (unless you 349 | already have a config.mak) and then uncomment the relevant lines. 350 | This should work around the bug by using %lld and (long long int) instead 351 | of %jd and (intmax_t). Since what we're actually printing is a time_t, 352 | this ought to work. 353 | -------------------------------------------------------------------------------- /bits.c: -------------------------------------------------------------------------------- 1 | /* 2 | quIRC - simple terminal-based IRC client 3 | Copyright (C) 2010-13 Edward Cree 4 | 5 | See quirc.c for license information 6 | bits: general helper functions (chiefly string manipulation) 7 | */ 8 | 9 | #include "bits.h" 10 | #include "strbuf.h" 11 | #include "ttyesc.h" 12 | #include "config.h" 13 | 14 | int wordline(const char *msg, unsigned int x, char **out, size_t *l, size_t *i, colour lc) 15 | { 16 | if(!msg) return(x); 17 | unsigned int tabx=x; 18 | if(tabx*2>width) 19 | tabx=8; 20 | size_t l2,i2; 21 | char *word; 22 | const char *ptr=msg; 23 | colour cc=lc; // current colour 24 | bool bold=false, underline=false, reverse=false; // format statuses (for when mcc==2) 25 | s_setcolour(cc, out, l, i); 26 | while(*ptr) 27 | { 28 | switch(*ptr) 29 | { 30 | case ' ': 31 | if(++x bold 90 | consumed=1; 91 | if(mirc_colour_compat==2) 92 | { 93 | cc.hi=(bold=!bold)||lc.hi; 94 | s_setcolour(cc, &word, &l2, &i2); 95 | } 96 | break; 97 | case 3: // ^C -> text colour 98 | if(sscanf(ptr, "\003%2d,%2d%zn", &fore, &back, &bytes)==2) // ^CNN,MM -> fore=N, back=M 99 | { 100 | consumed=bytes; 101 | if(mirc_colour_compat==2) 102 | { 103 | cc=reverse_colours(c_mirc(fore, back), reverse); 104 | cc.hi=bold||lc.hi; 105 | cc.ul=underline||lc.ul; 106 | s_setcolour(cc, &word, &l2, &i2); 107 | } 108 | } 109 | else if(sscanf(ptr, "\003%2d%zn", &fore, &bytes)==1) // ^CNN -> fore=N, back=default 110 | { 111 | consumed=bytes; 112 | if(mirc_colour_compat==2) 113 | { 114 | cc=c_mirc(fore, 1); 115 | cc.back=lc.back; 116 | cc=reverse_colours(cc, reverse); 117 | cc.hi=bold||lc.hi; 118 | cc.ul=underline||lc.ul; 119 | s_setcolour(cc, &word, &l2, &i2); 120 | } 121 | } 122 | else // ^C -> clear colours 123 | { 124 | consumed=1; 125 | if(mirc_colour_compat==2) 126 | { 127 | cc=reverse_colours(lc, reverse); 128 | cc.hi=bold||lc.hi; 129 | cc.ul=underline||lc.ul; 130 | s_setcolour(cc, &word, &l2, &i2); 131 | } 132 | } 133 | break; 134 | case 15: // ^O -> ordinary, clear all attributes 135 | consumed=1; 136 | if(mirc_colour_compat==2) 137 | { 138 | bold=underline=reverse=false; 139 | s_setcolour(cc=lc, &word, &l2, &i2); 140 | } 141 | break; 142 | case 18: // ^R -> reverse video 143 | consumed=1; 144 | if(mirc_colour_compat==2) 145 | { 146 | reverse=!reverse; 147 | s_setcolour(cc=reverse_colours(cc, true), &word, &l2, &i2); 148 | } 149 | break; 150 | case 21: // ^U -> underline 151 | consumed=1; 152 | if(mirc_colour_compat==2) 153 | { 154 | cc.ul=(underline=!underline)||lc.ul; 155 | s_setcolour(cc, &word, &l2, &i2); 156 | } 157 | break; 158 | default: 159 | break; 160 | } 161 | } 162 | if(!consumed) 163 | { 164 | if(*(unsigned char *)ptr<0x20) // TODO add UTF-8 decoding and replace this with isprint 165 | { 166 | consumed=1; 167 | char buf[16]; 168 | snprintf(buf, 16, "\\%03o", *(unsigned char *)ptr); 169 | append_str(&word, &l2, &i2, buf); 170 | wdlen+=strlen(buf); 171 | } 172 | } 173 | ptr+=consumed; 174 | if(!consumed) 175 | { 176 | append_char(&word, &l2, &i2, *ptr++); 177 | wdlen++; 178 | } 179 | if(wdlen+tabx>=width) 180 | { 181 | ptr--; 182 | break; 183 | } 184 | } 185 | if(wdlen+x>=width) 186 | { 187 | append_char(out, l, i, '\n'); 188 | s_setcolour(lc, out, l, i); 189 | for(x=0;xlen) 208 | { 209 | char *b=*buf; 210 | if(*b=='~') b++; 211 | char *rv=(char *)malloc(len+1); 212 | if(strlen(b)>len) 213 | { 214 | int right=(len-1)/2; 215 | int left=(len-1)-right; 216 | sprintf(rv, "%.*s~%s", left, b, b+strlen(b)-right); 217 | } 218 | else 219 | { 220 | strcpy(rv, b); 221 | } 222 | free(*buf); 223 | *buf=rv; 224 | } 225 | } 226 | 227 | void scrush(char **buf, unsigned int len) 228 | { 229 | if(strlen(*buf)>len) 230 | { 231 | if(strncmp(*buf, "irc.", 4)==0) 232 | { 233 | char *nb=(char *)malloc(strlen(*buf)); 234 | nb[0]='~'; 235 | strcpy(nb+1, (*buf)+4); 236 | free(*buf); 237 | scrush(&nb, len); 238 | *buf=nb; 239 | } 240 | else 241 | { 242 | char *dot=strchr(*buf, '.'); 243 | char *n=dot; 244 | while(n) 245 | { 246 | dot=n; 247 | n=strchr(n+1, '.'); 248 | } 249 | if(dot) *dot=0; 250 | crush(buf, len); 251 | } 252 | } 253 | } 254 | 255 | char *mktag(char *fmt, char *from) 256 | { 257 | char *rv=NULL; 258 | if(strlen(from)<=maxnlen) 259 | { 260 | size_t n=strlen(fmt)+maxnlen; 261 | rv=malloc(n); 262 | if(rv) 263 | { 264 | memset(rv, ' ', maxnlen+strlen(fmt)-1); 265 | ssize_t off=maxnlen-strlen(from); 266 | snprintf(rv+off, n-off, fmt, from); 267 | } 268 | } 269 | else 270 | { 271 | size_t n=strlen(fmt)+strlen(from); 272 | rv=malloc(n); 273 | if(rv) 274 | snprintf(rv, n, fmt, from); 275 | } 276 | return(rv); 277 | } 278 | 279 | #ifdef NEED_STRNDUP 280 | char *strndup(const char *s, size_t size) 281 | { 282 | char *rv=(char *)malloc(size+1); 283 | if(rv==NULL) return(NULL); 284 | strncpy(rv, s, size); 285 | rv[size]=0; 286 | return(rv); 287 | } 288 | #endif 289 | -------------------------------------------------------------------------------- /bits.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | quIRC - simple terminal-based IRC client 5 | Copyright (C) 2010-13 Edward Cree 6 | 7 | See quirc.c for license information 8 | bits: general helper functions (chiefly string manipulation) 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include "colour.h" 15 | 16 | // helper fn macros 17 | #define max(a,b) ((a)>(b)?(a):(b)) 18 | #define min(a,b) ((a)<(b)?(a):(b)) 19 | 20 | #ifdef NEED_STRNDUP 21 | char *strndup(const char *s, size_t size); 22 | #endif 23 | 24 | int wordline(const char *msg, unsigned int x, char **out, size_t *l, size_t *i, colour lc); // prepares a string for printing, breaking lines in between words; returns new x 25 | void crush(char **buf, unsigned int len); 26 | void scrush(char **buf, unsigned int len); 27 | char *mktag(char *fmt, char *from); 28 | -------------------------------------------------------------------------------- /buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | quIRC - simple terminal-based IRC client 5 | Copyright (C) 2010-13 Edward Cree 6 | 7 | See quirc.c for license information 8 | buffer: multiple-buffer control & buffer rendering 9 | */ 10 | 11 | #ifndef _GNU_SOURCE 12 | #define _GNU_SOURCE 13 | #endif 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "colour.h" 21 | #include "config.h" 22 | #include "input.h" 23 | #include "irc.h" 24 | #include "types.h" 25 | 26 | #define SERVER(buf) (bufs[bufs[buf].server]) // server of a buf 27 | #define LIVE(buf) (bufs[buf].live && SERVER(buf).live) // Check liveness 28 | #define STAMP_LEN 40 29 | 30 | typedef struct _buf 31 | { 32 | btype type; 33 | char *bname; // Buffer display name: "status" or serverloc(or NETWORK) or #channel or @nick (resp. types) 34 | char *serverloc; // address of server roundrobin 35 | char *realsname; // real server name (not the same as bname) 36 | name *nlist; // only used for CHANNELs and PRIVATE: linked-list of nicks 37 | name *us; // pointer to our entry in the nlist 38 | name *ilist; // ignore-list 39 | int handle; // used for SERVER: file descriptor 40 | int server; // used by CHANNELs and PRIVATE to denote their 'parent' server. In SERVER||STATUS, points to self. Is an offset into 'bufs' 41 | char *nick; // used for SERVER: user's nick on this server 42 | char *topic; // used for CHANNELs 43 | FILE *logf; 44 | logtype logt; 45 | int nlines; // number of lines allocated 46 | int ptr; // pointer to current unproc line 47 | int scroll; // unproc line of screen bottom (which is one physical line below the last displayed text) 48 | int ascroll; // physical line within [scroll] 49 | bool *ls; // array of whether line was sent or rxed 50 | prio *lq; // array of priority levels for lines 51 | mtype *lm; // array of message types for lines 52 | char *lp; // array of prefix chars (0 for use default) 53 | char **lt; // array of (unprocessed) text for lines 54 | char **ltag; // array of (unprocessed, uncrushed) tag text for lines 55 | time_t *ts; // array of timestamps for unproc lines 56 | bool filled; // buffer has filled up and looped? (the buffers are circular in nature) 57 | bool dirty; // processed lines are out of date? (TODO: make this indicate /which/ are out of date and only re-render those) 58 | int *lpl; // count of processed lines for each line 59 | colour *lpc; // array of colours for each line 60 | char ***lpt; // array of processed lines for each line 61 | bool alert; // tab has new messages? 62 | int hi_alert; // high-level alert status: 0 = none; 1: on (if alert then flashing else single flash); 2: off (flashing) 63 | int ping; // ping/idleness status (SERVER) 64 | time_t last; // when was the last RX? (SERVER) 65 | bool namreply; // tab is in the middle of reading a list of NAMES replies (RPL_NAMREPLY)? 66 | bool live; // tab is connected? when checking in a CHANNEL, remember to AND it with the parent's live (use LIVE(buf), defined further up this file) 67 | bool conninpr; // connection in progress? (SERVER only) 68 | ibuffer input; // input history 69 | cmap casemapping; // the SERVER's value is authoritative; the CHANNEL's value is ignored. STATUS's value is irrelevant. Set by ISUPPORT 70 | unsigned int npfx;// the SERVER's value denotes the available list (set by ISUPPORT); the CHANNEL's value lists the modes set on that channel (letter only) 71 | prefix *prefixes; // ^^ 72 | servlist * autoent; // if this was opened by autoconnect(), this is filled in to point to the server's servlist entry 73 | bool conf; // Conference Mode (hides joins, parts, quits, and /nicks) 74 | char *key; // channel key 75 | char *lastkey; // last key passed to /rejoin; copied to .key if rejoin succeeds 76 | } 77 | buffer; 78 | 79 | extern int nbufs; 80 | extern int cbuf; 81 | extern buffer *bufs; 82 | 83 | typedef struct 84 | { 85 | int nlines; 86 | int ptr; 87 | bool filled; 88 | char **lt; 89 | char **ltag; 90 | mtype *lm; 91 | time_t *ts; 92 | bool loop; 93 | int errs; 94 | } 95 | ring; 96 | 97 | extern ring s_buf, d_buf; 98 | 99 | int init_ring(ring *r); 100 | int add_to_ring(ring *r, mtype lm, const char *lt, const char *ltag); 101 | int atr_failsafe(ring *r, mtype lm, const char *lt, const char *ltag); 102 | int free_ring(ring *r); 103 | int initialise_buffers(int buflines); 104 | int init_buffer(int buf, btype type, const char *bname, int nlines); 105 | int free_buffer(int buf); 106 | int add_to_buffer(int buf, mtype lm, prio lq, char lp, bool ls, const char *lt, const char *ltag); 107 | int mark_buffer_dirty(int buf); 108 | int redraw_buffer(void); 109 | int render_buffer(int buf); 110 | int render_line(int buf, int uline); 111 | int e_buf_print(int buf, mtype lm, message pkt, const char *lead); 112 | int transfer_ring(ring *r, prio lq); 113 | int push_ring(ring *r, prio lq); 114 | void in_update(iline inp); 115 | void titlebar(void); 116 | int findptab(int b, const char *src); 117 | int makeptab(int b, const char *src); 118 | void timestamp(char stamp[STAMP_LEN], time_t t); 119 | bool isutf8(const char *src, size_t *len); // determine if a string starts with a non-ASCII UTF8 character; if so, give its length (in bytes) in len. If this function returns false, the value of *len is undefined 120 | -------------------------------------------------------------------------------- /c_init.awk: -------------------------------------------------------------------------------- 1 | # quIRC - simple terminal-based IRC client 2 | # Copyright (C) 2010-13 Edward Cree 3 | # 4 | # See quirc.c for license information 5 | # c_init.awk: colour initialiser 6 | 7 | # generate the colours initialisation c_init.c (used in colour.c) from 8 | # colour.d, the file of colour definitions 9 | # in records "S msg 7 0 0 1" 10 | # $1=type $2=name $3=fore $4=back $5=hi $6=ul 11 | BEGIN {FS=" ";print "colour curr;"} 12 | /^\043/ {next} 13 | /^%/ {printf "curr.fore=%d;curr.back=%d;curr.hi=%d;curr.ul=%d; c_%s[0]=curr;c_%s[1]=curr;\n", $3, $4, $5, $6, $2, $2} 14 | /^S/ {printf "curr.fore=%d;curr.back=%d;curr.hi=%d;curr.ul=%d; c_%s[0]=curr;\n", $3, $4, $5, $6, $2} 15 | /^R/ {printf "curr.fore=%d;curr.back=%d;curr.hi=%d;curr.ul=%d; c_%s[1]=curr;\n", $3, $4, $5, $6, $2} 16 | /^!/ {printf "curr.fore=%d;curr.back=%d;curr.hi=%d;curr.ul=%d; c_%s=curr;\n", $3, $4, $5, $6, $2} 17 | -------------------------------------------------------------------------------- /cmd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "buffer.h" 3 | #include "names.h" 4 | 5 | typedef int (*cmd_func)(char *cmd, char *args, char **qmsg, fd_set *master, int *fdmax, int flag); 6 | 7 | struct cmd_t { 8 | cmd_func func; 9 | char *name; 10 | char *help; 11 | }; 12 | 13 | //macro so that we can easily change function arguments 14 | //when we inevitably find out that we need more than we have. 15 | //also going to be using this form a lot. -Russell 16 | //flag is used to pass information between commands. see 17 | //example in afk and nick. 18 | #define CMD_FUN(NAME)\ 19 | static int _handle_##NAME(char *cmd, char *args, char **qmsg, fd_set *master, int *fdmax, int flag) 20 | #define CMD_FNAME(NAME)\ 21 | _handle_##NAME 22 | 23 | int init_cmds(); 24 | int call_cmd(char *cmd, char *args, char **qmsg, fd_set * master, int *fdmax); 25 | 26 | // Only singly-linked, lacks full information. 27 | // Valid fields are ->data, ->next 28 | extern name *cmds_as_nlist; 29 | -------------------------------------------------------------------------------- /colour.c: -------------------------------------------------------------------------------- 1 | /* 2 | quIRC - simple terminal-based IRC client 3 | Copyright (C) 2010-13 Edward Cree 4 | 5 | See quirc.c for license information 6 | colour: defined colours & mirc-colour-compat 7 | */ 8 | 9 | #include "colour.h" 10 | #include "ttyesc.h" 11 | 12 | colour c_list[19]; 13 | 14 | inline bool eq_colour(colour a, colour b) 15 | { 16 | if(a.fore!=b.fore) return(false); 17 | if(a.back!=b.back) return(false); 18 | if(a.hi!=b.hi) return(false); 19 | if(a.ul!=b.ul) return(false); 20 | return(true); 21 | } 22 | 23 | int setcolour(colour c) 24 | { 25 | return(setcol(c.fore, c.back, c.hi, c.ul)); 26 | } 27 | 28 | int s_setcolour(colour c, char **rv, size_t *l, size_t *i) 29 | { 30 | return(s_setcol(c.fore, c.back, c.hi, c.ul, rv, l, i)); 31 | } 32 | 33 | colour c_mirc(int fore, int back) 34 | { 35 | /*mIRC colours: 36 | 0=white, 1=black, 2=dk blue, 3=green, 4=red, 5=maroon, 6=purple, 7=orange, 8=yellow, 9=lt green, 10=teal, 11=cyan, 12=blue, 13=fuchsia, 14=dk gray, 15=lt gray 37 | converted: 38 | 0->7, 1->0, 2->4, 3->2, 4->1, 5->1, 6->5, 7->3, 8->3, 9->2, 10->6, 11->6, 12->4, 13->5, 14->0(7), 15->7 39 | */ 40 | const int col[16]={7, 0, 4, 2, 1, 1, 5, 3, 3, 2, 6, 6, 4, 5, 0, 7}; 41 | colour rv={7, 0, false, false}; 42 | if((fore>=0) && (fore<16)) 43 | { 44 | rv.fore=col[fore]; 45 | } 46 | if((back>=0) && (back<16)) 47 | { 48 | rv.back=col[back]; 49 | } 50 | if(rv.fore==rv.back) 51 | { 52 | rv.fore=7-rv.back; 53 | } 54 | return(rv); 55 | } 56 | 57 | colour reverse_colours(colour c, bool reverse) 58 | { 59 | return(reverse?(colour){c.back, c.fore, c.hi, c.ul}:c); 60 | } 61 | 62 | int c_init(void) 63 | { 64 | #include "c_init.c" 65 | return(0); 66 | } 67 | -------------------------------------------------------------------------------- /colour.def: -------------------------------------------------------------------------------- 1 | # defaults for the colours (used to generate c_init.c) 2 | # format is similar to the colour settings in .quirc, but not identical 3 | S msg 7 0 0 1 4 | R msg 7 0 0 0 5 | % notice 7 0 1 0 6 | % join 2 0 1 0 7 | % part 6 0 1 0 8 | % quit 3 0 1 0 9 | % nick 4 0 1 0 10 | S actn 0 3 0 1 11 | R actn 0 3 0 0 12 | ! status 5 0 0 0 13 | ! err 1 0 1 0 14 | ! unk 3 4 0 0 15 | ! unn 1 6 0 1 16 | -------------------------------------------------------------------------------- /colour.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | quIRC - simple terminal-based IRC client 4 | Copyright (C) 2010-13 Edward Cree 5 | 6 | See quirc.c for license information 7 | colour: defined colours & mirc-colour-compat 8 | */ 9 | 10 | #include 11 | #include 12 | 13 | typedef struct 14 | { 15 | int fore; 16 | int back; 17 | bool hi; 18 | bool ul; 19 | } 20 | colour; 21 | 22 | bool eq_colour(colour, colour); 23 | int setcolour(colour); // wrapper for setcol 24 | int s_setcolour(colour, char **, size_t *, size_t *); // wrapper for s_setcol 25 | colour c_mirc(int, int); 26 | colour reverse_colours(colour, bool); 27 | int c_init(void); 28 | 29 | extern colour c_list[19]; 30 | 31 | // These should really be generated procedurally 32 | #define c_msg c_list 33 | #define c_notice (c_list+2) 34 | #define c_join (c_list+4) 35 | #define c_part (c_list+6) 36 | #define c_quit (c_list+8) 37 | #define c_nick (c_list+10) 38 | #define c_actn (c_list+12) 39 | #define c_status c_list[14] 40 | #define c_err c_list[15] 41 | #define c_unk c_list[16] 42 | #define c_unn c_list[17] 43 | -------------------------------------------------------------------------------- /complete.c: -------------------------------------------------------------------------------- 1 | /* 2 | quIRC - simple terminal-based IRC client 3 | Copyright (C) 2010-16 Edward Cree 4 | 5 | See quirc.c for license information 6 | complete: tab completion 7 | */ 8 | 9 | #include "complete.h" 10 | #include "strbuf.h" 11 | #include "cmd.h" 12 | 13 | size_t find_word(const iline *inp, size_t right) 14 | { 15 | size_t sp = right; 16 | 17 | if (sp) 18 | sp--; 19 | while (sp > 0 && !strchr (" \t", inp->left.data[sp - 1])) 20 | sp--; 21 | return sp; 22 | } 23 | 24 | name *find_names(const char *word, size_t len, name *list, size_t *mlen_out) 25 | { 26 | name *curr = list, *found = NULL; 27 | size_t mlen = 0; 28 | 29 | while (curr) 30 | { 31 | if (!irc_strncasecmp(word, curr->data, len, bufs[cbuf].casemapping)) 32 | { 33 | name *old = found; 34 | n_add(&found, curr->data, bufs[cbuf].casemapping); 35 | if (old && (old->data)) 36 | { 37 | size_t i; 38 | for (i = 0; i < mlen; i++) 39 | if (irc_to_upper(curr->data[i], bufs[cbuf].casemapping) != 40 | irc_to_upper(old->data[i], bufs[cbuf].casemapping)) 41 | break; 42 | mlen = i; 43 | } 44 | else 45 | { 46 | mlen = strlen(curr->data); 47 | } 48 | } 49 | curr = curr->next; 50 | } 51 | if (mlen_out) 52 | *mlen_out = mlen; 53 | return found; 54 | } 55 | 56 | /* Add data to inp->left, duplicating any backslashes */ 57 | void i_add(iline *inp, size_t n, const char *data) 58 | { 59 | for (size_t i = 0; i < n; i++) 60 | { 61 | if (!data[i]) 62 | break; 63 | if (data[i] == '\\') 64 | append_char(&inp->left.data, &inp->left.l, 65 | &inp->left.i, data[i]); 66 | append_char(&inp->left.data, &inp->left.l, 67 | &inp->left.i, data[i]); 68 | } 69 | } 70 | 71 | void tab_complete(iline *inp) 72 | { 73 | size_t right = inp->left.i, left = find_word(inp, right); 74 | size_t len, mlen; 75 | const char *word; 76 | bool is_cmd = (!left) && right && (*inp->left.data == '/'); 77 | name *found, *list=bufs[cbuf].nlist; 78 | 79 | if (is_cmd) 80 | { 81 | left++; 82 | list = cmds_as_nlist; 83 | } 84 | 85 | len = right - left; 86 | word = inp->left.data + left; 87 | 88 | found = find_names(word, len, list, &mlen); 89 | 90 | if (found) 91 | { 92 | size_t count = 0; 93 | for (name *curr = found; curr; curr = curr->next) 94 | count++; 95 | if ((mlen > len) && (count > 1)) 96 | { 97 | for (size_t i = 0; i < len; i++) 98 | back_ichar(&inp->left); 99 | const char *p = found->data; 100 | i_add(inp, mlen, p); 101 | ttab = false; 102 | } 103 | else if ((count > 16) && !ttab) 104 | { 105 | add_to_buffer (cbuf, STA, NORMAL, 0, 106 | false, 107 | "Multiple matches (over 16; tab again to list)", 108 | "[tab] "); 109 | ttab = true; 110 | } 111 | else if (count > 1) 112 | { 113 | char *fmsg; 114 | size_t l, i; 115 | init_char(&fmsg, &l, &i); 116 | while (found) 117 | { 118 | append_str(&fmsg, &l, &i, found->data); 119 | found = found->next; 120 | if (--count) 121 | append_str(&fmsg, &l, &i, ", "); 122 | } 123 | if (!ttab) 124 | add_to_buffer(cbuf, STA, NORMAL, 0, false, 125 | "Multiple matches", "[tab] "); 126 | add_to_buffer(cbuf, STA, NORMAL, 0, false, fmsg, "[tab] "); 127 | free(fmsg); 128 | ttab = false; 129 | } 130 | else 131 | { 132 | for (size_t i = 0; i < len; i++) 133 | back_ichar(&inp->left); 134 | const char *p = found->data; 135 | i_add(inp, -1, p); 136 | if (!left) 137 | append_char(&inp->left.data, &inp->left.l, 138 | &inp->left.i, ':'); 139 | append_char(&inp->left.data, &inp->left.l, 140 | &inp->left.i, ' '); 141 | ttab = false; 142 | } 143 | } 144 | else 145 | { 146 | add_to_buffer(cbuf, STA, NORMAL, 0, false, 147 | is_cmd ? "No commands match" : "No nicks match", 148 | "[tab] "); 149 | } 150 | n_free(found); 151 | } 152 | -------------------------------------------------------------------------------- /complete.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | quIRC - simple terminal-based IRC client 5 | Copyright (C) 2010-16 Edward Cree 6 | 7 | See quirc.c for license information 8 | complete: tab completion 9 | */ 10 | 11 | #include "input.h" 12 | 13 | void tab_complete(iline *inp); 14 | -------------------------------------------------------------------------------- /config.c: -------------------------------------------------------------------------------- 1 | /* 2 | quIRC - simple terminal-based IRC client 3 | Copyright (C) 2010-13 Edward Cree 4 | 5 | See quirc.c for license information 6 | config: rc file and option parsing 7 | */ 8 | 9 | #include "config.h" 10 | #include "bits.h" 11 | #include "colour.h" 12 | #include "text.h" 13 | #include "version.h" 14 | #include "buffer.h" 15 | #include "strbuf.h" 16 | #include "ttyesc.h" 17 | 18 | #include "keymap.c" 19 | 20 | #include "config_globals.c" 21 | bool autojoin; 22 | char *username, *fname, *nick, *pass, *portno; 23 | bool defnick; 24 | servlist *servs; 25 | name *igns; 26 | char *version; 27 | unsigned int nkeys; 28 | keymod *kmap; 29 | 30 | void loadkeys(FILE *fp) 31 | { 32 | if(!fp) return; 33 | while(!feof(fp)) 34 | { 35 | char *line=fgetl(fp); 36 | if(line) 37 | { 38 | if((*line)&&(*line!='#')) 39 | { 40 | keymod new; 41 | new.name=strtok(line, " \t"); 42 | char *mod=strtok(NULL, " \t"); 43 | if(!mod) 44 | { 45 | char msg[32+strlen(new.name)]; 46 | sprintf(msg, "keys: missing mod in %s", new.name); 47 | atr_failsafe(&s_buf, ERR, msg, "init: "); 48 | free(line); 49 | continue; 50 | } 51 | off_t o=strlen(mod); 52 | if(o&1) 53 | { 54 | char msg[32+strlen(new.name)]; 55 | sprintf(msg, "keys: odd mod length in %s", new.name); 56 | atr_failsafe(&s_buf, ERR, msg, "init: "); 57 | free(line); 58 | continue; 59 | } 60 | new.mod=malloc((o>>1)+1); 61 | if(!new.mod) 62 | { 63 | char msg[32+strlen(new.name)]; 64 | sprintf(msg, "keys: malloc failure in %s", new.name); 65 | atr_failsafe(&s_buf, ERR, msg, "init: "); 66 | free(line); 67 | continue; 68 | } 69 | bool cont=false; 70 | for(int i=0;i>1]=c; 93 | } 94 | if(cont) continue; 95 | new.mod[o>>1]=0; 96 | bool match=false; 97 | for(unsigned int i=0;inext=servs; 257 | new->name=strdup(rest); 258 | new->nick=strdup(nick); 259 | new->portno=strdup(portno); 260 | new->pass=pass?strdup(pass):NULL; 261 | new->join=false; 262 | new->chans=NULL; 263 | new->igns=NULL; 264 | servs=new; 265 | } 266 | else if(servs && (strcmp(cmd, "*port")==0)) 267 | { 268 | free(servs->portno); 269 | servs->portno=strdup(rest); 270 | } 271 | else if(strcmp(cmd, "port")==0) 272 | { 273 | free(portno); 274 | portno=strdup(rest); 275 | } 276 | else if(strcmp(cmd, "pass")==0) 277 | { 278 | free(pass); 279 | pass=strdup(rest); 280 | } 281 | else if(servs && (strcmp(cmd, "*pass")==0)) 282 | { 283 | free(servs->pass); 284 | servs->pass=strdup(rest); 285 | } 286 | else if(strcmp(cmd, "uname")==0) 287 | { 288 | username=strdup(rest); 289 | if(defnick) 290 | { 291 | free(nick); 292 | nick=strdup(username); 293 | } 294 | } 295 | else if(strcmp(cmd, "fname")==0) 296 | { 297 | free(fname); 298 | fname=strdup(rest); 299 | } 300 | else if(servs && (strcmp(cmd, "*nick")==0)) 301 | { 302 | free(servs->nick); 303 | servs->nick=strdup(rest); 304 | } 305 | else if(strcmp(cmd, "nick")==0) 306 | { 307 | free(nick); 308 | nick=strdup(rest); 309 | defnick=false; 310 | } 311 | else if(strcmp(cmd, "ignore")==0) 312 | { 313 | char *sw=strtok(rest, " \t"); 314 | if(*sw!='-') 315 | { 316 | atr_failsafe(&s_buf, ERR, "rc: ignore: need options (use '-' for no options)", "init: "); 317 | nerrors++; 318 | } 319 | else 320 | { 321 | rest=strtok(NULL, ""); 322 | if(!rest) 323 | { 324 | atr_failsafe(&s_buf, ERR, "rc: ignore: need options (use '-' for no options)", "init: "); 325 | nerrors++; 326 | } 327 | else 328 | { 329 | bool icase=strchr(sw, 'i'); 330 | bool pms=strchr(sw, 'p'); 331 | bool regex=strchr(sw, 'r'); 332 | if(regex) 333 | { 334 | name *new=n_add(&igns, rest, RFC1459); 335 | if(new) 336 | { 337 | new->icase=icase; 338 | new->pms=pms; 339 | } 340 | } 341 | else 342 | { 343 | char *iusr=strtok(rest, "@"); 344 | char *ihst=strtok(NULL, ""); 345 | if((!iusr) || (*iusr==0) || (*iusr=='*')) 346 | iusr="[^@]*"; 347 | if((!ihst) || (*ihst==0) || (*ihst=='*')) 348 | ihst="[^@]*"; 349 | char expr[10+strlen(iusr)+strlen(ihst)]; 350 | sprintf(expr, "^%s[_~]*@%s$", iusr, ihst); 351 | name *new=n_add(&igns, expr, RFC1459); 352 | if(new) 353 | { 354 | new->icase=icase; 355 | new->pms=pms; 356 | } 357 | } 358 | } 359 | } 360 | } 361 | else if(servs && (strcmp(cmd, "*ignore")==0)) 362 | { 363 | char *sw=strtok(rest, " \t"); 364 | if(*sw!='-') 365 | { 366 | atr_failsafe(&s_buf, ERR, "rc: *ignore: need options (use '-' for no options)", "init: "); 367 | nerrors++; 368 | } 369 | else 370 | { 371 | rest=strtok(NULL, ""); 372 | if(!rest) 373 | { 374 | atr_failsafe(&s_buf, ERR, "rc: *ignore: need options (use '-' for no options)", "init: "); 375 | nerrors++; 376 | } 377 | else 378 | { 379 | bool icase=strchr(sw, 'i'); 380 | bool pms=strchr(sw, 'p'); 381 | bool regex=strchr(sw, 'r'); 382 | if(regex) 383 | { 384 | name *new=n_add(&servs->igns, rest, RFC1459); 385 | if(new) 386 | { 387 | new->icase=icase; 388 | new->pms=pms; 389 | } 390 | } 391 | else 392 | { 393 | char *isrc,*iusr,*ihst; 394 | prefix_split(rest, &isrc, &iusr, &ihst); 395 | if((!isrc) || (*isrc==0) || (*isrc=='*')) 396 | isrc="[^!@]*"; 397 | if((!iusr) || (*iusr==0) || (*iusr=='*')) 398 | iusr="[^!@]*"; 399 | if((!ihst) || (*ihst==0) || (*ihst=='*')) 400 | ihst="[^@]*"; 401 | char expr[16+strlen(isrc)+strlen(iusr)+strlen(ihst)]; 402 | sprintf(expr, "^%s[_~]*!%s@%s$", isrc, iusr, ihst); 403 | name *new=n_add(&servs->igns, expr, RFC1459); 404 | if(new) 405 | { 406 | new->icase=icase; 407 | new->pms=pms; 408 | } 409 | } 410 | } 411 | } 412 | } 413 | else if(servs && (strcmp(cmd, "*chan")==0)) 414 | { 415 | chanlist * new=(chanlist *)malloc(sizeof(chanlist)); 416 | new->next=servs->chans; 417 | new->name=strdup(rest); 418 | if((new->key=strpbrk(new->name, " \t"))) 419 | { 420 | *new->key++=0; 421 | new->key=strdup(new->key); 422 | } 423 | new->igns=NULL; 424 | new->logt=LOGT_NONE; 425 | new->logf=NULL; 426 | servs->chans=new; 427 | } 428 | else if(servs && servs->chans && (strcmp(cmd, ">log")==0)) 429 | { 430 | if(servs->chans->logf) fclose(servs->chans->logf); 431 | char *type=strtok(rest, " \t"); 432 | logtype logt=LOGT_NONE; 433 | if(strcasecmp(type, "plain")==0) 434 | logt=LOGT_PLAIN; 435 | else if(strcasecmp(type, "symbolic")==0) 436 | logt=LOGT_SYMBOLIC; 437 | else 438 | { 439 | atr_failsafe(&s_buf, ERR, "rc: >log: Unrecognised log type (valid types are: plain, symbolic)", "init: "); 440 | nerrors++; 441 | } 442 | if(logt!=LOGT_NONE) 443 | { 444 | char *logf=strtok(NULL, ""); 445 | servs->chans->logf=fopen(logf, "a"); 446 | if(!servs->chans->logf) 447 | { 448 | atr_failsafe(&s_buf, ERR, "rc: >log: Failed to open log file for append", "init: "); 449 | atr_failsafe(&s_buf, ERR, strerror(errno), "fopen: "); 450 | nerrors++; 451 | } 452 | servs->chans->logt=logt; 453 | } 454 | } 455 | #include "config_rcread.c" 456 | else 457 | { 458 | char msg[48+strlen(cmd)]; 459 | sprintf(msg, "rc: Unrecognised cmd %s in .quirc/rc (ignoring)", cmd); 460 | atr_failsafe(&s_buf, ERR, msg, "init: "); 461 | nerrors++; 462 | } 463 | } 464 | free(line); 465 | } 466 | return(nerrors); 467 | } 468 | 469 | signed int pargs(int argc, char *argv[]) 470 | { 471 | bool check=false; 472 | int arg; 473 | for(arg=1;argkey++=0; 550 | new->key=strdup(new->key); 551 | } 552 | new->igns=NULL; 553 | new->logt=LOGT_NONE; 554 | new->logf=NULL; 555 | servs->chans=new; 556 | } 557 | #include "config_pargs.c" 558 | else 559 | { 560 | char msg[40+strlen(argv[arg])]; 561 | sprintf(msg, "pargs: Unrecognised argument '%s'", argv[arg]); 562 | atr_failsafe(&s_buf, ERR, msg, "init: "); 563 | } 564 | } 565 | if(check) return(0); 566 | return(-1); 567 | } 568 | 569 | void freeservlist(servlist *serv) 570 | { 571 | if(serv) 572 | { 573 | free(serv->name); 574 | free(serv->nick); 575 | free(serv->portno); 576 | free(serv->pass); 577 | n_free(serv->igns); 578 | freechanlist(serv->chans); 579 | freeservlist(serv->next); 580 | } 581 | free(serv); 582 | } 583 | 584 | void freechanlist(chanlist *chan) 585 | { 586 | if(chan) 587 | { 588 | free(chan->name); 589 | free(chan->key); 590 | if(chan->logf) fclose(chan->logf); 591 | n_free(chan->igns); 592 | freechanlist(chan->next); 593 | } 594 | free(chan); 595 | } 596 | -------------------------------------------------------------------------------- /config.cdl: -------------------------------------------------------------------------------- 1 | int:width:80:30:::width:width:S:display width 2 | int:height:24:5:::height:height:S:display height 3 | int:mirc_colour_compat:1:0:2:mcc:mcc:mcc:L:mirc colour compatibility 4 | int:buflines:1024:32::buf:buf-lines:buf:S:buffer lines 5 | int:maxnlen:16:4::mnln:maxnicklen:mnln:S:max nick length 6 | bool:full_width_colour:false:::fwc:fwc:fwc:B:full width colour 7 | bool:hilite_tabstrip:false:::hts:hts:hts:B:highlight tabstrip 8 | bool:tsb:true:::tsb:tsb:tsb:B:top status bar 9 | int:tping:30:15::tping:tping:tping:S:outbound ping time 10 | int:ts:1:0:6:ts:timestamps:ts:L:timestamping 11 | bool:utc:false:::utc:utc:utc:B:UTC timestamps 12 | bool:its:false:::its:its:its:B:input clock 13 | bool:quiet:false:::quiet:quiet:quiet:B:quiet mode 14 | bool:debug:false:::debug:debug:debug:B:debugging 15 | bool:conf:false:::conf:conf::B:conference mode (default) 16 | bool:show_prefix:false:::prefix:prefix:prefix:B:display nick prefixes 17 | bool:titles:true:::titles:titles:titles:B:xterm title 18 | bool:winch:true:::winch:winch:winch:B:react to SIGWINCH (window change) 19 | bool:indent:true:::indent:indent:indent:B:hanging indent 20 | bool:merge:true:::merge:merge:merge:B:merge events 21 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | quIRC - simple terminal-based IRC client 5 | Copyright (C) 2010-13 Edward Cree 6 | 7 | See quirc.c for license information 8 | config: rc file and option parsing 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #define CLIENT_SOURCE "http://github.com/ec429/quIRC" 16 | 17 | #include "types.h" 18 | 19 | typedef struct _chanlist 20 | { 21 | char *name; 22 | char *key; 23 | FILE *logf; 24 | logtype logt; 25 | struct _chanlist *next; 26 | struct _name *igns; 27 | } 28 | chanlist; 29 | 30 | typedef struct _servlist 31 | { 32 | char *name; 33 | char *portno; 34 | char *nick; 35 | char *pass; 36 | bool join; 37 | chanlist *chans; 38 | struct _servlist *next; 39 | struct _name *igns; 40 | } 41 | servlist; 42 | 43 | #include "names.h" 44 | #include "keymod.h" 45 | 46 | // global settings & state 47 | #include "config_globals.h" 48 | extern bool autojoin; 49 | extern char *username, *fname, *nick, *pass, *portno; 50 | extern bool defnick; 51 | extern servlist *servs; 52 | extern name *igns; 53 | extern char *version; 54 | extern unsigned int nkeys; 55 | extern keymod *kmap; 56 | 57 | int initkeys(void); 58 | void loadkeys(FILE *); 59 | int conf_check(void); // writes diagnostics to start-buffer 60 | int def_config(void); // set these to their defaults 61 | int rcread(FILE *rcfp); // read & parse the rc file. 62 | signed int pargs(int argc, char *argv[]); // parse the cmdline args. If return is >=0, main should return it also 63 | void freeservlist(servlist *serv); 64 | void freechanlist(chanlist *chan); 65 | -------------------------------------------------------------------------------- /ctbuf.c: -------------------------------------------------------------------------------- 1 | /* 2 | quIRC - simple terminal-based IRC client 3 | Copyright (C) 2010-13 Edward Cree 4 | 5 | See quirc.c for license information 6 | ctbuf: coloured text buffers 7 | */ 8 | 9 | #include 10 | #include 11 | #include "ctbuf.h" 12 | 13 | void ct_init_char(ctchar **buf, size_t *l, size_t *i) 14 | { 15 | *l=80; 16 | *buf=malloc(*l*sizeof(ctchar)); 17 | (*buf)[0].d=0; 18 | *i=0; 19 | } 20 | 21 | void ct_append_char(ctchar **buf, size_t *l, size_t *i, char d) 22 | { 23 | ct_append_char_c(buf, l, i, (*buf&&*i)?(*buf)[(*i)-1].c:(colour){.fore=7,.back=0,.hi=false,.ul=false}, d); 24 | } 25 | 26 | void ct_append_char_c(ctchar **buf, size_t *l, size_t *i, colour c, char d) 27 | { 28 | if(*buf) 29 | { 30 | (*buf)[*i]=(ctchar){.c=c, .d=d}; 31 | (*i)++; 32 | } 33 | else 34 | { 35 | ct_init_char(buf, l, i); 36 | ct_append_char_c(buf, l, i, c, d); 37 | } 38 | ctchar *nbuf=*buf; 39 | if((*i)>=(*l)) 40 | { 41 | *l=*i*2; 42 | nbuf=realloc(*buf, *l*sizeof(ctchar)); 43 | } 44 | if(nbuf) 45 | { 46 | *buf=nbuf; 47 | (*buf)[*i].d=0; 48 | } 49 | else 50 | { 51 | free(*buf); 52 | ct_init_char(buf, l, i); 53 | } 54 | } 55 | 56 | void ct_append_str(ctchar **buf, size_t *l, size_t *i, const char *str) 57 | { 58 | while(str && *str) 59 | { 60 | ct_append_char(buf, l, i, *str++); 61 | } 62 | } 63 | 64 | void ct_append_str_c(ctchar **buf, size_t *l, size_t *i, colour c, const char *str) 65 | { 66 | while(str && *str) 67 | { 68 | ct_append_char_c(buf, l, i, c, *str++); 69 | } 70 | } 71 | 72 | void ct_putchar(ctchar a) 73 | { 74 | setcolour(a.c); 75 | putchar(a.d); 76 | } 77 | 78 | void ct_puts(const ctchar *buf) 79 | { 80 | if(!buf) return; 81 | size_t i=0; 82 | while(buf[i].d) 83 | { 84 | if(!(i&&eq_colour(buf[i].c, buf[i-1].c))) 85 | setcolour(buf[i].c); 86 | putchar(buf[i++].d); 87 | } 88 | } 89 | 90 | void ct_putsn(const ctchar *buf, size_t n) 91 | { 92 | if(!buf) return; 93 | size_t i=0; 94 | while((buf[i].d)&&(i 11 | #include 12 | #include 13 | #include "types.h" 14 | #include "buffer.h" 15 | #include "config.h" 16 | #include "version.h" 17 | 18 | int ctcp_strip(char *msg, const char *src, int b2, bool ha, bool notice, bool priv, bool tx) 19 | { 20 | int e=0; 21 | char *in=msg, *out=msg; 22 | while(*in) 23 | { 24 | if(*in==1) 25 | { 26 | char *t=strchr(in+1, 1); 27 | if(t) 28 | { 29 | *t=0; 30 | e|=ctcp(in+1, src, b2, ha, notice, priv, tx); 31 | in=t+1; 32 | continue; 33 | } 34 | } 35 | *out++=*in++; 36 | } 37 | *out=0; 38 | return(e); 39 | } 40 | 41 | int ctcp_action(__attribute__((unused)) int fd, const char *msg, const char *src, int b2, bool ha, bool tx) 42 | { 43 | add_to_buffer(b2, ACT, NORMAL, 0, tx, msg+7, src); 44 | ha=ha||strstr(msg+7, SERVER(b2).nick); 45 | if(ha) 46 | bufs[b2].hi_alert=5; 47 | return(0); 48 | } 49 | 50 | int ctcp_notice_generic(const char *msg, const char *src, int b2, bool ha) 51 | { 52 | add_to_buffer(b2, NOTICE, NORMAL, 0, false, msg, src); 53 | if(ha) 54 | bufs[b2].hi_alert=5; 55 | return(0); 56 | } 57 | 58 | int ctcp_finger(int fd, __attribute__((unused)) const char *msg, const char *src, __attribute__((unused)) int b2) 59 | { 60 | char resp[32+strlen(src)+strlen(fname)]; 61 | sprintf(resp, "NOTICE %s :\001FINGER %s\001", src, fname); 62 | irc_tx(fd, resp); 63 | return(0); 64 | } 65 | 66 | int ctcp_ping(int fd, const char *msg, const char *src, __attribute__((unused)) int b2) 67 | { 68 | char resp[16+strlen(src)+strlen(msg)]; 69 | sprintf(resp, "NOTICE %s :\001%s\001", src, msg); 70 | irc_tx(fd, resp); 71 | return(0); 72 | } 73 | 74 | int ctcp_notice_ping(const char *msg, const char *src, int b2, bool ha) 75 | { 76 | unsigned int t, u; 77 | ssize_t n; 78 | if((msg[4]==' ')&&(sscanf(msg+5, "%u%zn", &t, &n)==1)) 79 | { 80 | double dt=0; 81 | if((msg[5+n]==' ')&&(sscanf(msg+6+n, "%u", &u)==1)) 82 | { 83 | struct timeval tv; 84 | gettimeofday(&tv, NULL); 85 | dt=(tv.tv_sec-t)+1e-6*(tv.tv_usec-(suseconds_t)u); 86 | } 87 | else 88 | dt=difftime(time(NULL), t); 89 | char tm[32]; 90 | snprintf(tm, 32, "%gs", dt); 91 | add_to_buffer(b2, STA, NORMAL, 0, false, tm, "/ping: "); 92 | } 93 | else 94 | { 95 | add_to_buffer(b2, NOTICE, NORMAL, 0, false, msg, src); 96 | } 97 | if(ha) 98 | bufs[b2].hi_alert=5; 99 | return(0); 100 | } 101 | 102 | int ctcp_clientinfo(int fd, __attribute__((unused)) const char *msg, const char *src, __attribute__((unused)) int b2) 103 | { 104 | // XXX this doesn't fully implement the CLIENTINFO "protocol", it just gives a list of recognised top-level commands 105 | char resp[64+strlen(src)]; 106 | sprintf(resp, "NOTICE %s :\001CLIENTINFO ACTION FINGER PING CLIENTINFO TIME SOURCE VERSION\001", src); 107 | irc_tx(fd, resp); 108 | return(0); 109 | } 110 | 111 | int ctcp_version(int fd, __attribute__((unused)) const char *msg, const char *src, __attribute__((unused)) int b2) 112 | { 113 | char resp[32+strlen(src)+strlen(version)+strlen(CC_VERSION)]; 114 | sprintf(resp, "NOTICE %s :\001VERSION %s:%s:%s\001", src, "quIRC", version, CC_VERSION); 115 | irc_tx(fd, resp); 116 | return(0); 117 | } 118 | 119 | int ctcp_source(int fd, __attribute__((unused)) const char *msg, const char *src, __attribute__((unused)) int b2) 120 | { 121 | // XXX this response doesn't match the SOURCE "protocol", which expects an FTP address; quIRC is distributed by HTTP, so we just give a URL 122 | char resp[32+strlen(src)+strlen(CLIENT_SOURCE)]; 123 | sprintf(resp, "NOTICE %s :\001SOURCE %s\001", src, CLIENT_SOURCE); 124 | irc_tx(fd, resp); 125 | return(0); 126 | } 127 | 128 | int ctcp_time(int fd, __attribute__((unused)) const char *msg, const char *src, int b2) 129 | { 130 | time_t now=time(NULL); 131 | struct tm *tm=localtime(&now); 132 | if(!tm) 133 | { 134 | add_to_buffer(b2, ERR, QUIET, 0, false, strerror(errno), "CTCP TIME"); 135 | char resp[48+strlen(src)]; 136 | sprintf(resp, "NOTICE %s :\001ERRMSG TIME :localtime()\001", src); 137 | irc_tx(fd, resp); 138 | } 139 | char datebuf[256]; 140 | size_t datelen=strftime(datebuf, sizeof(datebuf), "%F %a %T %z", tm); 141 | char resp[32+strlen(src)+datelen]; 142 | sprintf(resp, "NOTICE %s :\001TIME %s\001", src, datebuf); 143 | irc_tx(fd, resp); 144 | return(0); 145 | } 146 | 147 | int ctcp(const char *msg, const char *src, int b2, bool ha, bool notice, bool priv, bool tx) 148 | { 149 | int b=bufs[b2].server; 150 | int fd=bufs[b].handle; 151 | if(priv&&!tx) b2=makeptab(b, src); 152 | if((strncmp(msg, "ACTION", 6)==0)&&((msg[6]==' ')||(msg[6]==0))) // allow 'ACTION' or 'ACTION blah' 153 | return(ctcp_action(fd, msg, src, b2, ha, tx)); 154 | if(tx) return(0); 155 | struct 156 | { 157 | const char *query; 158 | int (*handler)(int fd, const char *msg, const char *src, int b2); 159 | int (*noticer)(const char *msg, const char *src, int b2, bool ha); 160 | } 161 | queries[]= 162 | { 163 | {"FINGER", ctcp_finger, ctcp_notice_generic}, 164 | {"PING", ctcp_ping, ctcp_notice_ping}, 165 | {"CLIENTINFO", ctcp_clientinfo, ctcp_notice_generic}, 166 | {"VERSION", ctcp_version, ctcp_notice_generic}, 167 | {"TIME", ctcp_time, ctcp_notice_generic}, 168 | {"SOURCE", ctcp_source, ctcp_notice_generic}, 169 | }; 170 | for(unsigned int i=0;i 11 | 12 | int ctcp_strip(char *msg, const char *src, int b2, bool ha, bool notice, bool priv, bool tx); // strip out and handle CTCP queries from a message 13 | int ctcp(const char *msg, const char *src, int b2, bool ha, bool notice, bool priv, bool tx); // handle a CTCP query 14 | 15 | -------------------------------------------------------------------------------- /dist/config.mak: -------------------------------------------------------------------------------- 1 | # Sample config.mak file 2 | 3 | # uncomment if your libc does not support getaddrinfo_a 4 | # OPTFLAGS += -DASYNCH_NL=0 5 | # LIBS_ASYNCH_NL := 6 | 7 | # uncomment if you encounter errors with intmax_t and %jd 8 | # OPTFLAGS += -DINTMAX_BUG=1 9 | -------------------------------------------------------------------------------- /distMakefile: -------------------------------------------------------------------------------- 1 | # gitless Makefile for quIRC 2 | 3 | CC ?= gcc 4 | OPTFLAGS := -g 5 | CFLAGS = -Wall -Wextra -Werror -pedantic -std=gnu99 -D_GNU_SOURCE $(OPTFLAGS) 6 | AWK := gawk 7 | PREFIX := /usr/local 8 | LIBS_ASYNCH_NL := -lanl 9 | OPTLIBS = $(LIBS_ASYNCH_NL) 10 | LIBS = $(OBJS) -lm -lncurses $(OPTLIBS) 11 | OBJS := ttyraw.o ttyesc.o irc.o ctcp.o bits.o strbuf.o ctbuf.o colour.o buffer.o names.o numeric_text.o config.o input.o logging.o types.o cmd.o complete.o 12 | INCLUDE := $(OBJS:.o=.h) version.h osconf.h ctbuf_h.d buffer_h.d config_c.d config_h.d input_h.d 13 | 14 | -include config.mak 15 | 16 | all: quirc doc 17 | 18 | install: all doc 19 | install -D -m0755 quirc $(PREFIX)/bin/quirc 20 | install -D -m0644 quirc.1 $(PREFIX)/man/man1/quirc.1 21 | install -D -m0644 readme.htm $(PREFIX)/share/doc/quirc/readme.htm 22 | install -D -m0644 readme.css $(PREFIX)/share/doc/quirc/readme.css 23 | install -D -m0644 config_ref.htm $(PREFIX)/share/doc/quirc/config_ref.htm 24 | install -D -m0644 tutorial.htm $(PREFIX)/share/doc/quirc/tutorial.htm 25 | 26 | uninstall: 27 | -rm $(PREFIX)/bin/quirc 28 | -rm $(PREFIX)/man/man1/quirc.1 29 | -rm $(PREFIX)/share/doc/quirc/readme.htm 30 | -rm $(PREFIX)/share/doc/quirc/readme.css 31 | -rm $(PREFIX)/share/doc/quirc/config_ref.htm 32 | -rm $(PREFIX)/share/doc/quirc/tutorial.htm 33 | 34 | quirc: quirc.c $(OBJS) $(INCLUDE) 35 | $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $< $(LIBS) $(DEFINES) 36 | 37 | clean: 38 | -rm -f *.o *.d quirc genconfig genkeymap 39 | 40 | realclean: clean 41 | -rm -f c_init.c README version.h config_* keymap.c keymod.h quirc.1 numeric.h numeric_text.c 42 | 43 | doc: README config_ref.htm quirc.1 44 | 45 | README: readme.htm 46 | -sed -e "s/'/'/g" -e "s/"/\"/g" < readme.htm | html2text -nobs -o README 47 | 48 | # warning, this explodes if PREFIX contains a ! 49 | quirc.1: man.in 50 | echo ".\\\"\n.\\\"This man page is automatically generated from man.in by a sedscript; edit man.in, not this file, and 'make doc' to apply the changes.">quirc.1 51 | sed -e "s!\$$PREFIX!$(PREFIX)!g" < man.in >> quirc.1 52 | 53 | # a %.o /always/ depends on its %.h as well as its %.c 54 | %.o: %.c %.h 55 | $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ $(DEFINES) 56 | 57 | # dependency files have no work to do 58 | %.d: 59 | touch $@ 60 | 61 | cmd.o: buffer_h.d names.h irc.h logging.h strbuf.h types.h 62 | 63 | ttyesc.o: ttyesc.c ttyesc.h bits.h config_h.d 64 | 65 | irc.o: irc.c irc.h bits.h buffer_h.d colour.h names.h numeric.h numeric_text.h osconf.h ctcp.h 66 | 67 | bits.o: bits.c bits.h ttyesc.h colour.h 68 | 69 | ctbuf_h.d: ctbuf.h colour.h 70 | 71 | ctcp.c: types.h buffer_h.d config_h.d version.h 72 | 73 | colour.o: colour.c colour.h c_init.c ttyesc.h 74 | 75 | buffer.o: buffer.c buffer.h ttyesc.h colour.h bits.h names.h text.h irc.h version.h input_h.d logging.h osconf.h ctbuf_h.d 76 | 77 | buffer_h.d: buffer.h colour.h config_h.d input_h.d irc.h types.h 78 | 79 | config.o: config_c.d config_h.d names.h bits.h colour.h text.h ttyesc.h version.h 80 | 81 | config_c.d: config.c config_globals.c config_check.c config_def.c config_need.c config_rcread.c config_pargs.c config_help.c keymap.c 82 | 83 | config_h.d: config.h config_globals.h version.h keymod.h 84 | 85 | config_%: config.cdl genconfig 86 | ./genconfig $@ < config.cdl > $@ || (rm $@ && false) 87 | 88 | genconfig: genconfig.c strbuf.h strbuf.o 89 | $(CC) $(CFLAGS) $(CPPFLAGS) $< $(LDFLAGS) strbuf.o -o $@ 90 | 91 | input_h.d: input.h keymod.h 92 | 93 | input.o: input.c input_h.d ttyesc.h names.h buffer_h.d irc.h bits.h config_h.d logging.h complete.h config_set.c 94 | 95 | logging.o: types.h bits.h 96 | 97 | names.o: names.c names.h buffer_h.d irc.h 98 | 99 | complete.o: input_h.d names.h buffer_h.d irc.h strbuf.h cmd.h 100 | 101 | numeric.h: gen_numerics.py numerics.py 102 | ./$< > $@ 103 | 104 | numeric_text.c: gen_numerics_text.py numerics.py 105 | ./$< > $@ 106 | 107 | c_init.c: colour.def c_init.awk 108 | $(AWK) -f c_init.awk colour.def > c_init.c 109 | 110 | genkeymap: genkeymap.c strbuf.h strbuf.o 111 | $(CC) $(CFLAGS) $(CPPFLAGS) $< $(LDFLAGS) strbuf.o -o $@ 112 | 113 | keymod.h: keys genkeymap 114 | ./genkeymap h < keys > $@ || (rm $@ && false) 115 | 116 | keymap.c: keys genkeymap 117 | ./genkeymap c < keys > $@ || (rm $@ && false) 118 | 119 | -------------------------------------------------------------------------------- /gen_numerics.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # gen_numerics.py: generate numerics.h 3 | 4 | import numerics 5 | 6 | print """#pragma once 7 | /* 8 | quIRC - simple terminal-based IRC client 9 | Copyright (C) 2010-13 Edward Cree 10 | 11 | See quirc.c for license information 12 | numeric: IRC numeric replies 13 | */ 14 | 15 | /*** 16 | This file is generated by gen_numerics.py from masters in numerics.py. 17 | Do not make edits directly to this file! Edit the masters instead. 18 | ***/ 19 | 20 | /* 21 | A symbolic name defined here does not necessarily imply recognition or decoding of that numeric reply. 22 | Some numeric replies are non-normative; that is, they are not defined in the original RFC1459 or its superseding RFC2812, but instead are either defined in other, non-normative documents, or are entirely experimental. These are denoted with an X before the name (of the form RPL_X_BOGOSITY); where a numeric is being identified purely on the basis of usage "in the wild", the symbolic name will be completely arbitrary and may not align with usage elsewhere. 23 | */ 24 | 25 | /* Error replies */""" 26 | errs = [n for n in numerics.nums.values() if isinstance(n, numerics.NumericError)] 27 | for e in errs: 28 | print str(e) 29 | 30 | print """ 31 | /* Command responses */""" 32 | rpls = [n for n in numerics.nums.values() if isinstance(n, numerics.NumericReply)] 33 | for r in rpls: 34 | print str(r) 35 | -------------------------------------------------------------------------------- /gen_numerics_text.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # gen_numerics_text.py: generate numeric_text.c 3 | 4 | import numerics 5 | 6 | print """/* 7 | quIRC - simple terminal-based IRC client 8 | Copyright (C) 2010-13 Edward Cree 9 | 10 | See quirc.c for license information 11 | numeric_text: names of IRC numeric replies 12 | */ 13 | 14 | /*** 15 | This file is generated by gen_numerics_text.py from masters in numerics.py 16 | Do not make edits directly to this file! Edit the masters instead. 17 | ***/ 18 | 19 | #include // for NULL 20 | 21 | const char *numeric_names[1000]={""" 22 | for i in xrange(1000): 23 | if i in numerics.nums: 24 | print '"'+numerics.nums[i].getname()+'",' 25 | else: 26 | print 'NULL,' 27 | print '};' 28 | -------------------------------------------------------------------------------- /genkeymap.c: -------------------------------------------------------------------------------- 1 | /* 2 | quIRC - simple terminal-based IRC client 3 | Copyright (C) 2010-13 Edward Cree 4 | 5 | See quirc.c for license information 6 | genkeymap: generate keymapping code from data 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "strbuf.h" 14 | 15 | typedef struct 16 | { 17 | char *name; 18 | char *mod; 19 | } 20 | keymod; 21 | 22 | int main(int argc, char **argv) 23 | { 24 | if(argc!=2) 25 | { 26 | fprintf(stderr, "Usage: genkeymap {c|h}\n"); 27 | return(EXIT_FAILURE); 28 | } 29 | int mode=0; 30 | if(strcmp(argv[1], "c")==0) 31 | mode=1; 32 | else if(strcmp(argv[1], "h")==0) 33 | mode=2; 34 | if(!mode) 35 | { 36 | fprintf(stderr, "Usage: genkeymap {c|h}\n"); 37 | return(EXIT_FAILURE); 38 | } 39 | int nkeys=0; 40 | keymod *keys=NULL; 41 | while(!feof(stdin)) 42 | { 43 | char *line=fgetl(stdin); 44 | if(line) 45 | { 46 | if((*line)&&(*line!='#')) 47 | { 48 | keymod new; 49 | new.name=strdup(strtok(line, " \t")); 50 | char *mod=strtok(NULL, "\t"); 51 | if(*mod==':') 52 | { 53 | new.mod=strdup(mod); 54 | } 55 | else 56 | { 57 | off_t o=strlen(mod); 58 | if(o&1) 59 | { 60 | fprintf(stderr, "genkeymap: bad line (o&1) %s\t%s\n", new.name, mod); 61 | return(EXIT_FAILURE); 62 | } 63 | new.mod=malloc((o>>1)+1); 64 | for(int i=0;i>1]=c; 79 | } 80 | new.mod[o>>1]=0; 81 | } 82 | int n=nkeys++; 83 | (keys=realloc(keys, nkeys*sizeof(keymod)))[n]=new; 84 | } 85 | free(line); 86 | } 87 | else 88 | break; 89 | } 90 | switch(mode) 91 | { 92 | case 1: 93 | printf("#include \n\n"); 94 | printf("int initkeys(void)\n{\n"); 95 | printf("\tnkeys=%d;\n", nkeys); 96 | printf("\tkmap=malloc(nkeys*sizeof(keymod));\n"); 97 | printf("\tif(!kmap) return(1);\n"); 98 | for(int i=0;i&1 | tail -n 1) 10 | echo "/* 11 | quIRC - simple terminal-based IRC client 12 | Copyright (C) 2010-12 Edward Cree 13 | 14 | See quirc.c for license information 15 | version.h: contains version number (generated from git describe) 16 | */ 17 | #pragma once 18 | #define VERSION_MAJ (unsigned char)${MAJOR}U // Major version 19 | #define VERSION_MIN (unsigned char)${MINOR}U // Minor version 20 | #define VERSION_REV (unsigned char)${REV}U // Revision number 21 | #define VERSION_TXT \"$GIT\" // Rest of git describe 22 | #define CC_VERSION \"$CCVER\" // last line of cc -v" > version.h2 23 | 24 | if ! cmp version.h version.h2; then mv version.h2 version.h; else rm version.h2; fi 25 | -------------------------------------------------------------------------------- /input.c: -------------------------------------------------------------------------------- 1 | /* 2 | quIRC - simple terminal-based IRC client 3 | Copyright (C) 2010-13 Edward Cree 4 | 5 | See quirc.c for license information 6 | input: handle input routines 7 | */ 8 | 9 | #include "input.h" 10 | #include "logging.h" 11 | #include "strbuf.h" 12 | #include "ttyesc.h" 13 | #include "buffer.h" 14 | #include "irc.h" 15 | #include "bits.h" 16 | #include "config.h" 17 | #include "keymod.h" 18 | #include "cmd.h" 19 | #include "complete.h" 20 | 21 | bool ttab; 22 | 23 | size_t i_firstlen (ichar src); 24 | size_t i_lastlen (ichar src); 25 | void i_move (iline * inp, ssize_t bytes); 26 | 27 | void inputchar (iline * inp, int *state) 28 | { 29 | int c = getchar (); 30 | if ((c == 0) || (c == EOF)) // stdin is set to non-blocking, so this may happen 31 | return; 32 | char *seq; 33 | size_t sl, si; 34 | int mod = -1; 35 | init_char (&seq, &sl, &si); 36 | bool match = true; 37 | while (match && (mod < 0)) 38 | { 39 | append_char (&seq, &sl, &si, c); 40 | match = false; 41 | for (unsigned int i = 0; i < nkeys; i++) 42 | { 43 | if (strncmp (seq, kmap[i].mod, si) == 0) 44 | { 45 | if (kmap[i].mod[si] == 0) 46 | { 47 | mod = i; 48 | break; 49 | } 50 | else 51 | { 52 | match = true; 53 | } 54 | } 55 | } 56 | if (match && (mod < 0)) 57 | { 58 | c = getchar (); 59 | if ((c == 0) || (c == EOF)) 60 | { 61 | match = false; 62 | } 63 | } 64 | } 65 | if (mod < 0) 66 | { 67 | while (si > 1) 68 | ungetc (seq[--si], stdin); 69 | append_char (&inp->left.data, &inp->left.l, &inp->left.i, 70 | seq[0]); 71 | } 72 | free (seq); 73 | if (c != '\t') 74 | ttab = false; 75 | if (mod == KEY_BS) // backspace 76 | { 77 | size_t ll = i_lastlen (inp->left); 78 | for (size_t i = 0; i < ll; i++) 79 | back_ichar (&inp->left); 80 | return; 81 | } 82 | if ((mod < 0) && (c < 32)) // this also stomps on the newline 83 | { 84 | back_ichar (&inp->left); 85 | if (c == 8) // C-h ~= backspace 86 | { 87 | size_t ll = i_lastlen (inp->left); 88 | for (size_t i = 0; i < ll; i++) 89 | back_ichar (&inp->left); 90 | return; 91 | } 92 | if (c == 1) // C-a ~= home 93 | { 94 | i_home (inp); 95 | return; 96 | } 97 | if (c == 5) // C-e ~= end 98 | { 99 | i_end (inp); 100 | return; 101 | } 102 | if (c == 3) // C-c ~= clear 103 | { 104 | ifree (inp); 105 | return; 106 | } 107 | if (c == 24) // C-x ~= clear to left 108 | { 109 | free (inp->left.data); 110 | inp->left.data = NULL; 111 | inp->left.i = inp->left.l = 0; 112 | return; 113 | } 114 | if (c == 11) // C-k ~= clear to right 115 | { 116 | free (inp->right.data); 117 | inp->right.data = NULL; 118 | inp->right.i = inp->right.l = 0; 119 | return; 120 | } 121 | if (c == 23) // C-w ~= backspace word 122 | { 123 | while (back_ichar (&inp->left) == ' '); 124 | while (!strchr (" ", back_ichar (&inp->left))); 125 | if (inp->left.i) 126 | append_char (&inp->left.data, &inp->left.l, 127 | &inp->left.i, ' '); 128 | return; 129 | } 130 | if (c == '\t') // tab completion of nicks 131 | { 132 | tab_complete(inp); 133 | return; 134 | } 135 | } 136 | else if (mod >= 0) 137 | { 138 | bool gone = false; 139 | for (int n = 1; n <= 12; n++) 140 | { 141 | if (mod == KEY_F (n)) 142 | { 143 | cbuf = min (n % 12, nbufs - 1); 144 | redraw_buffer (); 145 | return; 146 | } 147 | } 148 | if (mod == KEY_UP) // Up 149 | { 150 | int old = bufs[cbuf].input.scroll; 151 | bufs[cbuf].input.scroll = 152 | min (bufs[cbuf].input.scroll + 1, 153 | bufs[cbuf].input.filled ? bufs[cbuf]. 154 | input.nlines - 1 : bufs[cbuf].input.ptr); 155 | if (old != bufs[cbuf].input.scroll) 156 | gone = true; 157 | if (gone && !old) 158 | { 159 | if (inp->left.i || inp->right.i) 160 | { 161 | char out[inp->left.i + inp->right.i + 162 | 1]; 163 | sprintf (out, "%s%s", 164 | inp->left.data ? inp->left. 165 | data : "", 166 | inp->right.data ? inp->right. 167 | data : ""); 168 | addtoibuf (&bufs[cbuf].input, out); 169 | bufs[cbuf].input.scroll = 2; 170 | } 171 | } 172 | } 173 | if (mod == KEY_DOWN) // Down 174 | { 175 | gone = true; 176 | if (!bufs[cbuf].input.scroll) 177 | { 178 | if (inp->left.i || inp->right.i) 179 | { 180 | char out[inp->left.i + inp->right.i + 181 | 1]; 182 | sprintf (out, "%s%s", 183 | inp->left.data ? inp->left. 184 | data : "", 185 | inp->right.data ? inp->right. 186 | data : ""); 187 | addtoibuf (&bufs[cbuf].input, out); 188 | bufs[cbuf].input.scroll = 0; 189 | } 190 | } 191 | bufs[cbuf].input.scroll = 192 | max (bufs[cbuf].input.scroll - 1, 0); 193 | } 194 | if (gone && (bufs[cbuf].input.ptr || bufs[cbuf].input.filled)) 195 | { 196 | if (bufs[cbuf].input.scroll) 197 | { 198 | char *ln = 199 | bufs[cbuf].input. 200 | line[(bufs[cbuf].input.ptr + 201 | bufs[cbuf].input.nlines - 202 | bufs[cbuf].input.scroll) % 203 | bufs[cbuf].input.nlines]; 204 | if (ln) 205 | { 206 | ifree (inp); 207 | inp->left.data = strdup (ln); 208 | inp->left.i = strlen (inp->left.data); 209 | inp->left.l = 0; 210 | } 211 | } 212 | else 213 | { 214 | ifree (inp); 215 | } 216 | return; 217 | } 218 | if (mod == KEY_RIGHT) 219 | { 220 | i_move (inp, i_firstlen (inp->right)); 221 | return; 222 | } 223 | if (mod == KEY_LEFT) 224 | { 225 | i_move (inp, -i_lastlen (inp->left)); 226 | return; 227 | } 228 | if (mod == KEY_HOME) 229 | { 230 | i_home (inp); 231 | return; 232 | } 233 | if (mod == KEY_END) 234 | { 235 | i_end (inp); 236 | return; 237 | } 238 | if (mod == KEY_DELETE) 239 | { 240 | size_t fl = i_firstlen (inp->right); 241 | if (inp->right.data && (inp->right.i > fl)) 242 | { 243 | char *nr = strdup (inp->right.data + fl); 244 | free (inp->right.data); 245 | inp->right.data = nr; 246 | inp->right.i -= fl; 247 | inp->right.l = inp->right.i; 248 | } 249 | else 250 | { 251 | free (inp->right.data); 252 | inp->right.data = NULL; 253 | inp->right.l = inp->right.i = 0; 254 | } 255 | return; 256 | } 257 | if (mod == KEY_CPGUP) 258 | { 259 | bufs[cbuf].ascroll -= height - (tsb ? 3 : 2); 260 | redraw_buffer (); 261 | return; 262 | } 263 | if (mod == KEY_CPGDN) 264 | { 265 | bufs[cbuf].ascroll += height - (tsb ? 3 : 2); 266 | redraw_buffer (); 267 | return; 268 | } 269 | gone = false; 270 | if (mod == KEY_PGUP) 271 | { 272 | int old = bufs[cbuf].input.scroll; 273 | bufs[cbuf].input.scroll = 274 | min (bufs[cbuf].input.scroll + 10, 275 | bufs[cbuf].input.filled ? bufs[cbuf]. 276 | input.nlines - 1 : bufs[cbuf].input.ptr); 277 | if (old != bufs[cbuf].input.scroll) 278 | gone = true; 279 | if (gone && !old) 280 | { 281 | if (inp->left.i || inp->right.i) 282 | { 283 | char out[inp->left.i + inp->right.i + 284 | 1]; 285 | sprintf (out, "%s%s", 286 | inp->left.data ? inp->left. 287 | data : "", 288 | inp->right.data ? inp->right. 289 | data : ""); 290 | addtoibuf (&bufs[cbuf].input, out); 291 | bufs[cbuf].input.scroll = 2; 292 | } 293 | } 294 | return; 295 | } 296 | if (mod == KEY_PGDN) 297 | { 298 | gone = true; 299 | if (!bufs[cbuf].input.scroll) 300 | { 301 | if (inp->left.i || inp->right.i) 302 | { 303 | char out[inp->left.i + inp->right.i + 304 | 1]; 305 | sprintf (out, "%s%s", 306 | inp->left.data ? inp->left. 307 | data : "", 308 | inp->right.data ? inp->right. 309 | data : ""); 310 | addtoibuf (&bufs[cbuf].input, out); 311 | bufs[cbuf].input.scroll = 0; 312 | } 313 | } 314 | bufs[cbuf].input.scroll = 315 | max (bufs[cbuf].input.scroll - 10, 0); 316 | return; 317 | } 318 | if (gone && (bufs[cbuf].input.ptr || bufs[cbuf].input.filled)) 319 | { 320 | if (bufs[cbuf].input.scroll) 321 | { 322 | char *ln = 323 | bufs[cbuf].input. 324 | line[(bufs[cbuf].input.ptr + 325 | bufs[cbuf].input.nlines - 326 | bufs[cbuf].input.scroll) % 327 | bufs[cbuf].input.nlines]; 328 | if (ln) 329 | { 330 | ifree (inp); 331 | inp->left.data = strdup (ln); 332 | inp->left.i = strlen (inp->left.data); 333 | inp->left.l = 0; 334 | } 335 | } 336 | else 337 | { 338 | ifree (inp); 339 | } 340 | return; 341 | } 342 | if ((mod == KEY_SLEFT) || (mod == KEY_CLEFT) 343 | || (mod == KEY_ALEFT)) 344 | { 345 | cbuf = max (cbuf - 1, 0); 346 | redraw_buffer (); 347 | return; 348 | } 349 | if ((mod == KEY_SRIGHT) || (mod == KEY_CRIGHT) 350 | || (mod == KEY_ARIGHT)) 351 | { 352 | cbuf = min (cbuf + 1, nbufs - 1); 353 | redraw_buffer (); 354 | return; 355 | } 356 | if ((mod == KEY_CUP) || (mod == KEY_AUP)) 357 | { 358 | bufs[cbuf].ascroll--; 359 | redraw_buffer (); 360 | return; 361 | } 362 | if ((mod == KEY_CDOWN) || (mod == KEY_ADOWN)) 363 | { 364 | bufs[cbuf].ascroll++; 365 | redraw_buffer (); 366 | return; 367 | } 368 | if ((mod == KEY_SHOME) || (mod == KEY_CHOME) 369 | || (mod == KEY_AHOME)) 370 | { 371 | bufs[cbuf].scroll = 372 | bufs[cbuf].filled ? (bufs[cbuf].ptr + 373 | 1) % 374 | bufs[cbuf].nlines : 0; 375 | bufs[cbuf].ascroll = 0; 376 | redraw_buffer (); 377 | return; 378 | } 379 | if ((mod == KEY_SEND) || (mod == KEY_CEND) 380 | || (mod == KEY_AEND)) 381 | { 382 | bufs[cbuf].scroll = bufs[cbuf].ptr; 383 | bufs[cbuf].ascroll = 0; 384 | redraw_buffer (); 385 | return; 386 | } 387 | } 388 | if ((c & 0xe0) == 0xc0) // 110xxxxx -> 2 bytes of UTF-8 389 | { 390 | int d = getchar (); 391 | if (d && (d != EOF) && ((d & 0xc0) == 0x80)) // 10xxxxxx - UTF middlebyte 392 | append_char (&inp->left.data, &inp->left.l, 393 | &inp->left.i, d); 394 | else 395 | ungetc (d, stdin); 396 | return; 397 | } 398 | if ((c & 0xf0) == 0xe0) // 1110xxxx -> 3 bytes of UTF-8 399 | { 400 | int d = getchar (); 401 | if (d && (d != EOF) && ((d & 0xc0) == 0x80)) // 10xxxxxx - UTF middlebyte 402 | { 403 | int e = getchar (); 404 | if (e && (e != EOF) && ((e & 0xc0) == 0x80)) // 10xxxxxx - UTF middlebyte 405 | { 406 | append_char (&inp->left.data, &inp->left.l, 407 | &inp->left.i, d); 408 | append_char (&inp->left.data, &inp->left.l, 409 | &inp->left.i, e); 410 | } 411 | else 412 | { 413 | ungetc (e, stdin); 414 | ungetc (d, stdin); 415 | } 416 | } 417 | else 418 | ungetc (d, stdin); 419 | return; 420 | } 421 | if ((c & 0xf8) == 0xf0) // 11110xxx -> 4 bytes of UTF-8 422 | { 423 | int d = getchar (); 424 | if (d && (d != EOF) && ((d & 0xc0) == 0x80)) // 10xxxxxx - UTF middlebyte 425 | { 426 | int e = getchar (); 427 | if (e && (e != EOF) && ((e & 0xc0) == 0x80)) // 10xxxxxx - UTF middlebyte 428 | { 429 | int f = getchar (); 430 | if (f && (f != EOF) && ((f & 0xc0) == 0x80)) // 10xxxxxx - UTF middlebyte 431 | { 432 | append_char (&inp->left.data, 433 | &inp->left.l, 434 | &inp->left.i, d); 435 | append_char (&inp->left.data, 436 | &inp->left.l, 437 | &inp->left.i, e); 438 | append_char (&inp->left.data, 439 | &inp->left.l, 440 | &inp->left.i, f); 441 | } 442 | else 443 | { 444 | ungetc (f, stdin); 445 | ungetc (e, stdin); 446 | ungetc (d, stdin); 447 | } 448 | } 449 | else 450 | { 451 | ungetc (e, stdin); 452 | ungetc (d, stdin); 453 | } 454 | } 455 | else 456 | ungetc (d, stdin); 457 | return; 458 | } 459 | if (c == '\n') 460 | { 461 | *state = 3; 462 | char out[inp->left.i + inp->right.i + 1]; 463 | sprintf (out, "%s%s", inp->left.data ? inp->left.data : "", 464 | inp->right.data ? inp->right.data : ""); 465 | addtoibuf (&bufs[cbuf].input, out); 466 | ifree (inp); 467 | return; 468 | } 469 | return; 470 | } 471 | 472 | char *slash_dequote (char *inp) 473 | { 474 | size_t l = strlen (inp) + 1; 475 | char *rv = (char *) malloc (l + 1); // we only get shorter, so this will be enough 476 | unsigned int o = 0; 477 | while ((*inp) && (o < l)) // o>=l should never happen, but it's covered just in case 478 | { 479 | if (*inp == '\\') // \n, \r, \\, \ooo (\0 remains escaped) 480 | { 481 | char c = *++inp; 482 | switch (c) 483 | { 484 | case 'n': 485 | rv[o++] = '\n'; 486 | inp++; 487 | break; 488 | case 'r': 489 | rv[o++] = '\r'; 490 | inp++; 491 | break; 492 | case '\\': 493 | rv[o++] = '\\'; 494 | inp++; 495 | break; 496 | case '0': // \000 to \377 are octal escapes 497 | case '1': 498 | case '2': 499 | case '3': 500 | { 501 | int digits = 0; 502 | int oval = c - '0'; // Octal VALue 503 | while (isdigit (inp[1]) 504 | && (inp[1] < '8') 505 | && (++digits < 3)) 506 | { 507 | oval *= 8; 508 | oval += (*++inp) - '0'; 509 | } 510 | if (oval) 511 | { 512 | rv[o++] = oval; 513 | } 514 | else // \0 is a special case (it remains escaped) 515 | { 516 | rv[o++] = '\\'; 517 | if (o < l) 518 | rv[o++] = '0'; 519 | } 520 | inp++; 521 | } 522 | break; 523 | default: 524 | rv[o++] = '\\'; 525 | break; 526 | } 527 | } 528 | else 529 | { 530 | rv[o++] = *inp++; 531 | } 532 | } 533 | rv[o] = 0; 534 | return (rv); 535 | } 536 | 537 | int cmd_handle (char *inp, char **qmsg, fd_set * master, int *fdmax) // old state=3; return new state 538 | { 539 | char *cmd = inp + 1; 540 | if (*cmd == '/') //msg sends /msg 541 | return (talk (cmd)); 542 | char *args = strchr (cmd, ' '); 543 | if (args) 544 | *args++ = 0; 545 | return call_cmd (cmd, args, qmsg, master, fdmax); 546 | } 547 | 548 | void initibuf (ibuffer * i) 549 | { 550 | i->nlines = buflines; 551 | i->ptr = 0; 552 | i->scroll = 0; 553 | i->filled = false; 554 | i->line = (char **) malloc (i->nlines * sizeof (char *)); 555 | } 556 | 557 | void addtoibuf (ibuffer * i, char *data) 558 | { 559 | if (i) 560 | { 561 | if (i->filled) 562 | { 563 | if (i->line[i->ptr]) 564 | free (i->line[i->ptr]); 565 | } 566 | i->line[i->ptr++] = strdup (data ? data : ""); 567 | if (i->ptr >= i->nlines) 568 | { 569 | i->ptr = 0; 570 | i->filled = true; 571 | } 572 | i->scroll = 0; 573 | } 574 | } 575 | 576 | void freeibuf (ibuffer * i) 577 | { 578 | if (i->line) 579 | { 580 | int l; 581 | for (l = 0; l < (i->filled ? i->nlines : i->ptr); l++) 582 | { 583 | if (i->line[l]) 584 | free (i->line[l]); 585 | } 586 | free (i->line); 587 | } 588 | } 589 | 590 | char back_ichar (ichar * buf) 591 | { 592 | char c = 0; 593 | if (buf->i) 594 | { 595 | c = buf->data[--(buf->i)]; 596 | buf->data[(buf->i)] = 0; 597 | } 598 | return (c); 599 | } 600 | 601 | char front_ichar (ichar * buf) 602 | { 603 | char c = 0; 604 | if (buf->i) 605 | { 606 | c = buf->data[0]; 607 | memmove (buf->data, buf->data + 1, buf->i--); 608 | } 609 | return (c); 610 | } 611 | 612 | size_t i_firstlen (ichar src) 613 | { 614 | if (!src.i) 615 | return (0); 616 | size_t u; 617 | if (isutf8 (src.data, &u)) 618 | return (u); 619 | return (1); 620 | } 621 | 622 | size_t i_lastlen (ichar src) 623 | { 624 | size_t start = max (src.i, 4) - 4, prev = start; 625 | size_t u; 626 | while (start < src.i) 627 | { 628 | prev = start; 629 | if (isutf8 (src.data + start, &u)) 630 | start += u; 631 | else if (src.data[start] & 0x80) 632 | start++; 633 | else 634 | { 635 | start++; 636 | if (start + 1 >= src.i) 637 | break; 638 | } 639 | } 640 | return (start - prev); 641 | } 642 | 643 | void i_move (iline * inp, ssize_t bytes) 644 | { 645 | bool fw = (bytes > 0); 646 | size_t b = fw ? bytes : -bytes; // can't use abs() because we don't know what length a size_t is (do we need labs()? llabs()?) 647 | char c; 648 | for (size_t i = 0; i < b; i++) 649 | if (fw) 650 | { 651 | if ((c = front_ichar (&inp->right))) 652 | append_char (&inp->left.data, &inp->left.l, 653 | &inp->left.i, c); 654 | } 655 | else if ((c = back_ichar (&inp->left))) 656 | prepend_char (&inp->right.data, &inp->right.l, 657 | &inp->right.i, c); 658 | } 659 | 660 | void ifree (iline * buf) 661 | { 662 | free (buf->left.data); 663 | free (buf->right.data); 664 | buf->left.data = NULL; 665 | buf->right.data = NULL; 666 | buf->left.i = buf->left.l = 0; 667 | buf->right.i = buf->right.l = 0; 668 | } 669 | 670 | void i_home (iline * inp) 671 | { 672 | if (inp->left.i) 673 | { 674 | size_t b = inp->left.i + inp->right.i; 675 | char *nr = (char *) malloc (b + 1); 676 | sprintf (nr, "%s%s", inp->left.data ? inp->left.data : "", 677 | inp->right.data ? inp->right.data : ""); 678 | ifree (inp); 679 | inp->right.data = nr; 680 | inp->right.i = b; 681 | inp->right.l = b + 1; 682 | } 683 | } 684 | 685 | void i_end (iline * inp) 686 | { 687 | if (inp->right.i) 688 | { 689 | size_t b = inp->left.i + inp->right.i; 690 | char *nl = (char *) malloc (b + 1); 691 | sprintf (nl, "%s%s", inp->left.data ? inp->left.data : "", 692 | inp->right.data ? inp->right.data : ""); 693 | ifree (inp); 694 | inp->left.data = nl; 695 | inp->left.i = b; 696 | inp->left.l = b + 1; 697 | } 698 | } 699 | -------------------------------------------------------------------------------- /input.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | quIRC - simple terminal-based IRC client 5 | Copyright (C) 2010-13 Edward Cree 6 | 7 | See quirc.c for license information 8 | input: handle input routines 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | typedef struct 19 | { 20 | char *data; 21 | size_t l; 22 | size_t i; 23 | } 24 | ichar; 25 | 26 | typedef struct 27 | { 28 | ichar left; 29 | ichar right; 30 | } 31 | iline; 32 | 33 | typedef struct 34 | { 35 | int nlines; 36 | int ptr; 37 | int scroll; 38 | bool filled; 39 | char **line; 40 | } 41 | ibuffer; 42 | 43 | #include "names.h" 44 | 45 | extern bool ttab; 46 | 47 | void inputchar(iline *inp, int *state); 48 | char * slash_dequote(char *inp); 49 | int cmd_handle(char *inp, char **qmsg, fd_set *master, int *fdmax); 50 | void initibuf(ibuffer *i); 51 | void addtoibuf(ibuffer *i, char *data); 52 | void freeibuf(ibuffer *i); 53 | char back_ichar(ichar *buf); // returns the deleted char 54 | char front_ichar(ichar *buf); // returns the deleted char 55 | void ifree(iline *buf); 56 | 57 | void i_home(iline *inp); 58 | void i_end(iline *inp); 59 | -------------------------------------------------------------------------------- /irc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | quIRC - simple terminal-based IRC client 5 | Copyright (C) 2010-13 Edward Cree 6 | 7 | See quirc.c for license information 8 | irc: networking functions 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | typedef enum 28 | { 29 | RFC1459, 30 | STRICT_RFC1459, 31 | ASCII 32 | } 33 | cmap; 34 | 35 | typedef struct 36 | { 37 | char *prefix; 38 | char *cmd; 39 | int nargs; 40 | char *args[15]; // RFC specifies maximum of 15 args 41 | } 42 | message; 43 | 44 | #include "colour.h" 45 | #include "names.h" 46 | #include "osconf.h" 47 | 48 | #define MQUOTE '\020' 49 | 50 | extern volatile sig_atomic_t sigpipe, sigwinch, sigusr1; 51 | 52 | void handle_signals(int); // handles sigpipe, sigwinch and sigusr1 53 | 54 | #if ASYNCH_NL 55 | typedef struct _nl_list 56 | { 57 | struct gaicb *nl_details; 58 | int reconn_b; 59 | servlist *autoent; // filled out by autoconnect 60 | char *pass; 61 | struct _nl_list *prev, *next; 62 | } 63 | nl_list; 64 | extern nl_list *nl_active; 65 | nl_list *irc_connect(char *server, const char *portno); // non-blocking NL 66 | int irc_conn_found(nl_list **list, fd_set *master, int *fdmax); // non-blocking; call this when the getaddrinfo_a() has finished 67 | #else 68 | int irc_connect(char *server, const char *portno, fd_set *master, int *fdmax); // non-blocking 69 | #endif 70 | int irc_conn_rest(int b, char *nick, char *username, char *passwd, char *fullname); // call this when the non-blocking connect() has finished 71 | int autoconnect(fd_set *master, int *fdmax, servlist *serv); 72 | int irc_tx(int fd, char * packet); 73 | int irc_rx(int fd, char ** data, fd_set *master); 74 | 75 | message irc_breakdown(char *packet); 76 | void message_free(message pkt); 77 | void prefix_split(char * prefix, char **src, char **user, char **host); 78 | 79 | void low_quote(char *from, char to[512]); 80 | char * low_dequote(char *buf); 81 | 82 | char irc_to_upper(char c, cmap casemapping); 83 | char irc_to_lower(char c, cmap casemapping); 84 | int irc_strcasecmp(const char *c1, const char *c2, cmap casemapping); 85 | int irc_strncasecmp(const char *c1, const char *c2, int n, cmap casemapping); 86 | 87 | // Send an IRC PRIVMSG (ie. ordinary talking) 88 | int talk(char *iinput); 89 | 90 | // Received-IRC message handlers 91 | int irc_numeric(message pkt, int b); 92 | int rx_ping(message pkt, int b); 93 | int rx_mode(message pkt, int b); // the first MODE triggers auto-join 94 | int rx_kill(message pkt, int b, fd_set *master); 95 | int rx_kick(message pkt, int b); 96 | int rx_error(message pkt, int b, fd_set *master); 97 | int rx_privmsg(message pkt, int b, bool notice); 98 | int rx_topic(message pkt, int b); 99 | int rx_join(message pkt, int b); 100 | int rx_part(message pkt, int b); 101 | int rx_quit(message pkt, int b); 102 | int rx_nick(message pkt, int b); 103 | 104 | int ctcp_strip(char *msg, const char *src, int b2, bool ha, bool notice, bool priv, bool tx); // Strip the CTCPs out of a privmsg and handle them 105 | int ctcp(const char *msg, const char *src, int b2, bool ha, bool notice, bool priv, bool tx); // Handle CTCP (Client-To-Client Protocol) messages 106 | -------------------------------------------------------------------------------- /keys: -------------------------------------------------------------------------------- 1 | BS :key_backspace 2 | UP :key_up 3 | DOWN :key_down 4 | LEFT :key_left 5 | RIGHT :key_right 6 | HOME :key_home 7 | END :key_end 8 | DELETE :key_dc 9 | PGUP :key_ppage 10 | PGDN :key_npage 11 | CUP 1b5b313b3541 12 | AUP 1b5b313b3341 13 | CDOWN 1b5b313b3542 14 | ADOWN 1b5b313b3342 15 | SLEFT :key_sleft 16 | CLEFT 1b5b313b3544 17 | ALEFT 1b5b313b3344 18 | SRIGHT :key_sright 19 | CRIGHT 1b5b313b3543 20 | ARIGHT 1b5b313b3343 21 | SHOME :key_shome 22 | CHOME 1b5b313b3548 23 | AHOME 1b5b313b3348 24 | SEND :key_send 25 | CEND 1b5b313b3546 26 | AEND 1b5b313b3346 27 | CPGUP 1b5b353b357e 28 | APGUP 1b5b353b337e 29 | CPGDN 1b5b363b357e 30 | APGDN 1b5b363b337e 31 | F1 :key_f1 32 | F2 :key_f2 33 | F3 :key_f3 34 | F4 :key_f4 35 | F5 :key_f5 36 | F6 :key_f6 37 | F7 :key_f7 38 | F8 :key_f8 39 | F9 :key_f9 40 | F10 :key_f10 41 | F11 :key_f11 42 | F12 :key_f12 43 | -------------------------------------------------------------------------------- /logging.c: -------------------------------------------------------------------------------- 1 | /* 2 | quIRC - simple terminal-based IRC client 3 | Copyright (C) 2010-13 Edward Cree 4 | 5 | See quirc.c for license information 6 | logging: routines to log activity 7 | */ 8 | 9 | #include "logging.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "bits.h" 15 | #include "osconf.h" 16 | 17 | int log_add_plain(FILE *logf, mtype lm, prio lq, char lp, bool ls, const char *lt, const char *ltag, time_t ts); 18 | int log_add_symbolic(FILE *logf, mtype lm, prio lq, char lp, bool ls, const char *lt, const char *ltag, time_t ts); 19 | void safeprint(FILE *logf, const char *text, bool escape_spaces); 20 | 21 | int log_init(FILE *logf, logtype logt) 22 | { 23 | if(!logf) return(1); 24 | time_t now=time(NULL); 25 | switch(logt) 26 | { 27 | case LOGT_PLAIN:; 28 | char stamp[40]; 29 | struct tm *td=gmtime(&now); 30 | strftime(stamp, 40, "[%H:%M:%S]", td); 31 | fprintf(logf, "* Started PLAIN logging at %s\n", stamp); 32 | fflush(logf); 33 | return(0); 34 | case LOGT_SYMBOLIC: 35 | fprintf(logf, "u+%lld LOGSTART\n", (signed long long)now); 36 | fflush(logf); 37 | return(0); 38 | default: 39 | return(2); 40 | } 41 | } 42 | 43 | int log_add(FILE *logf, logtype logt, mtype lm, prio lq, char lp, bool ls, const char *lt, const char *ltag, time_t ts) 44 | { 45 | if(!logf) return(1); 46 | int e; 47 | switch(logt) 48 | { 49 | case LOGT_PLAIN: 50 | e=log_add_plain(logf, lm, lq, lp, ls, lt, ltag, ts); 51 | fflush(logf); 52 | return(e); 53 | case LOGT_SYMBOLIC: 54 | e=log_add_symbolic(logf, lm, lq, lp, ls, lt, ltag, ts); 55 | fflush(logf); 56 | return(e); 57 | default: 58 | return(2); 59 | } 60 | } 61 | 62 | int log_add_plain(FILE *logf, mtype lm, __attribute__((unused)) prio lq, char lp, __attribute__((unused)) bool ls, const char *lt, const char *ltag, time_t ts) 63 | { 64 | char stamp[40]; 65 | struct tm *td=gmtime(&ts); 66 | strftime(stamp, 40, "[%H:%M:%S] ", td); 67 | char *tag=strdup(ltag?ltag:""); 68 | switch(lm) 69 | { 70 | case MSG: 71 | { 72 | char mk[6]="<%s> "; 73 | if(lp) 74 | mk[0]=mk[3]=lp; 75 | crush(&tag, 16); 76 | char *ntag=mktag(mk, tag); 77 | free(tag); 78 | tag=ntag; 79 | } 80 | break; 81 | case NOTICE: 82 | { 83 | if(*tag) 84 | { 85 | crush(&tag, 16); 86 | char *ntag=mktag("(from %s) ", tag); 87 | free(tag); 88 | tag=ntag; 89 | } 90 | } 91 | break; 92 | case PREFORMAT: 93 | break; 94 | case ACT: 95 | { 96 | crush(&tag, 16); 97 | char *ntag=mktag("* %s ", tag); 98 | free(tag); 99 | tag=ntag; 100 | } 101 | break; 102 | case QUIT_PREFORMAT: 103 | break; 104 | case JOIN: 105 | case PART: 106 | case QUIT: 107 | case NICK: 108 | { 109 | crush(&tag, 16); 110 | char *ntag=mktag("=%s= ", tag); 111 | free(tag); 112 | tag=ntag; 113 | } 114 | break; 115 | case MODE: 116 | break; 117 | case STA: 118 | free(tag); 119 | return(0); 120 | break; 121 | case ERR: 122 | break; 123 | case UNK: 124 | break; 125 | case UNK_NOTICE: 126 | if(*tag) 127 | { 128 | crush(&tag, 16); 129 | char *ntag=mktag("(from %s) ", tag); 130 | free(tag); 131 | tag=ntag; 132 | } 133 | break; 134 | case UNN: 135 | break; 136 | default: 137 | break; 138 | } 139 | fprintf(logf, "%s%s%s\n", stamp, tag, lt); 140 | free(tag); 141 | return(0); 142 | } 143 | 144 | int log_add_symbolic(FILE *logf, mtype lm, prio lq, char lp, bool ls, const char *lt, const char *ltag, time_t ts) 145 | { 146 | if(!logf) return(1); 147 | fprintf(logf, "u+"PRINTMAX" %s %s %c %c ", CASTINTMAX ts, mtype_name(lm), prio_name(lq), lp?lp:'0', ls?'>':'<'); 148 | safeprint(logf, ltag, true); 149 | fputc(' ', logf); 150 | safeprint(logf, lt, false); 151 | fputc('\n', logf); 152 | return(0); 153 | } 154 | 155 | void safeprint(FILE *logf, const char *text, bool es) 156 | { 157 | if(!logf) return; 158 | if(!text) return; 159 | while(*text) 160 | { 161 | if((!isprint(*text)) || (es&&isspace(*text))) 162 | fprintf(logf, "\\%03o", (unsigned char)*text); 163 | else 164 | fputc(*text, logf); 165 | text++; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /logging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | quIRC - simple terminal-based IRC client 4 | Copyright (C) 2010-13 Edward Cree 5 | 6 | See quirc.c for license information 7 | logging: routines to log activity 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include "types.h" 14 | 15 | int log_init(FILE *logf, logtype logt); 16 | int log_add(FILE *logf, logtype logt, mtype lm, prio lq, char lp, bool ls, const char *lt, const char *ltag, time_t ts); 17 | -------------------------------------------------------------------------------- /man.in: -------------------------------------------------------------------------------- 1 | .TH QUIRC 1 2012-06-14 Linux "User Manuals" 2 | .mso www.tmac 3 | .SH NAME 4 | quirc \- console-mode IRC client 5 | .SH SYNOPSIS 6 | .B quirc 7 | .BI [--server= server "] [--nick=" nick "] [--chan=" chan "]" 8 | .BR [ options... ] 9 | .br 10 | .B quirc -h|--help 11 | .br 12 | .B quirc -V|--version 13 | .SH DESCRIPTION 14 | This man page briefly documents the 15 | .B quirc 16 | command. The full documentation is available in 17 | .SM XHTML 18 | format at 19 | .I "$PREFIX/share/doc/quirc/readme.htm" 20 | and there is a tutorial for new users at 21 | .IR "$PREFIX/share/doc/quirc/tutorial.htm" . 22 | .P 23 | .B quirc 24 | is an 25 | .SM IRC 26 | (Internet Relay Chat) client with a roguelike interface. 27 | The main body of the display shows lines typed by you and others, output of 28 | commands, etc. At the bottom are a tab strip indicating the servers and 29 | channels to which you're connected (and which one you are viewing at present) 30 | and, on the last row, a space to enter commands. 31 | .P 32 | If you have used an 33 | .SM IRC 34 | client before, very little about quirc should be surprising to you. 35 | Ctrl-cursors switch tabs and scroll the display; 36 | .RI F n 37 | switches to tab 38 | .I n 39 | (except for F12 which switches to tab 0). 40 | The commands 41 | .IR /server , 42 | .IR /join , 43 | .IR /part , 44 | .IR /nick , 45 | .I /me 46 | and 47 | .I /quit 48 | all do exactly what you would expect them to do. 49 | .SH OPTIONS 50 | For a full listing of command-line options run 51 | .BR "quirc --help" ; 52 | the most commonly used options are: 53 | .TP 54 | .B --no-server 55 | Overrides any servers listed in the rc file, preventing 56 | .B quirc 57 | from auto-connecting. 58 | .TP 59 | .B --no-chan 60 | Overrides any channels listed in the rc file, preventing 61 | .B quirc 62 | from auto-joining. Implied by 63 | .BR --no-server . 64 | .TP 65 | .B --check,--lint 66 | Parses the configuration, including the rc file and command line, and reports 67 | any errors; then exits. 68 | .TP 69 | .BI --width= width " --height=" height 70 | Set the width and height of the terminal. Normally 71 | .B quirc 72 | will determine this from envvars and ioctls, but these options allow you to 73 | override that. 74 | .TP 75 | .B --quiet 76 | Start up in Quiet Mode; various status messages will be suppressed. This can 77 | also be changed during runtime with 78 | .IR "/set quiet" . 79 | .TP 80 | .B --debug 81 | Start up in Debug Mode; extra diagnostic output will be produced, including 82 | transcripts of the 83 | .SM IRC 84 | protocol messages send and received. 85 | This can also be changed during runtime with 86 | .IR "/set debug" . 87 | .SH ENVIRONMENT 88 | .IP HOME 89 | The user's home directory. Configuration files will be read from 90 | .I $(HOME)/.quirc/ 91 | .IP USER 92 | The user's login name. This is sent to the 93 | .SM IRC 94 | server at connect time as part of the 95 | .B USER 96 | command. 97 | .IP LINES,COLUMNS 98 | The size of the user's terminal; 99 | .B quirc 100 | uses this when painting the screen. If not set, it falls back on other 101 | methods including ioctls and the 102 | .B --width 103 | and 104 | .B --height 105 | command-line options. The ioctls will also be used when catching 106 | .BR SIGWINCH . 107 | .SH FILES 108 | .I ~/.quirc/rc 109 | .RS 110 | Runtime configuration file. For details see the manual and 111 | .IR "$PREFIX/share/doc/quirc/config_ref.htm" . 112 | .RE 113 | .I ~/.quirc/keys 114 | .RS 115 | Keymapping file. Users of 116 | .SM ANSI 117 | terminals can probably ignore this. 118 | .RE 119 | .SH CONFORMING TO 120 | .IP \(bu 121 | RFC 1459, "Internet Relay Chat Protocol" 122 | .URL "http://tools.ietf.org/html/rfc1459" 123 | .IP \(bu 124 | "The Client-To-Client Protocol", 125 | .URL "http://www.irchelp.org/irchelp/rfc/ctcpspec.html" 126 | .IP \(bu 127 | "IRC RPL_ISUPPORT Numeric Definition", 128 | .URL "http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt" 129 | (Draft specification) 130 | .SH BUGS 131 | The rc file has its own format; arguably, it should just be a series of 132 | commands to run. However, there are certain parts to it that can't yet be 133 | done that way; servlists and chanlists, custom colours. 134 | .P 135 | Unicode (particularly double-width characters) can confuse the line-breaking 136 | code. 137 | .P 138 | Connecting to some servers may hang (if they don't say anything until we've 139 | identified ourselves). 140 | .P 141 | If malloc ever fails, everything goes horribly wrong because we don't check it 142 | in most places. 143 | .SH SEE ALSO 144 | .IR "$PREFIX/share/doc/quirc/readme.htm" ", " "$PREFIX/share/doc/quirc/tutorial.htm" 145 | -------------------------------------------------------------------------------- /names.c: -------------------------------------------------------------------------------- 1 | /* 2 | quIRC - simple terminal-based IRC client 3 | Copyright (C) 2010-13 Edward Cree 4 | 5 | See quirc.c for license information 6 | names: handling for name lists 7 | */ 8 | 9 | #include "names.h" 10 | #include "strbuf.h" 11 | 12 | name *n_dup(const name *list) 13 | { 14 | if(!list) return(NULL); 15 | name *rv=malloc(sizeof(name)); 16 | if(!rv) return(NULL); 17 | *rv=*list; 18 | rv->prev=NULL; 19 | name *next=rv->next=n_dup(list->next); 20 | if(next) 21 | next->prev=rv; 22 | rv->data=strdup(list->data); 23 | rv->prefixes=malloc(rv->npfx*sizeof(prefix)); 24 | if(rv->prefixes) 25 | { 26 | for(unsigned int i=0;inpfx;i++) 27 | rv->prefixes[i]=list->prefixes[i]; 28 | } 29 | else 30 | rv->npfx=0; 31 | return(rv); 32 | } 33 | 34 | name * n_add(name ** list, const char *data, cmap casemapping) 35 | { 36 | if(!list) 37 | return(NULL); 38 | n_cull(list, data, casemapping); 39 | name *ptr=*list; 40 | bool last=false; 41 | while(ptr&&(irc_strcasecmp(data, ptr->data, casemapping)>0)) 42 | { 43 | if(ptr->next) 44 | { 45 | ptr=ptr->next; 46 | } 47 | else 48 | { 49 | last=true; 50 | break; 51 | } 52 | } 53 | name *new=(name *)malloc(sizeof(name)); 54 | new->data=strdup(data); 55 | new->icase=false; 56 | new->pms=false; 57 | new->npfx=0; 58 | new->prefixes=NULL; 59 | if(ptr) 60 | { 61 | if(last) 62 | { 63 | new->prev=ptr; 64 | new->next=NULL; 65 | ptr->next=new; 66 | } 67 | else 68 | { 69 | if(ptr->prev) 70 | { 71 | ptr->prev->next=new; 72 | new->prev=ptr->prev; 73 | } 74 | else 75 | { 76 | *list=new; 77 | new->prev=NULL; 78 | } 79 | ptr->prev=new; 80 | new->next=ptr; 81 | } 82 | } 83 | else 84 | { 85 | new->prev=NULL; 86 | new->next=*list; 87 | if(*list) 88 | (*list)->prev=new; 89 | *list=new; 90 | } 91 | return(new); 92 | } 93 | 94 | int n_cull(name ** list, const char *data, cmap casemapping) 95 | { 96 | if(!list) 97 | return(0); 98 | int rv=0; 99 | name *curr=*list; 100 | while(curr) 101 | { 102 | name *next=curr->next; 103 | if(irc_strcasecmp(curr->data, data, casemapping)==0) 104 | { 105 | if(curr->prev) 106 | { 107 | curr->prev->next=curr->next; 108 | } 109 | else 110 | { 111 | *list=curr->next; 112 | } 113 | if(curr->next) 114 | curr->next->prev=curr->prev; 115 | free(curr->data); 116 | free(curr); 117 | rv++; 118 | } 119 | curr=next; 120 | } 121 | return(rv); 122 | } 123 | 124 | void n_free(name * list) 125 | { 126 | if(list) 127 | { 128 | n_free(list->next); 129 | free(list->data); 130 | free(list->prefixes); 131 | free(list); 132 | } 133 | } 134 | 135 | int i_match(name * list, const char *nm, bool pm, cmap casemapping) 136 | { 137 | int rv=0; 138 | name *curr=list; 139 | while(curr) 140 | { 141 | if((!pm)||(curr->pms)) 142 | { 143 | char *data; 144 | if(curr->icase) 145 | { 146 | size_t l,i; 147 | init_char(&data, &l, &i); 148 | char *p=curr->data; 149 | while(*p) 150 | { 151 | append_char(&data, &l, &i, '['); 152 | if(irc_to_lower(*p, casemapping)=='^') 153 | append_char(&data, &l, &i, '\\'); 154 | if(irc_to_lower(*p, casemapping)==']') 155 | append_char(&data, &l, &i, '\\'); 156 | if(irc_to_lower(*p, casemapping)=='\\') 157 | append_char(&data, &l, &i, '\\'); 158 | append_char(&data, &l, &i, irc_to_lower(*p, casemapping)); // because of irc casemapping weirdness, we have to do our REG_ICASE by hand 159 | if(irc_to_upper(*p, casemapping)==']') 160 | append_char(&data, &l, &i, '\\'); 161 | if(irc_to_upper(*p, casemapping)=='\\') 162 | append_char(&data, &l, &i, '\\'); 163 | append_char(&data, &l, &i, irc_to_upper(*p, casemapping)); 164 | append_char(&data, &l, &i, ']'); 165 | } 166 | } 167 | else 168 | { 169 | data=strdup(curr->data); 170 | } 171 | regex_t comp; 172 | if(regcomp(&comp, data, REG_EXTENDED|REG_NOSUB)==0) 173 | { 174 | if(regexec(&comp, nm, 0, NULL, 0)==0) 175 | { 176 | rv++; 177 | } 178 | regfree(&comp); 179 | } 180 | free(data); 181 | } 182 | curr=curr->next; 183 | } 184 | return(rv); 185 | } 186 | 187 | int i_cull(name ** list, const char *nm) 188 | { 189 | if(!list) 190 | return(0); 191 | int rv=0; 192 | char *pnm=strdup(nm?nm:""); 193 | char *src, *user, *host; 194 | prefix_split(pnm, &src, &user, &host); 195 | char rm[strlen(src)+strlen(user)+strlen(host)+3]; 196 | sprintf(rm, "%s!%s@%s", src, user, host); 197 | name *curr=*list; 198 | while(curr) 199 | { 200 | name *next=curr->next; 201 | regex_t comp; 202 | if(regcomp(&comp, curr->data, REG_EXTENDED|REG_NOSUB|(curr->icase?REG_ICASE:0))==0) 203 | { 204 | if(regexec(&comp, rm, 0, NULL, 0)==0) 205 | { 206 | if(curr->prev) 207 | { 208 | curr->prev->next=curr->next; 209 | } 210 | else 211 | { 212 | *list=curr->next; 213 | } 214 | if(curr->next) 215 | curr->next->prev=curr->prev; 216 | free(curr->data); 217 | free(curr); 218 | rv++; 219 | } 220 | regfree(&comp); 221 | } 222 | curr=next; 223 | } 224 | return(rv); 225 | } 226 | 227 | void i_list(void) 228 | { 229 | int count=0; 230 | if(bufs[cbuf].type==CHANNEL) 231 | { 232 | name *curr=bufs[cbuf].ilist; 233 | while(curr) 234 | { 235 | name *next=curr->next; 236 | char tag[20]; 237 | sprintf(tag, "/ignore -l: C%s%s\t", curr->pms?"p":"", curr->icase?"i":""); 238 | add_to_buffer(cbuf, STA, NORMAL, 0, false, curr->data, tag); 239 | count++; 240 | curr=next; 241 | } 242 | } 243 | if(bufs[cbuf].server) 244 | { 245 | name *curr=SERVER(cbuf).ilist; 246 | while(curr) 247 | { 248 | name *next=curr->next; 249 | char tag[20]; 250 | sprintf(tag, "/ignore -l: S%s%s\t", curr->pms?"p":"", curr->icase?"i":""); 251 | add_to_buffer(cbuf, STA, NORMAL, 0, false, curr->data, tag); 252 | count++; 253 | curr=next; 254 | } 255 | } 256 | name *curr=bufs[0].ilist; 257 | while(curr) 258 | { 259 | name *next=curr->next; 260 | char tag[20]; 261 | sprintf(tag, "/ignore -l: *%s%s\t", curr->pms?"p":"", curr->icase?"i":""); 262 | add_to_buffer(cbuf, STA, NORMAL, 0, false, curr->data, tag); 263 | count++; 264 | curr=next; 265 | } 266 | if(!count) 267 | { 268 | add_to_buffer(cbuf, STA, NORMAL, 0, false, "No active ignores for this view.", "/ignore -l: "); 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /names.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | quIRC - simple terminal-based IRC client 5 | Copyright (C) 2010-13 Edward Cree 6 | 7 | See quirc.c for license information 8 | names: handling for name lists 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | typedef struct 19 | { 20 | char letter; 21 | char pfx; 22 | } 23 | prefix; 24 | 25 | typedef struct _name 26 | { 27 | bool icase; // only used for ignore lists; true = case-insensitive 28 | bool pms; // only used for ignore lists; true = affect private messages 29 | unsigned int npfx; 30 | prefix *prefixes; 31 | char *data; // is a unique pointer (eg. from strdup()), and must be free()d 32 | struct _name *next, *prev; 33 | } 34 | name; 35 | 36 | #include "buffer.h" 37 | #include "irc.h" 38 | 39 | name *n_dup(const name *list); 40 | name * n_add(name ** list, const char *data, cmap casemapping); // returns pointer to the added name. Calls n_cull() first 41 | int n_cull(name ** list, const char *data, cmap casemapping); // returns number of instances culled 42 | void n_free(name * list); 43 | 44 | int i_match(name * list, const char *nm, bool pm, cmap casemapping); // returns number of matches 45 | int i_cull(name ** list, const char *nm); // returns number of instances culled 46 | void i_list(void); 47 | -------------------------------------------------------------------------------- /numeric.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | quIRC - simple terminal-based IRC client 4 | Copyright (C) 2010-13 Edward Cree 5 | 6 | See quirc.c for license information 7 | numeric: IRC numeric replies 8 | */ 9 | 10 | /*** 11 | This file is generated by gen_numerics.py from masters in numerics.py. 12 | Do not make edits directly to this file! Edit the masters instead. 13 | ***/ 14 | 15 | /* 16 | A symbolic name defined here does not necessarily imply recognition or decoding of that numeric reply. 17 | Some numeric replies are non-normative; that is, they are not defined in the original RFC1459 or its superseding RFC2812, but instead are either defined in other, non-normative documents, or are entirely experimental. These are denoted with an X before the name (of the form RPL_X_BOGOSITY); where a numeric is being identified purely on the basis of usage "in the wild", the symbolic name will be completely arbitrary and may not align with usage elsewhere. 18 | */ 19 | 20 | /* Error replies */ 21 | #define ERR_NOSUCHNICK 401 // " :No such nick/channel" Used to indicate the nickname parameter supplied to a command is currently unused. 22 | #define ERR_NOSUCHSERVER 402 // " :No such server" Used to indicate the server name given currently doesn't exist. 23 | #define ERR_NOSUCHCHANNEL 403 // " :No such channel" Used to indicate the given channel name is invalid. 24 | #define ERR_CANNOTSENDTOCHAN 404 // " :Cannot send to channel" Sent to a user who is either (a) not on a channel which is mode +n or (b) not a chanop (or mode +v) on a channel which has mode +m set and is trying to send a PRIVMSG message to that channel. 25 | #define ERR_TOOMANYCHANNELS 405 // " :You have joined too many channels" Sent to a user when they have joined the maximum number of allowed channels and they try to join another channel. 26 | #define ERR_WASNOSUCHNICK 406 // " :There was no such nickname" Returned by WHOWAS to indicate there is no history information for that nickname. 27 | #define ERR_TOOMANYTARGETS 407 // " :Duplicate recipients. No message delivered" Returned to a client which is attempting to send PRIVMSG/NOTICE using the user@host destination format and for a user@host which has several occurrences. 28 | #define ERR_NOORIGIN 409 // ":No origin specified" PING or PONG message missing the originator parameter which is required since these commands must work without valid prefixes. 29 | #define ERR_NORECIPIENT 411 // ":No recipient given ()" 30 | #define ERR_NOTEXTTOSEND 412 // ":No text to send" 31 | #define ERR_NOTOPLEVEL 413 // " :No toplevel domain specified" 32 | #define ERR_WILDTOPLEVEL 414 // " :Wildcard in toplevel domain" 412 - 414 are returned by PRIVMSG to indicate that the message wasn't delivered for some reason. ERR_NOTOPLEVEL and ERR_WILDTOPLEVEL are errors that are returned when an invalid use of "PRIVMSG $" or "PRIVMSG #" is attempted. 33 | #define ERR_UNKNOWNCOMMAND 421 // " :Unknown command" Returned to a registered client to indicate that the command sent is unknown by the server. 34 | #define ERR_NOMOTD 422 // ":MOTD File is missing" Server's MOTD file could not be opened by the server. 35 | #define ERR_NOADMININFO 423 // " :No administrative info available" Returned by a server in response to an ADMIN message when there is an error in finding the appropriate information. 36 | #define ERR_FILEERROR 424 // ":File error doing on " Generic error message used to report a failed file operation during the processing of a message. 37 | #define ERR_NONICKNAMEGIVEN 431 // ":No nickname given" Returned when a nickname parameter expected for a command and isn't found. 38 | #define ERR_ERRONEOUSNICKNAME 432 // " :Erroneous nickname" Returned after receiving a NICK message which contains characters which do not fall in the defined set. See section x.x.x for details on valid nicknames. 39 | #define ERR_NICKNAMEINUSE 433 // " :Nickname is already in use" Returned when a NICK message is processed that results in an attempt to change to a currently existing nickname. 40 | #define ERR_NICKCOLLISION 436 // " :Nickname collision KILL" Returned by a server to a client when it detects a nickname collision (registered of a NICK that already exists by another server). 41 | #define ERR_USERNOTINCHANNEL 441 // " :They aren't on that channel" Returned by the server to indicate that the target user of the command is not on the given channel. 42 | #define ERR_NOTONCHANNEL 442 // " :You're not on that channel" Returned by the server whenever a client tries to perform a channel effecting command for which the client isn't a member. 43 | #define ERR_USERONCHANNEL 443 // " :is already on channel" Returned when a client tries to invite a user to a channel they are already on. 44 | #define ERR_NOLOGIN 444 // " :User not logged in" Returned by the summon after a SUMMON command for a user was unable to be performed since they were not logged in. 45 | #define ERR_SUMMONDISABLED 445 // ":SUMMON has been disabled" Returned as a response to the SUMMON command. Must be returned by any server which does not implement it. 46 | #define ERR_USERSDISABLED 446 // ":USERS has been disabled" Returned as a response to the USERS command. Must be returned by any server which does not implement it. 47 | #define ERR_NOTREGISTERED 451 // ":You have not registered" Returned by the server to indicate that the client must be registered before the server will allow it to be parsed in detail. 48 | #define ERR_NEEDMOREPARAMS 461 // " :Not enough parameters" Returned by the server by numerous commands to indicate to the client that it didn't supply enough parameters. 49 | #define ERR_ALREADYREGISTRED 462 // ":You may not reregister" Returned by the server to any link which tries to change part of the registered details (such as password or user details from second USER message). 50 | #define ERR_NOPERMFORHOST 463 // ":Your host isn't among the privileged" Returned to a client which attempts to register with a server which does not been setup to allow connections from the host the attempted connection is tried. 51 | #define ERR_PASSWDMISMATCH 464 // ":Password incorrect" Returned to indicate a failed attempt at registering a connection for which a password was required and was either not given or incorrect. 52 | #define ERR_YOUREBANNEDCREEP 465 // ":You are banned from this server" Returned after an attempt to connect and register yourself with a server which has been setup to explicitly deny connections to you. 53 | #define ERR_KEYSET 467 // " :Channel key already set" 54 | #define ERR_CHANNELISFULL 471 // " :Cannot join channel (+l)" 55 | #define ERR_UNKNOWNMODE 472 // " :is unknown mode char to me" 56 | #define ERR_INVITEONLYCHAN 473 // " :Cannot join channel (+i)" 57 | #define ERR_BANNEDFROMCHAN 474 // " :Cannot join channel (+b)" 58 | #define ERR_BADCHANNELKEY 475 // " :Cannot join channel (+k)" 59 | #define ERR_NOPRIVILEGES 481 // ":Permission Denied- You're not an IRC operator" Any command requiring operator privileges to operate must return this error to indicate the attempt was unsuccessful. 60 | #define ERR_CHANOPRIVSNEEDED 482 // " :You're not channel operator" Any command requiring 'chanop' privileges (such as MODE messages) must return this error if the client making the attempt is not a chanop on the specified channel. 61 | #define ERR_CANTKILLSERVER 483 // ":You cant kill a server!" Any attempts to use the KILL command on a server are to be refused and this error returned directly to the client. 62 | #define ERR_NOOPERHOST 491 // ":No O-lines for your host" If a client sends an OPER message and the server has not been configured to allow connections from the client's host as an operator, this error must be returned. 63 | #define ERR_UMODEUNKNOWNFLAG 501 // ":Unknown MODE flag" Returned by the server to indicate that a MODE message was sent with a nickname parameter and that the a mode flag sent was not recognized. 64 | #define ERR_USERSDONTMATCH 502 // ":Cant change mode for other users" Error sent to any user trying to view or change the user mode for a user other than themselves. 65 | 66 | /* Command responses */ 67 | #define RPL_X_ISUPPORT 005 // http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt 68 | #define RPL_TRACELINK 200 // "Link " 69 | #define RPL_TRACECONNECTING 201 // "Try. " 70 | #define RPL_TRACEHANDSHAKE 202 // "H.S. " 71 | #define RPL_TRACEUNKNOWN 203 // "???? []" 72 | #define RPL_TRACEOPERATOR 204 // "Oper " 73 | #define RPL_TRACEUSER 205 // "User " 74 | #define RPL_TRACESERVER 206 // "Serv S C @" 75 | #define RPL_TRACENEWTYPE 208 // " 0 " 76 | #define RPL_STATSLINKINFO 211 // "