[go: up one dir, main page]

File: key_grabber.c

package info (click to toggle)
tilda 1.3.1-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 1,860 kB
  • ctags: 481
  • sloc: ansic: 5,730; makefile: 115; xml: 48; sh: 35
file content (407 lines) | stat: -rw-r--r-- 16,566 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
/*
 * This is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Library General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 */

/* Some stolen from yeahconsole -- loving that open source :) */

#include <tilda-config.h>

#include "debug.h"
#include "key_grabber.h"
#include "tilda.h"
#include "xerror.h"
#include <glib.h>
#include <glib/gi18n.h>
#include "configsys.h"
#include "tomboykeybinder.h"

#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/Xatom.h>
#include <gtk/gtk.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <gdk/gdkx.h>

#if DEBUG
#define debug_printf(args...) do { g_print ("debug: " args); } while (0)
#else
#define debug_printf(args...) do { /* nothing */ } while (0)
#endif

#define ANIMATION_UP 0
#define ANIMATION_DOWN 1

/* Define local variables here */
#define ANIMATION_Y 0
#define ANIMATION_X 1

/*
 *  animation_coordinates:
 *  [0: up, 1: down]
 *  [0: ypos, 1: xpos]
 *  [animation steps]
 */
static gint animation_coordinates[2][2][32];

static float animation_ease_function_down(gint i, gint n) {
    const float t = (float)i/n;
    const float ts = t*t;
    const float tc = ts*t;
    return 1*tc*ts + -5*ts*ts + 10*tc + -10*ts + 5*t;
}

static float animation_ease_function_up(gint i, gint n) {
    const float t = (float)i/n;
    const float ts = t*t;
    const float tc = ts*t;
    return 1 - (0*tc*ts + 0*ts*ts + 0*tc + 1*ts + 0*t);
}

static void pull_down (struct tilda_window_ *tw);

static void pull_up (struct tilda_window_ *tw);

void generate_animation_positions (struct tilda_window_ *tw)
{
    DEBUG_FUNCTION ("generate_animation_positions");
    DEBUG_ASSERT (tw != NULL);

    gint i;
    gint last_pos_x = config_getint ("x_pos");
    gint last_pos_y = config_getint ("y_pos");
    gint last_width = config_getint ("max_width");
    gint last_height = config_getint ("max_height");
    gint screen_width = gdk_screen_width();
    gint screen_height = gdk_screen_height();

    for (i=0; i<32; i++)
    {
        switch (config_getint ("animation_orientation"))
        {
        case 3: /* right->left RIGHT */
            animation_coordinates[ANIMATION_UP][ANIMATION_Y][i] = last_pos_y;
            animation_coordinates[ANIMATION_UP][ANIMATION_X][i] =
                    (gint)(screen_width + (last_pos_x - screen_width) * animation_ease_function_up(i, 32));
            animation_coordinates[ANIMATION_DOWN][ANIMATION_Y][i] = last_pos_y;
            animation_coordinates[ANIMATION_DOWN][ANIMATION_X][i] =
                    (gint)(screen_width + (last_pos_x - screen_width) * animation_ease_function_down(i, 32));
            break;
        case 2: /* left->right LEFT */
            animation_coordinates[ANIMATION_UP][ANIMATION_Y][i] = last_pos_y;
            animation_coordinates[ANIMATION_UP][ANIMATION_X][i] =
                    (gint)(-last_width + (last_pos_x - -last_width) * animation_ease_function_up(i, 32));
            animation_coordinates[ANIMATION_DOWN][ANIMATION_Y][i] = last_pos_y;
            animation_coordinates[ANIMATION_DOWN][ANIMATION_X][i] =
                    (gint)(-last_width + (last_pos_x - -last_width) * animation_ease_function_down(i, 32));
            break;
        case 1: /* bottom->top BOTTOM */
            animation_coordinates[ANIMATION_UP][ANIMATION_Y][i] =
                    (gint)(screen_height + (last_pos_y - screen_height) * animation_ease_function_up(i, 32));
            animation_coordinates[ANIMATION_UP][ANIMATION_X][i] = last_pos_x;
            animation_coordinates[ANIMATION_DOWN][ANIMATION_Y][i] =
                    (gint)(screen_height + (last_pos_y - screen_height) * animation_ease_function_down(i, 32));
            animation_coordinates[ANIMATION_DOWN][ANIMATION_X][i] = last_pos_x;
            break;
        case 0: /* top->bottom TOP */
        default:
            animation_coordinates[ANIMATION_UP][ANIMATION_Y][i] =
                    (gint)(-last_height + (last_pos_y - -last_height) * animation_ease_function_up(i, 32));
            animation_coordinates[ANIMATION_UP][ANIMATION_X][i] = last_pos_x;
            animation_coordinates[ANIMATION_DOWN][ANIMATION_Y][i] =
                    (gint)(-last_height + (last_pos_y - -last_height) * animation_ease_function_down(i, 32));
            animation_coordinates[ANIMATION_DOWN][ANIMATION_X][i] = last_pos_x;
            break;
        }
    }
}

