1 /*
2 * cmd_proc.c: command processor (/join, /leave, etc)
3 * Copyright (C) 2002-2004 Saulius Menkevicius
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 *
19 * $Id: cmd_proc.c,v 1.5 2004/12/29 15:58:24 bobas Exp $
20 */
21
22 #include <string.h>
23 #include <glib.h>
24
25 #include "prefs.h"
26 #include "main.h"
27 #include "sess.h"
28 #include "user.h"
29 #include "gui.h"
30 #include "cmd_proc.h"
31
32 #define LOG_CMDPROC N_("Command: ")
33
34 /** struct defs */
35 typedef void (*cmd_handler_proc_t)(GString *);
36
37 #define CMD_ALIAS_MAX 2
38 struct cmd_handler_def_struct {
39 const char *alias[CMD_ALIAS_MAX];
40
41 /* note: cmd handler can modify the GString they get ptr to,
42 * though they cannot free it
43 */
44 cmd_handler_proc_t proc;
45 };
46
47 /** forward refs
48 **********************/
49 static GString * extract_first_word(GString *);
50
51 static void cmd_quit(GString *);
52 static void cmd_info(GString *);
53 static void cmd_query(GString *);
54 static void cmd_join(GString *);
55 static void cmd_close(GString *);
56 static void cmd_me(GString *);
57 static void cmd_nickname(GString *);
58 static void cmd_topic(GString *);
59
60 /** static vars
61 **********************/
62 static GList * hist_list,
63 * hist_current;
64 static int hist_size;
65
66 static struct cmd_handler_def_struct
67 cmd_handlers[] =
68 {
69 {{ "exit", "quit" }, cmd_quit },
70 {{ "info", NULL }, cmd_info },
71 {{ "query", "q" }, cmd_query },
72 {{ "join", "j" }, cmd_join },
73 {{ "close" "leave" }, cmd_close },
74 {{ "me", NULL }, cmd_me },
75 {{ "nickname", "nick" }, cmd_nickname },
76 {{ "topic", NULL }, cmd_topic },
77 {{ NULL }}
78 };
79
80 /** static routines
81 **********************/
82
83 /** chop_str:
84 * removes leading and trailing blanks
85 * returns:
86 * ptr to the same str
87 */
88 GString *
chop_str(GString * str)89 chop_str(GString * str)
90 {
91 char * s;
92
93 g_assert(str && str->str);
94
95 /* find beginning of leading blanks */
96 for(s=str->str; *s==' '; s++) ;
97 if(!*s) {
98 /* only blanks found:
99 * return empty string */
100 g_string_erase(str, 0, -1);
101 return str;
102 }
103
104 /* remove leading blanks */
105 g_string_erase(str, 0, s - str->str);
106
107 /* find beginning of trailing blanks */
108 for(s = str->str + str->len - 1; *s==' '; s--) ;
109
110 /* remove trailing blanks */
111 g_string_erase(str, s - str->str+1, -1);
112
113 return str;
114 }
115
116 /** check_channel_name:
117 * checks & modifies channel name (if needed)
118 * (str can be specified as NULL, 0 returned in that case)
119 */
120 static int
check_channel_name(GString * str)121 check_channel_name(GString * str)
122 {
123 if(!str || str->len<2
124 || (str->len > MAX_CHAN_LENGTH + (unsigned)(str->str[0]=='#'))
125 || strchr(str->str+1, '#')
126 || strchr(str->str, ' ')
127 ) return 0;
128
129 if(str->str[0]!='#') {
130 g_string_prepend_c(str, '#');
131 }
132 return 1;
133 }
134
135 /* cmd handlers */
136 static void
cmd_quit(GString * line_s)137 cmd_quit(GString *line_s)
138 {
139 raise_event(EVENT_IFACE_EXIT, NULL, 0);
140 }
141
142 static void
cmd_info(GString * line_s)143 cmd_info(GString *line_s)
144 {
145 GString * nick;
146 gpointer uid;
147
148 g_assert(line_s);
149
150 /* get the id of user we want to get info about */
151 nick = extract_first_word(line_s);
152 if(nick) {
153 uid = user_by_name(nick->str);
154
155 /* check if the user specified is valid nickname */
156 if(!uid) {
157 log_fevent(
158 _(LOG_CMDPROC),
159 g_strdup_printf(_("/info: \"%s\" is unknown user"), nick->str));
160 } else {
161 /* emit user-info-request event */
162 raise_event(EVENT_IFACE_USER_INFO_REQ, uid, 0);
163 }
164 g_string_free(nick, TRUE);
165 }
166 else {
167 /* no nickname specified */
168 log_event(_(LOG_CMDPROC), _("/info: no nickname specified"));
169 }
170 }
171
172 static void
cmd_query(GString * line_s)173 cmd_query(GString *line_s)
174 {
175 GString * nick;
176 gpointer uid;
177
178 g_assert(line_s);
179
180 /* get the id of user we want to get info about */
181 nick = extract_first_word(line_s);
182 if(nick) {
183 uid = user_by_name(nick->str);
184
185 /* check if the user specified is valid nickname */
186 if(!uid) {
187 log_ferror(
188 _(LOG_CMDPROC),
189 g_strdup_printf(_("/query: \"%s\" is unknown user"), nick->str));
190 } else {
191 /* emit query-request event */
192 raise_event(EVENT_IFACE_USER_OPEN_REQ, uid, 0);
193 }
194 g_string_free(nick, TRUE);
195 }
196 else {
197 /* no nickname specified */
198 log_error(_(LOG_CMDPROC), _("/query: no nickname specified"));
199 }
200 }
201
202 static void
cmd_join(GString * line_s)203 cmd_join(GString *line_s)
204 {
205 GString * target;
206
207 g_assert(line_s);
208
209 target = extract_first_word(line_s);
210 if(!check_channel_name(target)) {
211 /* channel name unspecified */
212 log_error(_(LOG_CMDPROC), _("/join: Invalid/unspecified channel"));
213 if(target) g_string_free(target, TRUE);
214 return;
215 }
216
217 raise_event(EVENT_IFACE_JOIN_CHANNEL, target->str, 0);
218
219 g_string_free(target, TRUE);
220 }
221
222 static void
cmd_close(GString * line_s)223 cmd_close(GString *line_s)
224 {
225 g_assert(line_s);
226
227 raise_event(EVENT_IFACE_PAGE_CLOSE, sess_current(), 0);
228 }
229
230 static void
cmd_me(GString * line_s)231 cmd_me(GString * line_s)
232 {
233 g_assert(line_s);
234
235 chop_str(line_s);
236 raise_event(EVENT_CMDPROC_SESSION_TEXT, (gpointer)chop_str(line_s)->str, 1);
237 }
238
239 static void
cmd_nickname(GString * line_s)240 cmd_nickname(GString * line_s)
241 {
242 GString * n;
243
244 g_assert(line_s);
245
246 n = extract_first_word(line_s);
247 if(n) {
248 raise_event(EVENT_IFACE_NICKNAME_ENTERED, (void*)n->str, 0);
249 g_string_free(n, TRUE);
250 }
251 }
252
253 static void
cmd_topic(GString * arg)254 cmd_topic(GString * arg)
255 {
256 gpointer sess;
257 gpointer v[2];
258
259 sess = sess_current();
260 if(sess_type(sess)==SESSTYPE_CHANNEL) {
261 v[0] = sess;
262 v[1] = (gpointer)arg->str;
263
264 raise_event(EVENT_IFACE_TOPIC_ENTER, v, 0);
265 } else {
266 log_event(_(LOG_CMDPROC), _("/topic: topic can be set on channel sessions only"));
267 }
268 }
269
270 static void
history_free()271 history_free()
272 {
273 GList * l;
274
275 /* free strings */
276 for(l = hist_list; l; l = l->next) {
277 g_assert(l->data);
278 g_free(l->data);
279 }
280
281 /* free list */
282 if(hist_list) {
283 g_list_free(hist_list);
284 hist_list = NULL;
285 hist_current = NULL;
286 hist_size = 0;
287 }
288 }
289
290 static void
history_append(const char * text)291 history_append(const char * text)
292 {
293 g_assert(text);
294
295 /* check if we didn't max out number of history lines */
296 if(hist_size==CMD_PROC_HISTORY_LINES) {
297 /* remove first entry */
298 g_assert(hist_list && hist_list->data);
299
300 g_free(hist_list->data);
301 hist_list = g_list_remove_link(hist_list, hist_list);
302 } else {
303 /* show history size */
304 ++ hist_size;
305 }
306
307 /* append new text at the entry */
308 hist_list = g_list_append(hist_list, g_strdup(text));
309
310 /* point current history after last */
311 hist_current = NULL;
312 }
313
314 static void
history_show(int next)315 history_show(int next)
316 {
317 g_assert((hist_size && hist_list)
318 || (!hist_size && !hist_list));
319
320 if(!hist_size) return;
321
322 if(hist_current==0) {
323 /* point to last */
324 hist_current = g_list_last(hist_list);
325 } else {
326 if(next)
327 {
328 if(hist_current->next==NULL) {
329 /* nowhere to go */
330 return;
331 }
332 hist_current = hist_current->next;
333 } else {
334 if(hist_current->prev==NULL) {
335 /* nowhere to go */
336 return;
337 }
338 hist_current = hist_current->prev;
339 }
340 }
341
342 /* update (gui) text */
343 g_assert(hist_current->data);
344 raise_event(
345 EVENT_CMDPROC_SET_TEXT,
346 hist_current->data, 0);
347 }
348
349
350 /** extract_first_word:
351 * does it
352 *
353 * modifies
354 * `from' GString
355 * returns:
356 * 1. new GString with `word' in it, or
357 * 2. NULL, if no word could be extracted
358 */
359 static GString *
extract_first_word(GString * from)360 extract_first_word(
361 GString * from)
362 {
363 GString * word;
364 char * s, * b;
365
366 g_assert(from && from->str);
367
368 /* skip blanks */
369 for(b = from->str; *b==' '; b++) ;
370
371 if(*b=='\0') {
372 /* no word to extract */
373 return NULL;
374 }
375
376 /* skip word */
377 for(s = b; *s!=' ' && *s!='\0'; s++) ;
378
379 /* extract word */
380 word = g_string_new(NULL);
381 g_string_append_len(word, b, s - b);
382 g_string_erase(from, 0, s - from->str);
383
384 return word;
385 }
386
387 /** invoke_cmd_handler:
388 * finds and invokes handler for command
389 */
390 static int
invoke_cmd_handler(const char * cmd_name,GString * line_s)391 invoke_cmd_handler(
392 const char * cmd_name,
393 GString * line_s) /* can be modified, though not freed, by cmd handler*/
394 {
395 struct cmd_handler_def_struct * cmd_h;
396 int a_nr;
397
398 g_assert(cmd_name && line_s && *cmd_name);
399
400 for(cmd_h = cmd_handlers; cmd_h->alias[0]!=NULL; cmd_h++)
401 {
402 for(a_nr=0; a_nr < CMD_ALIAS_MAX; a_nr++) {
403 if(cmd_h->alias[a_nr] && !g_strcasecmp(cmd_h->alias[a_nr], cmd_name)) {
404 g_assert(cmd_h->proc);
405 cmd_h->proc(line_s);
406
407 return TRUE;
408 }
409 }
410 }
411
412 /* obviously we found no handler
413 if we managed to get till here */
414
415 return FALSE;
416 }
417
418 /** process_command:
419 * processes user command
420 */
421 static void
process_command(const char * text)422 process_command(const char * text)
423 {
424 GString * line_s, * cmd_s;
425 g_assert(text);
426
427 line_s = g_string_new(text);
428
429 cmd_s = extract_first_word(line_s);
430 if(cmd_s==NULL) {
431 /* "empty" cmd, e.g. `/ mistype param1 param2',
432 * and not `/correct param1 param2'
433 */
434 log_error(_(LOG_CMDPROC), _("Invalid (empty) command \"/\""));
435 g_string_free(line_s, TRUE);
436 return;
437 }
438
439 /* find command handler or report error */
440 if(! invoke_cmd_handler(cmd_s->str, line_s)) {
441 /* invalid/unknown command */
442 log_ferror(_(LOG_CMDPROC),
443 g_strdup_printf(_("Invalid/unknown command \"%s\""), cmd_s->str));
444 }
445
446 g_string_free(line_s, TRUE);
447 g_string_free(cmd_s, TRUE);
448 }
449
450 /** parse_text:
451 * parses text, which user entered into text box
452 */
453 static gboolean
parse_text(const char * orig_text)454 parse_text(const char * orig_text)
455 {
456 GString * text = g_string_new(orig_text);
457
458 /* remove leading & trailing blanks */
459 chop_str(text);
460
461 /* string carries no info, just blanks */
462 if(!text->len) {
463 g_string_free(text, TRUE);
464 return FALSE;
465 }
466
467 /* check if it is a command */
468 if(text->str[0]==CMD_PROC_COMMAND_CHAR) {
469 process_command(text->str+1);
470 }
471 else {
472 /* text destined to current session */
473 raise_event(EVENT_CMDPROC_SESSION_TEXT, (gpointer)text->str, 0);
474 }
475
476 g_string_free(text, TRUE);
477 return TRUE;
478 }
479
480 static void
text_entered(const char * text)481 text_entered(
482 const char * text)
483 {
484 g_assert(text);
485
486 if(!parse_text(text)) {
487 /* invalid, probably an empty, line: ignore */
488 return;
489 }
490
491 /* insert this thing into history */
492 history_append(text);
493
494 /* clear current text */
495 raise_event(EVENT_CMDPROC_SET_TEXT, "", 0);
496 }
497
498 static void
cmd_proc_event_cb(enum app_event_enum e,gpointer p,gint i)499 cmd_proc_event_cb(
500 enum app_event_enum e,
501 gpointer p, gint i)
502 {
503 switch(e) {
504 case EVENT_MAIN_INIT:
505 hist_current = hist_list = NULL;
506 hist_size = 0;
507 break;
508 case EVENT_MAIN_CLOSE:
509 history_free();
510 break;
511 case EVENT_IFACE_TEXT_ENTER:
512 text_entered((const char*)p);
513 break;
514 case EVENT_IFACE_HISTORY:
515 history_show(i);
516 break;
517 default:
518 break; /* nothing else */
519 }
520 }
521
522 /** exported routines
523 **********************/
524
cmd_proc_register()525 void cmd_proc_register()
526 {
527 register_event_cb(cmd_proc_event_cb, EVENT_MAIN | EVENT_IFACE);
528 }
529
530