summaryrefslogtreecommitdiff
path: root/modules/gtk3/caribou-gtk-module.vala
blob: 540e8fc9dd302ab109a8ca479976b306bc22b62c (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
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.List<Gtk.Window> windows;
        private Keyboard keyboard;

        public GtkModule () {
            windows = new GLib.List<Gtk.Window>();
            try {
                keyboard = Bus.get_proxy_sync (BusType.SESSION,
                                               "org.gnome.Caribou.Keyboard",
                                               "/org/gnome/Caribou/Keyboard");
            } catch (Error e) {
                stderr.printf ("%s\n", e.message);
            }

            add_tracker ();

            GLib.Timeout.add (60, () => { add_tracker ();
                                                  return true; });
        }

        private void add_tracker () {
            GLib.List<weak Gtk.Window> toplevels;

            toplevels = Gtk.Window.list_toplevels();
            foreach (Gtk.Window window in toplevels) {
                if (windows.find(window) == null) {
                    window.notify["has-toplevel-focus"].connect(get_top_level_focus);
                    windows.append(window);
                }
            }
        }

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

        private void focus_tracker (Gtk.Window window, Gtk.Widget? widget) {
            uint32 timestamp = Gtk.get_current_event_time();
            if (widget != null && (widget is Gtk.Entry || widget is Gtk.TextView) && widget is Gtk.Editable) {
                Atk.Object focus_object = widget.get_accessible();
                Gdk.Window current_window = widget.get_window();
                int x=0, y=0, w=0, h=0;
                if (current_window != null && !get_acc_geometry (focus_object, out x, out y, out w, out h)) {
                    get_origin_geometry (current_window, out x, out y, out w, out h);
                }
                try {
                    keyboard.show (timestamp);
                    keyboard.set_entry_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
        }

        private Atk.Object? find_focused_accessible (Atk.Object acc) {
            Atk.StateSet state = acc.ref_state_set ();

            bool match = (state.contains_state (Atk.StateType.EDITABLE) &&
                          state.contains_state (Atk.StateType.FOCUSED) &&
                          acc.get_n_accessible_children () == 0);

            if (match)
                return acc;

            for (int i=0;i<acc.get_n_accessible_children ();i++) {
                 Atk.Object child = acc.ref_accessible_child (i);
                 Atk.Object focused_child = find_focused_accessible (child);
                 if (focused_child != null)
                     return focused_child;
            }

            return null;
        }

        private bool get_acc_geometry (Atk.Object acc,
                                       out int x, out int y, out int w, out int h) {
            Atk.Object child = null;


            if (acc.get_role () == Atk.Role.REDUNDANT_OBJECT) {
                /* It is probably Gecko */
                child = Atk.get_focus_object ();
            } else {
                child = find_focused_accessible (acc);
            }

            if (child == null)
                return false;

            if (!(child is Atk.Component)) {
                stderr.printf ("Accessible is not a component\n");
                return false;
            }

            /* We don't want the keyboard on the paragraph in OOo */
            if (child.get_role() == Atk.Role.PARAGRAPH)
                child = child.get_parent();

            Atk.component_get_extents ((Atk.Component) child,
                                       out x, out y, out w, out h,
                                       Atk.CoordType.SCREEN);

            return true;
        }

    }
}