444 lines
14 KiB
Plaintext
444 lines
14 KiB
Plaintext
|
#!/usr/bin/python3
|
||
|
|
||
|
import os
|
||
|
import asyncio
|
||
|
from pywayland.client import Display
|
||
|
from pywayland.protocol.wayland import (
|
||
|
WlSeat,
|
||
|
WlShell,
|
||
|
WlShm,
|
||
|
)
|
||
|
from protocols.text_input_unstable_v3 import ZwpTextInputManagerV3
|
||
|
|
||
|
import dbussy as dbus
|
||
|
from dbussy import \
|
||
|
DBUS, \
|
||
|
Introspection
|
||
|
import ravel
|
||
|
|
||
|
socket_path = os.environ["XDG_RUNTIME_DIR"] + "/maliit-server"
|
||
|
if not os.path.exists(socket_path):
|
||
|
os.makedirs(socket_path)
|
||
|
|
||
|
my_socket_name = "unix:path=" + socket_path + "/dbus-socket"
|
||
|
my_interface_name = "com.meego.inputmethod.uiserver1"
|
||
|
client_interface_name = "com.meego.inputmethod.inputcontext1"
|
||
|
|
||
|
idle_timeout = 15 # seconds, short value for testing
|
||
|
|
||
|
ClientInterface = ravel.def_proxy_interface \
|
||
|
(
|
||
|
ravel.INTERFACE.CLIENT,
|
||
|
name = "ClientInterface",
|
||
|
introspected =
|
||
|
Introspection.Interface
|
||
|
(
|
||
|
name = client_interface_name,
|
||
|
methods =
|
||
|
[
|
||
|
Introspection.Interface.Method
|
||
|
(
|
||
|
name = "setLanguage",
|
||
|
args =
|
||
|
[
|
||
|
Introspection.Interface.Method.Arg
|
||
|
(
|
||
|
name = "lang",
|
||
|
type = dbus.BasicType(dbus.TYPE.STRING),
|
||
|
direction = Introspection.DIRECTION.IN
|
||
|
),
|
||
|
],
|
||
|
),
|
||
|
|
||
|
Introspection.Interface.Method
|
||
|
(
|
||
|
name = "setRedirectKeys",
|
||
|
args =
|
||
|
[
|
||
|
Introspection.Interface.Method.Arg
|
||
|
(
|
||
|
name = "redi",
|
||
|
type = dbus.BasicType(dbus.TYPE.BOOLEAN),
|
||
|
direction = Introspection.DIRECTION.IN
|
||
|
),
|
||
|
],
|
||
|
),
|
||
|
|
||
|
Introspection.Interface.Method
|
||
|
(
|
||
|
name = "updateInputMethodArea",
|
||
|
args =
|
||
|
[
|
||
|
Introspection.Interface.Method.Arg
|
||
|
(
|
||
|
name = "x",
|
||
|
type = dbus.BasicType(dbus.TYPE.INT32),
|
||
|
direction = Introspection.DIRECTION.IN
|
||
|
),
|
||
|
Introspection.Interface.Method.Arg
|
||
|
(
|
||
|
name = "y",
|
||
|
type = dbus.BasicType(dbus.TYPE.INT32),
|
||
|
direction = Introspection.DIRECTION.IN
|
||
|
),
|
||
|
Introspection.Interface.Method.Arg
|
||
|
(
|
||
|
name = "width",
|
||
|
type = dbus.BasicType(dbus.TYPE.INT32),
|
||
|
direction = Introspection.DIRECTION.IN
|
||
|
),
|
||
|
Introspection.Interface.Method.Arg
|
||
|
(
|
||
|
name = "height",
|
||
|
type = dbus.BasicType(dbus.TYPE.INT32),
|
||
|
direction = Introspection.DIRECTION.IN
|
||
|
),
|
||
|
],
|
||
|
),
|
||
|
|
||
|
Introspection.Interface.Method
|
||
|
(
|
||
|
name = "commitString",
|
||
|
args =
|
||
|
[
|
||
|
Introspection.Interface.Method.Arg
|
||
|
(
|
||
|
name = "string",
|
||
|
type = dbus.BasicType(dbus.TYPE.STRING),
|
||
|
direction = Introspection.DIRECTION.IN
|
||
|
),
|
||
|
Introspection.Interface.Method.Arg
|
||
|
(
|
||
|
name = "replacementStart",
|
||
|
type = dbus.BasicType(dbus.TYPE.INT32),
|
||
|
direction = Introspection.DIRECTION.IN
|
||
|
),
|
||
|
Introspection.Interface.Method.Arg
|
||
|
(
|
||
|
name = "replacementLength",
|
||
|
type = dbus.BasicType(dbus.TYPE.INT32),
|
||
|
direction = Introspection.DIRECTION.IN
|
||
|
),
|
||
|
Introspection.Interface.Method.Arg
|
||
|
(
|
||
|
name = "cursorPos",
|
||
|
type = dbus.BasicType(dbus.TYPE.INT32),
|
||
|
direction = Introspection.DIRECTION.IN
|
||
|
),
|
||
|
],
|
||
|
),
|
||
|
],
|
||
|
),
|
||
|
is_async = True
|
||
|
)
|
||
|
|
||
|
useless_objects_bus_name = "org.maliit.server"
|
||
|
useless_objects_iface_name = "org.maliit.Server.Address"
|
||
|
|
||
|
@ravel.interface(ravel.INTERFACE.SERVER, name = useless_objects_iface_name)
|
||
|
class UselessObjectServer :
|
||
|
|
||
|
__slots__ = ("bus",)
|
||
|
|
||
|
def __init__(self, bus) :
|
||
|
self.bus = bus
|
||
|
#end __init__
|
||
|
|
||
|
@ravel.propgetter \
|
||
|
(
|
||
|
name = "address",
|
||
|
type = dbus.BasicType(dbus.TYPE.STRING),
|
||
|
change_notification = dbus.Introspection.PROP_CHANGE_NOTIFICATION.NEW_VALUE,
|
||
|
)
|
||
|
def get_address(self) :
|
||
|
return "unix:path=" + socket_path + "/dbus-socket"
|
||
|
#end get_address
|
||
|
|
||
|
object_created = ravel.def_signal_stub \
|
||
|
(
|
||
|
name = "object_created",
|
||
|
in_signature = [],
|
||
|
)
|
||
|
|
||
|
object_deleted = ravel.def_signal_stub \
|
||
|
(
|
||
|
name = "object_deleted",
|
||
|
in_signature = [],
|
||
|
)
|
||
|
|
||
|
#end UselessObjectServer
|
||
|
|
||
|
background_tasks = set()
|
||
|
def run_in_background(invocation):
|
||
|
task = asyncio.create_task(invocation)
|
||
|
background_tasks.add(task)
|
||
|
task.add_done_callback(background_tasks.discard)
|
||
|
|
||
|
@ravel.interface(ravel.INTERFACE.SERVER, name = my_interface_name)
|
||
|
class DirectConnectServer :
|
||
|
__slots__ = ("bus", "_last_request", "intf", "wayland")
|
||
|
|
||
|
def __init__(self, bus, wayland) :
|
||
|
self.bus = bus
|
||
|
self.wayland = wayland
|
||
|
|
||
|
self.intf = ClientInterface \
|
||
|
(
|
||
|
connection = self.bus.connection,
|
||
|
dest = "com.meego.inputmethod.inputcontext1", # no D-Bus daemon to care
|
||
|
)["/com/meego/inputmethod/inputcontext"]
|
||
|
#end __init__
|
||
|
|
||
|
# @ravel.method \
|
||
|
# (
|
||
|
# name = "reset",
|
||
|
# in_signature = "",
|
||
|
# out_signature = "",
|
||
|
# arg_keys = [],
|
||
|
# )
|
||
|
# def reset(self) :
|
||
|
# print("got reset() message")
|
||
|
# #end reset
|
||
|
|
||
|
@ravel.method \
|
||
|
(
|
||
|
name = "updateWidgetInformation",
|
||
|
in_signature = "a{sv}b",
|
||
|
out_signature = "",
|
||
|
arg_keys = ["vardict", "boolean"],
|
||
|
)
|
||
|
def updateWidgetInformation(self, vardict, boolean) :
|
||
|
# print("got updateWidgetInformation() message")
|
||
|
# print(vardict)
|
||
|
surrounding_text = vardict["surroundingText"][1]
|
||
|
cursor_position = vardict["cursorPosition"][1]
|
||
|
|
||
|
self.wayland["text_input"].set_surrounding_text(surrounding_text, cursor_position, cursor_position)
|
||
|
self.wayland["text_input"].commit()
|
||
|
self.wayland["display"].flush()
|
||
|
#end updateWidgetInformation
|
||
|
|
||
|
# @ravel.method \
|
||
|
# (
|
||
|
# name = "activateContext",
|
||
|
# in_signature = "",
|
||
|
# out_signature = "",
|
||
|
# arg_keys = [],
|
||
|
# )
|
||
|
# def activateContext(self) :
|
||
|
# print("got activateContext() message")
|
||
|
# #end activateContext
|
||
|
|
||
|
@ravel.method \
|
||
|
(
|
||
|
name = "appOrientationChanged",
|
||
|
in_signature = "i",
|
||
|
out_signature = "",
|
||
|
arg_keys = ["orientation"],
|
||
|
)
|
||
|
def appOrientationChanged(self, orientation) :
|
||
|
print("got appOrientationChanged() message")
|
||
|
#end appOrientationChanged
|
||
|
|
||
|
@ravel.method \
|
||
|
(
|
||
|
name = "showInputMethod",
|
||
|
in_signature = "",
|
||
|
out_signature = "",
|
||
|
arg_keys = [],
|
||
|
)
|
||
|
def showInputMethod(self) :
|
||
|
# print("got showInputMethod() message, setting input size")
|
||
|
|
||
|
self.wayland["text_input"].set_surrounding_text("magictext", 0, 0)
|
||
|
self.wayland["display"].flush()
|
||
|
|
||
|
self.wayland["text_input"].set_content_type(0, 0)
|
||
|
self.wayland["text_input"].enable()
|
||
|
self.wayland["text_input"].commit()
|
||
|
self.wayland["display"].flush()
|
||
|
|
||
|
run_in_background(self.intf.updateInputMethodArea(0, 1360, 1080, 670))
|
||
|
#end showInputMethod
|
||
|
|
||
|
@ravel.method \
|
||
|
(
|
||
|
name = "hideInputMethod",
|
||
|
in_signature = "",
|
||
|
out_signature = "",
|
||
|
arg_keys = [],
|
||
|
)
|
||
|
def hideInputMethod(self) :
|
||
|
# print("got hideInputMethod() message, setting input size")
|
||
|
|
||
|
self.wayland["text_input"].disable()
|
||
|
self.wayland["text_input"].commit()
|
||
|
self.wayland["display"].flush()
|
||
|
|
||
|
run_in_background(self.intf.updateInputMethodArea(0, 0, 0, 0))
|
||
|
#end hideInputMethod
|
||
|
|
||
|
# @ravel.method \
|
||
|
# (
|
||
|
# name = "setGlobalCorrectionEnabled",
|
||
|
# in_signature = "b",
|
||
|
# out_signature = "",
|
||
|
# arg_keys = ["enabled"],
|
||
|
# )
|
||
|
# def setGlobalCorrectionEnabled(self, enabled) :
|
||
|
# print("got setGlobalCorrectionEnabled() message")
|
||
|
# #end setGlobalCorrectionEnabled
|
||
|
|
||
|
# @ravel.method \
|
||
|
# (
|
||
|
# name = "setDetectableAutoRepeat",
|
||
|
# in_signature = "b",
|
||
|
# out_signature = "",
|
||
|
# arg_keys = ["detectable"],
|
||
|
# )
|
||
|
# def setDetectableAutoRepeat(self, detectable) :
|
||
|
# print("got setDetectableAutoRepeat() message")
|
||
|
# #end setDetectableAutoRepeat
|
||
|
|
||
|
#end DirectConnectServer
|
||
|
|
||
|
def handle_enter(text_input, surface):
|
||
|
# print("got enter event for surface ")
|
||
|
return 0
|
||
|
|
||
|
def handle_leave(text_input, surface):
|
||
|
# print("got leave event for surface ")
|
||
|
return 0
|
||
|
|
||
|
def handle_preedit_string(text_input, string, cursor_begin, cursor_end):
|
||
|
# print("got preedit str ")
|
||
|
return 0
|
||
|
|
||
|
def handle_commit_string(text_input, string):
|
||
|
# print("commit str: " + string + " sending to dbus")
|
||
|
wayland = text_input.user_data
|
||
|
|
||
|
run_in_background(wayland["dbus_server"].intf.commitString(string, 0, 0, 0))
|
||
|
|
||
|
return 0
|
||
|
|
||
|
def handle_delete_surrounding_text(text_input, before_len, after_len):
|
||
|
# print("got delete surrounding text")
|
||
|
return 0
|
||
|
|
||
|
def handle_done(text_input, serial):
|
||
|
# print("got a done event serial " + str(serial))
|
||
|
return 0
|
||
|
|
||
|
def handle_seat_capabilities(wl_seat, capabilities):
|
||
|
wayland = wl_seat.user_data
|
||
|
|
||
|
if "text_input_manager" not in wayland:
|
||
|
raise Exception("text_input_manager protocol is not supported by compositor")
|
||
|
|
||
|
wayland["text_input"] = wayland["text_input_manager"].get_text_input(wl_seat)
|
||
|
wayland["text_input"].user_data = wayland
|
||
|
wayland["text_input"].dispatcher["enter"] = handle_enter
|
||
|
wayland["text_input"].dispatcher["leave"] = handle_leave
|
||
|
# wayland["text_input"].dispatcher["preedit_string"] = handle_preedit_string
|
||
|
wayland["text_input"].dispatcher["commit_string"] = handle_commit_string
|
||
|
wayland["text_input"].dispatcher["delete_surrounding_text"] = handle_delete_surrounding_text
|
||
|
wayland["text_input"].dispatcher["done"] = handle_done
|
||
|
|
||
|
wayland["display"].flush()
|
||
|
|
||
|
return 1
|
||
|
|
||
|
def handle_registry_global(wl_registry, id_num, iface_name, version):
|
||
|
wayland = wl_registry.user_data
|
||
|
if iface_name == "wl_seat":
|
||
|
wayland["seat"] = wl_registry.bind(id_num, WlSeat, version)
|
||
|
wayland["seat"].user_data = wayland
|
||
|
wayland["seat"].dispatcher["capabilities"] = handle_seat_capabilities
|
||
|
elif iface_name == "zwp_text_input_manager_v3":
|
||
|
wayland["text_input_manager"] = wl_registry.bind(id_num, ZwpTextInputManagerV3, version)
|
||
|
|
||
|
wayland["display"].flush()
|
||
|
|
||
|
return 1
|
||
|
|
||
|
async def setup_dbus(wayland):
|
||
|
listen = ravel.Server(address = my_socket_name)
|
||
|
clients = {}
|
||
|
|
||
|
@ravel.signal \
|
||
|
(
|
||
|
in_signature = "",
|
||
|
bus_keyword = "conn"
|
||
|
)
|
||
|
def connection_terminated(conn) :
|
||
|
# handler for signal sent by libdbus when client disconnects.
|
||
|
print("connection from PID %s terminated\n" % conn.connection.unix_process_id)
|
||
|
del clients[id(conn)]
|
||
|
#end connection_terminated
|
||
|
|
||
|
while True :
|
||
|
conn = await listen.await_new_connection(timeout = idle_timeout / 3)
|
||
|
if conn != None :
|
||
|
print("new connection on DBus")
|
||
|
|
||
|
server = DirectConnectServer(conn, wayland)
|
||
|
wayland["dbus_server"] = server
|
||
|
|
||
|
conn.register \
|
||
|
(
|
||
|
path = "/com/meego/inputmethod/uiserver1",
|
||
|
fallback = True,
|
||
|
interface = server
|
||
|
)
|
||
|
conn.listen_signal \
|
||
|
(
|
||
|
path = "/",
|
||
|
fallback = True,
|
||
|
interface = DBUS.INTERFACE_LOCAL,
|
||
|
name = "Disconnected",
|
||
|
func = connection_terminated
|
||
|
)
|
||
|
clients[id(conn)] = conn
|
||
|
|
||
|
await server.intf.setLanguage('')
|
||
|
#end if
|
||
|
#end while
|
||
|
|
||
|
async def run_dbus_loop(wayland):
|
||
|
server = await setup_dbus(wayland)
|
||
|
|
||
|
display = Display()
|
||
|
display.connect()
|
||
|
|
||
|
loop = asyncio.new_event_loop()
|
||
|
loop.add_reader(display.get_fd(), lambda: display.dispatch(block=True))
|
||
|
|
||
|
wayland = {}
|
||
|
|
||
|
wayland["display"] = display
|
||
|
wayland["registry"] = wayland["display"].get_registry()
|
||
|
wayland["registry"].user_data = wayland
|
||
|
wayland["registry"].dispatcher["global"] = handle_registry_global
|
||
|
|
||
|
wayland["display"].flush()
|
||
|
|
||
|
bus = ravel.session_bus()
|
||
|
bus.attach_asyncio(loop)
|
||
|
|
||
|
bus.request_name \
|
||
|
(
|
||
|
bus_name = useless_objects_bus_name,
|
||
|
flags = DBUS.NAME_FLAG_DO_NOT_QUEUE
|
||
|
)
|
||
|
bus.register \
|
||
|
(
|
||
|
path = "/org/maliit/server",
|
||
|
fallback = True,
|
||
|
interface = UselessObjectServer(bus)
|
||
|
)
|
||
|
|
||
|
loop.create_task(run_dbus_loop(wayland))
|
||
|
|
||
|
loop.run_forever()
|