Compare commits

...

21 Commits

Author SHA1 Message Date
d46305d7ab CMakeLists.txt: omit end{if,foreach} expressions
Their usefulness was almost negative.
2020-10-29 16:09:31 +01:00
7edd9720cd Bump minimum CMake version to 3.0
A nice, round number.
2020-10-26 23:28:11 +01:00
ab5ca0cf8b Elaborate on avoiding XDG_RUNTIME_DIR 2020-10-26 23:09:23 +01:00
f699b89dad Reorder headers 2020-10-02 02:08:39 +02:00
9244d2b657 Write a start marker to the DB event table 2020-10-02 01:55:46 +02:00
4302fc4baf Use an empty string rather than "broken"
If we fail to retrieve the title, then there is no title,
though this doesn't mean the same as "no window",
for which we have NULL.
2020-10-02 01:50:37 +02:00
764dbaa126 Nullify a NULL concern
sqlite3_bind_text() is documented to bind NULL.
2020-10-02 01:37:08 +02:00
7d4695d8bd Ensure the inactivity alarm is launched on startup
We forgot to flush.
2020-10-02 01:32:19 +02:00
3482ee66a3 Watch changes of WM_CLASS
There may be some interesting information in there.
Sometimes it may be hard to identify applications by their title.
2020-10-02 01:31:46 +02:00
86b0579cb7 Write events to the SQLite database 2020-09-25 07:20:49 +02:00
27a63e3414 Collect events in the main thread 2020-09-25 06:45:27 +02:00
3dd4e69235 Update README.adoc
The last commit failed to update documentation.
2020-09-25 05:36:08 +02:00
6e3f3c950d Convert from Xlib xcb
This will make it easier to convert this project to Go/xgb later,
even though the SYNC extension isn't currently supported there.

So far unresolved: error handling.
2020-09-25 05:26:46 +02:00
93c61425b3 Cleanup 2020-09-23 16:47:03 +02:00
60bfaa1a97 Convert main source file from Vala to C
Bindings are incredible pain, this will be much easier.
2020-09-23 16:00:06 +02:00
0d35950715 Bind to a control socket
So far using a lockfile, which kind of sucks.

We're going to connect to DBus directly, so we'll see
if it can't be used for our purposes in a simple way.
2020-09-22 23:39:58 +02:00
e2d91aae1c Use a GTK+ main loop, generate events in a thread 2020-09-22 23:39:57 +02:00
b8242ff3c2 Open a database for writing, create a table 2020-09-22 14:11:10 +02:00
906b45982c Push events to an asynchronous queue, timestamped 2020-09-22 14:08:19 +02:00
c9795fe01a Add ability to build with Meson
So that vala-language-server can be used, see also
https://github.com/benwaffle/vala-language-server/issues/73

Works with both vim-lsp and Qt Creator, to some extent.
2020-09-17 09:20:39 +02:00
487ea01334 Name change 2020-09-17 09:14:05 +02:00
17 changed files with 1520 additions and 577 deletions

2
.gitignore vendored
View File

@@ -7,3 +7,5 @@
/wdmtg.files
/wdmtg.creator*
/wdmtg.includes
/wdmtg.cflags
/wdmtg.cxxflags

View File

@@ -1,51 +1,49 @@
project (wdmtg C)
cmake_minimum_required (VERSION 2.8.12)
cmake_minimum_required (VERSION 3.0)
project (wdmtg VERSION 0.1.0 LANGUAGES C)
# Vala really sucks at producing good C code
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
set (CMAKE_C_FLAGS_RELEASE
"${CMAKE_C_FLAGS_RELEASE} -Wno-ignored-qualifiers -Wno-incompatible-pointer-types")
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
endif ()
# Options
option (OPTION_NOINSTALL "Only for developers; work without installing" OFF)
# Version
set (project_VERSION "0.1.0")
# Set some variables
if (OPTION_NOINSTALL)
set (project_SHARE_DIR ${PROJECT_SOURCE_DIR}/share)
elseif (WIN32)
set (project_SHARE_DIR ../share)
set (project_INSTALL_SHARE_DIR share)
else (OPTION_NOINSTALL)
else ()
set (project_SHARE_DIR ${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME})
set (project_INSTALL_SHARE_DIR share/${PROJECT_NAME})
endif (OPTION_NOINSTALL)
endif ()
# Gather package information
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
find_package (Vala 0.12 REQUIRED)
find_package (PkgConfig REQUIRED)
pkg_check_modules (dependencies REQUIRED gtk+-3.0 sqlite3 x11 xext xextproto)
pkg_check_modules (dependencies REQUIRED gtk+-3.0 sqlite3 xcb xcb-sync)
# 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 gmodule-2.0 gio-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,24 +54,23 @@ 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 compound-text.c ${project_SOURCES})
target_link_libraries (${PROJECT_NAME} ${dependencies_LIBRARIES})
install (TARGETS ${PROJECT_NAME} DESTINATION bin)
# CPack
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Activity tracker")
set (CPACK_PACKAGE_VENDOR "Přemysl Janouch")
set (CPACK_PACKAGE_CONTACT "Přemysl Janouch <p@janouch.name>")
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")
set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
set (CPACK_PACKAGE_VERSION ${project_VERSION})
set (CPACK_GENERATOR "TGZ;ZIP")
set (CPACK_PACKAGE_FILE_NAME
"${PROJECT_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}-${CPACK_PACKAGE_VERSION}")
"${PROJECT_NAME}-${PROJECT_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}-${PROJECT_VERSION}")
set (CPACK_SOURCE_GENERATOR "TGZ;ZIP")
set (CPACK_SOURCE_IGNORE_FILES "/build;/\\\\.git")
set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${CPACK_PACKAGE_VERSION}")
set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
include (CPack)

View File

@@ -1,4 +1,4 @@
Copyright (c) 2016, Přemysl Janouch <p@janouch.name>
Copyright (c) 2016 - 2020, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

View File

@@ -16,7 +16,7 @@ a package with the latest development version from Archlinux's AUR.
Building and Running
--------------------
Build dependencies: CMake, pkg-config, Vala >= 0.12 +
Runtime dependencies: gtk+-3.0, sqlite3, x11, xextproto, xext
Runtime dependencies: gtk+-3.0, sqlite3, xcb, xcb-sync
$ git clone --recursive https://git.janouch.name/p/wdmtg.git
$ mkdir wdmtg/build
@@ -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

View File

@@ -6,7 +6,7 @@
# VALA_VERSION
#=============================================================================
# Copyright (c) 2011, 2016, Přemysl Janouch <p@janouch.name>
# Copyright (c) 2011, 2016, Přemysl Eric Janouch <p@janouch.name>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted.

View File

@@ -14,7 +14,7 @@
#
#=============================================================================
# Copyright (c) 2011, 2016, Přemysl Janouch <p@janouch.name>
# Copyright (c) 2011, 2016, Přemysl Eric Janouch <p@janouch.name>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted.

