Re: Help with external socket handling and xcb

Uli Schlachter <[email protected]> Sat, 25 Sep 2021 11:12:09 +0200
Newsgroups gmane.comp.freedesktop.xcb
Message-ID <[email protected]>
Hi,

Am 09.09.21 um 12:48 schrieb Carlo Wood:
> Just to be clear; so this means that in order for xcb_poll_for_event
> to work while only calling it when I see that the fd is readable, I only
> ever have to monitor that the fd is readable, and never have to worry
> about monitoring if the fd is writable, because all writing is done
> blocking, even though the fd itself is non-blocking?

Attached is a short example based on the code that I already linked to
elsewhere. This code sends some requests and non-blockingly waits for
the replies (via poll()). When poll() indicates a timeout, some more
requests are sent just so that the example does something.

The important bits are: Whenever anything used the xcb connection (in
this case: through xcb_get_input_focus()), this calls both xcb_flush()
(to (blockingly - sorry) send the new requests) and
dispatch_replies()/xcb_poll_for_reply() to check whether new replies
were received. The later is necessary since both xcb_get_input_focus()
(sending a request) and xcb_flush() (flushing the output buffer) might
read from the fd.

Put differently: We always have to check whether the fd is readable *or*
some replies are already pending.

This example does not deal with events, but that is just one
xcb_poll_for_event() away.

I hope this helps,
Uli
-- 
I'd be delighted to offer any advice I can. When I have some, I'll let
you know.
foo.c (text/x-csrc, 2.9 KB)
#include <xcb/xcb.h>
#include <xcb/xcbext.h>
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>

typedef void reply_callback(void *reply, xcb_generic_error_t *error, void *data);

typedef struct pending_reply_t {
	unsigned int sequence;
	reply_callback *callback;
	void *data;
	struct pending_reply_t *next;
} pending_reply_t;

// global state is bad
static pending_reply_t *pending_replies = NULL;

static void add_pending_reply(unsigned int sequence, reply_callback *callback, void *data) {
	struct pending_reply_t **prev = &pending_replies;
	struct pending_reply_t *new_reply = malloc(sizeof(*new_reply));

	if (sequence == 0) {
		// Sending the request failed.
		// TODO: Uhm... call the callback? Just drop things on the
		// floor?
		return;
	}

	new_reply->sequence = sequence;
	new_reply->callback = callback;
	new_reply->data = data;

	// Insert so that ->sequence is sorted
	// TODO: This should probably take overflows into account
	while (*prev && (*prev)->sequence < new_reply->sequence) {
		prev = &(*prev)->next;
	}

	// Actually insert
	new_reply->next = *prev;
	*prev = new_reply;
}

static void dispatch_replies(xcb_connection_t *conn, int poll) {
	while (pending_replies) {
		void *reply;
		xcb_generic_error_t *error = NULL;
		pending_reply_t *next = pending_replies;

		if (poll) {
			if (!xcb_poll_for_reply(conn, next->sequence, &reply, &error))
				// There was no reply yet
				return;
		} else {
			reply = xcb_wait_for_reply(conn, next->sequence, &error);
		}

		pending_replies = next->next;
		(*next->callback)(reply, error, next->data);
		free(next);
	}
}

static void print_reply(xcb_get_input_focus_reply_t *reply, xcb_generic_error_t *err, const char *str) {
	if (reply) {
		printf("%s got 0x%x\n", str, reply->focus);
	}
	if (err) {
		puts("How the heck did GetInputFocus fail?!");
	}
	free(reply);
	free(err);
}


int main() {
	xcb_connection_t *conn = xcb_connect(NULL, NULL);
	add_pending_reply(xcb_get_input_focus(conn).sequence, (reply_callback *) print_reply, "The first reply");
	add_pending_reply(xcb_get_input_focus(conn).sequence, (reply_callback *) print_reply, "The second reply");

	// Fake a main loop
	while (!xcb_connection_has_error(conn)) {
		struct pollfd fds[1];
		fds[0].fd = xcb_get_file_descriptor(conn);
		fds[0].events = POLLIN;
		fds[0].revents = 0;

		if (0 == poll(fds, 1, 1000)) {
			puts("Timeout! Sending more requests");
			add_pending_reply(xcb_get_input_focus(conn).sequence, (reply_callback *) print_reply, "The third reply");
			add_pending_reply(xcb_get_input_focus(conn).sequence, (reply_callback *) print_reply, "The fourth reply");
		}

		// The above might have put new stuff in xcb's output buffer.
		// Send that. (Yes, this does a blocking send - sorry).
		xcb_flush(conn);

		// The above calls to xcb_get_input_focus() and xcb_flush()
		// might have read from the socket. Make sure we are not missing
		// anything and check for new replies.
		dispatch_replies(conn, 1);
	}

	puts("done");
	xcb_disconnect(conn);

	return 0;
}