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