633
compound-text.c Normal file
View File

@@ -0,0 +1,633 @@
//
// compound-text.c: partial X11 COMPOUND_TEXT to UCS-4 transcoder
//
// Copyright (c) 2020, Přemysl Eric Janouch <p@janouch.name>
//
// 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 <stdbool.h>
#include <stdlib.h> // malloc, free, NULL, size_t
#include <glib.h> // g_utf8_*, g_ucs4_to_utf8
// None of the full Chinese, Japanese, Korean character sets are supported,
// and will be replaced by a lot of funny question marks
enum compound_text_encoding {
COMPOUND_TEXT_ASCII,
COMPOUND_TEXT_ISO8859_1_GR,
COMPOUND_TEXT_ISO8859_2_GR,
COMPOUND_TEXT_ISO8859_3_GR,
COMPOUND_TEXT_ISO8859_4_GR,
COMPOUND_TEXT_ISO8859_5_GR,
COMPOUND_TEXT_ISO8859_6_GR,
COMPOUND_TEXT_ISO8859_7_GR,
COMPOUND_TEXT_ISO8859_8_GR,
COMPOUND_TEXT_ISO8859_9_GR,
COMPOUND_TEXT_ISO8859_10_GR,
COMPOUND_TEXT_ISO8859_13_GR,
COMPOUND_TEXT_ISO8859_14_GR,
COMPOUND_TEXT_ISO8859_15_GR,
COMPOUND_TEXT_ISO8859_16_GR,
COMPOUND_TEXT_JIS_X0201_GR,
COMPOUND_TEXT_JIS_X0201_GL,
COMPOUND_TEXT_COUNT
};
/*
Generated from glibc charset data, based on the following script:
for enc in ISO-8859-{1,2,3,4,5,6,7,8,9,10,13,14,15,16} JIS_X0201; do
echo "[COMPOUND_TEXT_$enc] ="
zcat /usr/share/i18n/charmaps/$enc.gz | \
perl -nle '$x{hex($2)} = "0x$1" if m|^<U(.*?)> +/x(..) +.*|;
sub tbl { join ", ", map { $x{$_} || "0x0000" } @_ }
END { print tbl(0x20..0x7F); print tbl(0xa0..0xFF); }'
done | fmt -sw70 | sed 's|^0|\t&|; s|^|\t|'
*/
static unsigned short compound_text_tables[COMPOUND_TEXT_COUNT][96] = {
[COMPOUND_TEXT_ASCII] = {
0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F
},
[COMPOUND_TEXT_ISO8859_1_GR] = {
0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
},
[COMPOUND_TEXT_ISO8859_2_GR] = {
0x00A0, 0x0104, 0x02D8, 0x0141, 0x00A4, 0x013D, 0x015A, 0x00A7,
0x00A8, 0x0160, 0x015E, 0x0164, 0x0179, 0x00AD, 0x017D, 0x017B,
0x00B0, 0x0105, 0x02DB, 0x0142, 0x00B4, 0x013E, 0x015B, 0x02C7,
0x00B8, 0x0161, 0x015F, 0x0165, 0x017A, 0x02DD, 0x017E, 0x017C,
0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7,
0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E,
0x0110, 0x0143, 0x0147, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x00D7,
0x0158, 0x016E, 0x00DA, 0x0170, 0x00DC, 0x00DD, 0x0162, 0x00DF,
0x0155, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x013A, 0x0107, 0x00E7,
0x010D, 0x00E9, 0x0119, 0x00EB, 0x011B, 0x00ED, 0x00EE, 0x010F,
0x0111, 0x0144, 0x0148, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x00F7,
0x0159, 0x016F, 0x00FA, 0x0171, 0x00FC, 0x00FD, 0x0163, 0x02D9
},
[COMPOUND_TEXT_ISO8859_3_GR] = {
0x00A0, 0x0126, 0x02D8, 0x00A3, 0x00A4, 0x0000, 0x0124, 0x00A7,
0x00A8, 0x0130, 0x015E, 0x011E, 0x0134, 0x00AD, 0x0000, 0x017B,
0x00B0, 0x0127, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x0125, 0x00B7,
0x00B8, 0x0131, 0x015F, 0x011F, 0x0135, 0x00BD, 0x0000, 0x017C,
0x00C0, 0x00C1, 0x00C2, 0x0000, 0x00C4, 0x010A, 0x0108, 0x00C7,
0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
0x0000, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x0120, 0x00D6, 0x00D7,
0x011C, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x016C, 0x015C, 0x00DF,
0x00E0, 0x00E1, 0x00E2, 0x0000, 0x00E4, 0x010B, 0x0109, 0x00E7,
0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
0x0000, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x0121, 0x00F6, 0x00F7,
0x011D, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x016D, 0x015D, 0x02D9
},
[COMPOUND_TEXT_ISO8859_4_GR] = {
0x00A0, 0x0104, 0x0138, 0x0156, 0x00A4, 0x0128, 0x013B, 0x00A7,
0x00A8, 0x0160, 0x0112, 0x0122, 0x0166, 0x00AD, 0x017D, 0x00AF,
0x00B0, 0x0105, 0x02DB, 0x0157, 0x00B4, 0x0129, 0x013C, 0x02C7,
0x00B8, 0x0161, 0x0113, 0x0123, 0x0167, 0x014A, 0x017E, 0x014B,
0x0100, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x012E,
0x010C, 0x00C9, 0x0118, 0x00CB, 0x0116, 0x00CD, 0x00CE, 0x012A,
0x0110, 0x0145, 0x014C, 0x0136, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
0x00D8, 0x0172, 0x00DA, 0x00DB, 0x00DC, 0x0168, 0x016A, 0x00DF,
0x0101, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x012F,
0x010D, 0x00E9, 0x0119, 0x00EB, 0x0117, 0x00ED, 0x00EE, 0x012B,
0x0111, 0x0146, 0x014D, 0x0137, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
0x00F8, 0x0173, 0x00FA, 0x00FB, 0x00FC, 0x0169, 0x016B, 0x02D9
},
[COMPOUND_TEXT_ISO8859_5_GR] = {
0x00A0, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407,
0x0408, 0x0409, 0x040A, 0x040B, 0x040C, 0x00AD, 0x040E, 0x040F,
0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
0x2116, 0x0451, 0x0452, 0x0453, 0x0454, 0x0455, 0x0456, 0x0457,
0x0458, 0x0459, 0x045A, 0x045B, 0x045C, 0x00A7, 0x045E, 0x045F
},
[COMPOUND_TEXT_ISO8859_6_GR] = {
0x00A0, 0x0000, 0x0000, 0x0000, 0x00A4, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x060C, 0x00AD, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x061B, 0x0000, 0x0000, 0x0000, 0x061F,
0x0000, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627,
0x0628, 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F,
0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637,
0x0638, 0x0639, 0x063A, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0640, 0x0641, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647,
0x0648, 0x0649, 0x064A, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F,
0x0650, 0x0651, 0x0652, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
},
[COMPOUND_TEXT_ISO8859_7_GR] = {
0x00A0, 0x2018, 0x2019, 0x00A3, 0x20AC, 0x20AF, 0x00A6, 0x00A7,
0x00A8, 0x00A9, 0x037A, 0x00AB, 0x00AC, 0x00AD, 0x0000, 0x2015,
0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x0384, 0x0385, 0x0386, 0x00B7,
0x0388, 0x0389, 0x038A, 0x00BB, 0x038C, 0x00BD, 0x038E, 0x038F,
0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397,
0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F,
0x03A0, 0x03A1, 0x0000, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7,
0x03A8, 0x03A9, 0x03AA, 0x03AB, 0x03AC, 0x03AD, 0x03AE, 0x03AF,
0x03B0, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7,
0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF,
0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7,
0x03C8, 0x03C9, 0x03CA, 0x03CB, 0x03CC, 0x03CD, 0x03CE, 0x0000
},
[COMPOUND_TEXT_ISO8859_8_GR] = {
0x00A0, 0x0000, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
0x00A8, 0x00A9, 0x00D7, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
0x00B8, 0x00B9, 0x00F7, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2017,
0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7,
0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF,
0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7,
0x05E8, 0x05E9, 0x05EA, 0x0000, 0x0000, 0x200E, 0x200F, 0x0000
},
[COMPOUND_TEXT_ISO8859_9_GR] = {
0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
0x011E, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0130, 0x015E, 0x00DF,
0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
0x011F, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x0131, 0x015F, 0x00FF
},
[COMPOUND_TEXT_ISO8859_10_GR] = {
0x00A0, 0x0104, 0x0112, 0x0122, 0x012A, 0x0128, 0x0136, 0x00A7,
0x013B, 0x0110, 0x0160, 0x0166, 0x017D, 0x00AD, 0x016A, 0x014A,
0x00B0, 0x0105, 0x0113, 0x0123, 0x012B, 0x0129, 0x0137, 0x00B7,
0x013C, 0x0111, 0x0161, 0x0167, 0x017E, 0x2015, 0x016B, 0x014B,
0x0100, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x012E,
0x010C, 0x00C9, 0x0118, 0x00CB, 0x0116, 0x00CD, 0x00CE, 0x00CF,
0x00D0, 0x0145, 0x014C, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x0168,
0x00D8, 0x0172, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
0x0101, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x012F,
0x010D, 0x00E9, 0x0119, 0x00EB, 0x0117, 0x00ED, 0x00EE, 0x00EF,
0x00F0, 0x0146, 0x014D, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x0169,
0x00F8, 0x0173, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x0138
},
[COMPOUND_TEXT_ISO8859_13_GR] = {
0x00A0, 0x201D, 0x00A2, 0x00A3, 0x00A4, 0x201E, 0x00A6, 0x00A7,
0x00D8, 0x00A9, 0x0156, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00C6,
0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x201C, 0x00B5, 0x00B6, 0x00B7,
0x00F8, 0x00B9, 0x0157, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00E6,
0x0104, 0x012E, 0x0100, 0x0106, 0x00C4, 0x00C5, 0x0118, 0x0112,
0x010C, 0x00C9, 0x0179, 0x0116, 0x0122, 0x0136, 0x012A, 0x013B,
0x0160, 0x0143, 0x0145, 0x00D3, 0x014C, 0x00D5, 0x00D6, 0x00D7,
0x0172, 0x0141, 0x015A, 0x016A, 0x00DC, 0x017B, 0x017D, 0x00DF,
0x0105, 0x012F, 0x0101, 0x0107, 0x00E4, 0x00E5, 0x0119, 0x0113,
0x010D, 0x00E9, 0x017A, 0x0117, 0x0123, 0x0137, 0x012B, 0x013C,
0x0161, 0x0144, 0x0146, 0x00F3, 0x014D, 0x00F5, 0x00F6, 0x00F7,
0x0173, 0x0142, 0x015B, 0x016B, 0x00FC, 0x017C, 0x017E, 0x2019
},
[COMPOUND_TEXT_ISO8859_14_GR] = {
0x00A0, 0x1E02, 0x1E03, 0x00A3, 0x010A, 0x010B, 0x1E0A, 0x00A7,
0x1E80, 0x00A9, 0x1E82, 0x1E0B, 0x1EF2, 0x00AD, 0x00AE, 0x0178,
0x1E1E, 0x1E1F, 0x0120, 0x0121, 0x1E40, 0x1E41, 0x00B6, 0x1E56,
0x1E81, 0x1E57, 0x1E83, 0x1E60, 0x1EF3, 0x1E84, 0x1E85, 0x1E61,
0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
0x0174, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x1E6A,
0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x0176, 0x00DF,
0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
0x0175, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x1E6B,
0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x0177, 0x00FF
},
[COMPOUND_TEXT_ISO8859_15_GR] = {
0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x20AC, 0x00A5, 0x0160, 0x00A7,
0x0161, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x017D, 0x00B5, 0x00B6, 0x00B7,
0x017E, 0x00B9, 0x00BA, 0x00BB, 0x0152, 0x0153, 0x0178, 0x00BF,
0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
},
[COMPOUND_TEXT_ISO8859_16_GR] = {
0x00A0, 0x0104, 0x0105, 0x0141, 0x20AC, 0x201E, 0x0160, 0x00A7,
0x0161, 0x00A9, 0x0218, 0x00AB, 0x0179, 0x00AD, 0x017A, 0x017B,
0x00B0, 0x00B1, 0x010C, 0x0142, 0x017D, 0x201D, 0x00B6, 0x00B7,
0x017E, 0x010D, 0x0219, 0x00BB, 0x0152, 0x0153, 0x0178, 0x017C,
0x00C0, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0106, 0x00C6, 0x00C7,
0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
0x0110, 0x0143, 0x00D2, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x015A,
0x0170, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0118, 0x021A, 0x00DF,
0x00E0, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x0107, 0x00E6, 0x00E7,
0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
0x0111, 0x0144, 0x00F2, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x015B,
0x0171, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x0119, 0x021B, 0x00FF
},
[COMPOUND_TEXT_JIS_X0201_GR] = {
0x0000, 0x3002, 0x300C, 0x300D, 0x3001, 0x30FB, 0x30F2, 0x30A1,
0x30A3, 0x30A5, 0x30A7, 0x30A9, 0x30E3, 0x30E5, 0x30E7, 0x30C3,
0x30FC, 0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA, 0x30AB, 0x30AD,
0x30AF, 0x30B1, 0x30B3, 0x30B5, 0x30B7, 0x30B9, 0x30BB, 0x30BD,
0x30BF, 0x30C1, 0x30C4, 0x30C6, 0x30C8, 0x30CA, 0x30CB, 0x30CC,
0x30CD, 0x30CE, 0x30CF, 0x30D2, 0x30D5, 0x30D8, 0x30DB, 0x30DE,
0x30DF, 0x30E0, 0x30E1, 0x30E2, 0x30E4, 0x30E6, 0x30E8, 0x30E9,
0x30EA, 0x30EB, 0x30EC, 0x30ED, 0x30EF, 0x30F3, 0x309B, 0x309C,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
},
[COMPOUND_TEXT_JIS_X0201_GL] = {
0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
0x0058, 0x0059, 0x005A, 0x005B, 0x00A5, 0x005D, 0x005E, 0x005F,
0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x203E, 0x007F
},
};
struct compound_text_state {
const char *in; // Current input iterator
const char *end; // End of input
int *out; // Current result iterator
int gl_encoding; // Current GL encoding or -N for unknown N-octet
int gr_encoding; // Current GR encoding or -N for unknown N-octet
};
static bool
compound_text_peek(struct compound_text_state *s, unsigned char *c)
{
if (s->in >= s->end)
return false;
*c = *s->in;
return true;
}
static bool
compound_text_read(struct compound_text_state *s, unsigned char *c)
{
if (!compound_text_peek(s, c))
return false;
s->in++;
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static bool
compound_text_skip_I(struct compound_text_state *s, unsigned char *c)
{
if (*c >= 0x21 && *c <= 0x23) {
while (compound_text_peek(s, c) && *c >= 0x20 && *c <= 0x2f)
compound_text_read(s, c);
if (!compound_text_read(s, c))
return false;
}
return true;
}
static bool
compound_text_unknown_1(struct compound_text_state *s, unsigned char c,
int *encoding)
{
if (!compound_text_skip_I(s, &c))
return false;
else if (c >= 0x40 && c <= 0x7e)
*encoding = -1;
else
return false;
return true;
}
static bool
compound_text_unknown_N(struct compound_text_state *s, unsigned char c,
int *encoding)
{
if (!compound_text_skip_I(s, &c))
return false;
else if (c >= 0x40 && c <= 0x5f)
*encoding = -2;
else if (c >= 0x60 && c <= 0x6f)
*encoding = -3;
else if (c >= 0x70 && c <= 0x7e)
return false; // "4 or more"
else
return false;
return true;
}
static bool
compound_text_utf8(struct compound_text_state *s)
{
// The specification isn't entirely clear about termination,
// let's be naïve and careful about what we accept
while (s->in + 3 <= s->end) {
if (s->in[0] == 0x1b && s->in[1] == 0x25 && s->in[2] == 0x40) {
s->in += 3;
return true;
}
gunichar r = g_utf8_get_char_validated(s->in, s->end - s->in);
if (r == (gunichar) -1 || r == (gunichar) -2)
return false;
// Don't allow circumventing the rules with this stupid mode
if (r < 0x20 && r != '\t' && r != '\n')
r = 0xFFFD;
s->in = g_utf8_next_char(s->in);
*s->out++ = r;
}
return false;
}
static bool
compound_text_ESC(struct compound_text_state *s)
{
unsigned char c;
if (!compound_text_read(s, &c)) {
return false;
} else if (c == 0x28 /* GL 94 */) {
if (!compound_text_read(s, &c))
return false;
else if (c == 0x42)
s->gl_encoding = COMPOUND_TEXT_ASCII;
else if (c == 0x4a)
s->gl_encoding = COMPOUND_TEXT_JIS_X0201_GL;
else if (!compound_text_unknown_1(s, c, &s->gl_encoding))
return false;
} else if (c == 0x29 /* GR 94 */) {
if (!compound_text_read(s, &c))
return false;
else if (c == 0x49)
s->gr_encoding = COMPOUND_TEXT_JIS_X0201_GR;
else if (!compound_text_unknown_1(s, c, &s->gr_encoding))
return false;
} else if (c == 0x2d /* GR 96 */) {
if (!compound_text_read(s, &c))
return false;
else if (c == 0x41)
s->gr_encoding = COMPOUND_TEXT_ISO8859_1_GR;
else if (c == 0x42)
s->gr_encoding = COMPOUND_TEXT_ISO8859_2_GR;
else if (c == 0x43)
s->gr_encoding = COMPOUND_TEXT_ISO8859_3_GR;
else if (c == 0x44)
s->gr_encoding = COMPOUND_TEXT_ISO8859_4_GR;
else if (c == 0x46)
s->gr_encoding = COMPOUND_TEXT_ISO8859_7_GR;
else if (c == 0x47)
s->gr_encoding = COMPOUND_TEXT_ISO8859_6_GR;
else if (c == 0x48)
s->gr_encoding = COMPOUND_TEXT_ISO8859_8_GR;
else if (c == 0x4c)
s->gr_encoding = COMPOUND_TEXT_ISO8859_5_GR;
else if (c == 0x4d)
s->gr_encoding = COMPOUND_TEXT_ISO8859_9_GR;
else if (c == 0x56)
s->gr_encoding = COMPOUND_TEXT_ISO8859_10_GR;
else if (c == 0x59)
s->gr_encoding = COMPOUND_TEXT_ISO8859_13_GR;
else if (c == 0x5f)
s->gr_encoding = COMPOUND_TEXT_ISO8859_14_GR;
else if (c == 0x62)
s->gr_encoding = COMPOUND_TEXT_ISO8859_15_GR;
else if (c == 0x66)
s->gr_encoding = COMPOUND_TEXT_ISO8859_16_GR;
else if (!compound_text_unknown_1(s, c, &s->gr_encoding))
return false;
} else if (c == 0x24 /* ^N */) {
if (!compound_text_read(s, &c)) {
return false;
} else if (c == 0x28 /* GL 94^N */) {
if (!compound_text_read(s, &c) ||
!compound_text_unknown_N(s, c, &s->gl_encoding))
return false;
} else if (c == 0x29 /* GR 94^N */) {
if (!compound_text_read(s, &c) ||
!compound_text_unknown_N(s, c, &s->gr_encoding))
return false;
} else {
return false;
}
} else if (c == 0x25 /* Non-Standard Character Set Encodings */) {
if (!compound_text_read(s, &c))
return false;
if (c == 0x47 /* from version 1.1.xf86.1 */)
return compound_text_utf8(s);
if (c != 0x2f || !compound_text_read(s, &c) || c < 0x30 || c > 0x34)
return false;
if (!compound_text_read(s, &c) || !(c & 0x80))
return false;
int skip_h = c - 128;
if (!compound_text_read(s, &c) || !(c & 0x80))
return false;
int skip_l = c - 128;
for (int skip = skip_h << 8 | skip_l; skip--; ) {
if (!compound_text_read(s, &c))
return false;
}
// TODO: this would deserve more obvious handling,
// we're replacing an entire sequence with just one character.
// For that, we'd need to parse the sequence, though.
*s->out++ = 0xFFFD;
} else if (c == 0x23 /* Extensions, starting control sequences */) {
// NOTE: major version = c - 0x20 + 1
if (!compound_text_read(s, &c) || c < 0x20 || c > 0x2f)
return false;
if (!compound_text_read(s, &c))
return false;
else if (c == 0x30)
return false; // not supported: ignoring extensions is OK
else if (c == 0x31)
return false; // not supported: ignoring extensions is not OK
else
return false;
} else if (c >= 0x20 && c <= 0x2f /* extension, Intermediate */) {
return false; // not supported
} else if (c >= 0x30 && c <= 0x7e /* extension, Final */) {
return false; // not supported
}
return true;
}
static bool
compound_text_CSI(struct compound_text_state *s)
{
unsigned char c;
if (!compound_text_read(s, &c)) {
return false;
} else if (c == 0x31) {
if (!compound_text_read(s, &c) || c != 0x5d)
return false;
*s->out++ = 0x202A; // LRE
} else if (c == 0x32) {
if (!compound_text_read(s, &c) || c != 0x5d)
return false;
*s->out++ = 0x202B; // RLE
} else if (c == 0x5d) {
*s->out++ = 0x202C; // PDF
} else if (c >= 0x30 && c <= 0x3f /* extension, P */) {
return false; // not supported
} else if (c >= 0x20 && c <= 0x2f /* extension, Intermediate */) {
return false; // not supported
} else if (c >= 0x40 && c <= 0x7e /* extension, Final */) {
return false; // not supported
} else {
return false;
}
return true;
}
static bool
compound_text_GL(struct compound_text_state *s, unsigned char c)
{
if (s->gl_encoding < 0 ||
!(*s->out = compound_text_tables[s->gl_encoding][c - 0x20]))
*s->out = 0xFFFD;
for (int i = 0; --i > s->gl_encoding; ) {
if (!compound_text_read(s, &c) || c < 0x20 || c >= 0x80)
return false;
}
s->out++;
return true;
}
static bool
compound_text_GR(struct compound_text_state *s, unsigned char c)
{
if (s->gr_encoding < 0 ||
!(*s->out = compound_text_tables[s->gr_encoding][c - 0xa0]))
*s->out = 0xFFFD;
for (int i = 0; --i > s->gr_encoding; ) {
if (!compound_text_read(s, &c) || c < 0xa0)
return false;
}
s->out++;
return true;
}
int *
compound_text_to_ucs4(const char *compound_text, size_t length)
{
// This is a good approximation, as well as the upper bound
int *result = calloc(sizeof *result, length + 1);
struct compound_text_state s = {
.in = compound_text, .end = compound_text + length, .out = result,
.gl_encoding = COMPOUND_TEXT_ASCII,
.gr_encoding = COMPOUND_TEXT_ISO8859_1_GR,
};
unsigned char c;
while (compound_text_read(&s, &c) && c != 0) {
bool ok = true;
if (c == '\t' || c == '\n')
*s.out++ = c;
else if (c == 0x1b)
ok = compound_text_ESC(&s);
else if (c == 0x9b)
ok = compound_text_CSI(&s);
else if ((c & ~0x80) < 0x20 /* C0, C1 */)
*s.out++ = 0xFFFD;
else if (c < 0x80)
ok = compound_text_GL(&s, c);
else
ok = compound_text_GR(&s, c);
if (!ok) {
// TODO: consider returning partial results
free(result);
return NULL;
}
}
*s.out++ = 0;
return result;
}
#if COMPOUND_TEXT_SELFTEST
// Build with -DCOMPOUND_TEXT_SELFTEST `pkg-config --cflags --libs x11 glib-2.0`
#include <X11/Xlib.h>
#include <X11/Xutil.h>
int
main(void)
{
Display *dpy = XOpenDisplay(NULL); GString *s = g_string_new("");
while (1) {
g_string_truncate(s, 0);
for (gsize i = 0; i < 10; i++) {
int c = rand() & 0x10FFFF;
if ((c < 0xD800 || c > 0xDFFF) && // GLib rejects surrogates
(c != 0x9b) && // Xlib inserts a lone CSI (!)
(c >= 0x20)) // not allowed or disruptive
g_string_append_unichar(s, c);
}
XTextProperty prop;
Xutf8TextListToTextProperty(dpy, (char **) &s->str, 1,
XCompoundTextStyle, &prop);
int *ucs4 = NULL; char *x = NULL;
if (!(ucs4 = compound_text_to_ucs4((char *) prop.value, prop.nitems)))
g_printerr("parse error '%s' -> '%s'\n", s->str, prop.value);
else if (!(x = g_ucs4_to_utf8((gunichar *) ucs4, -1, NULL, NULL, NULL)))
g_printerr("total failure: %s\n", prop.value);
free(ucs4); free(x); XFree(prop.value);
}
}
#endif

21
compound-text.h Normal file
View File

@@ -0,0 +1,21 @@
//
// compound-text.h: partial X11 COMPOUND_TEXT to UCS-4 transcoder
//
// Copyright (c) 2020, Přemysl Eric Janouch <p@janouch.name>
//
// 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 <stdlib.h>
// Convert an X11 COMPOUND_TEXT string to a NUL-terminated UCS4 sequence
int *compound_text_to_ucs4(const char *compound_text, size_t length);

3
config.h.in Normal file
View File

@@ -0,0 +1,3 @@
#define PROJECT_NAME "@PROJECT_NAME@"
#define PROJECT_VERSION "@PROJECT_VERSION@"
#define SHARE_DIR "@project_SHARE_DIR@"

View File

@@ -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@";
}

