Compare commits
21 Commits
e80c8b5d86
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
d46305d7ab
|
|||
|
7edd9720cd
|
|||
|
ab5ca0cf8b
|
|||
|
f699b89dad
|
|||
|
9244d2b657
|
|||
|
4302fc4baf
|
|||
|
764dbaa126
|
|||
|
7d4695d8bd
|
|||
|
3482ee66a3
|
|||
|
86b0579cb7
|
|||
|
27a63e3414
|
|||
|
3dd4e69235
|
|||
|
6e3f3c950d
|
|||
|
93c61425b3
|
|||
|
60bfaa1a97
|
|||
|
0d35950715
|
|||
|
e2d91aae1c
|
|||
|
b8242ff3c2
|
|||
|
906b45982c
|
|||
|
c9795fe01a
|
|||
|
487ea01334
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,3 +7,5 @@
|
||||
/wdmtg.files
|
||||
/wdmtg.creator*
|
||||
/wdmtg.includes
|
||||
/wdmtg.cflags
|
||||
/wdmtg.cxxflags
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
633
compound-text.c
Normal 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
21
compound-text.h
Normal 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
3
config.h.in
Normal file
@@ -0,0 +1,3 @@
|
||||
#define PROJECT_NAME "@PROJECT_NAME@"
|
||||
#define PROJECT_VERSION "@PROJECT_VERSION@"
|
||||
#define SHARE_DIR "@project_SHARE_DIR@"
|
||||
@@ -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
6
config.vapi
Normal 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
24
gui.vala
Normal 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
31
meson.build
Normal 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
775
wdmtg.c
Normal 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;
|
||||
}
|
||||
287
wdmtg.vala
287
wdmtg.vala
@@ -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
1
xcb-sync.vapi
Normal file
@@ -0,0 +1 @@
|
||||
// https://github.com/mesonbuild/meson/issues/1195
|
||||
252
xext.vapi
252
xext.vapi
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user