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.
29 #include "translate.h"
38 GSList
*actions
[OB_NUM_MOUSE_ACTIONS
]; /* lists of Action pointers */
41 #define FRAME_CONTEXT(co, cl) ((cl && cl->type != OB_CLIENT_TYPE_DESKTOP) ? \
42 co == OB_FRAME_CONTEXT_FRAME : FALSE)
43 #define CLIENT_CONTEXT(co, cl) ((cl && cl->type == OB_CLIENT_TYPE_DESKTOP) ? \
44 co == OB_FRAME_CONTEXT_DESKTOP : \
45 co == OB_FRAME_CONTEXT_CLIENT)
47 /* Array of GSList*s of ObMouseBinding*s. */
48 static GSList
*bound_contexts
[OB_FRAME_NUM_CONTEXTS
];
49 /* TRUE when we have a grab on the pointer and need to replay the pointer event
50 to send it to other applications */
51 static gboolean replay_pointer_needed
;
53 ObFrameContext
mouse_button_frame_context(ObFrameContext context
,
58 ObFrameContext x
= context
;
60 for (it
= bound_contexts
[context
]; it
; it
= g_slist_next(it
)) {
61 ObMouseBinding
*b
= it
->data
;
63 if (b
->button
== button
&& b
->state
== state
)
68 case OB_FRAME_CONTEXT_NONE
:
69 case OB_FRAME_CONTEXT_DESKTOP
:
70 case OB_FRAME_CONTEXT_CLIENT
:
71 case OB_FRAME_CONTEXT_TITLEBAR
:
72 case OB_FRAME_CONTEXT_FRAME
:
73 case OB_FRAME_CONTEXT_MOVE_RESIZE
:
74 case OB_FRAME_CONTEXT_LEFT
:
75 case OB_FRAME_CONTEXT_RIGHT
:
77 case OB_FRAME_CONTEXT_ROOT
:
78 x
= OB_FRAME_CONTEXT_DESKTOP
;
80 case OB_FRAME_CONTEXT_BOTTOM
:
81 case OB_FRAME_CONTEXT_BLCORNER
:
82 case OB_FRAME_CONTEXT_BRCORNER
:
83 x
= OB_FRAME_CONTEXT_BOTTOM
;
85 case OB_FRAME_CONTEXT_TLCORNER
:
86 case OB_FRAME_CONTEXT_TRCORNER
:
87 case OB_FRAME_CONTEXT_TOP
:
88 case OB_FRAME_CONTEXT_MAXIMIZE
:
89 case OB_FRAME_CONTEXT_ALLDESKTOPS
:
90 case OB_FRAME_CONTEXT_SHADE
:
91 case OB_FRAME_CONTEXT_ICONIFY
:
92 case OB_FRAME_CONTEXT_ICON
:
93 case OB_FRAME_CONTEXT_CLOSE
:
94 x
= OB_FRAME_CONTEXT_TITLEBAR
;
96 case OB_FRAME_NUM_CONTEXTS
:
97 g_assert_not_reached();
100 /* allow for multiple levels of fall-through */
102 return mouse_button_frame_context(x
, button
, state
);
107 void mouse_grab_for_client(ObClient
*client
, gboolean grab
)
112 for (i
= 0; i
< OB_FRAME_NUM_CONTEXTS
; ++i
)
113 for (it
= bound_contexts
[i
]; it
; it
= g_slist_next(it
)) {
114 /* grab/ungrab the button */
115 ObMouseBinding
*b
= it
->data
;
120 if (FRAME_CONTEXT(i
, client
)) {
121 win
= client
->frame
->window
;
122 mode
= GrabModeAsync
;
123 mask
= ButtonPressMask
| ButtonMotionMask
| ButtonReleaseMask
;
124 } else if (CLIENT_CONTEXT(i
, client
)) {
125 win
= client
->window
;
126 mode
= GrabModeSync
; /* this is handled in event */
127 mask
= ButtonPressMask
; /* can't catch more than this with Sync
128 mode the release event is
129 manufactured in event() */
133 grab_button_full(b
->button
, b
->state
, win
, mask
, mode
,
136 ungrab_button(b
->button
, b
->state
, win
);
140 static void grab_all_clients(gboolean grab
)
144 for (it
= client_list
; it
; it
= g_list_next(it
))
145 mouse_grab_for_client(it
->data
, grab
);
148 void mouse_unbind_all(void)
153 for(i
= 0; i
< OB_FRAME_NUM_CONTEXTS
; ++i
) {
154 for (it
= bound_contexts
[i
]; it
; it
= g_slist_next(it
)) {
155 ObMouseBinding
*b
= it
->data
;
158 for (j
= 0; j
< OB_NUM_MOUSE_ACTIONS
; ++j
) {
161 for (jt
= b
->actions
[j
]; jt
; jt
= g_slist_next(jt
))
162 actions_act_unref(jt
->data
);
163 g_slist_free(b
->actions
[j
]);
167 g_slist_free(bound_contexts
[i
]);
168 bound_contexts
[i
] = NULL
;
172 static ObUserAction
mouse_action_to_user_action(ObMouseAction a
)
175 case OB_MOUSE_ACTION_PRESS
: return OB_USER_ACTION_MOUSE_PRESS
;
176 case OB_MOUSE_ACTION_RELEASE
: return OB_USER_ACTION_MOUSE_RELEASE
;
177 case OB_MOUSE_ACTION_CLICK
: return OB_USER_ACTION_MOUSE_CLICK
;
178 case OB_MOUSE_ACTION_DOUBLE_CLICK
:
179 return OB_USER_ACTION_MOUSE_DOUBLE_CLICK
;
180 case OB_MOUSE_ACTION_MOTION
: return OB_USER_ACTION_MOUSE_MOTION
;
182 g_assert_not_reached();
186 static gboolean
fire_binding(ObMouseAction a
, ObFrameContext context
,
187 ObClient
*c
, guint state
,
188 guint button
, gint x
, gint y
)
193 for (it
= bound_contexts
[context
]; it
; it
= g_slist_next(it
)) {
195 if (b
->state
== state
&& b
->button
== button
)
198 /* if not bound, then nothing to do! */
199 if (it
== NULL
) return FALSE
;
201 actions_run_acts(b
->actions
[a
], mouse_action_to_user_action(a
),
202 state
, x
, y
, button
, context
, c
);
206 void mouse_replay_pointer()
208 if (replay_pointer_needed
) {
209 /* replay the pointer event before any windows move */
210 XAllowEvents(ob_display
, ReplayPointer
, event_curtime
);
211 replay_pointer_needed
= FALSE
;
215 void mouse_event(ObClient
*client
, XEvent
*e
)
218 static guint button
= 0, state
= 0, lbutton
= 0;
219 static Window lwindow
= None
;
220 static gint px
, py
, pwx
= -1, pwy
= -1;
222 ObFrameContext context
;
223 gboolean click
= FALSE
;
224 gboolean dclick
= FALSE
;
228 context
= frame_context(client
, e
->xbutton
.window
,
229 e
->xbutton
.x
, e
->xbutton
.y
);
230 context
= mouse_button_frame_context(context
, e
->xbutton
.button
,
233 px
= e
->xbutton
.x_root
;
234 py
= e
->xbutton
.y_root
;
235 if (!button
) pwx
= e
->xbutton
.x
;
236 if (!button
) pwy
= e
->xbutton
.y
;
237 button
= e
->xbutton
.button
;
238 state
= e
->xbutton
.state
;
240 /* if the binding was in a client context, then we need to call
241 XAllowEvents with ReplayPointer at some point, to send the event
242 through to the client. when this happens though depends. if
243 windows are going to be moved on screen, then the click will end
244 up going somewhere wrong, set that we need it, and if nothing
245 else causes the replay pointer to be run, then we will do it
246 after all the actions are finished.
248 (We do it after all the actions because FocusIn interrupts
249 dragging for kdesktop, so if we send the button event now, and
250 then they get a focus event after, it breaks. Instead, wait to send
251 the button press until after the actions when possible.)
253 if (CLIENT_CONTEXT(context
, client
))
254 replay_pointer_needed
= TRUE
;
256 fire_binding(OB_MOUSE_ACTION_PRESS
, context
,
257 client
, e
->xbutton
.state
,
259 e
->xbutton
.x_root
, e
->xbutton
.y_root
);
261 /* if the bindings grab the pointer, there won't be a ButtonRelease
263 if (grab_on_pointer())
266 /* replay the pointer event if it hasn't been replayed yet (i.e. no
267 windows were moved) */
268 mouse_replay_pointer();
270 /* in the client context, we won't get a button release because of the
271 way it is grabbed, so just fake one */
272 if (!CLIENT_CONTEXT(context
, client
))
276 /* use where the press occured in the window */
277 context
= frame_context(client
, e
->xbutton
.window
, pwx
, pwy
);
278 context
= mouse_button_frame_context(context
, e
->xbutton
.button
,
281 if (e
->xbutton
.button
== button
)
284 if (e
->xbutton
.button
== button
) {
285 /* clicks are only valid if its released over the window */
288 guint ujunk
, b
, w
, h
;
289 /* this can cause errors to occur when the window closes */
290 xerror_set_ignore(TRUE
);
291 junk1
= XGetGeometry(ob_display
, e
->xbutton
.window
,
292 &wjunk
, &junk1
, &junk2
, &w
, &h
, &b
, &ujunk
);
293 xerror_set_ignore(FALSE
);
295 if (e
->xbutton
.x
>= (signed)-b
&&
296 e
->xbutton
.y
>= (signed)-b
&&
297 e
->xbutton
.x
< (signed)(w
+b
) &&
298 e
->xbutton
.y
< (signed)(h
+b
)) {
300 /* double clicks happen if there were 2 in a row! */
301 if (lbutton
== button
&&
302 lwindow
== e
->xbutton
.window
&&
303 e
->xbutton
.time
- config_mouse_dclicktime
<=
309 lwindow
= e
->xbutton
.window
;
319 ltime
= e
->xbutton
.time
;
321 fire_binding(OB_MOUSE_ACTION_RELEASE
, context
,
322 client
, e
->xbutton
.state
,
327 fire_binding(OB_MOUSE_ACTION_CLICK
, context
,
328 client
, e
->xbutton
.state
,
333 fire_binding(OB_MOUSE_ACTION_DOUBLE_CLICK
, context
,
334 client
, e
->xbutton
.state
,
342 context
= frame_context(client
, e
->xmotion
.window
, pwx
, pwy
);
343 context
= mouse_button_frame_context(context
, button
, state
);
345 if (ABS(e
->xmotion
.x_root
- px
) >= config_mouse_threshold
||
346 ABS(e
->xmotion
.y_root
- py
) >= config_mouse_threshold
) {
348 /* You can't drag on buttons */
349 if (context
== OB_FRAME_CONTEXT_MAXIMIZE
||
350 context
== OB_FRAME_CONTEXT_ALLDESKTOPS
||
351 context
== OB_FRAME_CONTEXT_SHADE
||
352 context
== OB_FRAME_CONTEXT_ICONIFY
||
353 context
== OB_FRAME_CONTEXT_ICON
||
354 context
== OB_FRAME_CONTEXT_CLOSE
)
357 fire_binding(OB_MOUSE_ACTION_MOTION
, context
,
358 client
, state
, button
, px
, py
);
366 g_assert_not_reached();
370 gboolean
mouse_bind(const gchar
*buttonstr
, const gchar
*contextstr
,
371 ObMouseAction mact
, ObActionsAct
*action
)
374 ObFrameContext context
;
378 if (!translate_button(buttonstr
, &state
, &button
)) {
379 g_message(_("Invalid button '%s' in mouse binding"), buttonstr
);
383 context
= frame_context_from_string(contextstr
);
385 g_message(_("Invalid context '%s' in mouse binding"), contextstr
);
389 for (it
= bound_contexts
[context
]; it
; it
= g_slist_next(it
)) {
391 if (b
->state
== state
&& b
->button
== button
) {
392 b
->actions
[mact
] = g_slist_append(b
->actions
[mact
], action
);
397 /* add the binding */
398 b
= g_new0(ObMouseBinding
, 1);
401 b
->actions
[mact
] = g_slist_append(NULL
, action
);
402 bound_contexts
[context
] = g_slist_append(bound_contexts
[context
], b
);
407 void mouse_startup(gboolean reconfig
)
409 grab_all_clients(TRUE
);
412 void mouse_shutdown(gboolean reconfig
)
414 grab_all_clients(FALSE
);