summaryrefslogtreecommitdiff
path: root/modules/gtk3/caribou-gtk-module.vala
blob: b50522b1157b079e3a1d28af2998e9a2765ed3eb (plain)
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
namespace Caribou {
    [DBus(name = "org.gnome.Caribou.Keyboard")]
    interface Keyboard : Object {
        public abstract void set_cursor_location (int x, int y, int w, int h)
            throws IOError;
        public abstract void set_entry_location (int x, int y, int w, int h)
            throws IOError;
        public abstract void show (uint32 timestamp) throws IOError;
        public abstract void hide (uint32 timestamp) throws IOError;
    }

    class GtkModule {
        private GLib.HashTable<Gtk.Window, bool> windows;
        private Keyboard keyboard;
        private Gdk.Display display;

        public GtkModule () {
            windows = new GLib.HashTable<Gtk.Window, bool> (null, null);
            display = Gdk.Display.get_default ();

            Bus.get_proxy.begin<Keyboard> (BusType.SESSION, "org.gnome.Caribou.Keyboard",
                                           "/org/gnome/Caribou/Keyboard", 0, null, callback);
        }

        private void callback (GLib.Object? obj, GLib.AsyncResult res) {
            try {
                keyboard = Bus.get_proxy.end(res);
            }
            catch (Error e) {
                stderr.printf ("%s\n", e.message);
                return;
            }

            Gdk.window_add_filter (null, event_filter);

            // Something might already be focused
            GLib.List<weak Gtk.Window> toplevels = Gtk.Window.list_toplevels();
            foreach (weak Gtk.Window w in toplevels) {
                if (w.has_toplevel_focus) {
                    do_focus_change (w.get_focus ());
                    break;
                }
            }
        }

        private Gdk.FilterReturn event_filter (Gdk.XEvent xevent, Gdk.Event evt) {
            Gdk.Window? gdkwindow;
            void* data;
            Gtk.Window window;

#if GTK2
            void* pointer = &xevent;
            X.Event* xev = (X.Event *) pointer;
            gdkwindow = (Gdk.Window) Gdk.x11_xid_table_lookup_for_display (display, (X.ID) xev.xany.window);
#else
            gdkwindow = evt.any.window;
#endif

            if (gdkwindow == null ||
                gdkwindow.get_window_type () != Gdk.WindowType.TOPLEVEL)
                return Gdk.FilterReturn.CONTINUE;

            Gdk.window_get_user_data (gdkwindow, out data);
            if (data == null || !(data is Gtk.Window))
                return Gdk.FilterReturn.CONTINUE;

            window = (Gtk.Window *) data;
            if (!windows.lookup (window)) {
                windows.insert (window, true);
                window.notify["has-toplevel-focus"].connect (toplevel_focus_changed);
                window.set_focus.connect (window_focus_changed);
                window.destroy.connect (() => { windows.remove (window); });
            }

            return Gdk.FilterReturn.CONTINUE;
        }

        private void toplevel_focus_changed (Object obj, ParamSpec prop) {
            Gtk.Window window = (Gtk.Window) obj;
            if (window.has_toplevel_focus)
                do_focus_change (window.get_focus ());
        }

        private void window_focus_changed (Gtk.Window window,
                                           Gtk.Widget? widget) {
            do_focus_change (widget);
        }

        private void do_focus_change (Gtk.Widget? widget) {
#if GTK2
            uint32 timestamp = Gdk.x11_display_get_user_time (display);
#else
            uint32 timestamp = Gdk.X11Display.get_user_time (display);
#endif

            if ((widget is Gtk.Editable &&
                 ((Gtk.Editable) widget).get_editable ()) ||
                (widget is Gtk.TextView &&
                 ((Gtk.TextView) widget).get_editable ())) {
                Gdk.Window current_window = widget.get_window ();
                Atk.Object object = widget.get_accessible ();
                int x = 0, y = 0, w = 0, h = 0;
                int caret_offset = 0;

                if (object is Atk.Text) {
                    caret_offset = ((Atk.Text) object).get_caret_offset ();
                    Atk.get_character_extents ((Atk.Text) object, caret_offset, out x, out y, out w, out h, Atk.CoordType.SCREEN);
                }
                else {
                    if (current_window != null)
                        get_origin_geometry (current_window, out x, out y, out w, out h);
                }

                try {
                    keyboard.show (timestamp);
                    keyboard.set_cursor_location (x, y, w, h);
                } catch (IOError e) {
                    stderr.printf ("%s\n", e.message);
                }
            } else {
                try {
                    keyboard.hide (timestamp);
                } catch (IOError e) {
                    stderr.printf ("%s\n", e.message);
                }
            }
        }

        private void get_origin_geometry (Gdk.Window window,
                                          out int x, out int y,
                                          out int w, out int h) {
            window.get_origin (out x, out y);
#if GTK2
            window.get_geometry (null, null, out w, out h, null);
#else
            window.get_geometry (null, null, out w, out h);
#endif
        }

        public void unload () {
            Gdk.window_remove_filter(null, event_filter);

            foreach (Gtk.Window window in windows.get_keys ())
                window.notify["has-toplevel-focus"].disconnect (toplevel_focus_changed);
        }

    }
}