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