I need to be notified when mouse enters or leaves a window (any window, that's why I'm grabbing on the root window). I have a solution using xcb_input_xi_grab_device() but it works only half way. When I run the app, I do get enter/leave events but the mouse pointer disappears and it consumes all mouse events and other windows stop (children of the root window) reacting to clicks.
Using XCB_INPUT_GRAB_OWNER_OWNER should allow passing-through the events but it doesn't work.
app.c:
#include <stdio.h>
#include <xcb/xcb.h>
#include <xcb/xinput.h>
xcb_input_device_id_t find_device(xcb_connection_t *conn)
{
xcb_input_xi_query_device_reply_t *reply = xcb_input_xi_query_device_reply(
conn, xcb_input_xi_query_device(conn, XCB_INPUT_DEVICE_ALL), NULL);
xcb_input_xi_device_info_iterator_t iter =
xcb_input_xi_query_device_infos_iterator(reply);
xcb_input_device_id_t device_id;
int found = 0;
while (iter.rem) {
xcb_input_xi_device_info_t *device = iter.data;
switch (device->type) {
case XCB_INPUT_DEVICE_TYPE_MASTER_POINTER:
case XCB_INPUT_DEVICE_TYPE_SLAVE_POINTER:
case XCB_INPUT_DEVICE_TYPE_FLOATING_SLAVE:
device_id = device->deviceid;
found = 1;
break;
}
if (found) {
break;
}
xcb_input_xi_device_info_next(&iter);
}
free(reply);
return device_id;
}
int main()
{
xcb_connection_t *conn = xcb_connect(NULL, NULL);
xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
uint32_t mask =
XCB_INPUT_XI_EVENT_MASK_ENTER | XCB_INPUT_XI_EVENT_MASK_LEAVE;
xcb_generic_error_t *error;
xcb_input_xi_grab_device_reply_t *reply = xcb_input_xi_grab_device_reply(
conn,
xcb_input_xi_grab_device(conn, screen->root, XCB_CURRENT_TIME,
XCB_CURSOR_NONE, find_device(conn),
XCB_INPUT_GRAB_MODE_22_ASYNC,
XCB_INPUT_GRAB_MODE_22_ASYNC,
XCB_INPUT_GRAB_OWNER_OWNER, 1, &mask),
&error);
free(reply);
if (error) {
printf("failed to grab device\n");
free(error);
return -1;
}
xcb_flush(conn);
xcb_generic_event_t *event;
while ((event = xcb_wait_for_event(conn))) {
xcb_ge_event_t *generic_event = event;
switch (generic_event->event_type) {
case XCB_INPUT_ENTER:
printf("enter\n");
break;
case XCB_INPUT_LEAVE:
printf("leave\n");
break;
}
free(event);
}
return -1; // never reached
}
cc -O2 app.c -o app `pkg-config --cflags --libs xcb xcb-xinput`
I also tried to achieve it with xcb_grab_button(), xcb_input_xi_select_events(), xcb_input_xi_passive_grab_device() or xcb_grab_pointer() but then the problem is the same. Either the mouse events are not passed-through or I don't receive enter/leave events at all.
Related
I'm using Vs Code as my IDE.
I've figured out how to add breakpoints and have the debugger hit them, but only if they're added before compiling. In other game development environments I'm used to being able to add a breakpoint whenever I want to halt the game and check things out.
Is there any way to add breakpoints while my game is running?
This is the command I'm running to build my game.
gcc -std=c17 main.c -g -I "C:\Program Files\clib\SDL2\include" -L "C:\Program Files\clib\SDL2\lib" -Wall -lmingw32 -lSDL2main -lSDL2 -o game
This is my little "game" that I copy-pasted from the internet, in case that helps provide context. It's a little red square that can move and jump around.
#include <stdio.h>
#include <stdbool.h>
#include <SDL2/SDL.h>
#define WIDTH 640
#define HEIGHT 480
#define SIZE 200
#define SPEED 600
#define GRAVITY 60
#define FPS 60
#define JUMP -1200
int main(int argc, char *argv[])
{
/* Initializes the timer, audio, video, joystick,
haptic, gamecontroller and events subsystems */
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
{
printf("Error initializing SDL: %s\n", SDL_GetError());
return 0;
}
/* Create a window */
SDL_Window *wind = SDL_CreateWindow(
"Hello Platformer!",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
WIDTH,
HEIGHT,
0
);
if (!wind)
{
printf("Error creating window: %s\n", SDL_GetError());
SDL_Quit();
return 0;
}
/* Create a renderer */
Uint32 render_flags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC;
SDL_Renderer *rend = SDL_CreateRenderer(wind, -1, render_flags);
if (!rend)
{
printf("Error creating renderer: %s\n", SDL_GetError());
SDL_DestroyWindow(wind);
SDL_Quit();
return 0;
}
/* Main loop */
bool running = true,
jump_pressed = false,
can_jump = true,
left_pressed = false,
right_pressed = false;
float x_pos = (WIDTH - SIZE) / 2, y_pos = (HEIGHT - SIZE) / 2, x_vel = 0, y_vel = 0;
SDL_Rect rect = {(int)x_pos, (int)y_pos, SIZE, SIZE};
SDL_Event event;
while (running)
{
/* Process events */
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
running = false;
break;
case SDL_KEYDOWN:
switch (event.key.keysym.scancode)
{
case SDL_SCANCODE_SPACE:
jump_pressed = true;
break;
case SDL_SCANCODE_A:
case SDL_SCANCODE_LEFT:
left_pressed = true;
break;
case SDL_SCANCODE_D:
case SDL_SCANCODE_RIGHT:
right_pressed = true;
break;
default:
break;
}
break;
case SDL_KEYUP:
switch (event.key.keysym.scancode)
{
case SDL_SCANCODE_SPACE:
jump_pressed = false;
break;
case SDL_SCANCODE_A:
case SDL_SCANCODE_LEFT:
left_pressed = false;
break;
case SDL_SCANCODE_D:
case SDL_SCANCODE_RIGHT:
right_pressed = false;
break;
default:
break;
}
break;
default:
break;
}
}
/* Clear screen */
SDL_SetRenderDrawColor(rend, 0, 0, 0, 255);
SDL_RenderClear(rend);
/* Move the rectangle */
x_vel = (right_pressed - left_pressed) * SPEED;
y_vel += GRAVITY;
if (jump_pressed && can_jump)
{
can_jump = false;
y_vel = JUMP;
}
x_pos += x_vel / 60;
y_pos += y_vel / 60;
if (x_pos <= 0) x_pos = 0;
if (x_pos >= WIDTH - rect.w) x_pos = WIDTH - rect.w;
if (y_pos <= 0) y_pos = 0;
if (y_pos >= HEIGHT - rect.h)
{
y_vel = 0;
y_pos = HEIGHT - rect.h;
if (!jump_pressed)
can_jump = true;
}
rect.x = (int)x_pos;
rect.y = (int)y_pos;
/* Draw the rectangle */
SDL_SetRenderDrawColor(rend, 255, 0, 0, 255);
SDL_RenderFillRect(rend, &rect);
/* Draw to window and loop */
SDL_RenderPresent(rend);
SDL_Delay(1000 / FPS);
}
/* Release resources */
SDL_DestroyRenderer(rend);
SDL_DestroyWindow(wind);
SDL_Quit();
return 0;
}
After adding a checkpoint, you need to send SIGINT to your program by pressing CtrlC in its terminal.
This will pause the program. After you resume it with the debugger, any previously set checkpoints will work.
I don't have experience with the stock C++ extension, but at least in the Native Debug extension you need to add "windows": {"terminal": ""}, to the current configuration in launch.json, to get a dedicated terminal for your app.
I want to put a string or bytes to the X11 selection so that when I switch to other applications, I can directly do a ctrl+p paste.
I try to follow the documentation of the X11 clipboard mechanism. If I understand correctly, I need to use XSetSelectionOwner to obtain the XA_CLIPBOARD selection and then use XChangeProperty to put my data to the clipboard.
Here is a simple snippet, but unfortunately it does not work:
// main.c
#include <stdlib.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xmu/Atoms.h>
int main() {
// try to write `hello` to the clipboard
const char *in = "hello\0";
const int n = 5;
Display* d = XOpenDisplay(0);
Window w = XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, 1, 1, 0, 0, 0);
Atom XA_CLIPBOARD = XInternAtom(d, "CLIPBOARD", True);
XSetSelectionOwner(d, XA_CLIPBOARD, w, CurrentTime);
XEvent event;
XNextEvent(d, &event);
if (event.type != SelectionRequest) {
XCloseDisplay(d);
return 0;
}
if (event.xselectionrequest.selection != XA_CLIPBOARD) {
XCloseDisplay(d);
return 0;
}
XSelectionRequestEvent* req = &event.xselectionrequest;
XChangeProperty(d, req->requestor, req->property, XA_STRING, 8, PropModeReplace, (unsigned char *)in, n);
XEvent re = {0};
re.xselection.type = SelectionNotify;
re.xselection.display = req->display;
re.xselection.requestor = req->requestor;
re.xselection.selection = req->selection;
re.xselection.property = req->property;
re.xselection.target = req->target;
XSendEvent(d, req->requestor, 0, 0, &re); // event is sent, but data is not in my clipboard
XFlush(d);
XCloseDisplay(d);
return 0;
}
Compile: clang -o main main.c -lX11 -lXmu
What did I do wrong, and how to fix it?
XNextEvent retrieves any event that occured (input, pointer, configuration, property changes, ...).
So it is very unlikely that the first event, will be your desired event.
You have to iterate over the event queue to check if a SelectionRequest event occured.
Therefore you have to call XNextEvent in a loop and check everytime the event.type for the desired event.
Edit:
If you retrieve a ClientMessage event and the client data equals the Atom WM_DELETE, there was a request by the user to close the window (pressing the X). Other than that, you can quit whenever you want.
XEvent evt;
while (i_want_to_receive_and_process_events) {
XNextEvent(dpy, &evt);
switch (evt.type) {
case SelectionRequest:
if (evt.xselectionrequest.selection == XA_CLIPBOARD) {
// what you were looking for, do your thing
// i_want_to_receive_and_process_events = false (?)
}
break;
case ClientMessage:
if (evt.xclient.data.l[0] == WM_DELETE) {
i_want_to_receive_and_process_events = false;
}
break;
}
}
In a purely GDK 3.10 (no GTK) project how do I refresh/clear/redraw a transparent background of a GdkWindow?
This test.c sets the background correctly on initialization (gdk_window_show()) and when I iconify+deiconify the window but not on resizing or moving it around per mouse:
#include <gdk/gdk.h>
// #include <gdk/gdkx.h> // GDK X11
// #include <X11/Xlib.h> // Xlib
// #include <X11/Xutil.h>
void eventHandler(GdkEvent *evt, gpointer data) {
GdkWindow *win = (GdkWindow *)data;
switch(evt->type)
{
case GDK_CONFIGURE: {
// refresh window background here
printf("refresh background here\n");
break;
}
case GDK_DELETE: {
gdk_window_destroy(win);
_exit(0);
break;
}
default:
break;
}
}
int main(int argc, char *argv[]) {
gdk_init(NULL, NULL);
GdkDisplay *disp=gdk_display_get_default();
GdkScreen *scr = gdk_display_get_default_screen (disp);
GdkWindow *root = gdk_screen_get_root_window(scr);
GdkWindowAttr attr;
attr.width=200;
attr.height=200;
attr.x=0;
attr.y=0;
attr.window_type = GDK_WINDOW_TOPLEVEL;
attr.wclass=GDK_INPUT_OUTPUT;
GdkVisual *vis = gdk_screen_get_rgba_visual (scr);
attr.visual = vis;
GdkWindow *newWin=gdk_window_new(root,&attr, GDK_WA_X | GDK_WA_Y);
GdkRGBA color = { .red=1.0, .green=0.0, .blue=1.0, .alpha=0.0};
gdk_window_set_background_rgba(newWin, &color);
gdk_event_handler_set (eventHandler, newWin, NULL);
gdk_window_show(newWin);
GMainLoop *mainloop = g_main_new (TRUE);
g_main_loop_run (mainloop);
gdk_display_close(disp);
return 0;
}
gcc build command:
gcc -o test test.c `pkg-config gdk-3.0 --libs --cflags`
Outcome:
Note: I'm targeting X11 so I'm also allowed to include Xlib or GDK X11 backend functions.
Thank you for any help or pointers.
The solution to this is a fix of a rather silly mistake I made passing an incomplete attribute mask to gdk_window_new().
To make it work the line in question has to be changed to:
GdkWindow *newWin=gdk_window_new(root,&attr, GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_WMCLASS);
I am trying to be notified about any pointer motion. Since I don't want to run as the window manager, I need to set XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_POINTER_MOTION on all windows which I do both on startup and when I get a create notify event.
This seems to work fine in general and I receive motion notify events on all windows. However, somehow, this isn't true for Google Chrome windows. I checked the event mask by explicitly querying it afterwards and it is correctly set. I also don't see anything unusual in the propagation mask.
What could cause Google Chrome to not report motion notify events? AFAIK, the X protocol doesn't allow that except for active pointer grabs which Chrome surely doesn't have.
Here is how I register myself on all existing windows. I call register_events on the root window and whenever I receive a create notify event as well:
static void register_events(xcb_window_t window) {
xcb_void_cookie_t cookie = xcb_change_window_attributes_checked(connection,
window, XCB_CW_EVENT_MASK, (uint32_t[]) { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_LEAVE_WINDOW });
xcb_generic_error_t *error = xcb_request_check(connection, cookie);
if (error != NULL) {
xcb_disconnect(connection);
errx(EXIT_FAILURE, "could not subscribe to events on a window, bailing out");
}
}
static void register_existing_windows(void) {
xcb_query_tree_reply_t *reply;
if ((reply = xcb_query_tree_reply(connection, xcb_query_tree(connection, root), 0)) == NULL) {
return;
}
int len = xcb_query_tree_children_length(reply);
xcb_window_t *children = xcb_query_tree_children(reply);
for (int i = 0; i < len; i++) {
register_events(children[i]);
}
xcb_flush(connection);
free(reply);
}
The Chrome windows appear to be comprised of quite the tree of nested child windows. It appears you'll need to walk the tree of windows and monitor them all. This code picks up pointer motion events across the entirety of my Chrome windows:
#include <stdio.h>
#include <stdlib.h>
#include <xcb/xcb.h>
#include <X11/Xlib.h>
static void register_events(xcb_connection_t *conn,
xcb_window_t window) {
xcb_void_cookie_t cookie =
xcb_change_window_attributes_checked(conn,
window, XCB_CW_EVENT_MASK,
(uint32_t[]) {
XCB_EVENT_MASK_POINTER_MOTION });
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
if (error != NULL) {
xcb_disconnect(conn);
exit(-1);
}
}
static void register_existing_windows(xcb_connection_t *conn,
xcb_window_t root) {
int i, len;
xcb_window_t *children;
xcb_query_tree_reply_t *reply;
if ((reply = xcb_query_tree_reply(conn,
xcb_query_tree(conn, root), 0))
== NULL)
{
return;
}
len = xcb_query_tree_children_length(reply);
children = xcb_query_tree_children(reply);
for (i = 0; i < len; i++) {
register_events(conn, children[i]);
register_existing_windows(conn, children[i]);
}
xcb_flush(conn);
}
void main(void) {
int i=0;
/* Open the connection to the X server */
xcb_connection_t *conn = xcb_connect (NULL, NULL);
/* Get the first screen */
xcb_screen_t *screen = xcb_setup_roots_iterator (xcb_get_setup (conn)).data;
register_existing_windows(conn, screen->root);
while(1) {
xcb_generic_event_t *evt;
evt = xcb_wait_for_event(conn);
printf("%i\n", i++);
}
}
(That's just intended as proof of concept, and not very nice.)
While #Jay Kominek's answer was helpful and valid, I've come to realize now that using the Xinput extension provides a much better approach as it won't interfere with applications whatsoever.
Simply selecting on the entire tree causes all kinds of issues, e.g., hover doesn't work in Chrome anymore.
xcb provides xcb_grab_pointer to capture pointer event without registe on specific window.
#include <stdlib.h>
#include <stdio.h>
#include <xcb/xcb.h>
void
print_modifiers (uint32_t mask)
{
const char **mod, *mods[] = {
"Shift", "Lock", "Ctrl", "Alt",
"Mod2", "Mod3", "Mod4", "Mod5",
"Button1", "Button2", "Button3", "Button4", "Button5"
};
printf ("Modifier mask: ");
for (mod = mods ; mask; mask >>= 1, mod++)
if (mask & 1)
printf(*mod);
putchar ('\n');
}
int
main ()
{
xcb_connection_t *c;
xcb_screen_t *screen;
xcb_window_t win;
xcb_generic_event_t *e;
uint32_t mask = 0;
/* Open the connection to the X server */
c = xcb_connect (NULL, NULL);
/* Get the first screen */
screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data;
mask = XCB_EVENT_MASK_BUTTON_PRESS |
XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION;
xcb_grab_pointer(c, false, screen->root, mask, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE, XCB_NONE, XCB_CURRENT_TIME);
xcb_flush (c);
while ((e = xcb_wait_for_event (c))) {
switch (e->response_type & ~0x80) {
case XCB_BUTTON_PRESS: {
xcb_button_press_event_t *ev = (xcb_button_press_event_t *)e;
print_modifiers(ev->state);
switch (ev->detail) {
case 4:
printf ("Wheel Button up in window %ld, at coordinates (%d,%d)\n",
ev->event, ev->event_x, ev->event_y);
break;
case 5:
printf ("Wheel Button down in window %ld, at coordinates (%d,%d)\n",
ev->event, ev->event_x, ev->event_y);
break;
default:
printf ("Button %d pressed in window %ld, at coordinates (%d,%d)\n",
ev->detail, ev->event, ev->event_x, ev->event_y);
}
break;
}
case XCB_BUTTON_RELEASE: {
xcb_button_release_event_t *ev = (xcb_button_release_event_t *)e;
print_modifiers(ev->state);
printf ("Button %d released in window %ld, at coordinates (%d,%d)\n",
ev->detail, ev->event, ev->event_x, ev->event_y);
break;
}
case XCB_MOTION_NOTIFY: {
xcb_motion_notify_event_t *ev = (xcb_motion_notify_event_t *)e;
printf ("Mouse moved in window %ld, at coordinates (%d,%d)\n",
ev->event, ev->event_x, ev->event_y);
break;
}
default:
/* Unknown event type, ignore it */
printf("Unknown event: %d\n", e->response_type);
break;
}
/* Free the Generic Event */
free (e);
}
return 0;
}
I have a lightweight application that catches Xorg and dbus events. In order to do this I initialized dbus loop and started g_main_loop, but I don't know how to add Xorg event handling in a natural way:
GMainLoop * mainloop = NULL;
mainloop = g_main_loop_new(NULL,FALSE);
dbus_g_thread_init ();
dbus_init();
// <<<<<<<<<<<<<<<<<<<<<<<<<
//1 way using timeout
//g_timeout_add(100, kbdd_default_iter, mainloop);
//2nd way using pthread
//GThread * t = g_thread_create(kbdd_default_loop, NULL, FALSE, NULL);
//>>>>>>>>>>>>>>>>>>>>>>>>>>>
g_main_loop_run(mainloop);
in default iter I'm checking if there is waiting X-event and handle them.
Both ways seems bad, first because I have unneeded calls for checking event, second because I make an additional thread and have to make additional locks.
P.S. I know I can use gtk lib, but I don't want to have dependencies on any toolkit.
If you want to add Xorg event handling to the main loop without using a timeout (which as you state is wasteful), you'll need to add a source that polls the X connection. For that, you'll need to get below the Xlib abstraction layer to get the underlying X connection file descriptor. That's what the complete program below does. It is an adaptation of C. Tronche's excellent X11 tutorial to use the glib main loop for polling. I also drew from "Foundations of GTK+ Development" by Andrew Krause.
If this doesn't seem very "natural", that's because I doubt there is a very "natural" way to do this - you're really re-implementing a core part of GDK here.
/* needed to break into 'Display' struct internals. */
#define XLIB_ILLEGAL_ACCESS
#include <X11/Xlib.h> // Every Xlib program must include this
#include <assert.h> // I include this to test return values the lazy way
#include <glib.h>
typedef struct _x11_source {
GSource source;
Display *dpy;
Window w;
} x11_source_t;
static gboolean
x11_fd_prepare(GSource *source,
gint *timeout)
{
*timeout = -1;
return FALSE;
}
static gboolean
x11_fd_check (GSource *source)
{
return TRUE;
}
static gboolean
x11_fd_dispatch(GSource* source, GSourceFunc callback, gpointer user_data)
{
static gint counter = 0;
Display *dpy = ((x11_source_t*)source)->dpy;
Window window = ((x11_source_t*)source)->w;
XEvent e;
while (XCheckWindowEvent(dpy,
window,
EnterWindowMask,
&e))
{
if (e.type == EnterNotify)
g_print("We're in!!! (%d)\n", ++counter);
}
return TRUE;
}
static gboolean
msg_beacon(gpointer data)
{
static gint counter = 0;
g_print("Beacon %d\n", ++counter);
return TRUE;
}
int
main()
{
Display *dpy = XOpenDisplay(NULL);
assert(dpy);
int blackColor = BlackPixel(dpy, DefaultScreen(dpy));
int whiteColor = WhitePixel(dpy, DefaultScreen(dpy));
Window w = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0,
200, 100, 0, blackColor, blackColor);
XSelectInput(dpy, w, StructureNotifyMask | EnterWindowMask);
XMapWindow(dpy, w);
for (;;) {
XEvent e;
XNextEvent(dpy, &e);
if (e.type == MapNotify)
break;
}
GMainLoop *mainloop = NULL;
mainloop = g_main_loop_new(NULL, FALSE);
/* beacon to demonstrate we're not blocked. */
g_timeout_add(300, msg_beacon, mainloop);
GPollFD dpy_pollfd = {dpy->fd,
G_IO_IN | G_IO_HUP | G_IO_ERR,
0};
GSourceFuncs x11_source_funcs = {
x11_fd_prepare,
x11_fd_check,
x11_fd_dispatch,
NULL, /* finalize */
NULL, /* closure_callback */
NULL /* closure_marshal */
};
GSource *x11_source =
g_source_new(&x11_source_funcs, sizeof(x11_source_t));
((x11_source_t*)x11_source)->dpy = dpy;
((x11_source_t*)x11_source)->w = w;
g_source_add_poll(x11_source, &dpy_pollfd);
g_source_attach(x11_source, NULL);
g_main_loop_run(mainloop);
return 0;
}