/* Shamelessly adapted (read: ripped off) from gdk_window_focus() and
 * http://code.google.com/p/ttm/ trunk/src/window.c set_active()
 *
 * Also, more thanks to halfline and marnanel from irc.gnome.org #gnome
 * for their help in figuring this out.
 *
 * Thank you. And boo to metacity, because they keep breaking us.
 */

/* This function will make sure that tilda window becomes active (gains
 * the focus) when it is called.
 *
 * This has to be the worst possible way of making this work, but it was the
 * only way to get metacity to play nicely. All the other WM's are so nice,
 * why oh why does metacity hate us so?
 */
void tilda_window_set_active (tilda_window *tw)
{
    DEBUG_FUNCTION ("tilda_window_set_active");
    DEBUG_ASSERT (tw != NULL);

    Display *x11_display = GDK_WINDOW_XDISPLAY (gtk_widget_get_window (tw->window) );
    Window x11_window = GDK_WINDOW_XID (gtk_widget_get_window (tw->window) );
    Window x11_root_window = GDK_WINDOW_XID ( gtk_widget_get_root_window (tw->window) );
    GdkScreen *screen = gtk_widget_get_screen (tw->window);

    XEvent event;
    long mask = SubstructureRedirectMask | SubstructureNotifyMask;
    gtk_window_move (GTK_WINDOW(tw->window), config_getint ("x_pos"), config_getint ("y_pos"));
    if (gdk_x11_screen_supports_net_wm_hint (screen,
                                             gdk_atom_intern_static_string ("_NET_ACTIVE_WINDOW")))
    {
        guint32 timestamp = gtk_get_current_event_time ();
        if (timestamp == 0) {
            timestamp = gdk_x11_get_server_time(gdk_screen_get_root_window (screen));
        }
        event.xclient.type = ClientMessage;
        event.xclient.serial = 0;
        event.xclient.send_event = True;
        event.xclient.display = x11_display;
        event.xclient.window = x11_window;
        event.xclient.message_type = gdk_x11_get_xatom_by_name ("_NET_ACTIVE_WINDOW");

        event.xclient.format = 32;
        event.xclient.data.l[0] = 2; /* pager */
        event.xclient.data.l[1] = timestamp; /* timestamp */
        event.xclient.data.l[2] = 0;
        event.xclient.data.l[3] = 0;
        event.xclient.data.l[4] = 0;

        XSendEvent (x11_display, x11_root_window, False, mask, &event);
    }
    else
    {
        /* The WM doesn't support the EWMH standards. We'll print a warning and
         * try this, though it probably won't work... */
        g_printerr (_("WARNING: Window manager (%s) does not support EWMH hints\n"),
                    gdk_x11_screen_get_window_manager_name (screen));
        XRaiseWindow (x11_display, x11_window);
    }
}

