1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 mouse.c for the Openbox window manager
4 Copyright (c) 2006 Mikael Magnusson
5 Copyright (c) 2003-2007 Dana Jansens
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 See the COPYING file for a copy of the GNU General Public License.
27 #include "translate.h"
30 #include "obt/display.h"
37 GSList
*actions
[OB_NUM_MOUSE_ACTIONS
]; /* lists of Action pointers */
40 /* Array of GSList*s of ObMouseBinding*s. */
41 static GSList
*bound_contexts
[OB_FRAME_NUM_CONTEXTS
];
42 /* TRUE when we have a grab on the pointer and need to replay the pointer event
43 to send it to other applications */
44 static gboolean replay_pointer_needed
;
46 ObFrameContext
mouse_button_frame_context(ObFrameContext context
,
51 ObFrameContext x
= context
;
53 for (it
= bound_contexts
[context
]; it
; it
= g_slist_next(it
)) {
54 ObMouseBinding
*b
= it
->data
;
56 if (b
->button
== button
&& b
->state
== state
)
61 case OB_FRAME_CONTEXT_NONE
:
62 case OB_FRAME_CONTEXT_DESKTOP
:
63 case OB_FRAME_CONTEXT_CLIENT
:
64 case OB_FRAME_CONTEXT_TITLEBAR
:
65 case OB_FRAME_CONTEXT_FRAME
:
66 case OB_FRAME_CONTEXT_MOVE_RESIZE
:
67 case OB_FRAME_CONTEXT_LEFT
:
68 case OB_FRAME_CONTEXT_RIGHT
:
70 case OB_FRAME_CONTEXT_ROOT
:
71 x
= OB_FRAME_CONTEXT_DESKTOP
;
73 case OB_FRAME_CONTEXT_BOTTOM
:
74 case OB_FRAME_CONTEXT_BLCORNER
:
75 case OB_FRAME_CONTEXT_BRCORNER
:
76 x
= OB_FRAME_CONTEXT_BOTTOM
;
78 case OB_FRAME_CONTEXT_TLCORNER
:
79 case OB_FRAME_CONTEXT_TRCORNER
:
80 case OB_FRAME_CONTEXT_TOP
:
81 case OB_FRAME_CONTEXT_MAXIMIZE
:
82 case OB_FRAME_CONTEXT_ALLDESKTOPS
:
83 case OB_FRAME_CONTEXT_SHADE
:
84 case OB_FRAME_CONTEXT_ICONIFY
:
85 case OB_FRAME_CONTEXT_ICON
:
86 case OB_FRAME_CONTEXT_CLOSE
:
87 x
= OB_FRAME_CONTEXT_TITLEBAR
;
89 case OB_FRAME_NUM_CONTEXTS
:
90 g_assert_not_reached();
93 /* allow for multiple levels of fall-through */
95 return mouse_button_frame_context(x
, button
, state
);
100 void mouse_grab_for_client(ObClient
*client
, gboolean grab
)
105 for (i
= 0; i
< OB_FRAME_NUM_CONTEXTS
; ++i
)
106 for (it
= bound_contexts
[i
]; it
; it
= g_slist_next(it
)) {
107 /* grab/ungrab the button */
108 ObMouseBinding
*b
= it
->data
;
113 if (FRAME_CONTEXT(i
, client
)) {
114 win
= client
->frame
->window
;
115 mode
= GrabModeAsync
;
116 mask
= ButtonPressMask
| ButtonMotionMask
| ButtonReleaseMask
;
117 } else if (CLIENT_CONTEXT(i
, client
)) {
118 win
= client
->window
;
119 mode
= GrabModeSync
; /* this is handled in event */
120 mask
= ButtonPressMask
; /* can't catch more than this with Sync
121 mode the release event is
122 manufactured in event() */
126 grab_button_full(b
->button
, b
->state
, win
, mask
, mode
,
129 ungrab_button(b
->button
, b
->state
, win
);
133 static void grab_all_clients(gboolean grab
)
137 for (it
= client_list
; it
; it
= g_list_next(it
))
138 mouse_grab_for_client(it
->data
, grab
);
141 void mouse_unbind_all(void)
146 for(i
= 0; i
< OB_FRAME_NUM_CONTEXTS
; ++i
) {
147 for (it
= bound_contexts
[i
]; it
; it
= g_slist_next(it
)) {
148 ObMouseBinding
*b
= it
->data
;
151 for (j
= 0; j
< OB_NUM_MOUSE_ACTIONS
; ++j
) {
154 for (jt
= b
->actions
[j
]; jt
; jt
= g_slist_next(jt
))
155 actions_act_unref(jt
->data
);
156 g_slist_free(b
->actions
[j
]);
160 g_slist_free(bound_contexts
[i
]);
161 bound_contexts
[i
] = NULL
;
165 static ObUserAction
mouse_action_to_user_action(ObMouseAction a
)
168 case OB_MOUSE_ACTION_PRESS
: return OB_USER_ACTION_MOUSE_PRESS
;
169 case OB_MOUSE_ACTION_RELEASE
: return OB_USER_ACTION_MOUSE_RELEASE
;
170 case OB_MOUSE_ACTION_CLICK
: return OB_USER_ACTION_MOUSE_CLICK
;
171 case OB_MOUSE_ACTION_DOUBLE_CLICK
:
172 return OB_USER_ACTION_MOUSE_DOUBLE_CLICK
;
173 case OB_MOUSE_ACTION_MOTION
: return OB_USER_ACTION_MOUSE_MOTION
;
175 g_assert_not_reached();
179 static gboolean
fire_binding(ObMouseAction a
, ObFrameContext context
,
180 ObClient
*c
, guint state
,
181 guint button
, gint x
, gint y
)
186 for (it
= bound_contexts
[context
]; it
; it
= g_slist_next(it
)) {
188 if (b
->state
== state
&& b
->button
== button
)
191 /* if not bound, then nothing to do! */
192 if (it
== NULL
) return FALSE
;
194 actions_run_acts(b
->actions
[a
], mouse_action_to_user_action(a
),
195 state
, x
, y
, button
, context
, c
);
199 void mouse_replay_pointer(void)
201 if (replay_pointer_needed
) {
202 /* replay the pointer event before any windows move */
203 XAllowEvents(obt_display
, ReplayPointer
, event_curtime
);
204 replay_pointer_needed
= FALSE
;
208 gboolean
mouse_event(ObClient
*client
, XEvent
*e
)
211 static guint button
= 0, state
= 0, lbutton
= 0;
212 static Window lwindow
= None
;
213 static gint px
, py
, pwx
= -1, pwy
= -1;
214 gboolean used
= FALSE
;
216 ObFrameContext context
;
217 gboolean click
= FALSE
;
218 gboolean dclick
= FALSE
;
222 context
= frame_context(client
, e
->xbutton
.window
,
223 e
->xbutton
.x
, e
->xbutton
.y
);
224 context
= mouse_button_frame_context(context
, e
->xbutton
.button
,
227 px
= e
->xbutton
.x_root
;
228 py
= e
->xbutton
.y_root
;
229 if (!button
) pwx
= e
->xbutton
.x
;
230 if (!button
) pwy
= e
->xbutton
.y
;
231 button
= e
->xbutton
.button
;
232 state
= e
->xbutton
.state
;
234 /* if the binding was in a client context, then we need to call
235 XAllowEvents with ReplayPointer at some point, to send the event
236 through to the client. when this happens though depends. if
237 windows are going to be moved on screen, then the click will end
238 up going somewhere wrong, set that we need it, and if nothing
239 else causes the replay pointer to be run, then we will do it
240 after all the actions are finished.
242 (We do it after all the actions because FocusIn interrupts
243 dragging for kdesktop, so if we send the button event now, and
244 then they get a focus event after, it breaks. Instead, wait to send
245 the button press until after the actions when possible.)
247 if (CLIENT_CONTEXT(context
, client
))
248 replay_pointer_needed
= TRUE
;
250 used
= fire_binding(OB_MOUSE_ACTION_PRESS
, context
,
251 client
, e
->xbutton
.state
,
253 e
->xbutton
.x_root
, e
->xbutton
.y_root
) || used
;
255 /* if the bindings grab the pointer, there won't be a ButtonRelease
257 if (grab_on_pointer())
260 /* replay the pointer event if it hasn't been replayed yet (i.e. no
261 windows were moved) */
262 mouse_replay_pointer();
264 /* in the client context, we won't get a button release because of the
265 way it is grabbed, so just fake one */
266 if (!CLIENT_CONTEXT(context
, client
))
270 /* use where the press occured in the window */
271 context
= frame_context(client
, e
->xbutton
.window
, pwx
, pwy
);
272 context
= mouse_button_frame_context(context
, e
->xbutton
.button
,
275 if (e
->xbutton
.button
== button
)
278 if (e
->xbutton
.button
== button
) {
279 /* clicks are only valid if its released over the window */
282 guint ujunk
, b
, w
, h
;
283 /* this can cause errors to occur when the window closes */
284 obt_display_ignore_errors(TRUE
);
285 junk1
= XGetGeometry(obt_display
, e
->xbutton
.window
,
286 &wjunk
, &junk1
, &junk2
, &w
, &h
, &b
, &ujunk
);
287 obt_display_ignore_errors(FALSE
);
289 if (e
->xbutton
.x
>= (signed)-b
&&
290 e
->xbutton
.y
>= (signed)-b
&&
291 e
->xbutton
.x
< (signed)(w
+b
) &&
292 e
->xbutton
.y
< (signed)(h
+b
)) {
294 /* double clicks happen if there were 2 in a row! */
295 if (lbutton
== button
&&
296 lwindow
== e
->xbutton
.window
&&
297 e
->xbutton
.time
- config_mouse_dclicktime
<=
303 lwindow
= e
->xbutton
.window
;
313 ltime
= e
->xbutton
.time
;
315 used
= fire_binding(OB_MOUSE_ACTION_RELEASE
, context
,
316 client
, e
->xbutton
.state
,
319 e
->xbutton
.y_root
) || used
;
321 used
= fire_binding(OB_MOUSE_ACTION_CLICK
, context
,
322 client
, e
->xbutton
.state
,
325 e
->xbutton
.y_root
) || used
;
327 used
= fire_binding(OB_MOUSE_ACTION_DOUBLE_CLICK
, context
,
328 client
, e
->xbutton
.state
,
331 e
->xbutton
.y_root
) || used
;
336 context
= frame_context(client
, e
->xmotion
.window
, pwx
, pwy
);
337 context
= mouse_button_frame_context(context
, button
, state
);
339 if (ABS(e
->xmotion
.x_root
- px
) >= config_mouse_threshold
||
340 ABS(e
->xmotion
.y_root
- py
) >= config_mouse_threshold
) {
342 /* You can't drag on buttons */
343 if (context
== OB_FRAME_CONTEXT_MAXIMIZE
||
344 context
== OB_FRAME_CONTEXT_ALLDESKTOPS
||
345 context
== OB_FRAME_CONTEXT_SHADE
||
346 context
== OB_FRAME_CONTEXT_ICONIFY
||
347 context
== OB_FRAME_CONTEXT_ICON
||
348 context
== OB_FRAME_CONTEXT_CLOSE
)
351 used
= fire_binding(OB_MOUSE_ACTION_MOTION
, context
,
352 client
, state
, button
, px
, py
);
360 g_assert_not_reached();
365 gboolean
mouse_bind(const gchar
*buttonstr
, const gchar
*contextstr
,
366 ObMouseAction mact
, ObActionsAct
*action
)
369 ObFrameContext context
;
373 if (!translate_button(buttonstr
, &state
, &button
)) {
374 g_message(_("Invalid button \"%s\" in mouse binding"), buttonstr
);
378 context
= frame_context_from_string(contextstr
);
380 g_message(_("Invalid context \"%s\" in mouse binding"), contextstr
);
384 for (it
= bound_contexts
[context
]; it
; it
= g_slist_next(it
)) {
386 if (b
->state
== state
&& b
->button
== button
) {
387 b
->actions
[mact
] = g_slist_append(b
->actions
[mact
], action
);
392 /* add the binding */
393 b
= g_new0(ObMouseBinding
, 1);
396 b
->actions
[mact
] = g_slist_append(NULL
, action
);
397 bound_contexts
[context
] = g_slist_append(bound_contexts
[context
], b
);
402 void mouse_startup(gboolean reconfig
)
404 grab_all_clients(TRUE
);
407 void mouse_shutdown(gboolean reconfig
)
409 grab_all_clients(FALSE
);