1 /*
2 ** 1999-03-10 -	Yay, it's a keyboard accelerator support module! I guess this should've been
3 **		written months ago, but it's not until now that GTK+'s keyboard support seems
4 **		to have developed enough for me to actually use it. :^) Oh, and then I'm evil
5 **		enough to actually go around it. I must be insane. But because I want the ability
6 **		to keyboard-accelerate stuff that isn't GTK+ objects, I'll do this my way.
7 ** 2000-02-09 -	Added support for a context-wide mask of modifiers to be ignored. Very handy for
8 **		e.g. masking out that pesky NumLock key.
9 */
10 
11 #include "gentoo.h"
12 
13 #include <stdarg.h>
14 
15 #include "cmdseq.h"
16 
17 #include "keyboard.h"
18 
19 /* ----------------------------------------------------------------------------------------- */
20 
21 typedef struct {
22 	GdkEventFunc	func;		/* GdkEvent function handler to call. */
23 	gpointer	user;		/* User data to pass along in call. */
24 } KEGdkEvent;
25 
26 typedef struct {
27 	GObject		*obj;		/* Which object to signal. */
28 	gchar		*signame;	/* Name of signal to emit. */
29 	gpointer	user;		/* User data passed along signal emission. */
30 } KEObject;
31 
32 typedef struct {
33 	gchar		*cmdseq;	/* Name of command sequence to run. */
34 } KECmdSeq;
35 
36 typedef struct {
37 	gchar		*key;		/* The key that activates it. */
38 	KEType		type;		/* Type of accelerator; GTK+ object, gentoo command, etc. */
39 	union {
40 	KEGdkEvent	gdkevent;
41 	KEObject	object;
42 	KECmdSeq	cmdseq;
43 	} entry;
44 } KbdEntry;
45 
46 struct KbdContext {
47 	MainInfo	*min;		/* A link to the ever-present core info structure. */
48 	GHashTable	*hash;		/* A hash of KbdEntries, hashed on key names. */
49 	GdkModifierType	mod_mask;	/* Indicated modifiers are ignored. */
50 	guint		use_count;	/* Counts # of attachments made. */
51 };
52 
53 /* ----------------------------------------------------------------------------------------- */
54 
55 /* 1999-03-10 -	Create a new, empty, keyboard context to which the user then can add "entries". */
kbd_context_new(MainInfo * min)56 KbdContext * kbd_context_new(MainInfo *min)
57 {
58 	KbdContext	*ctx;
59 
60 	ctx = g_malloc(sizeof *ctx);
61 
62 	ctx->min	= min;
63 	ctx->hash	= g_hash_table_new(g_str_hash, g_str_equal);
64 	ctx->mod_mask	= 0U;
65 	ctx->use_count	= 0U;
66 
67 	return ctx;
68 }
69 
70 /* ----------------------------------------------------------------------------------------- */
71 
72 /* 2000-02-08 -	Set a maskt to be applied against the 'state' member of keyboard events. */
kbd_context_mask_set(KbdContext * ctx,GdkModifierType mask)73 void kbd_context_mask_set(KbdContext *ctx, GdkModifierType mask)
74 {
75 	if(ctx != NULL)
76 		ctx->mod_mask = mask;
77 }
78 
79 /* 2000-02-08 -	Return current state mask. */
kbd_context_mask_get(KbdContext * ctx)80 GdkModifierType kbd_context_mask_get(KbdContext *ctx)
81 {
82 	if(ctx != NULL)
83 		return ctx->mod_mask;
84 	return 0U;
85 }
86 
87 /* ----------------------------------------------------------------------------------------- */
88 
89 /* 1999-03-10 -	Create a new entry. */
entry_new(const gchar * key,KEType type,va_list args)90 static KbdEntry * entry_new(const gchar *key, KEType type, va_list args)
91 {
92 	KbdEntry	*ent;
93 
94 	if(key == NULL)
95 		return NULL;
96 
97 	ent = g_malloc(sizeof *ent);
98 	ent->key = g_strdup(key);
99 	ent->type = type;
100 
101 	switch(type)
102 	{
103 		case KET_GDKEVENT:
104 			ent->entry.gdkevent.func  = va_arg(args, GdkEventFunc);
105 			ent->entry.gdkevent.user  = va_arg(args, gpointer);
106 			break;
107 		case KET_GTKOBJECT:
108 			ent->entry.object.obj	  = va_arg(args, GObject *);
109 			ent->entry.object.signame = g_strdup(va_arg(args, gchar *));
110 			ent->entry.object.user	  = va_arg(args, gpointer);
111 			break;
112 		case KET_CMDSEQ:
113 			ent->entry.cmdseq.cmdseq = g_strdup(va_arg(args, gchar *));
114 			break;
115 	}
116 
117 	return ent;
118 }
119 
120 /* 1999-03-10 -	Destroy an entry, freeing all memory used by it. */
entry_destroy(KbdEntry * ent)121 static void entry_destroy(KbdEntry *ent)
122 {
123 	if(ent != NULL)
124 	{
125 		switch(ent->type)
126 		{
127 			case KET_GDKEVENT:
128 				break;
129 			case KET_GTKOBJECT:
130 				g_free(ent->entry.object.signame);
131 				break;
132 			case KET_CMDSEQ:
133 				g_free(ent->entry.cmdseq.cmdseq);
134 				break;
135 		}
136 		g_free(ent->key);
137 		g_free(ent);
138 	}
139 }
140 
141 /* ----------------------------------------------------------------------------------------- */
142 
143 /* 1999-03-16 -	Add an entry based on the va_list of arguments. The list must be destroyed by
144 **		the caller; we don't do va_end() on it here.
145 ** 2000-02-09 -	Now applies the context-wide modifier mask before creating the binding, if any.
146 */
kbd_context_entry_vadd(KbdContext * ctx,const gchar * key,KEType type,va_list args)147 gint kbd_context_entry_vadd(KbdContext *ctx, const gchar *key, KEType type, va_list args)
148 {
149 	KbdEntry	*ent;
150 
151 	if((ctx == NULL) || (key == NULL))
152 		return 0;
153 
154 	if(ctx->mod_mask != 0U)		/* Does the context require modifiers to be filtered out? */
155 	{
156 		guint		keysym;
157 		GdkModifierType	mod;
158 
159 		gtk_accelerator_parse(key, &keysym, &mod);
160 		mod &= ~ctx->mod_mask;	/* Apply the modifier mask. */
161 		key = gtk_accelerator_name(keysym, mod);
162 	}
163 
164 	if((ent = entry_new(key, type, args)) != NULL)
165 	{
166 		kbd_context_entry_remove(ctx, key);
167 		g_hash_table_insert(ctx->hash, ent->key, ent);
168 	}
169 	return ent != NULL;
170 }
171 
172 /* 1999-03-10 -	Add an "entry" to a keyboard context. These entries are sort of like GTK+'s
173 **		accelerator entries, only a bit more general. Basically, they map a keyboard
174 **		event to some action. Keys are recognized by their GTK+ names (such as "<Shift>a").
175 **		Note that it is entierly OK to add entries even after the context has been attached
176 **		to a window (in fact, that is the expected usage). There can only be one entry
177 **		per key; any preexisting entry will be removed before the new is added.
178 **		  Returns 1 on success, 0 on failure.
179 */
kbd_context_entry_add(KbdContext * ctx,const gchar * key,KEType type,...)180 gint kbd_context_entry_add(KbdContext *ctx, const gchar *key, KEType type, ...)
181 {
182 	va_list	args;
183 	gint	ret;
184 
185 	va_start(args, type);
186 	ret = kbd_context_entry_vadd(ctx, key, type, args);
187 	va_end(args);
188 	return ret;
189 }
190 
191 /* 1999-03-10 -	Remove an entry from given context. Since entries are module-private and highly
192 **		opaque (there is no publicly accessible entry type), the entry is also destroyed.
193 */
kbd_context_entry_remove(KbdContext * ctx,const gchar * key)194 void kbd_context_entry_remove(KbdContext *ctx, const gchar *key)
195 {
196 	if((ctx != NULL) && (key != NULL))
197 	{
198 		KbdEntry	*ent;
199 
200 		if((ent = g_hash_table_lookup(ctx->hash, key)) != NULL)
201 		{
202 			g_hash_table_remove(ctx->hash, key);
203 			entry_destroy(ent);
204 		}
205 	}
206 }
207 
208 /* ----------------------------------------------------------------------------------------- */
209 
210 /* 1999-03-10 -	This gets called (by GTK+) when the user presses a key in a window to which
211 **		a keyboard context has been attached. Look up the key, trigger action.
212 ** 2000-02-09 -	Now knows about, and applies, the modifier mask allowing e.g. NumLock to be
213 **		ignored.
214 */
evt_key_press(GtkWidget * wid,GdkEventKey * evt,gpointer user)215 static gboolean evt_key_press(GtkWidget *wid, GdkEventKey *evt, gpointer user)
216 {
217 	KbdContext	*ctx = user;
218 	KbdEntry	*ent = NULL;
219 	gchar		*key;
220 	gint		ret;
221 	GdkModifierType	mod;
222 
223 	mod = evt->state;
224 	if(ctx->mod_mask != 0U)
225 		mod &= ~ctx->mod_mask;
226 
227 /*	printf("Someone pressed %u ('%c') state=%04X\n", evt->keyval, evt->keyval, evt->state);*/
228 
229 	/* Here's the GTK+ function that provides the crucial mapping of (keyval,state) => ASCII name. */
230 	if((key = gtk_accelerator_name(evt->keyval, mod)) != NULL)
231 	{
232 /*		printf(" a key also known as \"%s\"\n", key);*/
233 		if((ent = g_hash_table_lookup(ctx->hash, key)) != NULL)
234 		{
235 /*			printf("  with a binding of type %d, too\n", ent->type);*/
236 			switch(ent->type)
237 			{
238 				case KET_GDKEVENT:
239 					ent->entry.gdkevent.func((GdkEvent *) evt, ent->entry.gdkevent.user);
240 					break;
241 				case KET_GTKOBJECT:
242 					g_signal_emit_by_name(G_OBJECT(ent->entry.object.obj),
243 								ent->entry.object.signame, ent->entry.object.user, &ret);
244 					break;
245 				case KET_CMDSEQ:
246 					csq_execute(ctx->min, ent->entry.cmdseq.cmdseq);
247 					break;
248 			}
249 		}
250 		g_free(key);
251 	}
252 	return ent != NULL;
253 }
254 
255 /* 1999-03-10 -	Attach a keyboard context to a window. This will cause all keyboard events
256 **		generated in the window to be "trapped", and routed through the context, which
257 **		will envoke matching entries.
258 */
kbd_context_attach(KbdContext * ctx,GtkWindow * win)259 gint kbd_context_attach(KbdContext *ctx, GtkWindow *win)
260 {
261 	if((ctx == NULL) || (win == NULL))
262 		return 0;
263 
264 	return g_signal_connect(G_OBJECT(win), "key_press_event", G_CALLBACK(evt_key_press), ctx);
265 }
266 
267 /* 1999-04-02 -	Detach given keyboard context from given window. If it's not attached, nothing
268 **		happens.
269 */
kbd_context_detach(KbdContext * ctx,GtkWindow * win)270 void kbd_context_detach(KbdContext *ctx, GtkWindow *win)
271 {
272 	gulong	handler;
273 
274 	if((ctx == NULL) || (win == NULL))
275 		return;
276 
277 	if((handler = g_signal_handler_find(G_OBJECT(win), G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
278 			0u, 0, NULL, G_CALLBACK(evt_key_press), ctx)) != 0)
279 	{
280 		g_signal_handler_disconnect(G_OBJECT(win), handler);
281 	}
282 }
283 
284 /* ----------------------------------------------------------------------------------------- */
285 
286 /* 1999-03-10 -	Free an entry, with GHashTable prototype. */
entry_hash_free(gpointer key,gpointer value,gpointer user)287 static gboolean entry_hash_free(gpointer key, gpointer value, gpointer user)
288 {
289 	entry_destroy(value);
290 
291 	return TRUE;
292 }
293 
294 /* 1999-03-11 -	Clear a context from all its entries. */
kbd_context_clear(KbdContext * ctx)295 void kbd_context_clear(KbdContext *ctx)
296 {
297 	if(ctx != NULL)
298 		g_hash_table_foreach_remove(ctx->hash, entry_hash_free, NULL);
299 }
300 
301 /* 1999-03-10 -	Destroy a keyboard context. Will whine if it is still attached to some window. */
kbd_context_destroy(KbdContext * ctx)302 void kbd_context_destroy(KbdContext *ctx)
303 {
304 	if(ctx != NULL)
305 	{
306 		if(ctx->use_count != 0)
307 			fprintf(stderr, "KBD: Destroying key context with non-zero use-count!\n");
308 		kbd_context_clear(ctx);
309 
310 		g_hash_table_destroy(ctx->hash);
311 		g_free(ctx);
312 	}
313 }
314