Files
strans/tests/ibus_live_test.c
Hojun-Cho 921e632eae ibus: the text before the cursor, and the runes to take back
RequireSurroundingText asks the client to send its text; it arrives
through SetSurroundingText as an IBusText and a cursor counted in
runes, and DeleteSurroundingText asks for runes back before the commit
that replaces them.  The official libibus client now proves the whole
turn: 한자 typed, the Hanja key, Enter, and 漢字 arrives with the 한
taken away.

A client that has set EffectivePostProcessKeyEvent is never asked for
its text.  It reads the key's commits back after ProcessKeyEvent
returns, and a DeleteSurroundingText signal is not one of the things
that reply can carry, so the deletion would land after the text it was
meant to make room for and eat the wrong runes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 14:31:44 +09:00

263 lines
6.2 KiB
C

#define _GNU_SOURCE
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "live.h"
#include "livebus.h"
enum
{
Maxconnections = 64,
};
static int
invalidcall(DBusConnection *conn, char *path, char *member)
{
DBusMessage *reply;
const char *name;
int ok;
reply = sendcall(conn, contextcall(path, member), member);
if(reply == NULL)
return 0;
name = dbus_message_get_error_name(reply);
ok = dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR &&
name != NULL && strcmp(name, DBUS_ERROR_INVALID_ARGS) == 0;
if(!ok)
fail("malformed %s returned %s, expected %s", member,
name != NULL ? name : "a non-error reply",
DBUS_ERROR_INVALID_ARGS);
dbus_message_unref(reply);
return ok;
}
/* libibus destroys through the Service interface; the path must go away. */
static int
destroyed(DBusConnection *conn, char *path)
{
DBusMessage *m, *reply;
const char *name;
int ok;
m = dbus_message_new_method_call("org.freedesktop.IBus", path,
"org.freedesktop.IBus.Service", "Destroy");
reply = callret(conn, m, "Service.Destroy");
if(reply == NULL)
return 0;
dbus_message_unref(reply);
reply = sendcall(conn, contextcall(path, "FocusIn"), "FocusIn");
if(reply == NULL)
return 0;
name = dbus_message_get_error_name(reply);
ok = name != NULL && strcmp(name, DBUS_ERROR_UNKNOWN_OBJECT) == 0;
if(!ok)
fail("context survived Service.Destroy");
dbus_message_unref(reply);
return ok;
}
static int
overflowrejected(char *address)
{
DBusConnection *conn;
DBusError err;
struct pollfd pfd;
int fd, n, ok, timeout;
int64_t deadline;
dbus_error_init(&err);
conn = dbus_connection_open_private(address, &err);
if(conn == NULL){
ok = dbus_error_is_set(&err);
dbus_error_free(&err);
return ok || fail("overflow connection failed without a D-Bus error");
}
dbus_error_free(&err);
dbus_connection_set_exit_on_disconnect(conn, FALSE);
if(!dbus_connection_get_unix_fd(conn, &fd)){
closebus(&conn);
return fail("overflow connection has no Unix descriptor");
}
ok = 0;
deadline = nowms() + Calltimeout;
for(;;){
if(!dbus_connection_get_is_connected(conn)){
ok = 1;
break;
}
timeout = leftms(deadline);
if(timeout == 0){
fail("65th IBus connection was not rejected");
break;
}
pfd.fd = fd;
pfd.events = POLLIN;
pfd.revents = 0;
n = poll(&pfd, 1, timeout);
if(n < 0 && errno == EINTR)
continue;
if(n < 0){
fail("poll overflow IBus connection: %s", strerror(errno));
break;
}
if(n == 0)
continue;
if(pfd.revents & POLLNVAL){
fail("overflow IBus descriptor became invalid");
break;
}
dbus_connection_read_write_dispatch(conn, 0);
}
closebus(&conn);
return ok;
}
static int
runcontract(char *address)
{
static char *bad[] = {
"ProcessKeyEvent",
"SetEngine",
"SetCapabilities",
"SetCursorLocation",
"SetCursorLocationRelative",
"SetSurroundingText",
};
DBusConnection *conn;
char path[96];
int i, ok;
conn = openbus(address, "contract");
if(conn == NULL)
return 0;
ok = hello(conn, NULL, 0, "contract Hello") &&
createcontext(conn, path, sizeof path, "contract context");
for(i = 0; ok && i < (int)(sizeof bad / sizeof bad[0]); i++)
ok = invalidcall(conn, path, bad[i]);
ok = ok && destroyed(conn, path);
closebus(&conn);
return ok;
}
static int
runcapacity(char *address)
{
DBusConnection *conn[Maxconnections];
int i, ok;
for(i = 0; i < Maxconnections; i++)
conn[i] = NULL;
ok = 0;
for(i = 0; i < Maxconnections; i++){
conn[i] = openbus(address, "capacity");
if(conn[i] == NULL || !hello(conn[i], NULL, 0, "capacity Hello"))
goto out;
}
if(!overflowrejected(address))
goto out;
closebus(&conn[Maxconnections-1]);
if(!createcontext(conn[0], NULL, 0, "capacity context"))
goto out;
conn[Maxconnections-1] = openbus(address, "replacement");
if(conn[Maxconnections-1] == NULL ||
!hello(conn[Maxconnections-1], NULL, 0, "replacement Hello") ||
!createcontext(conn[Maxconnections-1], NULL, 0, "replacement context"))
goto out;
ok = 1;
out:
for(i = 0; i < Maxconnections; i++)
closebus(&conn[i]);
return ok;
}
static int
runclient(Live *l, char *address, char *program)
{
pid_t pid, n;
int status;
int64_t deadline;
pid = fork();
if(pid < 0)
return fail("fork official libibus client: %s", strerror(errno));
if(pid == 0){
if(setenv("XDG_RUNTIME_DIR", l->runtime, 1) < 0 ||
setenv("XDG_CONFIG_HOME", l->config, 1) < 0 ||
setenv("HOME", l->home, 1) < 0 ||
unsetenv("IBUS_ADDRESS_FILE") < 0)
_exit(126);
execl(program, program, address, (char*)0);
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
_exit(127);
}
deadline = nowms() + Starttimeout;
for(;;){
n = waitpid(pid, &status, WNOHANG);
if(n == pid)
break;
if(n < 0 && errno == EINTR)
continue;
if(n < 0)
return fail("wait official libibus client: %s", strerror(errno));
if(leftms(deadline) == 0){
fail("official libibus client timed out");
kill(pid, SIGKILL);
do
n = waitpid(pid, &status, 0);
while(n < 0 && errno == EINTR);
return 0;
}
pausems(10);
}
if(!WIFEXITED(status) || WEXITSTATUS(status) != 0)
return fail("official libibus client exited with wait status %#x",
status);
return 1;
}
int
main(int argc, char **argv)
{
Daemon daemon;
Live live;
char address[512];
int capacity, ok;
testname = "ibus_live_test";
capacity = argc == 4 && strcmp(argv[1], "--capacity") == 0;
if(argc != 4){
fprintf(stderr, "usage: ibus_live_test strans mapdir libibus-client\n");
fprintf(stderr, " ibus_live_test --capacity strans mapdir\n");
return 2;
}
daemoninit(&daemon, "daemon");
ok = livesetup(&live, "ibus") &&
startdaemon(&live, &daemon, argv[capacity ? 2 : 1],
argv[capacity ? 3 : 2]) &&
waitready(&live, &daemon, address, sizeof address);
if(ok)
ok = capacity ? runcapacity(address) :
runclient(&live, address, argv[3]) && runcontract(address);
if(!ok)
showerrors(&daemon);
if(!stopdaemon(&daemon))
ok = 0;
if(!closeerrors(&daemon))
ok = 0;
if(!liveclean(&live))
ok = 0;
if(!ok)
return 1;
printf(capacity ? "ibus connection capacity: ok\n" :
"ibus official client and malformed call: ok\n");
return 0;
}