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