Skip to content

Commit

Permalink
Grab keyboard focus when clickable area hovered
Browse files Browse the repository at this point in the history
Finally figured out how to do this.  This change makes it possible for
the first time for the overlay window to react to keyboard events, and
so it's possible to make any arbitrary interactive web UI with it!

It's pretty neato.

This is a proof of concept!  There are many minor bugs, like
drag-and-drop completely breaks when trying to drag out of the overlay's
clickable area, and enter/leave events jitter when sweeping the mouse
along edges of the screen.  That kind of thing.

Related to #3 and #5, but not global; no events are duplicated here.
  • Loading branch information
anko committed May 3, 2021
1 parent 16d1a9f commit d40cb15
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 1 deletion.
6 changes: 6 additions & 0 deletions example/page.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<body>
<div id="monitor-frame-container"></div>
<div id="explanation">
<input></input>
<h1><span>Welcome to the future!</span></h1>
<p><span>You should be able to—
<ul>
Expand All @@ -42,6 +43,11 @@ <h1><span>Welcome to the future!</span></h1>
</div>
<script>

const html = document.getElementsByTagName('html')[0]
document.addEventListener('keydown', e => console.log(e.code))
window.onfocus = () => console.log("focused")
window.onblur = () => console.log("blurred")

// Async wrapper necessary so we can use `await`
;(async () => {
// Make the overlay window clickable in a 100-pixel-wide strip at
Expand Down
60 changes: 59 additions & 1 deletion main.c
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,48 @@ void printUsage(char *programName) {
programName);
}

// Listen for the "pointer entered window" and "pointer left window" events,
// and in response grab or ungrab the keyboard device.
//
// This is necessary because our overlay window has the override_redirect flag
// set, meaning while it can get pointer events, the desktop environment will
// never give it keyboard events. It can however *grab the keyboard device*
// itself, effectively taking keyboard input focus instead of being given it.
// So we do that.
struct PointerEnterEventData {
GdkSeat *seat;
GdkWindow *window;
};
gboolean on_pointer_enter_window(
GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) {
struct PointerEnterEventData *data =
(struct PointerEnterEventData *) user_data;
printf("Pointer entered window. Grabbing.\n");
gdk_seat_grab(data->seat, data->window,
// Grab the keyboard only we already get pointer events.
GDK_SEAT_CAPABILITY_KEYBOARD,
FALSE, // owner events
NULL, // cursor
// event that triggered the grab: Internally, gdk_seat_grab uses
// XIGrabDevice, which reads the event's timestamp. The grab
// request is cancelled if the given event happened before the last
// device grab. This makes stuff happen in the expected order when
// under heavy system load, where events may be handled with
// significant delay.
(GdkEvent *) event,
NULL, // prepare func (don't need it; window is visible already)
NULL // user_data passed to prepare func
);
return FALSE;
}
gboolean on_pointer_leave_window(
GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) {
GdkSeat *seat = (GdkSeat *) user_data;
printf("Pointer left window. Releasing grab.\n");
gdk_seat_ungrab(seat);
return FALSE;
}

int main(int argc, char **argv) {

gtk_init(&argc, &argv);
Expand Down Expand Up @@ -661,6 +703,22 @@ int main(int argc, char **argv) {
g_signal_connect(G_OBJECT(window), "delete-event", gtk_main_quit, NULL);
gtk_widget_set_app_paintable(window, TRUE);

// Listen for enter and leave notify events.
//
// These are fired when the mouse pointer enters the overlay window. They
// won't fire by default (because it has no input-area), but will fire if
// JS has called `Hudkit.setClickableAreas`, and the mouse pointer enters
// one of those areas.
GdkDisplay *display = gdk_display_get_default();
GdkSeat *seat = gdk_display_get_default_seat(display);
gtk_widget_set_events(window,
GDK_ENTER_NOTIFY_MASK & GDK_LEAVE_NOTIFY_MASK);
struct PointerEnterEventData pointer_enter_event_data = { seat };
g_signal_connect (window, "enter-notify-event",
G_CALLBACK(on_pointer_enter_window), &pointer_enter_event_data);
g_signal_connect (window, "leave-notify-event",
G_CALLBACK(on_pointer_leave_window), seat);

//
// Set up the WebKit web view widget
//
Expand Down Expand Up @@ -729,7 +787,6 @@ int main(int argc, char **argv) {
// it correctly.
screen_changed(window, NULL, web_view);

GdkDisplay *display = gdk_display_get_default();
GdkRectangle *rectangles = NULL;
int nRectangles = get_monitor_rects(display, &rectangles);

Expand All @@ -738,6 +795,7 @@ int main(int argc, char **argv) {
// Hide the window, so we can get our properties ready without the window
// manager trying to mess with us.
GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
pointer_enter_event_data.window = gdk_window;
gdk_window_hide(GDK_WINDOW(gdk_window));

// "Can't touch this!" - to the window manager
Expand Down

0 comments on commit d40cb15

Please sign in to comment.