only do moveresize when grab is successful
[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 (!grab_pointer(TRUE, FALSE, cur))
166         return;
167     if (!grab_keyboard(TRUE)) {
168         grab_pointer(FALSE, FALSE, OB_CURSOR_NONE);
169         return;
170     }
171
172     frame_end_iconify_animation(c->frame);
173
174     moveresize_client = c;
175     start_cx = c->area.x;
176     start_cy = c->area.y;
177     /* these adjustments for the size_inc make resizing a terminal more
178        friendly. you essentially start the resize in the middle of the
179        increment instead of at 0, so you have to move half an increment
180        either way instead of a full increment one and 1 px the other. and this
181        is one large mother fucking comment. */
182     start_cw = c->area.width + c->size_inc.width / 2;
183     start_ch = c->area.height + c->size_inc.height / 2;
184     start_x = x;
185     start_y = y;
186     corner = cnr;
187     button = b;
188
189     /*
190       have to change start_cx and start_cy if going to do this..
191     if (corner == prop_atoms.net_wm_moveresize_move_keyboard ||
192         corner == prop_atoms.net_wm_moveresize_size_keyboard)
193         XWarpPointer(ob_display, None, c->window, 0, 0, 0, 0,
194                      c->area.width / 2, c->area.height / 2);
195     */
196
197     if (moving) {
198         cur_x = start_cx;
199         cur_y = start_cy;
200     } else {
201         cur_x = start_cw;
202         cur_y = start_ch;
203     }
204
205     moveresize_in_progress = TRUE;
206
207     if (corner == prop_atoms.net_wm_moveresize_size_topleft)
208         cur = OB_CURSOR_NORTHWEST;
209     else if (corner == prop_atoms.net_wm_moveresize_size_top)
210         cur = OB_CURSOR_NORTH;
211     else if (corner == prop_atoms.net_wm_moveresize_size_topright)
212         cur = OB_CURSOR_NORTHEAST;
213     else if (corner == prop_atoms.net_wm_moveresize_size_right)
214         cur = OB_CURSOR_EAST;
215     else if (corner == prop_atoms.net_wm_moveresize_size_bottomright)
216         cur = OB_CURSOR_SOUTHEAST;
217     else if (corner == prop_atoms.net_wm_moveresize_size_bottom)
218         cur = OB_CURSOR_SOUTH;
219     else if (corner == prop_atoms.net_wm_moveresize_size_bottomleft)
220         cur = OB_CURSOR_SOUTHWEST;
221     else if (corner == prop_atoms.net_wm_moveresize_size_left)
222         cur = OB_CURSOR_WEST;
223     else if (corner == prop_atoms.net_wm_moveresize_size_keyboard)
224         cur = OB_CURSOR_SOUTHEAST;
225     else if (corner == prop_atoms.net_wm_moveresize_move)
226         cur = OB_CURSOR_MOVE;
227     else if (corner == prop_atoms.net_wm_moveresize_move_keyboard)
228         cur = OB_CURSOR_MOVE;
229     else
230         g_assert_not_reached();
231
232 #ifdef SYNC
233     if (config_resize_redraw && !moving && extensions_shape &&
234         moveresize_client->sync_request && moveresize_client->sync_counter)
235     {
236         /* Initialize values for the resize syncing, and create an alarm for
237            the client's xsync counter */
238
239         XSyncValue val;
240         XSyncAlarmAttributes aa;
241
242         /* set the counter to an initial value */
243         XSyncIntToValue(&val, 0);
244         XSyncSetCounter(ob_display, moveresize_client->sync_counter, val);
245
246         /* this will be incremented when we tell the client what we're
247            looking for */
248         moveresize_client->sync_counter_value = 0;
249
250         /* the next sequence we're waiting for with the alarm */
251         XSyncIntToValue(&val, 1);
252
253         /* set an alarm on the counter */
254         aa.trigger.counter = moveresize_client->sync_counter;
255         aa.trigger.wait_value = val;
256         aa.trigger.value_type = XSyncAbsolute;
257         aa.trigger.test_type = XSyncPositiveTransition;
258         aa.events = True;
259         XSyncIntToValue(&aa.delta, 1);
260         moveresize_alarm = XSyncCreateAlarm(ob_display,
261                                             XSyncCACounter |
262                                             XSyncCAValue |
263                                             XSyncCAValueType |
264                                             XSyncCATestType |
265                                             XSyncCADelta |
266                                             XSyncCAEvents,
267                                             &aa);
268
269         waiting_for_sync = FALSE;
270     }
271 #endif
272 }
273
274 void moveresize_end(gboolean cancel)
275 {
276     gint x, y;
277
278     grab_keyboard(FALSE);
279     grab_pointer(FALSE, FALSE, OB_CURSOR_NONE);
280
281     popup_hide(popup);
282
283     if (moving) {
284         client_move(moveresize_client,
285                     (cancel ? start_cx : cur_x),
286                     (cancel ? start_cy : cur_y));
287     } else {
288 #ifdef SYNC
289         /* turn off the alarm */
290         if (moveresize_alarm != None) {
291             XSyncDestroyAlarm(ob_display, moveresize_alarm);
292             moveresize_alarm = None;
293         }
294 #endif
295
296         get_resize_position(&x, &y, cancel);
297         client_configure(moveresize_client, x, y,
298                          (cancel ? start_cw : cur_x),
299                          (cancel ? start_ch : cur_y), TRUE, TRUE);
300     }
301
302     moveresize_in_progress = FALSE;
303     moveresize_client = NULL;
304 }
305
306 static void do_move(gboolean keyboard)
307 {
308     gint resist;
309
310     if (keyboard) resist = KEY_DIST - 1; /* resist for one key press */
311     else resist = config_resist_win;
312     resist_move_windows(moveresize_client, resist, &cur_x, &cur_y);
313     if (!keyboard) resist = config_resist_edge;
314     resist_move_monitors(moveresize_client, resist, &cur_x, &cur_y);
315
316     client_configure(moveresize_client, cur_x, cur_y,
317                      moveresize_client->area.width,
318                      moveresize_client->area.height, TRUE, FALSE);
319     if (config_resize_popup_show == 2) /* == "Always" */
320         popup_coords(moveresize_client, "%d x %d",
321                      moveresize_client->frame->area.x,
322                      moveresize_client->frame->area.y);
323 }
324
325 static void do_resize()
326 {
327 #ifdef SYNC
328     if (config_resize_redraw && extensions_sync &&
329         moveresize_client->sync_request && moveresize_client->sync_counter)
330     {
331         XEvent ce;
332         XSyncValue val;
333         gint x, y, w, h, lw, lh;
334
335         /* are we already waiting for the sync counter to catch up? */
336         if (waiting_for_sync)
337             return;
338
339         /* see if it is actually going to resize */
340         x = 0;
341         y = 0;
342         w = cur_x;
343         h = cur_y;
344         client_try_configure(moveresize_client, &x, &y, &w, &h,
345                              &lw, &lh, TRUE);
346         if (w == moveresize_client->area.width &&
347             h == moveresize_client->area.height)
348         {
349             return;
350         }
351
352         /* increment the value we're waiting for */
353         ++moveresize_client->sync_counter_value;
354         XSyncIntToValue(&val, moveresize_client->sync_counter_value);
355
356         /* tell the client what we're waiting for */
357         ce.xclient.type = ClientMessage;
358         ce.xclient.message_type = prop_atoms.wm_protocols;
359         ce.xclient.display = ob_display;
360         ce.xclient.window = moveresize_client->window;
361         ce.xclient.format = 32;
362         ce.xclient.data.l[0] = prop_atoms.net_wm_sync_request;
363         ce.xclient.data.l[1] = event_curtime;
364         ce.xclient.data.l[2] = XSyncValueLow32(val);
365         ce.xclient.data.l[3] = XSyncValueHigh32(val);
366         ce.xclient.data.l[4] = 0l;
367         XSendEvent(ob_display, moveresize_client->window, FALSE,
368                    NoEventMask, &ce);
369
370         waiting_for_sync = TRUE;
371     }
372 #endif
373
374     {
375         gint x, y;
376         get_resize_position(&x, &y, FALSE);
377         client_configure(moveresize_client, x, y, cur_x, cur_y, TRUE, FALSE);
378     }
379
380     /* this would be better with a fixed width font ... XXX can do it better
381        if there are 2 text boxes */
382     if (config_resize_popup_show == 2 || /* == "Always" */
383             (config_resize_popup_show == 1 && /* == "Nonpixel" */
384              moveresize_client->size_inc.width > 1 &&
385              moveresize_client->size_inc.height > 1))
386         popup_coords(moveresize_client, "%d x %d",
387                      moveresize_client->logical_size.width,
388                      moveresize_client->logical_size.height);
389 }
390
391 static void calc_resize(gboolean keyboard)
392 {
393     gint resist;
394
395     /* resist_size_* needs the frame size */
396     cur_x += moveresize_client->frame->size.left +
397         moveresize_client->frame->size.right;
398     cur_y += moveresize_client->frame->size.top +
399         moveresize_client->frame->size.bottom;
400
401     if (keyboard) resist = KEY_DIST - 1; /* resist for one key press */
402     else resist = config_resist_win;
403     resist_size_windows(moveresize_client, resist, &cur_x, &cur_y, lockcorner);
404     if (!keyboard) resist = config_resist_edge;
405     resist_size_monitors(moveresize_client, resist, &cur_x, &cur_y,lockcorner);
406
407     cur_x -= moveresize_client->frame->size.left +
408         moveresize_client->frame->size.right;
409     cur_y -= moveresize_client->frame->size.top +
410         moveresize_client->frame->size.bottom;
411 }
412
413 gboolean moveresize_event(XEvent *e)
414 {
415     gboolean used = FALSE;
416
417     g_assert(moveresize_in_progress);
418
419     if (e->type == ButtonPress) {
420         if (!button) {
421             start_x = e->xbutton.x_root;
422             start_y = e->xbutton.y_root;
423             button = e->xbutton.button; /* this will end it now */
424         }
425         used = e->xbutton.button == button;
426     } else if (e->type == ButtonRelease) {
427         if (!button || e->xbutton.button == button) {
428             moveresize_end(FALSE);
429             used = TRUE;
430         }
431     } else if (e->type == MotionNotify) {
432         if (moving) {
433             cur_x = start_cx + e->xmotion.x_root - start_x;
434             cur_y = start_cy + e->xmotion.y_root - start_y;
435             do_move(FALSE);
436         } else {
437             if (corner == prop_atoms.net_wm_moveresize_size_topleft) {
438                 cur_x = start_cw - (e->xmotion.x_root - start_x);
439                 cur_y = start_ch - (e->xmotion.y_root - start_y);
440                 lockcorner = OB_CORNER_BOTTOMRIGHT;
441             } else if (corner == prop_atoms.net_wm_moveresize_size_top) {
442                 cur_x = start_cw;
443                 cur_y = start_ch - (e->xmotion.y_root - start_y);
444                 lockcorner = OB_CORNER_BOTTOMRIGHT;
445             } else if (corner == prop_atoms.net_wm_moveresize_size_topright) {
446                 cur_x = start_cw + (e->xmotion.x_root - start_x);
447                 cur_y = start_ch - (e->xmotion.y_root - start_y);
448                 lockcorner = OB_CORNER_BOTTOMLEFT;
449             } else if (corner == prop_atoms.net_wm_moveresize_size_right) { 
450                 cur_x = start_cw + (e->xmotion.x_root - start_x);
451                 cur_y = start_ch;
452                 lockcorner = OB_CORNER_BOTTOMLEFT;
453             } else if (corner ==
454                        prop_atoms.net_wm_moveresize_size_bottomright) {
455                 cur_x = start_cw + (e->xmotion.x_root - start_x);
456                 cur_y = start_ch + (e->xmotion.y_root - start_y);
457                 lockcorner = OB_CORNER_TOPLEFT;
458             } else if (corner == prop_atoms.net_wm_moveresize_size_bottom) {
459                 cur_x = start_cw;
460                 cur_y = start_ch + (e->xmotion.y_root - start_y);
461                 lockcorner = OB_CORNER_TOPLEFT;
462             } else if (corner ==
463                        prop_atoms.net_wm_moveresize_size_bottomleft) {
464                 cur_x = start_cw - (e->xmotion.x_root - start_x);
465                 cur_y = start_ch + (e->xmotion.y_root - start_y);
466                 lockcorner = OB_CORNER_TOPRIGHT;
467             } else if (corner == prop_atoms.net_wm_moveresize_size_left) {
468                 cur_x = start_cw - (e->xmotion.x_root - start_x);
469                 cur_y = start_ch;
470                 lockcorner = OB_CORNER_TOPRIGHT;
471             } else if (corner == prop_atoms.net_wm_moveresize_size_keyboard) {
472                 cur_x = start_cw + (e->xmotion.x_root - start_x);
473                 cur_y = start_ch + (e->xmotion.y_root - start_y);
474                 lockcorner = OB_CORNER_TOPLEFT;
475             } else
476                 g_assert_not_reached();
477
478             calc_resize(FALSE);
479             do_resize();
480         }
481         used = TRUE;
482     } else if (e->type == KeyPress) {
483         if (e->xkey.keycode == ob_keycode(OB_KEY_ESCAPE)) {
484             moveresize_end(TRUE);
485             used = TRUE;
486         } else if (e->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
487             moveresize_end(FALSE);
488             used = TRUE;
489         } else if (e->xkey.keycode == ob_keycode(OB_KEY_RIGHT) ||
490                    e->xkey.keycode == ob_keycode(OB_KEY_LEFT) ||
491                    e->xkey.keycode == ob_keycode(OB_KEY_DOWN) ||
492                    e->xkey.keycode == ob_keycode(OB_KEY_UP))
493         {
494             if (corner == prop_atoms.net_wm_moveresize_size_keyboard) {
495                 gint dx = 0, dy = 0, ox = cur_x, oy = cur_y;
496
497                 if (e->xkey.keycode == ob_keycode(OB_KEY_RIGHT))
498                     dx = MAX(KEY_DIST, moveresize_client->size_inc.width);
499                 else if (e->xkey.keycode == ob_keycode(OB_KEY_LEFT))
500                     dx = -MAX(KEY_DIST, moveresize_client->size_inc.width);
501                 else if (e->xkey.keycode == ob_keycode(OB_KEY_DOWN))
502                     dy = MAX(KEY_DIST, moveresize_client->size_inc.height);
503                 else /* if (e->xkey.keycode == ob_keycode(OB_KEY_UP)) */
504                     dy = -MAX(KEY_DIST, moveresize_client->size_inc.height);
505
506                 cur_x += dx;
507                 cur_y += dy;
508                 XWarpPointer(ob_display, None, None, 0, 0, 0, 0, dx, dy);
509                 /* steal the motion events this causes */
510                 XSync(ob_display, FALSE);
511                 {
512                     XEvent ce;
513                     while (XCheckTypedEvent(ob_display, MotionNotify, &ce));
514                 }
515
516                 calc_resize(TRUE);
517                 do_resize();
518
519                 /* because the cursor moves even though the window does
520                    not nessesarily (resistance), this adjusts where the curor
521                    thinks it started so that it keeps up with where the window
522                    actually is */
523                 start_x += dx - (cur_x - ox);
524                 start_y += dy - (cur_y - oy);
525
526                 used = TRUE;
527             } else if (corner == prop_atoms.net_wm_moveresize_move_keyboard) {
528                 gint dx = 0, dy = 0, ox = cur_x, oy = cur_y;
529                 gint opx, px, opy, py;
530
531                 if (e->xkey.keycode == ob_keycode(OB_KEY_RIGHT))
532                     dx = KEY_DIST;
533                 else if (e->xkey.keycode == ob_keycode(OB_KEY_LEFT))
534                     dx = -KEY_DIST;
535                 else if (e->xkey.keycode == ob_keycode(OB_KEY_DOWN))
536                     dy = KEY_DIST;
537                 else /* if (e->xkey.keycode == ob_keycode(OB_KEY_UP)) */
538                     dy = -KEY_DIST;
539
540                 cur_x += dx;
541                 cur_y += dy;
542                 screen_pointer_pos(&opx, &opy);
543                 XWarpPointer(ob_display, None, None, 0, 0, 0, 0, dx, dy);
544                 /* steal the motion events this causes */
545                 XSync(ob_display, FALSE);
546                 {
547                     XEvent ce;
548                     while (XCheckTypedEvent(ob_display, MotionNotify, &ce));
549                 }
550                 screen_pointer_pos(&px, &py);
551
552                 do_move(TRUE);
553
554                 /* because the cursor moves even though the window does
555                    not nessesarily (resistance), this adjusts where the curor
556                    thinks it started so that it keeps up with where the window
557                    actually is */
558                 start_x += (px - opx) - (cur_x - ox);
559                 start_y += (py - opy) - (cur_y - oy);
560
561                 used = TRUE;
562             }
563         }
564     }
565 #ifdef SYNC
566     else if (e->type == extensions_sync_event_basep + XSyncAlarmNotify)
567     {
568         waiting_for_sync = FALSE; /* we got our sync... */
569         do_resize(); /* ...so try resize if there is more change pending */
570         used = TRUE;
571     }
572 #endif
573     return used;
574 }