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