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