public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/ibus] autotool: Replace GNOME Xorg with GNOME Wayland in CI
@ 2026-05-31  2:08 Takao Fujiwara
  0 siblings, 0 replies; only message in thread
From: Takao Fujiwara @ 2026-05-31  2:08 UTC (permalink / raw)
  To: git-commits

A new commit has been pushed.

Repo   : rpms/ibus
Branch : autotool
Commit : cb19883e9b523e591ad874827b2a1cf3eb9d74ca
Author : Takao Fujiwara <tfujiwar@redhat.com>
Date   : 2024-07-27T20:35:24+09:00
Stats  : +3913/-1 in 2 file(s)
URL    : https://src.fedoraproject.org/rpms/ibus/c/cb19883e9b523e591ad874827b2a1cf3eb9d74ca?branch=autotool

Log:
Replace GNOME Xorg with GNOME Wayland in CI

---
diff --git a/ibus-HEAD.patch b/ibus-HEAD.patch
index d2f5082..bafefcd 100644
--- a/ibus-HEAD.patch
+++ b/ibus-HEAD.patch
@@ -66,3 +66,3911 @@ index 7531a4b2..a8e41a33 100644
 -- 
 2.45.0
 
+From a52861385bb5d15598f6c3c3d86c7a9ee19f140a Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Sat, 27 Jul 2024 19:00:53 +0900
+Subject: [PATCH 1/6] src/tests: Enable CI in GNOME Wayland
+
+- Implement headless desktop to test Wayland sessions
+- Implement ibus-desktop-testing-autostart to be executed in XDG
+  autostart in GNOME Wayland
+- Move shared codes to ibus-desktop-testing-module between
+  ibus-desktop-testing-runner and ibus-desktop-testing-autostart
+- Add --no-systemd option to enable the previous testing frame with running
+  gnome-session directly
+- Rename --desktop option to --session
+- Move save_screen() from runner to module
+- Copy gnome-shell service file to user home for headless & virtual-monitor
+- Add --delete-tests option to clean up tests
+- Add AutostartCondition=if-exists in XDG autostart desktop file
+- Add 60 secs in ibus-compose-locales for systemd desktop sessions
+- Quote file names as much as possible.
+- Check file existant and not to use "rm -f" as much as possible
+- Do not set GTK_IM_MODULE=ibus for Wayland
+
+BUG=https://github.com/ibus/ibus/pull/2657
+---
+ src/tests/Makefile.am                       |  23 +-
+ src/tests/ibus-compose-locales.in           |  16 +-
+ src/tests/ibus-desktop-testing-autostart    |  57 --
+ src/tests/ibus-desktop-testing-autostart.in |  83 ++
+ src/tests/ibus-desktop-testing-module       | 876 ++++++++++++++++++++
+ src/tests/ibus-desktop-testing-runner.in    | 495 +----------
+ src/tests/ibus-desktop-testing.desktop.in   |   2 +-
+ 7 files changed, 1034 insertions(+), 518 deletions(-)
+ delete mode 100755 src/tests/ibus-desktop-testing-autostart
+ create mode 100755 src/tests/ibus-desktop-testing-autostart.in
+ create mode 100755 src/tests/ibus-desktop-testing-module
+
+diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
+index b30a39fb..6c4c86cf 100644
+--- a/src/tests/Makefile.am
++++ b/src/tests/Makefile.am
+@@ -106,6 +106,7 @@ test_sourcesdir = $(datadir)/installed-tests/ibus
+ 
+ CLEANFILES += \
+     $(test_metas) \
++    ibus-desktop-testing-autostart \
+     ibus-desktop-testing-runner \
+     org.freedesktop.IBus.Desktop.Testing.desktop \
+     $(NULL)
+@@ -119,7 +120,10 @@ CLEANFILES += \
+     $(NULL)
+ endif
+ test_execsdir = $(libexecdir)/installed-tests/ibus
+-libexec_SCRIPTS = ibus-desktop-testing-autostart
++libexec_SCRIPTS = \
++  ibus-desktop-testing-autostart \
++  ibus-desktop-testing-module \
++  $(NULL)
+ 
+ test_frame_DATA = org.freedesktop.IBus.Desktop.Testing.desktop
+ test_framedir = $(pkgdatadir)/tests
+@@ -142,9 +146,21 @@ ibus-compose-locales: ibus-compose-locales.in
+ 	mv $@.tmp $@; \
+ 	$(NULL)
+ 
++ibus-desktop-testing-autostart: ibus-desktop-testing-autostart.in
++	INSTALLEDDIR=$(datadir)/installed-tests; \
++	sed -e "s|@DATADIR[@]|$(datadir)|g" \
++	    -e "s|@INSTALLEDDIR[@]|$$INSTALLEDDIR|g" \
++	    -e "s|@LIBEXECDIR[@]|$(libexecdir)|g" \
++	    $< > $@.tmp; \
++	mv $@.tmp $@; \
++	$(NULL)
++
+ ibus-desktop-testing-runner: ibus-desktop-testing-runner.in
+ 	INSTALLEDDIR=$(datadir)/installed-tests; \
+-	sed -e "s|@INSTALLEDDIR[@]|$$INSTALLEDDIR|g" $< > $@.tmp; \
++	sed -e "s|@DATADIR[@]|$(datadir)|g" \
++	    -e "s|@INSTALLEDDIR[@]|$$INSTALLEDDIR|g" \
++	    -e "s|@LIBEXECDIR[@]|$(libexecdir)|g" \
++	    $< > $@.tmp; \
+ 	mv $@.tmp $@; \
+ 	$(NULL)
+ 
+@@ -157,7 +173,8 @@ EXTRA_DIST = \
+     ibus-compose.env \
+     ibus-compose-locales.in \
+     ibus-desktop-testing.desktop.in \
+-    ibus-desktop-testing-autostart \
++    ibus-desktop-testing-autostart.in \
++    ibus-desktop-testing-module \
+     ibus-desktop-testing-runner.in \
+     $(NULL)
+ 
+diff --git a/src/tests/ibus-compose-locales.in b/src/tests/ibus-compose-locales.in
+index b36165fe..7a133ce0 100755
+--- a/src/tests/ibus-compose-locales.in
++++ b/src/tests/ibus-compose-locales.in
+@@ -6,7 +6,8 @@ BUILDDIR=`dirname $0`
+ 
+ export IBUS_COMPOSE_CACHE_DIR=$PWD
+ 
+-retval=0
++RETVAL=0
++INITED=0
+ # Deleted for var in `cat *.env` because IFS=$'\n' is not supported in POSIX sh
+ while read var
+ do
+@@ -14,19 +15,26 @@ do
+     if [ "x$IS_COMMENT" != x ] ; then
+         continue
+     fi
++    while [ x"$IBUS_DAEMON_WITH_SYSTEMD" != x ] && [ $INITED -lt 6 ] ; do
++        echo "Waiting for ${INITED}0 secs till 60 secs"
++        sleep 10
++        INITED=`expr $INITED + 1`
++    done
+     # Use $* instead of $@ not to mix strings and integers
+     echo "# Starting $var $BUILDDIR/ibus-compose $SRCDIR $*"
+     # Need to enclose $@ with double quotes not to split the array.
+     env $var $BUILDDIR/ibus-compose $SRCDIR "$@"
+-    retval=`expr $retval + $?`
+-    echo "# Finished $var $BUILDDIR/ibus-compose $SRCDIR $* with $retval"
++    RETVAL=`expr $RETVAL + $?`
++    echo "# Finished $var $BUILDDIR/ibus-compose $SRCDIR $* with $RETVAL"
+ 
+     CACHE_FILES=`ls *.cache`
+     if [ x"$CACHE_FILES" != x ] ; then
+         echo "Clean $CACHE_FILES"
+         rm $CACHE_FILES
+     fi
++    # Need to wait for 1 sec not to be freezed with gnome-shell in Wayland
++    sleep 1
+ done << EOF_ENVS
+ `cat $SRCDIR/ibus-compose.env`
+ EOF_ENVS
+-exit $retval
++exit $RETVAL
+diff --git a/src/tests/ibus-desktop-testing-autostart b/src/tests/ibus-desktop-testing-autostart
+deleted file mode 100755
+index 1e1eb180..00000000
+--- a/src/tests/ibus-desktop-testing-autostart
++++ /dev/null
+@@ -1,57 +0,0 @@
+-#!/bin/sh
+-# -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*-
+-# vim:set noet ts=4:
+-#
+-# ibus - The Input Bus
+-#
+-# Copyright (c) 2021 Takao Fujiwara <takao.fujiwara1@gmail.com>
+-# Copyright (c) 2021 Red Hat, Inc.
+-#
+-# This program is free software; you can redistribute it and/or modify
+-# it under the terms of the GNU General Public License as published by
+-# the Free Software Foundation; either version 2 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 General Public License for more details.
+-#
+-# You should have received a copy of the GNU 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.
+-
+-TEST_LOG=
+-COMMANDS='
+-id
+-echo $DISPLAY
+-pwd
+-pstree -asp $$
+-gsettings list-recursively org.gnome.shell
+-rpm -q gnome-shell-extension-no-overview gnome-shell gnome-session
+-ps -ef | grep ibus | grep -v grep
+-ibus address
+-'
+-
+-if [ $# -gt 0 ] ; then
+-    TEST_LOG=$1
+-fi
+-
+-run_test()
+-{
+-while read cmd ; do
+-    if [ x"$cmd" = x ] ; then
+-        continue
+-    fi
+-    echo "# $cmd"
+-    eval "$cmd"
+-done << EOF_COMMANDS
+-`echo "$COMMANDS"`
+-EOF_COMMANDS
+-}
+-
+-if [ x"$TEST_LOG" = x ] ; then
+-    run_test
+-else
+-    run_test 2>>$TEST_LOG 1>>$TEST_LOG
+-fi
+diff --git a/src/tests/ibus-desktop-testing-autostart.in b/src/tests/ibus-desktop-testing-autostart.in
+new file mode 100755
+index 00000000..d50354df
+--- /dev/null
++++ b/src/tests/ibus-desktop-testing-autostart.in
+@@ -0,0 +1,83 @@
++#!/bin/sh
++# -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*-
++# vim:set noet ts=4:
++#
++# ibus - The Input Bus
++#
++# Copyright (c) 2021-2024 Takao Fujiwara <takao.fujiwara1@gmail.com>
++# Copyright (c) 2021 Red Hat, Inc.
++#
++# This program is free software; you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation; either version 2 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 General Public License for more details.
++#
++# You should have received a copy of the GNU 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.
++
++
++PROGNAME=`basename $0`
++TEST_CASE_DIR="@INSTALLEDDIR@"
++MODULE_SH='ibus-desktop-testing-module'
++MODULE_SH_PATH="@LIBEXECDIR@/$MODULE_SH"
++TEST_LOG="test-autostart.log"
++TESTING_RUNNER=""
++
++
++if test -f $MODULE_SH_PATH ; then
++  . $MODULE_SH_PATH
++elif test -f $(dirname $0)/$MODULE_SH ; then
++  . $(dirname $0)/$MODULE_SH
++else
++  echo "Not found $MODULE_SH"
++  exit 1
++fi
++
++
++usage()
++{
++    $ECHO -e \
++"This test suite is called from /etc/xdg/autostart for systemd sessions\n"     \
++"$PROGNAME [OPTIONS…]\n"                                                       \
++"\n"                                                                           \
++"OPTIONS:\n"                                                                   \
++"-h, --help                       This help\n"                                 \
++"-v, --version                    Show version\n"                              \
++"-b, --builddir=BUILDDIR          Set the BUILDDIR\n"                          \
++"-s, --srcdir=SOURCEDIR           Set the SOURCEDIR\n"                         \
++"-V, --verbose                    Verbose log for ibus-daemon\n"               \
++"-t, --tests=\"TESTS...\"           Run TESTS programs which is separated by space\n" \
++"-r, --runner=RUNNER              Run TESTS programs with a test RUNNER.\n"    \
++"                                 RUNNDER = 'gnome', 'default' or ''.\n"       \
++"                                 'default' is an embedded runner.\n"          \
++"                                 '' is no tests.\n"                           \
++"-T, --timeout=TIMEOUT            Set timeout (default TIMEOUT is 300 sec).\n" \
++"-o, --output=OUTPUT_FILE         Output the log to OUTPUT_FILE\n"             \
++"                                 default is $TEST_LOG\n"                      \
++"-O, --result=RESULT_FILE         Output the result to RESULT_FILE\n"          \
++"                                 default is stdout\n"                         \
++"-S, --screendump=DUMP_FILE       Output the screen to DUMP_FILE ('STDOUT' can be stdout)\n" \
++"-e, --envcheck                   Retrieve environment variables\n"            \
++""
++}
++
++
++main()
++{
++    parse_args "$@"
++    init_session
++    check_env
++    save_screen
++    run_test_suite
++    finit
++}
++
++
++# Need to enclose $@ with double quotes not to split the array.
++main "$@"
+diff --git a/src/tests/ibus-desktop-testing-module b/src/tests/ibus-desktop-testing-module
+new file mode 100755
+index 00000000..2d686813
+--- /dev/null
++++ b/src/tests/ibus-desktop-testing-module
+@@ -0,0 +1,876 @@
++#!/bin/sh
++# -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*-
++# vim:set noet ts=4:
++#
++# ibus - The Input Bus
++#
++# Copyright (c) 2018-2024 Takao Fujiwara <takao.fujiwara1@gmail.com>
++# Copyright (c) 2018 Red Hat, Inc.
++#
++# This program is free software; you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation; either version 2 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 General Public License for more details.
++#
++# You should have received a copy of the GNU 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.
++
++
++# POSIX sh has no 'echo -e'
++: ${ECHO:='/usr/bin/echo'}
++# POSIX sh has $UID
++# DASH saves the graves in '``' as characters not to be extracted
++: ${UID:=`id -u`}
++: ${TEST_CASE_DIR='/usr/share/installed-tests'}
++: ${AUTOSTART_DESKTOP_DIR='/usr/share/ibus/tests'}
++
++
++VERSION=0.4
++BUILDDIR="."
++SRCDIR="."
++TEST_LOG_STDOUT=0
++RESULT_LOG=""
++SCREEN_LOG=""
++HAVE_GRAPHICS=1
++VERBOSE=0
++SESSION_COMMAND="dbus-launch --exit-with-session gnome-session"
++SESSION_LANG=
++SESSION_IS_GNOME=1
++GNOME_SHELL_WAYLAND_COMMAND="gnome-shell --wayland --headless --virtual-monitor 1024x768"
++SYSTEMD_SYSTEM_DIR="/usr/lib/systemd/user"
++PID_XORG=0
++PID_GNOME_SESSION=0
++ENABLED_SYSTEMD=1
++TEST_USER=itestuser
++TEST_USER_HOME=/export/home/$TEST_USER
++GDM_CONF=/run/gdm/custom.conf
++AUTOSTART_DESKTOP_FILE="org.freedesktop.IBus.Desktop.Testing.desktop"
++TESTS=""
++TIMEOUT=300
++GREEN='\033[0;32m'
++RED='\033[0;31m'
++NC='\033[0m'
++ENV_CHECK=0
++ENV_COMMANDS='
++id
++pwd
++pstree -asp $$
++gsettings list-recursively org.gnome.shell
++rpm -q gnome-shell-extension-no-overview gnome-shell gnome-session
++ps -ef | grep ibus | grep -v grep
++ibus address
++env
++#dbus-send --session --print-reply --dest=org.gnome.Shell.Introspect /org/gnome/Shell/Introspect org.gnome.Shell.Introspect.GetWindows
++'
++
++
++print_log()
++{
++    if [ x"$RESULT_LOG" != x ] ; then
++        # avoid 'echo -e' before call 'sed'.
++        if [ x"$1" = x'-e' ] ; then
++            shift
++        fi
++        NO_ESCAPE=`echo "$@" | sed -e 's/\\\033\\[0;3.m//g' -e 's/\\\033\\[0m//g'`
++        $ECHO $NO_ESCAPE >> $RESULT_LOG
++    else
++        $ECHO "$@"
++    fi
++}
++
++
++parse_args()
++{
++    # This is GNU getopt. "sudo port getopt" in BSD?
++    ARGS=`getopt -o hvb:s:cVd:t:r:iT:o:O:S:el:D --long \
++help,version,builddir:,srcdir:,no-graphics,verbose,desktop:,session:,tests:,\
++runner:,no-systemd,timeout:,output:,result:,screendump:,envcheck,lang:,\
++delete-tests\
++        -- "$@"`;
++    eval set -- "$ARGS"
++    while [ 1 ] ; do
++        case "$1" in
++        -h | --help )        usage; exit 0;;
++        -v | --version )     $ECHO -e "$VERSION"; exit 0;;
++        -b | --builddir )    BUILDDIR="$2"; shift 2;;
++        -s | --srcdir )      SRCDIR="$2"; shift 2;;
++        -c | --no-graphics ) HAVE_GRAPHICS=0; shift;;
++        -V | --verbose )     VERBOSE=1; shift;;
++        --desktop )          SESSION_COMMAND="$2"
++                             print_log -e "--desktop is deprecated. Use --session instead"
++                             shift 2;;
++        -d | --session )     SESSION_COMMAND="$2"; shift 2;;
++        -t | --tests )       TESTS="$2"; shift 2;;
++        -r | --runner )      TESTING_RUNNER="$2"; shift 2;;
++        -i | --no-systemd )  ENABLED_SYSTEMD=0; shift;;
++        -T | --timeout )     TIMEOUT="$2"; shift 2;;
++        -o | --output )      TEST_LOG="$2"; shift 2;;
++        -O | --result )      RESULT_LOG="$2"; shift 2;;
++        -S | --screendump )  SCREEN_LOG="$2"; shift 2;;
++        -e | --envcheck )    ENV_CHECK=1; shift;;
++        -l | --lang )        SESSION_LANG="$2"; shift 2;;
++        -D | --delete-tests ) delete_test_user; exit 0;;
++        -- )                 shift; break;;
++        * )                  usage; exit 1;;
++        esac
++    done
++    DL='$'
++    if echo "$SESSION_COMMAND" | grep -q -E ".*-with-dbus$DL" ; then
++        SESSION_COMMAND=`echo "$SESSION_COMMAND" | sed -e 's/-with-dbus$//'`
++        SESSION_COMMAND="dbus-launch --exit-with-session $SESSION_COMMAND"
++    fi
++}
++
++
++check_tty()
++{
++    TTY=`tty`
++    if echo $PROGNAME | grep -q runner ; then
++        if [ $ENABLED_SYSTEMD -eq 1 ] && [  $SESSION_IS_GNOME -eq 1 ] ; then
++            if echo "$TTY" | grep -E 'tty|console' ; then
++               print_log -e "${RED}FAIL${NC}: ${RED}ERROR${NC}: Changing runlevel does not support console. Please log into the host with ssh."
++               exit 1
++            fi
++        fi
++        if [ $ENABLED_SYSTEMD -eq 0 ] ; then
++            if echo "$TTY" | grep -E 'pts' ; then
++               print_log -e "Running session with ssh. It might be good to use console instead."
++            fi
++        fi
++        SESSION=`ps -ef | grep session | grep -v grep`
++        if [ x"$SESSION" != x ] ; then
++               print_log -e "${RED}FAIL${NC}: ${RED}ERROR${NC}: Session is running: $SESSION"
++               exit 1
++        fi
++    fi
++}
++
++
++check_env()
++{
++    if test $ENV_CHECK -eq 0 ; then
++        return
++    fi
++    while read cmd ; do
++        if test x"$cmd" = x ; then
++            continue
++        fi
++        if echo "$cmd" | grep -q "^#"; then
++            continue
++        fi
++        echo "# $cmd" 2>>$TEST_LOG 1>>$TEST_LOG
++        eval "$cmd" 2>>$TEST_LOG 1>>$TEST_LOG
++    done << EOF_ENV_COMMANDS
++`echo "$ENV_COMMANDS"`
++EOF_ENV_COMMANDS
++}
++
++
++save_screen_real()
++{
++    SCREEN_PNG="`date '+%Y%m%d%H%M%S'`.png"
++    gnome-screenshot --file=$SCREEN_PNG
++    if test x"$SCREEN_LOG" = xSTDOUT ; then
++        base64 $SCREEN_PNG
++        touch /var/tmp/STDOUT.log
++    else
++        base64 $SCREEN_PNG > $SCREEN_LOG
++    fi
++}
++
++
++save_screen()
++{
++    if test x"$SCREEN_LOG" = x ; then
++        return
++    fi
++    if test x"$SCREEN_LOG" = xSTDOUT ; then
++        if test -f /var/tmp/STDOUT.log ; then
++            rm /var/tmp/STDOUT.log
++        fi
++    else
++        if test -f "$SCREEN_LOG" ; then
++            rm "$SCREEN_LOG"
++        fi
++    fi
++    save_screen_real &
++    while test 1 ; do
++        if test x"$SCREEN_LOG" = xSTDOUT ; then
++            if test -f /var/tmp/STDOUT.log ; then
++                break
++            fi
++        else
++            if test -f "$SCREEN_LOG" ; then
++                break
++            fi
++        fi
++        sleep 1
++    done
++}
++
++
++create_test_user()
++{
++    if grep -q $TEST_USER /etc/passwd; then
++        return;
++    fi
++    useradd -d $TEST_USER_HOME -m -s /bin/bash $TEST_USER
++    pwconv
++    passwd -d $TEST_USER
++}
++
++
++create_autologin_gdm()
++{
++    if test -f $GDM_CONF && grep -q $TEST_USER $GDM_CONF; then
++        return;
++    fi
++    cat > $GDM_CONF << _EOF_GDM_CONF
++[daemon]
++AutomaticLoginEnable=true
++AutomaticLogin=$TEST_USER
++_EOF_GDM_CONF
++}
++
++
++create_xdg_autostart()
++{
++    if test -f "$TEST_USER_HOME/.config/autostart/$AUTOSTART_DESKTOP_FILE" ; then
++        return
++    fi
++    if test ! -d "$TEST_USER_HOME" ; then
++        print_log -e "${RED}FAIL${NC}: ${RED}ERROR${NC}: No $TEST_USER_HOME"
++        exit 1
++    fi
++    desktop_file="$AUTOSTART_DESKTOP_DIR/$AUTOSTART_DESKTOP_FILE"
++    if test ! -f $desktop_file ; then
++        print_log -e "${RED}FAIL${NC}: ${RED}ERROR${NC}: No $desktop_file"
++        exit 1
++    fi
++    mkdir -p "$TEST_USER_HOME/.config/autostart"
++    cp "$desktop_file" "$TEST_USER_HOME/.config/autostart"
++    LINE="AutostartCondition=if-exists $TEST_USER_HOME/.config/autostart/$AUTOSTART_DESKTOP_FILE"
++    echo "$LINE" >> "$TEST_USER_HOME/.config/autostart/$AUTOSTART_DESKTOP_FILE"
++    chown -R "$TEST_USER" "$TEST_USER_HOME/.config"
++}
++
++
++delete_test_user()
++{
++    print_log "Deleting $TEST_USER"
++    pkill -u $TEST_USER
++    if grep -q $TEST_USER /etc/passwd; then
++        userdel -r $TEST_USER
++    fi
++    if test -f $GDM_CONF && grep -q $TEST_USER $GDM_CONF; then
++        rm $GDM_CONF
++    fi
++
++    if echo "$SESSION_COMMAND" | grep -q gnome-session ; then
++        SESSION_IS_GNOME=1
++    else
++        SESSION_IS_GNOME=0
++    fi
++
++    LOGIN_USER=$USER
++    LOGIN_HOME=$HOME
++    if [ $ENABLED_SYSTEMD -eq 1 ] && [  $SESSION_IS_GNOME -eq 1 ] ; then
++        LOGIN_USER=$TEST_USER
++        LOGIN_HOME=$TEST_USER_HOME
++    fi
++
++    print_log "Deleting $LOGIN_USER environments"
++    if test -f "$LOGIN_HOME/.config/gnome-initial-setup-done" ; then
++        rm "$LOGIN_HOME/.config/gnome-initial-setup-done"
++    fi
++    if test -f "$LOGIN_HOME/.config/user-dirs.locale" ; then
++        rm "$LOGIN_HOME/.config/user-dirs.locale"
++    fi
++    if test -f /var/lib/AccountsService/users/$LOGIN_USER ; then
++        rm /var/lib/AccountsService/users/$LOGIN_USER
++    fi
++    sync
++}
++
++
++init_session()
++{
++    if [ "$RESULT_LOG" != "" ] ; then
++        if [ -f $RESULT_LOG ] ; then
++            rm $RESULT_LOG
++        fi
++    fi
++    echo "$TEST_LOG" | grep ':stdout' > /dev/null
++    HAS_STDOUT=$?
++    if [ $HAS_STDOUT -eq 0 ] ; then
++        TEST_LOG=`echo "$TEST_LOG" | sed -e 's|:stdout||'`
++        TEST_LOG_STDOUT=1
++    fi
++    if [ "$TEST_LOG" = "" ] ; then
++        print_log -e "${RED}FAIL${NC}: ${RED}ERROR${NC}: a log file is required to get return value with 'read' command"
++        exit 1
++    elif [ -f $TEST_LOG ] ; then
++        rm $TEST_LOG
++    fi
++
++    if echo "$SESSION_COMMAND" | grep -q gnome-session ; then
++        SESSION_IS_GNOME=1
++    else
++        SESSION_IS_GNOME=0
++    fi
++
++    LOGIN_USER=$USER
++    LOGIN_HOME=$HOME
++    check_tty
++    if [ $ENABLED_SYSTEMD -eq 1 ] && [  $SESSION_IS_GNOME -eq 1 ] ; then
++        SESSION_FILE1="/usr/lib/systemd/system/gnome-headless-session@.service"
++        SESSION_FILE2="/usr/lib/systemd/system/graphical.target"
++        if [ $HAVE_GRAPHICS -eq 0 ] && [ ! -f "$SESSION_FILE1" ] ; then
++            print_log -e "${RED}FAIL${NC}: ${RED}ERROR${NC}: No $SESSION_FILE1: You need to install 'gdm' and 'systemd' package"
++            exit 1
++        fi
++        if [ $HAVE_GRAPHICS -eq 1 ] ; then
++            if [ ! -f "$SESSION_FILE2" ] ; then
++                print_log -e "${RED}FAIL${NC}: ${RED}ERROR${NC}: No $SESSION_FILE2: You need to install 'systemd' package"
++                exit 1
++            fi
++            if [ ! -f /usr/sbin/gdm ] ; then
++                print_log -e "${RED}FAIL${NC}: ${RED}ERROR${NC}: No /usr/sbin/gdm: Currently only gdm is supported for auto login."
++                exit 1
++            fi
++        fi
++        create_test_user
++        create_autologin_gdm
++        create_xdg_autostart
++        LOGIN_USER=$TEST_USER
++        LOGIN_HOME=$TEST_USER_HOME
++    else
++        rm -f $TEST_USER_HOME/.config/autostart/$AUTOSTART_DESKTOP_FILE
++    fi
++
++    if [ ! -f $LOGIN_HOME/.config/gnome-initial-setup-done ] ; then
++        mkdir -p $LOGIN_HOME/.config
++        touch $LOGIN_HOME/.config/gnome-initial-setup-done
++        chown -R $LOGIN_USER $LOGIN_HOME/.config
++    fi
++    IS_SYSTEM_ACCOUNT=false
++    if [ "$LOGIN_USER" = "root" ] ; then
++            IS_SYSTEM_ACCOUNT=true
++    fi
++    if test x"$SESSION_LANG" = x ; then
++        SESSION_LANG=$LANG
++    fi
++    if test -f /var/lib/AccountsService/users/$LOGIN_USER; then
++        sed -i -e "s/\(Language=\).*/\1$SESSION_LANG/" \
++                /var/lib/AccountsService/users/$LOGIN_USER
++    else
++        mkdir -p /var/lib/AccountsService/users
++        cat > /var/lib/AccountsService/users/$LOGIN_USER << _EOF_AS_CONF
++[User]
++Language=$SESSION_LANG
++XSession=gnome
++SystemAccount=$IS_SYSTEM_ACCOUNT
++_EOF_AS_CONF
++    fi
++
++    # Prevent from launching a XDG dialog
++    XDG_LOCALE_FILE="$LOGIN_HOME/.config/user-dirs.locale"
++    if [ -f $XDG_LOCALE_FILE ] ; then
++        XDG_LANG_ORIG=`cat $XDG_LOCALE_FILE`
++        XDG_LANG_NEW=`echo $SESSION_LANG | sed -e 's/\(.*\)\..*/\1/'`
++        if [ "$XDG_LANG_ORIG" != "$XDG_LANG_NEW" ] ; then
++            echo "# Overriding XDG locale $XDG_LANG_ORIG with $XDG_LANG_NEW"
++            echo "$XDG_LANG_NEW" > $XDG_LOCALE_FILE
++        fi
++    fi
++
++    if [ $ENABLED_SYSTEMD -eq 1 ] && [  $SESSION_IS_GNOME -eq 1 ] ; then
++        return
++    fi
++    # `su` command does not run loginctl
++    export XDG_SESSION_TYPE='x11'
++    export XDG_SESSION_CLASS=user
++    # `su` command does not get focus in events without this variable.
++    # Need to restart sshd after set "PermitRootLogin yes" in sshd_config
++    if [ "x$XDG_RUNTIME_DIR" = x ] ; then
++        export XDG_RUNTIME_DIR="/run/user/$UID"
++        is_root_login=`grep "^PermitRootLogin" /etc/ssh/sshd_config | grep yes`
++        if [ "x$ANSIBLE" != x ] && [ "x$is_root_login" = x ] ; then
++            print_log -e "${RED}FAIL${NC}: No permission to get focus-in events in GtkWindow with ansible"
++            echo "su command does not configure necessary login info "         \
++                 "with systemd and GtkWindow cannot receive focus-events "     \
++                 "when ibus-desktop-testing-runner is executed by "            \
++                 "ansible-playbook." >> $TEST_LOG
++            echo "Enabling root login via sshd, restarting sshd, set "         \
++                 "XDG_RUNTIME_DIR can resolve the problem under "              \
++                 "ansible-playbook." >> $TEST_LOG
++            exit 1
++        fi
++    fi
++    #  Do we need XDG_SESSION_ID and XDG_SEAT?
++    #export XDG_CONFIG_DIRS=/etc/xdg
++    #export XDG_SESSION_ID=10
++    #export XDG_SESSION_DESKTOP=gnome
++    #export XDG_SEAT=seat0
++}
++
++
++run_dbus_daemon()
++{
++    if [ $ENABLED_SYSTEMD -eq 1 ] && [  $SESSION_IS_GNOME -eq 1 ] ; then
++        return
++    fi
++    # Use dbus-launch --exit-with-session later instead of --sh-syntax
++    # GNOME uses a unix:abstract address and it effects gsettings set values
++    # in each test case.
++    # TODO: Should we comment out this line?
++    export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$UID/bus"
++}
++
++
++init_gnome()
++{
++    if test $SESSION_IS_GNOME -ne 1 ; then
++        print_log -e "${RED}FAIL${NC}: ${RED}ERROR${NC}: Should not be called"
++        exit 1
++    fi
++    LOGIN_USER=$USER
++    if test $ENABLED_SYSTEMD -eq 1 ; then
++        LOGIN_USER=$TEST_USER
++    fi
++    # gsettings set command needs dconf-service with the same $DISPLAY
++    pkill dconf-service
++    # G_MESSAGES_DEBUG=all or G_MESSAGES_DEBUG=GLib-GIO-DEBUG would append
++    # debug messages to gsettings output and could not get the result correctly.
++    backup_G_MESSAGES_DEBUG="$G_MESSAGES_DEBUG"
++    unset G_MESSAGES_DEBUG
++    # Disable Tour dialog to get focus
++    GET_WELCOME_DIALOG="gsettings get org.gnome.shell welcome-dialog-last-shown-version"
++    if test $LOGIN_USER = $USER ; then
++        V=`dbus-run-session $GET_WELCOME_DIALOG`
++    else
++        V=`sudo -u $LOGIN_USER dbus-run-session $GET_WELCOME_DIALOG`
++    fi
++    if [ x"$V" = x"''" ] ; then
++        SET_WELCOME_DIALOG="gsettings set org.gnome.shell welcome-dialog-last-shown-version '100'"
++        if test $LOGIN_USER = $USER ; then
++            dbus-run-session $SET_WELCOME_DIALOG
++        else
++            sudo -u $LOGIN_USER dbus-run-session $SET_WELCOME_DIALOG
++        fi
++    fi
++    # gnome-shell now starts overview mode by login.
++    # https://extensions.gnome.org/extension/4099/no-overview/
++    NO_SYS_DIR=/usr/share/gnome-shell/extensions/no-overview@fthx
++    if test $LOGIN_USER = $USER ; then
++        NO_USER_DIR=$HOME/.local/share/gnome-shell/extensions/no-overview@fthx
++    else
++        NO_USER_DIR=$TEST_USER_HOME/.local/share/gnome-shell/extensions/no-overview@fthx
++    fi
++    if [ ! -d $NO_SYS_DIR ] && [ ! -d $NO_USER_DIR ] ; then
++        mkdir -p "`dirname $NO_USER_DIR`"
++        cp -R "no-overview@fthx" "`dirname $NO_USER_DIR`"
++        if test $LOGIN_USER = $USER ; then
++            chown -R $LOGIN_USER $HOME/.local
++        else
++            chown -R $LOGIN_USER $TEST_USER_HOME/.local
++        fi
++    fi
++    if [ $LOGIN_USER != $USER ] ; then
++        SHELL_SERVICE_FILE="org.gnome.Shell@wayland.service"
++        SYSTEMD_USER_DIR="$TEST_USER_HOME/.config/systemd/user"
++        if test $HAVE_GRAPHICS -ne 1 ; then
++            if test ! -f "$SYSTEMD_USER_DIR/$SHELL_SERVICE_FILE" ; then
++                mkdir -p "$SYSTEMD_USER_DIR"
++                pushd "$SYSTEMD_USER_DIR"
++                sed -e "s|^ExecStart=.*|ExecStart=$GNOME_SHELL_WAYLAND_COMMAND|" \
++                    $SYSTEMD_SYSTEM_DIR/$SHELL_SERVICE_FILE \
++                    > $SHELL_SERVICE_FILE
++                diff $SYSTEMD_SYSTEM_DIR/$SHELL_SERVICE_FILE $SHELL_SERVICE_FILE
++                popd
++                chown -R $LOGIN_USER "$TEST_USER_HOME/.config"
++            fi
++        else
++            if test -f "$SYSTEMD_USER_DIR/$SHELL_SERVICE_FILE" ; then
++                rm "$SYSTEMD_USER_DIR/$SHELL_SERVICE_FILE"
++            fi
++        fi
++    fi
++    GET_DISABLE_USER_EX="gsettings get org.gnome.shell disable-user-extensions"
++    if test $LOGIN_USER = $USER ; then
++        V=`dbus-run-session $GET_DISABLE_USER_EX`
++    else
++        V=`sudo -u $LOGIN_USER dbus-run-session $GET_DISABLE_USER_EX`
++    fi
++    if [ x"$V" = x"true" ] ; then
++        SET_DISABLE_USER_EX="gsettings set org.gnome.shell disable-user-extensions false"
++        if test $LOGIN_USER = $USER ; then
++            dbus-run-session $SET_DISABLE_USER_EX
++        else
++            sudo -u $LOGIN_USER dbus-run-session $SET_DISABLE_USER_EX
++        fi
++    fi
++    GET_ENABLED_EXS="gsettings get org.gnome.shell enabled-extensions"
++    if test $LOGIN_USER = $USER ; then
++        V=`dbus-run-session $GET_ENABLED_EXS`
++    else
++        V=`sudo -u $LOGIN_USER dbus-run-session $GET_ENABLED_EXS`
++    fi
++    echo "$V" | grep "no-overview" > /dev/null
++    V2=$?
++    if [ $V2 -ne 0 ] ; then
++        V3=`echo "$V" | sed -e 's/@as //' -e 's/\[//' -e 's/\]//'`
++        if [ x"$V3" = x"''" ] || [ x"$V3" = x"" ]; then
++            V4="['no-overview@fthx']"
++        else
++            V4="[$V3,'no-overview@fthx']"
++        fi
++        SET_ENABLED_EXS="gsettings set org.gnome.shell enabled-extensions \"$V4\""
++        if test $LOGIN_USER = $USER ; then
++            eval dbus-run-session $SET_ENABLED_EXS
++        else
++            eval sudo -u $LOGIN_USER dbus-run-session $SET_ENABLED_EXS
++        fi
++    fi
++    # Disable notify dialog when the disk usage is low.
++    SET_NOTIFY_DISK="gsettings set org.gnome.settings-daemon.plugins.housekeeping free-size-gb-no-notify 0"
++    if test $LOGIN_USER = $USER ; then
++            eval dbus-run-session $SET_NOTIFY_DISK
++    else
++            eval sudo -u $LOGIN_USER dbus-run-session $SET_NOTIFY_DISK
++    fi
++    if [ x"$backup_G_MESSAGES_DEBUG" != x ] ; then
++        export G_MESSAGES_DEBUG="$backup_G_MESSAGES_DEBUG"
++    fi
++}
++
++
++operate_desktop_with_systemd()
++{
++    SESSION=gnome-headless-session
++    COMMAND="$1"
++
++    #if test $HAVE_GRAPHICS -eq 1 ; then
++        case "$COMMAND" in
++        "start") systemctl isolate graphical.target
++                 echo ""
++                 ;;
++        "stop")  init 3;;
++        "")      print_log -e "${RED}FAIL${NC}: ${RED}ERROR${NC}: Wrong command $COMMAND"
++                 exit 1;;
++        esac
++    #else
++    #    setenforce 0
++    #    systemctl $COMMAND ${SESSION}@${TEST_USER}
++    #fi
++    case "$COMMAND" in
++    "start") sleep 30;;
++    "") ;;
++    esac
++    #if test $HAVE_GRAPHICS -eq 1 ; then
++        systemctl status --no-pager graphical.target
++    #else
++    #    systemctl status --no-pager ${SESSION}@${TEST_USER}
++    #fi
++    ps -ef | grep X
++    ps -ef | grep session
++    ps -ef | grep ibus
++}
++
++
++run_session()
++{
++    if [ $ENABLED_SYSTEMD -eq 1 ] && [  $SESSION_IS_GNOME -eq 1 ] ; then
++        init_gnome
++        operate_desktop_with_systemd "start"
++        return
++    fi
++    export DISPLAY=:99.0
++    if test $HAVE_GRAPHICS -eq 1 ; then
++        /usr/libexec/Xorg.wrap -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xorg.log -config ./xorg.conf -configdir . $DISPLAY &
++    else
++        /usr/bin/Xvfb $DISPLAY -noreset +extension GLX +extension RANDR +extension RENDER -screen 0 1280x1024x24 &
++    fi
++    PID_XORG=$!
++    sleep 1
++    # init_gnome need to be called with $DISPLAY before gnome-session is called
++    if [  $SESSION_IS_GNOME -eq 1 ] ; then
++        init_gnome
++    fi
++    echo "Running $SESSION_COMMAND with $USER and LANG=$SESSION_LANG in `tty`"
++    if test x"$SESSION_LANG" = x ; then
++        $SESSION_COMMAND &
++    else
++        env LANG=$SESSION_LANG $SESSION_COMMAND &
++    fi
++    PID_GNOME_SESSION=$!
++    sleep 30
++
++    IBUS_ARGS="--verbose --panel disable"
++    # gnome-shell 42 checks if org.freedesktop.IBus.session.GNOME.service
++    # systemd file is available with org.freedesktop.systemd1.Manager.GetUnit
++    # D-Bus method, which is provided by IBus 1.5.26, and if the file
++    # is available, gnome-shell no longer launch ibus-daemon
++    # because gnome-shell assumes gnome-session would launch ibus-daemon
++    # with org.freedesktop.systemd1.Manager.StartUnit D-Bus method.
++    # But actually gnome-session failed to launch ibus-daemon
++    # because the IBus systemd file depends on gnome-session.target
++    # but this CI launches gnome-session directly.
++    #
++    # So ibus-dameon is now always called here after gnome-shell fails to
++    # launch ibus-daemon.
++    # It may be better this CI launches GDM autologin to run gnome-session
++    # with gnome-session.target systemd file.
++    # But `systemctl start gdm` terminates the parent script forcibly
++    # and the script cannot get the CI result.
++    if test $VERBOSE -eq 1 ; then
++        ibus-daemon $IBUS_ARGS &
++    else
++        ibus-daemon $IBUS_ARGS --daemonize
++    fi
++    sleep 3
++    ps -ef | grep ibus
++}
++
++
++count_case_result()
++{
++    retval=$1
++    pass=$2
++    fail=$3
++
++    if test $retval -eq  0 ; then
++        pass=`expr $pass + 1`
++    else
++        fail=`expr $fail + 1`
++    fi
++    echo $pass $fail
++}
++
++
++echo_case_result()
++{
++    retval=$1
++    tst=$2
++    subtst=${3:-''}
++
++    if test $retval -eq  0 ; then
++        echo "PASS: $tst $subtst" >>$TEST_LOG
++    else
++        echo "FAIL: $tst $subtst" >>$TEST_LOG
++    fi
++}
++
++
++wait_for_systemd_autostart()
++{
++    PS_IBUS="ps -ef | grep ibus-desktop-testing | grep autostart | grep -v grep"
++    i=0
++    while test 1 ; do
++        R=`eval "$PS_IBUS"`
++        if test x"$R" != x ; then
++            break;
++        fi
++        if test $i -ge 12 ; then
++            print_log -e "${RED}FAIL${NC}: Timeout to run ibus-desktop-testing-autostart"
++            return
++        fi
++        i=`expr $i + 1`
++        sleep 5
++    done
++    print_log -e "Start ibus-desktop-testing-autostart"
++    i=0
++    while test 1 ; do
++        R=`eval "$PS_IBUS"`
++        if test x"$R" = x ; then
++            break;
++        fi
++        if test $i -ge $TIMEOUT ; then
++            print_log -e "${RED}FAIL${NC}: Timeout to exit ibus-desktop-testing-autostart"
++            return
++        fi
++        i=`expr $i + 1`
++        sleep 5
++    done
++    print_log -e "Exit ibus-desktop-testing-autostart"
++}
++
++run_direct_test_cases()
++{
++    pass=0
++    fail=0
++    for tst in $TESTS; do
++        ENVS=
++        if test -f $SRCDIR/${tst}.env ; then
++            ENVS="`cat $SRCDIR/${tst}.env`"
++        fi
++        if test x"$ENVS" = x ; then
++            $BUILDDIR/$tst $SRCDIR 2>>$TEST_LOG 1>>$TEST_LOG
++            retval=$?
++            read pass fail << EOF_COUNT
++            `count_case_result $retval $pass $fail`
++EOF_COUNT
++            echo_case_result $retval $tst
++            CACHE_FILES=`ls *.cache`
++            if [ x"$CACHE_FILES" != x ] ; then
++                echo "# Clean $CACHE_FILES" >>$TEST_LOG
++                rm $CACHE_FILES
++            fi
++        else
++            i=1
++            # Deleted for var in "$ENVS" because IFS=$'\n' is not supported
++            # in POSIX sh
++            while read e ; do
++                first=`echo "$e" | grep '^#'`
++                if test x"$first" = x"#" ; then
++                    continue
++                fi
++                echo "# Starting $e $BUILDDIR/$tst $SRCDIR" >>$TEST_LOG
++                env $e $BUILDDIR/$tst $SRCDIR 2>>$TEST_LOG 1>>$TEST_LOG
++                retval=$?
++                echo "# Finished $e $BUILDDIR/$tst $SRCDIR with $retval" >>$TEST_LOG
++                read pass fail << EOF_COUNT
++                `count_case_result $retval $pass $fail`
++EOF_COUNT
++                echo_case_result $retval $tst $e
++                CACHE_FILES=`ls *.cache`
++                if [ x"$CACHE_FILES" != x ] ; then
++                    echo "# Clean $CACHE_FILES" >>$TEST_LOG
++                    rm $CACHE_FILES
++                fi
++                i=`expr $i + 1`
++            done << EOF_ENVS
++            `echo "$ENVS"`
++EOF_ENVS
++        fi
++    done
++    echo $pass $fail
++}
++
++
++run_gnome_desktop_testing_runner()
++{
++    pass=0
++    fail=0
++    if [ x"$TESTS" = x ] ; then
++        TESTS='ibus'
++    fi
++    if echo $PROGNAME | grep -q autostart ; then
++        export IBUS_DAEMON_WITH_SYSTEMD=1
++    fi
++    for tst in $TESTS; do
++        tst_dir="$TEST_CASE_DIR/$tst"
++        if [ ! -d "$tst_dir" ] ; then
++            print_log -e "${RED}FAIL${NC}: Not found %tst_dir"
++            fail=1
++            continue
++        fi
++        gnome-desktop-testing-runner --timeout=$TIMEOUT $tst \
++                2>>$TEST_LOG 1>>$TEST_LOG
++        retval=$?
++        read pass fail << EOF
++        `count_case_result $retval $pass $fail`
++EOF
++    done
++    child_pass=`grep '^PASS:' $TEST_LOG | wc -l`
++    child_fail=`grep '^FAIL:' $TEST_LOG | wc -l`
++    if [ $child_pass -ne 0 ] || [ $child_fail -ne 0 ] ; then
++        pass=$child_pass
++        if [ $child_fail -ne 0 ] ; then
++            fail=`expr $child_fail / 2`
++        else
++            fail=0
++        fi
++    fi
++    echo $pass $fail
++}
++
++
++run_test_suite()
++{
++    print_log -e "Start test suite `date '+%F %H:%M:%S:%N'`"
++    if echo $PROGNAME | grep -q runner ; then
++        if [ $ENABLED_SYSTEMD -eq 1 ] && [  $SESSION_IS_GNOME -eq 1 ] ; then
++            wait_for_systemd_autostart
++            return
++        fi
++        if ps -ef | grep X | grep -q wayland ; then
++            # Expect GTK_IM_MODULE=wayland by default
++            :
++        else
++            export GTK_IM_MODULE=ibus
++        fi
++    else
++        if test x"$XDG_SESSION_TYPE" = xwayland ; then
++            :
++        else
++            export GTK_IM_MODULE=ibus
++        fi
++    fi
++    pass=0
++    fail=0
++    export IBUS_COMPOSE_CACHE_DIR=$PWD
++    if test x"$TESTING_RUNNER" = x ; then
++        return
++    fi
++    case $TESTING_RUNNER in
++    default)
++        # Get only the last value with do-while.
++        read pass fail << EOF_RUNNER
++        `run_direct_test_cases`
++EOF_RUNNER
++        ;;
++    gnome)
++        read pass fail << EOF_RUNNER
++        `run_gnome_desktop_testing_runner`
++EOF_RUNNER
++        ;;
++    esac
++    echo ""
++    print_log -e "End test suite `date '+%F %H:%M:%S:%N'`"
++    # Fedora CI assumes the test is failed even if $fail is 0.
++    if [ $pass -ne 0 ] ; then
++        print_log -e "${GREEN}PASS${NC}: $pass"
++    fi
++    if [ $fail -ne 0 ] ; then
++        print_log -e "${RED}FAIL${NC}: $fail"
++    fi
++}
++
++
++finit()
++{
++    if test $PID_XORG -ne 0 ; then
++        echo "# Killing left gnome-session and Xorg"
++        kill $PID_GNOME_SESSION $PID_XORG
++        ibus exit
++        SUSER=`echo "$USER" | cut -c 1-7`
++        LEFT_CALENDAR=`ps -ef | grep gnome-shell-calendar-server | grep $SUSER | grep -v grep`
++        if test x"$LEFT_CALENDAR" != x ; then
++            echo "# Killing left gnome-shell-calendar-server"
++            echo "$LEFT_CALENDAR"
++            echo "$LEFT_CALENDAR" | awk '{print $2}' | xargs kill
++        fi
++    fi
++    if echo $PROGNAME | grep -q runner ; then
++        if [ $ENABLED_SYSTEMD -eq 1 ] && [  $SESSION_IS_GNOME -eq 1 ] ; then
++            operate_desktop_with_systemd "stop"
++        fi
++    fi
++
++    echo ""
++    if test -f $TEST_LOG ; then
++        if [ $TEST_LOG_STDOUT -eq 1 ] ; then
++            cat $TEST_LOG
++        else
++            echo "# See $TEST_LOG"
++        fi
++    fi
++    echo "# Finished $PROGNAME testing"
++}
+diff --git a/src/tests/ibus-desktop-testing-runner.in b/src/tests/ibus-desktop-testing-runner.in
+index 1ac2dfc8..1d82bc76 100755
+--- a/src/tests/ibus-desktop-testing-runner.in
++++ b/src/tests/ibus-desktop-testing-runner.in
+@@ -4,7 +4,7 @@
+ #
+ # ibus - The Input Bus
+ #
+-# Copyright (c) 2018-2021 Takao Fujiwara <takao.fujiwara1@gmail.com>
++# Copyright (c) 2018-2024 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ # Copyright (c) 2018 Red Hat, Inc.
+ #
+ # This program is free software; you can redistribute it and/or modify
+@@ -22,58 +22,39 @@
+ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ 
+ # This test runs /usr/bin/ibus-daemon after install ibus
+-#
+-# # init 3
+-# Login as root
+-# # /root/ibus/tests/test-console.sh --tests ibus-compose \
+-#   --builddir /root/ibus/src/tests --srcdir /root/ibus/src/tests
+-
+-# POSIX sh has no 'echo -e'
+-: ${ECHO:='/usr/bin/echo'}
+-# POSIX sh has $UID
+-# DASH saves the graves in '``' as characters not to be extracted
+-: ${UID:=`id -u`}
+ 
+ 
+ PROGNAME=`basename $0`
+-VERSION=0.3
+-DISPLAY=:99.0
+-BUILDDIR="."
+-SRCDIR="."
++TEST_CASE_DIR="@INSTALLEDDIR@"
++MODULE_SH='ibus-desktop-testing-module'
++MODULE_SH_PATH="@LIBEXECDIR@/$MODULE_SH"
++AUTOSTART_DESKTOP_DIR="@DATADIR@/ibus/tests"
+ TEST_LOG="test-suite.log"
+-TEST_LOG_STDOUT=0
+-RESULT_LOG=""
+-SCREEN_LOG=""
+-HAVE_GRAPHICS=1
+-VERBOSE=0
+-DESKTOP_COMMAND="dbus-launch --exit-with-session gnome-session"
+-PID_XORG=0
+-PID_GNOME_SESSION=0
+ TESTING_RUNNER="default"
+-TESTS=""
+-TIMEOUT=300
+-GREEN='\033[0;32m'
+-RED='\033[0;31m'
+-NC='\033[0m'
+ 
+ 
+-print_log()
+-{
+-    if [ x"$RESULT_LOG" != x ] ; then
+-        # avoid 'echo -e' before call 'sed'.
+-        if [ x"$1" = x'-e' ] ; then
+-            shift
+-        fi
+-        NO_ESCAPE=`echo "$@" | sed -e 's/\\\033\\[0;3.m//g' -e 's/\\\033\\[0m//g'`
+-        $ECHO $NO_ESCAPE >> $RESULT_LOG
+-    else
+-        $ECHO "$@"
+-    fi
+-}
++if test -f $MODULE_SH_PATH ; then
++  . $MODULE_SH_PATH
++elif test -f $(dirname $0)/$MODULE_SH ; then
++  . $(dirname $0)/$MODULE_SH
++else
++  echo "Not found $MODULE_SH"
++  exit 1
++fi
+ 
+ 
+ usage()
+ {
++    command=""
++    for element in $SESSION_COMMAND; do
++        if test x"$element" = x; then
++            continue
++        fi
++        if echo "$element" | grep -q -E "^-" ; then
++            continue
++        fi
++        command="$element"
++    done
+     $ECHO -e \
+ "This test runs /usr/bin/ibus-daemon after install ibus\n"                     \
+ "$PROGNAME [OPTIONS…]\n"                                                       \
+@@ -83,429 +64,37 @@ usage()
+ "-v, --version                    Show version\n"                              \
+ "-b, --builddir=BUILDDIR          Set the BUILDDIR\n"                          \
+ "-s, --srcdir=SOURCEDIR           Set the SOURCEDIR\n"                         \
+-"-c, --no-graphics                Use Xvfb instead of Xorg\n"                  \
++"-c, --no-graphics                Use Xvfb instead of Xorg or Wayland\n"       \
+ "-V, --verbose                    Verbose log for ibus-daemon\n"               \
+-"-d, --desktop=DESKTOP            Run DESTKTOP. The default is gnome-session.\n" \
+-"                                 Suffix '-with-dbus' can run DESKTOP with dbus session." \
+-"                                 E.g. --desktop=mutter-with-dbus"             \
++"-d, --session=SESSION            Run SESSION. The default is ${command}.\n"   \
++"                                 Suffix '-with-dbus' can run SESSION with dbus session.\n" \
++"                                 E.g. --session=${command}-with-dbus\n"       \
++"-l, --lang=LANG                  Run SESSION with LANG\n"                     \
+ "-t, --tests=\"TESTS...\"           Run TESTS programs which is separated by space\n" \
+ "-r, --runner=RUNNER              Run TESTS programs with a test RUNNER.\n"    \
+-"                                 RUNNDER = gnome or default.\n"               \
+-"                                 default is an embedded runner.\n"            \
++"                                 RUNNDER = 'gnome' or 'default'.\n"           \
++"                                 'default' is an embedded runner.\n"          \
++"-i, --no-systemd                 Run gnome-seesion directly withoout systemd\n" \
++"                                 and login manager for legacy GNOME and this\n" \
++"                                 mode does not support Wayland.\n" \
+ "-T, --timeout=TIMEOUT            Set timeout (default TIMEOUT is 300 sec).\n" \
+-"-o, --output=OUTPUT_FILE         OUtput the log to OUTPUT_FILE\n"             \
+-"-O, --result=RESULT_FILE         OUtput the result to RESULT_FILE\n"          \
+-"-S, --screendump=DUMP_FILE       OUtput the screen to DUMP_FILE ('STDOUT' can be stdout)\n" \
++"-o, --output=OUTPUT_FILE         Output the log to OUTPUT_FILE\n"             \
++"                                 default is $TEST_LOG\n"                      \
++"-O, --result=RESULT_FILE         Output the result to RESULT_FILE\n"          \
++"                                 default is stdout\n"                         \
++"-S, --screendump=DUMP_FILE       Output the screen to DUMP_FILE ('STDOUT' can be stdout)\n" \
++"-D, --delete-tests               Delete test enviroments and user $TEST_USER\n" \
+ ""
+ }
+ 
+ 
+-parse_args()
+-{
+-    # This is GNU getopt. "sudo port getopt" in BSD?
+-    ARGS=`getopt -o hvb:s:cVd:t:r:T:o:O:S: --long \
+-          help,version,builddir:,srcdir:,no-graphics,verbose,desktop:,tests:,runner:,timeout:,output:,result:,screendump:\
+-        -- "$@"`;
+-    eval set -- "$ARGS"
+-    while [ 1 ] ; do
+-        case "$1" in
+-        -h | --help )        usage; exit 0;;
+-        -v | --version )     $ECHO -e "$VERSION"; exit 0;;
+-        -b | --builddir )    BUILDDIR="$2"; shift 2;;
+-        -s | --srcdir )      SRCDIR="$2"; shift 2;;
+-        -c | --no-graphics ) HAVE_GRAPHICS=0; shift;;
+-        -V | --verbose )     VERBOSE=1; shift;;
+-        -d | --desktop )     DESKTOP_COMMAND="$2"; shift 2;;
+-        -t | --tests )       TESTS="$2"; shift 2;;
+-        -r | --runner )      TESTING_RUNNER="$2"; shift 2;;
+-        -T | --timeout )     TIMEOUT="$2"; shift 2;;
+-        -o | --output )      TEST_LOG="$2"; shift 2;;
+-        -O | --result )      RESULT_LOG="$2"; shift 2;;
+-        -S | --screendump )  SCREEN_LOG="$2"; shift 2;;
+-        -- )                 shift; break;;
+-        * )                  usage; exit 1;;
+-        esac
+-    done
+-    DL='$'
+-    echo "$DESKTOP_COMMAND" | grep "\-with\-dbus$DL" > /dev/null
+-    HAS_DBUS_SUFFIX=$?
+-    if [ $HAS_DBUS_SUFFIX -eq 0 ] ; then
+-        DESKTOP_COMMAND=`echo "$DESKTOP_COMMAND" | sed -e 's/-with-dbus$//'`
+-        DESKTOP_COMMAND="dbus-launch --exit-with-session $DESKTOP_COMMAND"
+-    fi
+-}
+-
+-
+-init_desktop()
+-{
+-    if [ "$RESULT_LOG" != "" ] ; then
+-        if [ -f $RESULT_LOG ] ; then
+-            rm $RESULT_LOG
+-        fi
+-    fi
+-    echo "$TEST_LOG" | grep ':stdout' > /dev/null
+-    HAS_STDOUT=$?
+-    if [ $HAS_STDOUT -eq 0 ] ; then
+-        TEST_LOG=`echo "$TEST_LOG" | sed -e 's|:stdout||'`
+-        TEST_LOG_STDOUT=1
+-    fi
+-    if [ "$TEST_LOG" = "" ] ; then
+-        print_log -e "${RED}FAIL${NC}: ${RED}ERROR${NC}: a log file is required to get return value with 'read' command"
+-        exit 255
+-    elif [ -f $TEST_LOG ] ; then
+-        rm $TEST_LOG
+-    fi
+-    if [ x$FORCE_TEST != x ] ; then
+-        RUN_ARGS="$RUN_ARGS --force"
+-    fi
+-
+-    if [ ! -f $HOME/.config/gnome-initial-setup-done ] ; then
+-        IS_SYSTEM_ACCOUNT=false
+-        if [ "$USER" = "root" ] ; then
+-            IS_SYSTEM_ACCOUNT=true
+-        fi
+-        if test ! -f /var/lib/AccountsService/users/$USER ; then
+-            mkdir -p /var/lib/AccountsService/users
+-            cat >> /var/lib/AccountsService/users/$USER << _EOF
+-[User]
+-Language=ja_JP.UTF-8
+-XSession=gnome
+-SystemAccount=$IS_SYSTEM_ACCOUNT
+-_EOF
+-        fi
+-        mkdir -p $HOME/.config
+-        touch $HOME/.config/gnome-initial-setup-done
+-    fi
+-
+-    # Prevent from launching a XDG dialog
+-    XDG_LOCALE_FILE="$HOME/.config/user-dirs.locale"
+-    if [ -f $XDG_LOCALE_FILE ] ; then
+-        XDG_LANG_ORIG=`cat $XDG_LOCALE_FILE`
+-        XDG_LANG_NEW=`echo $LANG | sed -e 's/\(.*\)\..*/\1/'`
+-        if [ "$XDG_LANG_ORIG" != "$XDG_LANG_NEW" ] ; then
+-            echo "# Overriding XDG locale $XDG_LANG_ORIG with $XDG_LANG_NEW"
+-            echo "$XDG_LANG_NEW" > $XDG_LOCALE_FILE
+-        fi
+-    fi
+-    # `su` command does not run loginctl
+-    export XDG_SESSION_TYPE='x11'
+-    export XDG_SESSION_CLASS=user
+-    # `su` command does not get focus in events without this variable.
+-    # Need to restart sshd after set "PermitRootLogin yes" in sshd_config
+-    if [ "x$XDG_RUNTIME_DIR" = x ] ; then
+-        export XDG_RUNTIME_DIR="/run/user/$UID"
+-        is_root_login=`grep "^PermitRootLogin" /etc/ssh/sshd_config | grep yes`
+-        if [ "x$ANSIBLE" != x ] && [ "x$is_root_login" = x ] ; then
+-            print_log -e "${RED}FAIL${NC}: No permission to get focus-in events in GtkWindow with ansible"
+-            echo "su command does not configure necessary login info "         \
+-                 "with systemd and GtkWindow cannot receive focus-events "     \
+-                 "when ibus-desktop-testing-runner is executed by "            \
+-                 "ansible-playbook." >> $TEST_LOG
+-            echo "Enabling root login via sshd, restarting sshd, set "         \
+-                 "XDG_RUNTIME_DIR can resolve the problem under "              \
+-                 "ansible-playbook." >> $TEST_LOG
+-            exit 255
+-        fi
+-    fi
+-    #  Do we need XDG_SESSION_ID and XDG_SEAT?
+-    #export XDG_CONFIG_DIRS=/etc/xdg
+-    #export XDG_SESSION_ID=10
+-    #export XDG_SESSION_DESKTOP=gnome
+-    #export XDG_SEAT=seat0
+-}
+-
+-
+-run_dbus_daemon()
+-{
+-    # Use dbus-launch --exit-with-session later instead of --sh-syntax
+-    # GNOME uses a unix:abstract address and it effects gsettings set values
+-    # in each test case.
+-    # TODO: Should we comment out this line?
+-    export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$UID/bus"
+-}
+-
+-
+-init_gnome()
+-{
+-    # gsettings set command needs dconf-service with the same $DISPLAY
+-    pkill dconf-service
+-    # G_MESSAGES_DEBUG=all or G_MESSAGES_DEBUG=GLib-GIO-DEBUG would append
+-    # debug messages to gsettings output and could not get the result correctly.
+-    backup_G_MESSAGES_DEBUG="$G_MESSAGES_DEBUG"
+-    unset G_MESSAGES_DEBUG
+-    # Disable Tour dialog to get focus
+-    V=`gsettings get org.gnome.shell welcome-dialog-last-shown-version`
+-    if [ x"$V" = x"''" ] ; then
+-        gsettings set org.gnome.shell welcome-dialog-last-shown-version '100'
+-    fi
+-    # gnome-shell now starts overview mode by login.
+-    # https://extensions.gnome.org/extension/4099/no-overview/
+-    NO_SYS_DIR=/usr/share/gnome-shell/extensions/no-overview@fthx
+-    NO_USER_DIR=$HOME/.local/share/gnome-shell/extensions/no-overview@fthx
+-    if [ ! -d $NO_SYS_DIR ] && [ ! -d $NO_USER_DIR ] ; then
+-        mkdir -p "`dirname $NO_USER_DIR`"
+-        cp -R "no-overview@fthx" "`dirname $NO_USER_DIR`"
+-    fi
+-    V=`gsettings get org.gnome.shell disable-user-extensions`
+-    if [ x"$V" = x"true" ] ; then
+-        gsettings set org.gnome.shell disable-user-extensions false
+-    fi
+-    V=`gsettings get org.gnome.shell enabled-extensions`
+-    echo "$V" | grep "no-overview" > /dev/null
+-    V2=$?
+-    if [ $V2 -ne 0 ] ; then
+-        V3=`echo "$V" | sed -e 's/@as //' -e 's/\[//' -e 's/\]//'`
+-        if [ x"$V3" = x"''" ] || [ x"$V3" = x"" ]; then
+-            V4="['no-overview@fthx']"
+-        else
+-            V4="[$V3, 'no-overview@fthx']"
+-        fi
+-        gsettings set org.gnome.shell enabled-extensions "$V4"
+-    fi
+-    if [ x"$backup_G_MESSAGES_DEBUG" != x ] ; then
+-        export G_MESSAGES_DEBUG="$backup_G_MESSAGES_DEBUG"
+-    fi
+-}
+-
+-
+-run_desktop()
+-{
+-    echo "$DESKTOP_COMMAND" | grep gnome-session > /dev/null
+-    HAS_GNOME=$?
+-    export DISPLAY=$DISPLAY
+-    if test $HAVE_GRAPHICS -eq 1 ; then
+-        /usr/libexec/Xorg.wrap -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xorg.log -config ./xorg.conf -configdir . $DISPLAY &
+-    else
+-        /usr/bin/Xvfb $DISPLAY -noreset +extension GLX +extension RANDR +extension RENDER -screen 0 1280x1024x24 &
+-    fi
+-    PID_XORG=$!
+-    sleep 1
+-    # init_gnome need to be called with $DISPLAY before gnome-session is called
+-    if [  $HAS_GNOME -eq 0 ] ; then
+-        init_gnome
+-    fi
+-    echo "Running $DESKTOP_COMMAND with $USER in `tty`"
+-    $DESKTOP_COMMAND &
+-    PID_GNOME_SESSION=$!
+-    sleep 30
+-
+-    IBUS_ARGS="--verbose --panel disable"
+-    # gnome-shell 42 checks if org.freedesktop.IBus.session.GNOME.service
+-    # systemd file is available with org.freedesktop.systemd1.Manager.GetUnit
+-    # D-Bus method, which is provided by IBus 1.5.26, and if the file
+-    # is available, gnome-shell no longer launch ibus-daemon
+-    # because gnome-shell assumes gnome-session would launch ibus-daemon
+-    # with org.freedesktop.systemd1.Manager.StartUnit D-Bus method.
+-    # But actually gnome-session failed to launch ibus-daemon
+-    # because the IBus systemd file depends on gnome-session.target
+-    # but this CI launches gnome-session directly.
+-    #
+-    # So ibus-dameon is now always called here after gnome-shell fails to
+-    # launch ibus-daemon.
+-    # It may be better this CI launches GDM autologin to run gnome-session
+-    # with gnome-session.target systemd file.
+-    # But `systemctl start gdm` terminates the parent script forcibly
+-    # and the script cannot get the CI result.
+-    if test $VERBOSE -eq 1 ; then
+-        ibus-daemon $IBUS_ARGS &
+-    else
+-        ibus-daemon $IBUS_ARGS --daemonize
+-    fi
+-    sleep 3
+-    if test $VERBOSE -eq 1 ; then
+-        ps -ef | grep ibus
+-    fi
+-}
+-
+-
+-count_case_result()
+-{
+-    retval=$1
+-    pass=$2
+-    fail=$3
+-
+-    if test $retval -eq  0 ; then
+-        pass=`expr $pass + 1`
+-    else
+-        fail=`expr $fail + 1`
+-    fi
+-    echo $pass $fail
+-}
+-
+-
+-echo_case_result()
+-{
+-    retval=$1
+-    tst=$2
+-    subtst=${3:-''}
+-
+-    if test $retval -eq  0 ; then
+-        echo "PASS: $tst $subtst" >>$TEST_LOG
+-    else
+-        echo "FAIL: $tst $subtst" >>$TEST_LOG
+-    fi
+-}
+-
+-
+-run_direct_test_cases()
+-{
+-    pass=0
+-    fail=0
+-    for tst in $TESTS; do
+-        ENVS=
+-        if test -f $SRCDIR/${tst}.env ; then
+-            ENVS="`cat $SRCDIR/${tst}.env`"
+-        fi
+-        if test x"$ENVS" = x ; then
+-            $BUILDDIR/$tst $SRCDIR 2>>$TEST_LOG 1>>$TEST_LOG
+-            retval=$?
+-            read pass fail << EOF_COUNT
+-            `count_case_result $retval $pass $fail`
+-EOF_COUNT
+-            echo_case_result $retval $tst
+-            CACHE_FILES=`ls *.cache`
+-            if [ x"$CACHE_FILES" != x ] ; then
+-                echo "# Clean $CACHE_FILES" >>$TEST_LOG
+-                rm $CACHE_FILES
+-            fi
+-        else
+-            i=1
+-            # Deleted for var in "$ENVS" because IFS=$'\n' is not supported
+-            # in POSIX sh
+-            while read e ; do
+-                first=`echo "$e" | grep '^#'`
+-                if test x"$first" = x"#" ; then
+-                    continue
+-                fi
+-                echo "# Starting $e $BUILDDIR/$tst $SRCDIR" >>$TEST_LOG
+-                env $e $BUILDDIR/$tst $SRCDIR 2>>$TEST_LOG 1>>$TEST_LOG
+-                retval=$?
+-                echo "# Finished $e $BUILDDIR/$tst $SRCDIR with $retval" >>$TEST_LOG
+-                read pass fail << EOF_COUNT
+-                `count_case_result $retval $pass $fail`
+-EOF_COUNT
+-                echo_case_result $retval $tst $e
+-                CACHE_FILES=`ls *.cache`
+-                if [ x"$CACHE_FILES" != x ] ; then
+-                    echo "# Clean $CACHE_FILES" >>$TEST_LOG
+-                    rm $CACHE_FILES
+-                fi
+-                i=`expr $i + 1`
+-            done << EOF_ENVS
+-            `echo "$ENVS"`
+-EOF_ENVS
+-        fi
+-    done
+-    echo $pass $fail
+-}
+-
+-
+-run_gnome_desktop_testing_runner()
+-{
+-    pass=0
+-    fail=0
+-    if [ x"$TESTS" = x ] ; then
+-        TESTS='ibus'
+-    fi
+-    for tst in $TESTS; do
+-        tst_dir="@INSTALLEDDIR@/$tst"
+-        if [ ! -d "$tst_dir" ] ; then
+-            print_log -e "${RED}FAIL${NC}: Not found %tst_dir"
+-            fail=1
+-            continue
+-        fi
+-        gnome-desktop-testing-runner --timeout=$TIMEOUT $tst \
+-                2>>$TEST_LOG 1>>$TEST_LOG
+-        retval=$?
+-        read pass fail << EOF
+-        `count_case_result $retval $pass $fail`
+-EOF
+-    done
+-    child_pass=`grep '^PASS:' $TEST_LOG | wc -l`
+-    child_fail=`grep '^FAIL:' $TEST_LOG | wc -l`
+-    if [ $child_pass -ne 0 ] || [ $child_fail -ne 0 ] ; then
+-        pass=$child_pass
+-        if [ $child_fail -ne 0 ] ; then
+-            fail=`expr $child_fail / 2`
+-        else
+-            fail=0
+-        fi
+-    fi
+-    echo $pass $fail
+-}
+-
+-
+-run_test_suite()
+-{
+-    pass=0
+-    fail=0
+-    export GTK_IM_MODULE=ibus
+-    export IBUS_COMPOSE_CACHE_DIR=$PWD
+-    if [ x"$TESTING_RUNNER" = x ] ; then
+-        TESTING_RUNNER="default"
+-    fi
+-    case $TESTING_RUNNER in
+-    default)
+-        # Get only the last value with do-while.
+-        read pass fail << EOF_RUNNER
+-        `run_direct_test_cases`
+-EOF_RUNNER
+-        ;;
+-    gnome)
+-        read pass fail << EOF_RUNNER
+-        `run_gnome_desktop_testing_runner`
+-EOF_RUNNER
+-        ;;
+-    esac
+-    echo ""
+-    # Fedora CI assumes the test is failed even if $fail is 0.
+-    if [ $pass -ne 0 ] ; then
+-        print_log -e "${GREEN}PASS${NC}: $pass"
+-    fi
+-    if [ $fail -ne 0 ] ; then
+-        print_log -e "${RED}FAIL${NC}: $fail"
+-    fi
+-    echo ""
+-    if [ $TEST_LOG_STDOUT -eq 1 ] ; then
+-        cat $TEST_LOG
+-    else
+-        echo "# See $TEST_LOG"
+-    fi
+-}
+-
+-
+-finit()
+-{
+-    echo "# Killing left gnome-session and Xorg"
+-    kill $PID_GNOME_SESSION $PID_XORG
+-    ibus exit
+-    SUSER=`echo "$USER" | cut -c 1-7`
+-    LEFT_CALENDAR=`ps -ef | grep gnome-shell-calendar-server | grep $SUSER | grep -v grep`
+-    if [ x"$LEFT_CALENDAR" != x ] ; then
+-        echo "# Killing left gnome-shell-calendar-server"
+-        echo "$LEFT_CALENDAR"
+-        echo "$LEFT_CALENDAR" | awk '{print $2}' | xargs kill
+-    fi
+-
+-    echo "# Finished $PROGNAME testing"
+-}
+-
+-
+ main()
+ {
+     parse_args "$@"
+-    init_desktop
++    init_session
+     run_dbus_daemon 2>>$TEST_LOG 1>>$TEST_LOG
+-    run_desktop 2>>$TEST_LOG 1>>$TEST_LOG
+-    if [ x"$SCREEN_LOG" != x ] ; then
+-        SCREEN_PNG="`date '+%Y%m%d%H%M%S'`.png"
+-        gnome-screenshot --file=$SCREEN_PNG
+-        if [ x"$SCREEN_LOG" = xSTDOUT ] ; then
+-            base64 $SCREEN_PNG
+-        else
+-            base64 $SCREEN_PNG > $SCREEN_LOG
+-        fi
+-    fi
++    run_session 2>>$TEST_LOG 1>>$TEST_LOG
++    save_screen
+     run_test_suite
+     finit
+ }
+diff --git a/src/tests/ibus-desktop-testing.desktop.in b/src/tests/ibus-desktop-testing.desktop.in
+index 1b815345..e91900ed 100644
+--- a/src/tests/ibus-desktop-testing.desktop.in
++++ b/src/tests/ibus-desktop-testing.desktop.in
+@@ -2,7 +2,7 @@
+ Name=IBus Desktop Testing Runner
+ GenericName=Input Method Desktop Testing Runner
+ Comment=Test plugin for IBus Desktop Testing
+-Exec=@libexecdir@/ibus-desktop-testing-autostart /var/tmp/ibus-ci-autostart.log
++Exec=sh -c 'exec @libexecdir@/ibus-desktop-testing-autostart --envcheck --output $HOME/test-autostart.log --result $HOME/test.log --runner gnome --screendump $HOME/screen.log --tests ibus'
+ Terminal=false
+ Type=Application
+ Encoding=UTF-8
+-- 
+2.45.0
+
+From 039fcb16f18d341a244362c3e797eeaa5c51010e Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Sat, 27 Jul 2024 19:02:09 +0900
+Subject: [PATCH 2/6] Fix tests cases to be run in Wayland
+
+- Fix ibus-bus since ibus_bus_exit_async() does not work ibus-daemon
+  with systemd and run `ibus restart` instead.
+- Fix ibus-compose-locales to wait for 1 sec between key event test cases
+  to reset gnome-shell/mutter.
+- Fix ibus-compose to wait for commit-text event since the gnome-shell/mutter
+  process is a bit slow and can reset the previous commit-text events
+  before the previous events are not committed.
+- Fix counts in ibus-engine-switch.
+- Unref IBusEngineDesc in ibus-engine-switch.
+- Fix ibusimpl not to change the engine of the context when global-engine
+  is enabled.
+- Check DISPLAY and setxkbmap in xkb-latin-layouts for Wayland
+
+BUG=https://github.com/ibus/ibus/pull/2657
+---
+ bus/ibusimpl.c                 |   8 ++
+ bus/ibusimpl.h                 |   3 +-
+ bus/inputcontext.c             |   9 +++
+ src/ibusshare.c                |   2 +-
+ src/tests/ibus-bus.c           |  92 +++++++++++++++++++---
+ src/tests/ibus-compose.c       | 139 ++++++++++++++++++++++++---------
+ src/tests/ibus-engine-switch.c |  45 ++++++++++-
+ src/tests/xkb-latin-layouts    |   9 +++
+ 8 files changed, 254 insertions(+), 53 deletions(-)
+
+diff --git a/bus/ibusimpl.c b/bus/ibusimpl.c
+index 445c062b..4bed9bc0 100644
+--- a/bus/ibusimpl.c
++++ b/bus/ibusimpl.c
+@@ -2463,6 +2463,14 @@ bus_ibus_impl_is_embed_preedit_text (BusIBusImpl *ibus)
+     return ibus->embed_preedit_text;
+ }
+ 
++gboolean
++bus_ibus_impl_is_use_global_engine (BusIBusImpl *ibus)
++{
++    g_assert (BUS_IS_IBUS_IMPL (ibus));
++
++    return ibus->use_global_engine;
++}
++
+ BusInputContext *
+ bus_ibus_impl_get_focused_input_context (BusIBusImpl *ibus)
+ {
+diff --git a/bus/ibusimpl.h b/bus/ibusimpl.h
+index e3b43f87..428b773b 100644
+--- a/bus/ibusimpl.h
++++ b/bus/ibusimpl.h
+@@ -2,7 +2,7 @@
+ /* vim:set et sts=4: */
+ /* bus - The Input Bus
+  * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
+- * Copyright (C) 2022-2023 Takao Fujiwara <takao.fujiwara1@gmail.com>
++ * Copyright (C) 2022-2024 Takao Fujiwara <takao.fujiwara1@gmail.com>
+  * Copyright (C) 2008-2022 Red Hat, Inc.
+  *
+  * This library is free software; you can redistribute it and/or
+@@ -94,6 +94,7 @@ BusComponent    *bus_ibus_impl_lookup_component_by_name
+ gboolean         bus_ibus_impl_is_use_sys_layout    (BusIBusImpl        *ibus);
+ gboolean         bus_ibus_impl_is_embed_preedit_text
+                                                     (BusIBusImpl        *ibus);
++gboolean         bus_ibus_impl_is_use_global_engine (BusIBusImpl        *ibus);
+ BusInputContext *bus_ibus_impl_get_focused_input_context
+                                                     (BusIBusImpl        *ibus);
+ GHashTable      *bus_ibus_impl_get_engine_focus_id_table
+diff --git a/bus/inputcontext.c b/bus/inputcontext.c
+index 1e795733..85358241 100644
+--- a/bus/inputcontext.c
++++ b/bus/inputcontext.c
+@@ -1289,6 +1289,15 @@ _ic_set_engine (BusInputContext       *context,
+                 GDBusMethodInvocation *invocation)
+ {
+     gchar *engine_name = NULL;
++    BusIBusImpl *ibus = bus_ibus_impl_get_default ();
++
++    if (bus_ibus_impl_is_use_global_engine (ibus)) {
++        g_dbus_method_invocation_return_error (invocation,
++                G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
++                "Cannot set engines when use-global-engine is enabled.");
++        return;
++    }
++
+     g_variant_get (parameters, "(&s)", &engine_name);
+ 
+     if (!bus_input_context_has_focus (context)) {
+diff --git a/src/ibusshare.c b/src/ibusshare.c
+index 5ab6e889..57e3ef14 100644
+--- a/src/ibusshare.c
++++ b/src/ibusshare.c
+@@ -2,7 +2,7 @@
+ /* vim:set et sts=4: */
+ /* ibus - The Input Bus
+  * Copyright (C) 2008-2010 Peng Huang <shawn.p.huang@gmail.com>
+- * Copyright (C) 2015-2023 Takao Fujiwara <takao.fujiwara1@gmail.com>
++ * Copyright (C) 2015-2024 Takao Fujiwara <takao.fujiwara1@gmail.com>
+  * Copyright (C) 2008-2018 Red Hat, Inc.
+  *
+  * This library is free software; you can redistribute it and/or
+diff --git a/src/tests/ibus-bus.c b/src/tests/ibus-bus.c
+index d6b105cf..76120354 100644
+--- a/src/tests/ibus-bus.c
++++ b/src/tests/ibus-bus.c
+@@ -802,20 +802,29 @@ start_set_preload_engines_async (void)
+             NULL); /* user_data */
+ }
+ 
++typedef struct _ExitAsyncData {
++    gboolean has_socket_path;
++    gboolean exited;
++    guint    timeout_id;
++} ExitAsyncData;
++
+ static void
+ _socket_changed_cb (GFileMonitor       *monitor,
+                     GFile              *file,
+                     GFile              *other_file,
+                     GFileMonitorEvent   event_type,
+-                    IBusBus            *bus)
++                    ExitAsyncData      *data)
+ {
+     switch (event_type) {
+     case G_FILE_MONITOR_EVENT_CHANGED:
+         g_debug ("IBus socket file is changed");
+         call_next_async_function ();
++        data->exited = TRUE;
+         g_signal_handlers_disconnect_by_func (monitor,
+                                               G_CALLBACK (_socket_changed_cb),
+-                                              NULL);
++                                              data);
++        if (data->timeout_id)
++            g_source_remove (data->timeout_id);
+         g_object_unref (monitor);
+         break;
+     case G_FILE_MONITOR_EVENT_CREATED:
+@@ -829,6 +838,33 @@ _socket_changed_cb (GFileMonitor       *monitor,
+     }
+ }
+ 
++static gboolean
++_exit_timeout (gpointer user_data)
++{
++    g_error ("start_exit_async() is timeout. You might run ibus-daemon " \
++             "with systemd under GNOME and the exit API does not work. " \
++             "You need to export IBUS_DAEMON_WITH_SYSTEMD=1 .\n");
++    return G_SOURCE_REMOVE;
++}
++
++static void
++finish_ibus_restart_async (GPid      pid,
++                           gint      status,
++                           gpointer *user_data)
++{
++    ExitAsyncData *data = (ExitAsyncData *)user_data;
++    g_spawn_close_pid (pid);
++    if (data->has_socket_path == FALSE) {
++        g_debug ("ibus_bus_exit_finish: OK socket file: none");
++        g_usleep (G_USEC_PER_SEC);
++        call_next_async_function ();
++    } else {
++        g_debug ("ibus_bus_exit_finish: OK socket file: monitored");
++        if (!data->exited)
++            data->timeout_id = g_timeout_add_seconds (10, _exit_timeout, NULL);
++    }
++}
++
+ static void
+ finish_exit_async (GObject *source_object,
+                    GAsyncResult *res,
+@@ -838,25 +874,32 @@ finish_exit_async (GObject *source_object,
+     gboolean result = ibus_bus_exit_async_finish (bus,
+                                                   res,
+                                                   &error);
+-    gboolean has_socket_path = GPOINTER_TO_INT (user_data);
++    ExitAsyncData *data = (ExitAsyncData *)user_data;
+     if (error) {
+         g_warning ("Failed to ibus_bus_exit(): %s", error->message);
+         g_error_free (error);
+     }
+     g_assert (result);
+-    if (has_socket_path == FALSE) {
++    g_assert (data);
++    if (data->has_socket_path == FALSE) {
+         g_debug ("ibus_bus_exit_finish: OK socket file: none");
+         g_usleep (G_USEC_PER_SEC);
+         call_next_async_function ();
+     } else {
+         g_debug ("ibus_bus_exit_finish: OK socket file: monitored");
++        if (!data->exited)
++            data->timeout_id = g_timeout_add_seconds (10, _exit_timeout, NULL);
+     }
+ }
+ 
+ static void
+ start_exit_async (void)
+ {
+-    gboolean has_socket_path = FALSE;
++    static ExitAsyncData data = {
++        .has_socket_path = FALSE,
++        .exited          = FALSE,
++        .timeout_id      = 0
++    };
+     /* When `./runtest ibus-bus` runs, ibus-daemon sometimes failed to
+      * restart because closing a file descriptor was failed in
+      * bus/server.c:_restart_server() with a following error:
+@@ -879,7 +922,7 @@ start_exit_async (void)
+         g_assert (address_path);
+         file = g_file_new_for_path (address_path);
+         g_assert (file);
+-        has_socket_path = TRUE;
++        data.has_socket_path = TRUE;
+         monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, NULL, &error);
+         if (error) {
+             g_warning ("Failed to monitor socket file: %s", error->message);
+@@ -887,15 +930,38 @@ start_exit_async (void)
+         }
+         g_assert (monitor);
+         g_signal_connect (monitor, "changed",
+-                          G_CALLBACK (_socket_changed_cb), NULL);
++                          G_CALLBACK (_socket_changed_cb),
++                          &data);
+         g_object_unref (file);
+     }
+-    ibus_bus_exit_async (bus,
+-                         TRUE, /* restart */
+-                         -1, /* timeout */
+-                         NULL, /* cancellable */
+-                         finish_exit_async,
+-                         GINT_TO_POINTER (has_socket_path)); /* user_data */
++    /* When ibus-daemon runs with systemd, restarting the daemon with
++     * ibus_bus_exit_async() does not work so runs `ibus restart` command
++     * with IBUS_DAEMON_WITH_SYSTEMD variable instead.
++     */
++    if (g_getenv ("IBUS_DAEMON_WITH_SYSTEMD")) {
++        gchar *argv[] = { "ibus", "restart", NULL };
++        GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD \
++                            | G_SPAWN_SEARCH_PATH \
++                            | G_SPAWN_STDOUT_TO_DEV_NULL \
++                            | G_SPAWN_STDERR_TO_DEV_NULL;
++        GPid pid = 0;
++        GError *error = NULL;
++        g_spawn_async (NULL, argv, NULL, flags, NULL, NULL, &pid, &error);
++        if (error) {
++            g_warning ("Failed to call ibus restart: %s", error->message);
++            g_error_free (error);
++        }
++        g_child_watch_add (pid,
++                           (GChildWatchFunc)finish_ibus_restart_async,
++                           &data);
++    } else {
++        ibus_bus_exit_async (bus,
++                             TRUE, /* restart */
++                             -1, /* timeout */
++                             NULL, /* cancellable */
++                             finish_exit_async,
++                             &data); /* user_data */
++    }
+ }
+ 
+ static gboolean
+diff --git a/src/tests/ibus-compose.c b/src/tests/ibus-compose.c
+index 21de7a22..660aee7f 100644
+--- a/src/tests/ibus-compose.c
++++ b/src/tests/ibus-compose.c
+@@ -7,19 +7,28 @@
+ #define RED   "\033[0;31m"
+ #define NC    "\033[0m"
+ 
+-IBusBus *m_bus;
+-gchar *m_compose_file;
+-IBusComposeTableEx *m_compose_table;
+-IBusEngine *m_engine;
+-gchar *m_srcdir;
+-
+-guint ibus_compose_key_flag (guint key);
+-
++static IBusBus *m_bus;
++static gchar *m_compose_file;
++static IBusComposeTableEx *m_compose_table;
++static IBusEngine *m_engine;
++static gchar *m_srcdir;
++static GMainLoop *m_loop;
++
++typedef enum {
++    TEST_CREATE_ENGINE,
++    TEST_COMMIT_TEXT
++} TestIDleCategory;
++
++typedef struct _TestIdleData {
++    TestIDleCategory category;
++    guint            idle_id;
++} TestIdleData;
++
++extern guint ibus_compose_key_flag (guint key);
+ static gboolean window_focus_in_event_cb (GtkWidget     *entry,
+                                           GdkEventFocus *event,
+                                           gpointer       data);
+ 
+-
+ static gchar *
+ get_compose_path ()
+ {
+@@ -51,17 +60,50 @@ get_compose_path ()
+ }
+ 
+ 
++gboolean
++idle_cb (gpointer user_data)
++{
++    TestIdleData *data = (TestIdleData *)user_data;
++    g_assert (data);
++    switch (data->category) {
++    case TEST_CREATE_ENGINE:
++        g_test_fail_printf ("\"create-engine\" signal is timeout.");
++        break;
++    case TEST_COMMIT_TEXT:
++        if (data->idle_id)
++            g_test_fail_printf ("Commiting composed chars is timeout.");
++        if (m_loop) {
++            if (g_main_loop_is_running (m_loop))
++                g_main_loop_quit (m_loop);
++            g_clear_pointer (&m_loop, g_main_loop_unref);
++            gtk_main_quit ();
++        }
++        data->idle_id = 0;
++        break;
++    default:
++        g_test_fail_printf ("Idle func is called by wrong category:%d.",
++                            data->category);
++        break;
++    }
++    return G_SOURCE_REMOVE;
++}
++
++
+ static IBusEngine *
+ create_engine_cb (IBusFactory *factory,
+                   const gchar *name,
+-                  gpointer     data)
++                  gpointer     user_data)
+ {
+     static int i = 1;
+     gchar *engine_path =
+             g_strdup_printf ("/org/freedesktop/IBus/engine/simpletest/%d",
+                              i++);
+     gchar *compose_path;
++    TestIdleData *data = (TestIdleData *)user_data;
+ 
++    g_assert (data);
++    /* Don't reset idle_id to avoid duplicated register_ibus_engine(). */
++    g_source_remove (data->idle_id);
+     m_engine = ibus_engine_new_with_type (IBUS_TYPE_ENGINE_SIMPLE,
+                                           name,
+                                           engine_path,
+@@ -75,28 +117,33 @@ create_engine_cb (IBusFactory *factory,
+         ibus_engine_simple_add_compose_file (IBUS_ENGINE_SIMPLE (m_engine),
+                                              compose_path);
+         m_compose_table = ibus_compose_table_load_cache (compose_path);
+-        if (m_compose_table == NULL)
+-            g_warning ("Your locale uses en_US compose table.");
+     }
+     g_free (compose_path);
+     return m_engine;
+ }
+ 
++
+ static gboolean
+ register_ibus_engine ()
+ {
++    static TestIdleData data = { .category = TEST_CREATE_ENGINE, .idle_id = 0 };
+     IBusFactory *factory;
+     IBusComponent *component;
+     IBusEngineDesc *desc;
+ 
++    if (data.idle_id) {
++        g_test_incomplete ("Test is called twice due to a timeout.");
++        return TRUE;
++    }
+     m_bus = ibus_bus_new ();
+     if (!ibus_bus_is_connected (m_bus)) {
+-        g_critical ("ibus-daemon is not running.");
++        g_test_fail_printf ("ibus-daemon is not running.");
+         return FALSE;
+     }
+     factory = ibus_factory_new (ibus_bus_get_connection (m_bus));
++    data.idle_id = g_timeout_add_seconds (20, idle_cb, &data);
+     g_signal_connect (factory, "create-engine",
+-                      G_CALLBACK (create_engine_cb), NULL);
++                      G_CALLBACK (create_engine_cb), &data);
+ 
+     component = ibus_component_new (
+             "org.freedesktop.IBus.SimpleTest",
+@@ -122,34 +169,29 @@ register_ibus_engine ()
+     return TRUE;
+ }
+ 
+-static gboolean
+-finit (gpointer data)
+-{
+-    g_test_incomplete ("time out");
+-    gtk_main_quit ();
+-    return FALSE;
+-}
+ 
+ static void
+-set_engine_cb (GObject *object, GAsyncResult *res, gpointer data)
++set_engine_cb (GObject      *object,
++               GAsyncResult *res,
++               gpointer      user_data)
+ {
+     IBusBus *bus = IBUS_BUS (object);
+-    GtkWidget *entry = GTK_WIDGET (data);
++    GtkWidget *entry = GTK_WIDGET (user_data);
+     GError *error = NULL;
++    static TestIdleData data = { .category = TEST_COMMIT_TEXT, .idle_id = 0 };
+     int i, j;
+     int index_stride;
+     IBusComposeTablePrivate *priv;
+ 
+     if (!ibus_bus_set_global_engine_async_finish (bus, res, &error)) {
+-        gchar *msg = g_strdup_printf ("set engine failed: %s", error->message);
+-        g_test_incomplete (msg);
+-        g_free (msg);
++        g_test_fail_printf ("set engine failed: %s", error->message);
+         g_error_free (error);
+         return;
+     }
+ 
+     if (m_compose_table == NULL) {
+-        gtk_main_quit ();
++        g_test_skip ("Your locale uses en_US compose table.");
++        idle_cb (&data);
+         return;
+     }
+ 
+@@ -157,6 +199,7 @@ set_engine_cb (GObject *object, GAsyncResult *res, gpointer data)
+     for (i = 0;
+          i < (m_compose_table->n_seqs * index_stride);
+          i += index_stride) {
++        data.idle_id = g_timeout_add_seconds (20, idle_cb, &data);
+         for (j = i; j < i + (index_stride - 2); j++) {
+             guint keyval = m_compose_table->data[j];
+             guint keycode = 0;
+@@ -172,12 +215,22 @@ set_engine_cb (GObject *object, GAsyncResult *res, gpointer data)
+             g_signal_emit_by_name (m_engine, "process-key-event",
+                                    keyval, keycode, modifiers, &retval);
+         }
++        /* Need to wait for calling window_inserted_text_cb() with
++         * g_main_loop_run() because the commit-text event could be cancelled
++         * by the next commit-text event with gnome-shell in Wayland.
++         */
++        g_main_loop_run (m_loop);
++        if (data.idle_id) {
++            g_source_remove (data.idle_id);
++            data.idle_id = 0;
++        }
+     }
+     priv = m_compose_table->priv;
+     if (priv) {
+         for (i = 0;
+              i < (priv->first_n_seqs * index_stride);
+              i += index_stride) {
++            data.idle_id = g_timeout_add_seconds (20, idle_cb, &data);
+             for (j = i; j < i + (index_stride - 2); j++) {
+                 guint keyval = priv->data_first[j];
+                 guint keycode = 0;
+@@ -193,13 +246,18 @@ set_engine_cb (GObject *object, GAsyncResult *res, gpointer data)
+                 g_signal_emit_by_name (m_engine, "process-key-event",
+                                        keyval, keycode, modifiers, &retval);
+             }
++            g_main_loop_run (m_loop);
++            if (data.idle_id) {
++                g_source_remove (data.idle_id);
++                data.idle_id = 0;
++            }
+         }
+     }
+ 
+     g_signal_handlers_disconnect_by_func (entry, 
+                                           G_CALLBACK (window_focus_in_event_cb),
+                                           NULL);
+-    g_timeout_add_seconds (10, finit, NULL);
++    data.idle_id = g_timeout_add_seconds (10, idle_cb, &data);
+ }
+ 
+ static gboolean
+@@ -215,12 +273,13 @@ window_focus_in_event_cb (GtkWidget *entry, GdkEventFocus *event, gpointer data)
+     return FALSE;
+ }
+ 
++
+ static void
+ window_inserted_text_cb (GtkEntryBuffer *buffer,
+                          guint           position,
+                          const gchar    *chars,
+                          guint           nchars,
+-                         gpointer        data)
++                         gpointer        user_data)
+ {
+ /* https://gitlab.gnome.org/GNOME/gtk/commit/9981f46e0b
+  * The latest GTK does not emit "inserted-text" when the text is "".
+@@ -234,8 +293,9 @@ window_inserted_text_cb (GtkEntryBuffer *buffer,
+     int seq;
+     gunichar code = g_utf8_get_char (chars);
+     const gchar *test;
+-    GtkEntry *entry = GTK_ENTRY (data);
++    GtkEntry *entry = GTK_ENTRY (user_data);
+     IBusComposeTablePrivate *priv;
++    static TestIdleData data = { .category = TEST_COMMIT_TEXT, .idle_id = 0 };
+ 
+     g_assert (m_compose_table != NULL);
+ 
+@@ -302,21 +362,26 @@ window_inserted_text_cb (GtkEntryBuffer *buffer,
+             stride = 0;
+             seq = 0;
+         } else {
+-            gtk_main_quit ();
++            /* Finish tests */
++            idle_cb (&data);
+             return;
+         }
+     }
+     if (enable_32bit && seq == priv->first_n_seqs) {
+-        gtk_main_quit ();
++        /* Finish tests */
++        idle_cb (&data);
+         return;
+     }
+ 
+ #if !GTK_CHECK_VERSION (3, 22, 16)
+     n_loop++;
+ #endif
++
+     gtk_entry_set_text (entry, "");
++    g_main_loop_quit (m_loop);
+ }
+ 
++
+ static void
+ create_window ()
+ {
+@@ -335,14 +400,13 @@ create_window ()
+     gtk_widget_show_all (window);
+ }
+ 
++
+ static void
+ test_compose (void)
+ {
+     GLogLevelFlags flags;
+-    if (!register_ibus_engine ()) {
+-        g_test_fail ();
++    if (!register_ibus_engine ())
+         return;
+-    }
+ 
+     create_window ();
+     /* FIXME:
+@@ -357,6 +421,7 @@ test_compose (void)
+     g_log_set_always_fatal (flags);
+ }
+ 
++
+ int
+ main (int argc, char *argv[])
+ {
+@@ -380,10 +445,12 @@ main (int argc, char *argv[])
+ #else
+     test_name = g_strdup (g_getenv ("LANG"));
+ #endif
+-    if (!test_name || !g_ascii_strncasecmp (test_name, "en_US", 5)) {
++    if (m_compose_file &&
++        (!test_name || !g_ascii_strncasecmp (test_name, "en_US", 5))) {
+         g_free (test_name);
+         test_name = g_path_get_basename (m_compose_file);
+     }
++    m_loop = g_main_loop_new (NULL, TRUE);
+     test_path = g_build_filename ("/ibus-compose", test_name, NULL);
+     g_test_add_func (test_path, test_compose);
+     g_free (test_path);
+diff --git a/src/tests/ibus-engine-switch.c b/src/tests/ibus-engine-switch.c
+index a1eeba2a..b50bac59 100644
+--- a/src/tests/ibus-engine-switch.c
++++ b/src/tests/ibus-engine-switch.c
+@@ -1,6 +1,7 @@
+ /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+ 
+ #include <string.h>
++#include <unistd.h>
+ #include "ibus.h"
+ 
+ static IBusBus *bus;
+@@ -47,6 +48,7 @@ change_context_engine (IBusInputContext *context)
+         g_assert_cmpstr (ibus_engine_desc_get_name (engine_desc),
+                          ==,
+                          engine_names[i]);
++        g_object_unref (engine_desc);
+     }
+ }
+ 
+@@ -61,7 +63,7 @@ static void
+ global_engine_changed_cb (IBusBus *bus, gchar *name, gpointer user_data)
+ {
+     GlobalEngineChangedData *data = (GlobalEngineChangedData *) user_data;
+-    if (data->count++ == 0)
++    if (data->count++ == 1)
+         ibus_quit ();
+ }
+ 
+@@ -84,6 +86,35 @@ change_global_engine_cb (gpointer user_data)
+     return FALSE;
+ }
+ 
++gboolean
++_wait_for_key_release_cb (gpointer user_data)
++{
++    GMainLoop *loop = (GMainLoop *)user_data;
++    /* If this program is invoked by manual with Enter key in GNOME
++     * Wayland session, ibus_input_context_focus_in() can be called in
++     * test_context_engine_set_by_global() before the key release of
++     * the Enter key so ibus/bus/inputcontext.c:_ic_process_key_event()
++     * could call another bus_input_context_focus_in() in that test case
++     * and fail.
++     */
++    g_test_message ("Wait for 3 seconds for key release event");
++    g_main_loop_quit (loop);
++    return G_SOURCE_REMOVE;
++}
++
++static void
++test_init (void)
++{
++    char *tty_name = ttyname (STDIN_FILENO);
++    GMainLoop *loop = g_main_loop_new (NULL, TRUE);
++    g_test_message ("Test on %s", tty_name ? tty_name : "(null)");
++    if (tty_name && g_strstr_len (tty_name, -1, "pts")) {
++        g_timeout_add_seconds (3, _wait_for_key_release_cb, loop);
++        g_main_loop_run (loop);
++    }
++    g_main_loop_unref (loop);
++}
++
+ static void
+ test_global_engine (void)
+ {
+@@ -105,6 +136,10 @@ test_global_engine (void)
+     } else {
+         data.reverse = FALSE;
+     }
++    g_test_message ("Initial engine name: %s",
++                    desc ? ibus_engine_desc_get_name (desc) : "(null)");
++    if (desc)
++        g_object_unref (desc);
+ 
+     data.count = 0;
+ 
+@@ -112,7 +147,7 @@ test_global_engine (void)
+                                    "global-engine-changed",
+                                    G_CALLBACK (global_engine_changed_cb),
+                                    &data);
+-    data.timeout_id = g_timeout_add_seconds (1, timeout_cb, &data);
++    data.timeout_id = g_timeout_add_seconds (3, timeout_cb, &data);
+     data.idle_id = g_idle_add ((GSourceFunc) change_global_engine_cb, &data);
+ 
+     ibus_main ();
+@@ -144,6 +179,7 @@ test_context_engine (void)
+     change_context_engine (context);
+     engine_desc = ibus_input_context_get_engine (context);
+     g_assert_cmpstr (ibus_engine_desc_get_name (engine_desc), ==, AFTER_ENGINE);
++    g_object_unref (engine_desc);
+ 
+     g_object_unref (context);
+ }
+@@ -171,6 +207,7 @@ test_context_engine_set_by_global (void)
+ 
+     engine_desc = ibus_input_context_get_engine (context);
+     g_assert_cmpstr (ibus_engine_desc_get_name (engine_desc), ==, AFTER_ENGINE);
++    g_object_unref (engine_desc);
+ 
+     g_object_unref (context);
+ }
+@@ -199,9 +236,11 @@ test_context_engine_set_by_focus (void)
+ 
+     engine_desc = ibus_input_context_get_engine (context);
+     g_assert_cmpstr (ibus_engine_desc_get_name (engine_desc), ==, "dummy");
++    g_object_unref (engine_desc);
+ 
+     engine_desc = ibus_input_context_get_engine (another_context);
+     g_assert_cmpstr (ibus_engine_desc_get_name (engine_desc), ==, AFTER_ENGINE);
++    g_object_unref (engine_desc);
+ 
+     g_object_unref (context);
+     g_object_unref (another_context);
+@@ -220,6 +259,8 @@ main (gint    argc,
+ 
+     ibus_bus_set_watch_ibus_signal (bus, TRUE);
+ 
++    g_test_add_func ("/ibus/engine-switch/test-init",
++                     test_init);
+     g_test_add_func ("/ibus/engine-switch/global-engine",
+                      test_global_engine);
+     g_test_add_func ("/ibus/engine-switch/context-engine",
+diff --git a/src/tests/xkb-latin-layouts b/src/tests/xkb-latin-layouts
+index 92464234..45c99358 100755
+--- a/src/tests/xkb-latin-layouts
++++ b/src/tests/xkb-latin-layouts
+@@ -120,6 +120,15 @@ EOF_READ_XKB
+ 
+ main()
+ {
++    if [ x"$DISPLAY" = x ] ; then
++        echo "skip: No display. Maybe headless mode."
++        exit 77
++    fi
++    if ! which setxkbmap > /dev/null ; then
++        echo "skip: No setxkbmap"
++        exit 77
++    fi
++
+     parse_args "$@"
+ 
+     if [ x"$INSTALLED_SCHEMAS_DIR" != x ] ; then
+-- 
+2.45.0
+
+From 44f9a5957e4c53b4a6e6b3f42b56f2d20d58a21c Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Sat, 27 Jul 2024 19:04:39 +0900
+Subject: [PATCH 3/6] src/tests: Enable GTK4
+
+Seems GTK3 does not work in GNOME Wayland headless session to get
+GdkMonitor in gtk_init() and trying to migrate the test cases to
+GTK4.
+Calling ibus_init() twice with g_static_resource_init() causes a
+memory error and fix it.
+
+BUG=https://github.com/ibus/ibus/pull/2657
+---
+ src/tests/Makefile.am     |   8 +--
+ src/tests/ibus-compose.c  | 147 ++++++++++++++++++++++++++++++++------
+ src/tests/ibus-keypress.c | 146 ++++++++++++++++++++++++++++++-------
+ 3 files changed, 251 insertions(+), 50 deletions(-)
+
+diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
+index 6c4c86cf..10acd0a7 100644
+--- a/src/tests/Makefile.am
++++ b/src/tests/Makefile.am
+@@ -182,8 +182,8 @@ ibus_bus_SOURCES = ibus-bus.c
+ ibus_bus_LDADD = $(prog_ldadd)
+ 
+ ibus_compose_SOURCES = ibus-compose.c
+-ibus_compose_CFLAGS = @GTK3_CFLAGS@
+-ibus_compose_LDADD = $(prog_ldadd) @GTK3_LIBS@
++ibus_compose_CFLAGS = @GTK4_CFLAGS@
++ibus_compose_LDADD = $(prog_ldadd) @GTK4_LIBS@
+ 
+ ibus_config_SOURCES = ibus-config.c
+ ibus_config_LDADD = $(prog_ldadd)
+@@ -207,8 +207,8 @@ ibus_keynames_SOURCES = ibus-keynames.c
+ ibus_keynames_LDADD = $(prog_ldadd)
+ 
+ ibus_keypress_SOURCES = ibus-keypress.c
+-ibus_keypress_CFLAGS = @GTK3_CFLAGS@ @XTEST_CFLAGS@
+-ibus_keypress_LDADD = $(prog_ldadd) @GTK3_LIBS@ @XTEST_LIBS@
++ibus_keypress_CFLAGS = @GTK4_CFLAGS@ @XTEST_CFLAGS@
++ibus_keypress_LDADD = $(prog_ldadd) @GTK4_LIBS@ @XTEST_LIBS@
+ 
+ ibus_registry_SOURCES = ibus-registry.c
+ ibus_registry_LDADD = $(prog_ldadd)
+diff --git a/src/tests/ibus-compose.c b/src/tests/ibus-compose.c
+index 660aee7f..57851b0a 100644
+--- a/src/tests/ibus-compose.c
++++ b/src/tests/ibus-compose.c
+@@ -13,6 +13,9 @@ static IBusComposeTableEx *m_compose_table;
+ static IBusEngine *m_engine;
+ static gchar *m_srcdir;
+ static GMainLoop *m_loop;
++#if GTK_CHECK_VERSION (4, 0, 0)
++static gboolean m_list_toplevel;
++#endif
+ 
+ typedef enum {
+     TEST_CREATE_ENGINE,
+@@ -25,9 +28,16 @@ typedef struct _TestIdleData {
+ } TestIdleData;
+ 
+ extern guint ibus_compose_key_flag (guint key);
++
++#if GTK_CHECK_VERSION (4, 0, 0)
++static void     event_controller_enter_cb (GtkEventController *controller,
++                                           gpointer            user_data);
++#else
+ static gboolean window_focus_in_event_cb (GtkWidget     *entry,
+                                           GdkEventFocus *event,
+                                           gpointer       data);
++#endif
++
+ 
+ static gchar *
+ get_compose_path ()
+@@ -76,7 +86,11 @@ idle_cb (gpointer user_data)
+             if (g_main_loop_is_running (m_loop))
+                 g_main_loop_quit (m_loop);
+             g_clear_pointer (&m_loop, g_main_loop_unref);
++#if GTK_CHECK_VERSION (4, 0, 0)
++            m_list_toplevel = FALSE;
++#else
+             gtk_main_quit ();
++#endif
+         }
+         data->idle_id = 0;
+         break;
+@@ -170,13 +184,28 @@ register_ibus_engine ()
+ }
+ 
+ 
++static void
++window_destroy_cb (void)
++{
++#if GTK_CHECK_VERSION (4, 0, 0)
++    m_list_toplevel = FALSE;
++#else
++    gtk_main_quit ();
++#endif
++}
++
++
+ static void
+ set_engine_cb (GObject      *object,
+                GAsyncResult *res,
+                gpointer      user_data)
+ {
+     IBusBus *bus = IBUS_BUS (object);
++#if GTK_CHECK_VERSION (4, 0, 0)
++    GtkEventController *controller = GTK_EVENT_CONTROLLER (user_data);
++#else
+     GtkWidget *entry = GTK_WIDGET (user_data);
++#endif
+     GError *error = NULL;
+     static TestIdleData data = { .category = TEST_COMMIT_TEXT, .idle_id = 0 };
+     int i, j;
+@@ -254,24 +283,55 @@ set_engine_cb (GObject      *object,
+         }
+     }
+ 
++#if GTK_CHECK_VERSION (4, 0, 0)
++    g_signal_handlers_disconnect_by_func (
++            controller,
++            G_CALLBACK (event_controller_enter_cb),
++            NULL);
++#else
+     g_signal_handlers_disconnect_by_func (entry, 
+                                           G_CALLBACK (window_focus_in_event_cb),
+                                           NULL);
++#endif
+     data.idle_id = g_timeout_add_seconds (10, idle_cb, &data);
+ }
+ 
+-static gboolean
+-window_focus_in_event_cb (GtkWidget *entry, GdkEventFocus *event, gpointer data)
++
++static void
++set_engine (gpointer user_data)
+ {
++    g_test_message ("set_engine() is calling");
+     g_assert (m_bus != NULL);
+     ibus_bus_set_global_engine_async (m_bus,
+                                       "xkbtest:us::eng",
+                                       -1,
+                                       NULL,
+                                       set_engine_cb,
+-                                      entry);
++                                      user_data);
++}
++
++
++#if GTK_CHECK_VERSION (4, 0, 0)
++static void
++event_controller_enter_cb (GtkEventController *controller,
++                           gpointer            user_data)
++{
++    g_test_message ("EventController emits \"enter\" signal");
++    set_engine (controller);
++}
++
++#else
++
++static gboolean
++window_focus_in_event_cb (GtkWidget     *entry,
++                          GdkEventFocus *event,
++                          gpointer       data)
++{
++    g_test_message ("Entry emits \"focus-in-event\" signal");
++    set_engine (entry);
+     return FALSE;
+ }
++#endif
+ 
+ 
+ static void
+@@ -293,7 +353,9 @@ window_inserted_text_cb (GtkEntryBuffer *buffer,
+     int seq;
+     gunichar code = g_utf8_get_char (chars);
+     const gchar *test;
++#if ! GTK_CHECK_VERSION (4, 0, 0)
+     GtkEntry *entry = GTK_ENTRY (user_data);
++#endif
+     IBusComposeTablePrivate *priv;
+     static TestIdleData data = { .category = TEST_COMMIT_TEXT, .idle_id = 0 };
+ 
+@@ -306,6 +368,10 @@ window_inserted_text_cb (GtkEntryBuffer *buffer,
+         n_loop = 0;
+         return;
+     }
++#endif
++#if GTK_CHECK_VERSION (4, 0, 0)
++    if (code == 0)
++        return;
+ #endif
+     i = stride + (m_compose_table->max_seq_len + 2) - 2;
+     seq = (i + 2) / (m_compose_table->max_seq_len + 2);
+@@ -318,12 +384,12 @@ window_inserted_text_cb (GtkEntryBuffer *buffer,
+             test = RED "FAIL" NC;
+             g_test_fail ();
+         }
+-        g_print ("%05d/%05d %s expected: %04X typed: %04X\n",
+-                 seq,
+-                 m_compose_table->n_seqs,
+-                 test,
+-                 m_compose_table->data[i],
+-                 code);
++        g_test_message ("%05d/%05d %s expected: %04X typed: %04X",
++                        seq,
++                        m_compose_table->n_seqs,
++                        test,
++                        m_compose_table->data[i],
++                        code);
+     } else {
+         const gchar *p = chars;
+         guint num = priv->data_first[i];
+@@ -344,14 +410,14 @@ window_inserted_text_cb (GtkEntryBuffer *buffer,
+             test = RED "FAIL" NC;
+             g_test_fail ();
+         }
+-        g_print ("%05d/%05ld %s expected: %04X[%d] typed: %04X\n",
+-                 seq,
+-                 priv->first_n_seqs,
+-                 test,
+-                 valid_output ? priv->data_second[index]
+-                         : priv->data_second[index + j],
+-                 valid_output ? index + num : index + j,
+-                 valid_output ? g_utf8_get_char (chars) : code);
++        g_test_message ("%05d/%05ld %s expected: %04X[%d] typed: %04X",
++                        seq,
++                        priv->first_n_seqs,
++                        test,
++                        valid_output ? priv->data_second[index]
++                                : priv->data_second[index + j],
++                        valid_output ? index + num : index + j,
++                        valid_output ? g_utf8_get_char (chars) : code);
+     }
+ 
+     stride += m_compose_table->max_seq_len + 2;
+@@ -377,7 +443,11 @@ window_inserted_text_cb (GtkEntryBuffer *buffer,
+     n_loop++;
+ #endif
+ 
++#if GTK_CHECK_VERSION (4, 0, 0)
++    gtk_entry_buffer_set_text (buffer, "", 0);
++#else
+     gtk_entry_set_text (entry, "");
++#endif
+     g_main_loop_quit (m_loop);
+ }
+ 
+@@ -385,19 +455,43 @@ window_inserted_text_cb (GtkEntryBuffer *buffer,
+ static void
+ create_window ()
+ {
+-    GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+-    GtkWidget *entry = gtk_entry_new ();
++    GtkWidget *window;
++    GtkWidget *entry;
+     GtkEntryBuffer *buffer;
++#if GTK_CHECK_VERSION (4, 0, 0)
++    GtkEventController *controller;
++    GtkEditable *text;
++
++    window = gtk_window_new ();
++#else
++    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
++#endif
++    entry = gtk_entry_new ();
+ 
+     g_signal_connect (window, "destroy",
+-                      G_CALLBACK (gtk_main_quit), NULL);
++                      G_CALLBACK (window_destroy_cb), NULL);
++#if GTK_CHECK_VERSION (4, 0, 0)
++    controller = gtk_event_controller_focus_new ();
++    text = gtk_editable_get_delegate (GTK_EDITABLE (entry));
++    g_signal_connect (controller, "enter",
++                      G_CALLBACK (event_controller_enter_cb), NULL);
++    gtk_widget_add_controller (GTK_WIDGET (text), controller);
++#else
+     g_signal_connect (entry, "focus-in-event",
+                       G_CALLBACK (window_focus_in_event_cb), NULL);
++#endif
+     buffer = gtk_entry_get_buffer (GTK_ENTRY (entry));
+     g_signal_connect (buffer, "inserted-text",
+                       G_CALLBACK (window_inserted_text_cb), entry);
++
++#if GTK_CHECK_VERSION (4, 0, 0)
++    gtk_window_set_child (GTK_WINDOW (window), entry);
++    gtk_window_set_focus (GTK_WINDOW (window), entry);
++    gtk_window_present (GTK_WINDOW (window));
++#else
+     gtk_container_add (GTK_CONTAINER (window), entry);
+     gtk_widget_show_all (window);
++#endif
+ }
+ 
+ 
+@@ -408,6 +502,9 @@ test_compose (void)
+     if (!register_ibus_engine ())
+         return;
+ 
++#if GTK_CHECK_VERSION (4, 0, 0)
++    m_list_toplevel = TRUE;
++#endif
+     create_window ();
+     /* FIXME:
+      * IBusIMContext opens GtkIMContextSimple as the slave and
+@@ -417,7 +514,13 @@ test_compose (void)
+      " "GTK+ supports to output one char only: "
+      */
+     flags = g_log_set_always_fatal (G_LOG_LEVEL_CRITICAL);
++#if GTK_CHECK_VERSION (4, 0, 0)
++    gtk_window_list_toplevels ();
++    while (m_list_toplevel)
++        g_main_context_iteration (NULL, TRUE);
++#else
+     gtk_main ();
++#endif
+     g_log_set_always_fatal (flags);
+ }
+ 
+@@ -435,7 +538,11 @@ main (int argc, char *argv[])
+     if (!g_setenv ("NO_AT_BRIDGE", "1", TRUE))
+         g_message ("Failed setenv NO_AT_BRIDGE\n");
+     g_test_init (&argc, &argv, NULL);
++#if GTK_CHECK_VERSION (4, 0, 0)
++    gtk_init ();
++#else
+     gtk_init (&argc, &argv);
++#endif
+ 
+     m_srcdir = (argc > 1 && strlen (argv[1]) < FILENAME_MAX)
+             ? g_strdup (argv[1]) : g_strdup (".");
+diff --git a/src/tests/ibus-keypress.c b/src/tests/ibus-keypress.c
+index d44f39b2..a9288260 100644
+--- a/src/tests/ibus-keypress.c
++++ b/src/tests/ibus-keypress.c
+@@ -1,12 +1,16 @@
+ #include <gtk/gtk.h>
++#if GTK_CHECK_VERSION (4, 0, 0)
++#include <gdk/x11/gdkx.h>
++#else
+ #include <gdk/gdkx.h>
++#endif
+ #include "ibus.h"
+ #include <stdlib.h>
+ #include <X11/Xlib.h>
+ #include <X11/extensions/XTest.h>
+ 
+ #ifdef GDK_WINDOWING_WAYLAND
+-#if GTK_CHECK_VERSION (3, 98, 4)
++#if GTK_CHECK_VERSION (4, 0, 0)
+ #include <gdk/wayland/gdkwayland.h>
+ #else
+ #include <gdk/gdkwayland.h>
+@@ -51,12 +55,18 @@ static const gunichar test_results[][60] = {
+ };
+ 
+ 
+-IBusBus *m_bus;
+-IBusEngine *m_engine;
++static IBusBus *m_bus;
++static IBusEngine *m_engine;
++#if GTK_CHECK_VERSION (4, 0, 0)
++static gboolean m_list_toplevel;
+ 
++static gboolean event_controller_enter_cb (GtkEventController *controller,
++                                           gpointer            user_data);
++#else
+ static gboolean window_focus_in_event_cb (GtkWidget     *entry,
+                                           GdkEventFocus *event,
+                                           gpointer       data);
++#endif
+ 
+ static IBusEngine *
+ create_engine_cb (IBusFactory *factory, const gchar *name, gpointer data)
+@@ -118,7 +128,11 @@ static gboolean
+ finit (gpointer data)
+ {
+     g_critical ("time out");
++#if GTK_CHECK_VERSION (4, 0, 0)
++    m_list_toplevel = FALSE;
++#else
+     gtk_main_quit ();
++#endif
+     return FALSE;
+ }
+ 
+@@ -173,27 +187,41 @@ send_key_event (Display *xdisplay,
+     }
+ }
+ 
++static void
++window_destroy_cb (void)
++{
++#if GTK_CHECK_VERSION (4, 0, 0)
++    m_list_toplevel = FALSE;
++#else
++    gtk_main_quit ();
++#endif
++}
++
+ static void
+ set_engine_cb (GObject      *object,
+                GAsyncResult *res,
+-               gpointer      data)
++               gpointer      user_data)
+ {
+     IBusBus *bus = IBUS_BUS (object);
+-    GtkWidget *entry = GTK_WIDGET (data);
++#if ! GTK_CHECK_VERSION (4, 0, 0)
++    GtkWidget *entry = GTK_WIDGET (user_data);
++#endif
+     GdkDisplay *display;
+     Display *xdisplay = NULL;
+     GError *error = NULL;
+     int i, j;
+ 
+-    g_assert (GTK_IS_ENTRY (entry));
+-
+     if (!ibus_bus_set_global_engine_async_finish (bus, res, &error)) {
+         g_critical ("set engine failed: %s", error->message);
+         g_error_free (error);
+         return;
+     }
+ 
++#if GTK_CHECK_VERSION (4, 0, 0)
++    display = gdk_display_get_default ();
++#else
+     display = gtk_widget_get_display (entry);
++#endif
+     g_assert (GDK_IS_X11_DISPLAY (display));
+     xdisplay = gdk_x11_display_get_xdisplay (display);
+     g_return_if_fail (xdisplay);
+@@ -210,8 +238,8 @@ set_engine_cb (GObject      *object,
+     g_timeout_add_seconds (10, finit, NULL);
+ }
+ 
+-static gboolean
+-window_focus_in_event_cb (GtkWidget *entry, GdkEventFocus *event, gpointer data)
++static void
++set_engine (gpointer user_data)
+ {
+     g_assert (m_bus != NULL);
+     ibus_bus_set_global_engine_async (m_bus,
+@@ -219,18 +247,40 @@ window_focus_in_event_cb (GtkWidget *entry, GdkEventFocus *event, gpointer data)
+                                       -1,
+                                       NULL,
+                                       set_engine_cb,
+-                                      entry);
++                                      user_data);
++}
++
++#if GTK_CHECK_VERSION (4, 0, 0)
++static gboolean
++event_controller_enter_cb (GtkEventController *controller,
++                           gpointer            user_data)
++{
++    set_engine (controller);
++    return FALSE;
++}
++
++#else
++
++static gboolean
++window_focus_in_event_cb (GtkWidget     *entry,
++                          GdkEventFocus *event,
++                          gpointer       user_data)
++{
++    set_engine (entry);
+     return FALSE;
+ }
++#endif
+ 
+ static void
+ window_inserted_text_cb (GtkEntryBuffer *buffer,
+                          guint           position,
+                          const gchar    *chars,
+                          guint           nchars,
+-                         gpointer        data)
++                         gpointer        user_data)
+ {
+-    GtkWidget *entry = data;
++#if ! GTK_CHECK_VERSION (4, 0, 0)
++    GtkWidget *entry = user_data;
++#endif
+     static int i = 0;
+     static int j = 0;
+ 
+@@ -242,10 +292,19 @@ window_inserted_text_cb (GtkEntryBuffer *buffer,
+         g_print ("\n");
+         i++;
+         j = 0;
+-        if (test_results[i][0] == 0)
++        if (test_results[i][0] == 0) {
++#if GTK_CHECK_VERSION (4, 0, 0)
++            m_list_toplevel = FALSE;
++#else
+             gtk_main_quit ();
+-        else
++#endif
++        } else {
++#if GTK_CHECK_VERSION (4, 0, 0)
++            gtk_entry_buffer_set_text (buffer, "", 1);
++#else
+             gtk_entry_set_text (GTK_ENTRY (entry), "");
++#endif
++        }
+         return;
+     }
+     g_assert (g_utf8_get_char (chars) == test_results[i][j]);
+@@ -255,19 +314,39 @@ window_inserted_text_cb (GtkEntryBuffer *buffer,
+ static void
+ create_window ()
+ {
+-    GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
++    GtkWidget *window;
+     GtkWidget *entry = gtk_entry_new ();
+     GtkEntryBuffer *buffer;
++#if GTK_CHECK_VERSION (4, 0, 0)
++    GtkEventController *controller;
++
++    window = gtk_window_new ();
++#else
++    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
++#endif
+ 
+     g_signal_connect (window, "destroy",
+-                      G_CALLBACK (gtk_main_quit), NULL);
++                      G_CALLBACK (window_destroy_cb), NULL);
++#if GTK_CHECK_VERSION (4, 0, 0)
++    controller = gtk_event_controller_focus_new ();
++    g_signal_connect (controller, "enter",
++                      G_CALLBACK (event_controller_enter_cb), NULL);
++    gtk_widget_add_controller (window, controller);
++#else
+     g_signal_connect (entry, "focus-in-event",
+                       G_CALLBACK (window_focus_in_event_cb), NULL);
++#endif
+     buffer = gtk_entry_get_buffer (GTK_ENTRY (entry));
+     g_signal_connect (buffer, "inserted-text",
+                       G_CALLBACK (window_inserted_text_cb), entry);
++#if GTK_CHECK_VERSION (4, 0, 0)
++    gtk_window_set_child (GTK_WINDOW (window), entry);
++    gtk_window_set_focus (GTK_WINDOW (window), entry);
++    gtk_window_present (GTK_WINDOW (window));
++#else
+     gtk_container_add (GTK_CONTAINER (window), entry);
+     gtk_widget_show_all (window);
++#endif
+ }
+ 
+ static void
+@@ -277,6 +356,16 @@ test_keypress (void)
+     int status = 0;
+     GError *error = NULL;
+ 
++#ifdef GDK_WINDOWING_WAYLAND
++    {
++        GdkDisplay *display = gdk_display_get_default ();
++        if (GDK_IS_WAYLAND_DISPLAY (display)) {
++            g_test_skip_printf ("setxkbmap and XTEST do not work in Wayland.");
++            return;
++        }
++    }
++#endif
++
+     /* localectl does not change the session keymap. */
+     path = g_find_program_in_path ("setxkbmap");
+     if (path) {
+@@ -287,33 +376,38 @@ test_keypress (void)
+     g_free (path);
+     g_assert (register_ibus_engine ());
+ 
++#if GTK_CHECK_VERSION (4, 0, 0)
++    m_list_toplevel = TRUE;
++#endif
+     create_window ();
++#if GTK_CHECK_VERSION (4, 0, 0)
++    while (m_list_toplevel)
++        g_main_context_iteration (NULL, TRUE);
++#else
+     gtk_main ();
++#endif
+ }
+ 
+ int
+ main (int argc, char *argv[])
+ {
+-    ibus_init ();
++    /* ibus_init() should not be called here because
++     * ibus_init() is called from IBus Gtk4 IM module even if
++     * GTK_IM_MODULE=wayland is exported.
++     */
+     /* Avoid a warning of "AT-SPI: Could not obtain desktop path or name"
+      * with gtk_main().
+      */
+     if (!g_setenv ("NO_AT_BRIDGE", "1", TRUE))
+         g_message ("Failed setenv NO_AT_BRIDGE\n");
+     g_test_init (&argc, &argv, NULL);
++#if GTK_CHECK_VERSION (4, 0, 0)
++    gtk_init ();
++#else
+     gtk_init (&argc, &argv);
+-#ifdef GDK_WINDOWING_WAYLAND
+-    {
+-        GdkDisplay *display = gdk_display_get_default ();
+-        if (GDK_IS_WAYLAND_DISPLAY (display)) {
+-            g_print ("setxkbmap and XTEST do not work in Wayland.\n");
+-            return 0;
+-        }
+-    }
+ #endif
+ 
+     g_test_add_func ("/ibus/keyrepss", test_keypress);
+ 
+-
+     return g_test_run ();
+ }
+-- 
+2.45.0
+
+From a2689c4d66d1f5ae73d2ddbbb94374c98914a029 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Sat, 27 Jul 2024 19:07:58 +0900
+Subject: [PATCH 4/6] src/tests: Fix test cases in GNOME Xorg in ibus-compose
+
+"focus-in" signal is sent to IBus GtkIMModule with a delay in GNOME
+desktop session and the signal order is not fixed with GTK_PHASE_TARGET
+also "focus-in" signal is also sent with GtkText "realize"' signal.
+Now the test cases wait for GtkText 'realize' signal.
+This fixes the test cases in GNOME Xorg but there are still sone issues
+in GNOME Wayland.
+
+BUG=https://github.com/ibus/ibus/pull/2657
+---
+ src/tests/ibus-compose.c | 71 ++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 68 insertions(+), 3 deletions(-)
+
+diff --git a/src/tests/ibus-compose.c b/src/tests/ibus-compose.c
+index 57851b0a..e9f102a1 100644
+--- a/src/tests/ibus-compose.c
++++ b/src/tests/ibus-compose.c
+@@ -39,6 +39,23 @@ static gboolean window_focus_in_event_cb (GtkWidget     *entry,
+ #endif
+ 
+ 
++gboolean
++_wait_for_key_release_cb (gpointer user_data)
++{
++    GMainLoop *loop = (GMainLoop *)user_data;
++    /* If this program is invoked by manual with Enter key in GNOME
++     * Wayland session, ibus_input_context_focus_in() can be called in
++     * test_context_engine_set_by_global() before the key release of
++     * the Enter key so ibus/bus/inputcontext.c:_ic_process_key_event()
++     * could call another bus_input_context_focus_in() in that test case
++     * and fail.
++     */
++    g_test_message ("Wait for 3 seconds for key release event");
++    g_main_loop_quit (loop);
++    return G_SOURCE_REMOVE;
++}
++
++
+ static gchar *
+ get_compose_path ()
+ {
+@@ -312,12 +329,45 @@ set_engine (gpointer user_data)
+ 
+ 
+ #if GTK_CHECK_VERSION (4, 0, 0)
++static gboolean
++event_controller_enter_delay (gpointer user_data)
++{
++    GtkEventController *controller = (GtkEventController *)user_data;
++    GtkWidget *text = gtk_event_controller_get_widget (controller);
++    static int i = 0;
++
++    /* Wait for gtk_text_realize() which calls gtk_text_im_set_focus_in()
++     * while gtk_text_focus_changed() also calls gtk_text_im_set_focus_in()
++     * in GNOME Xorg.
++     */
++    if (gtk_widget_get_realized (text)) {
++        set_engine (user_data);
++        return G_SOURCE_REMOVE;
++    }
++    if (i++ == 10) {
++        g_test_fail_printf ("Window is not realized with %d times", i);
++        return G_SOURCE_REMOVE;
++    }
++    g_test_message ("event_controller_enter_delay %d", i);
++    return G_SOURCE_CONTINUE;
++}
++
++
+ static void
+ event_controller_enter_cb (GtkEventController *controller,
+                            gpointer            user_data)
+ {
++    static guint id = 0;
++
+     g_test_message ("EventController emits \"enter\" signal");
+-    set_engine (controller);
++    /* Call an idle function because gtk_widget_add_controller()
++     * calls g_list_prepend() for event_controllers and this controller is
++     * always called before "gtk-text-focus-controller"
++     * is caleld and the IM context does not receive the focus-in yet.
++     */
++    if (id)
++        return;
++    id = g_idle_add (event_controller_enter_delay, controller);
+ }
+ 
+ #else
+@@ -473,8 +523,9 @@ create_window ()
+ #if GTK_CHECK_VERSION (4, 0, 0)
+     controller = gtk_event_controller_focus_new ();
+     text = gtk_editable_get_delegate (GTK_EDITABLE (entry));
+-    g_signal_connect (controller, "enter",
+-                      G_CALLBACK (event_controller_enter_cb), NULL);
++    gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
++    g_signal_connect_after (controller, "enter",
++                            G_CALLBACK (event_controller_enter_cb), NULL);
+     gtk_widget_add_controller (GTK_WIDGET (text), controller);
+ #else
+     g_signal_connect (entry, "focus-in-event",
+@@ -495,6 +546,19 @@ create_window ()
+ }
+ 
+ 
++static void
++test_init (void)
++{
++    char *tty_name = ttyname (STDIN_FILENO);
++    GMainLoop *loop = g_main_loop_new (NULL, TRUE);
++    g_test_message ("Test on %s", tty_name ? tty_name : "(null)");
++    if (tty_name && g_strstr_len (tty_name, -1, "pts")) {
++        g_timeout_add_seconds (3, _wait_for_key_release_cb, loop);
++        g_main_loop_run (loop);
++    }
++    g_main_loop_unref (loop);
++}
++
+ static void
+ test_compose (void)
+ {
+@@ -557,6 +621,7 @@ main (int argc, char *argv[])
+         g_free (test_name);
+         test_name = g_path_get_basename (m_compose_file);
+     }
++    g_test_add_func ("/ibus-compose/test-init", test_init);
+     m_loop = g_main_loop_new (NULL, TRUE);
+     test_path = g_build_filename ("/ibus-compose", test_name, NULL);
+     g_test_add_func (test_path, test_compose);
+-- 
+2.45.0
+
+From e65110a9bed03c981884e28ff44e2ac1f639df45 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Sat, 27 Jul 2024 19:09:10 +0900
+Subject: [PATCH 5/6] src/tests: Fix ibus-compose for GNOME Wayland
+
+mutter calls meta_wayland_text_input_set_focus() after meta_window_show()
+and the delayed focus-in/out events are sent to IBusEngine.
+Now sleep() is added in ibus-compose because MetaWaylandInputMethod
+cannot know the application status while GTK application is already
+realized.
+
+Also GTK4 compose table supports 16bit compose keys only and it
+causes a warning with pt-BR and fi-FI compose tables but the warning
+is treated as a failure with g_test_init().
+Set LANG=en_US.UTF-8 for thw workaround to test pt-BR and fi-FI IBus
+compose tables.
+
+BUG=https://github.com/ibus/ibus/pull/2657
+---
+ src/tests/ibus-compose.c | 223 ++++++++++++++++++++++++++++++++++-----
+ 1 file changed, 195 insertions(+), 28 deletions(-)
+
+diff --git a/src/tests/ibus-compose.c b/src/tests/ibus-compose.c
+index e9f102a1..0679c259 100644
+--- a/src/tests/ibus-compose.c
++++ b/src/tests/ibus-compose.c
+@@ -7,19 +7,24 @@
+ #define RED   "\033[0;31m"
+ #define NC    "\033[0m"
+ 
++static gchar *m_test_name;
++static gchar *m_session_name;
+ static IBusBus *m_bus;
+ static gchar *m_compose_file;
+ static IBusComposeTableEx *m_compose_table;
+ static IBusEngine *m_engine;
+ static gchar *m_srcdir;
++static gboolean m_is_gtk_32bit_compose_error;
+ static GMainLoop *m_loop;
++static char *m_engine_is_focused;
+ #if GTK_CHECK_VERSION (4, 0, 0)
+ static gboolean m_list_toplevel;
+ #endif
+ 
+ typedef enum {
++    TEST_COMMIT_TEXT,
+     TEST_CREATE_ENGINE,
+-    TEST_COMMIT_TEXT
++    TEST_DELAYED_FOCUS_IN
+ } TestIDleCategory;
+ 
+ typedef struct _TestIdleData {
+@@ -39,6 +44,19 @@ static gboolean window_focus_in_event_cb (GtkWidget     *entry,
+ #endif
+ 
+ 
++gboolean
++is_integrated_desktop ()
++{
++    if (!m_session_name)
++        m_session_name = g_strdup (g_getenv ("XDG_SESSION_DESKTOP"));
++    if (!m_session_name)
++        return FALSE;
++    if (!g_ascii_strncasecmp (m_session_name, "gnome", strlen ("gnome")))
++       return TRUE;
++    return FALSE;
++}
++
++
+ gboolean
+ _wait_for_key_release_cb (gpointer user_data)
+ {
+@@ -63,11 +81,15 @@ get_compose_path ()
+     const gchar * const *l;
+     gchar *compose_path = NULL;
+ 
++    if (m_is_gtk_32bit_compose_error)
++        g_assert (g_setenv ("LANG", m_test_name, TRUE));
+ #if GLIB_CHECK_VERSION (2, 58, 0)
+     langs = g_get_language_names_with_category ("LC_CTYPE");
+ #else
+     langs = g_get_language_names ();
+ #endif
++    if (m_is_gtk_32bit_compose_error)
++        g_assert (g_setenv ("LANG", "en_US.UTF-8", TRUE));
+     for (l = langs; *l; l++) {
+         if (g_str_has_prefix (*l, "en_US"))
+             break;
+@@ -91,6 +113,9 @@ gboolean
+ idle_cb (gpointer user_data)
+ {
+     TestIdleData *data = (TestIdleData *)user_data;
++    static int n = 0;
++    gboolean terminate_program = FALSE;
++
+     g_assert (data);
+     switch (data->category) {
+     case TEST_CREATE_ENGINE:
+@@ -103,23 +128,75 @@ idle_cb (gpointer user_data)
+             if (g_main_loop_is_running (m_loop))
+                 g_main_loop_quit (m_loop);
+             g_clear_pointer (&m_loop, g_main_loop_unref);
+-#if GTK_CHECK_VERSION (4, 0, 0)
+-            m_list_toplevel = FALSE;
+-#else
+-            gtk_main_quit ();
+-#endif
++            terminate_program = TRUE;
+         }
+         data->idle_id = 0;
+         break;
++    case TEST_DELAYED_FOCUS_IN:
++        if (m_engine_is_focused) {
++            data->idle_id = 0;
++            n = 0;
++            g_main_loop_quit (m_loop);
++            return G_SOURCE_REMOVE;
++        }
++        if (n++ < 10) {
++            g_test_message ("Waiting for \"focus-in\" signal %dth times", n);
++            return G_SOURCE_CONTINUE;
++        }
++        g_test_fail_printf ("\"focus-in\" signal is timeout.");
++        g_main_loop_quit (m_loop);
++        terminate_program = TRUE;
++        n = 0;
++        break;
+     default:
+         g_test_fail_printf ("Idle func is called by wrong category:%d.",
+                             data->category);
+         break;
+     }
++    if (terminate_program) {
++#if GTK_CHECK_VERSION (4, 0, 0)
++        m_list_toplevel = FALSE;
++#else
++        gtk_main_quit ();
++#endif
++    }
+     return G_SOURCE_REMOVE;
+ }
+ 
+ 
++static void
++engine_focus_in_cb (IBusEngine *engine,
++#ifdef IBUS_FOCUS_IN_ID
++                    gchar      *object_path,
++                    gchar      *client,
++#endif
++                    gpointer    user_data)
++{
++#ifdef IBUS_FOCUS_IN_ID
++    g_test_message ("engine_focus_in_cb %s %s", object_path, client);
++    m_engine_is_focused = g_strdup (client);
++#else
++    g_test_message ("engine_focus_in_cb");
++    m_engine_is_focused = g_strdup ("No named");
++#endif
++}
++
++static void
++engine_focus_out_cb (IBusEngine *engine,
++#ifdef IBUS_FOCUS_IN_ID
++                     gchar      *object_path,
++#endif
++                     gpointer    user_data)
++{
++#ifdef IBUS_FOCUS_IN_ID
++    g_test_message ("engine_focus_out_cb %s", object_path);
++#else
++    g_test_message ("engine_focus_out_cb");
++#endif
++    g_clear_pointer (&m_engine_is_focused, g_free);
++}
++
++
+ static IBusEngine *
+ create_engine_cb (IBusFactory *factory,
+                   const gchar *name,
+@@ -135,11 +212,30 @@ create_engine_cb (IBusFactory *factory,
+     g_assert (data);
+     /* Don't reset idle_id to avoid duplicated register_ibus_engine(). */
+     g_source_remove (data->idle_id);
+-    m_engine = ibus_engine_new_with_type (IBUS_TYPE_ENGINE_SIMPLE,
+-                                          name,
+-                                          engine_path,
+-                                          ibus_bus_get_connection (m_bus));
++    m_engine = (IBusEngine *)g_object_new (
++            IBUS_TYPE_ENGINE_SIMPLE,
++            "engine-name",      name,
++            "object-path",      engine_path,
++            "connection",       ibus_bus_get_connection (m_bus),
++#ifdef IBUS_FOCUS_IN_ID
++            "has-focus-id",     TRUE,
++#endif
++            NULL);
+     g_free (engine_path);
++
++    m_engine_is_focused = NULL;
++#ifdef IBUS_FOCUS_IN_ID
++    g_signal_connect (m_engine, "focus-in-id",
++#else
++    g_signal_connect (m_engine, "focus-in",
++#endif
++                      G_CALLBACK (engine_focus_in_cb), NULL);
++#ifdef IBUS_FOCUS_IN_ID
++    g_signal_connect (m_engine, "focus-out-id",
++#else
++    g_signal_connect (m_engine, "focus-out",
++#endif
++                      G_CALLBACK (engine_focus_out_cb), NULL);
+     if (m_compose_file)
+         compose_path = g_build_filename (m_srcdir, m_compose_file, NULL);
+     else
+@@ -235,6 +331,13 @@ set_engine_cb (GObject      *object,
+         return;
+     }
+ 
++    /* ibus_im_context_focus_in() is called after GlboalEngine is set. */
++    if (is_integrated_desktop () && !m_engine_is_focused) {
++        data.category = TEST_DELAYED_FOCUS_IN;
++        data.idle_id = g_timeout_add_seconds (1, idle_cb, &data);
++        g_main_loop_run (m_loop);
++        if (data.idle_id != 0)
++            return;
+     if (m_compose_table == NULL) {
+         g_test_skip ("Your locale uses en_US compose table.");
+         idle_cb (&data);
+@@ -319,6 +422,7 @@ set_engine (gpointer user_data)
+ {
+     g_test_message ("set_engine() is calling");
+     g_assert (m_bus != NULL);
++
+     ibus_bus_set_global_engine_async (m_bus,
+                                       "xkbtest:us::eng",
+                                       -1,
+@@ -346,6 +450,11 @@ event_controller_enter_delay (gpointer user_data)
+     }
+     if (i++ == 10) {
+         g_test_fail_printf ("Window is not realized with %d times", i);
++#if GTK_CHECK_VERSION (4, 0, 0)
++        m_list_toplevel = FALSE;
++#else
++        gtk_main_quit ();
++#endif
+         return G_SOURCE_REMOVE;
+     }
+     g_test_message ("event_controller_enter_delay %d", i);
+@@ -360,16 +469,43 @@ event_controller_enter_cb (GtkEventController *controller,
+     static guint id = 0;
+ 
+     g_test_message ("EventController emits \"enter\" signal");
+-    /* Call an idle function because gtk_widget_add_controller()
+-     * calls g_list_prepend() for event_controllers and this controller is
+-     * always called before "gtk-text-focus-controller"
+-     * is caleld and the IM context does not receive the focus-in yet.
+-     */
+     if (id)
+         return;
+-    id = g_idle_add (event_controller_enter_delay, controller);
++    if (is_integrated_desktop ()) {
++        /* Wait for 3 seconds in GNOME Wayland because there is a long time lag
++         * between the "enter" signal on the event controller in GtkText
++         * and the "FocusIn" D-Bus signal in BusInputContext of ibus-daemon
++         * because mutter/core/window.c:meta_window_show() calls
++         * mutter/core/window.c:meta_window_focus() ->
++         * mutter/wayland/meta-wayland-text-input.c:
++         *   meta_wayland_text_input_set_focus() ->
++         * mutter/clutter/clutter/clutter-input-method.c:
++         *   clutter_input_method_focus_out()
++         * I.e. "FocusOut" and "FocusIn" D-Bus methods are always delayed
++         * against the window present in GNOME Wayland.
++         * If "FocusOut" and "FocusIn" D-Bus signals would be called after
++         * "SetGlobalEngine" D-BUs signal was called in ibus-daemon,
++         * the following functions could be called:
++         *   engine_focus_out_cb() for the "gnome-shell" context
++         *   engine_focus_in_cb() for the "fake" context
++         *   engine_focus_out_cb() for the "fake" context
++         *   engine_focus_in_cb() for the "gnome-shell" context
++         * and ibus_engine_commit_text() would not work.
++         * This assume the focus-in/out signals are called within the timeout
++         * seconds.
++         */
++        id = g_timeout_add_seconds (3,
++                                    event_controller_enter_delay,
++                                    controller);
++    } else {
++        /* Call an idle function in Xorg because gtk_widget_add_controller()
++         * calls g_list_prepend() for event_controllers and this controller is
++         * always called before "gtk-text-focus-controller"
++         * is caleld and the IM context does not receive the focus-in yet.
++         */
++        id = g_idle_add (event_controller_enter_delay, controller);
++    }
+ }
+-
+ #else
+ 
+ static gboolean
+@@ -550,10 +686,18 @@ static void
+ test_init (void)
+ {
+     char *tty_name = ttyname (STDIN_FILENO);
+-    GMainLoop *loop = g_main_loop_new (NULL, TRUE);
++    GMainLoop *loop;
++    static guint idle_id = 0;
++
++    if (idle_id) {
++        g_test_incomplete ("Test is called twice due to a timeout.");
++        return;
++    }
++
++    loop = g_main_loop_new (NULL, TRUE);
+     g_test_message ("Test on %s", tty_name ? tty_name : "(null)");
+     if (tty_name && g_strstr_len (tty_name, -1, "pts")) {
+-        g_timeout_add_seconds (3, _wait_for_key_release_cb, loop);
++        idle_id = g_timeout_add_seconds (3, _wait_for_key_release_cb, loop);
+         g_main_loop_run (loop);
+     }
+     g_main_loop_unref (loop);
+@@ -586,14 +730,16 @@ test_compose (void)
+     gtk_main ();
+ #endif
+     g_log_set_always_fatal (flags);
++    g_clear_pointer (&m_engine_is_focused, g_free);
++    g_clear_pointer (&m_session_name, g_free);
+ }
+ 
+ 
+ int
+ main (int argc, char *argv[])
+ {
+-    gchar *test_name;
+     gchar *test_path;
++    int retval;
+ 
+     ibus_init ();
+     /* Avoid a warning of "AT-SPI: Could not obtain desktop path or name"
+@@ -612,21 +758,42 @@ main (int argc, char *argv[])
+             ? g_strdup (argv[1]) : g_strdup (".");
+     m_compose_file = g_strdup (g_getenv ("COMPOSE_FILE"));
+ #if GLIB_CHECK_VERSION (2, 58, 0)
+-    test_name = g_strdup (g_get_language_names_with_category ("LC_CTYPE")[0]);
++    m_test_name = g_strdup (g_get_language_names_with_category ("LC_CTYPE")[0]);
+ #else
+-    test_name = g_strdup (g_getenv ("LANG"));
++    m_test_name = g_strdup (g_getenv ("LANG"));
+ #endif
+     if (m_compose_file &&
+-        (!test_name || !g_ascii_strncasecmp (test_name, "en_US", 5))) {
+-        g_free (test_name);
+-        test_name = g_path_get_basename (m_compose_file);
++        (!m_test_name || !g_ascii_strncasecmp (m_test_name, "en_US", 5))) {
++        g_free (m_test_name);
++        m_test_name = g_path_get_basename (m_compose_file);
++    }
++    /* The parent of GtkIMContextWayland is GtkIMContextSimple and
++     * it outputs a warning of "Can't handle >16bit keyvals" in
++     * gtk/gtkcomposetable.c:parse_compose_sequence() in pt-BR locales
++     * and any warnings are treated as errors with g_test_run()
++     * So export LANG=en_US.UTF-8 for GNOME Wayland as a workaround.
++     */
++    if (m_test_name && (!g_ascii_strncasecmp (m_test_name, "pt_BR", 5) ||
++                        !g_ascii_strncasecmp (m_test_name, "fi_FI", 5)
++                       )) {
++        m_is_gtk_32bit_compose_error = TRUE;
++    }
++    if (m_is_gtk_32bit_compose_error) {
++#if 1
++        g_assert (g_setenv ("LANG", "en_US.UTF-8", TRUE));
++#else
++        /* FIXME: Use expected_messages in g_log_structured() */
++        g_test_expect_message ("Gtk", G_LOG_LEVEL_WARNING,
++                               "Can't handle >16bit keyvals");
++#endif
+     }
+     g_test_add_func ("/ibus-compose/test-init", test_init);
+     m_loop = g_main_loop_new (NULL, TRUE);
+-    test_path = g_build_filename ("/ibus-compose", test_name, NULL);
++    test_path = g_build_filename ("/ibus-compose", m_test_name, NULL);
+     g_test_add_func (test_path, test_compose);
+     g_free (test_path);
+-    g_free (test_name);
+ 
+-    return g_test_run ();
++    retval = g_test_run ();
++    g_free (m_test_name);
++    return retval;
+ }
+-- 
+2.45.0
+
+From b485ef777352f2e4f0b1ef458826a0aff0c8d136 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Sat, 27 Jul 2024 19:19:43 +0900
+Subject: [PATCH 6/6] src/tests: Fix some CI issues with busy TMT virtual
+ session
+
+- Increase waiting time in ibus-compose-locales.
+- Wait for 3 secs after set-engine in ibus-compose to wait for the delayed
+  focus-in/out events.
+- delete screendump option in ibus-desktop-testing.desktop not to
+  support no display.
+- setlocale in ibus-engine-switch to get UTF-8 warnings.
+- Quote file names as shell syntax as much as possible.
+- Check file existent and do not use "rm -f" as much as possible.
+- Do not set GTK_IM_MODULE=ibus for Wayland.
+
+BUG=https://github.com/ibus/ibus/pull/2657
+---
+ src/tests/ibus-compose-locales.in           |  4 +-
+ src/tests/ibus-compose.c                    | 23 ++++--
+ src/tests/ibus-desktop-testing-autostart.in |  1 +
+ src/tests/ibus-desktop-testing-module       | 79 ++++++++++++++++-----
+ src/tests/ibus-desktop-testing.desktop.in   |  2 +-
+ src/tests/ibus-engine-switch.c              |  3 +
+ 6 files changed, 85 insertions(+), 27 deletions(-)
+
+diff --git a/src/tests/ibus-compose-locales.in b/src/tests/ibus-compose-locales.in
+index 7a133ce0..485204c0 100755
+--- a/src/tests/ibus-compose-locales.in
++++ b/src/tests/ibus-compose-locales.in
+@@ -15,8 +15,8 @@ do
+     if [ "x$IS_COMMENT" != x ] ; then
+         continue
+     fi
+-    while [ x"$IBUS_DAEMON_WITH_SYSTEMD" != x ] && [ $INITED -lt 6 ] ; do
+-        echo "Waiting for ${INITED}0 secs till 60 secs"
++    while [ x"$IBUS_DAEMON_WITH_SYSTEMD" != x ] && [ $INITED -lt 12 ] ; do
++        echo "Waiting for ${INITED}0 secs till 120 secs"
+         sleep 10
+         INITED=`expr $INITED + 1`
+     done
+diff --git a/src/tests/ibus-compose.c b/src/tests/ibus-compose.c
+index 0679c259..326d3b90 100644
+--- a/src/tests/ibus-compose.c
++++ b/src/tests/ibus-compose.c
+@@ -331,13 +331,22 @@ set_engine_cb (GObject      *object,
+         return;
+     }
+ 
+-    /* ibus_im_context_focus_in() is called after GlboalEngine is set. */
+-    if (is_integrated_desktop () && !m_engine_is_focused) {
+-        data.category = TEST_DELAYED_FOCUS_IN;
+-        data.idle_id = g_timeout_add_seconds (1, idle_cb, &data);
+-        g_main_loop_run (m_loop);
+-        if (data.idle_id != 0)
+-            return;
++    /* ibus_im_context_focus_in() is called after GlboalEngine is set.
++     * The focus-in/out events happen more slowly in a busy system
++     * likes with a TMT tool.
++     */
++    if (is_integrated_desktop () && g_getenv ("IBUS_DAEMON_WITH_SYSTEMD")) {
++        g_test_message ("Start tiny \"focus-in\" signal test");
++        for (i = 0; i < 3; i++) {
++            data.category = TEST_DELAYED_FOCUS_IN;
++            data.idle_id = g_timeout_add_seconds (1, idle_cb, &data);
++            g_main_loop_run (m_loop);
++            if (data.idle_id != 0)
++                return;
++        }
++        g_test_message ("End tiny \"focus-in\" signal test");
++        data.category = TEST_COMMIT_TEXT;
++    }
+     if (m_compose_table == NULL) {
+         g_test_skip ("Your locale uses en_US compose table.");
+         idle_cb (&data);
+diff --git a/src/tests/ibus-desktop-testing-autostart.in b/src/tests/ibus-desktop-testing-autostart.in
+index d50354df..9ecc8c27 100755
+--- a/src/tests/ibus-desktop-testing-autostart.in
++++ b/src/tests/ibus-desktop-testing-autostart.in
+@@ -73,6 +73,7 @@ main()
+     parse_args "$@"
+     init_session
+     check_env
++    #run_monitor
+     save_screen
+     run_test_suite
+     finit
+diff --git a/src/tests/ibus-desktop-testing-module b/src/tests/ibus-desktop-testing-module
+index 2d686813..e576df87 100755
+--- a/src/tests/ibus-desktop-testing-module
++++ b/src/tests/ibus-desktop-testing-module
+@@ -63,8 +63,9 @@ pwd
+ pstree -asp $$
+ gsettings list-recursively org.gnome.shell
+ rpm -q gnome-shell-extension-no-overview gnome-shell gnome-session
+-ps -ef | grep ibus | grep -v grep
++ps -ef | grep ibus | grep -v TMT | grep -v grep
+ ibus address
++locale
+ env
+ #dbus-send --session --print-reply --dest=org.gnome.Shell.Introspect /org/gnome/Shell/Introspect org.gnome.Shell.Introspect.GetWindows
+ '
+@@ -133,7 +134,8 @@ check_tty()
+     TTY=`tty`
+     if echo $PROGNAME | grep -q runner ; then
+         if [ $ENABLED_SYSTEMD -eq 1 ] && [  $SESSION_IS_GNOME -eq 1 ] ; then
+-            if echo "$TTY" | grep -E 'tty|console' ; then
++            # "not a tty" is correct with TMT tool
++            if echo "$TTY" | grep -E 'tty|console' | grep -v "not a tty" ; then
+                print_log -e "${RED}FAIL${NC}: ${RED}ERROR${NC}: Changing runlevel does not support console. Please log into the host with ssh."
+                exit 1
+             fi
+@@ -143,7 +145,7 @@ check_tty()
+                print_log -e "Running session with ssh. It might be good to use console instead."
+             fi
+         fi
+-        SESSION=`ps -ef | grep session | grep -v grep`
++        SESSION=`ps -ef | grep session | grep -v TMT | grep -v grep`
+         if [ x"$SESSION" != x ] ; then
+                print_log -e "${RED}FAIL${NC}: ${RED}ERROR${NC}: Session is running: $SESSION"
+                exit 1
+@@ -254,7 +256,8 @@ create_xdg_autostart()
+         exit 1
+     fi
+     mkdir -p "$TEST_USER_HOME/.config/autostart"
+-    cp "$desktop_file" "$TEST_USER_HOME/.config/autostart"
++    sed -e "s/\(^Exec=.*\)tests ibus\(.*\)/\1tests $TESTS\2/" "$desktop_file" \
++        > "$TEST_USER_HOME/.config/autostart/$AUTOSTART_DESKTOP_FILE"
+     LINE="AutostartCondition=if-exists $TEST_USER_HOME/.config/autostart/$AUTOSTART_DESKTOP_FILE"
+     echo "$LINE" >> "$TEST_USER_HOME/.config/autostart/$AUTOSTART_DESKTOP_FILE"
+     chown -R "$TEST_USER" "$TEST_USER_HOME/.config"
+@@ -327,6 +330,9 @@ init_session()
+ 
+     LOGIN_USER=$USER
+     LOGIN_HOME=$HOME
++    if [ x"$TESTS" = x ] ; then
++        TESTS='ibus'
++    fi
+     check_tty
+     if [ $ENABLED_SYSTEMD -eq 1 ] && [  $SESSION_IS_GNOME -eq 1 ] ; then
+         SESSION_FILE1="/usr/lib/systemd/system/gnome-headless-session@.service"
+@@ -482,9 +488,25 @@ init_gnome()
+             chown -R $LOGIN_USER $TEST_USER_HOME/.local
+         fi
+     fi
++
++    ## Use gnome-headless-session for the no-graphics option because currently
++    ## GDM itself cannot run the login screen(greeter) without a display so
++    ## the no-graphics option does not work with GDM and the TMT tool
++    ## while GDM can invoke the headless GNOME Wayland sessions.
++    ##
++    ## Use GNOME_SHELL_WAYLAND_COMMAND for the no-graphics option to add a
++    ## virtual monitor since gnome-headless-session provides a headless mode
++    ## but no monitor and IBus focus-in/out events does not work without
++    ## any monitors.
++    ##
++    ## Use the systemd locale configuration for the no-graphcis mode since
++    ## gnome-headless-session does not pull LANG from AccountsService.
++    ##
+     if [ $LOGIN_USER != $USER ] ; then
+         SHELL_SERVICE_FILE="org.gnome.Shell@wayland.service"
+         SYSTEMD_USER_DIR="$TEST_USER_HOME/.config/systemd/user"
++        ENV_USER_DIR="$TEST_USER_HOME/.config/environment.d"
++        LOCALE_CONF="$ENV_USER_DIR/ibus.conf"
+         if test $HAVE_GRAPHICS -ne 1 ; then
+             if test ! -f "$SYSTEMD_USER_DIR/$SHELL_SERVICE_FILE" ; then
+                 mkdir -p "$SYSTEMD_USER_DIR"
+@@ -494,12 +516,19 @@ init_gnome()
+                     > $SHELL_SERVICE_FILE
+                 diff $SYSTEMD_SYSTEM_DIR/$SHELL_SERVICE_FILE $SHELL_SERVICE_FILE
+                 popd
+-                chown -R $LOGIN_USER "$TEST_USER_HOME/.config"
+             fi
++            mkdir -p "$ENV_USER_DIR"
++            cat > "$LOCALE_CONF" << _EOF_LOCALE_CONF
++LANG=$SESSION_LANG
++_EOF_LOCALE_CONF
++            chown -R $LOGIN_USER "$TEST_USER_HOME/.config"
+         else
+             if test -f "$SYSTEMD_USER_DIR/$SHELL_SERVICE_FILE" ; then
+                 rm "$SYSTEMD_USER_DIR/$SHELL_SERVICE_FILE"
+             fi
++            if test -f "$LOCALE_CONF" ; then
++                rm "$LOCALE_CONF"
++            fi
+         fi
+     fi
+     GET_DISABLE_USER_EX="gsettings get org.gnome.shell disable-user-extensions"
+@@ -556,7 +585,7 @@ operate_desktop_with_systemd()
+     SESSION=gnome-headless-session
+     COMMAND="$1"
+ 
+-    #if test $HAVE_GRAPHICS -eq 1 ; then
++    if test $HAVE_GRAPHICS -eq 1 ; then
+         case "$COMMAND" in
+         "start") systemctl isolate graphical.target
+                  echo ""
+@@ -565,22 +594,24 @@ operate_desktop_with_systemd()
+         "")      print_log -e "${RED}FAIL${NC}: ${RED}ERROR${NC}: Wrong command $COMMAND"
+                  exit 1;;
+         esac
+-    #else
+-    #    setenforce 0
+-    #    systemctl $COMMAND ${SESSION}@${TEST_USER}
+-    #fi
++    else
++        setenforce 0
++        systemctl $COMMAND ${SESSION}@${TEST_USER}
++    fi
+     case "$COMMAND" in
+     "start") sleep 30;;
+     "") ;;
+     esac
+-    #if test $HAVE_GRAPHICS -eq 1 ; then
++    if test $HAVE_GRAPHICS -eq 1 ; then
+         systemctl status --no-pager graphical.target
+-    #else
+-    #    systemctl status --no-pager ${SESSION}@${TEST_USER}
+-    #fi
++    else
++        systemctl status --no-pager ${SESSION}@${TEST_USER}
++    fi
+     ps -ef | grep X
+     ps -ef | grep session
+     ps -ef | grep ibus
++    SUSER=`echo "$LOGIN_USER" | cut -c 1-7`
++    ps -ef | grep $SUSER
+ }
+ 
+ 
+@@ -639,6 +670,23 @@ run_session()
+ }
+ 
+ 
++run_monitor()
++{
++    if ! which gnome-monitor-config > /dev/null ; then
++        print_log -e "${RED}FAIL${NC}: No gnome-monitor-config"
++        return
++    fi
++    if [ $ENABLED_SYSTEMD -ne 1 ] || [  $SESSION_IS_GNOME -ne 1 ] \
++       || [ $HAVE_GRAPHICS -ne 1 ] ; then
++        return
++    fi
++    /usr/libexec/gnome-screen-cast.py -v 1024 768 &
++    sleep 3
++    gnome-monitor-config list
++    #gnome-monitor-config set -LpM Virtual-1 -t normal -m 1024x768@60.004
++}
++
++
+ count_case_result()
+ {
+     retval=$1
+@@ -758,9 +806,6 @@ run_gnome_desktop_testing_runner()
+ {
+     pass=0
+     fail=0
+-    if [ x"$TESTS" = x ] ; then
+-        TESTS='ibus'
+-    fi
+     if echo $PROGNAME | grep -q autostart ; then
+         export IBUS_DAEMON_WITH_SYSTEMD=1
+     fi
+diff --git a/src/tests/ibus-desktop-testing.desktop.in b/src/tests/ibus-desktop-testing.desktop.in
+index e91900ed..fe9356ad 100644
+--- a/src/tests/ibus-desktop-testing.desktop.in
++++ b/src/tests/ibus-desktop-testing.desktop.in
+@@ -2,7 +2,7 @@
+ Name=IBus Desktop Testing Runner
+ GenericName=Input Method Desktop Testing Runner
+ Comment=Test plugin for IBus Desktop Testing
+-Exec=sh -c 'exec @libexecdir@/ibus-desktop-testing-autostart --envcheck --output $HOME/test-autostart.log --result $HOME/test.log --runner gnome --screendump $HOME/screen.log --tests ibus'
++Exec=sh -c 'exec @libexecdir@/ibus-desktop-testing-autostart --envcheck --output $HOME/test-autostart.log --result $HOME/test.log --runner gnome --tests ibus'
+ Terminal=false
+ Type=Application
+ Encoding=UTF-8
+diff --git a/src/tests/ibus-engine-switch.c b/src/tests/ibus-engine-switch.c
+index b50bac59..7359e125 100644
+--- a/src/tests/ibus-engine-switch.c
++++ b/src/tests/ibus-engine-switch.c
+@@ -1,5 +1,6 @@
+ /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+ 
++#include <locale.h>
+ #include <string.h>
+ #include <unistd.h>
+ #include "ibus.h"
+@@ -251,6 +252,8 @@ main (gint    argc,
+       gchar **argv)
+ {
+     gint result;
++    /* To get UTF-8 error messages with glib2 */
++    setlocale (LC_ALL, "");
+     ibus_init ();
+     g_test_init (&argc, &argv, NULL);
+     bus = ibus_bus_new ();
+-- 
+2.45.0
+

diff --git a/ibus.spec b/ibus.spec
index d28f3d1..63425ea 100644
--- a/ibus.spec
+++ b/ibus.spec
@@ -62,7 +62,7 @@
 Name:           ibus
 Version:        1.5.31~beta1
 # https://github.com/fedora-infra/rpmautospec/issues/101
-Release:        9%{?dist}
+Release:        10%{?dist}
 Summary:        Intelligent Input Bus for Linux OS
 License:        LGPL-2.1-or-later
 URL:            https://github.com/ibus/%name/wiki
@@ -609,6 +609,7 @@ dconf update || :
 %{_bindir}/ibus-desktop-testing-runner
 %{_datadir}/ibus/tests
 %{_libexecdir}/ibus-desktop-testing-autostart
+%{_libexecdir}/ibus-desktop-testing-module
 
 %files tests
 %dir %{_libexecdir}/installed-tests
@@ -617,6 +618,9 @@ dconf update || :
 %{_datadir}/installed-tests/ibus
 
 %changelog
+* Sat Jul 27 2024 Takao Fujiwara <tfujiwar@redhat.com> - 1.5.31~beta1-10
+- Replace GNOME Xorg with GNOME Wayland in CI
+
 * Sat Jul 27 2024 Takao Fujiwara <tfujiwar@redhat.com> - 1.5.31~beta1-9
 - Update CI for RHEL packages
 

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-05-31  2:08 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-31  2:08 [rpms/ibus] autotool: Replace GNOME Xorg with GNOME Wayland in CI Takao Fujiwara

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox