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