1namespace Caribou { 2 // We can't use the name "Keyboard" here since caribou-gtk-module 3 // might register the name first. 4 [DBus (name = "org.gnome.Caribou.Keyboard")] 5 interface _Keyboard : Object { 6 public abstract void set_cursor_location (int x, int y, int w, int h) 7 throws IOError; 8 public abstract void set_entry_location (int x, int y, int w, int h) 9 throws IOError; 10 public abstract void show (uint32 timestamp) throws IOError; 11 public abstract void hide (uint32 timestamp) throws IOError; 12 } 13 14 [DBus (name = "org.gnome.Caribou.Daemon")] 15 class Daemon : Object { 16 _Keyboard keyboard; 17 Atspi.Accessible current_acc; 18 unowned Gdk.Display display; 19 uint name_id; 20 21 public Daemon () { 22 display = Gdk.Display.get_default (); 23 name_id = Bus.own_name (BusType.SESSION, 24 "org.gnome.Caribou.Daemon", 25 BusNameOwnerFlags.ALLOW_REPLACEMENT 26 | BusNameOwnerFlags.REPLACE, 27 on_bus_acquired, null, quit); 28 } 29 30 ~Daemon () { 31 Bus.unown_name (name_id); 32 } 33 34 void on_bus_acquired (DBusConnection conn) { 35 try { 36 conn.register_object ("/org/gnome/Caribou/Daemon", this); 37 } catch (IOError e) { 38 error ("Could not register D-Bus service: %s", e.message); 39 } 40 } 41 42 void on_get_proxy_ready (GLib.Object? obj, GLib.AsyncResult res) { 43 try { 44 keyboard = Bus.get_proxy.end (res); 45 } catch (Error e) { 46 error ("%s\n".printf (e.message)); 47 } 48 49 try { 50 register_event_listeners (); 51 } catch (Error e) { 52 warning ("can't register event listeners: %s", e.message); 53 } 54 } 55 56 uint32 get_timestamp () { 57 if (display is Gdk.X11Display) 58 return Gdk.X11Display.get_user_time (display); 59 else 60 return 0; 61 } 62 63 void set_entry_location (Atspi.Accessible acc) throws Error { 64 var text = acc.get_text (); 65 var rect = text.get_character_extents (text.get_caret_offset (), 66 Atspi.CoordType.SCREEN); 67 var component = acc.get_component (); 68 var entry_rect = component.get_extents (Atspi.CoordType.SCREEN); 69 if (rect.x == 0 && rect.y == 0 && 70 rect.width == 0 && rect.height == 0) { 71 rect = entry_rect; 72 } 73 74 keyboard.set_cursor_location (rect.x, rect.y, 75 rect.width, rect.height); 76 77 keyboard.set_entry_location (entry_rect.x, entry_rect.y, 78 entry_rect.width, entry_rect.height); 79 80 keyboard.show (get_timestamp ()); 81 } 82 83 void on_focus (owned Atspi.Event event) throws Error { 84 var acc = event.source; 85 var source_role = acc.get_role (); 86 if (acc.get_state_set ().contains (Atspi.StateType.EDITABLE) || 87 source_role == Atspi.Role.TERMINAL) { 88 switch (source_role) { 89 case Atspi.Role.TEXT: 90 case Atspi.Role.PARAGRAPH: 91 case Atspi.Role.PASSWORD_TEXT: 92 case Atspi.Role.TERMINAL: 93 case Atspi.Role.ENTRY: 94 if (event.type == "object:state-changed:focused") { 95 if (event.detail1 == 1) { 96 set_entry_location (acc); 97 current_acc = event.source; 98 debug ("enter text widget in %s", 99 event.source.get_application ().name); 100 } else if (acc == current_acc) { 101 keyboard.hide (get_timestamp ()); 102 current_acc = null; 103 debug ("leave text widget in %s", 104 event.source.get_application ().name); 105 } 106 } else { 107 warning ("unknown focus event: %s", event.type); 108 } 109 break; 110 default: 111 break; 112 } 113 } 114 } 115 116 void on_focus_ignore_error (owned Atspi.Event event) { 117 try { 118 on_focus (event); 119 } catch (Error e) { 120 warning ("error in focus handler: %s", e.message); 121 } 122 } 123 124 void on_text_caret_moved (owned Atspi.Event event) throws Error { 125 if (current_acc == event.source) { 126 var text = current_acc.get_text (); 127 var rect = text.get_character_extents (text.get_caret_offset (), 128 Atspi.CoordType.SCREEN); 129 if (rect.x == 0 && rect.y == 0 && 130 rect.width == 0 && rect.height == 0) { 131 var component = current_acc.get_component (); 132 rect = component.get_extents (Atspi.CoordType.SCREEN); 133 } 134 135 keyboard.set_cursor_location (rect.x, rect.y, 136 rect.width, rect.height); 137 debug ("object:text-caret-moved in %s: %d %s", 138 event.source.get_application ().name, 139 event.detail1, event.source.description); 140 } 141 } 142 143 void on_text_caret_moved_ignore_error (owned Atspi.Event event) { 144 try { 145 on_text_caret_moved (event); 146 } catch (Error e) { 147 warning ("error in text caret movement handler: %s", e.message); 148 } 149 } 150 151 void register_event_listeners () throws Error { 152 Atspi.EventListener.register_from_callback ( 153 on_focus_ignore_error, "object:state-changed:focused"); 154 Atspi.EventListener.register_from_callback ( 155 on_text_caret_moved_ignore_error, "object:text-caret-moved"); 156 } 157 158 void deregister_event_listeners () throws Error { 159 Atspi.EventListener.deregister_from_callback ( 160 on_focus_ignore_error, "object:state-changed:focused"); 161 Atspi.EventListener.deregister_from_callback ( 162 on_text_caret_moved_ignore_error, "object:text-caret-moved"); 163 } 164 165 [DBus (name = "Run")] 166 public void ping () { 167 // This method is called over D-Bus, upon activation. 168 } 169 170 [DBus (visible = false)] 171 public void run () { 172 Bus.get_proxy.begin<_Keyboard> (BusType.SESSION, 173 "org.gnome.Caribou.Keyboard", 174 "/org/gnome/Caribou/Keyboard", 175 0, 176 null, 177 on_get_proxy_ready); 178 // Use Atspi.Event.{main,quit}, instead of GLib.MainLoop 179 // to enable property caching in libatspi. 180 Atspi.Event.main (); 181 } 182 183 public void quit () { 184 if (keyboard != null) { 185 try { 186 keyboard.hide (get_timestamp ()); 187 } catch (IOError e) { 188 warning ("can't hide keyboard: %s", e.message); 189 } 190 191 try { 192 deregister_event_listeners (); 193 } catch (Error e) { 194 warning ("can't deregister event listeners: %s", e.message); 195 } 196 keyboard = null; 197 } 198 199 Atspi.Event.quit (); 200 } 201 } 202} 203 204static const OptionEntry[] options = { 205 { null } 206}; 207 208static int main (string[] args) { 209 Gdk.init (ref args); 210 211 Intl.setlocale (LocaleCategory.ALL, ""); 212 Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR); 213 Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8"); 214 Intl.textdomain (Config.GETTEXT_PACKAGE); 215 216 var option_context = new OptionContext (_( 217 "- accessibility event monitoring daemon for screen keyboard")); 218 option_context.add_main_entries (options, "caribou"); 219 try { 220 option_context.parse (ref args); 221 } catch (OptionError e) { 222 stderr.printf ("%s\n", e.message); 223 return 1; 224 } 225 226 var retval = Atspi.init (); 227 if (retval != 0) { 228 printerr ("can't initialize atspi\n"); 229 return 1; 230 } 231 232 var daemon = new Caribou.Daemon (); 233 Unix.signal_add (Posix.SIGINT, () => { 234 daemon.quit (); 235 return false; 236 }); 237 daemon.run (); 238 239 return 0; 240} 241