| #include "qemu/osdep.h" |
| #include "qemu/sockets.h" |
| #include "qemu/dbus.h" |
| #include "qemu/sockets.h" |
| #include "glib.h" |
| #include "glibconfig.h" |
| #include <gio/gio.h> |
| #include <gio/gunixfdlist.h> |
| #include "libqtest.h" |
| #ifndef WIN32 |
| #include <sys/mman.h> |
| #endif |
| #include "ui/dbus-display1.h" |
| |
| static GDBusConnection* |
| test_dbus_p2p_from_fd(int fd) |
| { |
| g_autoptr(GError) err = NULL; |
| g_autoptr(GSocket) socket = NULL; |
| g_autoptr(GSocketConnection) socketc = NULL; |
| GDBusConnection *conn; |
| |
| #ifdef WIN32 |
| socket = g_socket_new_from_fd(_get_osfhandle(fd), &err); |
| #else |
| socket = g_socket_new_from_fd(fd, &err); |
| #endif |
| g_assert_no_error(err); |
| |
| socketc = g_socket_connection_factory_create_connection(socket); |
| g_assert(socketc != NULL); |
| |
| conn = g_dbus_connection_new_sync( |
| G_IO_STREAM(socketc), NULL, |
| G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | |
| G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING, |
| NULL, NULL, &err); |
| g_assert_no_error(err); |
| |
| return conn; |
| } |
| |
| static void |
| test_setup(QTestState **qts, GDBusConnection **conn) |
| { |
| int pair[2]; |
| |
| *qts = qtest_init("-display dbus,p2p=yes -name dbus-test"); |
| |
| g_assert_cmpint(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0); |
| |
| qtest_qmp_add_client(*qts, "@dbus-display", pair[1]); |
| |
| *conn = test_dbus_p2p_from_fd(pair[0]); |
| g_dbus_connection_start_message_processing(*conn); |
| } |
| |
| static void |
| test_dbus_display_vm(void) |
| { |
| g_autoptr(GError) err = NULL; |
| g_autoptr(GDBusConnection) conn = NULL; |
| g_autoptr(QemuDBusDisplay1VMProxy) vm = NULL; |
| QTestState *qts = NULL; |
| |
| test_setup(&qts, &conn); |
| |
| vm = QEMU_DBUS_DISPLAY1_VM_PROXY( |
| qemu_dbus_display1_vm_proxy_new_sync( |
| conn, |
| G_DBUS_PROXY_FLAGS_NONE, |
| NULL, |
| DBUS_DISPLAY1_ROOT "/VM", |
| NULL, |
| &err)); |
| g_assert_no_error(err); |
| |
| g_assert_cmpstr( |
| qemu_dbus_display1_vm_get_name(QEMU_DBUS_DISPLAY1_VM(vm)), |
| ==, |
| "dbus-test"); |
| qtest_quit(qts); |
| } |
| |
| typedef struct TestDBusConsoleRegister { |
| GMainLoop *loop; |
| GThread *thread; |
| GDBusConnection *listener_conn; |
| GDBusObjectManagerServer *server; |
| bool with_map; |
| } TestDBusConsoleRegister; |
| |
| static gboolean listener_handle_scanout( |
| QemuDBusDisplay1Listener *object, |
| GDBusMethodInvocation *invocation, |
| guint arg_width, |
| guint arg_height, |
| guint arg_stride, |
| guint arg_pixman_format, |
| GVariant *arg_data, |
| TestDBusConsoleRegister *test) |
| { |
| if (!test->with_map) { |
| g_main_loop_quit(test->loop); |
| } |
| |
| return DBUS_METHOD_INVOCATION_HANDLED; |
| } |
| |
| #ifndef WIN32 |
| static gboolean listener_handle_scanout_map( |
| QemuDBusDisplay1ListenerUnixMap *object, |
| GDBusMethodInvocation *invocation, |
| GUnixFDList *fd_list, |
| GVariant *arg_handle, |
| guint arg_offset, |
| guint arg_width, |
| guint arg_height, |
| guint arg_stride, |
| guint arg_pixman_format, |
| TestDBusConsoleRegister *test) |
| { |
| int fd = -1; |
| gint32 handle = g_variant_get_handle(arg_handle); |
| g_autoptr(GError) error = NULL; |
| void *addr = NULL; |
| size_t len = arg_height * arg_stride; |
| |
| g_assert_cmpuint(g_unix_fd_list_get_length(fd_list), ==, 1); |
| fd = g_unix_fd_list_get(fd_list, handle, &error); |
| g_assert_no_error(error); |
| |
| addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, arg_offset); |
| g_assert_no_errno(addr == MAP_FAILED ? -1 : 0); |
| g_assert_no_errno(munmap(addr, len)); |
| |
| g_main_loop_quit(test->loop); |
| |
| close(fd); |
| return DBUS_METHOD_INVOCATION_HANDLED; |
| } |
| #endif |
| |
| static void |
| test_dbus_console_setup_listener(TestDBusConsoleRegister *test, bool with_map) |
| { |
| g_autoptr(GDBusObjectSkeleton) listener = NULL; |
| g_autoptr(QemuDBusDisplay1ListenerSkeleton) iface = NULL; |
| |
| test->server = g_dbus_object_manager_server_new(DBUS_DISPLAY1_ROOT); |
| listener = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Listener"); |
| iface = QEMU_DBUS_DISPLAY1_LISTENER_SKELETON( |
| qemu_dbus_display1_listener_skeleton_new()); |
| g_object_connect(iface, |
| "signal::handle-scanout", listener_handle_scanout, test, |
| NULL); |
| g_dbus_object_skeleton_add_interface(listener, |
| G_DBUS_INTERFACE_SKELETON(iface)); |
| if (with_map) { |
| #ifdef WIN32 |
| g_test_skip("map test lacking on win32"); |
| return; |
| #else |
| g_autoptr(QemuDBusDisplay1ListenerUnixMapSkeleton) iface_map = |
| QEMU_DBUS_DISPLAY1_LISTENER_UNIX_MAP_SKELETON( |
| qemu_dbus_display1_listener_unix_map_skeleton_new()); |
| |
| g_object_connect(iface_map, |
| "signal::handle-scanout-map", listener_handle_scanout_map, test, |
| NULL); |
| g_dbus_object_skeleton_add_interface(listener, |
| G_DBUS_INTERFACE_SKELETON(iface_map)); |
| g_object_set(iface, "interfaces", |
| (const gchar *[]) { "org.qemu.Display1.Listener.Unix.Map", NULL }, |
| NULL); |
| #endif |
| } |
| g_dbus_object_manager_server_export(test->server, listener); |
| g_dbus_object_manager_server_set_connection(test->server, |
| test->listener_conn); |
| |
| g_dbus_connection_start_message_processing(test->listener_conn); |
| } |
| |
| static void |
| test_dbus_console_registered(GObject *source_object, |
| GAsyncResult *res, |
| gpointer user_data) |
| { |
| TestDBusConsoleRegister *test = user_data; |
| g_autoptr(GError) err = NULL; |
| |
| qemu_dbus_display1_console_call_register_listener_finish( |
| QEMU_DBUS_DISPLAY1_CONSOLE(source_object), |
| #ifndef WIN32 |
| NULL, |
| #endif |
| res, &err); |
| |
| if (g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { |
| g_test_skip("The VM doesn't have a console!"); |
| g_main_loop_quit(test->loop); |
| return; |
| } |
| |
| g_assert_no_error(err); |
| |
| test->listener_conn = g_thread_join(test->thread); |
| test_dbus_console_setup_listener(test, test->with_map); |
| } |
| |
| static gpointer |
| test_dbus_p2p_server_setup_thread(gpointer data) |
| { |
| return test_dbus_p2p_from_fd(GPOINTER_TO_INT(data)); |
| } |
| |
| static void |
| test_dbus_display_console(const void* data) |
| { |
| g_autoptr(GError) err = NULL; |
| g_autoptr(GDBusConnection) conn = NULL; |
| g_autoptr(QemuDBusDisplay1ConsoleProxy) console = NULL; |
| g_autoptr(GMainLoop) loop = NULL; |
| QTestState *qts = NULL; |
| int pair[2]; |
| TestDBusConsoleRegister test = { 0, .with_map = GPOINTER_TO_INT(data) }; |
| #ifdef WIN32 |
| WSAPROTOCOL_INFOW info; |
| g_autoptr(GVariant) listener = NULL; |
| #else |
| g_autoptr(GUnixFDList) fd_list = NULL; |
| int idx; |
| #endif |
| |
| test_setup(&qts, &conn); |
| |
| g_assert_cmpint(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0); |
| #ifndef WIN32 |
| fd_list = g_unix_fd_list_new(); |
| idx = g_unix_fd_list_append(fd_list, pair[1], NULL); |
| #endif |
| |
| console = QEMU_DBUS_DISPLAY1_CONSOLE_PROXY( |
| qemu_dbus_display1_console_proxy_new_sync( |
| conn, |
| G_DBUS_PROXY_FLAGS_NONE, |
| NULL, |
| "/org/qemu/Display1/Console_0", |
| NULL, |
| &err)); |
| g_assert_no_error(err); |
| |
| test.loop = loop = g_main_loop_new(NULL, FALSE); |
| test.thread = g_thread_new(NULL, test_dbus_p2p_server_setup_thread, |
| GINT_TO_POINTER(pair[0])); |
| |
| #ifdef WIN32 |
| if (WSADuplicateSocketW(_get_osfhandle(pair[1]), |
| GetProcessId((HANDLE) qtest_pid(qts)), |
| &info) == SOCKET_ERROR) |
| { |
| g_autofree char *emsg = g_win32_error_message(WSAGetLastError()); |
| g_error("WSADuplicateSocket failed: %s", emsg); |
| } |
| close(pair[1]); |
| listener = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, |
| &info, |
| sizeof(info), |
| 1); |
| #endif |
| |
| qemu_dbus_display1_console_call_register_listener( |
| QEMU_DBUS_DISPLAY1_CONSOLE(console), |
| #ifdef WIN32 |
| listener, |
| #else |
| g_variant_new_handle(idx), |
| #endif |
| G_DBUS_CALL_FLAGS_NONE, |
| -1, |
| #ifndef WIN32 |
| fd_list, |
| #endif |
| NULL, |
| test_dbus_console_registered, |
| &test); |
| |
| g_main_loop_run(loop); |
| |
| g_clear_object(&test.server); |
| g_clear_object(&test.listener_conn); |
| qtest_quit(qts); |
| } |
| |
| static void |
| test_dbus_display_keyboard(void) |
| { |
| g_autoptr(GError) err = NULL; |
| g_autoptr(GDBusConnection) conn = NULL; |
| g_autoptr(QemuDBusDisplay1KeyboardProxy) keyboard = NULL; |
| QTestState *qts = NULL; |
| |
| test_setup(&qts, &conn); |
| |
| keyboard = QEMU_DBUS_DISPLAY1_KEYBOARD_PROXY( |
| qemu_dbus_display1_keyboard_proxy_new_sync( |
| conn, |
| G_DBUS_PROXY_FLAGS_NONE, |
| NULL, |
| "/org/qemu/Display1/Console_0", |
| NULL, |
| &err)); |
| g_assert_no_error(err); |
| |
| g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 0); |
| g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0); |
| |
| qemu_dbus_display1_keyboard_call_press_sync( |
| QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard), |
| 0x1C, /* qnum enter */ |
| G_DBUS_CALL_FLAGS_NONE, |
| -1, |
| NULL, |
| &err); |
| if (g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { |
| g_test_skip("The VM doesn't have a console!"); |
| qtest_quit(qts); |
| return; |
| } |
| |
| g_assert_no_error(err); |
| |
| /* may be should wait for interrupt? */ |
| g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 1); |
| g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0x5A); /* scan code 2 enter */ |
| |
| qemu_dbus_display1_keyboard_call_release_sync( |
| QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard), |
| 0x1C, /* qnum enter */ |
| G_DBUS_CALL_FLAGS_NONE, |
| -1, |
| NULL, |
| &err); |
| g_assert_no_error(err); |
| |
| g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 1); |
| g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0xF0); /* scan code 2 release */ |
| g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0x5A); /* scan code 2 enter */ |
| |
| g_assert_cmpint(qemu_dbus_display1_keyboard_get_modifiers( |
| QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard)), ==, 0); |
| |
| qtest_quit(qts); |
| } |
| |
| int |
| main(int argc, char **argv) |
| { |
| g_test_init(&argc, &argv, NULL); |
| |
| qtest_add_func("/dbus-display/vm", test_dbus_display_vm); |
| qtest_add_data_func("/dbus-display/console", GINT_TO_POINTER(false), test_dbus_display_console); |
| qtest_add_data_func("/dbus-display/console/map", GINT_TO_POINTER(true), test_dbus_display_console); |
| qtest_add_func("/dbus-display/keyboard", test_dbus_display_keyboard); |
| |
| return g_test_run(); |
| } |