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