1 /*
2 * This is free software; you can redistribute it and/or modify it under
3 * the terms of the GNU Library General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful, but
8 * WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * General Public License for more details.
11 *
12 * You should have received a copy of the GNU Library General Public
13 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
14 *
15 */
16
17 /* Some stolen from yeahconsole -- loving that open source :) */
18
19 #include <tilda-config.h>
20
21 #include "debug.h"
22 #include "key_grabber.h"
23 #include "screen-size.h"
24 #include "tilda.h"
25 #include "xerror.h"
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include "configsys.h"
29 #include "tomboykeybinder.h"
30
31 #include <X11/Xlib.h>
32 #include <X11/keysym.h>
33 #include <X11/Xutil.h>
34 #include <X11/cursorfont.h>
35 #include <X11/Xatom.h>
36 #include <gtk/gtk.h>
37 #include <unistd.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41
42 #include <gdk/gdkx.h>
43
44 #define ANIMATION_UP 0
45 #define ANIMATION_DOWN 1
46
47 /* Define local variables here */
48 #define ANIMATION_Y 0
49 #define ANIMATION_X 1
50
51 /*
52 * animation_coordinates:
53 * [0: up, 1: down]
54 * [0: ypos, 1: xpos]
55 * [animation steps]
56 */
57 static gint animation_coordinates[2][2][32];
58
animation_ease_function_down(gint i,gint n)59 static float animation_ease_function_down(gint i, gint n) {
60 const float t = (float)i/n;
61 const float ts = t*t;
62 const float tc = ts*t;
63 return 1*tc*ts + -5*ts*ts + 10*tc + -10*ts + 5*t;
64 }
65
animation_ease_function_up(gint i,gint n)66 static float animation_ease_function_up(gint i, gint n) {
67 const float t = (float)i/n;
68 const float ts = t*t;
69 const float tc = ts*t;
70 return 1 - (0*tc*ts + 0*ts*ts + 0*tc + 1*ts + 0*t);
71 }
72
73 static void pull_down (struct tilda_window_ *tw);
74
75 static void pull_up (struct tilda_window_ *tw);
76
generate_animation_positions(struct tilda_window_ * tw)77 void generate_animation_positions (struct tilda_window_ *tw)
78 {
79 DEBUG_FUNCTION ("generate_animation_positions");
80 DEBUG_ASSERT (tw != NULL);
81
82 gint i;
83 gint last_pos_x = config_getint ("x_pos");
84 gint last_pos_y = config_getint ("y_pos");
85
86 GdkRectangle rectangle;
87 config_get_configured_window_size (&rectangle);
88
89 gint last_width = rectangle.width;
90 gint last_height = rectangle.height;
91 gint screen_width;
92 gint screen_height;
93 screen_size_get_dimensions (&screen_width, &screen_height);
94
95 for (i=0; i<32; i++)
96 {
97 switch (config_getint ("animation_orientation"))
98 {
99 case 3: /* right->left RIGHT */
100 animation_coordinates[ANIMATION_UP][ANIMATION_Y][i] = last_pos_y;
101 animation_coordinates[ANIMATION_UP][ANIMATION_X][i] =
102 (gint)(screen_width + (last_pos_x - screen_width) * animation_ease_function_up(i, 32));
103 animation_coordinates[ANIMATION_DOWN][ANIMATION_Y][i] = last_pos_y;
104 animation_coordinates[ANIMATION_DOWN][ANIMATION_X][i] =
105 (gint)(screen_width + (last_pos_x - screen_width) * animation_ease_function_down(i, 32));
106 break;
107 case 2: /* left->right LEFT */
108 animation_coordinates[ANIMATION_UP][ANIMATION_Y][i] = last_pos_y;
109 animation_coordinates[ANIMATION_UP][ANIMATION_X][i] =
110 (gint)(-last_width + (last_pos_x - -last_width) * animation_ease_function_up(i, 32));
111 animation_coordinates[ANIMATION_DOWN][ANIMATION_Y][i] = last_pos_y;
112 animation_coordinates[ANIMATION_DOWN][ANIMATION_X][i] =
113 (gint)(-last_width + (last_pos_x - -last_width) * animation_ease_function_down(i, 32));
114 break;
115 case 1: /* bottom->top BOTTOM */
116 animation_coordinates[ANIMATION_UP][ANIMATION_Y][i] =
117 (gint)(screen_height + (last_pos_y - screen_height) * animation_ease_function_up(i, 32));
118 animation_coordinates[ANIMATION_UP][ANIMATION_X][i] = last_pos_x;
119 animation_coordinates[ANIMATION_DOWN][ANIMATION_Y][i] =
120 (gint)(screen_height + (last_pos_y - screen_height) * animation_ease_function_down(i, 32));
121 animation_coordinates[ANIMATION_DOWN][ANIMATION_X][i] = last_pos_x;
122 break;
123 case 0: /* top->bottom TOP */
124 default:
125 animation_coordinates[ANIMATION_UP][ANIMATION_Y][i] =
126 (gint)(-last_height + (last_pos_y - -last_height) * animation_ease_function_up(i, 32));
127 animation_coordinates[ANIMATION_UP][ANIMATION_X][i] = last_pos_x;
128 animation_coordinates[ANIMATION_DOWN][ANIMATION_Y][i] =
129 (gint)(-last_height + (last_pos_y - -last_height) * animation_ease_function_down(i, 32));
130 animation_coordinates[ANIMATION_DOWN][ANIMATION_X][i] = last_pos_x;
131 break;
132 }
133 }
134 }
135
136 /* Shamelessly adapted (read: ripped off) from gdk_window_focus() and
137 * http://code.google.com/p/ttm/ trunk/src/window.c set_active()
138 *
139 * Also, more thanks to halfline and marnanel from irc.gnome.org #gnome
140 * for their help in figuring this out.
141 *
142 * Thank you. And boo to metacity, because they keep breaking us.
143 */
144
145 /* This function will make sure that tilda window becomes active (gains
146 * the focus) when it is called.
147 *
148 * This has to be the worst possible way of making this work, but it was the
149 * only way to get metacity to play nicely. All the other WM's are so nice,
150 * why oh why does metacity hate us so?
151 */
tilda_window_set_active(tilda_window * tw)152 void tilda_window_set_active (tilda_window *tw)
153 {
154 DEBUG_FUNCTION ("tilda_window_set_active");
155 DEBUG_ASSERT (tw != NULL);
156
157 GdkScreen *screen = gtk_widget_get_screen (tw->window);
158 Display *x11_display = GDK_WINDOW_XDISPLAY (gdk_screen_get_root_window (screen));
159 Window x11_window = GDK_WINDOW_XID (gtk_widget_get_window (tw->window) );
160 Window x11_root_window = GDK_WINDOW_XID ( gdk_screen_get_root_window (screen) );
161
162 XEvent event;
163 long mask = SubstructureRedirectMask | SubstructureNotifyMask;
164 gtk_window_move (GTK_WINDOW(tw->window), config_getint ("x_pos"), config_getint ("y_pos"));
165 if (gdk_x11_screen_supports_net_wm_hint (screen,
166 gdk_atom_intern_static_string ("_NET_ACTIVE_WINDOW")))
167 {
168 guint32 timestamp = gtk_get_current_event_time ();
169 if (timestamp == 0) {
170 timestamp = gdk_x11_get_server_time(gdk_screen_get_root_window (screen));
171 }
172 event.xclient.type = ClientMessage;
173 event.xclient.serial = 0;
174 event.xclient.send_event = True;
175 event.xclient.display = x11_display;
176 event.xclient.window = x11_window;
177 event.xclient.message_type = gdk_x11_get_xatom_by_name ("_NET_ACTIVE_WINDOW");
178
179 event.xclient.format = 32;
180 event.xclient.data.l[0] = 2; /* pager */
181 event.xclient.data.l[1] = timestamp; /* timestamp */
182 event.xclient.data.l[2] = 0;
183 event.xclient.data.l[3] = 0;
184 event.xclient.data.l[4] = 0;
185
186 XSendEvent (x11_display, x11_root_window, False, mask, &event);
187 }
188 else
189 {
190 /* The WM doesn't support the EWMH standards. We'll print a warning and
191 * try this, though it probably won't work... */
192 g_printerr (_("WARNING: Window manager (%s) does not support EWMH hints\n"),
193 gdk_x11_screen_get_window_manager_name (screen));
194 XRaiseWindow (x11_display, x11_window);
195 }
196 }
197
198 /* Process all pending GTK events, without returning to the GTK mainloop */
process_all_pending_gtk_events()199 static void process_all_pending_gtk_events ()
200 {
201 GdkDisplay *display = gdk_display_get_default ();
202
203 while (gtk_events_pending ())
204 gtk_main_iteration ();
205
206 /* This is not strictly necessary, but I think it makes the animation
207 * look a little smoother. However, it probably does increase the load
208 * on the X server. */
209 gdk_display_flush (display);
210 }
211
212 /**
213 * @force_hide: This option is used by the auto hide feature, so we can ignore the checks to focus tilda instead
214 * of pulling up.
215 */
pull(struct tilda_window_ * tw,enum pull_action action,gboolean force_hide)216 void pull (struct tilda_window_ *tw, enum pull_action action, gboolean force_hide)
217 {
218 DEBUG_FUNCTION ("pull");
219 DEBUG_ASSERT (tw != NULL);
220 DEBUG_ASSERT (action == PULL_UP || action == PULL_DOWN || action == PULL_TOGGLE);
221
222 gboolean needsFocus = !tw->focus_loss_on_keypress
223 && !gtk_window_is_active(GTK_WINDOW(tw->window))
224 && !force_hide
225 && !tw->hide_non_focused;
226
227 if (g_get_monotonic_time() - tw->last_action_time < 150 * G_TIME_SPAN_MILLISECOND) {
228 /* this is to prevent crazy toggling, with 150ms prevention time */
229 return;
230 }
231
232 if (tw->current_state == STATE_DOWN && needsFocus) {
233 /**
234 * See tilda_window.c in focus_out_event_cb for an explanation about focus_loss_on_keypress
235 * This conditional branch will only focus tilda but it does not actually pull the window up.
236 */
237 g_debug ("Tilda window not focused but visible");
238 gdk_x11_window_set_user_time(gtk_widget_get_window(tw->window),
239 tomboy_keybinder_get_current_event_time());
240 tilda_window_set_active(tw);
241 return;
242 }
243
244 if ((tw->current_state == STATE_UP) && action != PULL_UP) {
245 pull_down (tw);
246 } else if ((tw->current_state == STATE_DOWN) && action != PULL_DOWN) {
247 pull_up (tw);
248 }
249
250 tw->last_action = action;
251 tw->last_action_time = g_get_monotonic_time();
252 }
253
pull_up(struct tilda_window_ * tw)254 static void pull_up (struct tilda_window_ *tw) {
255 tw->current_state = STATE_GOING_UP;
256
257 gdk_x11_window_set_user_time (gtk_widget_get_window (tw->window),
258 tomboy_keybinder_get_current_event_time());
259
260 if (tw->fullscreen)
261 {
262 gtk_window_unfullscreen (GTK_WINDOW(tw->window));
263 }
264
265 if (config_getbool ("animation") && !tw->fullscreen) {
266 GdkWindow *x11window;
267 GdkDisplay *display;
268 Atom atom;
269 if (!config_getbool ("set_as_desktop")) {
270 x11window = gtk_widget_get_window (tw->window);
271 display = gdk_window_get_display (x11window);
272 atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DOCK");
273 XChangeProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (x11window),
274 gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE"),
275 XA_ATOM, 32, PropModeReplace,
276 (guchar *) &atom, 1);
277 process_all_pending_gtk_events ();
278 }
279 gint slide_sleep_usec = config_getint ("slide_sleep_usec");
280 for (guint i=0; i<32; i++) {
281 gtk_window_move (GTK_WINDOW(tw->window),
282 animation_coordinates[ANIMATION_UP][ANIMATION_X][i],
283 animation_coordinates[ANIMATION_UP][ANIMATION_Y][i]);
284
285 process_all_pending_gtk_events ();
286 g_usleep (slide_sleep_usec);
287 }
288
289 if (!config_getbool ("set_as_desktop")) {
290 x11window = gtk_widget_get_window (tw->window);
291 display = gdk_window_get_display (x11window);
292 if (config_getbool ("set_as_desktop")) {
293 atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DESKTOP");
294 } else {
295 atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_NORMAL");
296 }
297 XChangeProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (x11window),
298 gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE"),
299 XA_ATOM, 32, PropModeReplace,
300 (guchar *) &atom, 1);
301 }
302 }
303
304 /* All we have to do at this point is hide the window.
305 * Case 1 - Animation on: The window has moved outside the screen, just hide it
306 * Case 2 - Animation off: Just hide the window */
307 gtk_widget_hide (GTK_WIDGET(tw->window));
308
309 g_debug ("pull_up(): MOVED UP");
310 tw->current_state = STATE_UP;
311 }
312
pull_down(struct tilda_window_ * tw)313 static void pull_down (struct tilda_window_ *tw) {
314 tw->current_state = STATE_GOING_DOWN;
315
316 /* Keep things here just like they are. If you use gtk_window_present() here, you
317 * will introduce some weird graphical glitches. Also, calling gtk_window_move()
318 * before showing the window avoids yet more glitches. You should probably not use
319 * gtk_window_show_all() here, as it takes a long time to execute.
320 *
321 * Overriding the user time here seems to work a lot better than calling
322 * gtk_window_present_with_time() here, or at the end of the function. I have
323 * no idea why, they should do the same thing. */
324 gdk_x11_window_set_user_time (gtk_widget_get_window (tw->window),
325 tomboy_keybinder_get_current_event_time());
326 gtk_widget_show (GTK_WIDGET(tw->window));
327 #if GTK_MINOR_VERSION == 16
328 /* Temporary fix for GTK breaking restore on Fullscreen, only needed for
329 * GTK+ version 3.16, since it was fixed early in 3.17 and above. */
330 tilda_window_set_fullscreen(tw);
331 #endif
332
333 /* The window should maintain its properties when it is merely hidden, but it does
334 * not. If you delete the following call, the window will not remain visible
335 * on all workspaces after pull()ing it up and down a number of times.
336 *
337 * Note that the "Always on top" property doesn't seem to go away, only this
338 * property (Show on all desktops) does... */
339 if (config_getbool ("pinned"))
340 gtk_window_stick (GTK_WINDOW (tw->window));
341
342 if (config_getbool ("animation") && !tw->fullscreen) {
343 GdkWindow *x11window;
344 GdkDisplay *display;
345 Atom atom;
346 if (!config_getbool ("set_as_desktop")) {
347 x11window = gtk_widget_get_window (tw->window);
348 display = gdk_window_get_display (x11window);
349 atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DOCK");
350 XChangeProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (x11window),
351 gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE"),
352 XA_ATOM, 32, PropModeReplace,
353 (guchar *) &atom, 1);
354 process_all_pending_gtk_events ();
355 }
356 gint slide_sleep_usec = config_getint ("slide_sleep_usec");
357 for (guint i=0; i<32; i++) {
358 gtk_window_move (GTK_WINDOW(tw->window),
359 animation_coordinates[ANIMATION_DOWN][ANIMATION_X][i],
360 animation_coordinates[ANIMATION_DOWN][ANIMATION_Y][i]);
361
362 process_all_pending_gtk_events ();
363 g_usleep (slide_sleep_usec);
364 }
365
366 if (!config_getbool ("set_as_desktop")) {
367 x11window = gtk_widget_get_window (tw->window);
368 display = gdk_window_get_display (x11window);
369 if (config_getbool ("set_as_desktop")) {
370 atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DESKTOP");
371 } else {
372 atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_NORMAL");
373 }
374 XChangeProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (x11window),
375 gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE"),
376 XA_ATOM, 32, PropModeReplace,
377 (guchar *) &atom, 1);
378 }
379 } else {
380 gtk_window_move (GTK_WINDOW(tw->window), config_getint ("x_pos"), config_getint ("y_pos"));
381 }
382
383 /* Nasty code to make metacity behave. Starting at metacity-2.22 they "fixed" the
384 * focus stealing prevention to make the old _NET_WM_USER_TIME hack
385 * not work anymore. This is working for now... */
386 tilda_window_set_active (tw);
387
388 if (tw->fullscreen)
389 {
390 gtk_window_fullscreen (GTK_WINDOW(tw->window));
391 }
392
393 g_debug ("pull_down(): MOVED DOWN");
394 tw->current_state = STATE_DOWN;
395 }
396
onKeybindingPull(G_GNUC_UNUSED const char * keystring,gpointer user_data)397 static void onKeybindingPull (G_GNUC_UNUSED const char *keystring, gpointer user_data)
398 {
399 DEBUG_FUNCTION("onKeybindingPull");
400 tilda_window *tw = TILDA_WINDOW(user_data);
401 pull (tw, PULL_TOGGLE, FALSE);
402 }
403
tilda_keygrabber_bind(const gchar * keystr,tilda_window * tw)404 gboolean tilda_keygrabber_bind (const gchar *keystr, tilda_window *tw)
405 {
406 /* Empty strings are no good */
407 if (keystr == NULL || strcmp ("", keystr) == 0)
408 return FALSE;
409
410 return tomboy_keybinder_bind (keystr, (TomboyBindkeyHandler)onKeybindingPull, tw);
411 }
412
tilda_keygrabber_unbind(const gchar * keystr)413 void tilda_keygrabber_unbind (const gchar *keystr)
414 {
415 tomboy_keybinder_unbind (keystr, (TomboyBindkeyHandler)onKeybindingPull);
416 }
417
418
419
420 /* vim: set ts=4 sts=4 sw=4 expandtab: */
421
422