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