/* Process all pending GTK events, without returning to the GTK mainloop */
static void process_all_pending_gtk_events ()
{
    while (gtk_events_pending ())
        gtk_main_iteration ();

    /* This is not strictly necessary, but I think it makes the animation
     * look a little smoother. However, it probably does increase the load
     * on the X server. */
    gdk_flush ();
}

/**
* @force_hide: This option is used by the auto hide feature, so we can ignore the checks to focus tilda instead
* of pulling up.
*/
void pull (struct tilda_window_ *tw, enum pull_action action, gboolean force_hide)
{
    DEBUG_FUNCTION ("pull");
    DEBUG_ASSERT (tw != NULL);
    DEBUG_ASSERT (action == PULL_UP || action == PULL_DOWN || action == PULL_TOGGLE);

    gboolean needsFocus = !tw->focus_loss_on_keypress
            && !gtk_window_is_active(GTK_WINDOW(tw->window))
            && !force_hide
            && !tw->hide_non_focused;

    if (action == PULL_TOGGLE && tw->last_action == PULL_UP
        && g_get_monotonic_time() - tw->last_action_time < 150 * G_TIME_SPAN_MILLISECOND) {
        /* this is to prevent crazy toggling, with 150ms prevention time */
        return;
    }

    if (tw->current_state == STATE_DOWN && needsFocus) {
        /**
        * See tilda_window.c in focus_out_event_cb for an explanation about focus_loss_on_keypress
        * This conditional branch will only focus tilda but it does not actually pull the window up.
        */
        TRACE (g_print("Tilda window not focused but visible\n"));
        gdk_x11_window_set_user_time(gtk_widget_get_window(tw->window),
                tomboy_keybinder_get_current_event_time());
        tilda_window_set_active(tw);
        return;
    }

    if ((tw->current_state == STATE_UP) && action != PULL_UP) {
        pull_down (tw);
    } else if ((tw->current_state == STATE_DOWN) && action != PULL_DOWN) {
        pull_up (tw);
    }

    tw->last_action = action;
    tw->last_action_time = g_get_monotonic_time();
}

static void pull_up (struct tilda_window_ *tw) {
    tw->current_state = STATE_GOING_UP;

    if (config_getbool ("animation") && !tw->fullscreen) {
        GdkWindow *x11window;
        GdkDisplay *display;
        Atom atom;
        if (!config_getbool ("set_as_desktop")) {
            x11window = gtk_widget_get_window (tw->window);
            display = gdk_window_get_display (x11window);
            atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DOCK");
            XChangeProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (x11window),
                             gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE"),
                             XA_ATOM, 32, PropModeReplace,
                             (guchar *) &atom, 1);
            process_all_pending_gtk_events ();
        }
        gint slide_sleep_usec = config_getint ("slide_sleep_usec");
        for (guint i=0; i<32; i++) {
            gtk_window_move (GTK_WINDOW(tw->window),
                             animation_coordinates[ANIMATION_UP][ANIMATION_X][i],
                             animation_coordinates[ANIMATION_UP][ANIMATION_Y][i]);

            process_all_pending_gtk_events ();
            g_usleep (slide_sleep_usec);
        }

        if (!config_getbool ("set_as_desktop")) {
            x11window = gtk_widget_get_window (tw->window);
            display = gdk_window_get_display (x11window);
            if (config_getbool ("set_as_desktop")) {
                atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DESKTOP");
            } else {
                atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_NORMAL");
            }
            XChangeProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (x11window),
                             gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE"),
                             XA_ATOM, 32, PropModeReplace,
                             (guchar *) &atom, 1);
        }
    }

    /* All we have to do at this point is hide the window.
     * Case 1 - Animation on:  The window has moved outside the screen, just hide it
     * Case 2 - Animation off: Just hide the window */
    gtk_widget_hide (GTK_WIDGET(tw->window));

    debug_printf ("pull_up(): MOVED UP\n");
    tw->current_state = STATE_UP;
}

