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