grab the pointer when windows move them selves so no enter events happen. i wonder...
[mikachu/openbox.git] / openbox / moveresize.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    moveresize.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
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.
11
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.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "grab.h"
21 #include "framerender.h"
22 #include "screen.h"
23 #include "prop.h"
24 #include "client.h"
25 #include "frame.h"
26 #include "openbox.h"
27 #include "resist.h"
28 #include "popup.h"
29 #include "moveresize.h"
30 #include "config.h"
31 #include "event.h"
32 #include "debug.h"
33 #include "extensions.h"
34 #include "render/render.h"
35 #include "render/theme.h"
36
37 #include <X11/Xlib.h>
38 #include <glib.h>
39
40 /* how far windows move and resize with the keyboard arrows */
41 #define KEY_DIST 4
42
43 gboolean moveresize_in_progress = FALSE;
44 ObClient *moveresize_client = NULL;
45 #ifdef SYNC
46 XSyncAlarm moveresize_alarm = None;
47 #endif
48
49 static gboolean moving = FALSE; /* TRUE - moving, FALSE - resizing */
50
51 static gint start_x, start_y, start_cx, start_cy, start_cw, start_ch;
52 static gint cur_x, cur_y;
53 static guint button;
54 static guint32 corner;
55 static ObCorner lockcorner;
56 #ifdef SYNC
57 static gboolean waiting_for_sync;
58 #endif
59
60 static ObPopup *popup = NULL;
61
62 static void client_dest(ObClient *client, gpointer data)
63 {
64     if (moveresize_client == client)
65         moveresize_end(TRUE);    
66 }
67
68 void moveresize_startup(gboolean reconfig)
69 {
70     popup = popup_new(FALSE);
71
72     if (!reconfig)
73         client_add_destroy_notify(client_dest, NULL);
74 }
75
76 void moveresize_shutdown(gboolean reconfig)
77 {
78     if (!reconfig) {
79         if (moveresize_in_progress)
80             moveresize_end(FALSE);
81         client_remove_destroy_notify(client_dest);
82     }
83
84     popup_free(popup);
85     popup = NULL;
86 }
87
88 static void get_resize_position(gint *x, gint *y, gboolean cancel)
89 {
90     gint dw, dh;
91     gint w, h, lw, lh;
92
93     *x = moveresize_client->frame->area.x;
94     *y = moveresize_client->frame->area.y;
95
96     if (cancel) {
97         w = start_cw;
98         h = start_ch;
99     } else {
100         w = cur_x;
101         h = cur_y;
102     }
103
104     /* see how much it is actually going to resize */
105     {
106         gint cx = *x, cy = *y;
107         frame_frame_gravity(moveresize_client->frame, &cx, &cy, w, h);
108         client_try_configure(moveresize_client, &cx, &cy, &w, &h,
109                              &lw, &lh, TRUE);
110     }
111     dw = w - moveresize_client->area.width;
112     dh = h - moveresize_client->area.height;
113
114     switch (lockcorner) {
115     case OB_CORNER_TOPLEFT:
116         break;
117     case OB_CORNER_TOPRIGHT:
118         *x -= dw;
119         break;
120     case OB_CORNER_BOTTOMLEFT:
121         *y -= dh;
122         break;
123     case OB_CORNER_BOTTOMRIGHT:
124         *x -= dw;
125         *y -= dh;
126         break;
127     }
128
129     frame_frame_gravity(moveresize_client->frame, x, y, w, h);
130 }
131
132 static void popup_coords(ObClient *c, const gchar *format, gint a, gint b)
133 {
134     gchar *text;
135
136     text = g_strdup_printf(format, a, b);
137     if (config_resize_popup_pos == 1) /* == "Top" */
138         popup_position(popup, SouthGravity,
139                        c->frame->area.x
140                      + c->frame->area.width/2,
141                        c->frame->area.y - ob_rr_theme->fbwidth);
142     else /* == "Center" */
143         popup_position(popup, CenterGravity,
144                        c->frame->area.x + c->frame->size.left +
145                        c->area.width / 2,
146                        c->frame->area.y + c->frame->size.top +
147                        c->area.height / 2);
148     popup_show(popup, text);
149     g_free(text);
150 }
151
152 void moveresize_start(ObClient *c, gint x, gint y, guint b, guint32 cnr)
153 {
154     ObCursor cur;
155
156     moving = (cnr == prop_atoms.net_wm_moveresize_move ||
157               cnr == prop_atoms.net_wm_moveresize_move_keyboard);
158
159     if (moveresize_in_progress || !c->frame->visible ||
160         !(moving ?
161           (c->functions & OB_CLIENT_FUNC_MOVE) :
162           (c->functions & OB_CLIENT_FUNC_RESIZE)))
163         return;
164
165     if (cnr == prop_atoms.net_wm_moveresize_size_topleft)
166         cur = OB_CURSOR_NORTHWEST;
167     else if (cnr == prop_atoms.net_wm_moveresize_size_top)
168         cur = OB_CURSOR_NORTH;
169     else if (cnr == prop_atoms.net_wm_moveresize_size_topright)
170         cur = OB_CURSOR_NORTHEAST;
171     else if (cnr == prop_atoms.net_wm_moveresize_size_right)
172         cur = OB_CURSOR_EAST;
173     else if (cnr == prop_atoms.net_wm_moveresize_size_bottomright)
174         cur = OB_CURSOR_SOUTHEAST;
175     else if (cnr == prop_atoms.net_wm_moveresize_size_bottom)
176         cur = OB_CURSOR_SOUTH;
177     else if (cnr == prop_atoms.net_wm_moveresize_size_bottomleft)
178         cur = OB_CURSOR_SOUTHWEST;
179     else if (cnr == prop_atoms.net_wm_moveresize_size_left)
180         cur = OB_CURSOR_WEST;
181     else if (cnr == prop_atoms.net_wm_moveresize_size_keyboard)
182         cur = OB_CURSOR_SOUTHEAST;
183     else if (cnr == prop_atoms.net_wm_moveresize_move)
184         cur = OB_CURSOR_MOVE;
185     else if (cnr == prop_atoms.net_wm_moveresize_move_keyboard)
186         cur = OB_CURSOR_MOVE;
187     else
188         g_assert_not_reached();
189
190     /* keep the pointer bounded to the screen for move/resize */
191     if (!grab_pointer(FALSE, TRUE, cur))
192         return;
193     if (!grab_keyboard()) {
194         ungrab_pointer();
195         return;
196     }
197
198     frame_end_iconify_animation(c->frame);
199
200     moveresize_client = c;
201     start_cx = c->area.x;
202     start_cy = c->area.y;
203     /* these adjustments for the size_inc make resizing a terminal more
204        friendly. you essentially start the resize in the middle of the
205        increment instead of at 0, so you have to move half an increment
206        either way instead of a full increment one and 1 px the other. and this
207        is one large mother fucking comment. */
208     start_cw = c->area.width + c->size_inc.width / 2;
209     start_ch = c->area.height + c->size_inc.height / 2;
210     start_x = x;
211     start_y = y;
212     corner = cnr;
213     button = b;
214
215     /*
216       have to change start_cx and start_cy if going to do this..
217     if (corner == prop_atoms.net_wm_moveresize_move_keyboard ||
218         corner == prop_atoms.net_wm_moveresize_size_keyboard)
219         XWarpPointer(ob_display, None, c->window, 0, 0, 0, 0,
220                      c->area.width / 2, c->area.height / 2);
221     */
222
223     if (moving) {
224         cur_x = start_cx;
225         cur_y = start_cy;
226     } else {
227         cur_x = start_cw;
228         cur_y = start_ch;
229     }
230
231     moveresize_in_progress = TRUE;
232
233 #ifdef SYNC
234     if (config_resize_redraw && !moving && extensions_shape &&
235         moveresize_client->sync_request && moveresize_client->sync_counter)
236     {
237         /* Initialize values for the resize syncing, and create an alarm for
238            the client's xsync counter */
239
240         XSyncValue val;
241         XSyncAlarmAttributes aa;
242
243         /* set the counter to an initial value */
244         XSyncIntToValue(&val, 0);
245         XSyncSetCounter(ob_display, moveresize_client->sync_counter, val);
246
247         /* this will be incremented when we tell the client what we're
248            looking for */
249         moveresize_client->sync_counter_value = 0;
250
251         /* the next sequence we're waiting for with the alarm */
252         XSyncIntToValue(&val, 1);
253
254         /* set an alarm on the counter */
255         aa.trigger.counter = moveresize_client->sync_counter;
256         aa.trigger.wait_value = val;
257         aa.trigger.value_type = XSyncAbsolute;
258         aa.trigger.test_type = XSyncPositiveTransition;
259         aa.events = True;
260         XSyncIntToValue(&aa.delta, 1);
261         moveresize_alarm = XSyncCreateAlarm(ob_display,
262                                             XSyncCACounter |
263                                             XSyncCAValue |
264                                             XSyncCAValueType |
265                                             XSyncCATestType |
266                                             XSyncCADelta |
267                                             XSyncCAEvents,
268                                             &aa);
269
270         waiting_for_sync = FALSE;
271     }
272 #endif
273 }
274
275 void moveresize_end(gboolean cancel)
276 {
277     gint x, y;
278
279     ungrab_keyboard();
280     ungrab_pointer();
281
282     popup_hide(popup);
283
284     if (moving) {
285         client_move(moveresize_client,
286                     (cancel ? start_cx : cur_x),
287                     (cancel ? start_cy : cur_y));
288     } else {
289 #ifdef SYNC
290         /* turn off the alarm */
291         if (moveresize_alarm != None) {
292             XSyncDestroyAlarm(ob_display, moveresize_alarm);
293             moveresize_alarm = None;
294         }
295 #endif
296
297         get_resize_position(&x, &y, cancel);
298         client_configure_full(moveresize_client, x, y,
299                               (cancel ? start_cw : cur_x),
300                               (cancel ? start_ch : cur_y), TRUE, TRUE);
301     }
302
303     moveresize_in_progress = FALSE;
304     moveresize_client = NULL;
305 }
306
307 static void do_move(gboolean keyboard)
308 {
309     gint resist;
310
311     if (keyboard) resist = KEY_DIST - 1; /* resist for one key press */
312     else resist = config_resist_win;
313     resist_move_windows(moveresize_client, resist, &cur_x, &cur_y);
314     if (!keyboard) resist = config_resist_edge;
315     resist_move_monitors(moveresize_client, resist, &cur_x, &cur_y);
316
317     client_configure_full(moveresize_client, cur_x, cur_y,
318                           moveresize_client->area.width,
319                           moveresize_client->area.height, TRUE, FALSE);
320     if (config_resize_popup_show == 2) /* == "Always" */
321         popup_coords(moveresize_client, "%d x %d",
322                      moveresize_client->frame->area.x,
323                      moveresize_client->frame->area.y);
324 }
325
326 static void do_resize()
327 {
328 #ifdef SYNC
329     if (config_resize_redraw && extensions_sync &&
330         moveresize_client->sync_request && moveresize_client->sync_counter)
331     {
332         XEvent ce;
333         XSyncValue val;
334         gint x, y, w, h, lw, lh;
335
336         /* are we already waiting for the sync counter to catch up? */
337         if (waiting_for_sync)
338             return;
339
340         /* see if it is actually going to resize */
341         x = 0;
342         y = 0;
343         w = cur_x;
344         h = cur_y;
345         client_try_configure(moveresize_client, &x, &y, &w, &h,
346                              &lw, &lh, TRUE);
347         if (w == moveresize_client->area.width &&
348             h == moveresize_client->area.height)
349         {
350             return;
351         }
352
353         /* increment the value we're waiting for */
354         ++moveresize_client->sync_counter_value;
355         XSyncIntToValue(&val, moveresize_client->sync_counter_value);
356
357         /* tell the client what we're waiting for */
358         ce.xclient.type = ClientMessage;
359         ce.xclient.message_type = prop_atoms.wm_protocols;
360         ce.xclient.display = ob_display;
361         ce.xclient.window = moveresize_client->window;
362         ce.xclient.format = 32;
363         ce.xclient.data.l[0] = prop_atoms.net_wm_sync_request;
364         ce.xclient.data.l[1] = event_curtime;
365         ce.xclient.data.l[2] = XSyncValueLow32(val);
366         ce.xclient.data.l[3] = XSyncValueHigh32(val);
367         ce.xclient.data.l[4] = 0l;
368         XSendEvent(ob_display, moveresize_client->window, FALSE,
369                    NoEventMask, &ce);
370
371         waiting_for_sync = TRUE;
372     }
373 #endif
374
375     {
376         gint x, y;
377         get_resize_position(&x, &y, FALSE);
378         client_configure_full(moveresize_client,
379                               x, y, cur_x, cur_y, TRUE, FALSE);
380     }
381
382     /* this would be better with a fixed width font ... XXX can do it better
383        if there are 2 text boxes */
384     if (config_resize_popup_show == 2 || /* == "Always" */
385             (config_resize_popup_show == 1 && /* == "Nonpixel" */
386              moveresize_client->size_inc.width > 1 &&
387              moveresize_client->size_inc.height > 1))
388         popup_coords(moveresize_client, "%d x %d",
389                      moveresize_client->logical_size.width,
390                      moveresize_client->logical_size.height);
391 }
392
393 static void calc_resize(gboolean keyboard)
394 {
395     gint resist;
396
397     /* resist_size_* needs the frame size */
398     cur_x += moveresize_client->frame->size.left +
399         moveresize_client->frame->size.right;
400     cur_y += moveresize_client->frame->size.top +
401         moveresize_client->frame->size.bottom;
402
403     if (keyboard) resist = KEY_DIST - 1; /* resist for one key press */
404     else resist = config_resist_win;
405     resist_size_windows(moveresize_client, resist, &cur_x, &cur_y, lockcorner);
406     if (!keyboard) resist = config_resist_edge;
407     resist_size_monitors(moveresize_client, resist, &cur_x, &cur_y,lockcorner);
408
409     cur_x -= moveresize_client->frame->size.left +
410         moveresize_client->frame->size.right;
411     cur_y -= moveresize_client->frame->size.top +
412         moveresize_client->frame->size.bottom;
413 }
414
415 gboolean moveresize_event(XEvent *e)
416 {
417     gboolean used = FALSE;
418
419     g_assert(moveresize_in_progress);
420
421     if (e->type == ButtonPress) {
422         if (!button) {
423             start_x = e->xbutton.x_root;
424             start_y = e->xbutton.y_root;
425             button = e->xbutton.button; /* this will end it now */
426         }
427         used = e->xbutton.button == button;
428     } else if (e->type == ButtonRelease) {
429         if (!button || e->xbutton.button == button) {
430             moveresize_end(FALSE);
431             used = TRUE;
432         }
433     } else if (e->type == MotionNotify) {
434         if (moving) {
435             cur_x = start_cx + e->xmotion.x_root - start_x;
436             cur_y = start_cy + e->xmotion.y_root - start_y;
437             do_move(FALSE);
438         } else {
439             if (corner == prop_atoms.net_wm_moveresize_size_topleft) {
440                 cur_x = start_cw - (e->xmotion.x_root - start_x);
441                 cur_y = start_ch - (e->xmotion.y_root - start_y);
442                 lockcorner = OB_CORNER_BOTTOMRIGHT;
443             } else if (corner == prop_atoms.net_wm_moveresize_size_top) {
444                 cur_x = start_cw;
445                 cur_y = start_ch - (e->xmotion.y_root - start_y);
446                 lockcorner = OB_CORNER_BOTTOMRIGHT;
447             } else if (corner == prop_atoms.net_wm_moveresize_size_topright) {
448                 cur_x = start_cw + (e->xmotion.x_root - start_x);
449                 cur_y = start_ch - (e->xmotion.y_root - start_y);
450                 lockcorner = OB_CORNER_BOTTOMLEFT;
451             } else if (corner == prop_atoms.net_wm_moveresize_size_right) { 
452                 cur_x = start_cw + (e->xmotion.x_root - start_x);
453                 cur_y = start_ch;
454                 lockcorner = OB_CORNER_BOTTOMLEFT;
455             } else if (corner ==
456                        prop_atoms.net_wm_moveresize_size_bottomright) {
457                 cur_x = start_cw + (e->xmotion.x_root - start_x);
458                 cur_y = start_ch + (e->xmotion.y_root - start_y);
459                 lockcorner = OB_CORNER_TOPLEFT;
460             } else if (corner == prop_atoms.net_wm_moveresize_size_bottom) {
461                 cur_x = start_cw;
462                 cur_y = start_ch + (e->xmotion.y_root - start_y);
463                 lockcorner = OB_CORNER_TOPLEFT;
464             } else if (corner ==
465                        prop_atoms.net_wm_moveresize_size_bottomleft) {
466                 cur_x = start_cw - (e->xmotion.x_root - start_x);
467                 cur_y = start_ch + (e->xmotion.y_root - start_y);
468                 lockcorner = OB_CORNER_TOPRIGHT;
469             } else if (corner == prop_atoms.net_wm_moveresize_size_left) {
470                 cur_x = start_cw - (e->xmotion.x_root - start_x);
471                 cur_y = start_ch;
472                 lockcorner = OB_CORNER_TOPRIGHT;
473             } else if (corner == prop_atoms.net_wm_moveresize_size_keyboard) {
474                 cur_x = start_cw + (e->xmotion.x_root - start_x);
475                 cur_y = start_ch + (e->xmotion.y_root - start_y);
476                 lockcorner = OB_CORNER_TOPLEFT;
477             } else
478                 g_assert_not_reached();
479
480             calc_resize(FALSE);
481             do_resize();
482         }
483         used = TRUE;
484     } else if (e->type == KeyPress) {
485         if (e->xkey.keycode == ob_keycode(OB_KEY_ESCAPE)) {
486             moveresize_end(TRUE);
487             used = TRUE;
488         } else if (e->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
489             moveresize_end(FALSE);
490             used = TRUE;
491         } else if (e->xkey.keycode == ob_keycode(OB_KEY_RIGHT) ||
492                    e->xkey.keycode == ob_keycode(OB_KEY_LEFT) ||
493                    e->xkey.keycode == ob_keycode(OB_KEY_DOWN) ||
494                    e->xkey.keycode == ob_keycode(OB_KEY_UP))
495         {
496             if (corner == prop_atoms.net_wm_moveresize_size_keyboard) {
497                 gint dx = 0, dy = 0, ox = cur_x, oy = cur_y;
498
499                 if (e->xkey.keycode == ob_keycode(OB_KEY_RIGHT))
500                     dx = MAX(KEY_DIST, moveresize_client->size_inc.width);
501                 else if (e->xkey.keycode == ob_keycode(OB_KEY_LEFT))
502                     dx = -MAX(KEY_DIST, moveresize_client->size_inc.width);
503                 else if (e->xkey.keycode == ob_keycode(OB_KEY_DOWN))
504                     dy = MAX(KEY_DIST, moveresize_client->size_inc.height);
505                 else /* if (e->xkey.keycode == ob_keycode(OB_KEY_UP)) */
506                     dy = -MAX(KEY_DIST, moveresize_client->size_inc.height);
507
508                 cur_x += dx;
509                 cur_y += dy;
510                 XWarpPointer(ob_display, None, None, 0, 0, 0, 0, dx, dy);
511                 /* steal the motion events this causes */
512                 XSync(ob_display, FALSE);
513                 {
514                     XEvent ce;
515                     while (XCheckTypedEvent(ob_display, MotionNotify, &ce));
516                 }
517
518                 calc_resize(TRUE);
519                 do_resize();
520
521                 /* because the cursor moves even though the window does
522                    not nessesarily (resistance), this adjusts where the curor
523                    thinks it started so that it keeps up with where the window
524                    actually is */
525                 start_x += dx - (cur_x - ox);
526                 start_y += dy - (cur_y - oy);
527
528                 used = TRUE;
529             } else if (corner == prop_atoms.net_wm_moveresize_move_keyboard) {
530                 gint dx = 0, dy = 0, ox = cur_x, oy = cur_y;
531                 gint opx, px, opy, py;
532
533                 if (e->xkey.keycode == ob_keycode(OB_KEY_RIGHT))
534                     dx = KEY_DIST;
535                 else if (e->xkey.keycode == ob_keycode(OB_KEY_LEFT))
536                     dx = -KEY_DIST;
537                 else if (e->xkey.keycode == ob_keycode(OB_KEY_DOWN))
538                     dy = KEY_DIST;
539                 else /* if (e->xkey.keycode == ob_keycode(OB_KEY_UP)) */
540                     dy = -KEY_DIST;
541
542                 cur_x += dx;
543                 cur_y += dy;
544                 screen_pointer_pos(&opx, &opy);
545                 XWarpPointer(ob_display, None, None, 0, 0, 0, 0, dx, dy);
546                 /* steal the motion events this causes */
547                 XSync(ob_display, FALSE);
548                 {
549                     XEvent ce;
550                     while (XCheckTypedEvent(ob_display, MotionNotify, &ce));
551                 }
552                 screen_pointer_pos(&px, &py);
553
554                 do_move(TRUE);
555
556                 /* because the cursor moves even though the window does
557                    not nessesarily (resistance), this adjusts where the curor
558                    thinks it started so that it keeps up with where the window
559                    actually is */
560                 start_x += (px - opx) - (cur_x - ox);
561                 start_y += (py - opy) - (cur_y - oy);
562
563                 used = TRUE;
564             }
565         }
566     }
567 #ifdef SYNC
568     else if (e->type == extensions_sync_event_basep + XSyncAlarmNotify)
569     {
570         waiting_for_sync = FALSE; /* we got our sync... */
571         do_resize(); /* ...so try resize if there is more change pending */
572         used = TRUE;
573     }
574 #endif
575     return used;
576 }