]> Dogcows Code - chaz/openbox/blob - openbox/moveresize.c
warp desktops when you hit the edge of the screen while moving a window
[chaz/openbox] / 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 "prop.h"
24 #include "client.h"
25 #include "frame.h"
26 #include "openbox.h"
27 #include "resist.h"
28 #include "mainloop.h"
29 #include "popup.h"
30 #include "moveresize.h"
31 #include "config.h"
32 #include "event.h"
33 #include "debug.h"
34 #include "extensions.h"
35 #include "render/render.h"
36 #include "render/theme.h"
37
38 #include <X11/Xlib.h>
39 #include <glib.h>
40
41 /* how far windows move and resize with the keyboard arrows */
42 #define KEY_DIST 4
43
44 gboolean moveresize_in_progress = FALSE;
45 ObClient *moveresize_client = NULL;
46 #ifdef SYNC
47 XSyncAlarm moveresize_alarm = None;
48 #endif
49
50 static gboolean moving = FALSE; /* TRUE - moving, FALSE - resizing */
51
52 static gint start_x, start_y, start_cx, start_cy, start_cw, start_ch;
53 static gint cur_x, cur_y;
54 static guint button;
55 static guint32 corner;
56 static ObCorner lockcorner;
57 static ObDirection edge_warp_dir = -1;
58 #ifdef SYNC
59 static gboolean waiting_for_sync;
60 #endif
61
62 static ObPopup *popup = NULL;
63
64 static void do_edge_warp(gint x, gint y);
65 static void cancel_edge_warp();
66
67 static void client_dest(ObClient *client, gpointer data)
68 {
69 if (moveresize_client == client)
70 moveresize_end(TRUE);
71 }
72
73 void moveresize_startup(gboolean reconfig)
74 {
75 popup = popup_new(FALSE);
76 popup_set_text_align(popup, RR_JUSTIFY_CENTER);
77
78 if (!reconfig)
79 client_add_destroy_notify(client_dest, NULL);
80 }
81
82 void moveresize_shutdown(gboolean reconfig)
83 {
84 if (!reconfig) {
85 if (moveresize_in_progress)
86 moveresize_end(FALSE);
87 client_remove_destroy_notify(client_dest);
88 }
89
90 popup_free(popup);
91 popup = NULL;
92 }
93
94 static void get_resize_position(gint *x, gint *y, gboolean cancel)
95 {
96 gint dw, dh;
97 gint w, h, lw, lh;
98
99 *x = moveresize_client->frame->area.x;
100 *y = moveresize_client->frame->area.y;
101
102 if (cancel) {
103 w = start_cw;
104 h = start_ch;
105 } else {
106 w = cur_x;
107 h = cur_y;
108 }
109
110 /* see how much it is actually going to resize */
111 {
112 gint cx = *x, cy = *y;
113 frame_frame_gravity(moveresize_client->frame, &cx, &cy);
114 client_try_configure(moveresize_client, &cx, &cy, &w, &h,
115 &lw, &lh, TRUE);
116 }
117 dw = w - moveresize_client->area.width;
118 dh = h - moveresize_client->area.height;
119
120 switch (lockcorner) {
121 case OB_CORNER_TOPLEFT:
122 break;
123 case OB_CORNER_TOPRIGHT:
124 *x -= dw;
125 break;
126 case OB_CORNER_BOTTOMLEFT:
127 *y -= dh;
128 break;
129 case OB_CORNER_BOTTOMRIGHT:
130 *x -= dw;
131 *y -= dh;
132 break;
133 }
134
135 frame_frame_gravity(moveresize_client->frame, x, y);
136 }
137
138 static void popup_coords(ObClient *c, const gchar *format, gint a, gint b)
139 {
140 gchar *text;
141
142 text = g_strdup_printf(format, a, b);
143 if (config_resize_popup_pos == 1) /* == "Top" */
144 popup_position(popup, SouthGravity,
145 c->frame->area.x
146 + c->frame->area.width/2,
147 c->frame->area.y - ob_rr_theme->fbwidth);
148 else /* == "Center" */
149 popup_position(popup, CenterGravity,
150 c->frame->area.x + c->frame->size.left +
151 c->area.width / 2,
152 c->frame->area.y + c->frame->size.top +
153 c->area.height / 2);
154 popup_show(popup, text);
155 g_free(text);
156 }
157
158 void moveresize_start(ObClient *c, gint x, gint y, guint b, guint32 cnr)
159 {
160 ObCursor cur;
161 gboolean mv = (cnr == prop_atoms.net_wm_moveresize_move ||
162 cnr == prop_atoms.net_wm_moveresize_move_keyboard);
163
164 if (moveresize_in_progress || !c->frame->visible ||
165 !(mv ?
166 (c->functions & OB_CLIENT_FUNC_MOVE) :
167 (c->functions & OB_CLIENT_FUNC_RESIZE)))
168 return;
169
170 if (cnr == prop_atoms.net_wm_moveresize_size_topleft)
171 cur = OB_CURSOR_NORTHWEST;
172 else if (cnr == prop_atoms.net_wm_moveresize_size_top)
173 cur = OB_CURSOR_NORTH;
174 else if (cnr == prop_atoms.net_wm_moveresize_size_topright)
175 cur = OB_CURSOR_NORTHEAST;
176 else if (cnr == prop_atoms.net_wm_moveresize_size_right)
177 cur = OB_CURSOR_EAST;
178 else if (cnr == prop_atoms.net_wm_moveresize_size_bottomright)
179 cur = OB_CURSOR_SOUTHEAST;
180 else if (cnr == prop_atoms.net_wm_moveresize_size_bottom)
181 cur = OB_CURSOR_SOUTH;
182 else if (cnr == prop_atoms.net_wm_moveresize_size_bottomleft)
183 cur = OB_CURSOR_SOUTHWEST;
184 else if (cnr == prop_atoms.net_wm_moveresize_size_left)
185 cur = OB_CURSOR_WEST;
186 else if (cnr == prop_atoms.net_wm_moveresize_size_keyboard)
187 cur = OB_CURSOR_SOUTHEAST;
188 else if (cnr == prop_atoms.net_wm_moveresize_move)
189 cur = OB_CURSOR_MOVE;
190 else if (cnr == prop_atoms.net_wm_moveresize_move_keyboard)
191 cur = OB_CURSOR_MOVE;
192 else
193 g_assert_not_reached();
194
195 /* keep the pointer bounded to the screen for move/resize */
196 if (!grab_pointer(FALSE, TRUE, cur))
197 return;
198 if (!grab_keyboard()) {
199 ungrab_pointer();
200 return;
201 }
202
203 frame_end_iconify_animation(c->frame);
204
205 moving = mv;
206 moveresize_client = c;
207 start_cx = c->area.x;
208 start_cy = c->area.y;
209 /* these adjustments for the size_inc make resizing a terminal more
210 friendly. you essentially start the resize in the middle of the
211 increment instead of at 0, so you have to move half an increment
212 either way instead of a full increment one and 1 px the other. and this
213 is one large mother fucking comment. */
214 start_cw = c->area.width + c->size_inc.width / 2;
215 start_ch = c->area.height + c->size_inc.height / 2;
216 start_x = x;
217 start_y = y;
218 corner = cnr;
219 button = b;
220
221 /*
222 have to change start_cx and start_cy if going to do this..
223 if (corner == prop_atoms.net_wm_moveresize_move_keyboard ||
224 corner == prop_atoms.net_wm_moveresize_size_keyboard)
225 XWarpPointer(ob_display, None, c->window, 0, 0, 0, 0,
226 c->area.width / 2, c->area.height / 2);
227 */
228
229 if (moving) {
230 cur_x = start_cx;
231 cur_y = start_cy;
232 } else {
233 cur_x = start_cw;
234 cur_y = start_ch;
235 }
236
237 moveresize_in_progress = TRUE;
238
239 #ifdef SYNC
240 if (config_resize_redraw && !moving && extensions_shape &&
241 moveresize_client->sync_request && moveresize_client->sync_counter)
242 {
243 /* Initialize values for the resize syncing, and create an alarm for
244 the client's xsync counter */
245
246 XSyncValue val;
247 XSyncAlarmAttributes aa;
248
249 /* set the counter to an initial value */
250 XSyncIntToValue(&val, 0);
251 XSyncSetCounter(ob_display, moveresize_client->sync_counter, val);
252
253 /* this will be incremented when we tell the client what we're
254 looking for */
255 moveresize_client->sync_counter_value = 0;
256
257 /* the next sequence we're waiting for with the alarm */
258 XSyncIntToValue(&val, 1);
259
260 /* set an alarm on the counter */
261 aa.trigger.counter = moveresize_client->sync_counter;
262 aa.trigger.wait_value = val;
263 aa.trigger.value_type = XSyncAbsolute;
264 aa.trigger.test_type = XSyncPositiveTransition;
265 aa.events = True;
266 XSyncIntToValue(&aa.delta, 1);
267 moveresize_alarm = XSyncCreateAlarm(ob_display,
268 XSyncCACounter |
269 XSyncCAValue |
270 XSyncCAValueType |
271 XSyncCATestType |
272 XSyncCADelta |
273 XSyncCAEvents,
274 &aa);
275
276 waiting_for_sync = FALSE;
277 }
278 #endif
279 }
280
281 void moveresize_end(gboolean cancel)
282 {
283 gint x, y;
284
285 ungrab_keyboard();
286 ungrab_pointer();
287
288 popup_hide(popup);
289
290 if (moving) {
291 client_move(moveresize_client,
292 (cancel ? start_cx : cur_x),
293 (cancel ? start_cy : cur_y));
294 } else {
295 #ifdef SYNC
296 /* turn off the alarm */
297 if (moveresize_alarm != None) {
298 XSyncDestroyAlarm(ob_display, moveresize_alarm);
299 moveresize_alarm = None;
300 }
301 #endif
302
303 get_resize_position(&x, &y, cancel);
304 client_configure(moveresize_client, x, y,
305 (cancel ? start_cw : cur_x),
306 (cancel ? start_ch : cur_y),
307 TRUE, TRUE, FALSE);
308 }
309
310 /* dont edge warp after its ended */
311 cancel_edge_warp();
312
313 moveresize_in_progress = FALSE;
314 moveresize_client = NULL;
315 }
316
317 static void do_move(gboolean keyboard)
318 {
319 gint resist;
320
321 if (keyboard) resist = KEY_DIST - 1; /* resist for one key press */
322 else resist = config_resist_win;
323 resist_move_windows(moveresize_client, resist, &cur_x, &cur_y);
324 if (!keyboard) resist = config_resist_edge;
325 resist_move_monitors(moveresize_client, resist, &cur_x, &cur_y);
326
327 client_configure(moveresize_client, cur_x, cur_y,
328 moveresize_client->area.width,
329 moveresize_client->area.height,
330 TRUE, FALSE, FALSE);
331 if (config_resize_popup_show == 2) /* == "Always" */
332 popup_coords(moveresize_client, "%d x %d",
333 moveresize_client->frame->area.x,
334 moveresize_client->frame->area.y);
335 }
336
337 static void do_resize()
338 {
339 gint x, y, w, h, lw, lh;
340
341 /* see if it is actually going to resize */
342 x = 0;
343 y = 0;
344 w = cur_x;
345 h = cur_y;
346 client_try_configure(moveresize_client, &x, &y, &w, &h,
347 &lw, &lh, TRUE);
348 if (w == moveresize_client->area.width &&
349 h == moveresize_client->area.height)
350 {
351 return;
352 }
353
354 #ifdef SYNC
355 if (config_resize_redraw && extensions_sync &&
356 moveresize_client->sync_request && moveresize_client->sync_counter)
357 {
358 XEvent ce;
359 XSyncValue val;
360
361 /* are we already waiting for the sync counter to catch up? */
362 if (waiting_for_sync)
363 return;
364
365 /* increment the value we're waiting for */
366 ++moveresize_client->sync_counter_value;
367 XSyncIntToValue(&val, moveresize_client->sync_counter_value);
368
369 /* tell the client what we're waiting for */
370 ce.xclient.type = ClientMessage;
371 ce.xclient.message_type = prop_atoms.wm_protocols;
372 ce.xclient.display = ob_display;
373 ce.xclient.window = moveresize_client->window;
374 ce.xclient.format = 32;
375 ce.xclient.data.l[0] = prop_atoms.net_wm_sync_request;
376 ce.xclient.data.l[1] = event_curtime;
377 ce.xclient.data.l[2] = XSyncValueLow32(val);
378 ce.xclient.data.l[3] = XSyncValueHigh32(val);
379 ce.xclient.data.l[4] = 0l;
380 XSendEvent(ob_display, moveresize_client->window, FALSE,
381 NoEventMask, &ce);
382
383 waiting_for_sync = TRUE;
384 }
385 #endif
386
387 get_resize_position(&x, &y, FALSE);
388 client_configure(moveresize_client, x, y, cur_x, cur_y, TRUE, FALSE, FALSE);
389
390 /* this would be better with a fixed width font ... XXX can do it better
391 if there are 2 text boxes */
392 if (config_resize_popup_show == 2 || /* == "Always" */
393 (config_resize_popup_show == 1 && /* == "Nonpixel" */
394 moveresize_client->size_inc.width > 1 &&
395 moveresize_client->size_inc.height > 1))
396 popup_coords(moveresize_client, "%d x %d",
397 moveresize_client->logical_size.width,
398 moveresize_client->logical_size.height);
399 }
400
401 static void calc_resize(gboolean keyboard)
402 {
403 gint resist;
404
405 /* resist_size_* needs the frame size */
406 cur_x += moveresize_client->frame->size.left +
407 moveresize_client->frame->size.right;
408 cur_y += moveresize_client->frame->size.top +
409 moveresize_client->frame->size.bottom;
410
411 if (keyboard) resist = KEY_DIST - 1; /* resist for one key press */
412 else resist = config_resist_win;
413 resist_size_windows(moveresize_client, resist, &cur_x, &cur_y, lockcorner);
414 if (!keyboard) resist = config_resist_edge;
415 resist_size_monitors(moveresize_client, resist, &cur_x, &cur_y,lockcorner);
416
417 cur_x -= moveresize_client->frame->size.left +
418 moveresize_client->frame->size.right;
419 cur_y -= moveresize_client->frame->size.top +
420 moveresize_client->frame->size.bottom;
421 }
422
423 static gboolean edge_warp_delay_func(gpointer data)
424 {
425 guint d;
426
427 d = screen_find_desktop(screen_desktop, edge_warp_dir, TRUE, FALSE);
428 if (d != screen_desktop) screen_set_desktop(d, TRUE);
429
430 edge_warp_dir = -1;
431
432 return FALSE; /* don't repeat */
433 }
434
435 static void do_edge_warp(gint x, gint y)
436 {
437 guint i, d;
438 ObDirection dir;
439
440 if (!config_mouse_screenedgetime) return;
441
442 dir = -1;
443
444 for (i = 0; i < screen_num_monitors; ++i) {
445 Rect *a = screen_physical_area_monitor(i);
446 if (x == RECT_LEFT(*a)) dir = OB_DIRECTION_WEST;
447 if (x == RECT_RIGHT(*a)) dir = OB_DIRECTION_EAST;
448 if (y == RECT_TOP(*a)) dir = OB_DIRECTION_NORTH;
449 if (y == RECT_BOTTOM(*a)) dir = OB_DIRECTION_SOUTH;
450
451 /* try check for xinerama boundaries */
452 if ((x + 1 == RECT_LEFT(*a) || x - 1 == RECT_RIGHT(*a)) &&
453 (dir == OB_DIRECTION_WEST || dir == OB_DIRECTION_EAST))
454 {
455 dir = -1;
456 }
457 if ((y + 1 == RECT_TOP(*a) || y - 1 == RECT_BOTTOM(*a)) &&
458 (dir == OB_DIRECTION_NORTH || dir == OB_DIRECTION_SOUTH))
459 {
460 dir = -1;
461 }
462 g_free(a);
463 }
464
465 if (dir != edge_warp_dir) {
466 if (dir == (ObDirection)-1)
467 cancel_edge_warp();
468 else
469 ob_main_loop_timeout_add(ob_main_loop,
470 config_mouse_screenedgetime * 1000,
471 edge_warp_delay_func,
472 NULL, NULL, NULL);
473 edge_warp_dir = dir;
474 }
475 }
476
477 static void cancel_edge_warp()
478 {
479 ob_main_loop_timeout_remove(ob_main_loop, edge_warp_delay_func);
480 }
481
482 gboolean moveresize_event(XEvent *e)
483 {
484 gboolean used = FALSE;
485
486 if (!moveresize_in_progress) return FALSE;
487
488 if (e->type == ButtonPress) {
489 if (!button) {
490 start_x = e->xbutton.x_root;
491 start_y = e->xbutton.y_root;
492 button = e->xbutton.button; /* this will end it now */
493 }
494 used = e->xbutton.button == button;
495 } else if (e->type == ButtonRelease) {
496 if (!button || e->xbutton.button == button) {
497 moveresize_end(FALSE);
498 used = TRUE;
499 }
500 } else if (e->type == MotionNotify) {
501 if (moving) {
502 cur_x = start_cx + e->xmotion.x_root - start_x;
503 cur_y = start_cy + e->xmotion.y_root - start_y;
504 do_move(FALSE);
505 do_edge_warp(e->xmotion.x_root, e->xmotion.y_root);
506 } else {
507 if (corner == prop_atoms.net_wm_moveresize_size_topleft) {
508 cur_x = start_cw - (e->xmotion.x_root - start_x);
509 cur_y = start_ch - (e->xmotion.y_root - start_y);
510 lockcorner = OB_CORNER_BOTTOMRIGHT;
511 } else if (corner == prop_atoms.net_wm_moveresize_size_top) {
512 cur_x = start_cw;
513 cur_y = start_ch - (e->xmotion.y_root - start_y);
514 lockcorner = OB_CORNER_BOTTOMRIGHT;
515 } else if (corner == prop_atoms.net_wm_moveresize_size_topright) {
516 cur_x = start_cw + (e->xmotion.x_root - start_x);
517 cur_y = start_ch - (e->xmotion.y_root - start_y);
518 lockcorner = OB_CORNER_BOTTOMLEFT;
519 } else if (corner == prop_atoms.net_wm_moveresize_size_right) {
520 cur_x = start_cw + (e->xmotion.x_root - start_x);
521 cur_y = start_ch;
522 lockcorner = OB_CORNER_BOTTOMLEFT;
523 } else if (corner ==
524 prop_atoms.net_wm_moveresize_size_bottomright) {
525 cur_x = start_cw + (e->xmotion.x_root - start_x);
526 cur_y = start_ch + (e->xmotion.y_root - start_y);
527 lockcorner = OB_CORNER_TOPLEFT;
528 } else if (corner == prop_atoms.net_wm_moveresize_size_bottom) {
529 cur_x = start_cw;
530 cur_y = start_ch + (e->xmotion.y_root - start_y);
531 lockcorner = OB_CORNER_TOPLEFT;
532 } else if (corner ==
533 prop_atoms.net_wm_moveresize_size_bottomleft) {
534 cur_x = start_cw - (e->xmotion.x_root - start_x);
535 cur_y = start_ch + (e->xmotion.y_root - start_y);
536 lockcorner = OB_CORNER_TOPRIGHT;
537 } else if (corner == prop_atoms.net_wm_moveresize_size_left) {
538 cur_x = start_cw - (e->xmotion.x_root - start_x);
539 cur_y = start_ch;
540 lockcorner = OB_CORNER_TOPRIGHT;
541 } else if (corner == prop_atoms.net_wm_moveresize_size_keyboard) {
542 cur_x = start_cw + (e->xmotion.x_root - start_x);
543 cur_y = start_ch + (e->xmotion.y_root - start_y);
544 lockcorner = OB_CORNER_TOPLEFT;
545 } else
546 g_assert_not_reached();
547
548 calc_resize(FALSE);
549 do_resize();
550 }
551 used = TRUE;
552 } else if (e->type == KeyPress) {
553 if (e->xkey.keycode == ob_keycode(OB_KEY_ESCAPE)) {
554 moveresize_end(TRUE);
555 used = TRUE;
556 } else if (e->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
557 moveresize_end(FALSE);
558 used = TRUE;
559 } else if (e->xkey.keycode == ob_keycode(OB_KEY_RIGHT) ||
560 e->xkey.keycode == ob_keycode(OB_KEY_LEFT) ||
561 e->xkey.keycode == ob_keycode(OB_KEY_DOWN) ||
562 e->xkey.keycode == ob_keycode(OB_KEY_UP))
563 {
564 if (corner == prop_atoms.net_wm_moveresize_size_keyboard) {
565 gint dx = 0, dy = 0, ox = cur_x, oy = cur_y;
566
567 if (e->xkey.keycode == ob_keycode(OB_KEY_RIGHT))
568 dx = MAX(KEY_DIST, moveresize_client->size_inc.width);
569 else if (e->xkey.keycode == ob_keycode(OB_KEY_LEFT))
570 dx = -MAX(KEY_DIST, moveresize_client->size_inc.width);
571 else if (e->xkey.keycode == ob_keycode(OB_KEY_DOWN))
572 dy = MAX(KEY_DIST, moveresize_client->size_inc.height);
573 else /* if (e->xkey.keycode == ob_keycode(OB_KEY_UP)) */
574 dy = -MAX(KEY_DIST, moveresize_client->size_inc.height);
575
576 cur_x += dx;
577 cur_y += dy;
578 XWarpPointer(ob_display, None, None, 0, 0, 0, 0, dx, dy);
579 /* steal the motion events this causes */
580 XSync(ob_display, FALSE);
581 {
582 XEvent ce;
583 while (XCheckTypedEvent(ob_display, MotionNotify, &ce));
584 }
585
586 calc_resize(TRUE);
587 do_resize();
588
589 /* because the cursor moves even though the window does
590 not nessesarily (resistance), this adjusts where the curor
591 thinks it started so that it keeps up with where the window
592 actually is */
593 start_x += dx - (cur_x - ox);
594 start_y += dy - (cur_y - oy);
595
596 used = TRUE;
597 } else if (corner == prop_atoms.net_wm_moveresize_move_keyboard) {
598 gint dx = 0, dy = 0, ox = cur_x, oy = cur_y;
599 gint opx, px, opy, py;
600
601 if (e->xkey.keycode == ob_keycode(OB_KEY_RIGHT))
602 dx = KEY_DIST;
603 else if (e->xkey.keycode == ob_keycode(OB_KEY_LEFT))
604 dx = -KEY_DIST;
605 else if (e->xkey.keycode == ob_keycode(OB_KEY_DOWN))
606 dy = KEY_DIST;
607 else /* if (e->xkey.keycode == ob_keycode(OB_KEY_UP)) */
608 dy = -KEY_DIST;
609
610 cur_x += dx;
611 cur_y += dy;
612 screen_pointer_pos(&opx, &opy);
613 XWarpPointer(ob_display, None, None, 0, 0, 0, 0, dx, dy);
614 /* steal the motion events this causes */
615 XSync(ob_display, FALSE);
616 {
617 XEvent ce;
618 while (XCheckTypedEvent(ob_display, MotionNotify, &ce));
619 }
620 screen_pointer_pos(&px, &py);
621
622 do_move(TRUE);
623
624 /* because the cursor moves even though the window does
625 not nessesarily (resistance), this adjusts where the curor
626 thinks it started so that it keeps up with where the window
627 actually is */
628 start_x += (px - opx) - (cur_x - ox);
629 start_y += (py - opy) - (cur_y - oy);
630
631 used = TRUE;
632 }
633 }
634 }
635 #ifdef SYNC
636 else if (e->type == extensions_sync_event_basep + XSyncAlarmNotify)
637 {
638 waiting_for_sync = FALSE; /* we got our sync... */
639 do_resize(); /* ...so try resize if there is more change pending */
640 used = TRUE;
641 }
642 #endif
643 return used;
644 }
This page took 0.06313 seconds and 4 git commands to generate.