Merge branch 'work' into wip/mikabox
[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 "client.h"
24 #include "focus.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 "obrender/render.h"
34 #include "obrender/theme.h"
35 #include "obt/display.h"
36 #include "obt/xqueue.h"
37 #include "obt/prop.h"
38 #include "obt/keyboard.h"
39
40 #include <X11/Xlib.h>
41 #include <glib.h>
42
43 /* how far windows move and resize with the keyboard arrows */
44 #define KEY_DIST 8
45 #define SYNC_TIMEOUTS 4
46
47 gboolean moveresize_in_progress = FALSE;
48 ObClient *moveresize_client = NULL;
49 #ifdef SYNC
50 XSyncAlarm moveresize_alarm = None;
51 #endif
52
53 static gboolean moving = FALSE; /* TRUE - moving, FALSE - resizing */
54
55 /* starting geometry for the window being moved/resized, so it can be
56    restored */
57 static gint start_x, start_y, start_cx, start_cy, start_cw, start_ch;
58 static gboolean was_max_horz, was_max_vert;
59 static Rect pre_max_area;
60 static gint cur_x, cur_y, cur_w, cur_h;
61 static guint button;
62 static guint32 corner;
63 static ObDirection edge_warp_dir = -1;
64 static gboolean edge_warp_odd = FALSE;
65 static guint edge_warp_timer = 0;
66 static ObDirection key_resize_edge = -1;
67 static guint waiting_for_sync;
68 #ifdef SYNC
69 static guint sync_timer = 0;
70 #endif
71
72 static ObPopup *popup = NULL;
73
74 static void do_move(gboolean keyboard, gint keydist);
75 static void do_resize(void);
76 static void do_edge_warp(gint x, gint y);
77 static void cancel_edge_warp();
78 #ifdef SYNC
79 static gboolean sync_timeout_func(gpointer data);
80 #endif
81
82 static void client_dest(ObClient *client, gpointer data)
83 {
84     if (moveresize_client == client)
85         moveresize_end(TRUE);
86     if (popup && client == popup->client)
87         popup->client = NULL;
88 }
89
90 void moveresize_startup(gboolean reconfig)
91 {
92     popup = popup_new();
93     popup_set_text_align(popup, RR_JUSTIFY_CENTER);
94
95     if (!reconfig)
96         client_add_destroy_notify(client_dest, NULL);
97 }
98
99 void moveresize_shutdown(gboolean reconfig)
100 {
101     if (!reconfig) {
102         if (moveresize_in_progress)
103             moveresize_end(FALSE);
104         client_remove_destroy_notify(client_dest);
105     }
106
107     popup_free(popup);
108     popup = NULL;
109 }
110
111 static void popup_coords(ObClient *c, const gchar *format, gint a, gint b)
112 {
113     gchar *text;
114
115     text = g_strdup_printf(format, a, b);
116     if (config_resize_popup_pos == OB_RESIZE_POS_TOP)
117         popup_position(popup, SouthGravity,
118                        c->frame->area.x
119                      + c->frame->area.width/2,
120                        c->frame->area.y - ob_rr_theme->fbwidth);
121     else if (config_resize_popup_pos == OB_RESIZE_POS_CENTER)
122         popup_position(popup, CenterGravity,
123                        c->frame->area.x + c->frame->area.width / 2,
124                        c->frame->area.y + c->frame->area.height / 2);
125     else /* Fixed */ {
126         const Rect *area = screen_physical_area_active();
127         gint gravity, x, y;
128
129         x = config_resize_popup_fixed.x.pos;
130         if (config_resize_popup_fixed.x.center)
131             x = area->x + area->width/2;
132         else if (config_resize_popup_fixed.x.opposite)
133             x = RECT_RIGHT(*area) - x;
134         else
135             x = area->x + x;
136
137         y = config_resize_popup_fixed.y.pos;
138         if (config_resize_popup_fixed.y.center)
139             y = area->y + area->height/2;
140         else if (config_resize_popup_fixed.y.opposite)
141             y = RECT_RIGHT(*area) - y;
142         else
143             y = area->y + y;
144
145         if (config_resize_popup_fixed.x.center) {
146             if (config_resize_popup_fixed.y.center)
147                 gravity = CenterGravity;
148             else if (config_resize_popup_fixed.y.opposite)
149                 gravity = SouthGravity;
150             else
151                 gravity = NorthGravity;
152         }
153         else if (config_resize_popup_fixed.x.opposite) {
154             if (config_resize_popup_fixed.y.center)
155                 gravity = EastGravity;
156             else if (config_resize_popup_fixed.y.opposite)
157                 gravity = SouthEastGravity;
158             else
159                 gravity = NorthEastGravity;
160         }
161         else {
162             if (config_resize_popup_fixed.y.center)
163                 gravity = WestGravity;
164             else if (config_resize_popup_fixed.y.opposite)
165                 gravity = SouthWestGravity;
166             else
167                 gravity = NorthWestGravity;
168         }
169
170         popup_position(popup, gravity, x, y);
171     }
172     popup->client = c;
173     popup_show(popup, text);
174     g_free(text);
175 }
176
177 void moveresize_start(ObClient *c, gint x, gint y, guint b, guint32 cnr)
178 {
179     ObCursor cur;
180     gboolean mv = (cnr == OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE) ||
181                    cnr == OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE_KEYBOARD));
182     gint up = 1;
183     gint left = 1;
184
185     if (moveresize_in_progress || !c->frame->visible ||
186         !(mv ?
187           (c->functions & OB_CLIENT_FUNC_MOVE) :
188           (c->functions & OB_CLIENT_FUNC_RESIZE)))
189         return;
190
191     if (cnr == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPLEFT)) {
192         cur = OB_CURSOR_NORTHWEST;
193         up = left = -1;
194     }
195     else if (cnr == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOP)) {
196         cur = OB_CURSOR_NORTH;
197         up = -1;
198     }
199     else if (cnr == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPRIGHT)) {
200         cur = OB_CURSOR_NORTHEAST;
201         up = -1;
202     }
203     else if (cnr == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_RIGHT))
204         cur = OB_CURSOR_EAST;
205     else if (cnr == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT))
206         cur = OB_CURSOR_SOUTHEAST;
207     else if (cnr == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOM))
208         cur = OB_CURSOR_SOUTH;
209     else if (cnr == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT)) {
210         cur = OB_CURSOR_SOUTHWEST;
211         left = -1;
212     }
213     else if (cnr == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_LEFT)) {
214         cur = OB_CURSOR_WEST;
215         left = -1;
216     }
217     else if (cnr == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_KEYBOARD))
218         cur = OB_CURSOR_SOUTHEAST;
219     else if (cnr == OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE))
220         cur = OB_CURSOR_MOVE;
221     else if (cnr == OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE_KEYBOARD))
222         cur = OB_CURSOR_MOVE;
223     else
224         g_assert_not_reached();
225
226     /* keep the pointer bounded to the screen for move/resize */
227     if (!grab_pointer(FALSE, TRUE, cur))
228         return;
229     if (!grab_keyboard()) {
230         ungrab_pointer();
231         return;
232     }
233
234     frame_end_iconify_animation(c->frame);
235
236     moving = mv;
237     moveresize_client = c;
238     start_cx = c->area.x;
239     start_cy = c->area.y;
240     start_cw = c->area.width;
241     start_ch = c->area.height;
242     /* these adjustments for the size_inc make resizing a terminal more
243        friendly. you essentially start the resize in the middle of the
244        increment instead of at 0, so you have to move half an increment
245        either way instead of a full increment one and 1 px the other. */
246     start_x = x - (mv ? 0 : left * c->size_inc.width / 2);
247     start_y = y - (mv ? 0 : up * c->size_inc.height / 2);
248     corner = cnr;
249     button = b;
250     key_resize_edge = -1;
251
252     /* default to not putting max back on cancel */
253     was_max_horz = was_max_vert = FALSE;
254
255     /*
256       have to change start_cx and start_cy if going to do this..
257     if (corner == prop_atoms.net_wm_moveresize_move_keyboard ||
258         corner == prop_atoms.net_wm_moveresize_size_keyboard)
259         XWarpPointer(ob_display, None, c->window, 0, 0, 0, 0,
260                      c->area.width / 2, c->area.height / 2);
261     */
262
263     cur_x = start_cx;
264     cur_y = start_cy;
265     cur_w = start_cw;
266     cur_h = start_ch;
267
268     moveresize_in_progress = TRUE;
269     waiting_for_sync = 0;
270
271     if (moving)
272         do_move(FALSE, 0);
273     else
274         do_resize();
275
276 #ifdef SYNC
277     if (config_resize_redraw && !moving && obt_display_extension_sync &&
278         moveresize_client->sync_request && moveresize_client->sync_counter &&
279         !moveresize_client->not_responding)
280     {
281         /* Initialize values for the resize syncing, and create an alarm for
282            the client's xsync counter */
283
284         XSyncValue val;
285         XSyncAlarmAttributes aa;
286
287         /* set the counter to an initial value */
288         XSyncIntToValue(&val, 0);
289         XSyncSetCounter(obt_display, moveresize_client->sync_counter, val);
290
291         /* this will be incremented when we tell the client what we're
292            looking for */
293         moveresize_client->sync_counter_value = 0;
294
295         /* the next sequence we're waiting for with the alarm */
296         XSyncIntToValue(&val, 1);
297
298         /* set an alarm on the counter */
299         aa.trigger.counter = moveresize_client->sync_counter;
300         aa.trigger.wait_value = val;
301         aa.trigger.value_type = XSyncAbsolute;
302         aa.trigger.test_type = XSyncPositiveTransition;
303         aa.events = True;
304         XSyncIntToValue(&aa.delta, 1);
305         moveresize_alarm = XSyncCreateAlarm(obt_display,
306                                             XSyncCACounter |
307                                             XSyncCAValue |
308                                             XSyncCAValueType |
309                                             XSyncCATestType |
310                                             XSyncCADelta |
311                                             XSyncCAEvents,
312                                             &aa);
313     }
314 #endif
315 }
316
317 void moveresize_end(gboolean cancel)
318 {
319     ungrab_keyboard();
320     ungrab_pointer();
321
322     popup_hide(popup);
323     popup->client = NULL;
324
325     if (!moving) {
326 #ifdef SYNC
327         /* turn off the alarm */
328         if (moveresize_alarm != None) {
329             XSyncDestroyAlarm(obt_display, moveresize_alarm);
330             moveresize_alarm = None;
331         }
332
333         if (sync_timer) g_source_remove(sync_timer);
334         sync_timer = 0;
335 #endif
336     }
337
338     /* don't use client_move() here, use the same width/height as
339        we've been using during the move, otherwise we get different results
340        when moving maximized windows between monitors of different sizes !
341     */
342     client_configure(moveresize_client,
343                      (cancel ? start_cx : cur_x),
344                      (cancel ? start_cy : cur_y),
345                      (cancel ? start_cw : cur_w),
346                      (cancel ? start_ch : cur_h),
347                      TRUE, TRUE, FALSE);
348
349     /* restore the client's maximized state. do this after putting the window
350        back in its original spot to minimize visible flicker */
351     if (cancel && (was_max_horz || was_max_vert)) {
352         const gboolean h = moveresize_client->max_horz;
353         const gboolean v = moveresize_client->max_vert;
354
355         client_maximize(moveresize_client, TRUE,
356                         was_max_horz && was_max_vert ? 0 :
357                         (was_max_horz ? 1 : 2));
358
359         /* replace the premax values with the ones we had saved if
360            the client doesn't have any already set */
361         if (was_max_horz && !h) {
362             moveresize_client->pre_max_area.x = pre_max_area.x;
363             moveresize_client->pre_max_area.width = pre_max_area.width;
364         }
365         if (was_max_vert && !v) {
366             moveresize_client->pre_max_area.y = pre_max_area.y;
367             moveresize_client->pre_max_area.height = pre_max_area.height;
368         }
369     }
370
371     /* dont edge warp after its ended */
372     cancel_edge_warp();
373
374     moveresize_in_progress = FALSE;
375     moveresize_client = NULL;
376 }
377
378 static void do_move(gboolean keyboard, gint keydist)
379 {
380     gint resist;
381
382     if (keyboard) resist = keydist - 1; /* resist for one key press */
383     else resist = config_resist_win;
384     resist_move_windows(moveresize_client, resist, &cur_x, &cur_y);
385     if (!keyboard) resist = config_resist_edge;
386     resist_move_monitors(moveresize_client, resist, &cur_x, &cur_y);
387
388     client_configure(moveresize_client, cur_x, cur_y, cur_w, cur_h,
389                      TRUE, FALSE, FALSE);
390     if (config_resize_popup_show == 2) /* == "Always" */
391         popup_coords(moveresize_client, "%d x %d",
392                      moveresize_client->frame->area.x,
393                      moveresize_client->frame->area.y);
394 }
395
396 static void do_resize(void)
397 {
398     gint x, y, w, h, lw, lh;
399
400     /* see if it is actually going to resize
401        USE cur_x AND cur_y HERE !  Otherwise the try_configure won't know
402        what struts to use !!
403      */
404     x = cur_x;
405     y = cur_y;
406     w = cur_w;
407     h = cur_h;
408     client_try_configure(moveresize_client, &x, &y, &w, &h,
409                          &lw, &lh, TRUE);
410     if (!(w == moveresize_client->area.width &&
411           h == moveresize_client->area.height) &&
412         /* if waiting_for_sync == 0, then we aren't waiting.
413            if it is > SYNC_TIMEOUTS, then we have timed out
414            that many times already, so forget about waiting more */
415         (waiting_for_sync == 0 || waiting_for_sync > SYNC_TIMEOUTS))
416     {
417 #ifdef SYNC
418         if (config_resize_redraw && obt_display_extension_sync &&
419             /* don't send another sync when one is pending */
420             waiting_for_sync == 0 &&
421             moveresize_client->sync_request &&
422             moveresize_client->sync_counter &&
423             !moveresize_client->not_responding)
424         {
425             XEvent ce;
426             XSyncValue val;
427
428             /* increment the value we're waiting for */
429             ++moveresize_client->sync_counter_value;
430             XSyncIntToValue(&val, moveresize_client->sync_counter_value);
431
432             /* tell the client what we're waiting for */
433             ce.xclient.type = ClientMessage;
434             ce.xclient.message_type = OBT_PROP_ATOM(WM_PROTOCOLS);
435             ce.xclient.display = obt_display;
436             ce.xclient.window = moveresize_client->window;
437             ce.xclient.format = 32;
438             ce.xclient.data.l[0] = OBT_PROP_ATOM(NET_WM_SYNC_REQUEST);
439             ce.xclient.data.l[1] = event_time();
440             ce.xclient.data.l[2] = XSyncValueLow32(val);
441             ce.xclient.data.l[3] = XSyncValueHigh32(val);
442             ce.xclient.data.l[4] = 0l;
443             XSendEvent(obt_display, moveresize_client->window, FALSE,
444                        NoEventMask, &ce);
445
446             waiting_for_sync = 1;
447
448             if (sync_timer) g_source_remove(sync_timer);
449             sync_timer = g_timeout_add(2000, sync_timeout_func, NULL);
450         }
451 #endif
452
453         /* force a ConfigureNotify, it is part of the spec for SYNC resizing
454            and MUST follow the sync counter notification */
455         client_configure(moveresize_client, cur_x, cur_y, cur_w, cur_h,
456                          TRUE, FALSE, TRUE);
457     }
458
459     /* this would be better with a fixed width font ... XXX can do it better
460        if there are 2 text boxes */
461     if (config_resize_popup_show == 2 || /* == "Always" */
462             (config_resize_popup_show == 1 && /* == "Nonpixel" */
463              moveresize_client->size_inc.width > 1 &&
464              moveresize_client->size_inc.height > 1))
465         popup_coords(moveresize_client, "%d x %d", lw, lh);
466 }
467
468 #ifdef SYNC
469 static gboolean sync_timeout_func(gpointer data)
470 {
471     ++waiting_for_sync; /* we timed out waiting for our sync... */
472     do_resize(); /* ...so let any pending resizes through */
473
474     if (waiting_for_sync > SYNC_TIMEOUTS) {
475         sync_timer = 0;
476         return FALSE; /* don't repeat */
477     }
478     else
479         return TRUE; /* keep waiting */
480 }
481 #endif
482
483 static void calc_resize(gboolean keyboard, gint keydist, gint *dw, gint *dh,
484                         ObDirection dir)
485 {
486     gint resist, x = 0, y = 0, lw, lh, ow, oh, nw, nh;
487     gint trydw, trydh;
488
489     ow = cur_w;
490     oh = cur_h;
491     nw = ow + *dw;
492     nh = oh + *dh;
493
494     if (!keyboard &&
495         (moveresize_client->max_ratio || moveresize_client->min_ratio))
496     {
497         switch (dir) {
498         case OB_DIRECTION_NORTH:
499         case OB_DIRECTION_SOUTH:
500             /* resize the width based on the height */
501             if (moveresize_client->min_ratio) {
502                 if (nh * moveresize_client->min_ratio > nw)
503                     nw = (gint)(nh * moveresize_client->min_ratio);
504             }
505             if (moveresize_client->max_ratio) {
506                 if (nh * moveresize_client->max_ratio < nw)
507                     nw = (gint)(nh * moveresize_client->max_ratio);
508             }
509             break;
510         default:
511             /* resize the height based on the width */
512             if (moveresize_client->min_ratio) {
513                 if (nh * moveresize_client->min_ratio > nw)
514                     nh = (gint)(nw / moveresize_client->min_ratio);
515             }
516             if (moveresize_client->max_ratio) {
517                 if (nh * moveresize_client->max_ratio < nw)
518                     nh = (gint)(nw / moveresize_client->max_ratio);
519             }
520             break;
521         }
522
523         /* see its actual size (apply aspect ratios) */
524         client_try_configure(moveresize_client, &x, &y, &nw, &nh, &lw, &lh,
525                              TRUE);
526         trydw = nw - ow;
527         trydh = nh - oh;
528     }
529
530     /* resist_size_* needs the frame size */
531     nw += moveresize_client->frame->size.left +
532         moveresize_client->frame->size.right;
533     nh += moveresize_client->frame->size.top +
534         moveresize_client->frame->size.bottom;
535
536     if (keyboard) resist = keydist - 1; /* resist for one key press */
537     else resist = config_resist_win;
538     resist_size_windows(moveresize_client, resist, &nw, &nh, dir);
539     if (!keyboard) resist = config_resist_edge;
540     resist_size_monitors(moveresize_client, resist, &nw, &nh, dir);
541
542     nw -= moveresize_client->frame->size.left +
543         moveresize_client->frame->size.right;
544     nh -= moveresize_client->frame->size.top +
545         moveresize_client->frame->size.bottom;
546
547     *dw = nw - ow;
548     *dh = nh - oh;
549
550     /* take aspect ratios into account for resistance */
551     if (!keyboard &&
552         (moveresize_client->max_ratio || moveresize_client->min_ratio))
553     {
554         if (*dh != trydh) { /* got resisted */
555             /* resize the width based on the height */
556             if (moveresize_client->min_ratio) {
557                 if (nh * moveresize_client->min_ratio > nw)
558                     nw = (gint)(nh * moveresize_client->min_ratio);
559             }
560             if (moveresize_client->max_ratio) {
561                 if (nh * moveresize_client->max_ratio < nw)
562                     nw = (gint)(nh * moveresize_client->max_ratio);
563             }
564         }
565         if (*dw != trydw) { /* got resisted */
566             /* resize the height based on the width */
567             if (moveresize_client->min_ratio) {
568                 if (nh * moveresize_client->min_ratio > nw)
569                     nh = (gint)(nw / moveresize_client->min_ratio);
570             }
571             if (moveresize_client->max_ratio) {
572                 if (nh * moveresize_client->max_ratio < nw)
573                     nh = (gint)(nw / moveresize_client->max_ratio);
574             }
575         }
576     }
577
578     /* make sure it's all valid */
579     client_try_configure(moveresize_client, &x, &y, &nw, &nh, &lw, &lh, TRUE);
580
581     *dw = nw - ow;
582     *dh = nh - oh;
583 }
584
585 static void edge_warp_move_ptr(void)
586 {
587     gint x, y;
588     const Rect* a;
589
590     screen_pointer_pos(&x, &y);
591     a = screen_physical_area_all_monitors();
592
593     switch (edge_warp_dir) {
594     case OB_DIRECTION_NORTH:
595         y = a->height - 1;
596         break;
597     case OB_DIRECTION_EAST:
598         x = a->x;
599         break;
600     case OB_DIRECTION_SOUTH:
601         y = a->y;
602         break;
603     case OB_DIRECTION_WEST:
604         x = a->width - 1;
605         break;
606     default:
607         g_assert_not_reached();
608     }
609
610     XWarpPointer(obt_display, 0, obt_root(ob_screen), 0, 0, 0, 0, x, y);
611 }
612
613 static gboolean edge_warp_delay_func(gpointer data)
614 {
615     guint d;
616
617     /* only fire every second time. so it's fast the first time, but slower
618        after that */
619     if (edge_warp_odd) {
620         d = screen_find_desktop(screen_desktop, edge_warp_dir, TRUE, FALSE);
621         if (d != screen_desktop) {
622             if (config_mouse_screenedgewarp) edge_warp_move_ptr();
623             screen_set_desktop(d, TRUE);
624         }
625     }
626     edge_warp_odd = !edge_warp_odd;
627
628     return TRUE; /* do repeat ! */
629 }
630
631 static void do_edge_warp(gint x, gint y)
632 {
633     guint i;
634     ObDirection dir;
635
636     if (!config_mouse_screenedgetime) return;
637
638     dir = -1;
639
640     for (i = 0; i < screen_num_monitors; ++i) {
641         const Rect *a = screen_physical_area_monitor(i);
642
643         if (!RECT_CONTAINS(*a, x, y))
644             continue;
645
646         if (x == RECT_LEFT(*a)) dir = OB_DIRECTION_WEST;
647         if (x == RECT_RIGHT(*a)) dir = OB_DIRECTION_EAST;
648         if (y == RECT_TOP(*a)) dir = OB_DIRECTION_NORTH;
649         if (y == RECT_BOTTOM(*a)) dir = OB_DIRECTION_SOUTH;
650
651         /* try check for xinerama boundaries */
652         if ((x + 1 == RECT_LEFT(*a) || x - 1 == RECT_RIGHT(*a)) &&
653             (dir == OB_DIRECTION_WEST || dir == OB_DIRECTION_EAST))
654         {
655             dir = -1;
656         }
657         if ((y + 1 == RECT_TOP(*a) || y - 1 == RECT_BOTTOM(*a)) &&
658             (dir == OB_DIRECTION_NORTH || dir == OB_DIRECTION_SOUTH))
659         {
660             dir = -1;
661         }
662     }
663
664     if (dir != edge_warp_dir) {
665         cancel_edge_warp();
666         if (dir != (ObDirection)-1) {
667             edge_warp_odd = TRUE; /* switch on the first timeout */
668             edge_warp_timer = g_timeout_add(config_mouse_screenedgetime,
669                                             edge_warp_delay_func, NULL);
670         }
671         edge_warp_dir = dir;
672     }
673 }
674
675 static void cancel_edge_warp(void)
676 {
677     if (edge_warp_timer) g_source_remove(edge_warp_timer);
678     edge_warp_timer = 0;
679 }
680
681 static void move_with_keys(KeySym sym, guint state)
682 {
683     gint dx = 0, dy = 0, ox = cur_x, oy = cur_y;
684     gint opx, px, opy, py;
685     gint dist = 0;
686
687     /* shift means jump to edge */
688     if (state & obt_keyboard_modkey_to_modmask(OBT_KEYBOARD_MODKEY_SHIFT))
689     {
690         gint x, y;
691         ObDirection dir;
692
693         if (sym == XK_Right)
694             dir = OB_DIRECTION_EAST;
695         else if (sym == XK_Left)
696             dir = OB_DIRECTION_WEST;
697         else if (sym == XK_Down)
698             dir = OB_DIRECTION_SOUTH;
699         else /* sym == XK_Up */
700             dir = OB_DIRECTION_NORTH;
701
702         client_find_move_directional(moveresize_client, dir, &x, &y);
703         dx = x - moveresize_client->area.x;
704         dy = y - moveresize_client->area.y;
705     } else {
706         /* control means fine grained */
707         if (state &
708             obt_keyboard_modkey_to_modmask(OBT_KEYBOARD_MODKEY_CONTROL))
709         {
710             dist = 1;
711         }
712         else
713             dist = KEY_DIST;
714
715         if (sym == XK_Right)
716             dx = dist;
717         else if (sym == XK_Left)
718             dx = -dist;
719         else if (sym == XK_Down)
720             dy = dist;
721         else /* if (sym == XK_Up) */
722             dy = -dist;
723     }
724
725     screen_pointer_pos(&opx, &opy);
726     XWarpPointer(obt_display, None, None, 0, 0, 0, 0, dx, dy);
727     /* steal the motion events this causes */
728     XSync(obt_display, FALSE);
729     {
730         XEvent ce;
731         while (xqueue_remove_local(&ce, xqueue_match_type,
732                                    GINT_TO_POINTER(MotionNotify)));
733     }
734     screen_pointer_pos(&px, &py);
735
736     cur_x += dx;
737     cur_y += dy;
738     do_move(TRUE, dist);
739
740     /* because the cursor moves even though the window does
741        not nessesarily (resistance), this adjusts where the curor
742        thinks it started so that it keeps up with where the window
743        actually is */
744     start_x += (px - opx) - (cur_x - ox);
745     start_y += (py - opy) - (cur_y - oy);
746 }
747
748 static void resize_with_keys(KeySym sym, guint state)
749 {
750     gint dw = 0, dh = 0, pdx = 0, pdy = 0, opx, opy, px, py;
751     gint resist = 0;
752     ObDirection dir;
753
754     /* pick the edge if it needs to move */
755     if (sym == XK_Right) {
756         dir = OB_DIRECTION_EAST;
757         if (key_resize_edge != OB_DIRECTION_WEST &&
758             key_resize_edge != OB_DIRECTION_EAST)
759         {
760             key_resize_edge = OB_DIRECTION_EAST;
761             return;
762         }
763     } else if (sym == XK_Left) {
764         dir = OB_DIRECTION_WEST;
765         if (key_resize_edge != OB_DIRECTION_WEST &&
766             key_resize_edge != OB_DIRECTION_EAST)
767         {
768             key_resize_edge = OB_DIRECTION_WEST;
769             return;
770         }
771     } else if (sym == XK_Up) {
772         dir = OB_DIRECTION_NORTH;
773         if (key_resize_edge != OB_DIRECTION_NORTH &&
774             key_resize_edge != OB_DIRECTION_SOUTH)
775         {
776             key_resize_edge = OB_DIRECTION_NORTH;
777             return;
778         }
779     } else /* if (sym == XK_Down) */ {
780         dir = OB_DIRECTION_SOUTH;
781         if (key_resize_edge != OB_DIRECTION_NORTH &&
782             key_resize_edge != OB_DIRECTION_SOUTH)
783         {
784             key_resize_edge = OB_DIRECTION_SOUTH;
785             return;
786         }
787     }
788
789     /* shift means jump to edge */
790     if (state & obt_keyboard_modkey_to_modmask(OBT_KEYBOARD_MODKEY_SHIFT))
791     {
792         gint x, y, w, h;
793
794         if (sym == XK_Right)
795             dir = OB_DIRECTION_EAST;
796         else if (sym == XK_Left)
797             dir = OB_DIRECTION_WEST;
798         else if (sym == XK_Down)
799             dir = OB_DIRECTION_SOUTH;
800         else /* if (sym == XK_Up)) */
801             dir = OB_DIRECTION_NORTH;
802
803         ObClientDirectionalResizeType resize_type =
804             key_resize_edge == dir ? CLIENT_RESIZE_GROW
805                                    : CLIENT_RESIZE_SHRINK;
806
807         client_find_resize_directional(moveresize_client,
808                                        key_resize_edge,
809                                        resize_type,
810                                        &x, &y, &w, &h);
811         dw = w - moveresize_client->area.width;
812         dh = h - moveresize_client->area.height;
813     } else {
814         gint distw, disth;
815
816         /* control means fine grained */
817         if (moveresize_client->size_inc.width > 1) {
818             distw = moveresize_client->size_inc.width;
819             resist = 1;
820         }
821         else if (state &
822                  obt_keyboard_modkey_to_modmask(OBT_KEYBOARD_MODKEY_CONTROL))
823         {
824             distw = 1;
825             resist = 1;
826         }
827         else {
828             distw = KEY_DIST;
829             resist = KEY_DIST;
830         }
831         if (moveresize_client->size_inc.height > 1) {
832             disth = moveresize_client->size_inc.height;
833             resist = 1;
834         }
835         else if (state &
836                  obt_keyboard_modkey_to_modmask(OBT_KEYBOARD_MODKEY_CONTROL))
837         {
838             disth = 1;
839             resist = 1;
840         }
841         else {
842             disth = KEY_DIST;
843             resist = KEY_DIST;
844         }
845
846         if (key_resize_edge == OB_DIRECTION_WEST) {
847             if (dir == OB_DIRECTION_WEST)
848                 dw = distw;
849             else
850                 dw = -distw;
851         }
852         else if (key_resize_edge == OB_DIRECTION_EAST) {
853             if (dir == OB_DIRECTION_EAST)
854                 dw = distw;
855             else
856                 dw = -distw;
857         }
858         else if (key_resize_edge == OB_DIRECTION_NORTH) {
859             if (dir == OB_DIRECTION_NORTH)
860                 dh = disth;
861             else
862                 dh = -disth;
863         }
864         else /*if (key_resize_edge == OB_DIRECTION_SOUTH)*/ {
865             if (dir == OB_DIRECTION_SOUTH)
866                 dh = disth;
867             else
868                 dh = -disth;
869         }
870     }
871
872     if (moveresize_client->max_horz &&
873         (key_resize_edge == OB_DIRECTION_WEST ||
874          key_resize_edge == OB_DIRECTION_EAST))
875     {
876         /* unmax horz */
877         was_max_horz = TRUE;
878         pre_max_area.x = moveresize_client->pre_max_area.x;
879         pre_max_area.width = moveresize_client->pre_max_area.width;
880
881         moveresize_client->pre_max_area.x = cur_x;
882         moveresize_client->pre_max_area.width = cur_w;
883         client_maximize(moveresize_client, FALSE, 1);
884     }
885     else if (moveresize_client->max_vert &&
886              (key_resize_edge == OB_DIRECTION_NORTH ||
887               key_resize_edge == OB_DIRECTION_SOUTH))
888     {
889         /* unmax vert */
890         was_max_vert = TRUE;
891         pre_max_area.y = moveresize_client->pre_max_area.y;
892         pre_max_area.height = moveresize_client->pre_max_area.height;
893
894         moveresize_client->pre_max_area.y = cur_y;
895         moveresize_client->pre_max_area.height = cur_h;
896         client_maximize(moveresize_client, FALSE, 2);
897     }
898
899     calc_resize(TRUE, resist, &dw, &dh, dir);
900     if (key_resize_edge == OB_DIRECTION_WEST)
901         cur_x -= dw;
902     else if (key_resize_edge == OB_DIRECTION_NORTH)
903         cur_y -= dh;
904     cur_w += dw;
905     cur_h += dh;
906
907     /* how to move the pointer to keep up with the change */
908     if (key_resize_edge == OB_DIRECTION_WEST)
909         pdx = -dw;
910     else if (key_resize_edge == OB_DIRECTION_EAST)
911         pdx = dw;
912     else if (key_resize_edge == OB_DIRECTION_NORTH)
913         pdy = -dh;
914     else if (key_resize_edge == OB_DIRECTION_SOUTH)
915         pdy = dh;
916
917     screen_pointer_pos(&opx, &opy);
918     XWarpPointer(obt_display, None, None, 0, 0, 0, 0, pdx, pdy);
919     /* steal the motion events this causes */
920     XSync(obt_display, FALSE);
921     {
922         XEvent ce;
923         while (xqueue_remove_local(&ce, xqueue_match_type,
924                                    GINT_TO_POINTER(MotionNotify)));
925     }
926     screen_pointer_pos(&px, &py);
927
928     do_resize();
929
930     /* because the cursor moves even though the window does
931        not nessesarily (resistance), this adjusts where the cursor
932        thinks it started so that it keeps up with where the window
933        actually is */
934     start_x += (px - opx) - dw;
935     start_y += (py - opy) - dh;
936
937 }
938
939 gboolean moveresize_event(XEvent *e)
940 {
941     gboolean used = FALSE;
942
943     if (!moveresize_in_progress) return FALSE;
944
945     if (e->type == ButtonPress) {
946         if (!button) {
947             start_x = e->xbutton.x_root;
948             start_y = e->xbutton.y_root;
949             button = e->xbutton.button; /* this will end it now */
950         }
951         used = e->xbutton.button == button;
952     } else if (e->type == ButtonRelease) {
953         if (!button || e->xbutton.button == button) {
954             moveresize_end(FALSE);
955             used = TRUE;
956         }
957     } else if (e->type == MotionNotify) {
958         if (moving) {
959             XEvent ce;
960             ObtXQueueWindowType wt;
961
962             wt.window = e->xmotion.window;
963             wt.type = MotionNotify;
964             while (xqueue_remove_local(&ce, xqueue_match_window_type, &wt)) {
965                 e->xmotion.x = ce.xmotion.x;
966                 e->xmotion.y = ce.xmotion.y;
967                 e->xmotion.x_root = ce.xmotion.x_root;
968                 e->xmotion.y_root = ce.xmotion.y_root;
969             }
970
971             cur_x = start_cx + e->xmotion.x_root - start_x;
972             cur_y = start_cy + e->xmotion.y_root - start_y;
973             do_move(FALSE, 0);
974             XSync(obt_display, FALSE);
975             do_edge_warp(e->xmotion.x_root, e->xmotion.y_root);
976         } else {
977             gint dw, dh;
978             ObDirection dir;
979
980             if (corner == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPLEFT)) {
981                 dw = -(e->xmotion.x_root - start_x);
982                 dh = -(e->xmotion.y_root - start_y);
983                 dir = OB_DIRECTION_NORTHWEST;
984             } else if (corner == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOP)) {
985                 dw = 0;
986                 dh = -(e->xmotion.y_root - start_y);
987                 dir = OB_DIRECTION_NORTH;
988             } else if (corner ==
989                        OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPRIGHT)) {
990                 dw = (e->xmotion.x_root - start_x);
991                 dh = -(e->xmotion.y_root - start_y);
992                 dir = OB_DIRECTION_NORTHEAST;
993             } else if (corner == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_RIGHT)) {
994                 dw = (e->xmotion.x_root - start_x);
995                 dh = 0;
996                 dir = OB_DIRECTION_EAST;
997             } else if (corner ==
998                        OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT)) {
999                 dw = (e->xmotion.x_root - start_x);
1000                 dh = (e->xmotion.y_root - start_y);
1001                 dir = OB_DIRECTION_SOUTHEAST;
1002             } else if (corner == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOM))
1003             {
1004                 dw = 0;
1005                 dh = (e->xmotion.y_root - start_y);
1006                 dir = OB_DIRECTION_SOUTH;
1007             } else if (corner ==
1008                        OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT)) {
1009                 dw = -(e->xmotion.x_root - start_x);
1010                 dh = (e->xmotion.y_root - start_y);
1011                 dir = OB_DIRECTION_SOUTHWEST;
1012             } else if (corner == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_LEFT)) {
1013                 dw = -(e->xmotion.x_root - start_x);
1014                 dh = 0;
1015                 dir = OB_DIRECTION_WEST;
1016             } else if (corner ==
1017                        OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_KEYBOARD)) {
1018                 dw = (e->xmotion.x_root - start_x);
1019                 dh = (e->xmotion.y_root - start_y);
1020                 dir = OB_DIRECTION_SOUTHEAST;
1021             } else
1022                 g_assert_not_reached();
1023
1024             /* override the client's max state if desired */
1025             if (ABS(dw) >= config_resist_edge) {
1026                 if (moveresize_client->max_horz) {
1027                     /* unmax horz */
1028                     was_max_horz = TRUE;
1029                     pre_max_area.x = moveresize_client->pre_max_area.x;
1030                     pre_max_area.width = moveresize_client->pre_max_area.width;
1031
1032                     moveresize_client->pre_max_area.x = cur_x;
1033                     moveresize_client->pre_max_area.width = cur_w;
1034                     client_maximize(moveresize_client, FALSE, 1);
1035                 }
1036             }
1037             else if (was_max_horz && !moveresize_client->max_horz) {
1038                 /* remax horz and put the premax back */
1039                 client_maximize(moveresize_client, TRUE, 1);
1040                 moveresize_client->pre_max_area.x = pre_max_area.x;
1041                 moveresize_client->pre_max_area.width = pre_max_area.width;
1042             }
1043
1044             if (ABS(dh) >= config_resist_edge) {
1045                 if (moveresize_client->max_vert) {
1046                     /* unmax vert */
1047                     was_max_vert = TRUE;
1048                     pre_max_area.y = moveresize_client->pre_max_area.y;
1049                     pre_max_area.height =
1050                         moveresize_client->pre_max_area.height;
1051
1052                     moveresize_client->pre_max_area.y = cur_y;
1053                     moveresize_client->pre_max_area.height = cur_h;
1054                     client_maximize(moveresize_client, FALSE, 2);
1055                 }
1056             }
1057             else if (was_max_vert && !moveresize_client->max_vert) {
1058                 /* remax vert and put the premax back */
1059                 client_maximize(moveresize_client, TRUE, 2);
1060                 moveresize_client->pre_max_area.y = pre_max_area.y;
1061                 moveresize_client->pre_max_area.height = pre_max_area.height;
1062             }
1063
1064             dw -= cur_w - start_cw;
1065             dh -= cur_h - start_ch;
1066
1067             calc_resize(FALSE, 0, &dw, &dh, dir);
1068             cur_w += dw;
1069             cur_h += dh;
1070
1071             if (corner == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPLEFT) ||
1072                 corner == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_LEFT) ||
1073                 corner == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT))
1074             {
1075                 cur_x -= dw;
1076             }
1077             if (corner == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPLEFT) ||
1078                 corner == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOP) ||
1079                 corner == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPRIGHT))
1080             {
1081                 cur_y -= dh;
1082             }
1083
1084             do_resize();
1085         }
1086         used = TRUE;
1087     } else if (e->type == KeyPress) {
1088         KeySym sym = obt_keyboard_keypress_to_keysym(e);
1089
1090         if (sym == XK_Escape) {
1091             moveresize_end(TRUE);
1092             used = TRUE;
1093         } else if (sym == XK_Return || sym == XK_KP_Enter) {
1094             moveresize_end(FALSE);
1095             used = TRUE;
1096         } else if (sym == XK_Right || sym == XK_Left ||
1097                    sym == XK_Up || sym == XK_Down)
1098         {
1099             if (corner == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_KEYBOARD)) {
1100                 resize_with_keys(sym, e->xkey.state);
1101                 used = TRUE;
1102             } else if (corner ==
1103                        OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE_KEYBOARD))
1104             {
1105                 move_with_keys(sym, e->xkey.state);
1106                 used = TRUE;
1107             }
1108         }
1109     }
1110 #ifdef SYNC
1111     else if (e->type == obt_display_extension_sync_basep + XSyncAlarmNotify)
1112     {
1113         waiting_for_sync = 0; /* we got our sync... */
1114         do_resize(); /* ...so try resize if there is more change pending */
1115         used = TRUE;
1116     }
1117 #endif
1118
1119     if (used && moveresize_client == focus_client)
1120         event_update_user_time();
1121
1122     return used;
1123 }