6
config.vapi Normal file
View File

@@ -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;
}

24
gui.vala Normal file
View File

@@ -0,0 +1,24 @@
//
// gui.vala: activity tracker - GUI part
//
// Copyright (c) 2020, Přemysl Eric Janouch <p@janouch.name>
//
// 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;
}
}

31
meson.build Normal file
View File

@@ -0,0 +1,31 @@
project('wdmtg', 'vala', 'c', default_options : ['c_std=gnu99'],
version : '0.1.0')
add_project_arguments(['--vapidir', meson.current_source_dir()],
language: 'vala')
conf = configuration_data()
conf.set('PROJECT_NAME', meson.project_name())
conf.set('PROJECT_VERSION', meson.project_version())
configure_file(
input : 'config.h.in',
output : 'config.h',
configuration : conf,
)
dependencies = [
dependency('gtk+-3.0'),
dependency('gmodule-2.0'),
dependency('gio-2.0'),
dependency('gio-unix-2.0'),
dependency('gee-0.8'),
dependency('sqlite3'),
dependency('xcb'),
dependency('xcb-sync'),
]
gui = static_library('gui', 'gui.vala', 'config.vapi',
install : false,
dependencies : dependencies)
executable('wdmtg', 'wdmtg.c', 'compound-text.c',
install : true,
link_with : [gui],
dependencies : [dependencies])

