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