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