775
wdmtg.c Normal file
View File

@@ -0,0 +1,775 @@
//
// wdmtg.c: activity tracker
//
// Copyright (c) 2016 - 2020, Přemysl Eric Janouch <p@janouch.name>
//
// 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 <stdbool.h>
#include <stdio.h>
#include <stdarg.h>
#include <locale.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <gtk/gtk.h>
#include <glib.h>
#include <sqlite3.h>
#include <xcb/xcb.h>
#include <xcb/sync.h>
#include "config.h"
#include "gui.h"
#include "compound-text.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);
}
static GString *
latin1_to_utf8(const gchar *latin1, gsize len)
{
GString *s = g_string_new(NULL);
while (len--) {
guchar c = *latin1++;
if (c < 0x80) {
g_string_append_c(s, c);
} else {
g_string_append_c(s, 0xC0 | (c >> 6));
g_string_append_c(s, 0x80 | (c & 0x3F));
}
}
return s;
}
// --- Globals -----------------------------------------------------------------
struct event {
gint64 timestamp; // When the event happened
gchar *title; // Current title at the time
gchar *class; // Current class at the time
gboolean idle; // Whether the user is idle
};
static void
event_free(struct event *self)
{
g_free(self->title);
g_free(self->class);
g_slice_free(struct event, self);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct {
GAsyncQueue *queue; // Async queue of `struct event`
sqlite3 *db; // Event database
sqlite3_stmt *add_event; // Prepared statement: add event
} g;
struct {
xcb_connection_t *X; // X display handle
xcb_screen_t *screen; // X screen information
GThread *thread; // Worker thread
xcb_atom_t atom_net_active_window; // _NET_ACTIVE_WINDOW
xcb_atom_t atom_net_wm_name; // _NET_WM_NAME
xcb_atom_t atom_utf8_string; // UTF8_STRING
xcb_atom_t atom_compound_text; // COMPOUND_TEXT
// Window title tracking
gchar *current_title; // Current window title or NULL
gchar *current_class; // Current window class or NULL
xcb_window_t current_window; // Current window
gboolean current_idle; // Current idle status
// XSync activity tracking
const xcb_query_extension_reply_t *sync; // Sync extension
xcb_sync_counter_t idle_counter; // Sync IDLETIME counter
xcb_sync_int64_t idle_timeout; // Idle timeout
xcb_sync_alarm_t idle_alarm_inactive; // User is inactive
xcb_sync_alarm_t idle_alarm_active; // User is active
} gen;
// --- XCB helpers -------------------------------------------------------------
static xcb_atom_t
intern_atom(const char *atom)
{
xcb_intern_atom_reply_t *iar = xcb_intern_atom_reply(gen.X,
xcb_intern_atom(gen.X, false, strlen(atom), atom), NULL);
xcb_atom_t result = iar ? iar->atom : XCB_NONE;
free(iar);
return result;
}
static xcb_sync_counter_t
find_counter(xcb_sync_list_system_counters_reply_t *slsr, const char *name)
{
// FIXME: https://gitlab.freedesktop.org/xorg/lib/libxcb/-/issues/36
const size_t xcb_sync_systemcounter_t_len = 14;
xcb_sync_systemcounter_iterator_t slsi =
xcb_sync_list_system_counters_counters_iterator(slsr);
while (slsi.rem--) {
xcb_sync_systemcounter_t *counter = slsi.data;
char *counter_name = (char *) counter + xcb_sync_systemcounter_t_len;
if (!strncmp(counter_name, name, counter->name_len) &&
!name[counter->name_len])
return counter->counter;
slsi.data = (void *) counter +
((xcb_sync_systemcounter_t_len + counter->name_len + 3) & ~3);
}
return XCB_NONE;
}
static xcb_sync_counter_t
get_counter(const char *name)
{
xcb_sync_list_system_counters_reply_t *slsr =
xcb_sync_list_system_counters_reply(gen.X,
xcb_sync_list_system_counters(gen.X), NULL);
xcb_sync_counter_t counter = XCB_NONE;
if (slsr) {
counter = find_counter(slsr, name);
free(slsr);
}
return counter;
}
// --- X helpers ---------------------------------------------------------------
static GString *
x_text_property_to_utf8(GString *value, xcb_atom_t encoding)
{
if (encoding == gen.atom_utf8_string)
return value;
if (encoding == XCB_ATOM_STRING) {
// Could use g_convert() but this will certainly never fail
GString *utf8 = latin1_to_utf8(value->str, value->len);
g_string_free(value, true);
return utf8;
}
// COMPOUND_TEXT doesn't deserve support for multiple NUL-separated items
int *ucs4 = NULL;
if (encoding == gen.atom_compound_text &&
(ucs4 = compound_text_to_ucs4((char *) value->str, value->len))) {
g_string_free(value, true);
glong len = 0;
gchar *utf8 = g_ucs4_to_utf8((gunichar *) ucs4, -1, NULL, &len, NULL);
free(ucs4);
// malloc failure or, rather theoretically, an out of range codepoint
if (utf8) {
value = g_string_new_len(utf8, len);
free(utf8);
return value;
}
}
g_string_free(value, true);
return NULL;
}
static GString *
x_text_property(xcb_window_t window, xcb_atom_t property)
{
GString *buffer = g_string_new(NULL);
xcb_atom_t type = XCB_NONE;
uint32_t offset = 0;
xcb_get_property_reply_t *gpr = NULL;
while ((gpr = xcb_get_property_reply(gen.X,
xcb_get_property(gen.X, false /* delete */, window,
property, XCB_GET_PROPERTY_TYPE_ANY, offset, 0x8000), NULL))) {
if (gpr->format != 8 || (type && gpr->type != type)) {
free(gpr);
break;
}
int len = xcb_get_property_value_length(gpr);
g_string_append_len(buffer, xcb_get_property_value(gpr), len);
offset += len >> 2;
type = gpr->type;
bool last = !gpr->bytes_after;
free(gpr);
if (last)
return x_text_property_to_utf8(buffer, type);
}
g_string_free(buffer, true);
return NULL;
}
// --- Async Queue Source ------------------------------------------------------
static gboolean
async_queue_source_prepare(G_GNUC_UNUSED GSource *source,
G_GNUC_UNUSED gint *timeout_)
{
return g_async_queue_length(g.queue) > 0;
}
static gboolean
async_queue_source_dispatch(G_GNUC_UNUSED GSource *source,
GSourceFunc callback, gpointer user_data)
{
// I don't want to call it once per message, prefer batch processing
if (callback)
return callback(user_data);
return G_SOURCE_CONTINUE;
}
static GSource *
async_queue_source_new(void)
{
static GSourceFuncs funcs = {
.prepare = async_queue_source_prepare,
.check = NULL,
.dispatch = async_queue_source_dispatch,
.finalize = NULL,
};
GSource *source = g_source_new(&funcs, sizeof *source);
g_source_set_name(source, "AsyncQueueSource");
return source;
}
// --- Generator ---------------------------------------------------------------
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->class = g_strdup(gen.current_class);
event->idle = gen.current_idle;
g_async_queue_push(g.queue, event);
// This is the best thing GLib exposes (GWakeUp is internal)
g_main_context_wakeup(g_main_context_default());
}
static char *
x_window_title(xcb_window_t window)
{
GString *title;
if (!(title = x_text_property(window, gen.atom_net_wm_name))
&& !(title = x_text_property(window, XCB_ATOM_WM_NAME)))
return g_strdup("");
return g_string_free(title, false);
}
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 char *
x_window_class(xcb_window_t window)
{
GString *title;
if (!(title = x_text_property(window, XCB_ATOM_WM_CLASS)))
return NULL;
// First is an "instance name", followed by a NUL and a "class name".
// Strongly prefer Firefox/Thunderbird over Navigator/Mail.
size_t skip = strlen(title->str);
if (++skip >= title->len)
return g_string_free(title, true);
g_string_erase(title, 0, skip);
return g_string_free(title, false);
}
static bool
update_window_class(char *new_class)
{
bool changed = !gen.current_class != !new_class
|| (new_class && strcmp(gen.current_class, new_class));
free(gen.current_class);
gen.current_class = new_class;
return changed;
}
static void
update_current_window(void)
{
xcb_get_property_reply_t *gpr = xcb_get_property_reply(gen.X,
xcb_get_property(gen.X, false /* delete */, gen.screen->root,
gen.atom_net_active_window, XCB_ATOM_WINDOW, 0, 1), NULL);
if (!gpr)
return;
char *new_title = NULL;
char *new_class = NULL;
if (xcb_get_property_value_length(gpr)) {
xcb_window_t active_window =
*(xcb_window_t *) xcb_get_property_value(gpr);
const uint32_t disable[] = { 0 };
if (gen.current_window != active_window && gen.current_window)
(void) xcb_change_window_attributes(gen.X,
gen.current_window, XCB_CW_EVENT_MASK, disable);
const uint32_t enable[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
(void) xcb_change_window_attributes(gen.X,
active_window, XCB_CW_EVENT_MASK, enable);
new_title = x_window_title(active_window);
new_class = x_window_class(active_window);
gen.current_window = active_window;
}
free(gpr);
// We need to absorb both pointers, so we need a bitwise OR
if (update_window_title(new_title) |
update_window_class(new_class))
push_event();
}
static void
on_x_property_notify(const xcb_property_notify_event_t *ev)
{
// This is from the EWMH specification, set by the window manager
if (ev->atom == gen.atom_net_active_window) {
update_current_window();
} else if (ev->window == gen.current_window &&
ev->atom == gen.atom_net_wm_name) {
if (update_window_title(x_window_title(ev->window)))
push_event();
}
}
static void
set_idle_alarm(xcb_sync_alarm_t *alarm, xcb_sync_testtype_t test,
xcb_sync_int64_t value)
{
// TODO: consider xcb_sync_{change,create}_alarm_aux()
uint32_t values[] = {
gen.idle_counter,
value.hi, value.lo,
test,
0, 0,
};
xcb_sync_ca_t flags = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE |
XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_DELTA;
if (*alarm) {
xcb_sync_change_alarm(gen.X, *alarm, flags, values);
} else {
*alarm = xcb_generate_id(gen.X);
xcb_sync_create_alarm(gen.X, *alarm, flags, values);
}
}
static void
on_x_alarm_notify(const xcb_sync_alarm_notify_event_t *ev)
{
if (ev->alarm == gen.idle_alarm_inactive) {
gen.current_idle = true;
push_event();
xcb_sync_int64_t minus_one = ev->counter_value;
if (!~(--minus_one.lo))
minus_one.hi--;
// Set an alarm for IDLETIME <= current_idletime - 1
set_idle_alarm(&gen.idle_alarm_active,
XCB_SYNC_TESTTYPE_NEGATIVE_COMPARISON, minus_one);
} else if (ev->alarm == gen.idle_alarm_active) {
gen.current_idle = false;
push_event();
set_idle_alarm(&gen.idle_alarm_inactive,
XCB_SYNC_TESTTYPE_POSITIVE_COMPARISON, gen.idle_timeout);
}
}
static void
process_x11_event(xcb_generic_event_t *ev)
{
int event_code = ev->response_type & 0x7f;
if (!event_code) {
xcb_generic_error_t *err = (xcb_generic_error_t *) ev;
// TODO: report the error
} else if (event_code == XCB_PROPERTY_NOTIFY) {
on_x_property_notify((const xcb_property_notify_event_t *) ev);
} else if (event_code == gen.sync->first_event + XCB_SYNC_ALARM_NOTIFY) {
on_x_alarm_notify((const xcb_sync_alarm_notify_event_t *) ev);
}
}
static gboolean
on_x_ready(G_GNUC_UNUSED gpointer user_data)
{
xcb_generic_event_t *event;
while ((event = xcb_poll_for_event(gen.X))) {
process_x11_event(event);
free(event);
}
(void) xcb_flush(gen.X);
// TODO: some form of error handling, this just silently stops working
return xcb_connection_has_error(gen.X) == 0;
}
static void
generator_thread(void)
{
GIOChannel *channel = g_io_channel_unix_new(xcb_get_file_descriptor(gen.X));
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);
}
static void
generator_init(void)
{
int which_screen = -1, xcb_error;
gen.X = xcb_connect(NULL, &which_screen);
if ((xcb_error = xcb_connection_has_error(gen.X)))
exit_fatal("cannot open display (code %d)", xcb_error);
if (!(gen.atom_net_active_window = intern_atom("_NET_ACTIVE_WINDOW")) ||
!(gen.atom_net_wm_name = intern_atom("_NET_WM_NAME")) ||
!(gen.atom_utf8_string = intern_atom("UTF8_STRING")) ||
!(gen.atom_compound_text = intern_atom("COMPOUND_TEXT")))
exit_fatal("unable to resolve atoms");
// 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
gen.sync = xcb_get_extension_data(gen.X, &xcb_sync_id);
if (!gen.sync->present)
exit_fatal("missing Sync extension");
xcb_generic_error_t *err = NULL;
xcb_sync_initialize_cookie_t sic = xcb_sync_initialize(gen.X,
XCB_SYNC_MAJOR_VERSION, XCB_SYNC_MINOR_VERSION);
xcb_sync_initialize_reply_t *sir =
xcb_sync_initialize_reply(gen.X, sic, &err);
if (!sir)
exit_fatal("failed to initialise Sync extension");
free(sir);
// The idle counter is not guaranteed to exist, only SERVERTIME is
if (!(gen.idle_counter = get_counter("IDLETIME")))
exit_fatal("idle counter is missing");
const xcb_setup_t *setup = xcb_get_setup(gen.X);
xcb_screen_iterator_t setup_iter = xcb_setup_roots_iterator(setup);
while (which_screen--)
xcb_screen_next(&setup_iter);
gen.screen = setup_iter.data;
xcb_window_t root = gen.screen->root;
const uint32_t values[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
(void) xcb_change_window_attributes(gen.X, root, XCB_CW_EVENT_MASK, values);
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);
// Write a start marker so that we can reliably detect interruptions
struct event *event = g_slice_new0(struct event);
event->timestamp = -1;
g_async_queue_push(g.queue, event);
update_current_window();
gint64 timeout_ms = timeout * 1000;
gen.idle_timeout.hi = timeout_ms >> 32;
gen.idle_timeout.lo = timeout_ms;
set_idle_alarm(&gen.idle_alarm_inactive,
XCB_SYNC_TESTTYPE_POSITIVE_COMPARISON, gen.idle_timeout);
(void) xcb_flush(gen.X);
// TODO: how are XCB errors handled? What if the last xcb_flush() fails?
}
static void
generator_launch(void)
{
gen.thread =
g_thread_new("generator", (GThreadFunc) generator_thread, NULL);
}
static void
generator_cleanup(void)
{
g_thread_join(gen.thread);
free(gen.current_title);
xcb_disconnect(gen.X);
}
// --- Main --------------------------------------------------------------------
static gboolean
on_queue_incoming(G_GNUC_UNUSED gpointer user_data)
{
int rc = 0;
char *errmsg = NULL;
if ((rc = sqlite3_exec(g.db, "BEGIN", NULL, NULL, &errmsg))) {
g_printerr("DB BEGIN error: %s\n", errmsg);
free(errmsg);
return G_SOURCE_CONTINUE;
}
// TODO: there should ideally be a limit to how many things can end up
// in a transaction at once (the amount of dequeues here)
struct event *event = NULL;
while ((event = g_async_queue_try_pop(g.queue))) {
printf("Event: ts: %ld, title: %s, class: %s, idle: %d\n",
event->timestamp, event->title ?: "(none)",
event->class ?: "(none)", event->idle);
if ((rc = sqlite3_bind_int64(g.add_event, 1, event->timestamp)) ||
(rc = sqlite3_bind_text(g.add_event, 2, event->title, -1,
SQLITE_STATIC)) ||
(rc = sqlite3_bind_text(g.add_event, 3, event->class, -1,
SQLITE_STATIC)) ||
(rc = sqlite3_bind_int(g.add_event, 4, event->idle)))
g_printerr("DB bind error: %s\n", sqlite3_errmsg(g.db));
if (((rc = sqlite3_step(g.add_event)) && rc != SQLITE_DONE) ||
(rc = sqlite3_reset(g.add_event)))
g_printerr("DB write error: %s\n", sqlite3_errmsg(g.db));
event_free(event);
}
if ((rc = sqlite3_exec(g.db, "COMMIT", NULL, NULL, &errmsg))) {
g_printerr("DB commit error: %s\n", errmsg);
free(errmsg);
}
return G_SOURCE_CONTINUE;
}
static sqlite3 *
database_init(const gchar *db_path)
{
sqlite3 *db = 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 ((rc = sqlite3_finalize(stmt)))
exit_fatal("%s: %s", db_path, "cannot retrieve user version");
if (user_version == 0) {
if ((rc = sqlite3_exec(db, "CREATE TABLE events ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"timestamp INTEGER, "
"title TEXT, "
"class 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);
if ((rc = sqlite3_prepare_v2(db,
"INSERT INTO events (timestamp, title, class, idle) "
"VALUES (?, ?, ?, ?)", -1, &g.add_event, NULL)))
exit_fatal("%s: %s", db_path, sqlite3_errmsg(db));
return db;
}
static int
socket_init(const gchar *socket_path)
{
// 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
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);
g_free(lock_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));
// lock_fd stays open as long as the application is running--leak
return socket_fd;
}
int
main(int argc, char *argv[])
{
if (!setlocale(LC_CTYPE, ""))
exit_fatal("cannot set locale");
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);
generator_init();
gchar *data_path =
g_build_filename(g_get_user_data_dir(), PROJECT_NAME, NULL);
g_mkdir_with_parents(data_path, 0755);
// Bind to a control socket, also ensuring only one instance is running.
// We're intentionally not using XDG_RUNTIME_DIR so that what is effectively
// the database lock is right next to the database.
gchar *socket_path = g_build_filename(data_path, "socket", NULL);
int socket_fd = socket_init(socket_path);
g_free(socket_path);
gchar *db_path = g_build_filename(data_path, "db.sqlite", NULL);
g_free(data_path);
g.db = database_init(db_path);
g_free(db_path);
GSource *queue_source = async_queue_source_new();
g_source_set_callback(queue_source, on_queue_incoming, NULL, NULL);
g_source_attach(queue_source, g_main_context_default());
// TODO: listen for connections on the control socket
// - just show/map the window when anything connects at all
WdmtgWindow *window = wdmtg_window_new_with_db(g.db);
generator_launch();
gtk_main();
generator_cleanup();
sqlite3_close(g.db);
close(socket_fd);
return 0;
}

View File

@@ -1,287 +0,0 @@
//
// wdmtg.vala: activity tracker
//
// Copyright (c) 2016, Přemysl Janouch <p@janouch.name>
//
// 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
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
// --- 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 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)");
}
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);
}
}
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");
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");
set_idle_alarm (ref idle_alarm_inactive,
X.Sync.TestType.PositiveComparison, idle_timeout);
}
}
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 {
var ctx = new OptionContext (" - activity tracker");
ctx.set_help_enabled (true);
ctx.add_main_entries (options, null);
ctx.parse (ref args);
} catch (OptionError e) {
exit_fatal ("option parsing failed: %s", e.message);
return 1;
}
if (show_version) {
stdout.printf (Config.PROJECT_NAME + " " + Config.PROJECT_VERSION + "\n");
return 0;
}
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 sync_base, 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 loop = new MainLoop ();
var channel = new IOChannel.unix_new (dpy.connection_number ());
channel.add_watch (IOCondition.IN, (source, condition) => {
if (0 == (condition & IOCondition.IN))
return true;
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;
});
loop.run ();
return 0;
}
}

1
xcb-sync.vapi Normal file
View File

@@ -0,0 +1 @@
// https://github.com/mesonbuild/meson/issues/1195

252
xext.vapi
View File

@@ -1,252 +0,0 @@
//
// xext.vapi: various extensions to the x11 vapi
//
// Copyright (c) 2016, Přemysl Janouch <p.janouch@gmail.com>
//
// 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);
}
}