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