Commit 1b7a0c6c authored by Daiki Ueno's avatar Daiki Ueno

Port daemon from Python to Vala

This eliminates the need of the shell script wrapper.

https://bugzilla.gnome.org/show_bug.cgi?id=688218
parent 4000c25a
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
SUBDIRS = caribou bin data po libcaribou modules tools vapi
SUBDIRS = caribou bin data po libcaribou modules tools vapi daemon
if HAVE_VALADOC
SUBDIRS += docs
......
bin_SCRIPTS = caribou caribou-preferences
bin_SCRIPTS = caribou-preferences
libexec_SCRIPTS = antler-keyboard
DISTCLEANFILES = $(bin_SCRIPTS) $(libexec_SCRIPTS)
......
#!/bin/sh
#
# Caribou - text entry and UI navigation application
#
# Copyright (C) 2009 Adaptive Technology Resource Centre
# * Contributor: Ben Konrath <ben@bagu.org>
# Copyright (C) 2009 Eitan Isaacson <eitan@monotonous.org>
# Copyright (C) 2009 Sun Microsystems, Inc.
# * Contributor: Willie Walker <william.walker@sun.com>
# Copyright (C) 2009 Flavio Percoco <flaper87@flaper87.org>
# * Contributor: Flavio Percoco <flaper87@flaper87.org>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 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 Lesser General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
script_dir="$(dirname "$(readlink -f ${0})")"
prefix=@prefix@
exec_prefix=@exec_prefix@
if [ $script_dir = "@bindir@" ]
then
export PYTHONPATH="@prefix@/lib/python@PYTHON_VERSION@/site-packages${PYTHONPATH:+:$PYTHONPATH}"
export GI_TYPELIB_PATH="@libdir@/girepository-1.0${GI_TYPELIB_PATH:+:$GI_TYPELIB_PATH}"
else
export PYTHONPATH="$(dirname $script_dir)${PYTHONPATH:+:$PYTHONPATH}"
export GI_TYPELIB_PATH="$(dirname $script_dir)/libcaribou${GI_TYPELIB_PATH:+:$GI_TYPELIB_PATH}"
fi
@PYTHON@ -c "from caribou.daemon.main import CaribouDaemon; CaribouDaemon().run()"
......@@ -6,8 +6,7 @@ caribou_PYTHON = \
SUBDIRS = \
antler/ \
settings/ \
daemon/
settings/
DISTCLEANFILES = i18n.py
......
caribou_daemondir = $(pkgpythondir)/daemon/
caribou_daemon_PYTHON = \
__init__.py \
main.py
-include $(top_srcdir)/git.mk
from main import CaribouDaemon
import pyatspi
from gi.repository import GLib
from gi.repository import Gio
from gi.repository import GdkX11
from string import Template
from caribou.i18n import _
from caribou import APP_NAME
debug = False
class CaribouDaemon:
def __init__(self):
try:
self.keyboard_proxy = Gio.DBusProxy.new_for_bus_sync(
Gio.BusType.SESSION,
Gio.DBusProxyFlags.NONE,
None,
"org.gnome.Caribou.Keyboard",
"/org/gnome/Caribou/Keyboard",
"org.gnome.Caribou.Keyboard",
None)
except GLib.GError, e:
self._show_error_dialog(e.message)
self._current_acc = None
self._x11_display = GdkX11.X11Display.get_default()
self._register_event_listeners()
def _show_error_dialog(self, message):
from gi.repository import Gtk
msgdialog = Gtk.MessageDialog(None,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.ERROR,
Gtk.ButtonsType.CLOSE,
_("Error starting %s") % APP_NAME)
msgdialog.format_secondary_text(message)
msgdialog.run()
quit()
def _register_event_listeners(self):
pyatspi.Registry.registerEventListener(
self.on_focus, "object:state-changed:focused")
pyatspi.Registry.registerEventListener(self.on_focus, "focus")
pyatspi.Registry.registerEventListener(
self.on_text_caret_moved, "object:text-caret-moved")
def _deregister_event_listeners(self):
pyatspi.Registry.deregisterEventListener(
self.on_focus, "object:state-changed:focused")
pyatspi.Registry.deregisterEventListener(self.on_focus, "focus")
pyatspi.Registry.deregisterEventListener(
self.on_text_caret_moved, "object:text-caret-moved")
def on_text_caret_moved(self, event):
if self._current_acc == event.source:
text = self._current_acc.queryText()
x, y, w, h = text.getCharacterExtents(text.caretOffset,
pyatspi.DESKTOP_COORDS)
if (x, y, w, h) == (0, 0, 0, 0):
component = self._current_acc.queryComponent()
bb = component.getExtents(pyatspi.DESKTOP_COORDS)
x, y, w, h = bb.x, bb.y, bb.width, bb.height
self.keyboard_proxy.SetCursorLocation('(iiii)', x, y, w, h)
if debug == True:
print "object:text-caret-moved in", event.host_application.name,
print event.detail1, event.source.description
def _set_entry_location(self, acc):
text = acc.queryText()
bx, by, bw, bh = text.getCharacterExtents(text.caretOffset,
pyatspi.DESKTOP_COORDS)
component = acc.queryComponent()
entry_bb = component.getExtents(pyatspi.DESKTOP_COORDS)
if (bx, by, bw, bh) == (0, 0, 0, 0):
bx, by, bw, bh = entry_bb.x, entry_bb.y, entry_bb.width, entry_bb.height
self.keyboard_proxy.SetCursorLocation('(iiii)', bx, by, bw, bh)
self.keyboard_proxy.SetEntryLocation('(iiii)', entry_bb.x, entry_bb.y,
entry_bb.width, entry_bb.height)
self.keyboard_proxy.Show('(u)', self._x11_display.get_user_time())
def on_focus(self, event):
acc = event.source
source_role = acc.getRole()
if acc.getState().contains(pyatspi.STATE_EDITABLE) or \
source_role == pyatspi.ROLE_TERMINAL:
if source_role in (pyatspi.ROLE_TEXT,
pyatspi.ROLE_PARAGRAPH,
pyatspi.ROLE_PASSWORD_TEXT,
pyatspi.ROLE_TERMINAL,
pyatspi.ROLE_ENTRY):
if event.type.startswith("focus") or event.detail1 == 1:
self._set_entry_location(acc)
self._current_acc = event.source
if debug == True:
print "enter text widget in", event.host_application.name
elif event.detail1 == 0 and acc == self._current_acc:
self.keyboard_proxy.Hide('(u)',
self._x11_display.get_user_time())
self._current_acc = None
if debug == True:
print "leave text widget in", event.host_application.name
else:
if debug == True:
print _("WARNING - Caribou: unhandled editable widget:"), \
event.source
def clean_exit(self):
self.keyboard_proxy.Hide('(u)', self._x11_display.get_user_time())
self._deregister_event_listeners()
def run(self):
try:
pyatspi.Registry.start()
except KeyboardInterrupt:
self.clean_exit()
pyatspi.Registry.stop()
......@@ -32,7 +32,10 @@ VALADOC_REQUIRED=0.3.1
PKG_CHECK_MODULES(CARIBOU, [
pygobject-3.0 >= $PYGOBJECT_REQUIRED,
gtk+-3.0 >= $GTK_REQUIRED,
clutter-1.0 >= $CLUTTER_REQUIRED
clutter-1.0 >= $CLUTTER_REQUIRED,
gdk-3.0 >= $GDK_REQUIRED,
x11,
atspi-2
])
AC_SUBST(CARIBOU_CFLAGS)
AC_SUBST(CARIBOU_LIBS)
......@@ -132,9 +135,7 @@ caribou/Makefile
caribou/i18n.py
caribou/antler/Makefile
caribou/settings/Makefile
caribou/daemon/Makefile
bin/Makefile
bin/caribou
bin/caribou-preferences
bin/antler-keyboard
data/Makefile
......@@ -151,4 +152,5 @@ modules/gtk2/Makefile
tools/Makefile
docs/Makefile
vapi/Makefile
daemon/Makefile
])
bin_PROGRAMS = caribou
caribou_VALAFLAGS = \
--vapidir=$(top_srcdir)/vapi \
--pkg config \
--pkg gtk+-3.0 \
--pkg gdk-x11-3.0 \
--pkg atspi-2 \
--pkg x11 \
--pkg posix \
--pkg libxklavier \
$(VALAFLAGS)
caribou_CFLAGS = \
$(CARIBOU_CFLAGS) \
-DLOCALEDIR=\"$(datadir)/locale\"
caribou_LDADD = \
$(CARIBOU_LIBS)
caribou_SOURCES = daemon.vala
-include $(top_srcdir)/git.mk
namespace Caribou {
// We can't use the name "Keyboard" here since caribou-gtk-module
// might register the name first.
[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 Daemon : Object {
_Keyboard keyboard;
Atspi.Accessible current_acc;
unowned Gdk.Display display;
public Daemon () {
display = Gdk.Display.get_default ();
}
void on_get_proxy_ready (GLib.Object? obj, GLib.AsyncResult res) {
try {
keyboard = Bus.get_proxy.end (res);
} catch (Error e) {
error ("%s\n".printf (e.message));
}
try {
register_event_listeners ();
} catch (Error e) {
warning ("can't register event listeners: %s", e.message);
}
}
uint32 get_timestamp () {
return Gdk.X11Display.get_user_time (display);
}
void set_entry_location (Atspi.Accessible acc) throws Error {
var text = acc.get_text ();
var rect = text.get_character_extents (text.get_caret_offset (),
Atspi.CoordType.SCREEN);
var component = acc.get_component ();
var entry_rect = component.get_extents (Atspi.CoordType.SCREEN);
if (rect.x == 0 && rect.y == 0 &&
rect.width == 0 && rect.height == 0) {
rect = entry_rect;
}
keyboard.set_cursor_location (rect.x, rect.y,
rect.width, rect.height);
keyboard.set_entry_location (entry_rect.x, entry_rect.y,
entry_rect.width, entry_rect.height);
keyboard.show (get_timestamp ());
}
void on_focus (owned Atspi.Event event) throws Error {
var acc = event.source;
var source_role = acc.get_role ();
if (acc.get_state_set ().contains (Atspi.StateType.EDITABLE) ||
source_role == Atspi.Role.TERMINAL) {
switch (source_role) {
case Atspi.Role.TEXT:
case Atspi.Role.PARAGRAPH:
case Atspi.Role.PASSWORD_TEXT:
case Atspi.Role.TERMINAL:
case Atspi.Role.ENTRY:
if (event.type.has_prefix ("focus") || event.detail1 == 1) {
set_entry_location (acc);
current_acc = event.source;
debug ("enter text widget in %s",
event.source.get_application ().name);
} else if (event.detail1 == 0 && acc == current_acc) {
keyboard.hide (get_timestamp ());
current_acc = null;
debug ("leave text widget in %s",
event.source.get_application ().name);
} else {
warning ("unhandled editable widget: %s",
event.source.name);
}
break;
default:
break;
}
}
}
void on_focus_ignore_error (owned Atspi.Event event) {
try {
on_focus (event);
} catch (Error e) {
warning ("error in focus handler: %s", e.message);
}
}
void on_text_caret_moved (owned Atspi.Event event) throws Error {
if (current_acc == event.source) {
var text = current_acc.get_text ();
var rect = text.get_character_extents (text.get_caret_offset (),
Atspi.CoordType.SCREEN);
if (rect.x == 0 && rect.y == 0 &&
rect.width == 0 && rect.height == 0) {
var component = current_acc.get_component ();
rect = component.get_extents (Atspi.CoordType.SCREEN);
}
keyboard.set_cursor_location (rect.x, rect.y,
rect.width, rect.height);
debug ("object:text-caret-moved in %s: %d %s",
event.source.get_application ().name,
event.detail1, event.source.description);
}
}
void on_text_caret_moved_ignore_error (owned Atspi.Event event) {
try {
on_text_caret_moved (event);
} catch (Error e) {
warning ("error in text caret movement handler: %s", e.message);
}
}
void register_event_listeners () throws Error {
Atspi.EventListener.register_from_callback (
on_focus_ignore_error, "object:state-changed:focused");
Atspi.EventListener.register_from_callback (
on_focus_ignore_error, "focus:");
Atspi.EventListener.register_from_callback (
on_text_caret_moved_ignore_error, "object:text-caret-moved");
}
void deregister_event_listeners () throws Error {
Atspi.EventListener.deregister_from_callback (
on_focus_ignore_error, "object:state-changed:focused");
Atspi.EventListener.deregister_from_callback (
on_focus_ignore_error, "focus:");
Atspi.EventListener.deregister_from_callback (
on_text_caret_moved_ignore_error, "object:text-caret-moved");
}
public void run () {
Bus.get_proxy.begin<_Keyboard> (BusType.SESSION,
"org.gnome.Caribou.Keyboard",
"/org/gnome/Caribou/Keyboard",
0,
null,
on_get_proxy_ready);
Gtk.main ();
}
public void quit () {
if (keyboard != null) {
try {
keyboard.hide (get_timestamp ());
} catch (IOError e) {
warning ("can't hide keyboard: %s", e.message);
}
try {
deregister_event_listeners ();
} catch (Error e) {
warning ("can't deregister event listeners: %s", e.message);
}
keyboard = null;
}
if (Gtk.main_level () > 0)
Gtk.main_quit ();
}
}
}
static int main (string[] args) {
Gtk.init (ref args);
Intl.setlocale (LocaleCategory.ALL, "");
Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
Intl.textdomain (Config.GETTEXT_PACKAGE);
Atspi.init ();
var daemon = new Caribou.Daemon ();
Unix.signal_add (Posix.SIGINT, () => {
daemon.quit ();
return false;
});
daemon.run ();
return 0;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment