woa.. let you do mouse actions while in an interactive keyboard action, and let you...
[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         popup_coords(moveresize_client, "%d x %d",
375                      moveresize_client->logical_size.width,
376                      moveresize_client->logical_size.height);
377 }
378
379 static void calc_resize(gboolean resist)
380 {
381     /* resist_size_* needs the frame size */
382     cur_x += moveresize_client->frame->size.left +
383         moveresize_client->frame->size.right;
384     cur_y += moveresize_client->frame->size.top +
385         moveresize_client->frame->size.bottom;
386
387     if (resist) {
388         resist_size_windows(moveresize_client, &cur_x, &cur_y, lockcorner);
389         resist_size_monitors(moveresize_client, &cur_x, &cur_y, lockcorner);
390     }
391
392     cur_x -= moveresize_client->frame->size.left +
393         moveresize_client->frame->size.right;
394     cur_y -= moveresize_client->frame->size.top +
395         moveresize_client->frame->size.bottom;
396 }
397
398 gboolean moveresize_event(XEvent *e)
399 {
400     gboolean used = FALSE;
401
402     g_assert(moveresize_in_progress);
403
404     if (e->type == ButtonPress) {
405         if (!button) {
406             start_x = e->xbutton.x_root;
407             start_y = e->xbutton.y_root;
408             button = e->xbutton.button; /* this will end it now */
409         }
410         used = TRUE;
411     } else if (e->type == ButtonRelease) {
412         if (!button || e->xbutton.button == button) {
413             moveresize_end(FALSE);
414         }
415         used = TRUE;
416     } else if (e->type == MotionNotify) {
417         if (moving) {
418             cur_x = start_cx + e->xmotion.x_root - start_x;
419             cur_y = start_cy + e->xmotion.y_root - start_y;
420             do_move(TRUE);
421         } else {
422             if (corner == prop_atoms.net_wm_moveresize_size_topleft) {
423                 cur_x = start_cw - (e->xmotion.x_root - start_x);
424                 cur_y = start_ch - (e->xmotion.y_root - start_y);
425                 lockcorner = OB_CORNER_BOTTOMRIGHT;
426             } else if (corner == prop_atoms.net_wm_moveresize_size_top) {
427                 cur_x = start_cw;
428                 cur_y = start_ch - (e->xmotion.y_root - start_y);
429                 lockcorner = OB_CORNER_BOTTOMRIGHT;
430             } else if (corner == prop_atoms.net_wm_moveresize_size_topright) {
431                 cur_x = start_cw + (e->xmotion.x_root - start_x);
432                 cur_y = start_ch - (e->xmotion.y_root - start_y);
433                 lockcorner = OB_CORNER_BOTTOMLEFT;
434             } else if (corner == prop_atoms.net_wm_moveresize_size_right) { 
435                 cur_x = start_cw + (e->xmotion.x_root - start_x);
436                 cur_y = start_ch;
437                 lockcorner = OB_CORNER_BOTTOMLEFT;
438             } else if (corner ==
439                        prop_atoms.net_wm_moveresize_size_bottomright) {
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_TOPLEFT;
443             } else if (corner == prop_atoms.net_wm_moveresize_size_bottom) {
444                 cur_x = start_cw;
445                 cur_y = start_ch + (e->xmotion.y_root - start_y);
446                 lockcorner = OB_CORNER_TOPLEFT;
447             } else if (corner ==
448                        prop_atoms.net_wm_moveresize_size_bottomleft) {
449                 cur_x = start_cw - (e->xmotion.x_root - start_x);
450                 cur_y = start_ch + (e->xmotion.y_root - start_y);
451                 lockcorner = OB_CORNER_TOPRIGHT;
452             } else if (corner == prop_atoms.net_wm_moveresize_size_left) {
453                 cur_x = start_cw - (e->xmotion.x_root - start_x);
454                 cur_y = start_ch;
455                 lockcorner = OB_CORNER_TOPRIGHT;
456             } else if (corner == prop_atoms.net_wm_moveresize_size_keyboard) {
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
461                 g_assert_not_reached();
462
463             calc_resize(TRUE);
464             do_resize();
465         }
466         used = TRUE;
467     } else if (e->type == KeyPress) {
468         if (e->xkey.keycode == ob_keycode(OB_KEY_ESCAPE)) {
469             moveresize_end(TRUE);
470             used = TRUE;
471         } else if (e->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
472             moveresize_end(FALSE);
473             used = TRUE;
474         } else if (e->xkey.keycode == ob_keycode(OB_KEY_RIGHT) ||
475                    e->xkey.keycode == ob_keycode(OB_KEY_LEFT) ||
476                    e->xkey.keycode == ob_keycode(OB_KEY_DOWN) ||
477                    e->xkey.keycode == ob_keycode(OB_KEY_UP))
478         {
479             if (corner == prop_atoms.net_wm_moveresize_size_keyboard) {
480                 gint dx = 0, dy = 0, ox = cur_x, oy = cur_y;
481
482                 if (e->xkey.keycode == ob_keycode(OB_KEY_RIGHT))
483                     dx = MAX(4, moveresize_client->size_inc.width);
484                 else if (e->xkey.keycode == ob_keycode(OB_KEY_LEFT))
485                     dx = -MAX(4, moveresize_client->size_inc.width);
486                 else if (e->xkey.keycode == ob_keycode(OB_KEY_DOWN))
487                     dy = MAX(4, moveresize_client->size_inc.height);
488                 else /* if (e->xkey.keycode == ob_keycode(OB_KEY_UP)) */
489                     dy = -MAX(4, moveresize_client->size_inc.height);
490
491                 cur_x += dx;
492                 cur_y += dy;
493                 XWarpPointer(ob_display, None, None, 0, 0, 0, 0, dx, dy);
494                 /* steal the motion events this causes */
495                 XSync(ob_display, FALSE);
496                 {
497                     XEvent ce;
498                     while (XCheckTypedEvent(ob_display, MotionNotify, &ce));
499                 }
500
501                 do_resize(FALSE);
502
503                 /* because the cursor moves even though the window does
504                    not nessesarily (resistance), this adjusts where the curor
505                    thinks it started so that it keeps up with where the window
506                    actually is */
507                 start_x += dx - (cur_x - ox);
508                 start_y += dy - (cur_y - oy);
509
510                 used = TRUE;
511             } else if (corner == prop_atoms.net_wm_moveresize_move_keyboard) {
512                 gint dx = 0, dy = 0, ox = cur_x, oy = cur_y;
513                 gint opx, px, opy, py;
514
515                 if (e->xkey.keycode == ob_keycode(OB_KEY_RIGHT))
516                     dx = 4;
517                 else if (e->xkey.keycode == ob_keycode(OB_KEY_LEFT))
518                     dx = -4;
519                 else if (e->xkey.keycode == ob_keycode(OB_KEY_DOWN))
520                     dy = 4;
521                 else /* if (e->xkey.keycode == ob_keycode(OB_KEY_UP)) */
522                     dy = -4;
523
524                 cur_x += dx;
525                 cur_y += dy;
526                 screen_pointer_pos(&opx, &opy);
527                 XWarpPointer(ob_display, None, None, 0, 0, 0, 0, dx, dy);
528                 /* steal the motion events this causes */
529                 XSync(ob_display, FALSE);
530                 {
531                     XEvent ce;
532                     while (XCheckTypedEvent(ob_display, MotionNotify, &ce));
533                 }
534                 screen_pointer_pos(&px, &py);
535
536                 do_move(FALSE);
537
538                 /* because the cursor moves even though the window does
539                    not nessesarily (resistance), this adjusts where the curor
540                    thinks it started so that it keeps up with where the window
541                    actually is */
542                 start_x += (px - opx) - (cur_x - ox);
543                 start_y += (py - opy) - (cur_y - oy);
544
545                 used = TRUE;
546             }
547         }
548     }
549 #ifdef SYNC
550     else if (e->type == extensions_sync_event_basep + XSyncAlarmNotify)
551     {
552         waiting_for_sync = FALSE; /* we got our sync... */
553         do_resize(); /* ...so try resize if there is more change pending */
554         used = TRUE;
555     }
556 #endif
557     return used;
558 }