diff --git a/CMakeLists.txt b/CMakeLists.txt
index 19bf901..2f6d3e4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -33,19 +33,20 @@ pkg_check_modules (dependencies REQUIRED gtk+-3.0 sqlite3 x11 xext xextproto)
# Precompile Vala sources
include (ValaPrecompile)
-set (config_path "${PROJECT_BINARY_DIR}/config.vala")
-configure_file (${PROJECT_SOURCE_DIR}/config.vala.in "${config_path}")
+set (config_path "${PROJECT_BINARY_DIR}/config.h")
+configure_file (${PROJECT_SOURCE_DIR}/config.h.in "${config_path}")
+include_directories ("${PROJECT_BINARY_DIR}")
# I'm not sure what this was about, look at slovnik-gui for more comments;
# seems to be so that symbols are exported for GModule to see
-set (symbols_path "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.def")
+set (symbols_path "${PROJECT_BINARY_DIR}/gui.def")
-set (project_VALA_SOURCES ${config_path} ${PROJECT_NAME}.vala xext.vapi)
+set (project_VALA_SOURCES gui.vala config.vapi)
vala_precompile (${project_VALA_SOURCES}
OUTPUTS project_VALA_C
- HEADER ${PROJECT_NAME}.h
+ HEADER gui.h
SYMBOLS ${symbols_path}
- PACKAGES posix gmodule-2.0 gio-2.0 gio-unix-2.0 gtk+-3.0 gee-0.8 sqlite3 x11)
+ PACKAGES gmodule-2.0 gtk+-3.0 gee-0.8 sqlite3)
# Include Vala sources as header files, so they appear in the IDE
# but CMake doesn't try to compile them directly
@@ -56,7 +57,7 @@ set (project_SOURCES ${project_VALA_SOURCES} ${project_VALA_C} ${symbols_path})
# Build the executable and install it
include_directories (${dependencies_INCLUDE_DIRS})
link_directories (${dependencies_LIBRARY_DIRS})
-add_executable (${PROJECT_NAME} ${project_SOURCES})
+add_executable (${PROJECT_NAME} ${PROJECT_NAME}.c ${project_SOURCES})
target_link_libraries (${PROJECT_NAME} ${dependencies_LIBRARIES})
install (TARGETS ${PROJECT_NAME} DESTINATION bin)
diff --git a/README.adoc b/README.adoc
index b002b2d..44b7735 100644
--- a/README.adoc
+++ b/README.adoc
@@ -43,9 +43,6 @@ Use https://git.janouch.name/p/wdmtg to report any bugs, request features,
or submit pull requests. `git send-email` is tolerated. If you want to discuss
the project, feel free to join me at ircs://irc.janouch.name, channel #dev.
-The in-source dependency comments are there for the VIM Syntastic Vala plugin.
-Vala just makes all sorts of things complicated but it's a necessary evil here.
-
Bitcoin donations are accepted at: 12r5uEWEgcHC46xd64tt3hHt9EUvYYDHe9
License
diff --git a/config.h.in b/config.h.in
new file mode 100644
index 0000000..32ca612
--- /dev/null
+++ b/config.h.in
@@ -0,0 +1,3 @@
+#define PROJECT_NAME "@CMAKE_PROJECT_NAME@"
+#define PROJECT_VERSION "@project_VERSION@"
+#define SHARE_DIR "@project_SHARE_DIR@"
diff --git a/config.vala.in b/config.vala.in
deleted file mode 100644
index 9a4b63b..0000000
--- a/config.vala.in
+++ /dev/null
@@ -1,8 +0,0 @@
-[CCode (cprefix = "", lower_case_cprefix = "")]
-namespace Config
-{
- public const string PROJECT_NAME = "@CMAKE_PROJECT_NAME@";
- public const string PROJECT_VERSION = "@project_VERSION@";
- public const string SHARE_DIR = "@project_SHARE_DIR@";
-}
-
diff --git a/config.vapi b/config.vapi
new file mode 100644
index 0000000..c760a9c
--- /dev/null
+++ b/config.vapi
@@ -0,0 +1,6 @@
+[CCode (cheader_filename = "config.h", cprefix = "", lower_case_cprefix = "")]
+namespace Config {
+ public const string PROJECT_NAME;
+ public const string PROJECT_VERSION;
+ public const string SHARE_DIR;
+}
diff --git a/gui.vala b/gui.vala
new file mode 100644
index 0000000..fe3bcb7
--- /dev/null
+++ b/gui.vala
@@ -0,0 +1,24 @@
+//
+// gui.vala: activity tracker - GUI part
+//
+// Copyright (c) 2020, Přemysl Eric Janouch
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+
+public class WdmtgWindow : Gtk.Window {
+ private unowned Sqlite.Database db;
+
+ public WdmtgWindow.with_db (Sqlite.Database db) {
+ this.db = db;
+ }
+}
diff --git a/meson.build b/meson.build
index f1c97b1..62f58d0 100644
--- a/meson.build
+++ b/meson.build
@@ -7,8 +7,8 @@ conf = configuration_data()
conf.set('CMAKE_PROJECT_NAME', meson.project_name())
conf.set('project_VERSION', meson.project_version())
configure_file(
- input : 'config.vala.in',
- output : 'config.vala',
+ input : 'config.h.in',
+ output : 'config.h',
configuration : conf,
)
@@ -20,18 +20,13 @@ dependencies = [
dependency('gee-0.8'),
dependency('sqlite3'),
dependency('x11'),
-
- # Only because of flock
- meson.get_compiler('vala').find_library('posix'),
-
- # Ours
dependency('xext'),
dependency('xextproto'),
]
-sources = files(
- 'wdmtg.vala',
- meson.current_build_dir() / 'config.vala',
-)
-executable('wdmtg', sources,
- install : true,
+gui = static_library('gui', 'gui.vala', 'config.vapi',
+ install : false,
dependencies : dependencies)
+executable('wdmtg', 'wdmtg.c',
+ install : true,
+ link_with : [gui],
+ dependencies : [dependencies])
diff --git a/wdmtg.c b/wdmtg.c
new file mode 100644
index 0000000..d794c8b
--- /dev/null
+++ b/wdmtg.c
@@ -0,0 +1,506 @@
+//
+// wdmtg.c: activity tracker
+//
+// Copyright (c) 2016 - 2020, Přemysl Eric Janouch
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include "config.h"
+#include "gui.h"
+
+// --- Utilities ---------------------------------------------------------------
+
+static void
+exit_fatal(const gchar *format, ...) G_GNUC_PRINTF(1, 2);
+
+static void
+exit_fatal(const gchar *format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+
+ gchar *format_nl = g_strdup_printf("%s\n", format);
+ vfprintf(stderr, format_nl, ap);
+ free(format_nl);
+
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+static GStrv
+get_xdg_config_dirs(void)
+{
+ GPtrArray *paths = g_ptr_array_new();
+ g_ptr_array_add(paths, g_strdup(g_get_user_config_dir()));
+ for (const gchar *const *sys = g_get_system_config_dirs(); *sys; sys++)
+ g_ptr_array_add(paths, g_strdup(*sys));
+ g_ptr_array_add(paths, NULL);
+ return (GStrv) g_ptr_array_free(paths, false);
+}
+
+// --- Globals -----------------------------------------------------------------
+
+struct event {
+ gint64 timestamp; // When the event happened
+ gchar *title; // Current title at the time
+ gboolean idle; // Whether the user is idle
+};
+
+static void
+event_free(struct event *self)
+{
+ g_free(self->title);
+ g_slice_free(struct event, self);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+struct {
+ GAsyncQueue *queue; // Async queue of `struct event`
+} g;
+
+struct {
+ Display *dpy; // X display handle
+
+ Atom net_active_window; // _NET_ACTIVE_WINDOW
+ Atom net_wm_name; // _NET_WM_NAME
+
+ // Window title tracking
+
+ gchar *current_title; // Current window title or NULL
+ Window current_window; // Current window
+ gboolean current_idle; // Current idle status
+
+ // XSync activity tracking
+
+ int xsync_base_event_code; // XSync base event code
+ XSyncCounter idle_counter; // XSync IDLETIME counter
+ XSyncValue idle_timeout; // Idle timeout
+
+ XSyncAlarm idle_alarm_inactive; // User is inactive
+ XSyncAlarm idle_alarm_active; // User is active
+} gen;
+
+// --- X helpers ---------------------------------------------------------------
+
+static XSyncCounter
+get_counter(const char *name)
+{
+ int n;
+ XSyncSystemCounter *counters = XSyncListSystemCounters(gen.dpy, &n);
+ XSyncCounter counter = None;
+ while (n--) {
+ if (!strcmp(counters[n].name, name))
+ counter = counters[n].counter;
+ }
+ XSyncFreeSystemCounterList(counters);
+ return counter;
+}
+
+static char *
+x_text_property_to_utf8(XTextProperty *prop)
+{
+ Atom utf8_string = XInternAtom(gen.dpy, "UTF8_STRING", true);
+ if (prop->encoding == utf8_string)
+ return g_strdup((char *) prop->value);
+
+ int n = 0;
+ char **list = NULL;
+ if (XmbTextPropertyToTextList(gen.dpy, prop, &list, &n) >= Success
+ && n > 0 && *list) {
+ char *result = g_locale_to_utf8(*list, -1, NULL, NULL, NULL);
+ XFreeStringList(list);
+ return result;
+ }
+ return NULL;
+}
+
+static char *
+x_text_property(Window window, Atom atom)
+{
+ XTextProperty name;
+ XGetTextProperty(gen.dpy, window, &name, atom);
+ if (!name.value)
+ return NULL;
+
+ char *result = x_text_property_to_utf8(&name);
+ XFree(name.value);
+ return result;
+}
+
+// --- X error handling --------------------------------------------------------
+
+static XErrorHandler g_default_x_error_handler;
+
+static int
+on_x_error(Display *dpy, XErrorEvent *ee)
+{
+ // This just is going to happen since those windows aren't ours
+ if (ee->error_code == BadWindow)
+ return 0;
+ return g_default_x_error_handler(dpy, ee);
+}
+
+// --- Application -------------------------------------------------------------
+
+static char *
+x_window_title(Window window)
+{
+ char *title;
+ if (!(title = x_text_property(window, gen.net_wm_name))
+ && !(title = x_text_property(window, XA_WM_NAME)))
+ title = g_strdup("broken");
+ return title;
+}
+
+static bool
+update_window_title(char *new_title)
+{
+ bool changed = !gen.current_title != !new_title
+ || (new_title && strcmp(gen.current_title, new_title));
+ free(gen.current_title);
+ gen.current_title = new_title;
+ return changed;
+}
+
+static void
+push_event(void) {
+ struct event *event = g_slice_new0(struct event);
+ event->timestamp = g_get_real_time();
+ event->title = g_strdup(gen.current_title);
+ event->idle = gen.current_idle;
+ g_async_queue_push(g.queue, event);
+}
+
+static void
+update_current_window(void)
+{
+ Window root = DefaultRootWindow(gen.dpy);
+
+ Atom dummy_type; int dummy_format; unsigned long nitems, dummy_bytes;
+ unsigned char *p = NULL;
+ if (XGetWindowProperty(gen.dpy, root, gen.net_active_window,
+ 0L, 1L, false, XA_WINDOW, &dummy_type, &dummy_format,
+ &nitems, &dummy_bytes, &p) != Success)
+ return;
+
+ char *new_title = NULL;
+ if (nitems) {
+ Window active_window = *(Window *) p;
+ XFree(p);
+
+ if (gen.current_window != active_window && gen.current_window)
+ XSelectInput(gen.dpy, gen.current_window, 0);
+
+ XSelectInput(gen.dpy, active_window, PropertyChangeMask);
+ new_title = x_window_title(active_window);
+ gen.current_window = active_window;
+ }
+ if (update_window_title(new_title)) {
+ printf("Window changed: %s\n",
+ gen.current_title ? gen.current_title : "(none)");
+ push_event();
+ }
+}
+
+static void
+on_x_property_notify(XPropertyEvent *ev)
+{
+ // This is from the EWMH specification, set by the window manager
+ if (ev->atom == gen.net_active_window) {
+ update_current_window();
+ } else if (ev->window == gen.current_window &&
+ ev->atom == gen.net_wm_name) {
+ if (update_window_title(x_window_title(ev->window))) {
+ printf("Title changed: %s\n", gen.current_title);
+ push_event();
+ }
+ }
+}
+
+static void
+set_idle_alarm(XSyncAlarm *alarm, XSyncTestType test, XSyncValue value)
+{
+ XSyncAlarmAttributes attr;
+ attr.trigger.counter = gen.idle_counter;
+ attr.trigger.test_type = test;
+ attr.trigger.wait_value = value;
+ XSyncIntToValue(&attr.delta, 0);
+
+ long flags = XSyncCACounter | XSyncCATestType | XSyncCAValue | XSyncCADelta;
+ if (*alarm)
+ XSyncChangeAlarm(gen.dpy, *alarm, flags, &attr);
+ else
+ *alarm = XSyncCreateAlarm(gen.dpy, flags, &attr);
+}
+
+static void
+on_x_alarm_notify(XSyncAlarmNotifyEvent *ev)
+{
+ if (ev->alarm == gen.idle_alarm_inactive) {
+ printf("User is inactive\n");
+ gen.current_idle = true;
+ push_event();
+
+ XSyncValue one, minus_one;
+ XSyncIntToValue(&one, 1);
+
+ Bool overflow;
+ XSyncValueSubtract(&minus_one, ev->counter_value, one, &overflow);
+
+ // Set an alarm for IDLETIME <= current_idletime - 1
+ set_idle_alarm(&gen.idle_alarm_active,
+ XSyncNegativeComparison, minus_one);
+ } else if (ev->alarm == gen.idle_alarm_active) {
+ printf("User is active\n");
+ gen.current_idle = false;
+ push_event();
+
+ set_idle_alarm(&gen.idle_alarm_inactive,
+ XSyncPositiveComparison, gen.idle_timeout);
+ }
+}
+
+static gboolean
+on_x_ready(G_GNUC_UNUSED gpointer user_data)
+{
+ XEvent ev;
+ while (XPending(gen.dpy)) {
+ if (XNextEvent(gen.dpy, &ev))
+ exit_fatal("XNextEvent returned non-zero");
+ else if (ev.type == PropertyNotify)
+ on_x_property_notify(&ev.xproperty);
+ else if (ev.type == gen.xsync_base_event_code + XSyncAlarmNotify)
+ on_x_alarm_notify((XSyncAlarmNotifyEvent *) &ev);
+ }
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+generate_events(void)
+{
+ GIOChannel *channel = g_io_channel_unix_new(ConnectionNumber(gen.dpy));
+ GSource *watch = g_io_create_watch(channel, G_IO_IN);
+ g_source_set_callback(watch, on_x_ready, NULL, NULL);
+
+ GMainLoop *loop =
+ g_main_loop_new(g_main_context_get_thread_default(), false);
+ g_source_attach(watch, g_main_loop_get_context(loop));
+ g_main_loop_run(loop);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+int
+main(int argc, char *argv[])
+{
+ gboolean show_version = false;
+ const GOptionEntry options[] = {
+ {"version", 'V', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE,
+ &show_version, "output version information and exit", NULL},
+ {},
+ };
+
+ GError *error = NULL;
+ if (!gtk_init_with_args(&argc, &argv, " - activity tracker",
+ options, NULL, &error))
+ exit_fatal("%s", error->message);
+ if (show_version) {
+ printf(PROJECT_NAME " " PROJECT_VERSION "\n");
+ return 0;
+ }
+
+ g.queue = g_async_queue_new_full((GDestroyNotify) event_free);
+
+ if (!setlocale(LC_CTYPE, ""))
+ exit_fatal("cannot set locale");
+ if (!XSupportsLocale())
+ exit_fatal("locale not supported by Xlib");
+
+ XInitThreads();
+ if (!(gen.dpy = XOpenDisplay(NULL)))
+ exit_fatal("cannot open display");
+
+ gen.net_active_window = XInternAtom(gen.dpy, "_NET_ACTIVE_WINDOW", true);
+ gen.net_wm_name = XInternAtom(gen.dpy, "_NET_WM_NAME", true);
+
+ // TODO: it is possible to employ a fallback mechanism via XScreenSaver
+ // by polling the XScreenSaverInfo::idle field, see
+ // https://www.x.org/releases/X11R7.5/doc/man/man3/Xss.3.html
+
+ int dummy;
+ if (!XSyncQueryExtension(gen.dpy, &gen.xsync_base_event_code, &dummy)
+ || !XSyncInitialize(gen.dpy, &dummy, &dummy))
+ exit_fatal("cannot initialize XSync");
+
+ // The idle counter is not guaranteed to exist, only SERVERTIME is
+ if (!(gen.idle_counter = get_counter("IDLETIME")))
+ exit_fatal("idle counter is missing");
+
+ Window root = DefaultRootWindow(gen.dpy);
+ XSelectInput(gen.dpy, root, PropertyChangeMask);
+ XSync(gen.dpy, False);
+ // TODO: what is the interaction with GTK+ here?
+ g_default_x_error_handler = XSetErrorHandler(on_x_error);
+
+ GKeyFile *kf = g_key_file_new();
+ gchar *subpath = g_build_filename(PROJECT_NAME, PROJECT_NAME ".conf", NULL);
+ GStrv dirs = get_xdg_config_dirs();
+
+ int timeout = 600;
+ if (g_key_file_load_from_dirs(kf,
+ subpath, (const gchar **) dirs, NULL, 0, NULL)) {
+ guint64 n = g_key_file_get_uint64(kf, "Settings", "idle_timeout", NULL);
+ if (n > 0 && n <= G_MAXINT / 1000)
+ timeout = n;
+ }
+
+ g_strfreev(dirs);
+ g_free(subpath);
+ g_key_file_free(kf);
+
+ XSyncIntToValue(&gen.idle_timeout, timeout * 1000);
+ update_current_window();
+ set_idle_alarm(&gen.idle_alarm_inactive,
+ XSyncPositiveComparison, gen.idle_timeout);
+
+ gchar *data_path =
+ g_build_filename(g_get_user_data_dir(), PROJECT_NAME, NULL);
+ g_mkdir_with_parents(data_path, 0755);
+
+ // TODO: try exclusivity/invocation either via DBus directly,
+ // or via GApplication or GtkApplication:
+ // - GtkApplication calls Gtk.init automatically during "startup" signal,
+ // Gtk.init doesn't get command line args
+ // - "inhibiting" makes no sense, it can't be used for mere delays
+ // - actually, the "query-end" signal
+ // - should check whether it tries to exit cleanly
+ // - what is the session manager, do I have it?
+ // - "register-session" looks useful
+ // - GTK+ keeps the application running as long as it has windows,
+ // though I want to keep it running forever
+ // - g_application_hold(), perhaps
+ // - so maybe just use GApplication, that will provide more control
+
+ // Bind to a control socket, also ensuring only one instance is running
+ gchar *socket_path = g_build_filename(data_path, "socket", NULL);
+
+ struct flock lock =
+ {
+ .l_type = F_WRLCK,
+ .l_start = 0,
+ .l_whence = SEEK_SET,
+ .l_len = 0,
+ };
+
+ gchar *lock_path = g_strdup_printf("%s.lock", socket_path);
+ int lock_fd = open(lock_path, O_RDWR | O_CREAT, 0644);
+ if (fcntl(lock_fd, F_SETLK, &lock))
+ exit_fatal("failed to acquire lock: %s", strerror(errno));
+ unlink(socket_path);
+
+ int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (socket_fd < 0)
+ exit_fatal("%s: %s", socket_path, strerror(errno));
+
+ struct sockaddr_un sun;
+ sun.sun_family = AF_UNIX;
+ strncpy(sun.sun_path, socket_path, sizeof sun.sun_path);
+ if (bind(socket_fd, (struct sockaddr *) &sun, sizeof sun))
+ exit_fatal("%s: %s", socket_path, strerror(errno));
+ if (listen(socket_fd, 10))
+ exit_fatal("%s: %s", socket_path, strerror(errno));
+
+ sqlite3 *db = NULL;
+ gchar *db_path = g_build_filename(data_path, "db.sqlite", NULL);
+ int rc = sqlite3_open(db_path, &db);
+ if (rc != SQLITE_OK)
+ exit_fatal("%s: %s", db_path, sqlite3_errmsg(db));
+
+ // This shouldn't normally happen but external applications may decide
+ // to read things out, and mess with us. When that takes too long, we may
+ // a/ wait for it to finish, b/ start with a whole-database lock or, even
+ // more simply, c/ crash on the BUSY error.
+ sqlite3_busy_timeout(db, 1000);
+
+ char *errmsg = NULL;
+ if ((rc = sqlite3_exec(db, "BEGIN", NULL, NULL, &errmsg)))
+ exit_fatal("%s: %s", db_path, errmsg);
+
+ sqlite3_stmt *stmt = NULL;
+ if ((rc = sqlite3_prepare_v2(db, "PRAGMA user_version", -1, &stmt, NULL)))
+ exit_fatal("%s: %s", db_path, sqlite3_errmsg(db));
+ if ((rc = sqlite3_step(stmt)) != SQLITE_ROW ||
+ sqlite3_data_count(stmt) != 1)
+ exit_fatal("%s: %s", db_path, "cannot retrieve user version");
+
+ int user_version = sqlite3_column_int(stmt, 0);
+ if (user_version == 0) {
+ if ((rc = sqlite3_exec(db, "CREATE TABLE events ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "timestamp INTEGER, "
+ "title TEXT, "
+ "idle BOOLEAN)", NULL, NULL, &errmsg)))
+ exit_fatal("%s: %s", db_path, errmsg);
+ if ((rc = sqlite3_exec(db, "PRAGMA user_version = 1", NULL, NULL,
+ &errmsg)))
+ exit_fatal("%s: %s", db_path, errmsg);
+ } else if (user_version != 1) {
+ exit_fatal("%s: unsupported DB version: %d", db_path, user_version);
+ }
+ if ((rc = sqlite3_exec(db, "COMMIT", NULL, NULL, &errmsg)))
+ exit_fatal("%s: %s", db_path, errmsg);
+
+ g_free(db_path);
+ g_free(lock_path);
+ g_free(socket_path);
+ g_free(data_path);
+
+ GThread *generator =
+ g_thread_new("generator", (GThreadFunc) generate_events, NULL);
+
+ // TODO: somehow read events from the async queue
+ // TODO: how in the name of fuck would our custom source wake up a sleeping
+ // main loop? There is g_main_context_wakeup() but...
+ // - GWakeUp is internal, apparently
+
+ // TODO: listen for connections on the control socket
+
+ WdmtgWindow *window = wdmtg_window_new_with_db(db);
+
+ gtk_main();
+ g_thread_join(generator);
+
+ free(gen.current_title);
+ XCloseDisplay(gen.dpy);
+ return 0;
+}
diff --git a/wdmtg.vala b/wdmtg.vala
deleted file mode 100644
index c40ddd1..0000000
--- a/wdmtg.vala
+++ /dev/null
@@ -1,406 +0,0 @@
-//
-// wdmtg.vala: activity tracker
-//
-// Copyright (c) 2016 - 2020, Přemysl Eric Janouch
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// vim: set sw=2 ts=2 sts=2 et tw=80:
-// modules: x11 xext config
-// vapidirs: . ../build build
-
-namespace Wdmtg {
-
-// --- Utilities ---------------------------------------------------------------
-
- void exit_fatal (string format, ...) {
- stderr.vprintf ("fatal: " + format + "\n", va_list ());
- Process.exit (1);
- }
-
- string[] get_xdg_config_dirs () {
- string[] paths = { Environment.get_user_config_dir () };
- foreach (var system_path in Environment.get_system_config_dirs ())
- paths += system_path;
- return paths;
- }
-
-// --- Globals -----------------------------------------------------------------
-
- X.Display dpy; ///< X display handle
- int sync_base; ///< Sync extension base
-
- X.ID idle_counter; ///< XSync IDLETIME counter
- X.Sync.Value idle_timeout; ///< User idle timeout
-
- X.ID idle_alarm_inactive; ///< User is inactive
- X.ID idle_alarm_active; ///< User is active
-
- X.Atom net_active_window; ///< _NET_ACTIVE_WINDOW atom
- X.Atom net_wm_name; ///< _NET_WM_NAME atom
-
- string? current_title; ///< Current window title
- X.Window current_window; ///< Current window
- bool current_idle = false; ///< Current idle status
-
- struct Event {
- public int64 timestamp; ///< When the event happened
- public string? title; ///< Current title at the time
- public bool idle; ///< Whether the user is idle
- }
-
- AsyncQueue queue; ///< Async queue
-
-// --- X helpers ---------------------------------------------------------------
-
- X.ID get_counter (string name) {
- int n_counters = 0;
- var counters = X.Sync.list_system_counters (dpy, out n_counters);
- X.ID counter = X.None;
- while (n_counters-- > 0) {
- if (counters[n_counters].name == name)
- counter = counters[n_counters].counter;
- }
- X.Sync.free_system_counter_list (counters);
- return counter;
- }
-
- string? x_text_property_to_utf8 (ref X.TextProperty prop) {
- X.Atom utf8_string = dpy.intern_atom ("UTF8_STRING", true);
- if (prop.encoding == utf8_string)
- return (string) prop.value;
-
- int n = 0;
- uint8 **list = null;
- if (X.mb_text_property_to_text_list (dpy, ref prop, out list, out n)
- >= X.Success && n > 0 && null != list[0]) {
- var result = ((string) list[0]).locale_to_utf8 (-1, null, null);
- X.free_string_list (list);
- return result;
- }
- return null;
- }
-
- string? x_text_property (X.Window window, X.Atom atom) {
- X.TextProperty name;
- X.get_text_property (dpy, window, out name, atom);
- if (null == name.@value)
- return null;
-
- string? result = x_text_property_to_utf8 (ref name);
- X.free (name.@value);
- return result;
- }
-
-// --- X error handling --------------------------------------------------------
-
- X.ErrorHandler default_x_error_handler;
-
- int on_x_error (X.Display dpy, X.ErrorEvent *ee) {
- // This just is going to happen since those windows aren't ours
- if (ee.error_code == X.ErrorCode.BAD_WINDOW)
- return 0;
- return default_x_error_handler (dpy, ee);
- }
-
-// --- Application -------------------------------------------------------------
-
- string x_window_title (X.Window window) {
- string? title;
- if (null == (title = x_text_property (window, net_wm_name))
- && null == (title = x_text_property (window, X.XA_WM_NAME)))
- title = "broken";
- return title;
- }
-
- bool update_window_title (string? new_title) {
- bool changed = (null == current_title) != (null == new_title)
- || current_title != new_title;
- current_title = new_title;
- return changed;
- }
-
- void push_event () {
- queue.push(Event () {
- timestamp = get_real_time (),
- title = current_title,
- idle = current_idle
- });
- }
-
- void update_current_window () {
- var root = dpy.default_root_window ();
- X.Atom dummy_type; int dummy_format; ulong nitems, dummy_bytes;
- void *p = null;
- if (dpy.get_window_property (root, net_active_window,
- 0, 1, false, X.XA_WINDOW, out dummy_type, out dummy_format,
- out nitems, out dummy_bytes, out p) != X.Success)
- return;
-
- string? new_title = null;
- if (0 != nitems) {
- X.Window active_window = *(X.Window *) p;
- X.free (p);
-
- if (current_window != active_window && X.None != current_window)
- dpy.select_input (current_window, 0);
- dpy.select_input (active_window, X.EventMask.PropertyChangeMask);
- new_title = x_window_title (active_window);
- current_window = active_window;
- }
- if (update_window_title (new_title)) {
- stdout.printf ("Window changed: %s\n",
- null != current_title ? current_title : "(none)");
- push_event ();
- }
- }
-
- void on_x_property_notify (X.PropertyEvent *xproperty) {
- // This is from the EWMH specification, set by the window manager
- if (xproperty.atom == net_active_window)
- update_current_window ();
- else if (xproperty.window == current_window
- && xproperty.atom == net_wm_name) {
- if (update_window_title (x_window_title (current_window))) {
- stdout.printf ("Title changed: %s\n", current_title);
- push_event ();
- }
- }
- }
-
- void set_idle_alarm
- (ref X.ID alarm, X.Sync.TestType test, X.Sync.Value @value) {
- X.Sync.AlarmAttributes attr = {};
- attr.trigger.counter = idle_counter;
- attr.trigger.test_type = test;
- attr.trigger.wait_value = @value;
- X.Sync.int_to_value (out attr.delta, 0);
-
- X.Sync.CA flags = X.Sync.CA.Counter | X.Sync.CA.TestType
- | X.Sync.CA.Value | X.Sync.CA.Delta;
- if (X.None != alarm)
- X.Sync.change_alarm (dpy, alarm, flags, ref attr);
- else
- alarm = X.Sync.create_alarm (dpy, flags, ref attr);
- }
-
- void on_x_alarm_notify (X.Sync.AlarmNotifyEvent *xalarm) {
- if (xalarm.alarm == idle_alarm_inactive) {
- stdout.printf ("User is inactive\n");
- current_idle = true;
- push_event ();
-
- X.Sync.Value one, minus_one;
- X.Sync.int_to_value (out one, 1);
-
- int overflow;
- X.Sync.value_subtract
- (out minus_one, xalarm.counter_value, one, out overflow);
-
- // Set an alarm for IDLETIME <= current_idletime - 1
- set_idle_alarm (ref idle_alarm_active,
- X.Sync.TestType.NegativeComparison, minus_one);
- } else if (xalarm.alarm == idle_alarm_inactive) {
- stdout.printf ("User is active\n");
- current_idle = false;
- push_event ();
-
- set_idle_alarm (ref idle_alarm_inactive,
- X.Sync.TestType.PositiveComparison, idle_timeout);
- }
- }
-
- void generate_events () {
- var channel = new IOChannel.unix_new (dpy.connection_number ());
- var watch = channel.create_watch (IOCondition.IN);
- watch.set_callback (() => {
- X.Event ev = {0};
- while (0 != dpy.pending ()) {
- if (0 != dpy.next_event (ref ev)) {
- exit_fatal ("XNextEvent returned non-zero");
- } else if (ev.type == X.EventType.PropertyNotify) {
- on_x_property_notify (&ev.xproperty);
- } else if (ev.type == sync_base + X.Sync.EventType.AlarmNotify) {
- on_x_alarm_notify ((X.Sync.AlarmNotifyEvent *) (&ev));
- }
- }
- return true;
- });
-
- var loop = new MainLoop (MainContext.get_thread_default ());
- watch.attach (loop.get_context ());
- loop.run ();
- }
-
- bool show_version;
- const OptionEntry[] options = {
- { "version", 'V', OptionFlags.IN_MAIN, OptionArg.NONE, ref show_version,
- "output version information and exit" },
- { null }
- };
-
- public int main (string[] args) {
- if (null == Intl.setlocale (GLib.LocaleCategory.CTYPE))
- exit_fatal ("cannot set locale");
- if (0 == X.supports_locale ())
- exit_fatal ("locale not supported by Xlib");
-
- try {
- Gtk.init_with_args (ref args, " - activity tracker", options, null);
- } catch (OptionError e) {
- exit_fatal ("option parsing failed: %s", e.message);
- } catch (Error e) {
- exit_fatal ("%s", e.message);
- }
- if (show_version) {
- stdout.printf (Config.PROJECT_NAME + " " + Config.PROJECT_VERSION + "\n");
- return 0;
- }
-
- queue = new AsyncQueue ();
-
- X.init_threads ();
- if (null == (dpy = new X.Display ()))
- exit_fatal ("cannot open display");
-
- net_active_window = dpy.intern_atom ("_NET_ACTIVE_WINDOW", true);
- net_wm_name = dpy.intern_atom ("_NET_WM_NAME", true);
-
- // TODO: it is possible to employ a fallback mechanism via XScreenSaver
- // by polling the XScreenSaverInfo::idle field, see
- // https://www.x.org/releases/X11R7.5/doc/man/man3/Xss.3.html
-
- int dummy;
- if (0 == X.Sync.query_extension (dpy, out sync_base, out dummy)
- || 0 == X.Sync.initialize (dpy, out dummy, out dummy))
- exit_fatal ("cannot initialize XSync");
-
- // The idle counter is not guaranteed to exist, only SERVERTIME is
- if (X.None == (idle_counter = get_counter ("IDLETIME")))
- exit_fatal ("idle counter is missing");
-
- var root = dpy.default_root_window ();
- dpy.select_input (root, X.EventMask.PropertyChangeMask);
- X.sync (dpy, false);
- default_x_error_handler = X.set_error_handler (on_x_error);
-
- int timeout = 600; // 10 minutes by default
- try {
- var kf = new KeyFile ();
- kf.load_from_dirs (Config.PROJECT_NAME + Path.DIR_SEPARATOR_S
- + Config.PROJECT_NAME + ".conf", get_xdg_config_dirs (), null, 0);
-
- var n = kf.get_uint64 ("Settings", "idle_timeout");
- if (0 != n && n <= int.MAX / 1000)
- timeout = (int) n;
- } catch (Error e) {
- // Ignore errors this far, keeping the defaults
- }
-
- X.Sync.int_to_value (out idle_timeout, timeout * 1000);
- update_current_window ();
- set_idle_alarm (ref idle_alarm_inactive,
- X.Sync.TestType.PositiveComparison, idle_timeout);
-
- var data_path = Path.build_filename (Environment.get_user_data_dir (),
- Config.PROJECT_NAME);
- DirUtils.create_with_parents (data_path, 0755);
-
- // TODO: try exclusivity/invocation either via DBus directly,
- // or via GApplication or GtkApplication:
- // - GtkApplication calls Gtk.init automatically during "startup" signal,
- // Gtk.init doesn't get command line args
- // - "inhibiting" makes no sense, it can't be used for mere delays
- // - actually, the "query-end" signal
- // - should check whether it tries to exit cleanly
- // - what is the session manager, do I have it?
- // - "register-session" looks useful
- // - GTK+ keeps the application running as long as it has windows,
- // though I want to keep it running forever
- // - g_application_hold(), perhaps
- // - so maybe just use GApplication, that will provide more control
-
- // Bind to a control socket, also ensuring only one instance is running
- var socket_path = Path.build_filename (data_path, "socket");
-
- Posix.Flock fl = Posix.Flock () {
- l_type = Posix.F_WRLCK,
- l_start = 0,
- l_whence = Posix.SEEK_SET,
- l_len = 0
- };
-
- var lk = FileStream.open (socket_path + ".lock", "w");
- if (Posix.fcntl (lk.fileno (), Posix.F_SETLK, &fl) < 0)
- exit_fatal("failed to acquire lock: %s", Posix.errno.to_string ());
- FileUtils.unlink (socket_path);
-
- Socket socket;
- try {
- socket = new Socket (SocketFamily.UNIX, SocketType.STREAM,
- SocketProtocol.DEFAULT);
- socket.bind (new UnixSocketAddress (socket_path), true /* allow_reuse */);
- socket.listen ();
- } catch (Error e) {
- exit_fatal ("%s: %s", socket_path, e.message);
- }
-
- Sqlite.Database db;
- var db_path = Path.build_filename (data_path, "db.sqlite");
- int rc = Sqlite.Database.open (db_path, out db);
- if (rc != Sqlite.OK)
- exit_fatal ("%s: %s", db_path, db.errmsg ());
-
- // This shouldn't normally happen but external applications may decide
- // to read things out, and mess with us. When that takes too long, we may
- // a/ wait for it to finish, b/ start with a whole-database lock or, even
- // more simply, c/ crash on the BUSY error.
- db.busy_timeout (1000);
-
- string errmsg;
- if ((rc = db.exec ("BEGIN", null, out errmsg)) != Sqlite.OK)
- exit_fatal ("%s: %s", db_path, errmsg);
-
- Sqlite.Statement stmt;
- if ((rc = db.prepare_v2 ("PRAGMA user_version", -1, out stmt, null))
- != Sqlite.OK)
- exit_fatal ("%s: %s", db_path, db.errmsg ());
- if ((rc = stmt.step ()) != Sqlite.ROW || stmt.data_count () != 1)
- exit_fatal ("%s: %s", db_path, "cannot retrieve user version");
-
- var user_version = stmt.column_int (0);
- if (user_version == 0) {
- if ((rc = db.exec ("""CREATE TABLE events (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- timestamp INTEGER,
- title TEXT,
- idle BOOLEAN
- )""", null, out errmsg)) != Sqlite.OK)
- exit_fatal ("%s: %s", db_path, errmsg);
-
- if ((rc = db.exec ("PRAGMA user_version = 1", null, out errmsg))
- != Sqlite.OK)
- exit_fatal ("%s: %s", db_path, errmsg);
- } else if (user_version != 1) {
- exit_fatal ("%s: unsupported DB version: %d", db_path, user_version);
- }
- if ((rc = db.exec ("COMMIT", null, out errmsg)) != Sqlite.OK)
- exit_fatal ("%s: %s", db_path, errmsg);
-
- var generator = new Thread ("generator", generate_events);
- // TODO: somehow read events from the async queue
- // TODO: listen for connections on the control socket
- Gtk.main ();
- generator.join ();
- return 0;
- }
-}
diff --git a/xext.deps b/xext.deps
deleted file mode 100644
index e181da0..0000000
--- a/xext.deps
+++ /dev/null
@@ -1 +0,0 @@
-x11
diff --git a/xext.vapi b/xext.vapi
index 69019b5..d5e2b49 100644
--- a/xext.vapi
+++ b/xext.vapi
@@ -1,252 +1 @@
-//
-// xext.vapi: various extensions to the x11 vapi
-//
-// Copyright (c) 2016 - 2020, Přemysl Eric Janouch
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// vim: set sw=2 ts=2 sts=2 et tw=80:
-// modules: x11
-
-namespace X {
- [CCode (cname = "XErrorHandler", has_target = false)]
- public delegate int ErrorHandler (Display dpy, ErrorEvent *ee);
- [CCode (cname = "XSetErrorHandler")]
- public ErrorHandler set_error_handler (ErrorHandler handler);
-
- // XXX: can we extend the Display class so that the argument goes away?
- [CCode (cname = "XSync")]
- public int sync (Display dpy, bool discard);
- [CCode (cname = "XSupportsLocale")]
- public int supports_locale ();
-
- [CCode (cname = "XTextProperty", has_type_id = false)]
- public struct TextProperty {
- // There is always a null byte at the end but it may also appear earlier
- // depending on the other fields, so this is a bit misleading
- [CCode (array_null_terminated = true)]
- // Vala tries to g_free0() owned arrays, you still need to call XFree()
- public unowned uint8[]? @value;
- public Atom encoding;
- public int format;
- public ulong nitems;
- }
- [CCode (cname = "XGetTextProperty")]
- public int get_text_property (Display dpy,
- Window window, out TextProperty text_prop_return, Atom property);
-
- [CCode (cname = "XmbTextPropertyToTextList")]
- public int mb_text_property_to_text_list (Display dpy,
- ref TextProperty text_prop,
- [CCode (type = "char ***")] out uint8 **list_return, out int count_return);
- [CCode (cname = "XFreeStringList")]
- public void free_string_list ([CCode (type = "char **")] uint8** list);
-}
-
-namespace X {
- [CCode (cprefix = "", cheader_filename = "X11/extensions/sync.h")]
- namespace Sync {
- [CCode (cprefix = "XSync", cname = "int", has_type_id = false)]
- public enum EventType {
- CounterNotify,
- AlarmNotify
- }
-
- [CCode (cprefix = "", cname = "int", has_type_id = false)]
- public enum ErrorCode {
- [CCode (cname = "XSyncBadCounter")]
- BAD_COUNTER,
- [CCode (cname = "XSyncBadAlarm")]
- BAD_ALARM,
- [CCode (cname = "XSyncBadFence")]
- BAD_FENCE
- }
-
- [CCode (cprefix = "XSyncCA", cname = "int")]
- [Flags]
- public enum CA {
- Counter,
- ValueType,
- Value,
- TestType,
- Delta,
- Events
- }
-
- [CCode (cname = "XSyncValueType", cprefix = "XSync")]
- public enum ValueType {
- Absolute,
- Relative
- }
- [CCode (cname = "XSyncTestType", cprefix = "XSync")]
- public enum TestType {
- PositiveTransition,
- NegativeTransition,
- PositiveComparison,
- NegativeComparison
- }
- [CCode (cname = "XSyncAlarmState", cprefix = "XSyncAlarm")]
- public enum AlarmState {
- Active,
- Inactive,
- Destroyed
- }
-
- [CCode (cname = "XSyncValue", has_type_id = false)]
- [SimpleType]
- public struct Value {
- public int hi;
- public uint lo;
- }
-
- [CCode (cname = "XSyncIntToValue")]
- public void int_to_value (out Value value, int v);
- [CCode (cname = "XSyncIntsToValue")]
- public void ints_to_value (out Value value, uint l, int h);
-
- [CCode (cname = "XSyncValueGreaterThan")]
- public int value_greater_than (Value a, Value b);
- [CCode (cname = "XSyncValueLessThan")]
- public int value_less_than (Value a, Value b);
- [CCode (cname = "XSyncValueGreaterOrEqual")]
- public int value_greater_or_equal (Value a, Value b);
- [CCode (cname = "XSyncValueLessOrEqual")]
- public int value_less_or_equal (Value a, Value b);
- [CCode (cname = "XSyncValueEqual")]
- public int value_equal (Value a, Value b);
-
- [CCode (cname = "XSyncValueIsNegative")]
- public int value_is_negative (Value a, Value b);
- [CCode (cname = "XSyncValueIsZero")]
- public int value_is_zero (Value a, Value b);
- [CCode (cname = "XSyncValueIsPositive")]
- public int value_is_positive (Value a, Value b);
-
- [CCode (cname = "XSyncValueLow32")]
- public uint value_low32 (Value value);
- [CCode (cname = "XSyncValueHigh32")]
- public int value_high32 (Value value);
-
- [CCode (cname = "XSyncValueAdd")]
- public void value_add
- (out Value result, Value a, Value b, out int poverflow);
- [CCode (cname = "XSyncValueSubtract")]
- public void value_subtract
- (out Value result, Value a, Value b, out int poverflow);
-
- [CCode (cname = "XSyncMaxValue")]
- public void max_value (out Value pv);
- [CCode (cname = "XSyncMinValue")]
- public void min_value (out Value pv);
-
- [CCode (cname = "XSyncSystemCounter", has_type_id = false)]
- public struct SystemCounter {
- public string name;
- public X.ID counter;
- public Value resolution;
- }
- [CCode (cname = "XSyncTrigger", has_type_id = false)]
- public struct Trigger {
- public X.ID counter;
- public ValueType value_type;
- public Value wait_value;
- public TestType test_type;
- }
- [CCode (cname = "XSyncWaitCondition", has_type_id = false)]
- public struct WaitCondition {
- public Trigger trigger;
- public Value event_threshold;
- }
- [CCode (cname = "XSyncAlarmAttributes", has_type_id = false)]
- public struct AlarmAttributes {
- public Trigger trigger;
- public Value delta;
- public int events;
- public AlarmState state;
- }
-
- [CCode (cname = "XSyncCounterNotifyEvent", has_type_id = false)]
- public struct CounterNotifyEvent {
- // TODO: other fields
- public X.ID counter;
- public Value wait_value;
- public Value counter_value;
- }
- [CCode (cname = "XSyncAlarmNotifyEvent", has_type_id = false)]
- public struct AlarmNotifyEvent {
- // TODO: other fields
- public X.ID alarm;
- public Value counter_value;
- public Value alarm_value;
- public AlarmState state;
- }
-
- [CCode (cname = "XSyncQueryExtension")]
- public X.Status query_extension (X.Display dpy,
- out int event_base, out int error_base);
- [CCode (cname = "XSyncInitialize")]
- public X.Status initialize (X.Display dpy,
- out int major_version, out int minor_version);
- [CCode (cname = "XSyncListSystemCounters")]
- public SystemCounter *list_system_counters (X.Display dpy,
- out int n_counters);
- [CCode (cname = "XSyncFreeSystemCounterList")]
- public void free_system_counter_list (SystemCounter *counters);
-
- [CCode (cname = "XSyncCreateCounter")]
- public X.ID create_counter (X.Display dpy, Value initial_value);
- [CCode (cname = "XSyncSetCounter")]
- public X.Status set_counter (X.Display dpy, X.ID counter, Value value);
- [CCode (cname = "XSyncChangeCounter")]
- public X.Status change_counter (X.Display dpy, X.ID counter, Value value);
- [CCode (cname = "XSyncDestroyCounter")]
- public X.Status destroy_counter (X.Display dpy, X.ID counter);
- [CCode (cname = "XSyncQueryCounter")]
- public X.Status query_counter (X.Display dpy,
- X.ID counter, out Value value);
-
- [CCode (cname = "XSyncAwait")]
- public X.Status await (X.Display dpy,
- WaitCondition *wait_list, int n_conditions);
-
- [CCode (cname = "XSyncCreateAlarm")]
- public X.ID create_alarm (X.Display dpy,
- CA values_mask, ref AlarmAttributes values);
- [CCode (cname = "XSyncDestroyAlarm")]
- public X.Status destroy_alarm (X.Display dpy, X.ID alarm);
- [CCode (cname = "XSyncQueryAlarm")]
- public X.Status query_alarm (X.Display dpy,
- X.ID alarm, out AlarmAttributes values_return);
- [CCode (cname = "XSyncChangeAlarm")]
- public X.Status change_alarm (X.Display dpy,
- X.ID alarm, CA values_mask, ref AlarmAttributes values);
-
- [CCode (cname = "XSyncSetPriority")]
- public X.Status set_priority (X.Display dpy, X.ID alarm, int priority);
- [CCode (cname = "XSyncGetPriority")]
- public X.Status get_priority (X.Display dpy, X.ID alarm, out int priority);
-
- [CCode (cname = "XSyncCreateFence")]
- public X.ID create_fence (X.Display dpy,
- X.Drawable d, int initially_triggered);
- [CCode (cname = "XSyncTriggerFence")]
- public int trigger_fence (X.Display dpy, X.ID fence);
- [CCode (cname = "XSyncResetFence")]
- public int reset_fence (X.Display dpy, X.ID fence);
- [CCode (cname = "XSyncDestroyFence")]
- public int destroy_fence (X.Display dpy, X.ID fence);
- [CCode (cname = "XSyncQueryFence")]
- public int query_fence (X.Display dpy, X.ID fence, out int triggered);
- [CCode (cname = "XSyncAwaitFence")]
- public int await_fence (X.Display dpy, X.ID *fence_list, int n_fences);
- }
-}
+// https://github.com/mesonbuild/meson/issues/1195