+/** Custom GLib event source for generic descriptor I/O.
+ * @see https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html
+ */
+struct fd_source {
+ GSource base;
+
+ int64_t timeout_us;
+ int64_t due_us;
+
+ /* Meta-data needed to keep track of installed sources */
+ struct sr_session *session;
+ void *key;
+
+ GPollFD pollfd;
+};
+
+/** FD event source prepare() method.
+ * This is called immediately before poll().
+ */
+static gboolean fd_source_prepare(GSource *source, int *timeout)
+{
+ int64_t now_us;
+ struct fd_source *fsource;
+ int remaining_ms;
+
+ fsource = (struct fd_source *)source;
+
+ if (fsource->timeout_us >= 0) {
+ now_us = g_source_get_time(source);
+
+ if (fsource->due_us == 0) {
+ /* First-time initialization of the expiration time */
+ fsource->due_us = now_us + fsource->timeout_us;
+ }
+ remaining_ms = (MAX(0, fsource->due_us - now_us) + 999) / 1000;
+ } else {
+ remaining_ms = -1;
+ }
+ *timeout = remaining_ms;
+
+ return (remaining_ms == 0);
+}
+
+/** FD event source check() method.
+ * This is called after poll() returns to check whether an event fired.
+ */
+static gboolean fd_source_check(GSource *source)
+{
+ struct fd_source *fsource;
+ unsigned int revents;
+
+ fsource = (struct fd_source *)source;
+ revents = fsource->pollfd.revents;
+
+ return (revents != 0 || (fsource->timeout_us >= 0
+ && fsource->due_us <= g_source_get_time(source)));
+}
+
+/** FD event source dispatch() method.
+ * This is called if either prepare() or check() returned TRUE.
+ */
+static gboolean fd_source_dispatch(GSource *source,
+ GSourceFunc callback, void *user_data)
+{
+ struct fd_source *fsource;
+ unsigned int revents;
+ gboolean keep;
+
+ fsource = (struct fd_source *)source;
+ revents = fsource->pollfd.revents;
+
+ if (!callback) {
+ sr_err("Callback not set, cannot dispatch event.");
+ return G_SOURCE_REMOVE;
+ }
+ keep = (*SR_RECEIVE_DATA_CALLBACK(callback))
+ (fsource->pollfd.fd, revents, user_data);
+
+ if (fsource->timeout_us >= 0 && G_LIKELY(keep)
+ && G_LIKELY(!g_source_is_destroyed(source)))
+ fsource->due_us = g_source_get_time(source)
+ + fsource->timeout_us;
+ return keep;
+}
+
+/** FD event source finalize() method.
+ */
+static void fd_source_finalize(GSource *source)
+{
+ struct fd_source *fsource;
+
+ fsource = (struct fd_source *)source;
+
+ sr_dbg("%s: key %p", __func__, fsource->key);
+
+ sr_session_source_destroyed(fsource->session, fsource->key, source);
+}
+
+/** Create an event source for I/O on a file descriptor.
+ *
+ * In order to maintain API compatibility, this event source also doubles
+ * as a timer event source.
+ *
+ * @param session The session the event source belongs to.
+ * @param key The key used to identify this source.
+ * @param fd The file descriptor or HANDLE.
+ * @param events Events.
+ * @param timeout_ms The timeout interval in ms, or -1 to wait indefinitely.
+ *
+ * @return A new event source object, or NULL on failure.
+ */
+static GSource *fd_source_new(struct sr_session *session, void *key,
+ gintptr fd, int events, int timeout_ms)
+{
+ static GSourceFuncs fd_source_funcs = {
+ .prepare = &fd_source_prepare,
+ .check = &fd_source_check,
+ .dispatch = &fd_source_dispatch,
+ .finalize = &fd_source_finalize
+ };
+ GSource *source;
+ struct fd_source *fsource;
+
+ source = g_source_new(&fd_source_funcs, sizeof(struct fd_source));
+ fsource = (struct fd_source *)source;
+
+ g_source_set_name(source, (fd < 0) ? "timer" : "fd");
+
+ if (timeout_ms >= 0) {
+ fsource->timeout_us = 1000 * (int64_t)timeout_ms;
+ fsource->due_us = 0;
+ } else {
+ fsource->timeout_us = -1;
+ fsource->due_us = INT64_MAX;
+ }
+ fsource->session = session;
+ fsource->key = key;
+
+ fsource->pollfd.fd = fd;
+ fsource->pollfd.events = events;
+ fsource->pollfd.revents = 0;
+
+ if (fd >= 0)
+ g_source_add_poll(source, &fsource->pollfd);
+
+ return source;
+}
+