blob: 5300739263195dbe66ab7a9ba2a11346c1454474 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/gtk/x/gtk_event_loop_x11.h"
#include <gtk/gtk.h>
#include <xcb/xcb.h>
#include <xcb/xproto.h>
#include "ui/base/x/x11_util.h"
#include "ui/gfx/x/event.h"
extern "C" {
#if BUILDFLAG(GTK_VERSION) >= 4
unsigned long gdk_x11_surface_get_xid(GdkSurface* surface);
#else
unsigned long gdk_x11_window_get_xid(GdkWindow* window);
#endif
}
namespace ui {
namespace {
x11::KeyButMask BuildXkbStateFromGdkEvent(unsigned int state,
unsigned char group) {
return static_cast<x11::KeyButMask>(state | ((group & 0x3) << 13));
}
x11::KeyEvent ConvertGdkEventToKeyEvent(GdkEvent* gdk_event) {
#if BUILDFLAG(GTK_VERSION) >= 4
GdkKeymapKey* keys = nullptr;
guint* keyvals = nullptr;
gint n_entries = 0;
gdk_display_map_keycode(gdk_display_get_default(),
gdk_key_event_get_keycode(gdk_event), &keys, &keyvals,
&n_entries);
guint keyval = gdk_key_event_get_keyval(gdk_event);
GdkKeymapKey keymap_key{0, 0, 0};
if (keys) {
for (gint i = 0; i < n_entries; i++) {
if (keyvals[i] == keyval) {
keymap_key = keys[i];
break;
}
}
g_free(keys);
g_free(keyvals);
}
return {
.opcode = gdk_event_get_event_type(gdk_event) == GDK_KEY_PRESS
? x11::KeyEvent::Press
: x11::KeyEvent::Release,
.detail = static_cast<x11::KeyCode>(keymap_key.keycode),
.time = static_cast<x11::Time>(gdk_event_get_time(gdk_event)),
.root = ui::GetX11RootWindow(),
.event = static_cast<x11::Window>(
gdk_x11_surface_get_xid(gdk_event_get_surface(gdk_event))),
.state = BuildXkbStateFromGdkEvent(
gdk_event_get_modifier_state(gdk_event), keymap_key.group),
.same_screen = true,
};
#else
return {
.opcode = gdk_event->key.type == GDK_KEY_PRESS ? x11::KeyEvent::Press
: x11::KeyEvent::Release,
.send_event = gdk_event->key.send_event,
.detail = static_cast<x11::KeyCode>(gdk_event->key.hardware_keycode),
.time = static_cast<x11::Time>(gdk_event->key.time),
.root = ui::GetX11RootWindow(),
.event = static_cast<x11::Window>(
gdk_x11_window_get_xid(gdk_event->key.window)),
.state =
BuildXkbStateFromGdkEvent(gdk_event->key.state, gdk_event->key.group),
.same_screen = true,
};
#endif
}
void ProcessGdkEvent(GdkEvent* gdk_event) {
// This function translates GdkEventKeys into XKeyEvents and puts them to
// the X event queue.
//
// base::MessagePumpX11 is using the X11 event queue and all key events should
// be processed there. However, there are cases(*1) that GdkEventKeys are
// created instead of XKeyEvents. In these cases, we have to translate
// GdkEventKeys to XKeyEvents and puts them to the X event queue so our main
// event loop can handle those key events.
//
// (*1) At least ibus-gtk in async mode creates a copy of user's key event and
// pushes it back to the GDK event queue. In this case, there is no
// corresponding key event in the X event queue. So we have to handle this
// case. ibus-gtk is used through gtk-immodule to support IMEs.
#if BUILDFLAG(GTK_VERSION) >= 4
auto event_type = gdk_event_get_event_type(gdk_event);
#else
auto event_type = gdk_event->type;
#endif
switch (event_type) {
case GDK_KEY_PRESS:
case GDK_KEY_RELEASE:
break;
default:
return;
}
// We want to process the gtk event; mapped to an X11 event immediately
// otherwise if we put it back on the queue we may get items out of order.
x11::Connection::Get()->DispatchEvent(
x11::Event{ConvertGdkEventToKeyEvent(gdk_event)});
}
} // namespace
GtkEventLoopX11::GtkEventLoopX11(GtkWidget* widget) {
#if BUILDFLAG(GTK_VERSION) >= 4
surface_ = gtk_native_get_surface(gtk_widget_get_native(widget));
signal_id_ =
g_signal_connect(surface_, "event", G_CALLBACK(OnEventThunk), this);
#else
gdk_event_handler_set(DispatchGdkEvent, nullptr, nullptr);
#endif
}
GtkEventLoopX11::~GtkEventLoopX11() {
#if BUILDFLAG(GTK_VERSION) >= 4
g_signal_handler_disconnect(surface_, signal_id_);
#else
gdk_event_handler_set(reinterpret_cast<GdkEventFunc>(gtk_main_do_event),
nullptr, nullptr);
#endif
}
#if BUILDFLAG(GTK_VERSION) >= 4
gboolean GtkEventLoopX11::OnEvent(GdkEvent* gdk_event) {
ProcessGdkEvent(gdk_event);
return false;
}
#else
// static
void GtkEventLoopX11::DispatchGdkEvent(GdkEvent* gdk_event, gpointer) {
ProcessGdkEvent(gdk_event);
gtk_main_do_event(gdk_event);
}
#endif
} // namespace ui