1 /* X-Chat
2  * Copyright (C) 1998 Peter Zelezny.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <string.h>
24 #include <fcntl.h>
25 #include <ctype.h>
26 
27 #ifdef WIN32
28 #include <io.h>
29 #else
30 #include <unistd.h>
31 #endif
32 
33 #define GLIB_DISABLE_DEPRECATION_WARNINGS
34 #include "fe-gtk.h"
35 
36 #include "../common/hexchat.h"
37 #include "../common/hexchatc.h"
38 #include "../common/cfgfiles.h"
39 #include "../common/fe.h"
40 #include "../common/userlist.h"
41 #include "../common/outbound.h"
42 #include "../common/util.h"
43 #include "../common/text.h"
44 #include "../common/plugin.h"
45 #include "../common/typedef.h"
46 #include <gdk/gdkkeysyms.h>
47 #include "gtkutil.h"
48 #include "menu.h"
49 #include "xtext.h"
50 #include "palette.h"
51 #include "maingui.h"
52 #include "textgui.h"
53 #include "fkeys.h"
54 
55 static void replace_handle (GtkWidget * wid);
56 void key_action_tab_clean (void);
57 
58 /***************** Key Binding Code ******************/
59 
60 /* NOTES:
61 
62    To add a new action:
63    1) inc KEY_MAX_ACTIONS
64    2) write the function at the bottom of this file (with all the others)
65    FIXME: Write about calling and returning
66    3) Add it to key_actions
67 
68    --AGL
69 
70  */
71 
72 /* Remember that the *number* of actions is this *plus* 1 --AGL */
73 #define KEY_MAX_ACTIONS 14
74 
75 struct key_binding
76 {
77 	guint keyval;					  /* keyval from gdk */
78 	GdkModifierType mod;			  /* Modifier, always ran through key_modifier_get_valid() */
79 	int action;						  /* Index into key_actions */
80 	char *data1, *data2;			  /* Pointers to strings, these must be freed */
81 };
82 
83 struct key_action
84 {
85 	int (*handler) (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
86 						 struct session * sess);
87 	char *name;
88 	char *help;
89 };
90 
91 struct gcomp_data
92 {
93 	char data[CHANLEN];
94 	int elen;
95 };
96 
97 static int key_load_kbs (void);
98 static int key_save_kbs (void);
99 static int key_action_handle_command (GtkWidget * wid, GdkEventKey * evt,
100 												  char *d1, char *d2,
101 												  struct session *sess);
102 static int key_action_page_switch (GtkWidget * wid, GdkEventKey * evt,
103 											  char *d1, char *d2, struct session *sess);
104 int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
105 							  struct session *sess);
106 static int key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt,
107 											  char *d1, char *d2, struct session *sess);
108 static int key_action_set_buffer (GtkWidget * wid, GdkEventKey * evt,
109 											 char *d1, char *d2, struct session *sess);
110 static int key_action_history_up (GtkWidget * wid, GdkEventKey * evt,
111 											 char *d1, char *d2, struct session *sess);
112 static int key_action_history_down (GtkWidget * wid, GdkEventKey * evt,
113 												char *d1, char *d2, struct session *sess);
114 static int key_action_tab_comp (GtkWidget * wid, GdkEventKey * evt, char *d1,
115 										  char *d2, struct session *sess);
116 static int key_action_comp_chng (GtkWidget * wid, GdkEventKey * evt, char *d1,
117                                                                                         char *d2, struct session *sess);
118 static int key_action_replace (GtkWidget * wid, GdkEventKey * evt, char *d1,
119 										 char *d2, struct session *sess);
120 static int key_action_move_tab_left (GtkWidget * wid, GdkEventKey * evt,
121 												 char *d1, char *d2,
122 												 struct session *sess);
123 static int key_action_move_tab_right (GtkWidget * wid, GdkEventKey * evt,
124 												  char *d1, char *d2,
125 												  struct session *sess);
126 static int key_action_move_tab_family_left (GtkWidget * wid, GdkEventKey * evt,
127 												 char *d1, char *d2,
128 												 struct session *sess);
129 static int key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * evt,
130 												  char *d1, char *d2,
131 												  struct session *sess);
132 static int key_action_put_history (GtkWidget * wid, GdkEventKey * evt,
133 												  char *d1, char *d2,
134 												  struct session *sess);
135 
136 static GSList *keybind_list = NULL;
137 
138 static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
139 
140 	{key_action_handle_command, "Run Command",
141 	 N_("The \002Run Command\002 action runs the data in Data 1 as if it had been typed into the entry box where you pressed the key sequence. Thus it can contain text (which will be sent to the channel/person), commands or user commands. When run all \002\\n\002 characters in Data 1 are used to deliminate separate commands so it is possible to run more than one command. If you want a \002\\\002 in the actual text run then enter \002\\\\\002")},
142 	{key_action_page_switch, "Change Page",
143 	 N_("The \002Change Page\002 command switches between pages in the notebook. Set Data 1 to the page you want to switch to. If Data 2 is set to anything then the switch will be relative to the current position. Set Data 1 to auto to switch to the page with the most recent and important activity (queries first, then channels with hilight, channels with dialogue, channels with other data)")},
144 	{key_action_insert, "Insert in Buffer",
145 	 N_("The \002Insert in Buffer\002 command will insert the contents of Data 1 into the entry where the key sequence was pressed at the current cursor position")},
146 	{key_action_scroll_page, "Scroll Page",
147 	 N_("The \002Scroll Page\002 command scrolls the text widget up or down one page or one line. Set Data 1 to either Top, Bottom, Up, Down, +1 or -1.")},
148 	{key_action_set_buffer, "Set Buffer",
149 	 N_("The \002Set Buffer\002 command sets the entry where the key sequence was entered to the contents of Data 1")},
150 	{key_action_history_up, "Last Command",
151 	 N_("The \002Last Command\002 command sets the entry to contain the last command entered - the same as pressing up in a shell")},
152 	{key_action_history_down, "Next Command",
153 	 N_("The \002Next Command\002 command sets the entry to contain the next command entered - the same as pressing down in a shell")},
154 	{key_action_tab_comp, "Complete nick/command",
155 	 N_("This command changes the text in the entry to finish an incomplete nickname or command. If Data 1 is set then double-tabbing in a string will select the last nick, not the next")},
156 	{key_action_comp_chng, "Change Selected Nick",
157 	 N_("This command scrolls up and down through the list of nicks. If Data 1 is set to anything it will scroll up, else it scrolls down")},
158 	{key_action_replace, "Check For Replace",
159 	 N_("This command checks the last word entered in the entry against the replace list and replaces it if it finds a match")},
160 	{key_action_move_tab_left, "Move front tab left",
161 	 N_("This command moves the front tab left by one")},
162 	{key_action_move_tab_right, "Move front tab right",
163 	 N_("This command moves the front tab right by one")},
164 	{key_action_move_tab_family_left, "Move tab family left",
165 	 N_("This command moves the current tab family to the left")},
166 	{key_action_move_tab_family_right, "Move tab family right",
167 	 N_("This command moves the current tab family to the right")},
168 	{key_action_put_history, "Push input line into history",
169 	 N_("Push input line into history but doesn't send to server")},
170 };
171 
172 #define default_kb_cfg \
173 	"ACCEL=<Primary>Page_Up\nChange Page\nD1:-1\nD2:Relative\n\n"\
174 	"ACCEL=<Primary>Page_Down\nChange Page\nD1:1\nD2:Relative\n\n"\
175 	"ACCEL=<Alt>9\nChange Page\nD1:9\nD2!\n\n"\
176 	"ACCEL=<Alt>8\nChange Page\nD1:8\nD2!\n\n"\
177 	"ACCEL=<Alt>7\nChange Page\nD1:7\nD2!\n\n"\
178 	"ACCEL=<Alt>6\nChange Page\nD1:6\nD2!\n\n"\
179 	"ACCEL=<Alt>5\nChange Page\nD1:5\nD2!\n\n"\
180 	"ACCEL=<Alt>4\nChange Page\nD1:4\nD2!\n\n"\
181 	"ACCEL=<Alt>3\nChange Page\nD1:3\nD2!\n\n"\
182 	"ACCEL=<Alt>2\nChange Page\nD1:2\nD2!\n\n"\
183 	"ACCEL=<Alt>1\nChange Page\nD1:1\nD2!\n\n"\
184 	"ACCEL=<Alt>grave\nChange Page\nD1:auto\nD2!\n\n"\
185 	"ACCEL=<Primary>o\nInsert in Buffer\nD1:\017\nD2!\n\n"\
186 	"ACCEL=<Primary>b\nInsert in Buffer\nD1:\002\nD2!\n\n"\
187 	"ACCEL=<Primary>k\nInsert in Buffer\nD1:\003\nD2!\n\n"\
188 	"ACCEL=<Primary>i\nInsert in Buffer\nD1:\035\nD2!\n\n"\
189 	"ACCEL=<Primary>u\nInsert in Buffer\nD1:\037\nD2!\n\n"\
190 	"ACCEL=<Primary>r\nInsert in Buffer\nD1:\026\nD2!\n\n"\
191 	"ACCEL=<Shift>Page_Down\nChange Selected Nick\nD1!\nD2!\n\n"\
192 	"ACCEL=<Shift>Page_Up\nChange Selected Nick\nD1:Up\nD2!\n\n"\
193 	"ACCEL=Page_Down\nScroll Page\nD1:Down\nD2!\n\n"\
194 	"ACCEL=<Primary>Home\nScroll Page\nD1:Top\nD2!\n\n"\
195 	"ACCEL=<Primary>End\nScroll Page\nD1:Bottom\nD2!\n\n"\
196 	"ACCEL=Page_Up\nScroll Page\nD1:Up\nD2!\n\n"\
197 	"ACCEL=<Shift>Down\nScroll Page\nD1:+1\nD2!\n\n"\
198 	"ACCEL=<Shift>Up\nScroll Page\nD1:-1\nD2!\n\n"\
199 	"ACCEL=Down\nNext Command\nD1!\nD2!\n\n"\
200 	"ACCEL=Up\nLast Command\nD1!\nD2!\n\n"\
201 	"ACCEL=Tab\nComplete nick/command\nD1!\nD2!\n\n"\
202 	"ACCEL=<Shift>ISO_Left_Tab\nComplete nick/command\nD1:Previous\nD2!\n\n"\
203 	"ACCEL=space\nCheck For Replace\nD1!\nD2!\n\n"\
204 	"ACCEL=Return\nCheck For Replace\nD1!\nD2!\n\n"\
205 	"ACCEL=KP_Enter\nCheck For Replace\nD1!\nD2!\n\n"\
206 	"ACCEL=<Primary>Tab\nComplete nick/command\nD1:Up\nD2!\n\n"\
207 	"ACCEL=<Alt>Left\nMove front tab left\nD1!\nD2!\n\n"\
208 	"ACCEL=<Alt>Right\nMove front tab right\nD1!\nD2!\n\n"\
209 	"ACCEL=<Primary><Shift>Page_Up\nMove tab family left\nD1!\nD2!\n\n"\
210 	"ACCEL=<Primary><Shift>Page_Down\nMove tab family right\nD1!\nD2!\n\n"\
211 	"ACCEL=F9\nRun Command\nD1:/GUI MENU TOGGLE\nD2!\n\n"
212 
213 void
key_init()214 key_init ()
215 {
216 	if (key_load_kbs () == 1)
217 	{
218 		fe_message (_("There was an error loading key"
219 							" bindings configuration"), FE_MSG_ERROR);
220 	}
221 }
222 
223 static inline int
key_get_action_from_string(char * text)224 key_get_action_from_string (char *text)
225 {
226 	int i;
227 
228 	for (i = 0; i < KEY_MAX_ACTIONS + 1; i++)
229 	{
230 		if (strcmp (key_actions[i].name, text) == 0)
231 		{
232 			return i;
233 		}
234 	}
235 
236 	return 0;
237 }
238 
239 static void
key_free(gpointer data)240 key_free (gpointer data)
241 {
242 	struct key_binding *kb = (struct key_binding*)data;
243 
244 	g_return_if_fail (kb != NULL);
245 
246 	g_free (kb->data1);
247 	g_free (kb->data2);
248 	g_free (kb);
249 }
250 
251 /* Ok, here are the NOTES
252 
253    key_handle_key_press now handles all the key presses and history_keypress is
254    now defunct. It goes thru the linked list keys_root and finds a matching
255    key. It runs the action func and switches on these values:
256    0) Return
257    1) Find next
258    2) stop signal and return
259 
260    * history_keypress is now dead (and gone)
261    * key_handle_key_press now takes its role
262    * All the possible actions are in a struct called key_actions (in fkeys.c)
263    * it is made of {function, name, desc}
264    * key bindings can pass 2 *text* strings to the handler. If more options are nee
265    ded a format can be put on one of these options
266    * key actions are passed {
267    the entry widget
268    the Gdk event
269    data 1
270    data 2
271    session struct
272    }
273    * key bindings are stored in a linked list of key_binding structs
274    * which looks like {
275    guint keyval;  GDK keynumber
276    int action;  Index into key_actions
277    GdkModiferType mod; modifier, only ones from key_modifer_get_valid()
278    char *data1, *data2;  Pointers to strings, these must be freed
279    struct key_binding *next;
280    }
281    * remember that is (data1 || data2) != NULL then they need to be free()'ed
282 
283    --AGL
284 
285  */
286 
287 static inline GdkModifierType
key_modifier_get_valid(GdkModifierType mod)288 key_modifier_get_valid (GdkModifierType mod)
289 {
290 	GdkModifierType ret;
291 
292 #ifdef __APPLE__
293 	ret = mod & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK);
294 #else
295 	/* These masks work on both Windows and Unix */
296 	ret = mod & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK);
297 #endif
298 
299 	return ret;
300 }
301 
302 gboolean
key_handle_key_press(GtkWidget * wid,GdkEventKey * evt,session * sess)303 key_handle_key_press (GtkWidget *wid, GdkEventKey *evt, session *sess)
304 {
305 	struct key_binding *kb;
306 	int n;
307 	GSList *list;
308 
309 	/* where did this event come from? */
310 	list = sess_list;
311 	while (list)
312 	{
313 		sess = list->data;
314 		if (sess->gui->input_box == wid)
315 		{
316 			if (sess->gui->is_tab)
317 				sess = current_tab;
318 			break;
319 		}
320 		list = list->next;
321 	}
322 	if (!list)
323 		return FALSE;
324 	current_sess = sess;
325 
326 	if (plugin_emit_keypress (sess, evt->state, evt->keyval, gdk_keyval_to_unicode (evt->keyval)))
327 		return 1;
328 
329 	/* maybe the plugin closed this tab? */
330 	if (!is_session (sess))
331 		return 1;
332 
333 	list = keybind_list;
334 	while (list)
335 	{
336 		kb = (struct key_binding*)list->data;
337 
338 		if (kb->keyval == evt->keyval && kb->mod == key_modifier_get_valid (evt->state))
339 		{
340 			if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS)
341 				return 0;
342 
343 			/* Run the function */
344 			n = key_actions[kb->action].handler (wid, evt, kb->data1,
345 															 kb->data2, sess);
346 			switch (n)
347 			{
348 			case 0:
349 				return 1;
350 			case 2:
351 				g_signal_stop_emission_by_name (G_OBJECT (wid),
352 														"key_press_event");
353 				return 1;
354 			}
355 		}
356 		list = g_slist_next (list);
357 	}
358 
359 	switch (evt->keyval)
360 	{
361 	case GDK_KEY_space:
362 		key_action_tab_clean ();
363 		break;
364 	}
365 
366 	return 0;
367 }
368 
369 
370 /* ***** GUI code here ******************* */
371 
372 enum
373 {
374 	KEY_COLUMN,
375 	ACCEL_COLUMN,
376 	ACTION_COLUMN,
377 	D1_COLUMN,
378 	D2_COLUMN,
379 	N_COLUMNS
380 };
381 
382 static GtkWidget *key_dialog = NULL;
383 
384 static inline GtkTreeModel *
get_store(void)385 get_store (void)
386 {
387 	return gtk_tree_view_get_model (g_object_get_data (G_OBJECT (key_dialog), "view"));
388 }
389 
390 static void
key_dialog_print_text(GtkXText * xtext,char * text)391 key_dialog_print_text (GtkXText *xtext, char *text)
392 {
393 	unsigned int old = prefs.hex_stamp_text;
394 	prefs.hex_stamp_text = 0;	/* temporarily disable stamps */
395 	gtk_xtext_clear (GTK_XTEXT (xtext)->buffer, 0);
396 	PrintTextRaw (GTK_XTEXT (xtext)->buffer, text, 0, 0);
397 	prefs.hex_stamp_text = old;
398 }
399 
400 static void
key_dialog_set_key(GtkCellRendererAccel * accel,gchar * pathstr,guint accel_key,GdkModifierType accel_mods,guint hardware_keycode,gpointer userdata)401 key_dialog_set_key (GtkCellRendererAccel *accel, gchar *pathstr, guint accel_key,
402 					GdkModifierType accel_mods, guint hardware_keycode, gpointer userdata)
403 {
404 	GtkTreeModel *model = get_store ();
405 	GtkTreePath *path = gtk_tree_path_new_from_string (pathstr);
406 	GtkTreeIter iter;
407 	gchar *label_name, *accel_name;
408 
409 	/* Shift tab requires an exception, hopefully that list ends here.. */
410 	if (accel_key == GDK_KEY_Tab && accel_mods & GDK_SHIFT_MASK)
411 		accel_key = GDK_KEY_ISO_Left_Tab;
412 
413 	label_name = gtk_accelerator_get_label (accel_key, key_modifier_get_valid (accel_mods));
414 	accel_name = gtk_accelerator_name (accel_key, key_modifier_get_valid (accel_mods));
415 
416 	gtk_tree_model_get_iter (model, &iter, path);
417 	gtk_list_store_set (GTK_LIST_STORE (model), &iter, KEY_COLUMN, label_name,
418 						ACCEL_COLUMN, accel_name, -1);
419 
420 	gtk_tree_path_free (path);
421 	g_free (label_name);
422 	g_free (accel_name);
423 }
424 
425 static void
key_dialog_combo_changed(GtkCellRendererCombo * combo,gchar * pathstr,GtkTreeIter * new_iter,gpointer data)426 key_dialog_combo_changed (GtkCellRendererCombo *combo, gchar *pathstr,
427 						GtkTreeIter *new_iter, gpointer data)
428 {
429 	GtkTreeModel *model;
430 	GtkXText *xtext;
431 	gchar *actiontext = NULL;
432 	gint action;
433 
434 	xtext = GTK_XTEXT (g_object_get_data (G_OBJECT (key_dialog), "xtext"));
435 	model = GTK_TREE_MODEL (data);
436 
437 	gtk_tree_model_get (model, new_iter, 0, &actiontext, -1);
438 
439 	if (actiontext)
440 	{
441 #ifdef WIN32
442 		/* We need to manually update the store */
443 		GtkTreePath *path;
444 		GtkTreeIter iter;
445 
446 		path = gtk_tree_path_new_from_string (pathstr);
447 		model = get_store ();
448 
449 		gtk_tree_model_get_iter (model, &iter, path);
450 		gtk_list_store_set (GTK_LIST_STORE (model), &iter, ACTION_COLUMN, actiontext, -1);
451 
452 		gtk_tree_path_free (path);
453 #endif
454 
455 		action = key_get_action_from_string (actiontext);
456 		key_dialog_print_text (xtext, key_actions[action].help);
457 
458 		g_free (actiontext);
459 	}
460 }
461 
462 static void
key_dialog_entry_edited(GtkCellRendererText * render,gchar * pathstr,gchar * new_text,gpointer data)463 key_dialog_entry_edited (GtkCellRendererText *render, gchar *pathstr, gchar *new_text, gpointer data)
464 {
465 	GtkTreeModel *model = get_store ();
466 	GtkTreePath *path = gtk_tree_path_new_from_string (pathstr);
467 	GtkTreeIter iter;
468 	gint column = GPOINTER_TO_INT (data);
469 
470 	gtk_tree_model_get_iter (model, &iter, path);
471 	gtk_list_store_set (GTK_LIST_STORE (model), &iter, column, new_text, -1);
472 
473 	gtk_tree_path_free (path);
474 }
475 
476 static gboolean
key_dialog_keypress(GtkWidget * wid,GdkEventKey * evt,gpointer userdata)477 key_dialog_keypress (GtkWidget *wid, GdkEventKey *evt, gpointer userdata)
478 {
479 	GtkTreeView *view = g_object_get_data (G_OBJECT (key_dialog), "view");
480 	GtkTreeModel *store;
481 	GtkTreeIter iter1, iter2;
482 	GtkTreeSelection *sel;
483 	GtkTreePath *path;
484 	gboolean handled = FALSE;
485 	int delta;
486 
487 	if (evt->state & GDK_SHIFT_MASK)
488 	{
489 		if (evt->keyval == GDK_KEY_Up)
490 		{
491 			handled = TRUE;
492 			delta = -1;
493 		}
494 		else if (evt->keyval == GDK_KEY_Down)
495 		{
496 			handled = TRUE;
497 			delta = 1;
498 		}
499 	}
500 
501 	if (handled)
502 	{
503 		sel = gtk_tree_view_get_selection (view);
504 		gtk_tree_selection_get_selected (sel, &store, &iter1);
505 		path = gtk_tree_model_get_path (store, &iter1);
506 		if (delta == 1)
507 			gtk_tree_path_next (path);
508 		else
509 			gtk_tree_path_prev (path);
510 		gtk_tree_model_get_iter (store, &iter2, path);
511 		gtk_tree_path_free (path);
512 		gtk_list_store_swap (GTK_LIST_STORE (store), &iter1, &iter2);
513 	}
514 
515 	return handled;
516 }
517 
518 static void
key_dialog_selection_changed(GtkTreeSelection * sel,gpointer userdata)519 key_dialog_selection_changed (GtkTreeSelection *sel, gpointer userdata)
520 {
521 	GtkTreeModel *model;
522 	GtkTreeIter iter;
523 	GtkXText *xtext;
524 	char *actiontext;
525 	int action;
526 
527 	if (!gtk_tree_selection_get_selected (sel, &model, &iter) || model == NULL)
528 		return;
529 
530 	xtext = GTK_XTEXT (g_object_get_data (G_OBJECT (key_dialog), "xtext"));
531 	gtk_tree_model_get (model, &iter, ACTION_COLUMN, &actiontext, -1);
532 
533 	if (actiontext)
534 	{
535 		action = key_get_action_from_string (actiontext);
536 		key_dialog_print_text (xtext, key_actions[action].help);
537 		g_free (actiontext);
538 	}
539 	else
540 		key_dialog_print_text (xtext, _("Select a row to get help information on its Action."));
541 }
542 
543 static void
key_dialog_close(GtkWidget * wid,gpointer userdata)544 key_dialog_close (GtkWidget *wid, gpointer userdata)
545 {
546 	gtk_widget_destroy (key_dialog);
547 	key_dialog = NULL;
548 }
549 
550 static void
key_dialog_save(GtkWidget * wid,gpointer userdata)551 key_dialog_save (GtkWidget *wid, gpointer userdata)
552 {
553 	GtkTreeModel *store = get_store ();
554 	GtkTreeIter iter;
555 	struct key_binding *kb;
556 	char *data1, *data2, *accel, *actiontext;
557 	guint keyval;
558 	GdkModifierType mod;
559 
560 	if (keybind_list)
561 	{
562 		g_slist_free_full (keybind_list, key_free);
563 		keybind_list = NULL;
564 	}
565 
566 	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
567 	{
568 		do
569 		{
570 			kb = g_new0 (struct key_binding, 1);
571 
572 			gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ACCEL_COLUMN, &accel,
573 															ACTION_COLUMN, &actiontext,
574 															D1_COLUMN, &data1,
575 															D2_COLUMN, &data2,
576 															-1);
577 			kb->data1 = data1;
578 			kb->data2 = data2;
579 
580 			if (accel)
581 			{
582 				gtk_accelerator_parse (accel, &keyval, &mod);
583 
584 				kb->keyval = keyval;
585 				kb->mod = key_modifier_get_valid (mod);
586 
587 				g_free (accel);
588 			}
589 
590 			if (actiontext)
591 			{
592 				kb->action = key_get_action_from_string (actiontext);
593 				g_free (actiontext);
594 			}
595 
596 			if (!accel || !actiontext)
597 				key_free (kb);
598 			else
599 				keybind_list = g_slist_append (keybind_list, kb);
600 
601 		}
602 		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
603 	}
604 
605 	if (key_save_kbs () == 0)
606 		key_dialog_close (wid, NULL);
607 }
608 
609 static void
key_dialog_add(GtkWidget * wid,gpointer userdata)610 key_dialog_add (GtkWidget *wid, gpointer userdata)
611 {
612 	GtkTreeView *view = g_object_get_data (G_OBJECT (key_dialog), "view");
613 	GtkTreeViewColumn *col;
614 	GtkListStore *store = GTK_LIST_STORE (get_store ());
615 	GtkTreeIter iter;
616 	GtkTreePath *path;
617 
618 	gtk_list_store_append (store, &iter);
619 
620 	/* make sure the new row is visible and selected */
621 	path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
622 	col = gtk_tree_view_get_column (view, ACTION_COLUMN);
623 	gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0.0, 0.0);
624 	gtk_tree_view_set_cursor (view, path, col, TRUE);
625 	gtk_tree_path_free (path);
626 }
627 
628 static void
key_dialog_delete(GtkWidget * wid,gpointer userdata)629 key_dialog_delete (GtkWidget *wid, gpointer userdata)
630 {
631 	GtkTreeView *view = g_object_get_data (G_OBJECT (key_dialog), "view");
632 	GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
633 	GtkTreeIter iter;
634 	GtkTreePath *path;
635 
636 	if (gtkutil_treeview_get_selected (view, &iter, -1))
637 	{
638 		/* delete this row, select next one */
639 		if (gtk_list_store_remove (store, &iter))
640 		{
641 			path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
642 			gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 1.0, 0.0);
643 			gtk_tree_view_set_cursor (view, path, NULL, FALSE);
644 			gtk_tree_path_free (path);
645 		}
646 	}
647 }
648 
649 static GtkWidget *
key_dialog_treeview_new(GtkWidget * box)650 key_dialog_treeview_new (GtkWidget *box)
651 {
652 	GtkWidget *scroll;
653 	GtkListStore *store, *combostore;
654 	GtkTreeViewColumn *col;
655 	GtkWidget *view;
656 	GtkCellRenderer *render;
657 	int i;
658 
659 	scroll = gtk_scrolled_window_new (NULL, NULL);
660 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
661 	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_IN);
662 
663 	store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
664 								G_TYPE_STRING, G_TYPE_STRING);
665 	g_return_val_if_fail (store != NULL, NULL);
666 
667 	view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
668 	gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view), TRUE);
669 	gtk_tree_view_set_enable_search (GTK_TREE_VIEW (view), FALSE);
670 	gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view), TRUE);
671 
672 	g_signal_connect (G_OBJECT (view), "key-press-event",
673 					G_CALLBACK (key_dialog_keypress), NULL);
674 	g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW(view))),
675 					"changed", G_CALLBACK (key_dialog_selection_changed), NULL);
676 
677 	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
678 
679 	render = gtk_cell_renderer_accel_new ();
680 	g_object_set (render, "editable", TRUE,
681 #ifndef WIN32
682 					"accel-mode", GTK_CELL_RENDERER_ACCEL_MODE_OTHER,
683 #endif
684 					NULL);
685 	g_signal_connect (G_OBJECT (render), "accel-edited",
686 					G_CALLBACK (key_dialog_set_key), NULL);
687 	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), KEY_COLUMN,
688 												"Key", render,
689 												"text", KEY_COLUMN,
690 												NULL);
691 
692 	render = gtk_cell_renderer_text_new ();
693 	gtk_tree_view_insert_column_with_attributes (
694 							GTK_TREE_VIEW (view), ACCEL_COLUMN,
695 							"Accel", render,
696 							"text", ACCEL_COLUMN,
697 							NULL);
698 
699 	combostore = gtk_list_store_new (1, G_TYPE_STRING);
700 	for (i = 0; i <= KEY_MAX_ACTIONS; i++)
701 	{
702 		GtkTreeIter iter;
703 
704 		if (key_actions[i].name[0])
705 		{
706 			gtk_list_store_append (combostore, &iter);
707 			gtk_list_store_set (combostore, &iter, 0, key_actions[i].name, -1);
708 		}
709 	}
710 
711 	render = gtk_cell_renderer_combo_new ();
712 	g_object_set (G_OBJECT (render), "model", combostore,
713 									"has-entry", FALSE,
714 									"editable", TRUE,
715 									"text-column", 0,
716 									NULL);
717 	g_signal_connect (G_OBJECT (render), "edited",
718 					G_CALLBACK (key_dialog_entry_edited), GINT_TO_POINTER (ACTION_COLUMN));
719 	g_signal_connect (G_OBJECT (render), "changed",
720 					G_CALLBACK (key_dialog_combo_changed), combostore);
721 	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), ACTION_COLUMN,
722 													"Action", render,
723 													"text", ACTION_COLUMN,
724 													NULL);
725 
726 	render = gtk_cell_renderer_text_new ();
727 	g_object_set (render, "editable", TRUE, NULL);
728 	g_signal_connect (G_OBJECT (render), "edited",
729 				G_CALLBACK (key_dialog_entry_edited), GINT_TO_POINTER (D1_COLUMN));
730 	gtk_tree_view_insert_column_with_attributes (
731 							GTK_TREE_VIEW (view), D1_COLUMN,
732 							"Data1", render,
733 							"text", D1_COLUMN,
734 							NULL);
735 
736 	render = gtk_cell_renderer_text_new ();
737 	g_object_set (render, "editable", TRUE, NULL);
738 	g_signal_connect (G_OBJECT (render), "edited",
739 				G_CALLBACK (key_dialog_entry_edited), GINT_TO_POINTER (D2_COLUMN));
740 	gtk_tree_view_insert_column_with_attributes (
741 							GTK_TREE_VIEW (view), D2_COLUMN,
742 							"Data2", render,
743 							"text", D2_COLUMN,
744 							NULL);
745 
746 	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), KEY_COLUMN);
747 	gtk_tree_view_column_set_fixed_width (col, 200);
748 	gtk_tree_view_column_set_resizable (col, TRUE);
749 	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), ACCEL_COLUMN);
750 	gtk_tree_view_column_set_visible (col, FALSE);
751 	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), ACTION_COLUMN);
752 	gtk_tree_view_column_set_fixed_width (col, 160);
753 	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), D1_COLUMN);
754 	gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
755 	gtk_tree_view_column_set_min_width (col, 80);
756 	gtk_tree_view_column_set_resizable (col, TRUE);
757 	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), D2_COLUMN);
758 	gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
759 	gtk_tree_view_column_set_min_width (col, 80);
760 	gtk_tree_view_column_set_resizable (col, TRUE);
761 
762 	gtk_container_add (GTK_CONTAINER (scroll), view);
763 	gtk_container_add (GTK_CONTAINER (box), scroll);
764 
765 	return view;
766 }
767 
768 static void
key_dialog_load(GtkListStore * store)769 key_dialog_load (GtkListStore *store)
770 {
771 	struct key_binding *kb = NULL;
772 	char *label_text, *accel_text;
773 	GtkTreeIter iter;
774 	GSList *list = keybind_list;
775 
776 	while (list)
777 	{
778 		kb = (struct key_binding*)list->data;
779 
780 		label_text = gtk_accelerator_get_label (kb->keyval, kb->mod);
781 		accel_text = gtk_accelerator_name (kb->keyval, kb->mod);
782 
783 		gtk_list_store_append (store, &iter);
784 		gtk_list_store_set (store, &iter,
785 							KEY_COLUMN, label_text,
786 							ACCEL_COLUMN, accel_text,
787 							ACTION_COLUMN, key_actions[kb->action].name,
788 							D1_COLUMN, kb->data1,
789 							D2_COLUMN, kb->data2, -1);
790 
791 		g_free (accel_text);
792 		g_free (label_text);
793 
794 		list = g_slist_next (list);
795 	}
796 }
797 
798 void
key_dialog_show()799 key_dialog_show ()
800 {
801 	GtkWidget *vbox, *box;
802 	GtkWidget *view, *xtext;
803 	GtkListStore *store;
804 	char buf[128];
805 
806 	if (key_dialog)
807 	{
808 		mg_bring_tofront (key_dialog);
809 		return;
810 	}
811 
812 	g_snprintf(buf, sizeof(buf), _("Keyboard Shortcuts - %s"), _(DISPLAY_NAME));
813 	key_dialog = mg_create_generic_tab ("editkeys", buf, TRUE, FALSE, key_dialog_close,
814 									NULL, 600, 360, &vbox, 0);
815 
816 	view = key_dialog_treeview_new (vbox);
817 	xtext = gtk_xtext_new (colors, 0);
818 	gtk_box_pack_start (GTK_BOX (vbox), xtext, FALSE, TRUE, 2);
819 	gtk_xtext_set_font (GTK_XTEXT (xtext), prefs.hex_text_font);
820 
821 	g_object_set_data (G_OBJECT (key_dialog), "view", view);
822 	g_object_set_data (G_OBJECT (key_dialog), "xtext", xtext);
823 
824 	box = gtk_hbutton_box_new ();
825 	gtk_button_box_set_layout (GTK_BUTTON_BOX (box), GTK_BUTTONBOX_SPREAD);
826 	gtk_box_pack_start (GTK_BOX (vbox), box, FALSE, FALSE, 2);
827 	gtk_container_set_border_width (GTK_CONTAINER (box), 5);
828 
829 	gtkutil_button (box, GTK_STOCK_NEW, NULL, key_dialog_add,
830 					NULL, _("Add"));
831 	gtkutil_button (box, GTK_STOCK_DELETE, NULL, key_dialog_delete,
832 					NULL, _("Delete"));
833 	gtkutil_button (box, GTK_STOCK_CANCEL, NULL, key_dialog_close,
834 					NULL, _("Cancel"));
835 	gtkutil_button (box, GTK_STOCK_SAVE, NULL, key_dialog_save,
836 					NULL, _("Save"));
837 
838 	store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (view)));
839 	key_dialog_load (store);
840 
841 	gtk_widget_show_all (key_dialog);
842 }
843 
844 static int
key_save_kbs(void)845 key_save_kbs (void)
846 {
847 	int fd;
848 	char buf[512];
849 	char *accel_text;
850 	GSList *list = keybind_list;
851 	struct key_binding *kb;
852 
853 	fd = hexchat_open_file ("keybindings.conf", O_CREAT | O_TRUNC | O_WRONLY,
854 									 0x180, XOF_DOMODE);
855 	if (fd < 0)
856 		return 1;
857 	write (fd, buf, g_snprintf (buf, 510, "# HexChat key bindings config file\n\n"));
858 
859 	while (list)
860 	{
861 		kb = list->data;
862 
863 		accel_text = gtk_accelerator_name (kb->keyval, kb->mod);
864 
865 		g_snprintf (buf, 510, "ACCEL=%s\n%s\n", accel_text, key_actions[kb->action].name);
866 		write (fd, buf, strlen (buf));
867 		g_free (accel_text);
868 
869 		if (kb->data1 && kb->data1[0])
870 			write (fd, buf, g_snprintf (buf, 510, "D1:%s\n", kb->data1));
871 		else
872 			write (fd, "D1!\n", 4);
873 
874 		if (kb->data2 && kb->data2[0])
875 			write (fd, buf, g_snprintf (buf, 510, "D2:%s\n", kb->data2));
876 		else
877 			write (fd, "D2!\n", 4);
878 
879 		write (fd, "\n", 1);
880 
881 		list = g_slist_next (list);
882 	}
883 
884 	close (fd);
885 	return 0;
886 }
887 
888 #define KBSTATE_MOD 0
889 #define KBSTATE_KEY 1
890 #define KBSTATE_ACT 2
891 #define KBSTATE_DT1 3
892 #define KBSTATE_DT2 4
893 
894 #define STRIP_WHITESPACE \
895 	while (buf[0] == ' ' || buf[0] == '\t') \
896 		buf++; \
897 		len = strlen (buf); \
898 	while (buf[len] == ' ' || buf[len] == '\t') \
899 	{ \
900 		buf[len] = 0; \
901 		len--; \
902 	} \
903 
904 static inline int
key_load_kbs_helper_mod(char * buf,GdkModifierType * out)905 key_load_kbs_helper_mod (char *buf, GdkModifierType *out)
906 {
907 	int n, len, mod = 0;
908 
909 	/* First strip off the fluff */
910 	STRIP_WHITESPACE
911 
912 	if (strcmp (buf, "None") == 0)
913 	{
914 		*out = 0;
915 		return 0;
916 	}
917 	for (n = 0; n < len; n++)
918 	{
919 		switch (buf[n])
920 		{
921 		case 'C':
922 			mod |= GDK_CONTROL_MASK;
923 			break;
924 		case 'A':
925 			mod |= GDK_MOD1_MASK;
926 			break;
927 		case 'S':
928 			mod |= GDK_SHIFT_MASK;
929 			break;
930 		default:
931 			return 1;
932 		}
933 	}
934 
935 	*out = mod;
936 	return 0;
937 }
938 
939 static int
key_load_kbs(void)940 key_load_kbs (void)
941 {
942 	char *buf, *ibuf;
943 	struct stat st;
944 	struct key_binding *kb = NULL;
945 	int fd, len, state = 0, pnt = 0;
946 	guint keyval;
947 	GdkModifierType mod = 0;
948 	off_t size;
949 
950 	fd = hexchat_open_file ("keybindings.conf", O_RDONLY, 0, 0);
951 	if (fd < 0)
952 	{
953 		ibuf = g_strdup (default_kb_cfg);
954 		size = strlen (default_kb_cfg);
955 	}
956 	else
957 	{
958 		if (fstat (fd, &st) != 0)
959 		{
960 			close (fd);
961 			return 1;
962 		}
963 
964 		ibuf = g_malloc(st.st_size);
965 		read (fd, ibuf, st.st_size);
966 		size = st.st_size;
967 		close (fd);
968 	}
969 
970 	if (keybind_list)
971 	{
972 		g_slist_free_full (keybind_list, key_free);
973 		keybind_list = NULL;
974 	}
975 
976 	while (buf_get_line (ibuf, &buf, &pnt, size))
977 	{
978 		if (buf[0] == '#')
979 			continue;
980 		if (strlen (buf) == 0)
981 			continue;
982 
983 		switch (state)
984 		{
985 		case KBSTATE_MOD:
986 			kb = g_new0 (struct key_binding, 1);
987 
988 			/* New format */
989 			if (strncmp (buf, "ACCEL=", 6) == 0)
990 			{
991 				buf += 6;
992 
993 				gtk_accelerator_parse (buf, &keyval, &mod);
994 
995 
996 				kb->keyval = keyval;
997 				kb->mod = key_modifier_get_valid (mod);
998 
999 				state = KBSTATE_ACT;
1000 				continue;
1001 			}
1002 
1003 			if (key_load_kbs_helper_mod (buf, &mod))
1004 				goto corrupt_file;
1005 
1006 			kb->mod = mod;
1007 
1008 			state = KBSTATE_KEY;
1009 			continue;
1010 
1011 		case KBSTATE_KEY:
1012 			STRIP_WHITESPACE
1013 
1014 			keyval = gdk_keyval_from_name (buf);
1015 			if (keyval == 0)
1016 			{
1017 				g_free (ibuf);
1018 				return 2;
1019 			}
1020 
1021 			kb->keyval = keyval;
1022 
1023 			state = KBSTATE_ACT;
1024 			continue;
1025 
1026 		case KBSTATE_ACT:
1027 			STRIP_WHITESPACE
1028 
1029 			kb->action = key_get_action_from_string (buf);
1030 
1031 			if (kb->action == KEY_MAX_ACTIONS + 1)
1032 			{
1033 				g_free (ibuf);
1034 				return 3;
1035 			}
1036 
1037 			state = KBSTATE_DT1;
1038 			continue;
1039 
1040 		case KBSTATE_DT1:
1041 		case KBSTATE_DT2:
1042 			if (state == KBSTATE_DT1)
1043 				kb->data1 = kb->data2 = NULL;
1044 
1045 			while (buf[0] == ' ' || buf[0] == '\t')
1046 				buf++;
1047 
1048 			if (buf[0] != 'D')
1049 			{
1050 				g_free (ibuf);
1051 				return 4;
1052 			}
1053 
1054 			switch (buf[1])
1055 			{
1056 			case '1':
1057 				if (state != KBSTATE_DT1)
1058 					goto corrupt_file;
1059 				break;
1060 			case '2':
1061 				if (state != KBSTATE_DT2)
1062 					goto corrupt_file;
1063 				break;
1064 			default:
1065 				goto corrupt_file;
1066 			}
1067 
1068 			if (buf[2] == ':')
1069 			{
1070 				len = strlen (buf);
1071 				/* Add one for the NULL, subtract 3 for the "Dx:" */
1072 				len++;
1073 				len -= 3;
1074 				if (state == KBSTATE_DT1)
1075 				{
1076 					kb->data1 = g_strndup (&buf[3], len);
1077 				} else
1078 				{
1079 					kb->data2 = g_strndup (&buf[3], len);
1080 				}
1081 			} else if (buf[2] == '!')
1082 			{
1083 				if (state == KBSTATE_DT1)
1084 					kb->data1 = NULL;
1085 				else
1086 					kb->data2 = NULL;
1087 			}
1088 			if (state == KBSTATE_DT1)
1089 			{
1090 				state = KBSTATE_DT2;
1091 				continue;
1092 			} else
1093 			{
1094 				keybind_list = g_slist_append (keybind_list, kb);
1095 
1096 				state = KBSTATE_MOD;
1097 			}
1098 
1099 			continue;
1100 		}
1101 	}
1102 	g_free (ibuf);
1103 	return 0;
1104 
1105 corrupt_file:
1106 	g_free (ibuf);
1107 	g_free (kb);
1108 	return 5;
1109 }
1110 
1111 /* ***** Key actions start here *********** */
1112 
1113 /* See the NOTES above --AGL */
1114 
1115 /* "Run command" */
1116 static int
key_action_handle_command(GtkWidget * wid,GdkEventKey * evt,char * d1,char * d2,struct session * sess)1117 key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, char *d1,
1118 									char *d2, struct session *sess)
1119 {
1120 	int ii, oi, len;
1121 	char out[2048], d = 0;
1122 
1123 	if (!d1)
1124 		return 0;
1125 
1126 	len = strlen (d1);
1127 
1128 	/* Replace each "\n" substring with '\n' */
1129 	for (ii = oi = 0; ii < len; ii++)
1130 	{
1131 		d = d1[ii];
1132 		if (d == '\\')
1133 		{
1134 			ii++;
1135 			d = d1[ii];
1136 			if (d == 'n')
1137 				out[oi++] = '\n';
1138 			else if (d == '\\')
1139 				out[oi++] = '\\';
1140 			else
1141 			{
1142 				out[oi++] = '\\';
1143 				out[oi++] = d;
1144 			}
1145 			continue;
1146 		}
1147 		out[oi++] = d;
1148 	}
1149 	out[oi] = 0;
1150 
1151 	handle_multiline (sess, out, 0, 0);
1152 	return 0;
1153 }
1154 
1155 /*
1156  * Check if the given session is inside the main window. This predicate
1157  * is passed to lastact_getfirst() as a way to filter out detached sessions.
1158  * XXX: Consider moving this in a different file?
1159  */
1160 static int
session_check_is_tab(session * sess)1161 session_check_is_tab(session *sess)
1162 {
1163 	if (!sess || !sess->gui)
1164 		return FALSE;
1165 
1166 	return (sess->gui->is_tab);
1167 }
1168 
1169 static int
key_action_page_switch(GtkWidget * wid,GdkEventKey * evt,char * d1,char * d2,struct session * sess)1170 key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1,
1171 								char *d2, struct session *sess)
1172 {
1173 	session *newsess;
1174 	int len, i, num;
1175 
1176 	if (!d1)
1177 		return 1;
1178 
1179 	len = strlen (d1);
1180 	if (!len)
1181 		return 1;
1182 
1183 	if (strcasecmp(d1, "auto") == 0)
1184 	{
1185 		/* Auto switch makes no sense in detached sessions */
1186 		if (!sess->gui->is_tab)
1187 			return 1;
1188 
1189 		/* Obtain a session with recent activity */
1190 		newsess = lastact_getfirst(session_check_is_tab);
1191 
1192 		if (newsess)
1193 		{
1194 			/*
1195 			 * Only sessions in the current window should be considered (i.e.
1196 			 * we don't want to move the focus on a different window). This
1197 			 * call could, in theory, do this, but we checked before that
1198 			 * newsess->gui->is_tab and sess->gui->is_tab.
1199 			 */
1200 			mg_bring_tofront_sess(newsess);
1201 			return 0;
1202 		}
1203 		else
1204 			return 1;
1205 	}
1206 
1207 	for (i = 0; i < len; i++)
1208 	{
1209 		if (d1[i] < '0' || d1[i] > '9')
1210 		{
1211 			if (i == 0 && (d1[i] == '+' || d1[i] == '-'))
1212 				continue;
1213 			else
1214 				return 1;
1215 		}
1216 	}
1217 
1218 	num = atoi (d1);
1219 	if (!d2)
1220 		num--;
1221 	if (!d2 || d2[0] == 0)
1222 		mg_switch_page (FALSE, num);
1223 	else
1224 		mg_switch_page (TRUE, num);
1225 	return 0;
1226 }
1227 
1228 int
key_action_insert(GtkWidget * wid,GdkEventKey * evt,char * d1,char * d2,struct session * sess)1229 key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
1230 						 struct session *sess)
1231 {
1232 	int tmp_pos;
1233 
1234 	if (!d1)
1235 		return 1;
1236 
1237 	tmp_pos = SPELL_ENTRY_GET_POS (wid);
1238 	SPELL_ENTRY_INSERT (wid, d1, strlen (d1), &tmp_pos);
1239 	SPELL_ENTRY_SET_POS (wid, tmp_pos);
1240 	return 2;
1241 }
1242 
1243 /* handles PageUp/Down keys */
1244 static int
key_action_scroll_page(GtkWidget * wid,GdkEventKey * evt,char * d1,char * d2,struct session * sess)1245 key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt, char *d1,
1246 								char *d2, struct session *sess)
1247 {
1248 	int value, end;
1249 	GtkAdjustment *adj;
1250 	enum scroll_type { PAGE_TOP, PAGE_BOTTOM, PAGE_UP, PAGE_DOWN, LINE_UP, LINE_DOWN };
1251 	int type = PAGE_DOWN;
1252 
1253 	if (d1)
1254 	{
1255 		if (!g_ascii_strcasecmp (d1, "top"))
1256 			type = PAGE_TOP;
1257 		else if (!g_ascii_strcasecmp (d1, "bottom"))
1258 			type = PAGE_BOTTOM;
1259 		else if (!g_ascii_strcasecmp (d1, "up"))
1260 			type = PAGE_UP;
1261 		else if (!g_ascii_strcasecmp (d1, "down"))
1262 			type = PAGE_DOWN;
1263 		else if (!g_ascii_strcasecmp (d1, "+1"))
1264 			type = LINE_DOWN;
1265 		else if (!g_ascii_strcasecmp (d1, "-1"))
1266 			type = LINE_UP;
1267 	}
1268 
1269 	if (!sess)
1270 		return 0;
1271 
1272 	adj = gtk_range_get_adjustment (GTK_RANGE (sess->gui->vscrollbar));
1273 	end = gtk_adjustment_get_upper (adj) - gtk_adjustment_get_lower (adj) - gtk_adjustment_get_page_size (adj);
1274 
1275 	switch (type)
1276 	{
1277 	case PAGE_TOP:
1278 		value = 0;
1279 		break;
1280 
1281 	case PAGE_BOTTOM:
1282 		value = end;
1283 		break;
1284 
1285 	case PAGE_UP:
1286 		value = gtk_adjustment_get_value (adj) - (gtk_adjustment_get_page_size (adj) - 1);
1287 		break;
1288 
1289 	case PAGE_DOWN:
1290 		value = gtk_adjustment_get_value (adj) + (gtk_adjustment_get_page_size (adj) - 1);
1291 		break;
1292 
1293 	case LINE_UP:
1294 		value = gtk_adjustment_get_value (adj) - 1.0;
1295 		break;
1296 
1297 	case LINE_DOWN:
1298 		value = gtk_adjustment_get_value (adj) + 1.0;
1299 		break;
1300 	}
1301 
1302 	if (value < 0)
1303 		value = 0;
1304 	if (value > end)
1305 		value = end;
1306 
1307 	gtk_adjustment_set_value (adj, value);
1308 
1309 	return 0;
1310 }
1311 
1312 static int
key_action_set_buffer(GtkWidget * wid,GdkEventKey * evt,char * d1,char * d2,struct session * sess)1313 key_action_set_buffer (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
1314 							  struct session *sess)
1315 {
1316 	if (!d1)
1317 		return 1;
1318 	if (d1[0] == 0)
1319 		return 1;
1320 
1321 	SPELL_ENTRY_SET_TEXT (wid, d1);
1322 	SPELL_ENTRY_SET_POS (wid, -1);
1323 
1324 	return 2;
1325 }
1326 
1327 static int
key_action_history_up(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1328 key_action_history_up (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2,
1329 							  struct session *sess)
1330 {
1331 	char *new_line;
1332 
1333 	new_line = history_up (&sess->history, SPELL_ENTRY_GET_TEXT (wid));
1334 	if (new_line)
1335 	{
1336 		SPELL_ENTRY_SET_TEXT (wid, new_line);
1337 		SPELL_ENTRY_SET_POS (wid, -1);
1338 	}
1339 
1340 	return 2;
1341 }
1342 
1343 static int
key_action_history_down(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1344 key_action_history_down (GtkWidget * wid, GdkEventKey * ent, char *d1,
1345 								 char *d2, struct session *sess)
1346 {
1347 	char *new_line;
1348 
1349 	new_line = history_down (&sess->history);
1350 	if (new_line)
1351 	{
1352 		SPELL_ENTRY_SET_TEXT (wid, new_line);
1353 		SPELL_ENTRY_SET_POS (wid, -1);
1354 	}
1355 
1356 	return 2;
1357 }
1358 
1359 /* old data that we reuse */
1360 static struct gcomp_data old_gcomp;
1361 
1362 /* work on the data, ie return only channels */
1363 static int
double_chan_cb(session * lsess,GList ** list)1364 double_chan_cb (session *lsess, GList **list)
1365 {
1366 	if (lsess->type == SESS_CHANNEL)
1367 		*list = g_list_prepend(*list, lsess->channel);
1368 	return TRUE;
1369 }
1370 
1371 /* convert a slist -> list. */
1372 static GList *
chanlist_double_list(GSList * inlist)1373 chanlist_double_list (GSList *inlist)
1374 {
1375 	GList *list = NULL;
1376 	g_slist_foreach(inlist, (GFunc)double_chan_cb, &list);
1377 	return list;
1378 }
1379 
1380 /* handle commands */
1381 static int
double_cmd_cb(struct popup * pop,GList ** list)1382 double_cmd_cb (struct popup *pop, GList **list)
1383 {
1384 	*list = g_list_prepend(*list, pop->name);
1385 	return TRUE;
1386 }
1387 
1388 /* convert a slist -> list. */
1389 static GList *
cmdlist_double_list(GSList * inlist)1390 cmdlist_double_list (GSList *inlist)
1391 {
1392 	GList *list = NULL;
1393 	g_slist_foreach (inlist, (GFunc)double_cmd_cb, &list);
1394 	return list;
1395 }
1396 
1397 static char *
gcomp_nick_func(char * data)1398 gcomp_nick_func (char *data)
1399 {
1400 	if (data)
1401 		return ((struct User *)data)->nick;
1402 	return "";
1403 }
1404 
1405 void
key_action_tab_clean(void)1406 key_action_tab_clean(void)
1407 {
1408 	if (old_gcomp.elen)
1409 	{
1410 		old_gcomp.data[0] = 0;
1411 		old_gcomp.elen = 0;
1412 	}
1413 }
1414 
1415 /* For use in sorting the user list for completion
1416 
1417 This sorts everyone by the last talked time except your own nick
1418 which is forced to the bottom of the list to avoid completing your
1419 own name, which is very unlikely.
1420 */
1421 static int
talked_recent_cmp(struct User * a,struct User * b)1422 talked_recent_cmp (struct User *a, struct User *b)
1423 {
1424 	if (a->me)
1425 		return -1;
1426 
1427 	if (b->me)
1428 		return 1;
1429 
1430 	if (a->lasttalk < b->lasttalk)
1431 		return -1;
1432 
1433 	if (a->lasttalk > b->lasttalk)
1434 		return 1;
1435 
1436 	return 0;
1437 }
1438 
1439 #define COMP_BUF 2048
1440 
1441 static inline glong
len_to_offset(const char * str,glong len)1442 len_to_offset (const char *str, glong len)
1443 {
1444 	return g_utf8_pointer_to_offset (str, str + len);
1445 }
1446 
1447 static inline glong
offset_to_len(const char * str,glong offset)1448 offset_to_len (const char *str, glong offset)
1449 {
1450 	return g_utf8_offset_to_pointer (str, offset) - str;
1451 }
1452 
1453 static int
key_action_tab_comp(GtkWidget * t,GdkEventKey * entry,char * d1,char * d2,struct session * sess)1454 key_action_tab_comp (GtkWidget *t, GdkEventKey *entry, char *d1, char *d2,
1455 							struct session *sess)
1456 {
1457 	int len = 0, elen = 0, i = 0, cursor_pos, ent_start = 0, comp = 0, prefix_len, skip_len = 0;
1458 	gboolean is_nick = FALSE, is_cmd = FALSE, found = FALSE, has_nick_prefix = FALSE;
1459 	char ent[CHANLEN], *postfix = NULL, *result, *ch;
1460 	GList *list = NULL, *tmp_list = NULL;
1461 	const char *text;
1462 	GCompletion *gcomp = NULL;
1463 	GString *buf;
1464 
1465 	/* force the IM Context to reset */
1466 	SPELL_ENTRY_SET_EDITABLE (t, FALSE);
1467 	SPELL_ENTRY_SET_EDITABLE (t, TRUE);
1468 
1469 	text = SPELL_ENTRY_GET_TEXT (t);
1470 	if (text[0] == 0)
1471 		return 1;
1472 
1473 	len = g_utf8_strlen (text, -1); /* must be null terminated */
1474 
1475 	cursor_pos = SPELL_ENTRY_GET_POS (t);
1476 
1477 	/* handle "nick: " or "nick " or "#channel "*/
1478 	ch = g_utf8_find_prev_char(text, g_utf8_offset_to_pointer(text,cursor_pos));
1479 	if (ch && ch[0] == ' ')
1480 	{
1481 		skip_len++;
1482 		ch = g_utf8_find_prev_char(text, ch);
1483 		if (!ch)
1484 			return 2;
1485 
1486 		cursor_pos = g_utf8_pointer_to_offset(text, ch);
1487 		if (cursor_pos && (g_utf8_get_char_validated(ch, -1) == ':' ||
1488 					g_utf8_get_char_validated(ch, -1) == ',' ||
1489 					g_utf8_get_char_validated (ch, -1) == g_utf8_get_char_validated (prefs.hex_completion_suffix, -1)))
1490 		{
1491 			skip_len++;
1492 		}
1493 		else
1494 			cursor_pos = g_utf8_pointer_to_offset(text, g_utf8_offset_to_pointer(ch, 1));
1495 	}
1496 
1497 	comp = skip_len;
1498 
1499 	/* store the text following the cursor for reinsertion later */
1500 	if ((cursor_pos + skip_len) < len)
1501 		postfix = g_utf8_offset_to_pointer(text, cursor_pos + skip_len);
1502 
1503 	for (ent_start = cursor_pos; ; --ent_start)
1504 	{
1505 		if (ent_start == 0)
1506 			break;
1507 		ch = g_utf8_offset_to_pointer(text, ent_start - 1);
1508 		if (ch && ch[0] == ' ')
1509 			break;
1510 	}
1511 
1512 	if (ent_start == 0 && text[0] == prefs.hex_input_command_char[0])
1513 	{
1514 		ent_start++;
1515 		is_cmd = TRUE;
1516 	}
1517 	else if (strchr (sess->server->chantypes, text[ent_start]) == NULL)
1518 	{
1519 		is_nick = TRUE;
1520 		if (strchr (sess->server->nick_prefixes, text[ent_start]) != NULL)
1521 		{
1522 			if (ent_start == 0)
1523 				has_nick_prefix = TRUE;
1524 			ent_start++;
1525 		}
1526 	}
1527 
1528 	prefix_len = ent_start;
1529 	elen = cursor_pos - ent_start;
1530 
1531 	g_utf8_strncpy (ent, g_utf8_offset_to_pointer (text, prefix_len), elen);
1532 
1533 	if (sess->type == SESS_DIALOG && is_nick)
1534 	{
1535 		/* tab in a dialog completes the other person's name */
1536 		if (rfc_ncasecmp (sess->channel, ent, elen) == 0)
1537 		{
1538 			result =  sess->channel;
1539 			is_nick = FALSE;
1540 		}
1541 		else
1542 			return 2;
1543 	}
1544 	else
1545 	{
1546 		if (is_nick)
1547 		{
1548 			gcomp = g_completion_new((GCompletionFunc)gcomp_nick_func);
1549 			tmp_list = userlist_double_list(sess); /* create a temp list so we can free the memory */
1550 			if (prefs.hex_completion_sort == 1)	/* sort in last-talk order? */
1551 				tmp_list = g_list_sort (tmp_list, (void *)talked_recent_cmp);
1552 		}
1553 		else
1554 		{
1555 			gcomp = g_completion_new (NULL);
1556 			if (is_cmd)
1557 			{
1558 				tmp_list = cmdlist_double_list (command_list);
1559 				for(i = 0; xc_cmds[i].name != NULL ; i++)
1560 				{
1561 					tmp_list = g_list_prepend (tmp_list, xc_cmds[i].name);
1562 				}
1563 				tmp_list = plugin_command_list(tmp_list);
1564 			}
1565 			else
1566 				tmp_list = chanlist_double_list (sess_list);
1567 		}
1568 		tmp_list = g_list_reverse(tmp_list); /* make the comp entries turn up in the right order */
1569 		g_completion_set_compare (gcomp, (GCompletionStrncmpFunc)rfc_ncasecmp);
1570 		if (tmp_list)
1571 		{
1572 			g_completion_add_items (gcomp, tmp_list);
1573 			g_list_free (tmp_list);
1574 		}
1575 
1576 		if (comp && !(rfc_ncasecmp(old_gcomp.data, ent, old_gcomp.elen) == 0))
1577 		{
1578 			key_action_tab_clean ();
1579 			comp = 0;
1580 		}
1581 
1582 		list = g_completion_complete_utf8 (gcomp, comp ? old_gcomp.data : ent, &result);
1583 
1584 		if (result == NULL) /* No matches found */
1585 		{
1586 			g_completion_free(gcomp);
1587 			return 2;
1588 		}
1589 
1590 		if (comp) /* existing completion */
1591 		{
1592 			while(list) /* find the current entry */
1593 			{
1594 				if(rfc_ncasecmp(list->data, ent, elen) == 0)
1595 				{
1596 					found = TRUE;
1597 					break;
1598 				}
1599 				list = list->next;
1600 			}
1601 
1602 			if (found)
1603 			{
1604 				if (!(d1 && d1[0])) /* not holding down shift */
1605 				{
1606 					if (g_list_next(list) == NULL)
1607 						list = g_list_first(list);
1608 					else
1609 						list = g_list_next(list);
1610 				}
1611 				else
1612 				{
1613 					if (g_list_previous(list) == NULL)
1614 						list = g_list_last(list);
1615 					else
1616 						list = g_list_previous(list);
1617 				}
1618 				g_free(result);
1619 				result = (char*)list->data;
1620 			}
1621 			else
1622 			{
1623 				g_free(result);
1624 				g_completion_free(gcomp);
1625 				return 2;
1626 			}
1627 		}
1628 		else
1629 		{
1630 			strcpy(old_gcomp.data, ent);
1631 			old_gcomp.elen = elen;
1632 
1633 			/* Get the first nick and put out the data for future nickcompletes */
1634 			if (prefs.hex_completion_amount > 0 && g_list_length (list) <= (guint) prefs.hex_completion_amount)
1635 			{
1636 				g_free(result);
1637 				result = (char*)list->data;
1638 			}
1639 			else
1640 			{
1641 				/* bash style completion */
1642 				if (g_list_next(list) != NULL)
1643 				{
1644 					buf = g_string_sized_new (MAX(COMP_BUF, len + NICKLEN));
1645 					if (strlen (result) > elen) /* the largest common prefix is larger than nick, change the data */
1646 					{
1647 						if (prefix_len)
1648 							g_string_append_len (buf, text, offset_to_len (text, prefix_len));
1649 						g_string_append (buf, result);
1650 						cursor_pos = buf->len;
1651 						g_free(result);
1652 						if (postfix)
1653 						{
1654 							g_string_append_c (buf, ' ');
1655 							g_string_append (buf, postfix);
1656 						}
1657 						SPELL_ENTRY_SET_TEXT (t, buf->str);
1658 						SPELL_ENTRY_SET_POS (t, len_to_offset (buf->str, cursor_pos));
1659 						g_string_erase (buf, 0, -1);
1660 					}
1661 					else
1662 						g_free(result);
1663 
1664 					while (list)
1665 					{
1666 						len = buf->len;
1667 						elen = strlen (list->data);	/* next item to add */
1668 						if (len + elen + 2 >= COMP_BUF) /* +2 is space + null */
1669 						{
1670 							PrintText (sess, buf->str);
1671 							g_string_erase (buf, 0, -1);
1672 						}
1673 						g_string_append (buf, (char*)list->data);
1674 						g_string_append_c (buf, ' ');
1675 						list = list->next;
1676 					}
1677 					PrintText (sess, buf->str);
1678 					g_completion_free(gcomp);
1679 					g_string_free (buf, TRUE);
1680 					return 2;
1681 				}
1682 				/* Only one matching entry */
1683 				g_free(result);
1684 				result = list->data;
1685 			}
1686 		}
1687 	}
1688 
1689 	if(result)
1690 	{
1691 		buf = g_string_sized_new (len + NICKLEN);
1692 		if (prefix_len)
1693 			g_string_append_len (buf, text, offset_to_len (text, prefix_len));
1694 		g_string_append (buf, result);
1695 		if((!prefix_len || has_nick_prefix) && is_nick && prefs.hex_completion_suffix[0] != '\0')
1696 			g_string_append_unichar (buf, g_utf8_get_char_validated (prefs.hex_completion_suffix, -1));
1697 		g_string_append_c (buf, ' ');
1698 		cursor_pos = buf->len;
1699 		if (postfix)
1700 			g_string_append (buf, postfix);
1701 		SPELL_ENTRY_SET_TEXT (t, buf->str);
1702 		SPELL_ENTRY_SET_POS (t, len_to_offset (buf->str, cursor_pos));
1703 		g_string_free (buf, TRUE);
1704 	}
1705 	if (gcomp)
1706 		g_completion_free(gcomp);
1707 	return 2;
1708 }
1709 #undef COMP_BUF
1710 
1711 static int
key_action_comp_chng(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1712 key_action_comp_chng (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2,
1713 		struct session *sess)
1714 {
1715 	key_action_tab_comp(wid, ent, d1, d2, sess);
1716 	return 2;
1717 }
1718 
1719 
1720 static int
key_action_replace(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1721 key_action_replace (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2,
1722 						  struct session *sess)
1723 {
1724 	replace_handle (wid);
1725 	return 1;
1726 }
1727 
1728 
1729 static int
key_action_move_tab_left(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1730 key_action_move_tab_left (GtkWidget * wid, GdkEventKey * ent, char *d1,
1731 								  char *d2, struct session *sess)
1732 {
1733 	mg_move_tab (sess, +1);
1734 	return 2;						  /* don't allow default action */
1735 }
1736 
1737 static int
key_action_move_tab_right(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1738 key_action_move_tab_right (GtkWidget * wid, GdkEventKey * ent, char *d1,
1739 									char *d2, struct session *sess)
1740 {
1741 	mg_move_tab (sess, -1);
1742 	return 2;						  /* -''- */
1743 }
1744 
1745 static int
key_action_move_tab_family_left(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1746 key_action_move_tab_family_left (GtkWidget * wid, GdkEventKey * ent, char *d1,
1747 								  char *d2, struct session *sess)
1748 {
1749 	mg_move_tab_family (sess, +1);
1750 	return 2;						  /* don't allow default action */
1751 }
1752 
1753 static int
key_action_move_tab_family_right(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1754 key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * ent, char *d1,
1755 									char *d2, struct session *sess)
1756 {
1757 	mg_move_tab_family (sess, -1);
1758 	return 2;						  /* -''- */
1759 }
1760 
1761 static int
key_action_put_history(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1762 key_action_put_history (GtkWidget * wid, GdkEventKey * ent, char *d1,
1763 									char *d2, struct session *sess)
1764 {
1765 	history_add (&sess->history, SPELL_ENTRY_GET_TEXT (wid));
1766 	SPELL_ENTRY_SET_TEXT (wid, "");
1767 	return 2;						  /* -''- */
1768 }
1769 
1770 
1771 /* -------- */
1772 
1773 static void
replace_handle(GtkWidget * t)1774 replace_handle (GtkWidget *t)
1775 {
1776 	const char *text, *postfix_pnt;
1777 	struct popup *pop;
1778 	GSList *list = replace_list;
1779 	char word[256];
1780 	char postfix[256];
1781 	char outbuf[4096];
1782 	int c, len, xlen;
1783 
1784 	text = SPELL_ENTRY_GET_TEXT (t);
1785 
1786 	len = strlen (text);
1787 	if (len < 1)
1788 		return;
1789 
1790 	for (c = len - 1; c > 0; c--)
1791 	{
1792 		if (text[c] == ' ')
1793 			break;
1794 	}
1795 	if (text[c] == ' ')
1796 		c++;
1797 	xlen = c;
1798 	if (len - c >= (sizeof (word) - 12))
1799 		return;
1800 	if (len - c < 1)
1801 		return;
1802 	memcpy (word, &text[c], len - c);
1803 	word[len - c] = 0;
1804 	len = strlen (word);
1805 	if (word[0] == '\'' && word[len] == '\'')
1806 		return;
1807 	postfix_pnt = NULL;
1808 	for (c = 0; c < len; c++)
1809 	{
1810 		if (word[c] == '\'')
1811 		{
1812 			postfix_pnt = &word[c + 1];
1813 			word[c] = 0;
1814 			break;
1815 		}
1816 	}
1817 
1818 	if (postfix_pnt != NULL)
1819 	{
1820 		if (strlen (postfix_pnt) > sizeof (postfix) - 12)
1821 			return;
1822 		strcpy (postfix, postfix_pnt);
1823 	}
1824 	while (list)
1825 	{
1826 		pop = (struct popup *) list->data;
1827 		if (strcmp (pop->name, word) == 0)
1828 		{
1829 			memcpy (outbuf, text, xlen);
1830 			outbuf[xlen] = 0;
1831 			if (postfix_pnt == NULL)
1832 				g_snprintf (word, sizeof (word), "%s", pop->cmd);
1833 			else
1834 				g_snprintf (word, sizeof (word), "%s%s", pop->cmd, postfix);
1835 			g_strlcat (outbuf, word, sizeof(outbuf));
1836 			SPELL_ENTRY_SET_TEXT (t, outbuf);
1837 			SPELL_ENTRY_SET_POS (t, -1);
1838 			return;
1839 		}
1840 		list = list->next;
1841 	}
1842 }
1843 
1844