diff options
| -rw-r--r-- | .gitignore | 22 | ||||
| -rw-r--r-- | Makefile.am | 7 | ||||
| -rw-r--r-- | configure.ac | 109 | ||||
| -rw-r--r-- | m4/ac_attribute.m4 | 47 | ||||
| -rw-r--r-- | m4/ax_append_compile_flags.m4 | 65 | ||||
| -rw-r--r-- | m4/ax_append_flag.m4 | 69 | ||||
| -rw-r--r-- | m4/ax_cflags_warn_all.m4 | 122 | ||||
| -rw-r--r-- | m4/pkg.m4 | 158 | ||||
| -rw-r--r-- | src/.gitignore | 5 | ||||
| -rw-r--r-- | src/Makefile.am | 9 | ||||
| -rw-r--r-- | src/common.h | 19 | ||||
| -rw-r--r-- | src/main.c | 893 |
12 files changed, 1525 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c9d63bb --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +.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 diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..29abe97 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,7 @@ +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 diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..e6e71ec --- /dev/null +++ b/configure.ac @@ -0,0 +1,109 @@ +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_GNU_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE" +AX_CFLAGS_WARN_ALL([DEFINES]) +AC_ARG_ENABLE([debug], AC_HELP_STRING([compile with debug options]), + if test "x$enableval" = "xyes"; then + DEFINES="$DEFINES -g -DDEBUG" + 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 +fi +AM_CONDITIONAL([HAVE_PTHREAD], [test "x$have_pthread" = "x1"]) + +if test "x$have_pthread" = "x0"; then + AC_MSG_ERROR([no thread implementation found]) +fi + +# timeval + +# clock + +have_clock_gettime=0 +have_clock_nanosleep=0 +CLOCK_LIBS= + +AC_MSG_CHECKING([for clock_gettime]) +AC_LINK_IFELSE([AC_LANG_CALL([], [clock_gettime])], + AC_MSG_RESULT([yes]) + have_clock_gettime=1, + AC_MSG_RESULT([no]) + OLDLIBS="$LIBS" + LIBS="$LIBS -lrt" + AC_MSG_CHECKING([for clock_gettime in -lrt]) + AC_LINK_IFELSE([AC_LANG_CALL([], [clock_gettime])], + AC_MSG_RESULT([yes]) + have_clock_gettime=1 + CLOCK_LIBS="$CLOCK_LIBS -lrt", + AC_MSG_RESULT([no])) + LIBS="$OLDLIBS") +AC_DEFINE_UNQUOTED([HAVE_CLOCK_GETTIME], [$have_clock_gettime], [define to 1 if clock_gettime is available]) +AC_MSG_CHECKING([for clock_nanosleep]) +AC_LINK_IFELSE([AC_LANG_CALL([], [clock_nanosleep])], + AC_MSG_RESULT([yes]) + have_clock_nanosleep=1, + AC_MSG_RESULT([no]) + OLDLIBS="$LIBS" + LIBS="$LIBS -lrt" + AC_MSG_CHECKING([for clock_nanosleep in -lrt]) + AC_LINK_IFELSE([AC_LANG_CALL([], [clock_nanosleep])], + AC_MSG_RESULT([yes]) + have_clock_nanosleep=1 + CLOCK_LIBS="$CLOCK_LIBS -lrt", + AC_MSG_RESULT([no])) + LIBS="$OLDLIBS") +AC_DEFINE_UNQUOTED([HAVE_CLOCK_NANOSLEEP], [$have_clock_nanosleep], [define to 1 if clock_nanosleep is available]) +AC_SUBST([CLOCK_LIBS]) + +# 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/m4/ac_attribute.m4 b/m4/ac_attribute.m4 new file mode 100644 index 0000000..23479a9 --- /dev/null +++ b/m4/ac_attribute.m4 @@ -0,0 +1,47 @@ +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 new file mode 100644 index 0000000..1f8e708 --- /dev/null +++ b/m4/ax_append_compile_flags.m4 @@ -0,0 +1,65 @@ +# =========================================================================== +# 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 new file mode 100644 index 0000000..1d38b76 --- /dev/null +++ b/m4/ax_append_flag.m4 @@ -0,0 +1,69 @@ +# =========================================================================== +# 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 new file mode 100644 index 0000000..0fa3e18 --- /dev/null +++ b/m4/ax_cflags_warn_all.m4 @@ -0,0 +1,122 @@ +# =========================================================================== +# 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 new file mode 100644 index 0000000..bfe1dd1 --- /dev/null +++ b/m4/pkg.m4 @@ -0,0 +1,158 @@ +# 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/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..71eefc4 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,5 @@ +/compiler.h +/paths.h +/safe_fifo.h +/thread.h +/timer diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..c4ad583 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,9 @@ +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 +timer_CFLAGS = @XCB_CFLAGS@ +timer_LDADD = libpaths.a libcommon.a @XCB_LIBS@ @CLOCK_LIBS@ diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..974e03f --- /dev/null +++ b/src/common.h @@ -0,0 +1,19 @@ +#ifndef COMMON_H +#define COMMON_H + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <stdbool.h> +#include <stdlib.h> + +#ifdef DEBUG +# include <assert.h> +#else +# define assert(x) /* x */ +#endif + +#define API + +#endif /* COMMON_H */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..6c30fec --- /dev/null +++ b/src/main.c @@ -0,0 +1,893 @@ +#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 + +#include "compiler.h" +#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); + } +#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; +} |
