don't set the global moving state unless the move/resize is accepted
[dana/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     gboolean mv = (cnr == prop_atoms.net_wm_moveresize_move ||
156                    cnr == prop_atoms.net_wm_moveresize_move_keyboard);
157
158     if (moveresize_in_progress || !c->frame->visible ||
159         !(mv ?
160           (c->functions & OB_CLIENT_FUNC_MOVE) :
161           (c->functions & OB_CLIENT_FUNC_RESIZE)))
162         return;
163
164     if (cnr == prop_atoms.net_wm_moveresize_size_topleft)
165         cur = OB_CURSOR_NORTHWEST;
166     else if (cnr == prop_atoms.net_wm_moveresize_size_top)
167         cur = OB_CURSOR_NORTH;
168     else if (cnr == prop_atoms.net_wm_moveresize_size_topright)
169         cur = OB_CURSOR_NORTHEAST;
170     else if (cnr == prop_atoms.net_wm_moveresize_size_right)
171         cur = OB_CURSOR_EAST;
172     else if (cnr == prop_atoms.net_wm_moveresize_size_bottomright)
173         cur = OB_CURSOR_SOUTHEAST;
174     else if (cnr == prop_atoms.net_wm_moveresize_size_bottom)
175         cur = OB_CURSOR_SOUTH;
176     else if (cnr == prop_atoms.net_wm_moveresize_size_bottomleft)
177         cur = OB_CURSOR_SOUTHWEST;
178     else if (cnr == prop_atoms.net_wm_moveresize_size_left)
179         cur = OB_CURSOR_WEST;
180     else if (cnr == prop_atoms.net_wm_moveresize_size_keyboard)
181         cur = OB_CURSOR_SOUTHEAST;
182     else if (cnr == prop_atoms.net_wm_moveresize_move)
183         cur = OB_CURSOR_MOVE;
184     else if (cnr == prop_atoms.net_wm_moveresize_move_keyboard)
185         cur = OB_CURSOR_MOVE;
186     else
187         g_assert_not_reached();
188
189     /* keep the pointer bounded to the screen for move/resize */
190     if (!grab_pointer(FALSE, TRUE, cur))
191         return;
192     if (!grab_keyboard()) {
193         ungrab_pointer();
194         return;
195     }
196
197     frame_end_iconify_animation(c->frame);
198
199     moving = mv;
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(moveresize_client, x, y,
299                          (cancel ? start_cw : cur_x),
300                          (cancel ? start_ch : cur_y),
301                          moveresize_client->border_width, TRUE, TRUE);
302     }
303
304     moveresize_in_progress = FALSE;
305     moveresize_client = NULL;
306 }
307
308 static void do_move(gboolean keyboard)
309 {
310     gint resist;
311
312     if (keyboard) resist = KEY_DIST - 1; /* resist for one key press */
313     else resist = config_resist_win;
314     resist_move_windows(moveresize_client, resist, &cur_x, &cur_y);
315     if (!keyboard) resist = config_resist_edge;
316     resist_move_monitors(moveresize_client, resist, &cur_x, &cur_y);
317
318     client_configure(moveresize_client, cur_x, cur_y,
319                      moveresize_client->area.width,
320                      moveresize_client->area.height,
321                      moveresize_client->border_width, TRUE, FALSE);
322     if (config_resize_popup_show == 2) /* == "Always" */
323         popup_coords(moveresize_client, "%d x %d",
324                      moveresize_client->frame->area.x,
325                      moveresize_client->frame->area.y);
326 }
327
328 static void do_resize()
329 {
330     gint x, y, w, h, lw, lh;
331
332     /* see if it is actually going to resize */
333     x = 0;
334     y = 0;
335     w = cur_x;
336     h = cur_y;
337     client_try_configure(moveresize_client, &x, &y, &w, &h,
338                          &lw, &lh, TRUE);
339     if (w == moveresize_client->area.width &&
340         h == moveresize_client->area.height)
341     {
342         return;
343     }
344
345 #ifdef SYNC
346     if (config_resize_redraw && extensions_sync &&
347         moveresize_client->sync_request && moveresize_client->sync_counter)
348     {
349         XEvent ce;
350         XSyncValue val;
351
352         /* are we already waiting for the sync counter to catch up? */
353         if (waiting_for_sync)
354             return;
355
356         /* increment the value we're waiting for */
357         ++moveresize_client->sync_counter_value;
358         XSyncIntToValue(&val, moveresize_client->sync_counter_value);
359
360         /* tell the client what we're waiting for */
361         ce.xclient.type = ClientMessage;
362         ce.xclient.message_type = prop_atoms.wm_protocols;
363         ce.xclient.display = ob_display;
364         ce.xclient.window = moveresize_client->window;
365         ce.xclient.format = 32;
366         ce.xclient.data.l[0] = prop_atoms.net_wm_sync_request;
367         ce.xclient.data.l[1] = event_curtime;
368         ce.xclient.data.l[2] = XSyncValueLow32(val);
369         ce.xclient.data.l[3] = XSyncValueHigh32(val);
370         ce.xclient.data.l[4] = 0l;
371         XSendEvent(ob_display, moveresize_client->window, FALSE,
372                    NoEventMask, &ce);
373
374         waiting_for_sync = TRUE;
375     }
376 #endif
377
378     get_resize_position(&x, &y, FALSE);
379     client_configure(moveresize_client, x, y, cur_x, cur_y,
380                      moveresize_client->border_width, TRUE, FALSE);
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 }