diff options
| author | Joel Klinghed <the_jk@spawned.biz> | 2021-01-27 22:06:49 +0100 |
|---|---|---|
| committer | Joel Klinghed <the_jk@spawned.biz> | 2021-01-27 22:06:49 +0100 |
| commit | 06950aab233de6a2f47293d59575bb42f6131660 (patch) | |
| tree | 62f6eed4a6d35414f656d22b9ac7420849018a11 | |
| parent | 1ef9c463f1efc1adfb62e42ab3dd17e8c6394373 (diff) | |
Complete rewrite using C++ and with shared state support
57 files changed, 2636 insertions, 4529 deletions
@@ -1,22 +1 @@ -.deps -lib*.a -*.analyze -*.dSYM -*.o -*.plist -Makefile -Makefile.in -/aclocal.m4 -/autom4te.cache -/compile -/config.h -/config.h.in -/config.h.in~ -/config.log -/config.status -/configure -/depcomp -/install-sh -/missing -/stamp-h1 -/test-driver
\ No newline at end of file +build/ @@ -0,0 +1,25 @@ +MIT License + +Copyright (c) 2021 Joel Klinghed + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +data/org.the_jk.timer.png: +Creative Commons(Attribution 3.0 unported) +Icon by Free Preloaders [https://freeicons.io/profile/726] on https://freeicons.io
\ No newline at end of file diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index 29abe97..0000000 --- a/Makefile.am +++ /dev/null @@ -1,7 +0,0 @@ -ACLOCAL_AMFLAGS = -I m4 - -MAINTAINERCLEANFILES = Makefile.in aclocal.m4 config.guess config.h.in \ - config.sub configure depcomp install-sh ltmain.sh \ - missing config.rpath mkinstalldirs - -SUBDIRS = src @@ -1,11 +1,7 @@ -Some sources are missing, these are found in the sawmill project. +Timer +===== -Specifically you need: - lib/common/src/compiler.h - lib/common/src/safe_fifo.h - lib/common/src/timespec.h - lib/common/src/thread.h - lib/paths/src/paths.h - - lib/paths/src/libcommon.a - lib/paths/src/libpaths.a
\ No newline at end of file +A fairly simple little timer to keep track of work hours. +The new version uses dbus to allow multiple instances of +timer to share state, useful when connecting remotely to +VNC or similar.
\ No newline at end of file diff --git a/configure.ac b/configure.ac deleted file mode 100644 index 1b85507..0000000 --- a/configure.ac +++ /dev/null @@ -1,99 +0,0 @@ -AC_INIT([timer], [0.1], [the_jk@yahoo.com]) -AC_CONFIG_MACRO_DIR([m4]) - -AC_ISC_POSIX -AC_HEADER_STDC -AC_C_CONST -AC_C_INLINE -#AC_C_BIGENDIAN -AC_C___ATTRIBUTE__ -#AC_PROG_RANLIB - -AM_INIT_AUTOMAKE([dist-bzip2 foreign color-tests parallel-tests]) -AM_SILENT_RULES([yes]) -AC_PROG_CC -AM_PROG_CC_C_O - -DEFINES="-D_DEFAULT_SOURCE -D_GNU_SOURCE -D_XOPEN_SOURCE" -AX_CFLAGS_WARN_ALL([DEFINES]) -AC_ARG_ENABLE([debug], AC_HELP_STRING([compile with debug options]), - use_debug="$enableval", - use_debug=no) -if test "x$use_debug" = "xyes"; then - DEFINES="$DEFINES -g -DDEBUG" -else - DEFINES="$DEFINES -DNDEBUG" -fi -AC_SUBST([DEFINES]) - -# Common - -AC_HEADER_STDBOOL - -AC_SEARCH_LIBS([round], [m],, AC_MSG_ERROR([need round])) - -# Threads - -AC_CHECK_HEADER([pthread.h],have_pthread=1,have_pthread=0) -if test "x$have_pthread" = "x1"; then - OLDCFLAGS="$CFLAGS" - OLDLDFLAGS="$LDFLAGS" - CFLAGS="$OLDCFLAGS -pthread" - LDFLAGS="$OLDLDFLAGS -pthread" - AC_SEARCH_LIBS([pthread_create], [pthread],, have_pthread=0) - LDFLAGS="$OLDLDFLAGS" - CFLAGS="$OLDCFLAGS" - if test "x$have_pthread" = "x1"; then - DEFINES="$DEFINES -pthread" - LIBS="$LIBS -pthread" - fi - - OLDCFLAGS="$CFLAGS" - CFLAGS="$OLDCFLAGS $DEFINES" - - AC_CHECK_FUNC([pthread_yield],[have_pthread_yield=1],[have_pthread_yield=0]) - AC_DEFINE_UNQUOTED([HAVE_PTHREAD_YIELD],[$have_pthread_yield],[define to 1 if pthread_yield is available]) - - if test "x$have_pthread_yield" = "x1"; then - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ -#include <pthread.h> -]],[[ -return pthread_yield(); -]])],pthread_yield_void=0, - pthread_yield_void=1) - AC_DEFINE_UNQUOTED([PTHREAD_YIELD_VOID],[$pthread_yield_void],[define to 1 if pthread_yield returns void]) - fi - CFLAGS="$OLDCFLAGS" -fi -AM_CONDITIONAL([HAVE_PTHREAD], [test "x$have_pthread" = "x1"]) -AC_DEFINE_UNQUOTED([HAVE_PTHREAD],[$have_pthread],[define to 1 if pthread is available]) - -if test "x$have_pthread" = "x0"; then - AC_MSG_ERROR([no thread implementation found]) -fi - -# timeval -AC_CHECK_TYPES([struct timespec],,,[[#include <time.h>]]) - -# clock - -AC_SEARCH_LIBS([clock_gettime], [rt], [have_clock_gettime=1], [have_clock_gettime=0]) -AC_DEFINE_UNQUOTED([HAVE_CLOCK_GETTIME], [$have_clock_gettime], [Define to 1 if clock_gettime is available]) - -# XCB - -PKG_PROG_PKG_CONFIG() - -xcb_modules="xcb xcb-event xcb-keysyms" -PKG_CHECK_EXISTS([xcb-icccm >= 0.3.8], - [have_xcb_icccm=1 - xcb_modules="$xcb_modules xcb-icccm"],[have_xcb_icccm=0]) - -PKG_CHECK_MODULES([XCB], [$xcb_modules]) - -AC_DEFINE_UNQUOTED([HAVE_XCB_ICCCM],[$have_xcb_icccm],[define to 1 if xcb-icccm is available]) - -# Finish up - -AC_CONFIG_HEADERS([config.h]) -AC_OUTPUT([Makefile src/Makefile]) diff --git a/data/org.the_jk.timer.desktop b/data/org.the_jk.timer.desktop new file mode 100644 index 0000000..7536e98 --- /dev/null +++ b/data/org.the_jk.timer.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Name=Timer +Comment=Keeps track of the hours +Categories=Utility +Exec=timer +Icon=org.the_jk.timer +StartupNotify=false +StartupWMClass=org.the_jk.timer diff --git a/data/org.the_jk.timer.png b/data/org.the_jk.timer.png Binary files differnew file mode 100644 index 0000000..7bc15e8 --- /dev/null +++ b/data/org.the_jk.timer.png diff --git a/m4/ac_attribute.m4 b/m4/ac_attribute.m4 deleted file mode 100644 index 23479a9..0000000 --- a/m4/ac_attribute.m4 +++ /dev/null @@ -1,47 +0,0 @@ -dnl Copyright (C) 2004-2008 Kim Woelders -dnl Copyright (C) 2008 Vincent Torri <vtorri at univ-evry dot fr> -dnl That code is public domain and can be freely used or copied. -dnl Originally snatched from somewhere... - -dnl Macro for checking if the compiler supports __attribute__ - -dnl Usage: AC_C___ATTRIBUTE__ -dnl call AC_DEFINE for HAVE___ATTRIBUTE__ and __UNUSED__ -dnl if the compiler supports __attribute__, HAVE___ATTRIBUTE__ is -dnl defined to 1 and __UNUSED__ is defined to __attribute__((unused)) -dnl otherwise, HAVE___ATTRIBUTE__ is not defined and __UNUSED__ is -dnl defined to nothing. - -AC_DEFUN([AC_C___ATTRIBUTE__], -[ - -AC_MSG_CHECKING([for __attribute__]) - -AC_CACHE_VAL([ac_cv___attribute__], - [AC_TRY_COMPILE( - [ -#include <stdlib.h> - -int func(int x); -int foo(int x __attribute__ ((unused))) -{ - exit(1); -} - ], - [], - [ac_cv___attribute__="yes"], - [ac_cv___attribute__="no"] - )]) - -AC_MSG_RESULT($ac_cv___attribute__) - -if test "x${ac_cv___attribute__}" = "xyes" ; then - AC_DEFINE([HAVE___ATTRIBUTE__], [1], [Define to 1 if your compiler has __attribute__]) - AC_DEFINE([__UNUSED__], [__attribute__((unused))], [Macro declaring a function argument to be unused]) - else - AC_DEFINE([__UNUSED__], [], [Macro declaring a function argument to be unused]) -fi - -]) - -dnl End of ac_attribute.m4 diff --git a/m4/ax_append_compile_flags.m4 b/m4/ax_append_compile_flags.m4 deleted file mode 100644 index 1f8e708..0000000 --- a/m4/ax_append_compile_flags.m4 +++ /dev/null @@ -1,65 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_append_compile_flags.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_APPEND_COMPILE_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS]) -# -# DESCRIPTION -# -# For every FLAG1, FLAG2 it is checked whether the compiler works with the -# flag. If it does, the flag is added FLAGS-VARIABLE -# -# If FLAGS-VARIABLE is not specified, the current language's flags (e.g. -# CFLAGS) is used. During the check the flag is always added to the -# current language's flags. -# -# If EXTRA-FLAGS is defined, it is added to the current language's default -# flags (e.g. CFLAGS) when the check is done. The check is thus made with -# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to -# force the compiler to issue an error when a bad flag is given. -# -# NOTE: This macro depends on the AX_APPEND_FLAG and -# AX_CHECK_COMPILE_FLAG. Please keep this macro in sync with -# AX_APPEND_LINK_FLAGS. -# -# LICENSE -# -# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> -# -# This program is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation, either version 3 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see <http://www.gnu.org/licenses/>. -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Archive. When you make and distribute a -# modified version of the Autoconf Macro, you may extend this special -# exception to the GPL to apply to your modified version as well. - -#serial 3 - -AC_DEFUN([AX_APPEND_COMPILE_FLAGS], -[AC_REQUIRE([AX_CHECK_COMPILE_FLAG]) -AC_REQUIRE([AX_APPEND_FLAG]) -for flag in $1; do - AX_CHECK_COMPILE_FLAG([$flag], [AX_APPEND_FLAG([$flag], [$2])], [], [$3]) -done -])dnl AX_APPEND_COMPILE_FLAGS diff --git a/m4/ax_append_flag.m4 b/m4/ax_append_flag.m4 deleted file mode 100644 index 1d38b76..0000000 --- a/m4/ax_append_flag.m4 +++ /dev/null @@ -1,69 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_append_flag.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_APPEND_FLAG(FLAG, [FLAGS-VARIABLE]) -# -# DESCRIPTION -# -# FLAG is appended to the FLAGS-VARIABLE shell variable, with a space -# added in between. -# -# If FLAGS-VARIABLE is not specified, the current language's flags (e.g. -# CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains -# FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly -# FLAG. -# -# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. -# -# LICENSE -# -# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de> -# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> -# -# This program is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation, either version 3 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see <http://www.gnu.org/licenses/>. -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Archive. When you make and distribute a -# modified version of the Autoconf Macro, you may extend this special -# exception to the GPL to apply to your modified version as well. - -#serial 2 - -AC_DEFUN([AX_APPEND_FLAG], -[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX -AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])])dnl -AS_VAR_SET_IF(FLAGS, - [case " AS_VAR_GET(FLAGS) " in - *" $1 "*) - AC_RUN_LOG([: FLAGS already contains $1]) - ;; - *) - AC_RUN_LOG([: FLAGS="$FLAGS $1"]) - AS_VAR_SET(FLAGS, ["AS_VAR_GET(FLAGS) $1"]) - ;; - esac], - [AS_VAR_SET(FLAGS,["$1"])]) -AS_VAR_POPDEF([FLAGS])dnl -])dnl AX_APPEND_FLAG diff --git a/m4/ax_cflags_warn_all.m4 b/m4/ax_cflags_warn_all.m4 deleted file mode 100644 index 0fa3e18..0000000 --- a/m4/ax_cflags_warn_all.m4 +++ /dev/null @@ -1,122 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_cflags_warn_all.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_CFLAGS_WARN_ALL [(shellvar [,default, [A/NA]])] -# AX_CXXFLAGS_WARN_ALL [(shellvar [,default, [A/NA]])] -# AX_FCFLAGS_WARN_ALL [(shellvar [,default, [A/NA]])] -# -# DESCRIPTION -# -# Try to find a compiler option that enables most reasonable warnings. -# -# For the GNU compiler it will be -Wall (and -ansi -pedantic) The result -# is added to the shellvar being CFLAGS, CXXFLAGS, or FCFLAGS by default. -# -# Currently this macro knows about the GCC, Solaris, Digital Unix, AIX, -# HP-UX, IRIX, NEC SX-5 (Super-UX 10), Cray J90 (Unicos 10.0.0.8), and -# Intel compilers. For a given compiler, the Fortran flags are much more -# experimental than their C equivalents. -# -# - $1 shell-variable-to-add-to : CFLAGS, CXXFLAGS, or FCFLAGS -# - $2 add-value-if-not-found : nothing -# - $3 action-if-found : add value to shellvariable -# - $4 action-if-not-found : nothing -# -# NOTE: These macros depend on AX_APPEND_FLAG. -# -# LICENSE -# -# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de> -# Copyright (c) 2010 Rhys Ulerich <rhys.ulerich@gmail.com> -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation; either version 3 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see <http://www.gnu.org/licenses/>. -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Archive. When you make and distribute a -# modified version of the Autoconf Macro, you may extend this special -# exception to the GPL to apply to your modified version as well. - -#serial 14 - -AC_DEFUN([AX_FLAGS_WARN_ALL],[dnl -AS_VAR_PUSHDEF([FLAGS],[_AC_LANG_PREFIX[]FLAGS])dnl -AS_VAR_PUSHDEF([VAR],[ac_cv_[]_AC_LANG_ABBREV[]flags_warn_all])dnl -AC_CACHE_CHECK([m4_ifval($1,$1,FLAGS) for maximum warnings], -VAR,[VAR="no, unknown" -ac_save_[]FLAGS="$[]FLAGS" -for ac_arg dnl -in "-warn all % -warn all" dnl Intel - "-pedantic % -Wall" dnl GCC - "-xstrconst % -v" dnl Solaris C - "-std1 % -verbose -w0 -warnprotos" dnl Digital Unix - "-qlanglvl=ansi % -qsrcmsg -qinfo=all:noppt:noppc:noobs:nocnd" dnl AIX - "-ansi -ansiE % -fullwarn" dnl IRIX - "+ESlit % +w1" dnl HP-UX C - "-Xc % -pvctl[,]fullmsg" dnl NEC SX-5 (Super-UX 10) - "-h conform % -h msglevel 2" dnl Cray C (Unicos) - # -do FLAGS="$ac_save_[]FLAGS "`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'` - AC_COMPILE_IFELSE([AC_LANG_PROGRAM], - [VAR=`echo $ac_arg | sed -e 's,.*% *,,'` ; break]) -done -FLAGS="$ac_save_[]FLAGS" -]) -AS_VAR_POPDEF([FLAGS])dnl -AC_REQUIRE([AX_APPEND_FLAG]) -case ".$VAR" in - .ok|.ok,*) m4_ifvaln($3,$3) ;; - .|.no|.no,*) m4_default($4,[m4_ifval($2,[AX_APPEND_FLAG([$2], [$1])])]) ;; - *) m4_default($3,[AX_APPEND_FLAG([$VAR], [$1])]) ;; -esac -AS_VAR_POPDEF([VAR])dnl -])dnl AX_FLAGS_WARN_ALL -dnl implementation tactics: -dnl the for-argument contains a list of options. The first part of -dnl these does only exist to detect the compiler - usually it is -dnl a global option to enable -ansi or -extrawarnings. All other -dnl compilers will fail about it. That was needed since a lot of -dnl compilers will give false positives for some option-syntax -dnl like -Woption or -Xoption as they think of it is a pass-through -dnl to later compile stages or something. The "%" is used as a -dnl delimiter. A non-option comment can be given after "%%" marks -dnl which will be shown but not added to the respective C/CXXFLAGS. - -AC_DEFUN([AX_CFLAGS_WARN_ALL],[dnl -AC_LANG_PUSH([C]) -AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) -AC_LANG_POP([C]) -]) - -AC_DEFUN([AX_CXXFLAGS_WARN_ALL],[dnl -AC_LANG_PUSH([C++]) -AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) -AC_LANG_POP([C++]) -]) - -AC_DEFUN([AX_FCFLAGS_WARN_ALL],[dnl -AC_LANG_PUSH([Fortran]) -AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) -AC_LANG_POP([Fortran]) -]) diff --git a/m4/pkg.m4 b/m4/pkg.m4 deleted file mode 100644 index bfe1dd1..0000000 --- a/m4/pkg.m4 +++ /dev/null @@ -1,158 +0,0 @@ -# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- -# -# Copyright © 2004 Scott James Remnant <scott@netsplit.com>. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - -# PKG_PROG_PKG_CONFIG([MIN-VERSION]) -# ---------------------------------- -AC_DEFUN([PKG_PROG_PKG_CONFIG], -[m4_pattern_forbid([^_?PKG_[A-Z_]+$]) -m4_pattern_allow([^PKG_CONFIG(_PATH)?$]) -AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl -if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then - AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) -fi -if test -n "$PKG_CONFIG"; then - _pkg_min_version=m4_default([$1], [0.9.0]) - AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) - if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then - AC_MSG_RESULT([yes]) - else - AC_MSG_RESULT([no]) - PKG_CONFIG="" - fi - -fi[]dnl -])# PKG_PROG_PKG_CONFIG - -# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) -# -# Check to see whether a particular set of modules exists. Similar -# to PKG_CHECK_MODULES(), but does not set variables or print errors. -# -# -# Similar to PKG_CHECK_MODULES, make sure that the first instance of -# this or PKG_CHECK_MODULES is called, or make sure to call -# PKG_CHECK_EXISTS manually -# -------------------------------------------------------------- -AC_DEFUN([PKG_CHECK_EXISTS], -[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl -if test -n "$PKG_CONFIG" && \ - AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then - m4_ifval([$2], [$2], [:]) -m4_ifvaln([$3], [else - $3])dnl -fi]) - - -# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) -# --------------------------------------------- -m4_define([_PKG_CONFIG], -[if test -n "$PKG_CONFIG"; then - if test -n "$$1"; then - pkg_cv_[]$1="$$1" - else - PKG_CHECK_EXISTS([$3], - [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`], - [pkg_failed=yes]) - fi -else - pkg_failed=untried -fi[]dnl -])# _PKG_CONFIG - -# _PKG_SHORT_ERRORS_SUPPORTED -# ----------------------------- -AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], -[AC_REQUIRE([PKG_PROG_PKG_CONFIG]) -if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then - _pkg_short_errors_supported=yes -else - _pkg_short_errors_supported=no -fi[]dnl -])# _PKG_SHORT_ERRORS_SUPPORTED - - -# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], -# [ACTION-IF-NOT-FOUND]) -# -# -# Note that if there is a possibility the first call to -# PKG_CHECK_MODULES might not happen, you should be sure to include an -# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac -# -# -# -------------------------------------------------------------- -AC_DEFUN([PKG_CHECK_MODULES], -[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl -AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl -AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl - -pkg_failed=no -AC_MSG_CHECKING([for $2]) - -_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) -_PKG_CONFIG([$1][_LIBS], [libs], [$2]) - -m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS -and $1[]_LIBS to avoid the need to call pkg-config. -See the pkg-config man page for more details.]) - -if test $pkg_failed = yes; then - _PKG_SHORT_ERRORS_SUPPORTED - if test $_pkg_short_errors_supported = yes; then - $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "$2"` - else - $1[]_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"` - fi - # Put the nasty error message in config.log where it belongs - echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD - - ifelse([$4], , [AC_MSG_ERROR(dnl -[Package requirements ($2) were not met: - -$$1_PKG_ERRORS - -Consider adjusting the PKG_CONFIG_PATH environment variable if you -installed software in a non-standard prefix. - -_PKG_TEXT -])], - [$4]) - AC_MSG_RESULT([no]) -elif test $pkg_failed = untried; then - ifelse([$4], , [AC_MSG_FAILURE(dnl -[The pkg-config script could not be found or is too old. Make sure it -is in your PATH or set the PKG_CONFIG environment variable to the full -path to pkg-config. - -_PKG_TEXT - -To get pkg-config, see <http://www.freedesktop.org/software/pkgconfig>.])], - [$4]) - AC_MSG_RESULT([no]) -else - $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS - $1[]_LIBS=$pkg_cv_[]$1[]_LIBS - AC_MSG_RESULT([yes]) - ifelse([$3], , :, [$3]) -fi[]dnl -])# PKG_CHECK_MODULES diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..12495da --- /dev/null +++ b/meson.build @@ -0,0 +1,88 @@ +project('timer', 'cpp', + version: '0.2', + default_options: [ + 'warning_level=3', + 'cpp_std=c++17', + 'cpp_rtti=false' + ]) + +cpp_optional_flags = [ + '-fvisibility=hidden', + # Redefining explicit to be able to include xkb.h + '-Wno-keyword-macro', +] +cpp_flags = [ + '-DVERSION="' + meson.project_version() + '"' +] +if get_option('buildtype') == 'release' + cpp_flags += '-DNDEBUG' +endif + +cpp = meson.get_compiler('cpp') +cpp_flags += cpp.get_supported_arguments(cpp_optional_flags) +add_project_arguments(cpp_flags, language: 'cpp') + +thread_dep = dependency('threads') + +xcb_dep = [dependency('xcb', version: '>= 1.14'), + dependency('xcb-xkb', version: '>= 1.14'), + dependency('xcb-event', version: '>= 0.4.0'), + dependency('xcb-icccm', version: '>= 0.4.1'), + dependency('xcb-keysyms', version: '>= 0.4.0'), + dependency('xkbcommon-x11', version: '>= 1.0.3')] + +xcb_xrm_dep = dependency('xcb-xrm', version: '>= 1.3', required: false) + +dbus_dep = dependency('sdbus-c++', version: '>= 0.8.3') + +timer_sources = [ + 'src/args.cc', + 'src/io.cc', + 'src/timer.cc', + 'src/timer_state.cc', + 'src/unique_fd.cc', + 'src/xcb_atoms.cc', + 'src/xcb_colors.cc', + 'src/xcb_connection.cc', + 'src/xcb_resource.cc', + 'src/xcb_xkb.cc', + 'src/xdg.cc', +] + +if xcb_xrm_dep.found() + timer_sources += 'src/xcb_resources_xrm.cc' +else + timer_sources += 'src/xcb_resources_none.cc' +endif + +exe = executable('timer', + sources: timer_sources, + dependencies: [dbus_dep, thread_dep, xcb_dep, xcb_xrm_dep], + install: true) + +xdg_desktop_menu = find_program('xdg-desktop-menu', required: false, + native: true) +if xdg_desktop_menu.found() + meson.add_install_script(xdg_desktop_menu, 'install', '--novendor', + files('data/org.the_jk.timer.desktop')) +else + desktop_path = get_option('datadir') / 'applications' + + install_data('data/org.the_jk.timer.desktop', install_dir : desktop_path) +endif + +xdg_icon_resource = find_program('xdg-icon-resource', required: false, + native: true) +if xdg_icon_resource.found() + meson.add_install_script(xdg_icon_resource, 'install', '--novendor', + '--size', '128', files('data/org.the_jk.timer.png')) +else + icon_path = get_option('datadir') / 'icons' / 'hicolor' / '128x128' / 'apps' + install_data('data/org.the_jk.timer.png', install_dir : icon_path) + + gtk_update_icon_cache = find_program('gtk-update-icon-cache', + required: false, native: true) + if gtk_update_icon_cache.found() + meson.add_install_script(gtk_update_icon_cache, icon_path) + endif +endif diff --git a/src/Makefile.am b/src/Makefile.am deleted file mode 100644 index 7131b86..0000000 --- a/src/Makefile.am +++ /dev/null @@ -1,14 +0,0 @@ -MAINTAINERCLEANFILES = Makefile.in - -AM_CPPFLAGS = @DEFINES@ -DVERSION='"@VERSION@"' - -bin_PROGRAMS = timer - -timer_SOURCES = main.c common.h compiler.h safe_fifo.h thread.h paths.h \ - safe_fifo.c timespec.h timespec.c paths.c ref.h macros.h \ - strutil.h strutil.c dynstr.h dynstr.c -if HAVE_PTHREAD -timer_SOURCES += thread-pthread.c -endif -timer_CFLAGS = @XCB_CFLAGS@ -timer_LDADD = @XCB_LIBS@ diff --git a/src/args.cc b/src/args.cc new file mode 100644 index 0000000..50f6c5a --- /dev/null +++ b/src/args.cc @@ -0,0 +1,288 @@ +#include "common.hh" + +#include "args.hh" + +#include <iostream> +#include <unordered_map> +#include <vector> + +namespace { + +class OptionImpl : public Option { +public: + OptionImpl(char short_name, std::string long_name, std::string description, + bool require_arg, std::string arg_description) + : short_name_(short_name), + long_name_(std::move(long_name)), + description_(std::move(description)), + require_arg_(require_arg), + arg_description_(std::move(arg_description)) { + } + + bool is_set() const override { return set_; } + + std::string const& arg() const override { return arg_; } + + char short_name() const { return short_name_; } + + std::string const& long_name() const { return long_name_; } + + std::string const& description() const { return description_; } + + bool require_arg() const { return require_arg_; } + + std::string const& arg_description() const { return arg_description_; } + + void reset() { + set_ = false; + arg_.clear(); + } + + void set() { + set_ = true; + } + + void set_arg(std::string arg) { + arg_ = std::move(arg); + } + +private: + char const short_name_; + std::string const long_name_; + std::string const description_; + bool const require_arg_; + std::string const arg_description_; + bool set_ = false; + std::string arg_; +}; + +class ArgsImpl : public Args { +public: + ArgsImpl() = default; + + Option const* add_option(char short_name, std::string long_name, + std::string description) override { + prepare_option(short_name, long_name); + options_.push_back(std::make_unique<OptionImpl>(short_name, + std::move(long_name), + std::move(description), + false, std::string())); + return options_.back().get(); + } + + Option const* add_option_with_arg(char short_name, std::string long_name, + std::string description, + std::string arg_description) override { + prepare_option(short_name, long_name); + options_.push_back(std::make_unique<OptionImpl>(short_name, + std::move(long_name), + std::move(description), + true, arg_description)); + return options_.back().get(); + } + + bool run(int argc, char** argv, std::string_view prgname, std::ostream& err, + std::vector<std::string>* out) override { + for (int a = 1; a < argc; ++a) { + if (argv[a][0] == '-') { + if (argv[a][1] == '-') { + if (argv[a][2] != '\0') { + // A long name with optional "=" argument + size_t len = 2; + while (argv[a][len] != '=' && argv[a][len]) + ++len; + std::string name(argv[a] + 2, len - 2); + auto it = long_names_.find(name); + if (it == long_names_.end()) { + err << prgname << ": unrecognized option '--" + << name << "'" << std::endl; + return false; + } + auto* opt = options_[it->second].get(); + opt->set(); + if (argv[a][len]) { + if (opt->require_arg()) { + opt->set_arg(std::string(argv[a] + len + 1)); + } else { + err << prgname << ": option '--" + << name << "' doesn't allow an argument" << std::endl; + return false; + } + } else { + if (opt->require_arg()) { + if (a + 1 >= argc) { + err << prgname << ": option '--" + << name << "' requires an argument" << std::endl; + return false; + } else { + opt->set_arg(argv[++a]); + } + } + } + continue; + } else { + // "--", all following values are arguments + for (++a; a < argc; ++a) + out->push_back(argv[a]); + break; + } + } else if (argv[a][1] != '\0') { + // One or more short names + for (auto* name = argv[a] + 1; *name; ++name) { + auto it = short_names_.find(*name); + if (it == short_names_.end()) { + err << prgname << ": invalid option -- '" + << *name << "'" << std::endl; + return false; + } + auto* opt = options_[it->second].get(); + opt->set(); + if (opt->require_arg()) { + if (a + 1 >= argc) { + err << prgname << ": option requires an argument" + << " -- '" << *name << "'" << std::endl; + return false; + } else { + opt->set_arg(argv[++a]); + } + } + } + continue; + } else { + // single "-", treat as argument + } + } + + out->push_back(argv[a]); + } + return true; + } + + void print_descriptions(std::ostream& out, + uint32_t column_width) const override { + uint32_t max_left = 0; + for (auto const& option : options_) { + uint32_t left = 0; + if (option->short_name() != '\0') { + if (!option->long_name().empty()) { + left = 6 + option->long_name().size(); // -S, --long + } else { + left = 2; // -S + } + } else if (!option->long_name().empty()) { + left = 2 + option->long_name().size(); // --long + } + if (option->require_arg()) + left += 1 + option->arg_description().size(); // (=| )ARG + if (left > 0) + left += 2; // Need at least two spaces between option and desc + + if (left > max_left) + max_left = left; + } + + uint32_t const avail_right = + max_left > column_width ? 0 : column_width - max_left; + + if (avail_right < 20) { + // Fallback mode, description on its own row. + for (auto const& option : options_) { + print_option(out, *option); + out << '\n' << option->description() << '\n'; + } + return; + } + + // Check if all descriptions fit, justify to the right on a 80 col width + bool all_desc_fit = true; + uint32_t max_right = 0; + for (auto const& option : options_) { + uint32_t right = option->description().size(); + if (right > avail_right) { + all_desc_fit = false; + break; + } + if (right > max_right) + max_right = right; + } + + if (all_desc_fit) + max_left = std::max(80u, column_width) - max_right; + + for (auto const& option : options_) { + uint32_t left = print_option(out, *option); + std::fill_n(std::ostreambuf_iterator<char>(out), max_left - left, ' '); + + if (option->description().size() <= avail_right) { + out << option->description() << '\n'; + continue; + } + + // Wrap description + size_t last = 0; + bool first = true; + while (true) { + if (first) { + first = false; + } else { + std::fill_n(std::ostreambuf_iterator<char>(out), max_left, ' '); + } + + size_t end = last + avail_right; + if (end >= option->description().size()) { + out << option->description() << '\n'; + break; + } + size_t space = option->description().rfind(' ', end); + if (space == std::string::npos || space < last) { + space = end; + } + out << option->description().substr(last, space - last) << '\n'; + last = space < end ? space + 1 : end; + } + } + } + +private: + void prepare_option(char short_name, std::string const& long_name) { + if (short_name != '\0') + short_names_.emplace(short_name, options_.size()); + if (!long_name.empty()) { + assert(long_name.find('=') == std::string::npos); + long_names_.emplace(long_name, options_.size()); + } + } + + size_t print_option(std::ostream& out, const OptionImpl& option) const { + bool only_short = false; + size_t ret = 0; + if (option.short_name() != '\0') { + out << '-' << option.short_name(); + if (!option.long_name().empty()) { + out << ", --" << option.long_name(); + ret = 6 + option.long_name().size(); + } else { + ret = 2; + only_short = true; + } + } else if (!option.long_name().empty()) { + out << "--" << option.long_name(); + ret = 2 + option.long_name().size(); + } + if (option.require_arg()) { + out << (only_short ? ' ' : '=') << option.arg_description(); + ret += 1 + option.arg_description().size(); + } + return ret; + } + + std::vector<std::unique_ptr<OptionImpl>> options_; + std::unordered_map<char, size_t> short_names_; + std::unordered_map<std::string, size_t> long_names_; +}; + +} // namespace + +std::unique_ptr<Args> Args::create() { + return std::make_unique<ArgsImpl>(); +} diff --git a/src/args.hh b/src/args.hh new file mode 100644 index 0000000..b581307 --- /dev/null +++ b/src/args.hh @@ -0,0 +1,53 @@ +#ifndef ARGS_HH +#define ARGS_HH + +#include <iosfwd> +#include <memory> +#include <string> +#include <string_view> +#include <vector> + +class Option { +public: + virtual ~Option() = default; + + virtual bool is_set() const = 0; + virtual std::string const& arg() const = 0; + +protected: + Option() = default; + Option(Option const&) = delete; + Option& operator=(Option const&) = delete; +}; + +class Args { +public: + virtual ~Args() = default; + + static std::unique_ptr<Args> create(); + + // Returned Option is owned by Args instance. + virtual Option const* add_option( + char short_name, + std::string long_name, + std::string description) = 0; + + virtual Option const* add_option_with_arg( + char short_name, + std::string long_name, + std::string description, + std::string arg_description) = 0; + + virtual bool run(int argc, char** argv, std::string_view prgname, + std::ostream& err, std::vector<std::string>* out) = 0; + + virtual void print_descriptions(std::ostream& out, + uint32_t column_width) const = 0; + +protected: + Args() = default; + Args(Args const&) = delete; + Args& operator=(Args const&) = delete; +}; + +#endif // ARGS_HH diff --git a/src/common.h b/src/common.h deleted file mode 100644 index 5b5da04..0000000 --- a/src/common.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef COMMON_H -#define COMMON_H - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include <assert.h> -#include <stdbool.h> -#include <stdlib.h> - -#include "compiler.h" -#include "macros.h" - -#define API - -#endif /* COMMON_H */ diff --git a/src/common.hh b/src/common.hh new file mode 100644 index 0000000..1618bc1 --- /dev/null +++ b/src/common.hh @@ -0,0 +1,6 @@ +#ifndef COMMON_HH +#define COMMON_HH + +#include <assert.h> + +#endif // COMMON_HH diff --git a/src/compiler.h b/src/compiler.h deleted file mode 100644 index 6a5ce4a..0000000 --- a/src/compiler.h +++ /dev/null @@ -1,42 +0,0 @@ -/** - * \file compiler.h - * Part of common.h, defines macros for hints to the compiler. - */ - -#ifndef COMPILER_H -#define COMPILER_H - -#if HAVE___ATTRIBUTE__ -# define MALLOC __attribute__((malloc)) -# define NONULL __attribute__((nonnull)) -# define NONULL_ARGS(...) __attribute__((nonnull (__VA_ARGS__))) -# define PRINTF(_n,_m) __attribute__((format (printf, (_n), (_m)))) -# define UNUSED __attribute__((unused)) -#else -# warning Less compile time checks as compiler is unsupported -/** - * Mark a function as always returning a new unique pointer or NULL - */ -# define MALLOC -/** - * Mark a function as not taking NULL to any of its pointer parameters - */ -# define NONULL -/** - * Mark a function as not taking NULL to the given list of pointer parameters. - * The list of parameters is 1 based. - */ -# define NONULL_ARGS(...) -/** - * Mark a function as taking printf format style parameter. - * @param _n the 1 based index of the format parameter - * @param _m the 1 based index of the first variable list parameter - */ -# define PRINTF(_n,_m) -/** - * Mark a function paramter as being unused - */ -# define UNUSED -#endif - -#endif /* COMPILER_H */ diff --git a/src/dynstr.c b/src/dynstr.c deleted file mode 100644 index 72db26d..0000000 --- a/src/dynstr.c +++ /dev/null @@ -1,94 +0,0 @@ -#include "common.h" - -#include "dynstr.h" - -dynstr_t* dynstr_new(void) -{ - dynstr_t* str = calloc(1, sizeof(dynstr_t)); - if (str) - { - str->size = 64; - str->data = malloc(str->size); - if (!str->data) - { - free(str); - return NULL; - } - } - return str; -} - -dynstr_t* dynstr_new_strn(const char* cstr, size_t len) -{ - dynstr_t* str = calloc(1, sizeof(dynstr_t)); - assert(cstr); - if (str) - { - str->len = len; - str->size = len + 1; - str->data = malloc(str->size); - if (!str->data) - { - free(str); - return NULL; - } - memcpy(str->data, cstr, len); - } - return str; -} - -void dynstr_free(dynstr_t* str) -{ - if (str) - { - free(str->data); - free(str); - } -} - -char* dynstr_done(dynstr_t* str) -{ - char* ret; - assert(str); - assert(str->len < str->size); - str->data[str->len] = '\0'; - ret = str->data; - free(str); - return ret; -} - -const char* dynstr_peek(dynstr_t* str) -{ - assert(str); - assert(str->len < str->size); - str->data[str->len] = '\0'; - return str->data; -} - -bool dynstr_nappend(dynstr_t* str, const char* add, size_t len) -{ - if (str->len + len >= str->size) - { - size_t ns = MAX(str->size * 2, str->len + len + 1); - char* tmp = realloc(str->data, ns); - if (!tmp) - { - return false; - } - str->data = tmp; - str->size = ns; - } - memcpy(str->data + str->len, add, len); - str->len += len; - return true; -} - -void dynstr_cut(dynstr_t* str, size_t newlen) -{ - assert(str); - assert(newlen <= str->len); - if (newlen < str->len) - { - str->len = newlen; - } -} diff --git a/src/dynstr.h b/src/dynstr.h deleted file mode 100644 index f18e4f2..0000000 --- a/src/dynstr.h +++ /dev/null @@ -1,122 +0,0 @@ -/** - * @file dynstr.h - * Dynamically allocated string. - */ - -#ifndef DYNSTR_H -#define DYNSTR_H - -/** - * Dynamically allocated string. - * If you modify the content of the struct your self (please don't) make sure - * len is ALWAYS less than size. - */ -typedef struct dynstr_t -{ - /** - * String data, size large and filled with len bytes of string. - * Not null-terminated. - */ - char* data; - /** - * Number of bytes in data - */ - size_t len; - /** - * Size of data in bytes - */ - size_t size; -} dynstr_t; - -#include <string.h> - -/** - * Allocate a dynamically allocated string. - * Returned string is empty but has allocate at least one byte of space. - * Returns NULL in case of allocation error. - * @return empty string - */ -MALLOC dynstr_t* dynstr_new(void); -/** - * Allocate a dynamically allocated string and initialize with parameters. - * Returned string has at least one more byte allocated than len. - * Returns NULL in case of allocation error. - * @param str string to copy len bytes from, may not be NULL - * @param len number of bytes to copy from str - * @return string with given data - */ -MALLOC NONULL dynstr_t* dynstr_new_strn(const char* str, size_t len); -/** - * Allocate a dynamically allocated string and initialize with parameter. - * Returned string has space for at least one more byte. - * Returns NULL in case of allocation error. - * @param str null-terminated string to copy from, may not be NULL - * @return string with given data - */ -static inline MALLOC NONULL dynstr_t* dynstr_new_str(const char* str) -{ - assert(str); - return dynstr_new_strn(str, strlen(str)); -} -/** - * Free a dynamic string. - * @param str string to free, may be NULL - */ -void dynstr_free(dynstr_t* str); - -/** - * Return null-terminated static string and free dynamic string. - * @param str string to free, may not be NULL - * @return null-terminated content of str, never NULL - */ -NONULL char* dynstr_done(dynstr_t* str); - -/** - * Return null-terminated static string. - * The returned string is only valid until the next call to a dynstr method - * on the same string. - * @param str string to free, may not be NULL - * @return null-terminated content of str, never NULL - */ -NONULL const char* dynstr_peek(dynstr_t* str); - -/** - * Append len bytes of data from add to dynamic string. - * @param str string to append to, may not be NULL - * @param add string to append, may not be NULL - * @param len number of bytes to append from add - * @return false in case of allocation error - */ -NONULL bool dynstr_nappend(dynstr_t* str, const char* add, size_t len); - -/** - * Append string to dynamic string. - * @param str string to append to, may not be NULL - * @param add null-terminated string to append, may not be NULL - * @return false in case of allocation error - */ -static inline NONULL bool dynstr_append(dynstr_t* str, const char* add) -{ - assert(add); - return dynstr_nappend(str, add, strlen(add)); -} - -/** - * Append character to dynamic string. - * @param str string to append to, may not be NULL - * @param c character to append - * @return false in case of allocation error - */ -static inline NONULL bool dynstr_appendc(dynstr_t* str, char c) -{ - return dynstr_nappend(str, &c, 1); -} - -/** - * Make dynamic string shorter by cutting of a bit at the end. - * @param str string to cut shorter, may not be NULL - * @param newlen new length of string - */ -NONULL void dynstr_cut(dynstr_t* str, size_t newlen); - -#endif /* DYNSTR_H */ diff --git a/src/io.cc b/src/io.cc new file mode 100644 index 0000000..0fb31d9 --- /dev/null +++ b/src/io.cc @@ -0,0 +1,41 @@ +#include "common.hh" + +#include "io.hh" + +#include <errno.h> +#include <unistd.h> + +namespace io { + +bool read_all(int fd, std::string* out) { + char buf[32768]; + while (true) { + auto ret = read(fd, buf, sizeof(buf)); + if (ret == 0) + return true; + if (ret < 0) { + if (errno == EINTR) + continue; + return false; + } + out->append(buf, ret); + } +} + +bool write_all(int fd, std::string const& in) { + size_t offset = 0; + while (offset < in.size()) { + auto ret = write(fd, in.data() + offset, in.size() - offset); + if (ret == 0) + return false; + if (ret < 0) { + if (errno == EINTR) + continue; + return false; + } + offset += ret; + } + return true; +} + +} // namespace io diff --git a/src/io.hh b/src/io.hh new file mode 100644 index 0000000..8786549 --- /dev/null +++ b/src/io.hh @@ -0,0 +1,13 @@ +#ifndef IO_HH +#define IO_HH + +#include <string> + +namespace io { + +bool read_all(int fd, std::string* out); +bool write_all(int fd, std::string const& in); + +} // namespace io + +#endif // IO_HH diff --git a/src/macros.h b/src/macros.h deleted file mode 100644 index 06d59c6..0000000 --- a/src/macros.h +++ /dev/null @@ -1,19 +0,0 @@ -/** - * \file macros.h - * Part of common.h, defines common small macros if needed - */ - -#ifndef MACROS_H -#define MACROS_H - -#ifndef MIN -/** x < y ? x : y */ -# define MIN(_x, _y) (((_x) < (_y)) ? (_x) : (_y)) -#endif - -#ifndef MAX -/** x > y ? x : y */ -# define MAX(_x, _y) (((_x) > (_y)) ? (_x) : (_y)) -#endif - -#endif /* MACROS_H */ diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 9bb5b6c..0000000 --- a/src/main.c +++ /dev/null @@ -1,896 +0,0 @@ -#include "common.h" - -#include <errno.h> -#include <math.h> -#include <stdio.h> -#include <string.h> - -#include <xcb/xcb.h> -#include <xcb/xcb_event.h> -#include <xcb/xcb_keysyms.h> -#if HAVE_XCB_ICCCM -# include <xcb/xcb_icccm.h> -#endif - -#define HAVE_STRUCT_TIMESPEC 1 - -#include "paths.h" -#include "safe_fifo.h" -#include "thread.h" - -#if HAVE_XCB_ICCCM -typedef struct atoms_t -{ - xcb_atom_t WM_DELETE_WINDOW; - xcb_atom_t WM_PROTOCOLS; -} atoms_t; -#endif - -typedef enum message_type_t -{ - MSG_QUIT, - MSG_DRAW, - MSG_SWAP, - MSG_CLEAR -} message_type_t; - -typedef struct message_t -{ - message_type_t type; -} message_t; - -static message_t quit_message = { MSG_QUIT }; -static message_t draw_message = { MSG_DRAW }; -static message_t swap_message = { MSG_SWAP }; -static message_t clear_message = { MSG_CLEAR }; - -typedef struct time_target_t time_target_t; - -typedef bool (* timer_callback_t)(void *data); - -struct time_target_t -{ - time_target_t *next; - struct timespec target; - unsigned long interval_ms; - void *data; - timer_callback_t callback; -}; - -typedef struct main_t -{ - xcb_connection_t *conn; - xcb_window_t wnd; - xcb_gcontext_t gc; - xcb_font_t font; -#if HAVE_XCB_ICCCM - atoms_t atoms; -#endif - safe_fifo_t *queue; - const char *state; - paths_t *paths; - time_target_t *first_timer; - bool save_queued; - bool redraw_queued; - unsigned int width, height; - uint32_t background[2], foreground[2]; - - /* State */ - bool working; - time_t last_time; - unsigned long total_min; -} main_t; - -static const char DEFAULT_STATE[] = "timer.state"; - -static const char FONT_NAME[] = "7x13"; - -static const unsigned long REDRAW_INTERVAL_MS = 36 * 1000; - -static bool handle_arguments(main_t *m, int argc, char **argv, int *exitcode); -static void draw(main_t *m); -static void *run_xcb(void *arg); -static bool load_state(main_t *m); -static bool save_state(main_t *m); -static void default_state(main_t *m); -static void swap(main_t *m); -static void clear(main_t *m); -static void queue_save(main_t *m); -static void add_timer(main_t *m, unsigned long interval_ms, - timer_callback_t callback, void *data); -static void insert_timer(main_t *m, time_target_t *target); -static void run_timer(main_t *m); -static bool redraw(void *arg); -static int timeval_cmp(const struct timespec* x, const struct timespec* y); - -int main(int argc, char **argv) -{ - main_t m; - xcb_screen_iterator_t iter; - xcb_screen_t *screen; - int screen_index; - uint32_t mask, values[2]; - xcb_void_cookie_t cookie[10]; - size_t cookies = 0; - int exitcode; - bool quit = false; - memset(&m, 0, sizeof(m)); - - if (!handle_arguments(&m, argc, argv, &exitcode)) - { - return exitcode; - } - - m.paths = paths_new(); - if (!m.paths) - { - fputs("Unable to allocate memory.\n", stderr); - return EXIT_FAILURE; - } - - if (!load_state(&m)) - { - fputs("Error loading old state.\n", stderr); - paths_unref(m.paths); - return EXIT_FAILURE; - } - - m.conn = xcb_connect(NULL, &screen_index); - if (!m.conn) - { - fputs("Unable to connect to X11 display.\n", stderr); - paths_unref(m.paths); - return EXIT_FAILURE; - } - -#if HAVE_XCB_ICCCM - { - xcb_intern_atom_cookie_t cookie[2]; - xcb_intern_atom_reply_t *reply; - xcb_generic_error_t *err; - - cookie[0] = xcb_intern_atom(m.conn, 0, strlen("WM_DELETE_WINDOW"), - "WM_DELETE_WINDOW");; - cookie[1] = xcb_intern_atom(m.conn, 0, strlen("WM_PROTOCOLS"), - "WM_PROTOCOLS");; - reply = xcb_intern_atom_reply(m.conn, cookie[0], &err); - if (!reply) - { - fprintf(stderr, "ICCCM init atoms failed\n"); - xcb_disconnect(m.conn); - paths_unref(m.paths); - return EXIT_FAILURE; - } - m.atoms.WM_DELETE_WINDOW = reply->atom; - free(reply); - reply = xcb_intern_atom_reply(m.conn, cookie[1], &err); - if (!reply) - { - fprintf(stderr, "ICCCM init atoms failed\n"); - xcb_disconnect(m.conn); - paths_unref(m.paths); - return EXIT_FAILURE; - } - m.atoms.WM_PROTOCOLS = reply->atom; - free(reply); - } -#endif - - iter = xcb_setup_roots_iterator(xcb_get_setup(m.conn)); - while (screen_index-- > 0) - { - xcb_screen_next(&iter); - } - - screen = iter.data; - - { - xcb_alloc_color_cookie_t req[4]; - xcb_alloc_color_reply_t* rep; - /* background, not working */ - req[0] = xcb_alloc_color(m.conn, screen->default_colormap, - 0x8000, 0x8000, 0x8000); - /* background, working */ - req[1] = xcb_alloc_color(m.conn, screen->default_colormap, - 0x8000, 0xffff, 0x8000); - /* foreground, not working */ - req[2] = xcb_alloc_color(m.conn, screen->default_colormap, - 0, 0, 0); - /* foreground, working */ - req[3] = xcb_alloc_color(m.conn, screen->default_colormap, - 0, 0, 0); - rep = xcb_alloc_color_reply(m.conn, req[0], 0); - m.background[0] = rep ? rep->pixel : screen->white_pixel; - free(rep); - rep = xcb_alloc_color_reply(m.conn, req[1], 0); - m.background[1] = rep ? rep->pixel : screen->white_pixel; - free(rep); - rep = xcb_alloc_color_reply(m.conn, req[2], 0); - m.foreground[0] = rep ? rep->pixel : screen->black_pixel; - free(rep); - rep = xcb_alloc_color_reply(m.conn, req[3], 0); - m.foreground[1] = rep ? rep->pixel : screen->black_pixel; - free(rep); - } - - m.font = xcb_generate_id(m.conn); - cookie[cookies++] = xcb_open_font_checked(m.conn, m.font, - sizeof(FONT_NAME) / - sizeof(FONT_NAME[0]), - FONT_NAME); - - mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; - values[0] = screen->black_pixel; - values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS | - XCB_EVENT_MASK_KEY_RELEASE; - - m.width = 100; - m.height = 50; - - m.wnd = xcb_generate_id(m.conn); - cookie[cookies++] = xcb_create_window_checked(m.conn, XCB_COPY_FROM_PARENT, - m.wnd, screen->root, - 0, 0, m.width, m.height, 0, - XCB_WINDOW_CLASS_INPUT_OUTPUT, - screen->root_visual, - mask, values); - - m.gc = xcb_generate_id(m.conn); - mask = XCB_GC_FONT; - values[0] = m.font; - cookie[cookies++] = xcb_create_gc_checked(m.conn, m.gc, m.wnd, - mask, values); - - cookie[cookies++] = xcb_map_window_checked(m.conn, m.wnd); - -#if HAVE_XCB_ICCCM - { - xcb_atom_t protocols[1]; - protocols[0] = m.atoms.WM_DELETE_WINDOW; - - xcb_icccm_set_wm_protocols(m.conn, m.wnd, m.atoms.WM_PROTOCOLS, - 1, protocols); - xcb_icccm_set_wm_name(m.conn, m.wnd, XCB_ATOM_STRING, 8, 5, "timer"); - xcb_icccm_set_wm_class(m.conn, m.wnd, 8, "jk.timer"); - } -#endif - - while (cookies--) - { - xcb_generic_error_t *error = xcb_request_check(m.conn, cookie[cookies]); - if (error) - { - fprintf(stderr, "Error: %d\n", error->error_code); - xcb_disconnect(m.conn); - paths_unref(m.paths); - return EXIT_FAILURE; - } - } - - xcb_flush(m.conn); - - m.queue = safe_fifo_new(); - - thread_new(run_xcb, &m); - - if (m.working) - { - add_timer(&m, REDRAW_INTERVAL_MS, redraw, &m); - m.redraw_queued = true; - } - - while (!quit) - { - message_t *msg; - if (m.first_timer) - { - struct timespec now; - thread_abstime(&now, 0); - if (timeval_cmp(&now, &m.first_timer->target) >= 0) - { - run_timer(&m); - msg = safe_fifo_trypop(m.queue); - } - else - { - msg = safe_fifo_timedpop(m.queue, &m.first_timer->target); - } - } - else - { - msg = safe_fifo_pop(m.queue); - } - if (msg) - { - switch (msg->type) - { - case MSG_QUIT: - quit = true; - break; - case MSG_DRAW: - draw(&m); - break; - case MSG_SWAP: - swap(&m); - break; - case MSG_CLEAR: - clear(&m); - break; - } - } - } - - if (m.save_queued) - { - save_state(&m); - } - - xcb_close_font(m.conn, m.font); - xcb_free_gc(m.conn, m.gc); - xcb_destroy_window(m.conn, m.wnd); - - xcb_disconnect(m.conn); - while (m.first_timer) - { - time_target_t *next = m.first_timer->next; - free(m.first_timer); - m.first_timer = next; - } - paths_unref(m.paths); - safe_fifo_unref(m.queue); - - return EXIT_SUCCESS; -} - -static void print_usage(void) -{ - fputs("Usage: timer [OPTION]...\n", stdout); - fputs("Timer is a timekeeping tool.\n", stdout); - fputs("\n", stdout); - fputs("Options:\n", stdout); - fputs(" -state FILE load state from FILE instead of default\n", stdout); - fputs(" -help display this information and exit\n", stdout); - fputs(" -version display version and exit\n", stdout); -} - -static void print_version(void) -{ - fputs("timer version " VERSION " written by Joel Klinghed\n", stdout); -} - -bool handle_arguments(main_t *m, int argc, char **argv, int *exitcode) -{ - bool usage = false, version = false, error = false; - int a; - for (a = 1; a < argc; a++) - { - if (argv[a][0] == '-') - { - if (strcmp(argv[a] + 1, "help") == 0) - { - usage = true; - break; - } - else if (strcmp(argv[a] + 1, "version") == 0) - { - version = true; - break; - } - else if (strcmp(argv[a] + 1, "state") == 0) - { - if (++a == argc) - { - error = true; - fputs("Option `state` expects an argument.\n", stderr); - break; - } - if (m->state) - { - error = true; - fputs("Option `state` given twice.\n", stderr); - break; - } - m->state = argv[a]; - } - else - { - error = true; - fprintf(stderr, "Unknown option: %s\n", argv[a] + 1); - break; - } - } - else - { - break; - } - } - if (!error && a < argc) - { - fputs("Too many arguments given.\n", stderr); - error = true; - } - if (usage) - { - print_usage(); - *exitcode = error ? EXIT_FAILURE : EXIT_SUCCESS; - return false; - } - if (error) - { - fputs("Try `timer -help` for usage.\n", stderr); - *exitcode = EXIT_FAILURE; - return false; - } - if (version) - { - print_version(); - *exitcode = EXIT_SUCCESS; - return false; - } - return true; -} - -void draw(main_t *m) -{ - xcb_rectangle_t r; - xcb_char2b_t *tmp; - uint32_t values[2]; - char text[50]; - int x, y, i, len; - xcb_query_text_extents_cookie_t cookie; - xcb_query_text_extents_reply_t *reply; - - if (m->working) - { - double diff = difftime(time(NULL), m->last_time); - diff /= 60.0 * 60.0; - - len = snprintf(text, sizeof(text), "%.2f (%.2f)", diff, - (double)m->total_min / 60.0 + diff); - } - else - { - len = snprintf(text, sizeof(text), "(%.2f)", - (double)m->total_min / 60.0); - } - if (len < 0 || len == sizeof(text)) - { - return; - } - tmp = malloc(len * sizeof(xcb_char2b_t)); - for (i = 0; i < len; i++) - { - tmp[i].byte1 = 0; - tmp[i].byte2 = text[i]; - } - - cookie = xcb_query_text_extents(m->conn, m->font, len, tmp); - - r.x = 0; - r.y = 0; - r.width = m->width; - r.height = m->height; - - values[0] = m->background[m->working ? 1 : 0]; - xcb_change_gc(m->conn, m->gc, XCB_GC_FOREGROUND, values); - xcb_poly_fill_rectangle(m->conn, m->wnd, m->gc, 1, &r); - - values[0] = m->foreground[m->working ? 1 : 0]; - values[1] = m->background[m->working ? 1 : 0]; - xcb_change_gc(m->conn, m->gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values); - - x = 0; - y = m->height / 2; - - reply = xcb_query_text_extents_reply(m->conn, cookie, NULL); - if (reply) - { - if (reply->overall_width < m->width) - { - x = (m->width - reply->overall_width) / 2; - } - else - { - x = 0; - } - y += reply->font_ascent / 2; - free(reply); - } - - xcb_image_text_16(m->conn, len, m->wnd, m->gc, x, y, tmp); - - xcb_flush(m->conn); - - free(tmp); -} - -static void send_message(main_t *m, message_t* msg) -{ - safe_fifo_push(m->queue, msg); -} - -void *run_xcb(void *arg) -{ - main_t *m = arg; - xcb_key_symbols_t *syms; - - syms = xcb_key_symbols_alloc(m->conn); - - for (;;) - { - bool done = false; - xcb_generic_event_t *event = xcb_wait_for_event(m->conn); - if (!event || xcb_connection_has_error(m->conn)) - { - free(event); - break; - } - switch (XCB_EVENT_RESPONSE_TYPE(event)) - { - case XCB_EXPOSE: - { - xcb_expose_event_t *e = (xcb_expose_event_t*)event; - if (e->count == 0) - { - send_message(m, &draw_message); - } - break; - } -#if HAVE_XCB_ICCCM - case XCB_CLIENT_MESSAGE: - { - xcb_client_message_event_t *e = (xcb_client_message_event_t*)event; - if (e->type == m->atoms.WM_PROTOCOLS && e->format == 32 && - e->data.data32[0] == m->atoms.WM_DELETE_WINDOW) - { - done = true; - } - break; - } -#endif - case XCB_BUTTON_PRESS: - { - xcb_button_press_event_t *e = (xcb_button_press_event_t*)event; - if (e->detail == 1) - { - send_message(m, &swap_message); - } - break; - } - case XCB_KEY_RELEASE: - { - xcb_key_press_event_t *e = (xcb_key_press_event_t*)event; - xcb_keysym_t sym = xcb_key_press_lookup_keysym(syms, e, 0); - if (sym == ' ') - { - send_message(m, &swap_message); - } - else if (sym == 'r' && (e->state & XCB_MOD_MASK_CONTROL)) - { - send_message(m, &clear_message); - } - break; - } - case XCB_MAPPING_NOTIFY: - { - xcb_mapping_notify_event_t *e = (xcb_mapping_notify_event_t*)event; - xcb_refresh_keyboard_mapping(syms, e); - break; - } - } - free(event); - if (done) - { - break; - } - } - xcb_key_symbols_free(syms); - send_message(m, &quit_message); - return NULL; -} - -bool load_state(main_t *m) -{ - FILE *fh; - char *tmp, *line; - const char *fname; - char buf[1024]; - char *end; - long l; - unsigned long ul; - struct tm tm; - if (m->state) - { - fname = m->state; - tmp = NULL; - } - else - { - const char *dir = paths_user_dir(m->paths, PATHS_DATA); - size_t dlen = strlen(dir); - tmp = malloc(dlen + 1 + sizeof(DEFAULT_STATE)); - if (!tmp) - { - fputs("Out of memory\n", stderr); - return false; - } - memcpy(tmp, dir, dlen); - tmp[dlen] = '/'; - memcpy(tmp + dlen + 1, DEFAULT_STATE, sizeof(DEFAULT_STATE)); - fname = tmp; - } - fh = fopen(fname, "rb"); - if (!fh) - { - if (errno == ENOENT) - { - default_state(m); - free(tmp); - return true; - } - fprintf(stderr, "Error loading state from %s: %s\n", fname, - strerror(errno)); - free(tmp); - return false; - } - - line = fgets(buf, sizeof(buf), fh); - if (!line) - { - fprintf(stderr, "Error reading state from %s: %s\n", fname, - strerror(errno)); - free(tmp); - return false; - } - - fclose(fh); - - end = NULL; - l = strtol(line, &end, 10); - if ((l != -1 && l != 1) || !end || *end != '|') - { - fprintf(stderr, "Invalid data in state %s: %s\n", fname, - strerror(errno)); - free(tmp); - return false; - } - - line = end + 1; - end = NULL; - ul = strtoul(line, &end, 10); - if (!end || *end != '|') - { - fprintf(stderr, "Invalid data in state %s: %s\n", fname, - strerror(errno)); - free(tmp); - return false; - } - - end = strptime(end + 1, "%Y-%m-%d %H:%M:%S", &tm); - if (!end) - { - fprintf(stderr, "Invalid data in state %s: %s\n", fname, - strerror(errno)); - free(tmp); - return false; - } - - m->working = l == 1; - m->last_time = timegm(&tm); - m->total_min = ul; - - free(tmp); - return true; -} - -bool save_state(main_t *m) -{ - paths_file_t *file = paths_write(m->paths, PATHS_DATA, - m->state ? m->state : DEFAULT_STATE, - PATHS_CREATE | PATHS_TRUNCATE, - 0600); - char buf[1024]; - size_t len2; - int pos, len; - m->save_queued = false; - - if (!file) - { - fprintf(stderr, "Failed to save state: %s\n", strerror(errno)); - return false; - } - - len = snprintf(buf, sizeof(buf), "%d|%lu|", - m->working ? 1 : -1, m->total_min); - if (len < 0 || len == sizeof(buf)) - { - fprintf(stderr, "Failed to save state: %s\n", strerror(errno)); - paths_file_abort(file); - return false; - } - len2 = strftime(buf + len, sizeof(buf) - len, "%Y-%m-%d %H:%M:%S", - gmtime(&m->last_time)); - if (len2 == 0 || len2 == sizeof(buf) - len) - { - fprintf(stderr, "Failed to save state: %s\n", strerror(errno)); - paths_file_abort(file); - return false; - } - len += len2; - pos = 0; - while (pos < len) - { - ssize_t got = paths_file_write(file, buf + pos, len - pos); - if (got <= 0) - { - fprintf(stderr, "Failed to save state: %s\n", strerror(errno)); - paths_file_abort(file); - return false; - } - pos += got; - } - - if (paths_file_close(file)) - { - fprintf(stderr, "Failed to finish saving state: %s\n", strerror(errno)); - paths_file_abort(file); - return false; - } - return true; -} - -void default_state(main_t *m) -{ - m->working = false; - m->last_time = time(NULL); - m->total_min = 0; -} - -void swap(main_t *m) -{ - if (m->working) - { - double diff = difftime(time(NULL), m->last_time); - m->total_min += round(diff / 60.0); - m->working = false; - } - else - { - m->working = true; - if (!m->redraw_queued) - { - add_timer(m, REDRAW_INTERVAL_MS, redraw, m); - m->redraw_queued = true; - } - } - - m->last_time = time(NULL); - - draw(m); - queue_save(m); -} - -void clear(main_t *m) -{ - if (m->working) - { - return; - } - - default_state(m); - draw(m); - queue_save(m); -} - -static bool do_save(void *arg) -{ - main_t *m = arg; - assert(m->save_queued); - save_state(m); - return false; -} - -void queue_save(main_t *m) -{ - if (m->save_queued) - { - return; - } - add_timer(m, 5000, do_save, m); - m->save_queued = true; -} - -void add_timer(main_t *m, unsigned long interval_ms, - timer_callback_t callback, void *data) -{ - time_target_t* tgt = calloc(1, sizeof(time_target_t)); - assert(interval_ms > 0); - tgt->interval_ms = interval_ms; - tgt->callback = callback; - tgt->data = data; - thread_abstime(&tgt->target, interval_ms); - insert_timer(m, tgt); -} - -void insert_timer(main_t* m, time_target_t *target) -{ - time_target_t *cur, *last; - assert(target->next == NULL); - if (!m->first_timer) - { - m->first_timer = target; - return; - } - last = NULL; - cur = m->first_timer; - while (cur) - { - if (timeval_cmp(&cur->target, &target->target) > 0) - { - target->next = cur; - if (last == NULL) - { - m->first_timer = target; - return; - } - else - { - break; - } - } - last = cur; - cur = last->next; - } - last->next = target; -} - -void run_timer(main_t *m) -{ - time_target_t *target = m->first_timer; - m->first_timer = target->next; - target->next = NULL; - if (target->callback(target->data)) - { - thread_abstime(&target->target, target->interval_ms); - insert_timer(m, target); - } - else - { - free(target); - } -} - -bool redraw(void *arg) -{ - main_t* m = arg; - assert(m->redraw_queued); - if (m->working) - { - draw(m); - return true; - } - m->redraw_queued = false; - return false; -} - -int timeval_cmp(const struct timespec* x, const struct timespec* y) -{ - assert(x && y); - if (x->tv_sec < y->tv_sec) - { - return -1; - } - else if (x->tv_sec == y->tv_sec) - { - if (x->tv_nsec < y->tv_nsec) - { - return -1; - } - else if (x->tv_nsec == y->tv_nsec) - { - return 0; - } - } - - return 1; -} diff --git a/src/paths.c b/src/paths.c deleted file mode 100644 index 1bdc4e3..0000000 --- a/src/paths.c +++ /dev/null @@ -1,1029 +0,0 @@ -#include "common.h" - -#include "paths.h" -#include "ref.h" -#include "thread.h" -#include "safe_fifo.h" -#include "strutil.h" - -#include <sys/file.h> -#include <sys/stat.h> -#include <dirent.h> -#include <errno.h> -#include <fcntl.h> -#include <glob.h> -#include <pwd.h> -#include <stdarg.h> -#include <stdio.h> -#include <string.h> -#include <unistd.h> - -#define SEARCH_CACHE_SIZE (2) - -typedef struct search_t -{ - const char* dir; - const char** dirs; - char* match; - const char* match_name; - bool plain; - paths_find_callback_t find_callback; - void* userdata; -} search_t; - -typedef struct searchargs_t -{ - bool quit; - safe_fifo_t* queue; - thread_lock_t* lock; - search_t* cache[SEARCH_CACHE_SIZE]; -} searchargs_t; - -struct paths_t -{ - REF_DECLARE(); - char* user_data; - char* user_config; - char* user_cache; - char* user_runtime; - char** search_data; - char** search_config; - - searchargs_t* search; -#ifdef DEBUG - thread_t* searcher; -#endif -}; - -static void free_list(char** list); -static void free_search_data(search_t* search); -static bool paths_init(paths_t* paths); -static char* build_path(paths_t* paths, paths_source_t source, ...); -static bool create_dir(paths_t* paths, paths_source_t source, - const char* addon, size_t addon_len); -static void* searcher(void* _args); -static search_t* get_search(searchargs_t* args); -static void put_search(searchargs_t* args, search_t* search); - -paths_t* paths_new(void) -{ - paths_t* paths = calloc(1, sizeof(paths_t)); - if (!paths) - { - return NULL; - } - if (!REF_INIT(paths)) - { - free(paths); - return NULL; - } - for (;;) - { - paths->search = calloc(1, sizeof(searchargs_t)); - if (!paths->search) - { - break; - } - paths->search->lock = thread_lock_new(); - if (!paths->search->lock) - { - break; - } - paths->search->queue = safe_fifo_new(); - if (!paths->search->queue) - { - break; - } - safe_fifo_ref(paths->search->queue); -#ifdef DEBUG - paths->searcher = thread_joinable_new(searcher, paths->search); - if (!paths->searcher) - { - safe_fifo_unref(paths->search->queue); - break; - } -#else - if (!thread_new(searcher, paths->search)) - { - safe_fifo_unref(paths->search->queue); - break; - } -#endif - if (!paths_init(paths)) - { - break; - } - return paths; - } - paths_unref(paths); - return NULL; -} - -void paths_ref(paths_t* paths) -{ - assert(paths); - REF_INC(paths); -} - -void paths_unref(paths_t* paths) -{ - if (paths && REF_DEC(paths)) - { - if (paths->search) - { - if (paths->search->queue) - { - search_t* search = get_search(paths->search); - paths->search->quit = true; - if (search) - { - memset(search, 0, sizeof(search_t)); - safe_fifo_push(paths->search->queue, search); - } - safe_fifo_unref(paths->search->queue); - } -#ifdef DEBUG - thread_join(paths->searcher); -#endif - } - free(paths->user_data); - free(paths->user_config); - free(paths->user_cache); - free(paths->user_runtime); - free_list(paths->search_data); - free_list(paths->search_config); - REF_FREE(paths); - free(paths); - } -} - -const char* paths_user_dir(paths_t* paths, paths_source_t source) -{ - assert(paths); - switch (source) - { - case PATHS_DATA: - return paths->user_data; - case PATHS_CONFIG: - return paths->user_config; - case PATHS_CACHE: - return paths->user_cache; - case PATHS_RUNTIME: - return paths->user_runtime; - } - assert(false); - return NULL; -} - -const char** paths_search_dirs(paths_t* paths, paths_source_t source) -{ - assert(paths); - switch (source) - { - case PATHS_DATA: - return (const char**)paths->search_data; - case PATHS_CONFIG: - return (const char**)paths->search_config; - case PATHS_CACHE: - case PATHS_RUNTIME: - return NULL; - } - assert(false); - return NULL; -} - -struct paths_file_t -{ - paths_source_t source; - char* path, * target; - int fd; -}; - -paths_file_t* paths_write(paths_t* paths, paths_source_t source, - const char* filename, unsigned int flags, ...) -{ - paths_file_t* file; - int f; - const char* subpath; - assert(filename); - if (source == PATHS_RUNTIME && !paths->user_runtime) - { - errno = ENOENT; - return NULL; - } - if (filename[0] == '/') - { - filename++; - } - subpath = strrchr(filename, '/'); - if (subpath) - { - if (!create_dir(paths, source, filename, subpath - filename)) - { - return NULL; - } - } - else - { - if (!create_dir(paths, source, NULL, 0)) - { - return NULL; - } - } - file = calloc(1, sizeof(paths_file_t)); - if (!file) - { - return NULL; - } - file->source = source; - if (source == PATHS_CONFIG && - (((flags & PATHS_CREATE) && (flags & PATHS_TRUNCATE)) || - ((flags & PATHS_CREATE) && (flags & PATHS_EXCLUSIVE)))) - { - file->target = build_path(paths, source, filename, NULL); - if (!file->target) - { - free(file); - return NULL; - } - file->path = build_path(paths, source, ".#", filename, NULL); - } - else - { - file->path = build_path(paths, source, filename, NULL); - } - if (!file->path) - { - free(file->target); - free(file); - return NULL; - } - f = O_WRONLY; - if ((flags & PATHS_APPEND)) - { - f |= O_APPEND; - } - if ((flags & PATHS_CREATE)) - { - f |= O_CREAT; - if ((flags & PATHS_EXCLUSIVE)) - { - f |= O_EXCL; - } - } - if ((flags & PATHS_TRUNCATE)) - { - f |= O_TRUNC; - } -#ifdef O_EXLOCK - f |= O_EXLOCK; -#endif - if ((f & O_CREAT)) - { - va_list args; - va_start(args, flags); - file->fd = open(file->path, f, va_arg(args, int)); - va_end(args); - } - else - { - file->fd = open(file->path, f); - } - if (file->fd < 0) - { - free(file->path); - free(file->target); - free(file); - return NULL; - } -#ifndef O_EXLOCK - for (;;) - { - if (flock(file->fd, LOCK_EX) == 0) - { - break; - } - if (errno != EINTR) - { - if ((flags & O_CREAT)) - { - unlink(file->path); - } - close(file->fd); - free(file->path); - free(file->target); - free(file); - return NULL; - } - } -#endif - return file; -} - -ssize_t paths_file_write(paths_file_t* file, - const void* data, size_t size) -{ - size_t pos = 0; - assert(file); - assert(size == 0 || data); - while (pos < size) - { - ssize_t ret = write(file->fd, data + pos, size - pos); - if (ret < 0) - { - if (errno == EAGAIN || errno == EWOULDBLOCK || - errno == EINTR) - { - /* Non fatal errors */ - if (pos > 0) - { - return pos; - } - } - return -1; - } - if (ret == 0) - { - return pos; - } - pos += ret; - } - return size; -} - -int paths_file_close(paths_file_t* file) -{ - assert(file); - if (close(file->fd)) - { - return -1; - } - file->fd = -1; - if (file->target) - { - size_t len = strlen(file->target); - char* tmp = malloc(len + 2); - int ret; - if (!tmp) - { - return -1; - } - memcpy(tmp, file->target, len); - memcpy(tmp + len, "~", 2); - ret = rename(file->target, tmp); - free(tmp); - if (ret && errno == ENOENT) - { - int olderrno = errno; - /* Check if rename failed because target doesn't exist */ - if (access(file->target, F_OK) == 0 || - errno != ENOENT) - { - /* Unable to create backup */ - errno = olderrno; - return -1; - } - } - if (rename(file->path, file->target)) - { - return -1; - } - free(file->target); - } - free(file->path); - free(file); - return 0; -} - -void paths_file_abort(paths_file_t* file) -{ - if (file) - { - if (file->fd >= 0) - { - close(file->fd); - } - unlink(file->path); - free(file->path); - free(file->target); - free(file); - } -} - -bool create_dir(paths_t* paths, paths_source_t source, const char* addon, - size_t addon_len) -{ - const char* dir = paths_user_dir(paths, source); - char* tmp = NULL; - assert(dir); - assert(addon || !addon_len); - if (addon_len) - { - size_t dlen = strlen(dir); - tmp = malloc(dlen + 1 + addon_len + 1); - if (!tmp) - { - return false; - } - memcpy(tmp, dir, dlen); - tmp[dlen++] = '/'; - memcpy(tmp + dlen, addon, addon_len); - tmp[dlen + addon_len] = '\0'; - dir = tmp; - } - if (mkdir(dir, 0700)) - { - free(tmp); - return errno == EEXIST; - } - free(tmp); - return true; -} - -void free_list(char** list) -{ - if (list) - { - char** i; - for (i = list; *i; i++) - { - free(*i); - } - free(list); - } -} - -typedef struct homedir_t -{ - char* path; - size_t len; -} homedir_t; - -static char* get_userdir(const char* envvar, homedir_t* homedir, - const char* addon); -static char** get_searchdirs(const char* envvar, const char* default_list); - -static bool valid_runtime(const char* path); - -bool paths_init(paths_t* paths) -{ - homedir_t homedir = { NULL, 0 }; - paths->user_data = get_userdir("XDG_DATA_HOME", &homedir, "/.local/share"); - paths->user_config = get_userdir("XDG_CONFIG_HOME", &homedir, "/.config"); - paths->user_cache = get_userdir("XDG_CACHE_HOME", &homedir, "/.cache"); - paths->user_runtime = get_userdir("XDG_RUNTIME_HOME", NULL, NULL); - free(homedir.path); - if (!paths->user_data || !paths->user_config || !paths->user_cache) - { - return false; - } - if (paths->user_runtime) - { - if (!valid_runtime(paths->user_runtime)) - { - free(paths->user_runtime); - paths->user_runtime = NULL; - } - } - paths->search_data = get_searchdirs("XDG_DATA_DIRS", - "/usr/local/share:/usr/share"); - if (!paths->search_data) - { - return false; - } - paths->search_config = get_searchdirs("XDG_CONFIG_DIRS", "/etc/xdg"); - if (!paths->search_config) - { - return false; - } - return true; -} - -static void cleanup_dir(char* path, size_t len) -{ - while (len > 1 && path[len - 1] == '/') - { - path[--len] = '\0'; - } -} - -static char* get_homedir(void) -{ - const char* data = getenv("HOME"); - size_t len; - char* ret; - if (data && *data) - { - len = strlen(data); - ret = malloc(len + 1); - if (ret) - { - memcpy(ret, data, len + 1); - cleanup_dir(ret, len); - } - return ret; - } - for (;;) - { - long size = sysconf(_SC_GETPW_R_SIZE_MAX); - char* buf; - struct passwd entry, *result = NULL; - assert(size != -1); - if (size == -1) - { - break; - } - buf = malloc(size); - if (!buf) - { - break; - } - if (getpwuid_r(getuid(), &entry, buf, size, &result) != 0) - { - /* Error getting entry */ - free(buf); - break; - } - if (!result) - { - /* No entry found */ - free(buf); - break; - } - len = strlen(entry.pw_dir); - if (len == 0) - { - free(buf); - break; - } - ret = malloc(len + 1); - if (!ret) - { - free(buf); - return NULL; - } - memcpy(ret, entry.pw_dir, len + 1); - free(buf); - cleanup_dir(ret, len); - return ret; - } - /* When all else fails */ - return strdup("/"); -} - -char* get_userdir(const char* envvar, homedir_t* homedir, const char* addon) -{ - const char* data = getenv(envvar); - size_t len; - char* ret; - assert(!addon || addon[0] == '/'); - if (data && *data) - { - len = strlen(data); - ret = malloc(len + 1); - if (ret) - { - memcpy(ret, data, len + 1); - cleanup_dir(ret, len); - } - return ret; - } - if (!homedir && !addon) - { - return NULL; - } - assert(homedir && addon); - if (!homedir->path) - { - homedir->path = get_homedir(); - if (!homedir->path) - { - return NULL; - } - homedir->len = strlen(homedir->path); - } - len = homedir->len + strlen(addon); - ret = malloc(len + 1); - if (ret) - { - memcpy(ret, homedir->path, homedir->len); - if (homedir->len > 1) - { - assert(homedir->path[homedir->len - 1] != '/'); - memcpy(ret + homedir->len, addon, (len - homedir->len) + 1); - } - else - { - assert(homedir->path[0] == '/'); - len--; - memcpy(ret + homedir->len, addon + 1, len - homedir->len); - ret[len] = '\0'; - } - } - return ret; -} - -static void cleanup_dirs(char** dirs) -{ - char** d; - for (d = dirs; *d; d++) - { - cleanup_dir(*d, strlen(*d)); - } -} - -static char** splitlist(const char* str) -{ - size_t count = 0, size = 2; - char** ret = calloc(size + 1, sizeof(char*)); - const char* last = str, * pos; - if (!ret) - { - return NULL; - } - for (;;) - { - size_t len; - pos = strchr(last, ':'); - if (!pos) - { - pos = last + strlen(last); - } - - if (count == size) - { - size_t ns = size * 2; - char** tmp = realloc(ret, (ns + 1) * sizeof(char*)); - if (!tmp) - { - free_list(ret); - return NULL; - } - memset(tmp + size, 0, (ns + 1 - size) * sizeof(char*)); - ret = tmp; - size = ns; - } - len = pos - last; - ret[count] = strdup_len(last, len); - if (!ret[count]) - { - free_list(ret); - return NULL; - } - count++; - - if (*pos) - { - last = pos + 1; - } - else - { - break; - } - } - assert(!ret[count]); - return ret; -} - -char** get_searchdirs(const char* envvar, const char* default_list) -{ - const char* data = getenv(envvar); - char** ret; - bool cleanup; - if (data && *data) - { - ret = splitlist(data); - cleanup = true; - } - else - { - ret = splitlist(default_list); - cleanup = false; - } - if (!ret) - { - return NULL; - } - if (cleanup) - { - cleanup_dirs(ret); - } - return ret; -} - -bool valid_runtime(const char* path) -{ - struct stat buf; - if (stat(path, &buf) != 0) - { - return false; - } - if (buf.st_uid != getuid()) - { - return false; - } - if ((buf.st_mode & 0777) != 0700) - { - return false; - } - return true; -} - -char* build_path(paths_t* paths, paths_source_t source, ...) -{ - va_list args; - bool first = true; - dynstr_t* ret; - const char* base = paths_user_dir(paths, source), * part; - assert(base); - ret = dynstr_new_str(base); - if (!ret) - { - return NULL; - } - va_start(args, source); - while ((part = va_arg(args, const char*))) - { - if (first) - { - dynstr_appendc(ret, '/'); - first = false; - } - dynstr_append(ret, part); - } - return dynstr_done(ret); -} - -static bool need_glob(char* match) -{ - const char* magic_chars = "*?[]{}"; - char* s; - bool need_unescape = false; - for (s = match; *s; s++) - { - if (*s == '\\') - { - s++; - need_unescape = true; - } - else if (strchr(magic_chars, *s)) - { - return true; - } - } - if (need_unescape) - { - unescape(match); - } - return false; -} - -void paths_find(paths_t* paths, paths_source_t source, - const char* match, paths_find_callback_t find_callback, - void* userdata) -{ - search_t* search; - search = get_search(paths->search); - search->dir = paths_user_dir(paths, source); - search->dirs = paths_search_dirs(paths, source); - search->match = strdup(match); - search->find_callback = find_callback; - search->userdata = userdata; - if (!search->match) - { - find_callback(userdata, NULL); - put_search(paths->search, search); - return; - } - if (*search->match == '/') - { - size_t len = strlen(search->match); - memmove(search->match, search->match + 1, len); - } - if (!*search->match) - { - find_callback(userdata, NULL); - put_search(paths->search, search); - return; - } - search->plain = !need_glob(search->match); - if (search->plain) - { - search->match_name = strrchr(search->match, '/'); - if (search->match_name) - { - search->match_name++; - } - else - { - search->match_name = search->match; - } - } - safe_fifo_push(paths->search->queue, search); -} - -search_t* get_search(searchargs_t* args) -{ - size_t i; - thread_lock_lock(args->lock); - for (i = 0; i < SEARCH_CACHE_SIZE; i++) - { - if (args->cache[i]) - { - search_t* ret = args->cache[i]; - args->cache[i] = NULL; - thread_lock_unlock(args->lock); - return ret; - } - } - thread_lock_unlock(args->lock); - return malloc(sizeof(search_t)); -} - -void put_search(searchargs_t* args, search_t* search) -{ - size_t i; - thread_lock_lock(args->lock); - for (i = 0; i < SEARCH_CACHE_SIZE; i++) - { - if (!args->cache[i]) - { - args->cache[i] = search; - free_search_data(search); - thread_lock_unlock(args->lock); - return; - } - } - thread_lock_unlock(args->lock); - free_search_data(search); - free(search); -} - -void free_search_data(search_t* search) -{ - free(search->match); -} - -static bool search_dir(search_t* search, const char* dir) -{ - bool canceled = false; - if (search->plain) - { - DIR* dh; - struct dirent* d; - char* path = NULL; - size_t dirlen = strlen(dir); - size_t matchlen = strlen(search->match); - if (search->match == search->match_name) - { - path = malloc(dirlen + 1 + matchlen + 1); - if (!path) - { - return false; - } - memcpy(path, dir, dirlen + 1); - } - else - { - size_t subdirlen = search->match_name - 1 - search->match; - matchlen = strlen(search->match_name); - path = malloc(dirlen + 1 + subdirlen + 1 + matchlen + 1); - assert(search->match_name > search->match); - assert(search->match_name[-1] == '/'); - if (!path) - { - return false; - } - memcpy(path, dir, dirlen); - path[dirlen++] = '/'; - memcpy(path + dirlen, search->match, subdirlen); - dirlen += subdirlen; - path[dirlen] = '\0'; - } - dh = opendir(path); - if (!dh) - { - free(path); - return false; - } - path[dirlen] = '/'; - while ((d = readdir(dh))) - { - if (strcmp(d->d_name, search->match_name) == 0) - { - memcpy(path + dirlen + 1, search->match_name, matchlen + 1); - if (!search->find_callback(search->userdata, path)) - { - canceled = true; - break; - } - } - } - closedir(dh); - free(path); - } - else - { - glob_t data; - dynstr_t* str; - char* pattern; - str = dynstr_new_str(dir); - if (!str) - { - return false; - } - if (!dynstr_escape(str, "*?[]{}") || !dynstr_appendc(str, '/') || - !dynstr_append(str, search->match)) - { - dynstr_free(str); - return false; - } - pattern = dynstr_done(str); - memset(&data, 0, sizeof(glob_t)); - if (glob(pattern, GLOB_NOSORT, NULL, &data) == 0) - { - size_t i; - for (i = 0; i < data.gl_pathc; i++) - { - if (!search->find_callback(search->userdata, data.gl_pathv[i])) - { - canceled = true; - break; - } - } - globfree(&data); - } - free(pattern); - } - return canceled; -} - -void* searcher(void* _args) -{ - searchargs_t* args = _args; - size_t i; - - while (!args->quit) - { - search_t* search = safe_fifo_pop(args->queue); - bool canceled = false; - if (!search->match) - { - free(search); - assert(args->quit); - break; - } - do - { - if (search->dir) - { - if (args->quit || - (canceled = search_dir(search, search->dir))) - { - break; - } - } - if (search->dirs) - { - const char** dir; - for (dir = search->dirs; *dir; dir++) - { - if (args->quit || - (canceled = search_dir(search, *dir))) - { - break; - } - } - } - } while (false); - if (!canceled) - { - search->find_callback(search->userdata, NULL); - } - put_search(args, search); - } - - /* Free all items in the queue */ - for (;;) - { - search_t* search = safe_fifo_trypop(args->queue); - if (!search) - { - break; - } - put_search(args, search); - } - - safe_fifo_unref(args->queue); - thread_lock_free(args->lock); - for (i = 0; i < SEARCH_CACHE_SIZE; i++) - { - if (args->cache[i]) - { - free_search_data(args->cache[i]); - free(args->cache[i]); - } - } - free(args); - return NULL; -} diff --git a/src/paths.h b/src/paths.h deleted file mode 100644 index 8be56f8..0000000 --- a/src/paths.h +++ /dev/null @@ -1,261 +0,0 @@ -/** - * \file paths.h - * XDG Directory Specification implementation - */ - -#ifndef PATHS_H -#define PATHS_H - -/** - * Opaque type for paths - */ -typedef struct paths_t paths_t; - -/** - * Opaque type for a currently open file - */ -typedef struct paths_file_t paths_file_t; - -/** - * The different sources or directories if you will that are in the - * specification. - */ -typedef enum paths_source_t -{ - /** directory for data files */ - PATHS_DATA, - /** directory for config files */ - PATHS_CONFIG, - /** directory for cache files */ - PATHS_CACHE, - /** directory for runtime files */ - PATHS_RUNTIME, -} paths_source_t; - -/** Do not block write calls */ -#define PATHS_NONBLOCK (0x01) -/** Append on each write */ -#define PATHS_APPEND (0x02) -/** Create file if needed */ -#define PATHS_CREATE (0x04) -/** Truncate file when opening */ -#define PATHS_TRUNCATE (0x08) -/** If PATHS_CREATE is set, give error if file already exists */ -#define PATHS_EXCLUSIVE (0x10) - -/** - * Create a new paths. - * Initializes the list of directories from enviroment variables. - * Returned object has a reference count of one. - * Threadsafe. - * Returns NULL in case of allocation errors. - * @return a new paths - */ -MALLOC paths_t* paths_new(void); - -/** - * Increase the reference count for paths. - * Threadsafe. - * @param paths paths object, may not be NULL - */ -NONULL void paths_ref(paths_t* paths); - -/** - * Decrease the reference count for paths and free if it reached zero. - * Threadsafe. - * @param paths paths object, may be NULL - */ -void paths_unref(paths_t* paths); - -/** - * Get single directory where user-specific files should be written. - * May only return NULL for PATHS_RUNTIME all other sources are guaranteed to - * return a non-NULL string. They may however return non-existant paths. - * PATHS_RUNTIME will return NULL if no such path was found as it has no - * default. - * If you wish paths to be created if missing, please use paths_write(). - * Threadsafe. - * @param paths paths object, may not be NULL - * @param source requested directory - * @return absolute path to directory or NULL - */ -NONULL const char* paths_user_dir(paths_t* paths, paths_source_t source); - -/** - * Get preference ordered list of directories to search for files. - * All paths are absolute and NULL terminated. The list itself is NULL - * terminated and may never be empty. Path returned by paths_user_dir() for - * the same source is not included. - * Will return NULL for PATHS_CACHE and PATHS_RUNTIME as they are only - * user-specific writable directories. - * Threadsafe. - * @param paths paths object, may not be NULL - * @param source requested directory - * @return NULL terminated list of absolute paths to search or NULL - */ -NONULL const char** paths_search_dirs(paths_t* paths, paths_source_t source); - -/** - * Callback called with the result(s) from paths_find(). May be called from - * any thread. Return false to cancel the search, true to continue until all - * has been found. filename will be NULL when no more matches was found. - * The filename pointer is only valid until the callback has returned. - * @param userdata userdata given to paths_find() - * @param filenamme found file or NULL if no more results - * @return true to continue the search, false to not */ -typedef bool (* paths_find_callback_t)(void* userdata, const char* filename); - -/** - * Asynchronous search for matching files in directories for source. - * Returned in preference order, first paths_user_dir() and then - * paths_search_dirs(). - * The callback is always called at least once. - * Match may use *, ?, [] and {} glob matches. It may also contain / characters - * to search in subdirectories - * Threadsafe. - * @param paths paths object, may not be NULL - * @param source requested directory - * @param match glob pattern to search with. may not be NULL - * @param find_callback callback to call when finding results, may not be NULL - * @param userdata argument to callback, may be NULL - */ -NONULL_ARGS(1, 3, 4) -void paths_find(paths_t* paths, paths_source_t source, const char* match, - paths_find_callback_t find_callback, void* userdata); - -/** - * Synchronous setup to create file in directory. - * Returns NULL in case of error, see errno for details. - * Depending on source and convention, the actual file may not be created with - * the given name until paths_file_close(). - * The file will eventually always end up in paths_user_dir() for directory. - * If any part of the target path is missing it will be created if possible. - * If flags contains PATHS_CREATE then the 5th argument mode_t mode must be - * given, just as for open(). - * Threadsafe but what happens if two threads at the same time opens the same - * target file is up to the platforms locking mechanism. - * The returned paths_file_t is not threadsafe. - * @param paths paths object, may not be NULL - * @param source target directoru - * @param filename relative path of target file, may not be NULL - * @param flags flags - * @param ... mode if PATHS_CREATE is included in flags - * @return a file reference or NULL - */ -NONULL paths_file_t* paths_write(paths_t* paths, paths_source_t source, - const char* filename, unsigned int flags, - ...); - -/** - * Write data to file. - * Works just as libc write(). - * Not threadsafe, - * the paths_file_t must only be accessed by one thread at a time. - * @param file target file, may not be NULL - * @param data data to write, may be NULL if size is zero - * @param size the maximum number of bytes to read from data - * @return number of bytes written to target - */ -NONULL_ARGS(1) -ssize_t paths_file_write(paths_file_t* file, const void* data, size_t size); - -/** - * Close and finish saving file. - * If the file was saved successfully, the paths_file_t object is freed. - * If the save failed in any way, the object is not freed so you must call - * paths_file_abort() to finish up (or call paths_file_close() again and - * hope for better luck this time). - * Not threadsafe, - * the paths_file_t must only be accessed by one thread at a time. - * @param file target file, may not be NULL - * @return zero on success, non-zero in case of error - */ -NONULL int paths_file_close(paths_file_t* file); - -/** - * Abort saving the file. The target file is removed unless the original version - * before paths_write() could be restored. - * Not threadsafe, - * the paths_file_t must only be accessed by one thread at a time. - * @param file target file, may be NULL - */ -void paths_file_abort(paths_file_t* file); - -/** - * Get absolute path to directory where user-specific data files should be - * written. Returned directory may not exist. - * Threadsafe. - * @param paths paths object, may not be NULL - * @return absolute path to data directory - */ -static inline NONULL const char* paths_user_data(paths_t* paths) -{ - return paths_user_dir(paths, PATHS_DATA); -} - -/** - * Get absolute path to directory where user-specific config files should be - * written. Returned directory may not exist. - * Threadsafe. - * @param paths paths object, may not be NULL - * @return absolute path to config directory - */ -static inline NONULL const char* paths_user_config(paths_t* paths) -{ - return paths_user_dir(paths, PATHS_CONFIG); -} - -/** - * Get absolute path to directory where user-specific cache files should be - * written. Returned directory may not exist. - * Threadsafe. - * @param paths paths object, may not be NULL - * @return absolute path to cache directory - */ -static inline NONULL const char* paths_user_cache(paths_t* paths) -{ - return paths_user_dir(paths, PATHS_CACHE); -} - -/** - * Get absolute path to directory where user-specific runtime files should be - * written. - * If not defined in enviroment or not valid NULL is returned. - * Threadsafe. - * @param paths paths object, may not be NULL - * @return absolute path to runtime directory or NULL - */ -static inline NONULL const char* paths_user_runtime(paths_t* paths) -{ - return paths_user_dir(paths, PATHS_RUNTIME); -} - -/** - * Get preference ordered list of directories to search for data files. - * All paths are absolute and NULL terminated. The list itself is NULL - * terminated and may never be empty. Path returned by paths_user_data() - * is not included. - * Threadsafe. - * @param paths paths object, may not be NULL - * @return NULL terminated list of absolute paths to search - */ -static inline NONULL const char** paths_data(paths_t* paths) -{ - return paths_search_dirs(paths, PATHS_DATA); -} - -/** - * Get preference ordered list of directories to search for config files. - * All paths are absolute and NULL terminated. The list itself is NULL - * terminated and may never be empty. Path returned by paths_user_config() - * is not included. - * Threadsafe. - * @param paths paths object, may not be NULL - * @return NULL terminated list of absolute paths to search - */ -static inline NONULL const char** paths_config(paths_t* paths) -{ - return paths_search_dirs(paths, PATHS_CONFIG); -} - -#endif /* PATHS_H */ diff --git a/src/ref.h b/src/ref.h deleted file mode 100644 index ea36ec7..0000000 --- a/src/ref.h +++ /dev/null @@ -1,95 +0,0 @@ -/** - * \file ref.h - * Thread-safe reference counter. - */ - -#ifndef REF_H -#define REF_H - -#if __GNUC__ >= 4 -# define REF_DECLARE() volatile int _ref -# define REF_INIT(ptr) \ - ((ptr)->_ref = 1) -# define REF_FREE(ptr) -# define REF_INC(ptr) \ - __sync_add_and_fetch(&((ptr)->_ref), 1) -# define REF_DEC(ptr) \ - (__sync_sub_and_fetch(&((ptr)->_ref), 1) <= 0) - -#else -# warning Unsupported compiler, horrible fallback threadsafe reference counters - -# include "thread.h" - -/** - * Declare reference counter. Use in struct declaration. - */ -# define REF_DECLARE() int _ref; thread_lock_t* _reflock -/** - * Initialize reference counter. Must be first MACRO used. - * @param ptr pointer to the struct, may not be NULL - * @return false in case of allocation errors - */ -# define REF_INIT(ptr) \ - _ref_init(&((ptr)->_ref), &((ptr)->_reflock)) -/** - * Free reference counter. - * @param ptr pointer to the struct, may not be NULL - */ -# define REF_FREE(ptr) thread_lock_free((ptr)->_reflock) -/** - * Increase reference counter. - * @param ptr pointer to the struct, may not be NULL - */ -# define REF_INC(ptr) _ref_inc(&((ptr)->_ref), (ptr)->_reflock) -/** - * Decrease reference counter. - * @param ptr pointer to the struct, may not be NULL - * @return true if reference counter now is zero - */ -# define REF_DEC(ptr) _ref_dec(&((ptr)->_ref), (ptr)->_reflock) - -/** - * Fallback implementation of REF_INIT. - * @param value pointer to reference counter, may not be NULL - * @param lock pointer to reference counter lock, may not be NULL - * @return true if both pointers was initialized correctly - */ -static inline NONULL bool _ref_init(int* value, thread_lock_t** lock) -{ - assert(value); - assert(lock); - *value = 1; - *lock = thread_lock_new(); - return *lock; -} - -/** - * Fallback implementation of REF_INC. - * @param value pointer to reference counter, may not be NULL - * @param lock reference counter lock, may not be NULL - */ -static inline NONULL void _ref_inc(int* value, thread_lock_t* lock) -{ - thread_lock_lock(lock); - (*value)++; - thread_lock_unlock(lock); -} - -/** - * Fallback implementation of REF_DEC. - * @param value pointer to reference counter, may not be NULL - * @param lock reference counter lock, may not be NULL - * @return true if reference counter was decreased to 0 - */ -static inline NONULL bool _ref_dec(int* value, thread_lock_t* lock) -{ - bool ret; - thread_lock_lock(lock); - ret = --(*value) == 0; - thread_lock_unlock(lock); - return ret; -} -#endif - -#endif /* REF_H */ diff --git a/src/safe_fifo.c b/src/safe_fifo.c deleted file mode 100644 index a294d1c..0000000 --- a/src/safe_fifo.c +++ /dev/null @@ -1,151 +0,0 @@ -#include "common.h" - -#include "safe_fifo.h" -#include "thread.h" -#include "ref.h" - -#include <string.h> - -struct safe_fifo_t -{ - REF_DECLARE(); - thread_lock_t* lock; - thread_cond_t* empty; - size_t pos, fill, size; - void** data; -}; - -safe_fifo_t* safe_fifo_new(void) -{ - safe_fifo_t* fifo = calloc(1, sizeof(safe_fifo_t)); - if (fifo) - { - if (!REF_INIT(fifo)) - { - free(fifo); - return NULL; - } - fifo->lock = thread_lock_new(); - fifo->empty = thread_cond_new(); - if (!fifo->lock || !fifo->empty) - { - thread_lock_free(fifo->lock); - thread_cond_free(fifo->empty); - free(fifo); - return NULL; - } - } - return fifo; -} - -void safe_fifo_ref(safe_fifo_t* fifo) -{ - assert(fifo); - REF_INC(fifo); -} - -void safe_fifo_unref(safe_fifo_t* fifo) -{ - if (fifo && REF_DEC(fifo)) - { - thread_cond_free(fifo->empty); - thread_lock_free(fifo->lock); - free(fifo->data); - REF_FREE(fifo); - free(fifo); - } -} - -void safe_fifo_push(safe_fifo_t* fifo, void* item) -{ - thread_lock_lock(fifo->lock); - do - { - if (fifo->pos > 0) - { - fifo->fill -= fifo->pos; - memmove(fifo->data, fifo->data + fifo->pos, - fifo->fill * sizeof(void*)); - fifo->pos = 0; - } - if (fifo->fill == fifo->size) - { - size_t ns = MAX(4, fifo->size * 2); - void** tmp = realloc(fifo->data, ns * sizeof(void*)); - if (!tmp) - { - break; - } - fifo->size = ns; - fifo->data = tmp; - } - fifo->data[fifo->fill++] = item; - } while (false); - thread_cond_signal(fifo->empty); - thread_lock_unlock(fifo->lock); -} - -void* safe_fifo_pop(safe_fifo_t* fifo) -{ - void* ret; - assert(fifo); - thread_lock_lock(fifo->lock); - for (;;) - { - if (fifo->fill > 0) - { - ret = fifo->data[fifo->pos++]; - if (fifo->pos == fifo->fill) - { - fifo->pos = fifo->fill = 0; - } - break; - } - thread_cond_wait(fifo->empty, fifo->lock); - } - thread_lock_unlock(fifo->lock); - return ret; -} - -void* safe_fifo_trypop(safe_fifo_t* fifo) -{ - void* ret = NULL; - assert(fifo); - thread_lock_lock(fifo->lock); - if (fifo->fill > 0) - { - ret = fifo->data[fifo->pos++]; - if (fifo->pos == fifo->fill) - { - fifo->pos = fifo->fill = 0; - } - } - thread_lock_unlock(fifo->lock); - return ret; -} - -void* safe_fifo_timedpop(safe_fifo_t* fifo, const struct timespec* abstime) -{ - void* ret; - assert(fifo); - thread_lock_lock(fifo->lock); - for (;;) - { - if (fifo->fill > 0) - { - ret = fifo->data[fifo->pos++]; - if (fifo->pos == fifo->fill) - { - fifo->pos = fifo->fill = 0; - } - break; - } - if (!thread_cond_timedwait(fifo->empty, fifo->lock, abstime)) - { - ret = NULL; - break; - } - } - thread_lock_unlock(fifo->lock); - return ret; -} diff --git a/src/safe_fifo.h b/src/safe_fifo.h deleted file mode 100644 index 4644c33..0000000 --- a/src/safe_fifo.h +++ /dev/null @@ -1,71 +0,0 @@ -/** - * \file safe_fifo.h - * Thread-safe FIFO queue. - */ - -#ifndef SAFE_FIFO_H -#define SAFE_FIFO_H - -#include "timespec.h" - -/** - * Opaque type describing a thread-safe FIFO queue - */ -typedef struct safe_fifo_t safe_fifo_t; - -/** - * Create a new queue. - * The new queue will have a reference count of one. - * Returns NULL in case of allocation errors. - * @return a new queue - */ -MALLOC safe_fifo_t* safe_fifo_new(void); - -/** - * Increase the reference count on the queue. - * @param fifo queue, may not be NULL - */ -NONULL void safe_fifo_ref(safe_fifo_t* fifo); - -/** - * Decrease the reference count on the queue and free if it goes down to zero. - * @param fifo queue, may be NULL - */ -void safe_fifo_unref(safe_fifo_t* fifo); - -/** - * Push an item on the queue. - * Will never block. In case of allocation errors, new items are ignored. - * @param fifo queue, may not be NULL - * @param item pointer to add to queue, may not be NULL - */ -NONULL void safe_fifo_push(safe_fifo_t* fifo, void* item); - -/** - * Pop an item from the queue. - * Will block until there is an item available. - * @param fifo queue, may not be NULL - * @return oldest item pushed onto the queue - */ -NONULL void* safe_fifo_pop(safe_fifo_t* fifo); - -/** - * Try to pop an item from the queue. - * Will not wait until there is an item available. - * @param fifo queue, may not be NULL - * @return oldest item in queue if any otherwise NULL - */ -NONULL void* safe_fifo_trypop(safe_fifo_t* fifo); - -/** - * Try to pop an item from the queue. - * Waits until abstime has occurred before giving up and returning NULL. - * Use thread_abstime() to fill out the abstime. - * @param fifo queue, may not be NULL - * @param abstime absolute time when to give up - * @return oldest item in queue if any otherwise NULL - */ -NONULL void* safe_fifo_timedpop(safe_fifo_t* fifo, - const struct timespec *abstime); - -#endif /* SAFE_FIFO_H */ diff --git a/src/strutil.c b/src/strutil.c deleted file mode 100644 index 4502b83..0000000 --- a/src/strutil.c +++ /dev/null @@ -1,123 +0,0 @@ -#include "common.h" - -#include "strutil.h" - -#include <string.h> - -char* strdup_len(const char* src, size_t len) -{ - char* ret = malloc(len + 1); - if (ret) - { - memcpy(ret, src, len); - ret[len] = '\0'; - } - return ret; -} - -static inline bool escaped(char* start, char* cur) -{ - return cur > start && cur[-1] == '\\' && !escaped(start, cur - 1); -} - -void unescape(char* str) -{ - char* end = str + strlen(str); - char* s; - for (s = end - 1; s >= str; s--) - { - if (*s == '\\' && s < end - 1 && !escaped(str, s)) - { - end--; - memmove(s, s + 1, end - s); - } - } - *end = '\0'; -} - -char* escape(const char* str, const char* chars) -{ - dynstr_t* d = dynstr_new_str(str); - if (d) - { - if (dynstr_escape(d, chars)) - { - return dynstr_done(d); - } - dynstr_free(d); - } - return NULL; -} - -bool dynstr_escape(dynstr_t* str, const char* chars) -{ - char* s; - for (s = str->data + str->len - 1; s >= str->data; s--) - { - if (*s == '\\' || strchr(chars, *s)) - { - size_t pos = s - str->data; - if (!dynstr_appendc(str, '/')) - { - return false; - } - s = str->data + pos; - memmove(s + 1, s, str->len - (pos + 1)); - *s = '\\'; - } - } - return true; -} - -static void free_list(char** list, size_t count) -{ - char** l; - for (l = list; count-- > 0; l++) - { - free(*l); - } - free(list); -} - -char** split_len(const char* start, const char* end, char delim) -{ - char** ret; - size_t count = 0, size = 2; - assert(start <= end); - ret = malloc((size + 1) * sizeof(char*)); - if (ret) - { - const char* last = start; - for (;;) - { - const char* p; - for (p = last; p < end && *p != delim; p++); - if (count == size) - { - size_t ns = size * 2; - char** tmp = realloc(ret, (ns + 1) * sizeof(char*)); - if (!tmp) - { - free_list(ret, count); - return NULL; - } - size = ns; - ret = tmp; - } - ret[count] = strdup_len(last, p - last); - if (!ret[count]) - { - free_list(ret, count); - return NULL; - } - count++; - if (p == end) - { - break; - } - last = p + 1; - } - ret[count] = NULL; - } - return ret; -} diff --git a/src/strutil.h b/src/strutil.h deleted file mode 100644 index f4dea54..0000000 --- a/src/strutil.h +++ /dev/null @@ -1,81 +0,0 @@ -/** - * \file strutil.h - * A collection of string utilities. - */ - -#ifndef STRUTIL_H -#define STRUTIL_H - -#include "dynstr.h" - -#include <string.h> - -/** - * Allocate a string initialized with the len first bytes of src. - * Similar to strndup but it doesn't check for '\0' in src. - * Returns NULL in case of allocation errors. - * @param src string to copy len bytes from, may not be NULL - * @param len bytes to copy from src - * @return newly allocate null-terminated string - */ -MALLOC NONULL char* strdup_len(const char* src, size_t len); - -/** - * Unescape (remove backslashes from) a null-terminated string inline. - * Does not have any special handling for sequences like \\n, \\t or \\0. - * @param str null-terminated string to remove backslashes from, may not be NULL - */ -NONULL void unescape(char* str); - -/** - * Escape all listed characters if they appear in string with backslash. - * Also escapes backslash. - * Returns NULL in case of allocation errors. - * @param str null-terminated string to escape characters in if needed, - * may not be NULL - * @param chars null-terminated string containing all characters that need to - * be escaped, may not be NULL - * @return newly allocated null-terminated copy of string with - * characters escaped - */ -MALLOC NONULL char* escape(const char* str, const char* chars); - -/** - * Escape all listed characters if they appear in the dynamic string with - * backslash inline. Also escapes backslash. - * @param str dynamic string to escape characters in if needed, - * may not be NULL - * @param chars null-terminated string containing all characters that need to - * be escaped, may not be NULL - * @return false in case of allocation errors - */ -NONULL bool dynstr_escape(dynstr_t* str, const char* chars); - -/** - * Split string by delimiter. - * Returned list will always contain at least one item and will be - * null-terminated. - * Returns NULL in case of allocation errors. - * @param start start of string to split by delimiter - * @param end end of string to split by delimiter - * @param delim delimiter to use - * @return null-terminated list of strings or NULL - */ -NONULL char** split_len(const char* start, const char* end, char delim); - -/** - * Split string by delimiter. - * Returned list will always contain at least one item and will be - * null-terminated. - * Returns NULL in case of allocation errors. - * @param str string to split by delimiter - * @param delim delimiter to use - * @return null-terminated list of strings or NULL - */ -NONULL static inline char** split(const char* str, char delim) -{ - assert(str); - return split_len(str, str + strlen(str), delim); -} - -#endif /* STRUTIL_H */ diff --git a/src/thread-pthread.c b/src/thread-pthread.c deleted file mode 100644 index 6e31cf9..0000000 --- a/src/thread-pthread.c +++ /dev/null @@ -1,343 +0,0 @@ -#include "common.h" - -#include "thread.h" - -#include <errno.h> -#include <pthread.h> - -#if !HAVE_PTHREAD_YIELD -#include <sched.h> -#endif - -#if !HAVE_CLOCK_GETTIME -# include <sys/time.h> -#endif - -#ifdef DEBUG -#define ABORTFAIL(x) \ - do { \ - if ((x)) abort(); \ - } while (false) -#else -#define ABORTFAIL(x) (x) -#endif - -bool thread_new(thread_run_t run, void* userdata) -{ - pthread_t thread; - pthread_attr_t attr; - assert(run); - if (pthread_attr_init(&attr)) - { - return NULL; - } - if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) - { - pthread_attr_destroy(&attr); - return NULL; - } - if (pthread_create(&thread, &attr, run, userdata)) - { - pthread_attr_destroy(&attr); - return false; - } - pthread_attr_destroy(&attr); - return true; -} - -struct thread_t -{ - pthread_t thread; -}; - -thread_t* thread_joinable_new(thread_run_t run, void* userdata) -{ - thread_t* ret = malloc(sizeof(thread_t)); - pthread_attr_t attr; - assert(run); - if (!ret) - { - return NULL; - } - if (pthread_attr_init(&attr)) - { - free(ret); - return NULL; - } - if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE)) - { - pthread_attr_destroy(&attr); - free(ret); - return NULL; - } - if (pthread_create(&ret->thread, &attr, run, userdata)) - { - pthread_attr_destroy(&attr); - free(ret); - return NULL; - } - pthread_attr_destroy(&attr); - return ret; -} - -void* thread_join(thread_t* thread) -{ - void* ret; - assert(thread); - ABORTFAIL(pthread_join(thread->thread, &ret)); - free(thread); - return ret; -} - -struct thread_id_t -{ - pthread_t thread; -}; - -thread_id_t* thread_id_new(thread_t* thread) -{ - thread_id_t* id = malloc(sizeof(thread_id_t)); - if (id) - { - id->thread = thread ? thread->thread : pthread_self(); - } - return id; -} - -void thread_id_free(thread_id_t* id) -{ - free(id); -} - -bool thread_id_is_current(thread_id_t* id) -{ - assert(id); - return id->thread == pthread_self(); -} - -void thread_yield(void) -{ -#if HAVE_PTHREAD_YIELD -# if PTHREAD_YIELD_VOID - pthread_yield(); -# else - ABORTFAIL(pthread_yield()); -# endif -#else - ABORTFAIL(sched_yield()); -#endif -} - -struct thread_lock_t -{ - pthread_mutex_t mutex; -}; - -thread_lock_t* thread_lock_new(void) -{ - thread_lock_t* lock = malloc(sizeof(thread_lock_t)); - if (!lock) - { - return lock; - } - for (;;) - { -#ifdef DEBUG - pthread_mutexattr_t attr; - if (pthread_mutexattr_init(&attr)) - { - break; - } - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); - if (pthread_mutex_init(&lock->mutex, &attr)) - { - pthread_mutexattr_destroy(&attr); - break; - } - pthread_mutexattr_destroy(&attr); -#else - if (pthread_mutex_init(&lock->mutex, NULL)) - { - break; - } -#endif - return lock; - } - free(lock); - return NULL; -} - -thread_lock_t* thread_lock_recursive_new(void) -{ - thread_lock_t* lock = malloc(sizeof(thread_lock_t)); - for (;;) - { - if (!lock) - { - break; - } - pthread_mutexattr_t attr; - if (pthread_mutexattr_init(&attr)) - { - break; - } - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - if (pthread_mutex_init(&lock->mutex, &attr)) - { - pthread_mutexattr_destroy(&attr); - break; - } - pthread_mutexattr_destroy(&attr); - return lock; - } - free(lock); - return NULL; -} - -void thread_lock_free(thread_lock_t* lock) -{ - if (lock) - { - pthread_mutex_destroy(&lock->mutex); - free(lock); - } -} - -void thread_lock_lock(thread_lock_t* lock) -{ - assert(lock); - ABORTFAIL(pthread_mutex_lock(&lock->mutex)); -} - -void thread_lock_unlock(thread_lock_t* lock) -{ - assert(lock); - ABORTFAIL(pthread_mutex_unlock(&lock->mutex)); -} - -struct thread_cond_t -{ - pthread_cond_t cond; -}; - -thread_cond_t* thread_cond_new(void) -{ - thread_cond_t* cond = malloc(sizeof(thread_cond_t)); - if (cond) - { - if (pthread_cond_init(&cond->cond, NULL)) - { - free(cond); - return NULL; - } - } - return cond; -} - -void thread_cond_free(thread_cond_t* cond) -{ - if (cond) - { - pthread_cond_destroy(&cond->cond); - free(cond); - } -} - -void thread_cond_wait(thread_cond_t* cond, thread_lock_t* lock) -{ - assert(cond); - assert(lock); - ABORTFAIL(pthread_cond_wait(&cond->cond, &lock->mutex)); -} - -bool thread_cond_timedwait(thread_cond_t* cond, thread_lock_t* lock, - const struct timespec *abstime) -{ - int ret; - assert(cond); - assert(lock); - assert(abstime); - ret = pthread_cond_timedwait(&cond->cond, &lock->mutex, abstime); - if (ret == 0) - { - return true; - } -#ifdef DEBUG - if (ret != ETIMEDOUT) - { - abort(); - } -#endif - return false; -} - -void thread_cond_signal(thread_cond_t* cond) -{ - assert(cond); - ABORTFAIL(pthread_cond_signal(&cond->cond)); -} - -void thread_cond_broadcast(thread_cond_t* cond) -{ - ABORTFAIL(pthread_cond_broadcast(&cond->cond)); -} - -void thread_abstime(struct timespec *abstime, unsigned long add_ms) -{ -#if HAVE_CLOCK_GETTIME - clock_gettime(CLOCK_REALTIME, abstime); -#else - { - struct timeval tv; - gettimeofday(&tv, NULL); - abstime->tv_sec = tv.tv_sec; - abstime->tv_nsec = tv.tv_usec * 1000; - } -#endif - timespec_addms(abstime, add_ms); -} - -struct thread_data_t -{ - pthread_key_t key; -}; - -thread_data_t *thread_data_new(thread_data_value_free_t free_value) -{ - thread_data_t *data = malloc(sizeof(thread_data_t)); - if (data) - { - if (pthread_key_create(&data->key, free_value)) - { - free(data); - return NULL; - } - } - return data; -} - -void thread_data_free(thread_data_t *data) -{ - if (data) - { - pthread_key_delete(data->key); - free(data); - } -} - -void *thread_data_value(thread_data_t *data) -{ - assert(data); - return pthread_getspecific(data->key); -} - -void thread_data_set(thread_data_t *data, void *value) -{ - assert(data); - ABORTFAIL(pthread_setspecific(data->key, value)); -} - -void thread_once(thread_once_t *once, thread_run_once_t run) -{ - ABORTFAIL(pthread_once(once, run)); -} diff --git a/src/thread.h b/src/thread.h deleted file mode 100644 index c0a21b0..0000000 --- a/src/thread.h +++ /dev/null @@ -1,266 +0,0 @@ -/** - * \file thread.h - * A collection of thread methods. - */ - -#ifndef THREAD_H -#define THREAD_H - -#include "timespec.h" - -#if HAVE_PTHREAD -#include <pthread.h> -/** - * Opaque type defining an once variable. - */ -typedef pthread_once_t thread_once_t; -/** - * Init value for an once variable - */ -#define THREAD_ONCE_INIT PTHREAD_ONCE_INIT -#else -# error missing thread_once_t implementation -#endif - -/** - * Opaque type defining a joinable thread. - */ -typedef struct thread_t thread_t; -/** - * Opaque type defining an id. - */ -typedef struct thread_id_t thread_id_t; -/** - * Opaque type defining a lock. - */ -typedef struct thread_lock_t thread_lock_t; -/** - * Opaque type defining a conditional. - */ -typedef struct thread_cond_t thread_cond_t; -/** - * Opaque type defining a thread local variable. - */ -typedef struct thread_data_t thread_data_t; - -/** - * Signature or function type for a thread run method. - * A thread run method is the thread main() if you will. - * When the method returns the thread is terminated. - * @param userdata argument given when starting the thread - */ -typedef void* (* thread_run_t)(void* userdata); - -/** - * Create and start a thread. - * @param run thread run method - * @param userdata argument to run method, may not be NULL - * @return true if thread was started without errors, may be NULL - */ -NONULL_ARGS(1) -bool thread_new(thread_run_t run, void* userdata); - -/** - * Create and start a joinable thread. - * Must call thread_join to free thread resources. - * @param run thread run method, may not be NULL - * @param userdata argument to run method, may be NULL - * @return NULL in case of error - */ -NONULL_ARGS(1) -MALLOC thread_t* thread_joinable_new(thread_run_t run, void* userdata); - -/** - * Wait for a joinable thread to exit, free it and return the result. - * @param thread result from call to thread_joinable_new, may not be NULL - * @return result of the thread run method - */ -NONULL void* thread_join(thread_t* thread); - - -/** - * Create a unique thread id pointing to the given or current thread. - * If no thread is given then a id for the current running thread is returned. - * Returns NULL in case of error. - * @param thread result from call to thread_joinable_new or NULL - * @return a new thread id - */ -MALLOC thread_id_t* thread_id_new(thread_t* thread); - -/** - * Free a thread id. - * @param id thread id to free, may be NULL - */ -void thread_id_free(thread_id_t* id); - -/** - * Return true if the current thread matches the thread id. - * @param id thread id to check, may not be NULL - * @return true if the id matches the current thread - */ -NONULL bool thread_id_is_current(thread_id_t* id); - -/** - * Thread yield, give another thread the chance to run. - */ -void thread_yield(void); - -/** - * Create a possibly non-recursive lock (mutex). - * Returns NULL in case of error. - * @return a new thread lock - */ -MALLOC thread_lock_t* thread_lock_new(void); - -/** - * Create a recursive lock (mutex). - * Returns NULL in case of error. - * @return a new thread lock - */ -MALLOC thread_lock_t* thread_lock_recursive_new(void); - -/** - * Free a lock. - * The lock must not have a owner when freed or undefined behaviour will ensue. - * @param lock lock to free, may be NULL - */ -void thread_lock_free(thread_lock_t* lock); - -/** - * Take ownership of the lock. - * If another thread has ownership of the lock, wait until that thread releases - * the lock. - * If the current thread already has ownership of the lock and the lock is - * recursive the current thread now owns the lock twice. - * Undefined behavior (abort or deadlock probably) if the lock isn't recursive - * and the current thread already has ownership. - * @param lock lock to take ownership of - */ -NONULL void thread_lock_lock(thread_lock_t* lock); -/** - * Release ownership of the lock. - * Only call if the current thread has one or more ownerships of the lock. - * If the lock is recursive only one ownership is released. - * Undefined behaviour (abort or deadlock probably) if the lock isn't owned - * by the current thread. - * @param lock lock to release - */ -NONULL void thread_lock_unlock(thread_lock_t* lock); - -/** - * Create a conditional. Conditionals can be used to signal other threads - * that a condition has been fulfilled if they were waiting for it. - * Returns NULL in case of error. - * @return new conditional - */ -MALLOC thread_cond_t* thread_cond_new(void); -/** - * Free a conditional. No thread may be waiting on the conditional or undefined - * behaviour will ensue. - * @param cond conditional to free, may be NULL - */ -void thread_cond_free(thread_cond_t* cond); - -/** - * Setup the current thread to wait for the condition to be signalled. - * The lock must be owned by the current thread before calling and will be - * release while the current thread is waiting but reclaimed upon return. - * @param cond conditonal to wait upon, may not be NULL - * @param lock lock to release and retake ownership of, may not be NULL - */ -NONULL void thread_cond_wait(thread_cond_t* cond, thread_lock_t* lock); - -/** - * Setup the current thread to wait for the condition to be signalled until - * abstime. - * The lock must be owned by the current thread before calling and will be - * release while the current thread is waiting but reclaimed upon return. - * Use thread_abstime() to fill out the abstime. - * @param cond conditonal to wait upon, may not be NULL - * @param lock lock to release and retake ownership of, may not be NULL - * @param abstime time when to give up and return, may not be NULL - * @return true if the condition was signalled, false if it timed out - */ -NONULL bool thread_cond_timedwait(thread_cond_t* cond, thread_lock_t* lock, - const struct timespec *abstime); - -/** - * Signal one thread that is currently waiting on the given condition. - * The selected thread (which one is random) will be woken up. - * The calling thread may or may not own the lock when calling - * thread_cond_signal but the signalled thread will not wake up before the lock - * is released. - * @param cond conditional to signal, may not be NULL - */ -NONULL void thread_cond_signal(thread_cond_t* cond); - -/** - * Signal all threads that is currently waiting on the given condition. - * All waiting threads will be woken up but only one may get ownership - * of the lock. - * The calling thread may or may not own the lock when calling - * thread_cond_broadcast but the signalled threads will not wake up before the - * lock is released. - * @param cond conditional to signal, may not be NULL - */ -NONULL void thread_cond_broadcast(thread_cond_t* cond); - -/** - * Get the current absolute time and add a certain number of milliseconds - * to it. To be used with thread_cond_timedwait. - * @param abstime struct to file with the current absolute time and the - * extra milliseconds. - * @param add_ms milliseconds to add to the current absolute time - */ -NONULL void thread_abstime(struct timespec *abstime, unsigned long add_ms); - -/** - * Function type for a thread local variable value destructor - */ -typedef void (* thread_data_value_free_t)(void *value); - -/** - * Create a thread local variable. - * Returns NULL in case of error. - * @param free_value destructor for set non-null value at thread exit, - * may be NULL - * @return new thread local variable or NULL - */ -MALLOC thread_data_t *thread_data_new(thread_data_value_free_t free_value); - -/** - * Free a thread local variable. - * Does not call any destructor. - * @param data thread local variable to free, may be NULL - */ -void thread_data_free(thread_data_t *data); - -/** - * Get the thread local value of the variable. - * @param data thread local variable, may not be NULL - * @return thread local value for variable - */ -NONULL void *thread_data_value(thread_data_t *data); - -/** - * Set thread local value for variable. - * @param data thread local variable, may not be NULL - * @param value new value, may be NULL - */ -NONULL_ARGS(1) -void thread_data_set(thread_data_t *data, void *value); - -/** - * Function type for a thread once method - */ -typedef void (* thread_run_once_t)(void); - -/** - * The first thread calling this method will run "run". Any subsequent thread - * calling thread_once for the same once variable will do nothing. - * @param once address to a static once variable, may not be NULL - * @param run method to run if this is the first thread, may not be NULL - */ -NONULL void thread_once(thread_once_t *once, thread_run_once_t run); - -#endif /* THREAD_H */ diff --git a/src/timer.cc b/src/timer.cc new file mode 100644 index 0000000..8c97d32 --- /dev/null +++ b/src/timer.cc @@ -0,0 +1,555 @@ +#include "common.hh" + +#include "args.hh" +#include "timer_state.hh" +#include "xcb_atoms.hh" +#include "xcb_colors.hh" +#include "xcb_connection.hh" +#include "xcb_event.hh" +#include "xcb_resource.hh" +#include "xcb_resources.hh" +#include "xcb_xkb.hh" +#include "xdg.hh" + +#include <algorithm> +#include <condition_variable> +#include <deque> +#include <errno.h> +#include <iostream> +#include <limits> +#include <mutex> +#include <optional> +#include <string.h> +#include <thread> +#include <xcb/xcb_icccm.h> + +#ifndef VERSION +# warning No version defined +# define VERSION +#endif + +namespace { + +constexpr char const kTitle[] = "Timer"; +constexpr char const kClass[] = "org.the_jk.timer"; + +constexpr std::chrono::seconds const kRedrawInterval{36}; + +class Timer : public TimerState::Delegate { +public: + explicit Timer(std::filesystem::path state_file) + : state_file_(std::move(state_file)), + state_(TimerState::create(state_file_, this)) {} + + bool good() const { + return state_ != nullptr; + } + + bool run(Option const* display, std::optional<std::string> font_name) { + int screen_index = 0; + if (display->is_set()) { + conn_ = xcb::make_shared_conn(xcb_connect(display->arg().c_str(), + &screen_index)); + } else { + conn_ = xcb::make_shared_conn(xcb_connect(nullptr, &screen_index)); + } + + { + auto err = xcb_connection_has_error(conn_.get()); + if (err) { + std::cerr << "Unable to connect to X display: " << err << std::endl; + return false; + } + } + + auto atoms = xcb::Atoms::create(conn_); + string_atom_ = atoms->get("STRING"); + wm_protocols_ = atoms->get("WM_PROTOCOLS"); + wm_delete_window_ = atoms->get("WM_DELETE_WINDOW"); + + auto* screen = xcb::get_screen(conn_.get(), screen_index); + assert(screen); + + auto colors = xcb::Colors::create(conn_, screen->default_colormap); + background_inactive_ = + colors->get_with_fallback(0x80, 0x80, 0x80, screen->white_pixel); + foreground_inactive_ = + colors->get_with_fallback(0x00, 0x00, 0x00, screen->black_pixel); + background_active_ = + colors->get_with_fallback(0x80, 0xff, 0x80, screen->white_pixel); + foreground_active_ = + colors->get_with_fallback(0x00, 0x00, 0x00, screen->black_pixel); + + keyboard_ = xcb::Keyboard::create(conn_.get()); + if (!keyboard_) { + std::cerr << "Failed to initialize XKB." << std::endl; + return EXIT_FAILURE; + } + + wnd_ = xcb::make_unique_wnd(conn_); + gc_ = xcb::make_unique_gc(conn_); + font_ = xcb::make_unique_font(conn_); + + if (!font_name) { + auto resources = xcb::Resources::create(conn_); + font_name = resources->get_string("timer.font", ""); + if (!font_name) { + font_name = "fixed"; + } + } + + auto font_cookie = xcb_open_font_checked(conn_.get(), font_->id(), + font_name->size(), + font_name->data()); + + if (!atoms->sync()) { + std::cerr << "Failed to get X atoms." << std::endl; + return EXIT_FAILURE; + } + if (!colors->sync()) { + std::cerr << "Failed to get X colors." << std::endl; + return EXIT_FAILURE; + } + + uint32_t value_list[3]; + uint32_t value_mask = 0; + value_mask |= XCB_CW_BACK_PIXEL; + value_list[0] = screen->black_pixel; + value_mask |= XCB_CW_EVENT_MASK; + value_list[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS | + XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_STRUCTURE_NOTIFY; + xcb_create_window(conn_.get(), XCB_COPY_FROM_PARENT, wnd_->id(), + screen->root, 0, 0, wnd_width_, wnd_height_, 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + screen->root_visual, value_mask, value_list); + + xcb_icccm_set_wm_name(conn_.get(), wnd_->id(), string_atom_->get(), + 8, sizeof(kTitle) - 1, kTitle); + xcb_icccm_set_wm_class(conn_.get(), wnd_->id(), sizeof(kClass) - 1, kClass); + xcb_atom_t atom_list[1]; + atom_list[0] = wm_delete_window_->get(); + xcb_icccm_set_wm_protocols(conn_.get(), wnd_->id(), + wm_protocols_->get(), 1, atom_list); + + value_mask = XCB_GC_FONT; + value_list[0] = font_->id(); + xcb_create_gc(conn_.get(), gc_->id(), wnd_->id(), value_mask, value_list); + + { + xcb::generic_error error(xcb_request_check(conn_.get(), font_cookie)); + if (error) { + std::cerr << "Failed to load font " << *font_name << ": " + << static_cast<int>(error->error_code) << std::endl; + return false; + } + } + + update_text(); + + xcb_map_window(conn_.get(), wnd_->id()); + xcb_flush(conn_.get()); + + std::thread xcb_thread(&Timer::run_xcb, this); + + bool quit = false; + std::chrono::steady_clock::time_point next_redraw; + if (active_) { + next_redraw = std::chrono::steady_clock::now() + kRedrawInterval; + } + while (!quit) { + ActionData action; + { + std::unique_lock<std::mutex> lock(action_lock_); + while (action_.empty()) { + if (active_) { + if (action_cond_.wait_until(lock, next_redraw) + == std::cv_status::timeout) { + next_redraw = std::chrono::steady_clock::now() + kRedrawInterval; + update_text(); + draw(); + } + } else { + action_cond_.wait(lock); + } + } + action = action_.front(); + action_.pop_front(); + } + + switch (action.action) { + case Action::QUIT: + quit = true; + break; + case Action::DRAW: + if (action.b) { + draw(); + } + break; + case Action::RESIZE: + wnd_width_ = action.rect.width; + wnd_height_ = action.rect.height; + break; + case Action::TOGGLE: + if (active_) { + state_->stop(); + } else { + state_->start(); + } + break; + case Action::SEND_RESET: + state_->reset(); + break; + case Action::RECV_RESET: + total_ = std::chrono::minutes::zero(); + update_text(); + draw(); + break; + case Action::START: + active_ = true; + next_redraw = std::chrono::steady_clock::now() + kRedrawInterval; + total_ = action.duration; + epoch_ = action.point; + update_text(); + draw(); + break; + case Action::STOP: + active_ = false; + total_ = action.duration; + update_text(); + draw(); + break; + case Action::RESTART: + state_ = TimerState::create(state_file_, this); + if (!state_) { + std::cerr << "Timer state failed and unable to restart." << std::endl; + quit = true; + conn_.reset(); + break; + } + } + } + + xcb_thread.join(); + return true; + } + + void start( + std::chrono::minutes total, + std::chrono::time_point<std::chrono::system_clock> epoch) override { + post(Action::START, total, epoch); + } + + void stop(std::chrono::minutes total) override { + post(Action::STOP, total); + } + + void reset() override { + post(Action::RECV_RESET); + } + + void restart() override { + post(Action::RESTART); + } + +private: + enum class Action { + QUIT, + DRAW, + RESIZE, + TOGGLE, + START, + STOP, + SEND_RESET, + RECV_RESET, + RESTART, + }; + + struct ActionData { + Action action; + xcb_rectangle_t rect; + bool b{false}; + std::chrono::minutes duration; + std::chrono::time_point<std::chrono::system_clock> point; + + ActionData() = default; + + explicit ActionData(Action action) + : action(action) { + assert(action == Action::QUIT || action == Action::TOGGLE || + action == Action::SEND_RESET || action == Action::RECV_RESET || + action == Action::RESTART); + } + + ActionData(Action action, xcb_rectangle_t rect, bool last) + : action(action), rect(rect), b(last) { + assert(action == Action::DRAW); + } + + ActionData(Action action, uint16_t width, uint16_t height) + : action(action), rect({0, 0, width, height}) { + assert(action == Action::RESIZE); + } + + ActionData(Action action, std::chrono::minutes total, + std::chrono::time_point<std::chrono::system_clock> epoch) + : action(action), duration(total), point(epoch) { + assert(action == Action::START); + } + + ActionData(Action action, std::chrono::minutes total) + : action(action), duration(total) { + assert(action == Action::STOP); + } + }; + + void run_xcb() { + while (true) { + xcb::generic_event event(xcb_wait_for_event(conn_.get())); + if (!event) { + auto err = xcb_connection_has_error(conn_.get()); + if (err) { + std::cerr << "X connection had fatal error: " << err << std::endl; + } else { + std::cerr << "X connection had fatal I/O error." << std::endl; + } + break; + } + auto response_type = XCB_EVENT_RESPONSE_TYPE(event.get()); + if (response_type == XCB_EXPOSE) { + auto* e = reinterpret_cast<xcb_expose_event_t*>(event.get()); + if (e->window == wnd_->id()) { + xcb_rectangle_t rect; + rect.x = e->x; + rect.y = e->y; + rect.width = e->width; + rect.height = e->height; + post(Action::DRAW, rect, e->count == 0); + } + continue; + } else if (response_type == XCB_KEY_PRESS) { + auto* e = reinterpret_cast<xcb_key_press_event_t*>(event.get()); + if (e->event == wnd_->id()) { + auto str = keyboard_->get_utf8(e); + if (str == "q" || str == "\x1b" /* Escape */) { + break; + } else if (str == " ") { + post(Action::TOGGLE); + } else if ((e->state & XCB_MOD_MASK_CONTROL) && + str == "\x12" /* Ctrl + R */) { + post(Action::SEND_RESET); + } + } + continue; + } else if (response_type == XCB_BUTTON_PRESS) { + auto* e = reinterpret_cast<xcb_button_press_event_t*>(event.get()); + if (e->event == wnd_->id()) { + if (e->detail == 1 /* Left button */) { + post(Action::TOGGLE); + } + } + continue; + } else if (response_type == XCB_CONFIGURE_NOTIFY) { + auto* e = reinterpret_cast<xcb_configure_notify_event_t*>(event.get()); + if (e->window == wnd_->id()) { + post(Action::RESIZE, e->width, e->height); + } + continue; + } else if (response_type == XCB_REPARENT_NOTIFY) { + // Ignored, part of XCB_EVENT_MASK_STRUCTURE_NOTIFY + continue; + } else if (response_type == XCB_MAP_NOTIFY) { + // Ignored, part of XCB_EVENT_MASK_STRUCTURE_NOTIFY + continue; + } else if (keyboard_->handle_event(conn_.get(), event.get())) { + continue; + } else if (response_type == XCB_CLIENT_MESSAGE) { + auto* e = reinterpret_cast<xcb_client_message_event_t*>(event.get()); + if (e->window == wnd_->id() && e->type == wm_protocols_->get() && + e->format == 32) { + if (e->data.data32[0] == wm_delete_window_->get()) { + break; + } + } + continue; + } + +#ifndef NDEBUG + if (response_type == 0) { + auto* e = reinterpret_cast<xcb_generic_error_t*>(event.get()); + std::cout << "Unhandled error: " + << xcb_event_get_error_label(e->error_code) << std::endl; + } else { + std::cout << "Unhandled event: " << xcb_event_get_label(response_type) + << std::endl; + } +#endif + } + + post(Action::QUIT); + } + + template<class... Args> + void post(Args&&... args) { + bool notify; + { + std::lock_guard<std::mutex> lock(action_lock_); + notify = action_.empty(); + action_.emplace_back(std::forward<Args>(args)...); + } + if (notify) + action_cond_.notify_one(); + } + + void update_text() { + char tmp[50]; + int len; + + using hours = std::chrono::duration<float, std::ratio<60 * 60>>; + + if (active_) { + auto diff = std::chrono::duration_cast<std::chrono::minutes>( + std::chrono::system_clock::now() - epoch_); + + len = snprintf(tmp, sizeof(tmp), "%.2f (%.2f)", hours(diff).count(), + hours(total_ + diff).count()); + } else { + len = snprintf(tmp, sizeof(tmp), "(%.2f)", hours(total_).count()); + } + if (len < 0 || len == sizeof(tmp)) + return; + + text_.resize(len); + for (int i = 0; i < len; ++i) { + text_[i].byte1 = 0; + text_[i].byte2 = tmp[i]; + } + + text_extents_cookie_ = xcb_query_text_extents(conn_.get(), font_->id(), + text_.size(), text_.data()); + text_extents_width_ = 0; + } + + void draw() { + xcb_rectangle_t r{0, 0, wnd_width_, wnd_height_}; + + uint32_t values[2]; + values[0] = + active_ ? background_active_->get() : background_inactive_->get(); + xcb_change_gc(conn_.get(), gc_->id(), XCB_GC_FOREGROUND, values); + xcb_poly_fill_rectangle(conn_.get(), wnd_->id(), gc_->id(), 1, &r); + + values[0] = + active_ ? foreground_active_->get() : foreground_inactive_->get(); + values[1] = + active_ ? background_active_->get() : background_inactive_->get(); + xcb_change_gc(conn_.get(), gc_->id(), + XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values); + + if (text_extents_width_ == 0) { + xcb::reply<xcb_query_text_extents_reply_t> reply( + xcb_query_text_extents_reply(conn_.get(), text_extents_cookie_, + nullptr)); + if (reply) { + text_extents_width_ = reply->overall_width; + text_extents_height_ = reply->font_ascent; + } else { + text_extents_width_ = 1; + text_extents_height_ = 0; + } + } + + auto x = (text_extents_width_ < wnd_width_) ? + (wnd_width_ - text_extents_width_) / 2 : 0; + auto y = wnd_height_ / 2 + text_extents_height_ / 2; + + xcb_image_text_16(conn_.get(), text_.size(), wnd_->id(), gc_->id(), x, y, + text_.data()); + + xcb_flush(conn_.get()); + } + + xcb::shared_conn conn_; + std::optional<xcb::Atoms::Reference> string_atom_; + std::optional<xcb::Atoms::Reference> wm_protocols_; + std::optional<xcb::Atoms::Reference> wm_delete_window_; + std::optional<xcb::Colors::Color> background_inactive_; + std::optional<xcb::Colors::Color> background_active_; + std::optional<xcb::Colors::Color> foreground_inactive_; + std::optional<xcb::Colors::Color> foreground_active_; + std::unique_ptr<xcb::Keyboard> keyboard_; + xcb::unique_wnd wnd_; + xcb::unique_gc gc_; + xcb::unique_font font_; + uint16_t wnd_width_{100}; + uint16_t wnd_height_{50}; + + bool active_{false}; + std::chrono::minutes total_{0}; + std::chrono::time_point<std::chrono::system_clock> epoch_; + + std::vector<xcb_char2b_t> text_; + xcb_query_text_extents_cookie_t text_extents_cookie_; + uint16_t text_extents_width_{0}; + uint16_t text_extents_height_{0}; + + std::mutex action_lock_; + std::condition_variable action_cond_; + std::deque<ActionData> action_; + + std::filesystem::path const state_file_; + std::unique_ptr<TimerState> state_; +}; + +} // namespace + +int main(int argc, char** argv) { + auto args = Args::create(); + auto* help = args->add_option('h', "help", "display this text and exit."); + auto* version = args->add_option('V', "version", "display version and exit."); + auto* opt_state_file = args->add_option_with_arg( + 'S', "state", "load state from FILE instead of default.", "FILE"); + auto* opt_font_name = args->add_option_with_arg( + 'F', "font", "use font named FONT instead of default or Xresources.", + "FONT"); + auto* display = args->add_option_with_arg( + 'D', "display", "connect to DISPLAY instead of default.", "DISPLAY"); + std::vector<std::string> arguments; + if (!args->run(argc, argv, "timer", std::cerr, &arguments)) { + std::cerr << "Try `timer --help` for usage." << std::endl; + return EXIT_FAILURE; + } + if (help->is_set()) { + std::cout << "Usage: `timer [OPTIONS]`\n" + << "Timer is a timekeeping tool.\n" + << "\n"; + args->print_descriptions(std::cout, 80); + return EXIT_SUCCESS; + } + if (version->is_set()) { + std::cout << "Timer " VERSION " written by " + << "Joel Klinghed <the_jk@spawned.biz>" << std::endl; + std::cout << "Icon by Free Preloaders [https://freeicons.io/profile/726]" + << " on https://freeicons.io" << std::endl; + return EXIT_SUCCESS; + } + if (!arguments.empty()) { + std::cerr << "Unexpected arguments after options.\n" + << "Try `timer --help` for usage." << std::endl; + return EXIT_FAILURE; + } + + std::filesystem::path state_file; + if (opt_state_file->is_set()) { + state_file = opt_state_file->arg(); + } else { + state_file = xdg::path_to_write(xdg::Type::DATA, "timer.state"); + } + + Timer timer(state_file); + if (!timer.good()) + return EXIT_FAILURE; + return timer.run(display, + opt_font_name->is_set() ? opt_font_name->arg() : + std::optional<std::string>()) + ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/timer_state.cc b/src/timer_state.cc new file mode 100644 index 0000000..770bbad --- /dev/null +++ b/src/timer_state.cc @@ -0,0 +1,419 @@ +#include "common.hh" + +#include "io.hh" +#include "timer_state.hh" +#include "unique_fd.hh" + +#include <errno.h> +#include <fcntl.h> +#include <functional> +#include <iostream> +#include <sdbus-c++/sdbus-c++.h> +#include <string.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <thread> + +namespace { + +constexpr char kServiceName[] = "org.the_jk.timer"; +constexpr char kObjectPath[] = "/org/the_jk/timer/state"; +constexpr char kInterfaceName[] = "org.the_jk.timer.State"; + +class TimerStateImpl { +public: + virtual ~TimerStateImpl() = default; + + virtual void start() = 0; + virtual void stop() = 0; + virtual void reset() = 0; + + virtual void enterLoop() { + conn_->enterEventLoop(); + } + + virtual void leaveLoop() { + conn_->leaveEventLoop(); + } + +protected: + TimerStateImpl(std::shared_ptr<sdbus::IConnection> conn, + TimerState::Delegate* delegate) + : conn_(std::move(conn)), delegate_(delegate) { + } + + std::shared_ptr<sdbus::IConnection> conn_; + TimerState::Delegate* const delegate_; +}; + +class TimerStateClient : public TimerStateImpl { +public: + TimerStateClient(std::shared_ptr<sdbus::IConnection> conn, + TimerState::Delegate* delegate) + : TimerStateImpl(std::move(conn), delegate) {} + + void start() override { + try { + proxy_->callMethod("start").onInterface(kInterfaceName).dontExpectReply(); + } catch (sdbus::Error const& err) { + std::cerr << "Failed to call start: " << err.what() << std::endl; + } + } + + void stop() override { + try { + proxy_->callMethod("stop").onInterface(kInterfaceName).dontExpectReply(); + } catch (sdbus::Error const& err) { + std::cerr << "Failed to call stop: " << err.what() << std::endl; + } + } + + void reset() override { + try { + proxy_->callMethod("reset").onInterface(kInterfaceName).dontExpectReply(); + } catch (sdbus::Error const& err) { + std::cerr << "Failed to call reset: " << err.what() << std::endl; + } + } + + bool init() { + try { + proxy_ = sdbus::createProxy(*conn_.get(), kServiceName, kObjectPath); + proxy_->uponSignal("started").onInterface(kInterfaceName) + .call([this](uint32_t total, time_t epoch){ + signal_started(std::chrono::minutes(total), + std::chrono::system_clock::from_time_t(epoch)); + }); + proxy_->uponSignal("stopped").onInterface(kInterfaceName) + .call([this](uint32_t total){ + signal_stopped(std::chrono::minutes(total)); + }); + proxy_->uponSignal("reset").onInterface(kInterfaceName) + .call([this](){ signal_reset(); }); + proxy_->finishRegistration(); + + dbus_proxy_ = sdbus::createProxy(*conn_.get(), "org.freedesktop.DBus", + "/org/freedesktop/DBus"); + dbus_proxy_->uponSignal("NameOwnerChanged") + .onInterface("org.freedesktop.DBus") + .call([this](const std::string& name, + const std::string& /* old_owner */, + const std::string& new_owner) { + if (name == kServiceName && new_owner.empty()) { + signal_restart(); + } + }); + dbus_proxy_->finishRegistration(); + + sync_state(); + } catch (sdbus::Error const& err) { + std::cerr << "Failed to init client: " << err.what() << std::endl; + return false; + } + + return true; + } + +private: + void signal_started( + std::chrono::minutes total, + std::chrono::time_point<std::chrono::system_clock> epoch) { + delegate_->start(total, epoch); + } + + void signal_stopped(std::chrono::minutes total) { + delegate_->stop(total); + } + + void signal_reset() { + delegate_->reset(); + } + + void signal_restart() { + delegate_->restart(); + } + + void sync_state() { + auto method = proxy_->createMethodCall(kInterfaceName, "get_state"); + auto reply = proxy_->callMethod(std::move(method)); + bool active; + uint32_t total; + time_t epoch; + reply >> active; + reply >> total; + reply >> epoch; + if (active) { + delegate_->start(std::chrono::minutes(total), + std::chrono::system_clock::from_time_t(epoch)); + } else { + delegate_->stop(std::chrono::minutes(total)); + } + } + + std::unique_ptr<sdbus::IProxy> proxy_; + std::unique_ptr<sdbus::IProxy> dbus_proxy_; +}; + +class TimerStateServer : public TimerStateImpl { +public: + TimerStateServer(std::shared_ptr<sdbus::IConnection> conn, + TimerState::Delegate* delegate) + : TimerStateImpl(std::move(conn), delegate) {} + + void start() override { + if (active_) return; + active_ = true; + start_ = std::chrono::system_clock::now(); + write_state(); + + try { + object_->emitSignal("started").onInterface(kInterfaceName).withArguments( + total_.count(), std::chrono::system_clock::to_time_t(start_)); + } catch (sdbus::Error const& err) { + std::cerr << "Failed to emit started: " << err.what() << std::endl; + } + delegate_->start(total_, start_); + } + + void stop() override { + if (!active_) return; + active_ = false; + total_ += + std::chrono::duration_cast<std::chrono::minutes>( + std::chrono::system_clock::now() - start_); + write_state(); + + try { + object_->emitSignal("stopped").onInterface(kInterfaceName).withArguments( + total_.count()); + } catch (sdbus::Error const& err) { + std::cerr << "Failed to emit started: " << err.what() << std::endl; + } + delegate_->stop(total_); + } + + void reset() override { + if (active_) return; + total_ = std::chrono::minutes::zero(); + write_state(); + + try { + object_->emitSignal("reset").onInterface(kInterfaceName); + } catch (sdbus::Error const& err) { + std::cerr << "Failed to emit reset: " << err.what() << std::endl; + } + delegate_->reset(); + } + + bool init(std::filesystem::path state_file) { + if (!load_state(state_file)) + return false; + + try { + auto object = sdbus::createObject(*conn_.get(), kObjectPath); + std::function<void()> fun = std::bind(&TimerStateServer::start, this); + object->registerMethod("start").onInterface(kInterfaceName) + .implementedAs(fun).withNoReply(); + fun = std::bind(&TimerStateServer::stop, this); + object->registerMethod("stop").onInterface(kInterfaceName) + .implementedAs(fun).withNoReply(); + fun = std::bind(&TimerStateServer::reset, this); + object->registerMethod("reset").onInterface(kInterfaceName) + .implementedAs(fun).withNoReply(); + std::function<void(sdbus::MethodCall)> call_fun = + std::bind(&TimerStateServer::get_state, this, std::placeholders::_1); + object->registerMethod(kInterfaceName, "get_state", "", "bdd", call_fun); + object->registerSignal("started").onInterface(kInterfaceName) + .withParameters<uint32_t, time_t>(); + object->registerSignal("stopped").onInterface(kInterfaceName) + .withParameters<uint32_t>(); + object->registerSignal("reset").onInterface(kInterfaceName); + object->finishRegistration(); + + object_ = std::move(object); + } catch (sdbus::Error const& err) { + std::cerr << "Failed to init server: " << err.what() << std::endl; + return false; + } + + if (active_) { + delegate_->start(total_, start_); + } else { + delegate_->stop(total_); + } + + return true; + } + +private: + void get_state(sdbus::MethodCall call) { + try { + auto reply = call.createReply(); + reply << active_; + reply << static_cast<uint32_t>(total_.count()); + if (active_) { + reply << std::chrono::system_clock::to_time_t(start_); + } else { + reply << static_cast<time_t>(0); + } + reply.send(); + } catch (sdbus::Error const& err) { + std::cerr << "Failed to reply to get_state: " << err.what() << std::endl; + } + } + + bool load_state(std::filesystem::path state_file) { + fd_.reset(open(state_file.c_str(), O_RDWR | O_CREAT, S_IRWXU)); + if (!fd_) { + std::cerr << "Unable to open or create " << state_file + << " for reading and writing." << std::endl; + return false; + } + if (flock(fd_.get(), LOCK_EX | LOCK_NB)) { + std::cerr << "Unable to get exclusive lock on " << state_file + << ": " << strerror(errno) << std::endl; + return false; + } + std::string data; + if (!io::read_all(fd_.get(), &data)) { + std::cerr << "Error reading " << state_file + << ": " << strerror(errno) << std::endl; + return false; + } + if (!parse_state(std::move(data))) { + std::cerr << "Invalid data in state " << state_file << "." << std::endl; + return false; + } + return true; + } + + bool parse_state(std::string data) { + try { + size_t end; + auto active = std::stol(data, &end); + if (end == data.size() || data[end] != '|') + return false; + data = data.substr(end + 1); + auto total = std::stoul(data, &end); + if (end == data.size() || data[end] != '|') + return false; + struct tm tm; + auto* endp = strptime( + data.substr(end + 1).c_str(), "%Y-%m-%d %H:%M:%S", &tm); + if (!endp || (*endp != '\0' && *endp != '\n')) + return false; + + active_ = active == 1; + total_ = std::chrono::minutes(total); + start_ = std::chrono::system_clock::from_time_t(timegm(&tm)); + + return true; + } catch (std::exception const& e) { + return false; + } + } + + bool write_state() { + std::string data = + std::to_string(active_ ? 1L : -1L) + '|' + std::to_string( + std::chrono::duration_cast<std::chrono::minutes>(total_).count()) + + '|'; + char tmp[50]; + auto time = std::chrono::system_clock::to_time_t(start_); + auto len = strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", gmtime(&time)); + if (len == 0 || len == sizeof(tmp)) { + std::cerr << "Failed to store state: invalid time." << std::endl; + return false; + } + data.append(tmp, len); + data.push_back('\n'); + if (lseek(fd_.get(), 0, SEEK_SET) || + !io::write_all(fd_.get(), data) || + ftruncate(fd_.get(), data.size())) { + std::cerr << "Failed to store state: " << strerror(errno) << std::endl; + return false; + } + return true; + } + + bool active_{false}; + std::chrono::minutes total_{0}; + std::chrono::time_point<std::chrono::system_clock> start_; + std::unique_ptr<sdbus::IObject> object_; + unique_fd fd_; +}; + +class TimerStateWrapper : public TimerState { +public: + explicit TimerStateWrapper(Delegate* delegate) + : delegate_(delegate) {} + + ~TimerStateWrapper() override { + if (impl_) impl_->leaveLoop(); + thread_.join(); + } + + bool init(std::filesystem::path state_file) { + try { + std::shared_ptr<sdbus::IConnection> conn( + sdbus::createSessionBusConnection()); + + try { + conn->requestName(kServiceName); + + auto server = std::make_unique<TimerStateServer>(conn, delegate_); + if (server->init(std::move(state_file))) { + impl_ = std::move(server); + return post_init(); + } + // If server fails to init in any way, try client as backup. + } catch (sdbus::Error const& e) { + // This is here to catch requestName call, if requestName fails there + // is a server running. + } + + auto client = std::make_unique<TimerStateClient>(conn, delegate_); + if (client->init()) { + impl_ = std::move(client); + return post_init(); + } + } catch (sdbus::Error const& e) { + } + return false; + } + + void start() override { + impl_->start(); + } + + void stop() override { + impl_->stop(); + } + + void reset() override { + impl_->reset(); + } + +private: + bool post_init() { + thread_ = std::thread(&TimerStateWrapper::run_impl, this); + return true; + } + + void run_impl() { + impl_->enterLoop(); + } + + Delegate* const delegate_; + std::unique_ptr<TimerStateImpl> impl_; + std::thread thread_; +}; + +} // namespace + +std::unique_ptr<TimerState> TimerState::create(std::filesystem::path state_file, + Delegate* delegate) { + auto state = std::make_unique<TimerStateWrapper>(delegate); + return state->init(std::move(state_file)) ? std::move(state) : nullptr; +} diff --git a/src/timer_state.hh b/src/timer_state.hh new file mode 100644 index 0000000..0a3c1d1 --- /dev/null +++ b/src/timer_state.hh @@ -0,0 +1,64 @@ +#ifndef TIMER_STATE_HH +#define TIMER_STATE_HH + +#include <chrono> +#include <filesystem> +#include <memory> + +class TimerState { +public: + virtual ~TimerState() = default; + + /** + * Delegate methods must be thread-safe, they will be called on + * a thread owned by TimerState. + */ + class Delegate { + public: + virtual ~Delegate() = default; + + virtual void start( + std::chrono::minutes total, + std::chrono::time_point<std::chrono::system_clock> epoch) = 0; + + virtual void stop(std::chrono::minutes total) = 0; + + virtual void reset() = 0; + + virtual void restart() = 0; + + protected: + Delegate() = default; + }; + + /** + * Start timer. If already started nothing happens. + * Method must be called on the same thread as create. + */ + virtual void start() = 0; + /** + * Stop timer. If already stopped nothing happens. + * Method must be called on the same thread as create. + */ + virtual void stop() = 0; + /** + * Clear total if stopped. If started nothing happens. + * Method must be called on the same thread as create. + */ + virtual void reset() = 0; + + /** + * Sets up a shared TimerState. state is stored in state_file. + * Note that delegate may be called before method returns. + * Returns nullptr in case of error. + */ + static std::unique_ptr<TimerState> create(std::filesystem::path state_file, + Delegate* delegate); + +protected: + TimerState() = default; + TimerState(TimerState const&) = delete; + TimerState& operator=(TimerState const&) = delete; +}; + +#endif // TIMER_STATE_HH diff --git a/src/timespec.c b/src/timespec.c deleted file mode 100644 index 533f00d..0000000 --- a/src/timespec.c +++ /dev/null @@ -1,280 +0,0 @@ -#include "common.h" - -#include "timespec.h" - -#if !HAVE_CLOCK_GETTIME -# include <sys/time.h> -#endif - -#if HAVE_CLOCK_GETTIME -static bool support_monotonic = true; -#elif defined(__MACH__) -#include <mach/mach_time.h> -static struct mach_timebase_info timebase_info = { 0, 0 }; - -/* - * The methods below to muldiv128 have the listed copyright: - * - * Copyright (c) 1999, 2003, 2006, 2007 Apple Inc. All rights reserved. - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - */ - -typedef struct -{ - uint64_t high; - uint64_t low; -} uint128_t; - -/* acc += add */ -static inline void add128_128(uint128_t *acc, uint128_t *add) -{ - acc->high += add->high; - acc->low += add->low; - if (acc->low < add->low) - { - acc->high++; // carry - } -} - -/* acc -= sub */ -static inline void sub128_128(uint128_t *acc, uint128_t *sub) -{ - acc->high -= sub->high; - if (acc->low < sub->low) - { - acc->high--; // borrow - } - acc->low -= sub->low; -} - -static inline double uint128_double(uint128_t *u) -{ - return (((double)(1ULL << 32)) * ((double)(1ULL << 32))) * - u->high + u->low; // may loses precision - } - -/* 64x64 -> 128 bit multiplication */ -static inline void mul64x64(uint64_t x, uint64_t y, uint128_t *prod) -{ - uint128_t add; - /* - * Split the two 64-bit multiplicands into 32-bit parts: - * x => 2^32 * x1 + x2 - * y => 2^32 * y1 + y2 - */ - uint32_t x1 = (uint32_t)(x >> 32); - uint32_t x2 = (uint32_t)x; - uint32_t y1 = (uint32_t)(y >> 32); - uint32_t y2 = (uint32_t)y; - /* - * direct multiplication: - * x * y => 2^64 * (x1 * y1) + 2^32 (x1 * y2 + x2 * y1) + (x2 * y2) - * The first and last terms are direct assignmenet into the uint128_t - * structure. Then we add the middle two terms separately, to avoid - * 64-bit overflow. (We could use the Karatsuba algorithm to save - * one multiply, but it is harder to deal with 64-bit overflows.) - */ - prod->high = (uint64_t)x1 * (uint64_t)y1; - prod->low = (uint64_t)x2 * (uint64_t)y2; - add.low = (uint64_t)x1 * (uint64_t)y2; - add.high = (add.low >> 32); - add.low <<= 32; - add128_128(prod, &add); - add.low = (uint64_t)x2 * (uint64_t)y1; - add.high = (add.low >> 32); - add.low <<= 32; - add128_128(prod, &add); -} - -/* (x * y / divisor) */ -static uint64_t muldiv128(uint64_t x, uint64_t y, uint64_t divisor) -{ - uint128_t temp; - uint128_t divisor128 = {0, divisor}; - uint64_t result = 0; - double recip; - - mul64x64(x, y, &temp); - - /* - * Now divide by the divisor. We use floating point to calculate an - * approximate answer and update the results. Then we iterate and - * calculate a correction from the difference. - */ - recip = 1.0 / (double)divisor; - while (temp.high || temp.low >= divisor) - { - uint128_t backmul; - uint64_t uapprox; - - uapprox = (uint64_t)(uint128_double(&temp) * recip); - mul64x64(uapprox, divisor, &backmul); - /* - * Because we are using unsigned integers, we need to approach the - * answer from the lesser side. So if our estimate is too large - * we need to decrease it until it is smaller. - */ - while (backmul.high > temp.high || - (backmul.high == temp.high && backmul.low > temp.low)) - { - sub128_128(&backmul, &divisor128); - uapprox--; - } - sub128_128(&temp, &backmul); - result += uapprox; - } - return result; -} -#endif - -void timespec_now(struct timespec *ts) -{ - assert(ts); -#if HAVE_CLOCK_GETTIME - if (support_monotonic) - { - if (clock_gettime(CLOCK_MONOTONIC, ts) == 0) - { - return; - } - support_monotonic = false; - } - clock_gettime(CLOCK_REALTIME, ts); -#elif defined( __MACH__) - if (timebase_info.denom == 0) - { - mach_timebase_info(&timebase_info); - } - { - uint64_t time; - if (timebase_info.denom == timebase_info.numer) - { - time = mach_absolute_time(); - } - else - { - time = muldiv128(timebase_info.numer, mach_absolute_time(), - timebase_info.denom); - } - ts->tv_sec = time / 1000000000; - ts->tv_nsec = time % 1000000000; - } -#else - { - struct timeval tv; - gettimeofday(&tv, NULL); - ts->tv_sec = tv.tv_sec; - ts->tv_nsec = tv.tv_usec * 1000; - } -#endif -} - -void timespec_addms(struct timespec *ts, unsigned long ms) -{ - const unsigned int sec = ms / 1000; - assert(ts); - ms -= sec * 1000; - ts->tv_nsec += ms * 1000000; - ts->tv_sec += ts->tv_nsec / 1000000000 + sec; - ts->tv_nsec = ts->tv_nsec % 1000000000; -} - -void timespec_add(struct timespec *ts, const struct timespec *add) -{ - ts->tv_nsec += add->tv_nsec; - ts->tv_sec += ts->tv_nsec / 1000000000 + add->tv_sec; - ts->tv_nsec = ts->tv_nsec % 1000000000; -} - -int timespec_sub(struct timespec *ts, const struct timespec *sub) -{ - if (ts->tv_sec < sub->tv_sec) - { - ts->tv_sec = sub->tv_sec - ts->tv_sec; - if (ts->tv_nsec <= sub->tv_nsec) - { - ts->tv_nsec = sub->tv_nsec - ts->tv_nsec; - } - else - { - ts->tv_sec--; - ts->tv_nsec = 1000000000 + sub->tv_nsec - ts->tv_nsec; - } - return -1; - } - else if (ts->tv_sec > sub->tv_sec) - { - ts->tv_sec -= sub->tv_sec; - if (ts->tv_nsec < sub->tv_nsec) - { - ts->tv_sec--; - ts->tv_nsec = 1000000000 + ts->tv_nsec - sub->tv_nsec; - } - else - { - ts->tv_nsec -= sub->tv_nsec; - } - return 1; - } - else /* if (ts->tv_sec == sub->tv_sec) */ - { - ts->tv_sec = 0; - if (ts->tv_nsec < sub->tv_nsec) - { - ts->tv_nsec = sub->tv_nsec - ts->tv_nsec; - return -1; - } - else if (ts->tv_nsec > sub->tv_nsec) - { - ts->tv_nsec -= sub->tv_nsec; - return 1; - } - else - { - ts->tv_nsec = 0; - return 0; - } - } -} - -int timespec_cmp(const struct timespec *x, const struct timespec *y) -{ - if (x->tv_sec < y->tv_sec) - { - return -1; - } - else if (x->tv_sec > y->tv_sec) - { - return 1; - } - else /* if (x->tv_sec == y->tv_sec) */ - { - if (x->tv_nsec < y->tv_nsec) - { - return -1; - } - else if (x->tv_nsec > y->tv_nsec) - { - return 1; - } - else - { - return 0; - } - } -} - diff --git a/src/timespec.h b/src/timespec.h deleted file mode 100644 index 2bf1182..0000000 --- a/src/timespec.h +++ /dev/null @@ -1,26 +0,0 @@ -/** - * \file timespec.h - * Declares timespec struct if platform is missing it - */ - -#ifndef TIMESPEC_H -#define TIMESPEC_H - -#include <time.h> -#if !HAVE_STRUCT_TIMESPEC -struct timespec -{ - time_t tv_sec; - long tv_nsec; -}; -#endif - -NONULL void timespec_now(struct timespec *ts); - -NONULL void timespec_addms(struct timespec *ts, unsigned long ms); -NONULL void timespec_add(struct timespec *ts, const struct timespec *add); - -NONULL int timespec_sub(struct timespec *ts, const struct timespec *sub); -NONULL int timespec_cmp(const struct timespec *x, const struct timespec *y); - -#endif /* TIMESPEC_H */ diff --git a/src/unique_fd.cc b/src/unique_fd.cc new file mode 100644 index 0000000..a289f0e --- /dev/null +++ b/src/unique_fd.cc @@ -0,0 +1,17 @@ +#include "common.hh" + +#include "unique_fd.hh" + +#include <unistd.h> + +void unique_fd::reset(int fd) { + if (fd_ >= 0) + close(fd_); + fd_ = fd; +} + +unique_fd unique_fd::dup() { + if (fd_ >= 0) + return unique_fd(::dup(fd_)); + return unique_fd(); +} diff --git a/src/unique_fd.hh b/src/unique_fd.hh new file mode 100644 index 0000000..d795d68 --- /dev/null +++ b/src/unique_fd.hh @@ -0,0 +1,50 @@ +#ifndef UNIQUE_FD_HH +#define UNIQUE_FD_HH + +#include <cstddef> + +class unique_fd { +public: + constexpr unique_fd() noexcept + : fd_(-1) {} + constexpr unique_fd(std::nullptr_t) noexcept + : fd_(-1) {} + explicit unique_fd(int fd) noexcept + : fd_(fd) {} + unique_fd(unique_fd&& fd) noexcept + : fd_(fd.release()) {} + + ~unique_fd() { reset(); } + + unique_fd& operator=(unique_fd&& fd) noexcept { + reset(fd.release()); + return *this; + } + unique_fd& operator=(std::nullptr_t) noexcept { + reset(); + return *this; + } + + int get() const noexcept { return fd_; } + int operator*() const { return get(); } + + explicit operator bool() const noexcept { return fd_ >= 0; } + + int release() noexcept { + int ret = fd_; + fd_ = -1; + return ret; + } + + void reset(int fd = -1); + + unique_fd dup(); + +private: + unique_fd(unique_fd const&) = delete; + unique_fd& operator=(unique_fd const&) = delete; + + int fd_; +}; + +#endif // UNIQUE_FD_HH diff --git a/src/xcb_atoms.cc b/src/xcb_atoms.cc new file mode 100644 index 0000000..300b859 --- /dev/null +++ b/src/xcb_atoms.cc @@ -0,0 +1,75 @@ +#include "common.hh" + +#include "xcb_atoms.hh" +#include "xcb_connection.hh" +#include "xcb_event.hh" + +#include <map> +#include <vector> + +namespace xcb { + +namespace { + +class AtomsImpl : public Atoms { +public: + explicit AtomsImpl(shared_conn conn) + : conn_(conn), storage_(std::make_shared<StorageImpl>()) {} + + Reference get(std::string atom) override { + auto it = index_.find(atom); + size_t index; + if (it == index_.end()) { + index = cookie_.size(); + cookie_.push_back( + xcb_intern_atom(conn_.get(), 0, atom.size(), atom.c_str())); + index_.emplace(std::move(atom), index); + } else { + index = it->second; + } + return Reference(storage_, index); + } + + bool sync() override { + std::vector<xcb_atom_t> atoms; + atoms.reserve(cookie_.size()); + for (auto const& cookie : cookie_) { + xcb::reply<xcb_intern_atom_reply_t> reply( + xcb_intern_atom_reply(conn_.get(), cookie, nullptr)); + if (!reply) + return false; + atoms.push_back(reply->atom); + } + storage_->set(std::move(atoms)); + return true; + } + +private: + class StorageImpl : public Storage { + public: + xcb_atom_t get(size_t id) const override { + assert(id < resolved_.size()); + return resolved_[id]; + } + + void set(std::vector<xcb_atom_t> resolved) { + resolved_ = std::move(resolved); + } + + private: + std::vector<xcb_atom_t> resolved_; + }; + + shared_conn conn_; + std::map<std::string, size_t> index_; + std::vector<xcb_intern_atom_cookie_t> cookie_; + std::shared_ptr<StorageImpl> storage_; +}; + +} // namespace + +std::unique_ptr<Atoms> Atoms::create(shared_conn conn) { + return std::make_unique<AtomsImpl>(conn); +} + +} // namespace xcb diff --git a/src/xcb_atoms.hh b/src/xcb_atoms.hh new file mode 100644 index 0000000..a0f136e --- /dev/null +++ b/src/xcb_atoms.hh @@ -0,0 +1,57 @@ +#ifndef XCB_ATOMS_HH +#define XCB_ATOMS_HH + +#include "xcb_connection.hh" + +#include <memory> +#include <string> +#include <xcb/xproto.h> + +namespace xcb { + +class Atoms { +protected: + class Storage; + +public: + virtual ~Atoms() = default; + + class Reference { + public: + xcb_atom_t get() { + return atoms_->get(id_); + } + + Reference(std::shared_ptr<Storage> atoms, size_t id) + : atoms_(atoms), id_(id) {} + + private: + std::shared_ptr<Storage> atoms_; + size_t id_; + }; + + virtual Reference get(std::string atom) = 0; + + virtual bool sync() = 0; + + static std::unique_ptr<Atoms> create(shared_conn conn); + +protected: + Atoms() = default; + Atoms(Atoms const&) = delete; + Atoms& operator=(Atoms const&) = delete; + + class Storage { + public: + virtual ~Storage() = default; + + virtual xcb_atom_t get(size_t id) const = 0; + + protected: + Storage() = default; + }; +}; + +} // namespace xcb + +#endif // XCB_ATOMS_HH diff --git a/src/xcb_colors.cc b/src/xcb_colors.cc new file mode 100644 index 0000000..f4caca4 --- /dev/null +++ b/src/xcb_colors.cc @@ -0,0 +1,93 @@ +#include "common.hh" + +#include "xcb_colors.hh" +#include "xcb_connection.hh" +#include "xcb_event.hh" + +#include <map> +#include <vector> +#include <xcb/xproto.h> + +namespace xcb { + +namespace { + +class ColorsImpl : public Colors { +public: + ColorsImpl(shared_conn conn, xcb_colormap_t colormap) + : conn_(conn), colormap_(colormap), + storage_(std::make_shared<StorageImpl>()) {} + + Color get_with_fallback(uint8_t r, uint8_t g, uint8_t b, + uint32_t fallback) override { + auto key = make_key(r, g, b); + auto it = index_.find(key); + size_t index; + if (it == index_.end()) { + index = cookie_.size(); + cookie_.push_back(xcb_alloc_color(conn_.get(), colormap_, + static_cast<uint16_t>(r) << 8, + static_cast<uint16_t>(g) << 8, + static_cast<uint16_t>(b) << 8)); + fallback_.push_back(fallback); + index_.emplace(key, index); + } else { + index = it->second; + } + return Color(storage_, index); + } + + bool sync() override { + std::vector<uint32_t> colors; + colors.reserve(cookie_.size()); + for (size_t i = 0; i < cookie_.size(); ++i) { + xcb::reply<xcb_alloc_color_reply_t> reply( + xcb_alloc_color_reply(conn_.get(), cookie_[i], nullptr)); + if (reply) { + colors.push_back(reply->pixel); + } else { + colors.push_back(fallback_[i]); + } + } + storage_->set(std::move(colors)); + return true; + } + +private: + class StorageImpl : public Storage { + public: + uint32_t get(size_t id) const override { + assert(id < resolved_.size()); + return resolved_[id]; + } + + void set(std::vector<uint32_t> resolved) { + resolved_ = std::move(resolved); + } + + private: + std::vector<uint32_t> resolved_; + }; + + static uint32_t make_key(uint8_t r, uint8_t g, uint8_t b) { + return static_cast<uint32_t>(r) << 16 | + static_cast<uint32_t>(g) << 8 | b; + } + + shared_conn conn_; + xcb_colormap_t colormap_; + std::map<uint32_t, size_t> index_; + std::vector<xcb_alloc_color_cookie_t> cookie_; + std::vector<uint32_t> fallback_; + std::shared_ptr<StorageImpl> storage_; +}; + +} // namespace + +std::unique_ptr<Colors> Colors::create(shared_conn conn, + xcb_colormap_t colormap) { + return std::make_unique<ColorsImpl>(conn, colormap); +} + +} // namespace xcb + diff --git a/src/xcb_colors.hh b/src/xcb_colors.hh new file mode 100644 index 0000000..4db2b93 --- /dev/null +++ b/src/xcb_colors.hh @@ -0,0 +1,57 @@ +#ifndef XCB_COLORS_HH +#define XCB_COLORS_HH + +#include "xcb_connection.hh" + +#include <memory> + +namespace xcb { + +class Colors { +protected: + class Storage; + +public: + virtual ~Colors() = default; + + class Color { + public: + uint32_t get() { + return colors_->get(id_); + } + + Color(std::shared_ptr<Storage> colors, size_t id) + : colors_(colors), id_(id) {} + + private: + std::shared_ptr<Storage> colors_; + size_t id_; + }; + + virtual Color get_with_fallback(uint8_t r, uint8_t g, uint8_t b, + uint32_t fallback) = 0; + + virtual bool sync() = 0; + + static std::unique_ptr<Colors> create(shared_conn conn, + xcb_colormap_t colormap); + +protected: + Colors() = default; + Colors(Colors const&) = delete; + Colors& operator=(Colors const&) = delete; + + class Storage { + public: + virtual ~Storage() = default; + + virtual uint32_t get(size_t id) const = 0; + + protected: + Storage() = default; + }; +}; + +} // namespace xcb + +#endif // XCB_COLORS_HH diff --git a/src/xcb_connection.cc b/src/xcb_connection.cc new file mode 100644 index 0000000..66a5a1a --- /dev/null +++ b/src/xcb_connection.cc @@ -0,0 +1,26 @@ +#include "common.hh" + +#include "xcb_connection.hh" + +namespace xcb { + +shared_conn make_shared_conn(xcb_connection_t* conn) { + return std::shared_ptr<xcb_connection_t>( + conn, internal::xcb_connection_deleter()); +} + +unique_conn make_unique_conn(xcb_connection_t* conn) { + return unique_conn(conn); +} + +xcb_screen_t* get_screen(xcb_connection_t* conn, int screen_index) { + auto iter = xcb_setup_roots_iterator(xcb_get_setup(conn)); + for (; iter.rem; --screen_index, xcb_screen_next(&iter)) { + if (screen_index == 0) { + return iter.data; + } + } + return nullptr; +} + +} // namespace xcb diff --git a/src/xcb_connection.hh b/src/xcb_connection.hh new file mode 100644 index 0000000..74ee751 --- /dev/null +++ b/src/xcb_connection.hh @@ -0,0 +1,30 @@ +#ifndef XCB_CONNECTION_HH +#define XCB_CONNECTION_HH + +#include <memory> +#include <xcb/xcb.h> + +namespace xcb { + +namespace internal { + +struct xcb_connection_deleter { + void operator() (xcb_connection_t* ptr) { + xcb_disconnect(ptr); + } +}; + +} // namespace internal + +typedef std::shared_ptr<xcb_connection_t> shared_conn; +typedef std::unique_ptr<xcb_connection_t, + internal::xcb_connection_deleter> unique_conn; + +shared_conn make_shared_conn(xcb_connection_t* conn); +unique_conn make_unique_conn(xcb_connection_t* conn); + +xcb_screen_t* get_screen(xcb_connection_t* conn, int screen_index); + +} // namespace xcb + +#endif // XCB_CONNECTION_HH diff --git a/src/xcb_event.hh b/src/xcb_event.hh new file mode 100644 index 0000000..5934603 --- /dev/null +++ b/src/xcb_event.hh @@ -0,0 +1,32 @@ +#ifndef XCB_EVENT_HH +#define XCB_EVENT_HH + +#include <memory> +#include <stdlib.h> +#include <xcb/xcb.h> +#include <xcb/xcb_event.h> + +namespace xcb { + +namespace internal { + +struct FreeDeleter { + void operator() (void* ptr) { + free(ptr); + } +}; + +} // namespace internal + +typedef std::unique_ptr<xcb_generic_event_t, + internal::FreeDeleter> generic_event; + +typedef std::unique_ptr<xcb_generic_error_t, + internal::FreeDeleter> generic_error; + +template<typename T> +using reply = std::unique_ptr<T, internal::FreeDeleter>; + +} // namespace xcb + +#endif // XCB_EVENT_HH diff --git a/src/xcb_resource.cc b/src/xcb_resource.cc new file mode 100644 index 0000000..b64b611 --- /dev/null +++ b/src/xcb_resource.cc @@ -0,0 +1,37 @@ +#include "common.hh" + +#include "xcb_resource.hh" + +namespace xcb { + +unique_wnd make_unique_wnd(shared_conn conn) { + return std::make_unique<xcb_resource<xcb_window_t, + internal::WndDeleter>>(conn); +} + +shared_wnd make_shared_wnd(shared_conn conn) { + return std::make_shared<xcb_resource<xcb_window_t, + internal::WndDeleter>>(conn); +} + +unique_gc make_unique_gc(shared_conn conn) { + return std::make_unique<xcb_resource<xcb_gcontext_t, + internal::GCDeleter>>(conn); +} + +shared_gc make_shared_gc(shared_conn conn) { + return std::make_shared<xcb_resource<xcb_gcontext_t, + internal::GCDeleter>>(conn); +} + +unique_font make_unique_font(shared_conn conn) { + return std::make_unique<xcb_resource<xcb_font_t, + internal::FontDeleter>>(conn); +} + +shared_font make_shared_font(shared_conn conn) { + return std::make_shared<xcb_resource<xcb_font_t, + internal::FontDeleter>>(conn); +} + +} // namespace xcb diff --git a/src/xcb_resource.hh b/src/xcb_resource.hh new file mode 100644 index 0000000..15619e5 --- /dev/null +++ b/src/xcb_resource.hh @@ -0,0 +1,105 @@ +#ifndef XCB_RESOURCE_HH +#define XCB_RESOURCE_HH + +#include "xcb_connection.hh" + +#include <memory> +#include <xcb/xproto.h> + +namespace xcb { + +namespace internal { + +struct WndDeleter { + void operator() (xcb_connection_t* conn, xcb_window_t wnd) const { + xcb_destroy_window(conn, wnd); + } +}; + +struct GCDeleter { + void operator() (xcb_connection_t* conn, xcb_gcontext_t gc) const { + xcb_free_gc(conn, gc); + } +}; + +struct FontDeleter { + void operator() (xcb_connection_t* conn, xcb_font_t font) const { + xcb_close_font(conn, font); + } +}; + +} // namespace internal + +template<typename T, typename Deleter> +class xcb_resource { +public: + explicit xcb_resource(shared_conn conn) + : conn_(conn), id_(xcb_generate_id(conn_.get())) {} + constexpr xcb_resource() + : id_(XCB_NONE) {} + xcb_resource(xcb_resource const& res) = delete; + xcb_resource(xcb_resource&& res) + : conn_(res.conn_), id_(res.release()) { } + ~xcb_resource() { + reset(); + } + + xcb_resource& operator=(xcb_resource const& res) = delete; + xcb_resource& operator=(xcb_resource&& res) { + reset(); + conn_ = res.conn_; + id_ = res.release(); + return *this; + } + + T id() const { + return id_; + } + + void reset() { + if (id_ == XCB_NONE) + return; + deleter_(conn_.get(), id_); + id_ = XCB_NONE; + } + + T release() { + auto ret = id_; + id_ = XCB_NONE; + conn_.reset(); + return ret; + } + +private: + shared_conn conn_; + T id_; + Deleter const deleter_{}; +}; + +typedef std::unique_ptr<xcb_resource<xcb_window_t, + internal::WndDeleter>> unique_wnd; +typedef std::shared_ptr<xcb_resource<xcb_window_t, + internal::WndDeleter>> shared_wnd; + +unique_wnd make_unique_wnd(shared_conn conn); +shared_wnd make_shared_wnd(shared_conn conn); + +typedef std::unique_ptr<xcb_resource<xcb_gcontext_t, + internal::GCDeleter>> unique_gc; +typedef std::shared_ptr<xcb_resource<xcb_gcontext_t, + internal::GCDeleter>> shared_gc; + +unique_gc make_unique_gc(shared_conn conn); +shared_gc make_shared_gc(shared_conn conn); + +typedef std::unique_ptr<xcb_resource<xcb_font_t, + internal::FontDeleter>> unique_font; +typedef std::shared_ptr<xcb_resource<xcb_font_t, + internal::FontDeleter>> shared_font; + +unique_font make_unique_font(shared_conn conn); +shared_font make_shared_font(shared_conn conn); + +} // namespace xcb + +#endif // XCB_RESOURCE_HH diff --git a/src/xcb_resources.hh b/src/xcb_resources.hh new file mode 100644 index 0000000..5e04e26 --- /dev/null +++ b/src/xcb_resources.hh @@ -0,0 +1,35 @@ +#ifndef XCB_RESOURCES_HH +#define XCB_RESOURCES_HH + +#include "xcb_connection.hh" + +#include <memory> +#include <optional> +#include <string> + +namespace xcb { + +class Resources { +public: + virtual ~Resources() = default; + + virtual std::optional<std::string> get_string(std::string const& name, + std::string const& clazz) = 0; + + virtual std::optional<long> get_long(std::string const& name, + std::string const& clazz) = 0; + + virtual std::optional<bool> get_bool(std::string const& name, + std::string const& clazz) = 0; + + static std::unique_ptr<Resources> create(shared_conn conn); + +protected: + Resources() = default; + Resources(Resources const&) = delete; + Resources& operator=(Resources const&) = delete; +}; + +} // namespace xcb + +#endif // XCB_RESOURCES_HH diff --git a/src/xcb_resources_none.cc b/src/xcb_resources_none.cc new file mode 100644 index 0000000..be37dd9 --- /dev/null +++ b/src/xcb_resources_none.cc @@ -0,0 +1,35 @@ +#include "common.hh" + +#include "xcb_resources.hh" + +namespace xcb { + +namespace { + +class ResourcesNone : public Resources { +public: + ResourcesNone() = default; + + std::optional<std::string> get_string(std::string const&, + std::string const&) override { + return std::optional<std::string>(); + } + + std::optional<long> get_long(std::string const&, + std::string const&) override { + return std::optional<long>(); + } + + std::optional<bool> get_bool(std::string const&, + std::string const&) override { + return std::optional<bool>(); + } +}; + +} // namespace + +std::unique_ptr<Resources> Resources::create(shared_conn) { + return std::make_unique<ResourcesNone>(); +} + +} // namespace xcb diff --git a/src/xcb_resources_xrm.cc b/src/xcb_resources_xrm.cc new file mode 100644 index 0000000..a9149af --- /dev/null +++ b/src/xcb_resources_xrm.cc @@ -0,0 +1,78 @@ +#include "common.hh" + +#include "xcb_resources.hh" + +#include <xcb/xcb_xrm.h> + +namespace xcb { + +namespace { + +struct DatabaseDeleter { + void operator() (xcb_xrm_database_t* db) { + xcb_xrm_database_free(db); + } +}; + +class ResourcesXrm : public Resources { +public: + explicit ResourcesXrm(shared_conn conn) + : conn_(conn), db_(xcb_xrm_database_from_default(conn_.get())) { + } + + std::optional<std::string> get_string(std::string const& name, + std::string const& clazz) override { + if (db_) { + char* tmp; + if (xcb_xrm_resource_get_string(db_.get(), + std::string(name).c_str(), + std::string(clazz).c_str(), + &tmp) >= 0) { + std::string ret(tmp); + free(tmp); + return ret; + } + } + return std::optional<std::string>(); + } + + std::optional<long> get_long(std::string const& name, + std::string const& clazz) override { + if (db_) { + long tmp; + if (xcb_xrm_resource_get_long(db_.get(), + std::string(name).c_str(), + std::string(clazz).c_str(), + &tmp) >= 0) { + return tmp; + } + } + return std::optional<long>(); + } + + std::optional<bool> get_bool(std::string const& name, + std::string const& clazz) override { + if (db_) { + bool tmp; + if (xcb_xrm_resource_get_bool(db_.get(), + std::string(name).c_str(), + std::string(clazz).c_str(), + &tmp) >= 0) { + return tmp; + } + } + return std::optional<bool>(); + } + +private: + shared_conn conn_; + std::unique_ptr<xcb_xrm_database_t, DatabaseDeleter> db_; +}; + +} // namespace + +std::unique_ptr<Resources> Resources::create(shared_conn conn) { + return std::make_unique<ResourcesXrm>(conn); +} + +} // namespace xcb diff --git a/src/xcb_xkb.cc b/src/xcb_xkb.cc new file mode 100644 index 0000000..f50ffe1 --- /dev/null +++ b/src/xcb_xkb.cc @@ -0,0 +1,171 @@ +#include "common.hh" + +#include "xcb_event.hh" +#include "xcb_xkb.hh" + +#define explicit dont_use_cxx_explicit +#include <xcb/xkb.h> +#undef explicit +#include <xkbcommon/xkbcommon-x11.h> + +namespace xcb { + +namespace { + +struct KeymapDeleter { + void operator() (xkb_keymap* keymap) const { + if (keymap) + xkb_keymap_unref(keymap); + } +}; + +struct StateDeleter { + void operator() (xkb_state* state) const { + if (state) + xkb_state_unref(state); + } +}; + +struct ContextDeleter { + void operator() (xkb_context* context) const { + if (context) + xkb_context_unref(context); + } +}; + +class KeyboardImpl : public Keyboard { +public: + KeyboardImpl() = default; + + bool init(xcb_connection_t* conn) { + if (!xkb_x11_setup_xkb_extension(conn, + XKB_X11_MIN_MAJOR_XKB_VERSION, + XKB_X11_MIN_MINOR_XKB_VERSION, + XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, + NULL, NULL, &first_xkb_event_, NULL)) + return false; + + ctx_.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS)); + if (!ctx_) + return false; + device_id_ = xkb_x11_get_core_keyboard_device_id(conn); + if (device_id_ == -1) + return false; + if (!update_keymap(conn)) + return false; + select_events(conn); + return true; + } + + bool handle_event(xcb_connection_t* conn, + xcb_generic_event_t* event) override { + if (XCB_EVENT_RESPONSE_TYPE(event) == first_xkb_event_) { + auto* xkb_event = reinterpret_cast<xkb_generic_event_t*>(event); + if (xkb_event->deviceID == device_id_) { + switch (xkb_event->xkbType) { + case XCB_XKB_NEW_KEYBOARD_NOTIFY: { + auto* e = + reinterpret_cast<xcb_xkb_new_keyboard_notify_event_t*>(event); + if (e->changed & XCB_XKB_NKN_DETAIL_KEYCODES) + update_keymap(conn); + break; + } + case XCB_XKB_MAP_NOTIFY: + update_keymap(conn); + break; + case XCB_XKB_STATE_NOTIFY: { + auto* e = + reinterpret_cast<xcb_xkb_state_notify_event_t*>(event); + xkb_state_update_mask(state_.get(), + e->baseMods, e->latchedMods, e->lockedMods, + e->baseGroup, e->latchedGroup, e->lockedGroup); + break; + } + } + } + return true; + } + return false; + } + + std::string get_utf8(xcb_key_press_event_t* event) override { + char tmp[16]; + xkb_state_key_get_utf8(state_.get(), event->detail, tmp, sizeof(tmp)); + return std::string(tmp); + } + +private: + struct xkb_generic_event_t { + uint8_t response_type; + uint8_t xkbType; + uint16_t sequence; + xcb_timestamp_t time; + uint8_t deviceID; + }; + + bool update_keymap(xcb_connection_t* conn) { + auto* keymap = xkb_x11_keymap_new_from_device(ctx_.get(), conn, device_id_, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) + return false; + auto* state = xkb_x11_state_new_from_device(keymap, conn, device_id_); + if (!state) { + xkb_keymap_unref(keymap); + return false; + } + keymap_.reset(keymap); + state_.reset(state); + return true; + } + + void select_events(xcb_connection_t *conn) { + static const uint16_t new_keyboard_details = XCB_XKB_NKN_DETAIL_KEYCODES; + static const uint16_t map_parts = XCB_XKB_MAP_PART_KEY_TYPES | + XCB_XKB_MAP_PART_KEY_SYMS | + XCB_XKB_MAP_PART_MODIFIER_MAP | + XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS | + XCB_XKB_MAP_PART_KEY_ACTIONS | + XCB_XKB_MAP_PART_VIRTUAL_MODS | + XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP; + static const uint16_t state_details = XCB_XKB_STATE_PART_MODIFIER_BASE | + XCB_XKB_STATE_PART_MODIFIER_LATCH | + XCB_XKB_STATE_PART_MODIFIER_LOCK | + XCB_XKB_STATE_PART_GROUP_BASE | + XCB_XKB_STATE_PART_GROUP_LATCH | + XCB_XKB_STATE_PART_GROUP_LOCK; + + xcb_xkb_select_events_details_t details = {}; + details.affectNewKeyboard = new_keyboard_details; + details.newKeyboardDetails = new_keyboard_details; + details.affectState = state_details; + details.stateDetails = state_details; + + xcb_xkb_select_events_aux(conn, + device_id_, + XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY | + XCB_XKB_EVENT_TYPE_MAP_NOTIFY | + XCB_XKB_EVENT_TYPE_STATE_NOTIFY, + 0, + 0, + map_parts, + map_parts, + &details); + } + + std::unique_ptr<xkb_context, ContextDeleter> ctx_; + std::unique_ptr<xkb_keymap, KeymapDeleter> keymap_; + std::unique_ptr<xkb_state, StateDeleter> state_; + uint8_t first_xkb_event_; + int32_t device_id_; +}; + +} // namespace + +std::unique_ptr<Keyboard> Keyboard::create(xcb_connection_t* conn) { + auto ret = std::make_unique<KeyboardImpl>(); + if (ret->init(conn)) + return ret; + return nullptr; +} + +} // namespace xcb diff --git a/src/xcb_xkb.hh b/src/xcb_xkb.hh new file mode 100644 index 0000000..7bb3681 --- /dev/null +++ b/src/xcb_xkb.hh @@ -0,0 +1,30 @@ +#ifndef XCB_XKB_HH +#define XCB_XKB_HH + +#include <memory> +#include <string> +#include <xcb/xcb.h> +#include <xcb/xproto.h> + +namespace xcb { + +class Keyboard { +public: + virtual ~Keyboard() = default; + + virtual bool handle_event(xcb_connection_t* conn, + xcb_generic_event_t* event) = 0; + + virtual std::string get_utf8(xcb_key_press_event_t* event) = 0; + + static std::unique_ptr<Keyboard> create(xcb_connection_t* conn); + +protected: + Keyboard() = default; + Keyboard(Keyboard const&) = delete; + Keyboard& operator=(Keyboard const&) = delete; +}; + +} // namespace xcb + +#endif // XCB_XKB_HH diff --git a/src/xdg.cc b/src/xdg.cc new file mode 100644 index 0000000..b61b4ba --- /dev/null +++ b/src/xdg.cc @@ -0,0 +1,116 @@ +#include "common.hh" + +#include "xdg.hh" + +#include <filesystem> +#include <pwd.h> +#include <stdlib.h> +#include <unistd.h> + +namespace xdg { + +namespace { + +bool valid(char const* path) { + return path && path[0] == '/'; +} + +bool valid(std::string const& path, size_t pos, size_t len) { + return len > 0 && path[pos] == '/'; +} + +std::filesystem::path get_home() { + auto* env = getenv("HOME"); + if (valid(env)) + return env; + auto* pwd = getpwuid(getuid()); + if (pwd && valid(pwd->pw_dir)) + return pwd->pw_dir; + return "/"; +} + +void get_paths(Type type, std::string* dirs, std::filesystem::path* home) { + char const* env; + switch (type) { + case Type::CONFIG: + if (dirs) { + env = getenv("XDG_CONFIG_DIRS"); + if (valid(env)) { + dirs->assign(env); + } else { + dirs->assign("/etc/xdg"); + } + } + if (home) { + env = getenv("XDG_CONFIG_HOME"); + if (valid(env)) { + *home = env; + } else { + *home = get_home() / ".config"; + } + } + break; + case Type::DATA: + if (dirs) { + env = getenv("XDG_DATA_DIRS"); + if (valid(env)) { + dirs->assign(env); + } else { + dirs->assign("usr/local/share/:/usr/share/"); + } + } + if (home) { + env = getenv("XDG_DATA_HOME"); + if (valid(env)) { + *home = env; + } else { + *home = get_home() / ".local/share"; + } + } + break; + case Type::CACHE: + if (dirs) + dirs->clear(); + if (home) { + env = getenv("XDG_CACHE_HOME"); + if (valid(env)) { + *home = env; + } else { + *home = get_home() / ".cache"; + } + } + break; + } +} + +} // namespace + +void paths_to_read(Type type, std::string_view name, + std::vector<std::filesystem::path>* out) { + std::string dirs; + std::filesystem::path home; + get_paths(type, &dirs, &home); + out->clear(); + out->push_back(home / name); + size_t last = 0; + while (true) { + size_t next = dirs.find(':', last); + if (next == std::string::npos) { + if (valid(dirs, last, dirs.size() - last)) + out->push_back(std::filesystem::path(dirs.substr(last)) / name); + break; + } + if (valid(dirs, last, next - last)) + out->push_back( + std::filesystem::path(dirs.substr(last, next - last)) / name); + last = next + 1; + } +} + +std::filesystem::path path_to_write(Type type, std::string_view name) { + std::filesystem::path home; + get_paths(type, nullptr, &home); + return home / name; +} + +} // namespace xdg diff --git a/src/xdg.hh b/src/xdg.hh new file mode 100644 index 0000000..1c7e4f0 --- /dev/null +++ b/src/xdg.hh @@ -0,0 +1,23 @@ +#ifndef XDG_HH +#define XDG_HH + +#include <filesystem> +#include <string_view> +#include <vector> + +namespace xdg { + +enum class Type { + CONFIG, + DATA, + CACHE, +}; + +void paths_to_read(Type type, std::string_view path, + std::vector<std::filesystem::path>* out); + +std::filesystem::path path_to_write(Type type, std::string_view path); + +} // namespace xdg + +#endif // XDG_HH |
