1 #include "../../kernel/openbox.h"
2 #include "../../kernel/dispatch.h"
3 #include "../../kernel/action.h"
4 #include "../../kernel/client.h"
5 #include "../../kernel/frame.h"
6 #include "../../kernel/grab.h"
7 #include "../../kernel/engine.h"
12 static int drag_threshold
= 3;
14 /* GData of GSList*s of PointerBinding*s. */
15 static GData
*bound_contexts
;
17 struct foreach_grab_temp
{
22 static void foreach_grab(GQuark key
, gpointer data
, gpointer user_data
)
24 struct foreach_grab_temp
*d
= user_data
;
26 for (it
= data
; it
!= NULL
; it
= it
->next
) {
27 /* grab/ungrab the button */
28 MouseBinding
*b
= it
->data
;
33 if (key
== g_quark_try_string("frame")) {
34 win
= d
->client
->frame
->window
;
36 mask
= ButtonPressMask
| ButtonMotionMask
| ButtonReleaseMask
;
37 } else if (key
== g_quark_try_string("client")) {
38 win
= d
->client
->frame
->plate
;
39 mode
= GrabModeSync
; /* this is handled in event */
40 mask
= ButtonPressMask
; /* can't catch more than this with Sync
41 mode the release event is manufactured
46 grab_button(b
->button
, b
->state
, win
, mask
, mode
);
48 ungrab_button(b
->button
, b
->state
, win
);
52 static void grab_for_client(Client
*client
, gboolean grab
)
54 struct foreach_grab_temp bt
;
57 g_datalist_foreach(&bound_contexts
, foreach_grab
, &bt
);
60 static void grab_all_clients(gboolean grab
)
64 for (it
= client_list
; it
!= NULL
; it
= it
->next
)
65 grab_for_client(it
->data
, grab
);
68 static void foreach_clear(GQuark key
, gpointer data
, gpointer user_data
)
71 user_data
= user_data
;
72 for (it
= data
; it
!= NULL
; it
= it
->next
) {
75 MouseBinding
*b
= it
->data
;
76 for (i
= 0; i
< NUM_MOUSEACTION
; ++i
)
77 if (b
->action
[i
] != NULL
)
78 action_free(b
->action
[i
]);
84 static void fire_button(MouseAction a
, GQuark context
, Client
*c
, guint state
,
90 for (it
= g_datalist_id_get_data(&bound_contexts
, context
);
91 it
!= NULL
; it
= it
->next
) {
93 if (b
->state
== state
&& b
->button
== button
)
96 /* if not bound, then nothing to do! */
97 if (it
== NULL
) return;
99 if (b
->action
[a
] != NULL
&& b
->action
[a
]->func
!= NULL
) {
100 b
->action
[a
]->data
.any
.c
= c
;
102 g_assert(!(b
->action
[a
]->func
== action_move
||
103 b
->action
[a
]->func
== action_resize
));
105 b
->action
[a
]->func(&b
->action
[a
]->data
);
109 /* corner should be the opposite corner of the window in which the pointer
110 clicked, Corner_TopLeft if a good default if there is no client */
111 static void fire_motion(MouseAction a
, GQuark context
, Client
*c
, guint state
,
112 guint button
, int cx
, int cy
, int cw
, int ch
,
113 int dx
, int dy
, gboolean final
, Corner corner
)
118 for (it
= g_datalist_id_get_data(&bound_contexts
, context
);
119 it
!= NULL
; it
= it
->next
) {
121 if (b
->state
== state
&& b
->button
== button
)
124 /* if not bound, then nothing to do! */
125 if (it
== NULL
) return;
127 if (b
->action
[a
] != NULL
&& b
->action
[a
]->func
!= NULL
) {
128 b
->action
[a
]->data
.any
.c
= c
;
130 if (b
->action
[a
]->func
== action_move
) {
131 b
->action
[a
]->data
.move
.x
= cx
+ dx
;
132 b
->action
[a
]->data
.move
.y
= cy
+ dy
;
133 b
->action
[a
]->data
.move
.final
= final
;
134 } else if (b
->action
[a
]->func
== action_resize
) {
135 b
->action
[a
]->data
.resize
.corner
= corner
;
138 b
->action
[a
]->data
.resize
.x
= cw
+ dx
;
139 b
->action
[a
]->data
.resize
.y
= ch
+ dy
;
141 case Corner_TopRight
:
142 b
->action
[a
]->data
.resize
.x
= cw
- dx
;
143 b
->action
[a
]->data
.resize
.y
= ch
+ dy
;
145 case Corner_BottomLeft
:
146 b
->action
[a
]->data
.resize
.x
= cw
+ dx
;
147 b
->action
[a
]->data
.resize
.y
= ch
- dy
;
149 case Corner_BottomRight
:
150 b
->action
[a
]->data
.resize
.x
= cw
- dx
;
151 b
->action
[a
]->data
.resize
.y
= ch
- dy
;
154 b
->action
[a
]->data
.resize
.final
= final
;
156 b
->action
[a
]->func(&b
->action
[a
]->data
);
160 static Corner
pick_corner(int x
, int y
, int cx
, int cy
, int cw
, int ch
)
162 if (x
- cx
< cw
/ 2) {
164 return Corner_BottomRight
;
166 return Corner_TopRight
;
169 return Corner_BottomLeft
;
171 return Corner_TopLeft
;
175 static void event(ObEvent
*e
, void *foo
)
178 static int px
, py
, cx
, cy
, cw
, ch
, dx
, dy
;
179 static guint button
= 0, lbutton
= 0;
180 static gboolean drag
= FALSE
;
181 static Corner corner
= Corner_TopLeft
;
182 gboolean click
= FALSE
;
183 gboolean dclick
= FALSE
;
187 case Event_Client_Mapped
:
188 grab_for_client(e
->data
.c
.client
, TRUE
);
191 case Event_Client_Destroy
:
192 grab_for_client(e
->data
.c
.client
, FALSE
);
195 case Event_X_ButtonPress
:
197 if (e
->data
.x
.client
!= NULL
) {
198 cx
= e
->data
.x
.client
->frame
->area
.x
;
199 cy
= e
->data
.x
.client
->frame
->area
.y
;
200 cw
= e
->data
.x
.client
->frame
->area
.width
;
201 ch
= e
->data
.x
.client
->frame
->area
.height
;
202 px
= e
->data
.x
.e
->xbutton
.x_root
;
203 py
= e
->data
.x
.e
->xbutton
.y_root
;
204 corner
= pick_corner(px
, py
, cx
, cy
, cw
, ch
);
206 button
= e
->data
.x
.e
->xbutton
.button
;
208 context
= engine_get_context(e
->data
.x
.client
,
209 e
->data
.x
.e
->xbutton
.window
);
211 fire_button(MouseAction_Press
, context
,
212 e
->data
.x
.client
, e
->data
.x
.e
->xbutton
.state
,
213 e
->data
.x
.e
->xbutton
.button
);
215 if (context
== g_quark_try_string("client")) {
216 /* Replay the event, so it goes to the client*/
217 XAllowEvents(ob_display
, ReplayPointer
, CurrentTime
);
218 /* Fall through to the release case! */
222 case Event_X_ButtonRelease
:
223 context
= engine_get_context(e
->data
.x
.client
,
224 e
->data
.x
.e
->xbutton
.window
);
225 if (e
->data
.x
.e
->xbutton
.button
== button
) {
228 fire_motion(MouseAction_Motion
, context
,
229 e
->data
.x
.client
, e
->data
.x
.e
->xbutton
.state
,
230 e
->data
.x
.e
->xbutton
.button
,
231 cx
, cy
, cw
, ch
, dx
, dy
, TRUE
, corner
);
236 /* clicks are only valid if its released over the window */
237 if (e
->data
.x
.e
->xbutton
.x
>= 0 &&
238 e
->data
.x
.e
->xbutton
.y
>= 0) {
242 XGetGeometry(ob_display
, e
->data
.x
.e
->xbutton
.window
,
243 &wjunk
, &junk
, &junk
, &w
, &h
, &ujunk
, &ujunk
);
244 if (e
->data
.x
.e
->xbutton
.x
< (signed)w
&&
245 e
->data
.x
.e
->xbutton
.y
< (signed)h
) {
247 /* double clicks happen if there were 2 in a row! */
248 if (lbutton
== button
&&
249 e
->data
.x
.e
->xbutton
.time
- 300 <= ltime
)
258 ltime
= e
->data
.x
.e
->xbutton
.time
;
260 fire_button(MouseAction_Press
, context
,
261 e
->data
.x
.client
, e
->data
.x
.e
->xbutton
.state
,
262 e
->data
.x
.e
->xbutton
.button
);
264 fire_button(MouseAction_Click
, context
,
265 e
->data
.x
.client
, e
->data
.x
.e
->xbutton
.state
,
266 e
->data
.x
.e
->xbutton
.button
);
268 fire_button(MouseAction_DClick
, context
,
269 e
->data
.x
.client
, e
->data
.x
.e
->xbutton
.state
,
270 e
->data
.x
.e
->xbutton
.button
);
273 case Event_X_MotionNotify
:
275 dx
= e
->data
.x
.e
->xmotion
.x_root
- px
;
276 dy
= e
->data
.x
.e
->xmotion
.y_root
- py
;
277 if (ABS(dx
) >= drag_threshold
|| ABS(dy
) >= drag_threshold
)
280 context
= engine_get_context(e
->data
.x
.client
,
281 e
->data
.x
.e
->xbutton
.window
);
282 fire_motion(MouseAction_Motion
, context
,
283 e
->data
.x
.client
, e
->data
.x
.e
->xmotion
.state
,
284 button
, cx
, cy
, cw
, ch
, dx
, dy
, FALSE
, corner
);
290 g_assert_not_reached();
294 static gboolean
mbind(char *buttonstr
, char *contextstr
, MouseAction mact
,
303 if (!translate_button(buttonstr
, &state
, &button
)) {
304 g_warning("invalid button");
308 context
= g_quark_try_string(contextstr
);
310 g_warning("invalid context");
314 for (it
= g_datalist_id_get_data(&bound_contexts
, context
);
315 it
!= NULL
; it
= it
->next
){
317 if (b
->state
== state
&& b
->button
== button
) {
319 if (b
->action
[mact
] != NULL
) {
320 g_warning("duplicate binding");
323 b
->action
[mact
] = action
;
328 grab_all_clients(FALSE
);
330 /* add the binding */
331 b
= g_new(MouseBinding
, 1);
334 for (i
= 0; i
< NUM_MOUSEACTION
; ++i
)
336 b
->action
[mact
] = action
;
337 g_datalist_id_set_data(&bound_contexts
, context
,
338 g_slist_append(g_datalist_id_get_data(&bound_contexts
, context
), b
));
340 grab_all_clients(TRUE
);
345 static void binddef()
349 /* When creating an Action struct, all of the data elements in the
350 appropriate struct need to be set, except the Client*, which will be set
351 at call-time when then action function is used.
353 For action_move and action_resize, the 'x', 'y', and 'final' data
354 elements should not be set, as they are set at call-time.
357 a
= action_new(action_move
);
358 mbind("1", "titlebar", MouseAction_Motion
, a
);
359 a
= action_new(action_move
);
360 mbind("1", "handle", MouseAction_Motion
, a
);
361 a
= action_new(action_move
);
362 mbind("A-1", "frame", MouseAction_Motion
, a
);
364 a
= action_new(action_resize
);
365 mbind("1", "blcorner", MouseAction_Motion
, a
);
366 a
= action_new(action_resize
);
367 mbind("1", "brcorner", MouseAction_Motion
, a
);
368 a
= action_new(action_resize
);
369 mbind("A-3", "frame", MouseAction_Motion
, a
);
371 a
= action_new(action_focus
);
372 mbind("1", "titlebar", MouseAction_Press
, a
);
373 a
= action_new(action_focus
);
374 mbind("1", "handle", MouseAction_Press
, a
);
375 a
= action_new(action_raise
);
376 mbind("1", "titlebar", MouseAction_Click
, a
);
377 a
= action_new(action_raise
);
378 mbind("1", "handle", MouseAction_Click
, a
);
379 a
= action_new(action_lower
);
380 mbind("2", "titlebar", MouseAction_Press
, a
);
381 a
= action_new(action_lower
);
382 mbind("2", "handle", MouseAction_Press
, a
);
383 a
= action_new(action_raise
);
384 mbind("A-1", "frame", MouseAction_Click
, a
);
385 a
= action_new(action_lower
);
386 mbind("A-3", "frame", MouseAction_Click
, a
);
388 a
= action_new(action_focus
);
389 mbind("1", "client", MouseAction_Press
, a
);
391 a
= action_new(action_toggle_shade
);
392 mbind("1", "titlebar", MouseAction_DClick
, a
);
393 a
= action_new(action_shade
);
394 mbind("4", "titlebar", MouseAction_Press
, a
);
395 a
= action_new(action_unshade
);
396 mbind("5", "titlebar", MouseAction_Click
, a
);
398 a
= action_new(action_toggle_maximize_full
);
399 mbind("1", "maximize", MouseAction_Click
, a
);
400 a
= action_new(action_toggle_maximize_vert
);
401 mbind("2", "maximize", MouseAction_Click
, a
);
402 a
= action_new(action_toggle_maximize_horz
);
403 mbind("3", "maximize", MouseAction_Click
, a
);
404 a
= action_new(action_iconify
);
405 mbind("1", "iconify", MouseAction_Click
, a
);
406 a
= action_new(action_close
);
407 mbind("1", "icon", MouseAction_DClick
, a
);
408 a
= action_new(action_close
);
409 mbind("1", "close", MouseAction_Click
, a
);
410 a
= action_new(action_kill
);
411 mbind("2", "close", MouseAction_Click
, a
);
412 a
= action_new(action_toggle_omnipresent
);
413 mbind("1", "alldesktops", MouseAction_Click
, a
);
415 a
= action_new(action_next_desktop
);
416 a
->data
.nextprevdesktop
.wrap
= TRUE
;
417 mbind("4", "root", MouseAction_Click
, a
);
418 a
= action_new(action_previous_desktop
);
419 a
->data
.nextprevdesktop
.wrap
= TRUE
;
420 mbind("5", "root", MouseAction_Click
, a
);
421 a
= action_new(action_next_desktop
);
422 a
->data
.nextprevdesktop
.wrap
= TRUE
;
423 mbind("A-4", "root", MouseAction_Click
, a
);
424 a
= action_new(action_previous_desktop
);
425 a
->data
.nextprevdesktop
.wrap
= TRUE
;
426 mbind("A-5", "root", MouseAction_Click
, a
);
427 a
= action_new(action_next_desktop
);
428 a
->data
.nextprevdesktop
.wrap
= TRUE
;
429 mbind("A-4", "frame", MouseAction_Click
, a
);
430 a
= action_new(action_previous_desktop
);
431 a
->data
.nextprevdesktop
.wrap
= TRUE
;
432 mbind("A-5", "frame", MouseAction_Click
, a
);
435 void plugin_startup()
437 dispatch_register(Event_Client_Mapped
| Event_Client_Destroy
|
438 Event_X_ButtonPress
| Event_X_ButtonRelease
|
439 Event_X_MotionNotify
, (EventHandler
)event
, NULL
);
441 /* XXX parse a config */
445 void plugin_shutdown()
447 dispatch_register(0, (EventHandler
)event
, NULL
);
449 grab_all_clients(FALSE
);
450 g_datalist_foreach(&bound_contexts
, foreach_clear
, NULL
);