ion->notion
[notion/jeffpc.git] / ioncore / key.c
blob8675b8ce475f4663fb0b89954d317a498ae90bac
1 /*
2 * ion/ioncore/key.c
4 * Copyright (c) Tuomo Valkonen 1999-2009.
6 * See the included file LICENSE for details.
7 */
9 #include <ctype.h>
11 #include <libtu/objp.h>
12 #include <libextl/extl.h>
13 #include <libmainloop/defer.h>
15 #include "common.h"
16 #include "key.h"
17 #include "binding.h"
18 #include "global.h"
19 #include "event.h"
20 #include "cursor.h"
21 #include "grab.h"
22 #include "regbind.h"
23 #include "strings.h"
24 #include "xwindow.h"
27 static void waitrelease(WRegion *reg);
28 static void submapgrab(WRegion *reg);
31 static void insstr(WWindow *wwin, XKeyEvent *ev)
33 static XComposeStatus cs={NULL, 0};
34 char buf[32]={0,};
35 Status stat;
36 int n, i;
37 KeySym ksym;
39 if(wwin->xic!=NULL){
40 if(XFilterEvent((XEvent*)ev, ev->window))
41 return;
42 n=XmbLookupString(wwin->xic, ev, buf, 16, &ksym, &stat);
43 if(stat!=XLookupChars && stat!=XLookupBoth)
44 return;
45 }else{
46 n=XLookupString(ev, buf, 32, &ksym, &cs);
49 if(n<=0)
50 return;
52 /* Won't catch bad strings, but should filter out most crap. */
53 if(ioncore_g.use_mb){
54 if(!iswprint(str_wchar_at(buf, 32)))
55 return;
56 }else{
57 if(iscntrl(*buf))
58 return;
61 window_insstr(wwin, buf, n);
65 static void send_key(XEvent *ev, WClientWin *cwin)
67 Window win=cwin->win;
68 ev->xkey.window=win;
69 ev->xkey.subwindow=None;
70 XSendEvent(ioncore_g.dpy, win, False, KeyPressMask, ev);
74 static bool quote_next_handler(WRegion *reg, XEvent *xev)
76 XKeyEvent *ev=&xev->xkey;
77 if(ev->type!=KeyPress)
78 return FALSE;
79 if(ioncore_ismod(ev->keycode))
80 return FALSE;
81 assert(OBJ_IS(reg, WClientWin));
82 send_key(xev, (WClientWin*)reg);
83 return TRUE; /* remove the grab */
87 /*EXTL_DOC
88 * Send next key press directly to \var{cwin}.
90 EXTL_EXPORT_MEMBER
91 void clientwin_quote_next(WClientWin *cwin)
93 ioncore_grab_establish((WRegion*)cwin, quote_next_handler, NULL, 0);
94 ioncore_change_grab_cursor(IONCORE_CURSOR_WAITKEY);
98 static bool waitrelease_handler(WRegion *reg, XEvent *ev)
100 return (ioncore_unmod(ev->xkey.state, ev->xkey.keycode)==0);
104 static void waitrelease(WRegion *reg)
106 if(ioncore_modstate()==0)
107 return;
109 /* We need to grab on the root window as <reg> might have been
110 * ioncore_defer_destroy:ed by the binding handler (the most common case
111 * for using this kpress_wait!). In such a case the grab may
112 * be removed before the modifiers are released.
114 ioncore_grab_establish((WRegion*)region_rootwin_of(reg),
115 waitrelease_handler,
116 NULL, 0);
117 ioncore_change_grab_cursor(IONCORE_CURSOR_WAITKEY);
121 static void free_sub(WSubmapState *p)
123 /*extl_unref_fn(p->leave);
124 watch_reset(&p->leave_reg);
127 free(p);
131 void region_free_submapstat(WRegion *reg)
133 while(reg->submapstat!=NULL){
134 WSubmapState *p=reg->submapstat;
135 reg->submapstat=p->next;
136 free_sub(p);
141 WHook *ioncore_submap_ungrab_hook=NULL;
144 static void call_submap_ungrab_hook()
146 hook_call_v(ioncore_submap_ungrab_hook);
150 static void clear_subs(WRegion *reg)
152 region_free_submapstat(reg);
153 mainloop_defer_action(NULL, (WDeferredAction*)call_submap_ungrab_hook);
155 while(reg!=NULL && reg->submapstat!=NULL){
156 WSubmapState *p=reg->submapstat;
157 reg->submapstat=p->next;
159 if(p->leave!=extl_fn_none() && p->leave_reg.obj!=NULL){
160 Watch regw=WATCH_INIT;
162 watch_setup(&regw, (Obj*)reg, NULL);
164 extl_call(p->leave, "o", NULL, p->leave_reg.obj);
166 reg=(WRegion*)regw.obj;
168 watch_reset(&regw);
171 free_sub(p);
177 static WSubmapState *add_sub(WRegion *reg, uint key, uint state)
179 WSubmapState **p;
180 WSubmapState *s;
182 if(reg->submapstat==NULL){
183 p=&(reg->submapstat);
184 }else{
185 s=reg->submapstat;
186 while(s->next!=NULL)
187 s=s->next;
188 p=&(s->next);
191 s=ALLOC(WSubmapState);
193 if(s==NULL)
194 return NULL;
196 s->key=key;
197 s->state=state;
198 /*s->leave=extl_fn_none();
199 watch_init(&s->leave_reg);*/
201 *p=s;
203 return s;
208 static XKeyEvent *current_key_event=NULL;
209 static uint current_kcb, current_state;
210 static bool current_submap;
212 /* Note: state set to AnyModifier for submaps */
213 bool ioncore_current_key(uint *kcb, uint *state, bool *sub)
215 if(current_kcb==0)
216 return FALSE;
218 *kcb=current_kcb;
219 *state=current_state;
220 *sub=current_submap;
222 return TRUE;
226 enum{GRAB_NONE, GRAB_NONE_SUBMAP, GRAB_SUBMAP, GRAB_WAITRELEASE};
229 static WBinding *lookup_binding_(WRegion *reg,
230 int act, uint state, uint kcb,
231 WSubmapState *st,
232 WRegion **binding_owner, WRegion **subreg)
234 WBinding *binding;
236 *subreg=NULL;
239 binding=region_lookup_keybinding(reg, act, state, kcb, st,
240 binding_owner);
242 if(binding!=NULL)
243 break;
245 if(OBJ_IS(reg, WRootWin))
246 break;
248 *subreg=reg;
249 reg=REGION_PARENT_REG(reg);
250 }while(reg!=NULL);
252 return binding;
255 static WBinding *lookup_binding(WRegion *oreg,
256 int act, uint state, uint kcb,
257 WRegion **binding_owner, WRegion **subreg)
259 WRegion *reg=oreg;
261 /* Find the deepest nested active window grabbing this key. */
262 while(reg->active_sub!=NULL)
263 reg=reg->active_sub;
265 return lookup_binding_(reg, act, state, kcb, oreg->submapstat,
266 binding_owner, subreg);
270 static void do_call_binding(WBinding *binding, WRegion *reg, WRegion *subreg)
272 WRegion *mgd=region_managed_within(reg, subreg);
274 /* TODO: having to pass both mgd and subreg for some handlers
275 * to work is ugly and complex.
277 extl_call(binding->func, "ooo", NULL, reg, mgd, subreg);
281 static int do_key(WRegion *oreg, XKeyEvent *ev)
283 WBinding *binding=NULL;
284 WRegion *binding_owner=NULL, *subreg=NULL;
285 bool grabbed=(oreg->flags&REGION_BINDINGS_ARE_GRABBED);
286 int ret=GRAB_NONE;
288 if(grabbed){
289 binding=lookup_binding(oreg, BINDING_KEYPRESS, ev->state, ev->keycode,
290 &binding_owner, &subreg);
291 }else{
292 binding=region_lookup_keybinding(oreg, BINDING_KEYPRESS,
293 ev->state, ev->keycode,
294 oreg->submapstat,
295 &binding_owner);
298 if(binding!=NULL){
299 bool subs=(oreg->submapstat!=NULL);
300 WBinding *call=NULL;
302 if(binding->submap!=NULL){
303 WSubmapState *s=add_sub(oreg, ev->keycode, ev->state);
304 if(s!=NULL){
305 /*WRegion *own2, *subreg2;
307 call=lookup_binding(binding_owner, BINDING_SUBMAP_LEAVE, 0, 0,
308 oreg->submapstat, &own2, &subreg2);
310 if(call!=NULL){
311 s->leave=extl_ref_fn(call->func);
312 watch_setup(&s->leave_reg, (Obj*)own2, NULL);
315 call=lookup_binding_(binding_owner, BINDING_SUBMAP_ENTER, 0, 0,
316 oreg->submapstat,
317 &binding_owner, &subreg);
319 ret=(grabbed ? GRAB_SUBMAP : GRAB_NONE_SUBMAP);
321 }else{
322 call=binding;
324 if(grabbed)
325 XUngrabKeyboard(ioncore_g.dpy, CurrentTime);
327 if(ev->state!=0 && !subs && binding->wait)
328 ret=GRAB_WAITRELEASE;
331 if(call!=NULL){
332 current_kcb=ev->keycode;
333 current_state=ev->state;
334 current_submap=subs;
336 do_call_binding(call, binding_owner, subreg);
338 current_kcb=0;
340 }else if(oreg->submapstat==NULL && OBJ_IS(oreg, WWindow)){
341 insstr((WWindow*)oreg, ev);
344 return ret;
348 static bool submapgrab_handler(WRegion* reg, XEvent *xev)
350 XKeyEvent *ev=&xev->xkey;
351 if(ev->type!=KeyPress){
352 if(ioncore_unmod(ev->state, ev->keycode)==0){
353 WBinding *binding;
354 WRegion *binding_owner, *subreg;
356 binding=lookup_binding(reg,
357 BINDING_SUBMAP_RELEASEMOD, 0, 0,
358 &binding_owner, &subreg);
360 if(binding!=NULL)
361 do_call_binding(binding, binding_owner, subreg);
363 return FALSE;
366 if(ioncore_ismod(ev->keycode))
367 return FALSE;
368 if(do_key(reg, ev)!=GRAB_SUBMAP){
369 clear_subs(reg);
370 return TRUE;
371 }else{
372 return FALSE;
379 static void submapgrab(WRegion *reg)
381 ioncore_grab_establish(reg, submapgrab_handler, clear_subs, 0);
382 ioncore_change_grab_cursor(IONCORE_CURSOR_WAITKEY);
386 void ioncore_do_handle_keypress(XKeyEvent *ev)
388 WRegion *reg=(WRegion*)XWINDOW_REGION_OF(ev->window);
390 if(reg!=NULL){
391 Watch w=WATCH_INIT;
392 int grab;
394 /* reg might be destroyed by binding handlers */
395 watch_setup(&w, (Obj*)reg, NULL);
397 grab=do_key(reg, ev);
399 reg=(WRegion*)w.obj;
401 if(reg!=NULL){
402 if(grab==GRAB_SUBMAP)
403 submapgrab(reg);
404 else if(grab==GRAB_WAITRELEASE)
405 waitrelease(reg);
406 else if(grab==GRAB_NONE_SUBMAP)
407 /* nothing */;
408 else if(grab==GRAB_NONE && reg->submapstat!=NULL)
409 clear_subs(reg);
412 watch_reset(&w);