static void pull_down (struct tilda_window_ *tw) {
    tw->current_state = STATE_GOING_DOWN;

    /* Keep things here just like they are. If you use gtk_window_present() here, you
     * will introduce some weird graphical glitches. Also, calling gtk_window_move()
     * before showing the window avoids yet more glitches. You should probably not use
     * gtk_window_show_all() here, as it takes a long time to execute.
     *
     * Overriding the user time here seems to work a lot better than calling
     * gtk_window_present_with_time() here, or at the end of the function. I have
     * no idea why, they should do the same thing. */
    gdk_x11_window_set_user_time (gtk_widget_get_window (tw->window),
                                  tomboy_keybinder_get_current_event_time());
    gtk_widget_show (GTK_WIDGET(tw->window));
#if GTK_MINOR_VERSION == 16
        /* Temporary fix for GTK breaking restore on Fullscreen, only needed for
         * GTK+ version 3.16, since it was fixed early in 3.17 and above. */
        tilda_window_set_fullscreen(tw);
#endif

    /* The window should maintain its properties when it is merely hidden, but it does
     * not. If you delete the following call, the window will not remain visible
     * on all workspaces after pull()ing it up and down a number of times.
     *
     * Note that the "Always on top" property doesn't seem to go away, only this
     * property (Show on all desktops) does... */
    if (config_getbool ("pinned"))
            gtk_window_stick (GTK_WINDOW (tw->window));

    if (config_getbool ("animation") && !tw->fullscreen) {
        GdkWindow *x11window;
        GdkDisplay *display;
        Atom atom;
        if (!config_getbool ("set_as_desktop")) {
            x11window = gtk_widget_get_window (tw->window);
            display = gdk_window_get_display (x11window);
            atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DOCK");
            XChangeProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (x11window),
                             gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE"),
                             XA_ATOM, 32, PropModeReplace,
                             (guchar *) &atom, 1);
            process_all_pending_gtk_events ();
        }
        gint slide_sleep_usec = config_getint ("slide_sleep_usec");
        for (guint i=0; i<32; i++) {
            gtk_window_move (GTK_WINDOW(tw->window),
                             animation_coordinates[ANIMATION_DOWN][ANIMATION_X][i],
                             animation_coordinates[ANIMATION_DOWN][ANIMATION_Y][i]);

            process_all_pending_gtk_events ();
            g_usleep (slide_sleep_usec);
        }

        if (!config_getbool ("set_as_desktop")) {
            x11window = gtk_widget_get_window (tw->window);
            display = gdk_window_get_display (x11window);
            if (config_getbool ("set_as_desktop")) {
                atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DESKTOP");
            } else {
                atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_NORMAL");
            }
            XChangeProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (x11window),
                             gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE"),
                             XA_ATOM, 32, PropModeReplace,
                             (guchar *) &atom, 1);
        }
    } else {
        gtk_window_move (GTK_WINDOW(tw->window), config_getint ("x_pos"), config_getint ("y_pos"));
    }

    /* Nasty code to make metacity behave. Starting at metacity-2.22 they "fixed" the
     * focus stealing prevention to make the old _NET_WM_USER_TIME hack
     * not work anymore. This is working for now... */
    tilda_window_set_active (tw);

    debug_printf ("pull_down(): MOVED DOWN\n");
    tw->current_state = STATE_DOWN;
}

static void onKeybindingPull (G_GNUC_UNUSED const char *keystring, gpointer user_data)
{
    DEBUG_FUNCTION("onKeybindingPull");
    tilda_window *tw = TILDA_WINDOW(user_data);
    pull (tw, PULL_TOGGLE, FALSE);
}

gboolean tilda_keygrabber_bind (const gchar *keystr, tilda_window *tw)
{
    /* Empty strings are no good */
    if (keystr == NULL || strcmp ("", keystr) == 0)
        return FALSE;

    return tomboy_keybinder_bind (keystr, (TomboyBindkeyHandler)onKeybindingPull, tw);
}

void tilda_keygrabber_unbind (const gchar *keystr)
{
    tomboy_keybinder_unbind (keystr, (TomboyBindkeyHandler)onKeybindingPull);
}



/* vim: set ts=4 sts=4 sw=4 expandtab: */