From 69a05d516bd0c7e369a0ba1fb78aac57a5ea26f2 Mon Sep 17 00:00:00 2001
From: Simeon Simeonov
Date: Thu, 12 Mar 2020 12:30:51 +0100
Subject: Initial commit for the last i3lock commit at this time
f3b061233e28ad0cf920c9421adc2459b6920812
---
.clang-format | 10 +
.github/CONTRIBUTING.md | 3 +
.github/ISSUE_TEMPLATE.md | 29 +
.gitignore | 31 +
.travis.yml | 17 +
CHANGELOG | 187 ++++++
I3LOCK_VERSION | 1 +
LICENSE | 27 +
Makefile.am | 58 ++
README.md | 76 +++
configure.ac | 154 +++++
cursors.h | 8 +
dpi.c | 109 ++++
dpi.h | 22 +
i3lock.1 | 173 ++++++
i3lock.c | 1318 +++++++++++++++++++++++++++++++++++++++++++
i3lock.h | 14 +
m4/ax_append_flag.m4 | 71 +++
m4/ax_cflags_warn_all.m4 | 122 ++++
m4/ax_check_compile_flag.m4 | 74 +++
m4/ax_check_enable_debug.m4 | 124 ++++
m4/ax_check_gnu_make.m4 | 84 +++
m4/ax_check_link_flag.m4 | 74 +++
m4/ax_code_coverage.m4 | 273 +++++++++
m4/ax_configure_args.m4 | 70 +++
m4/ax_enable_builddir.m4 | 302 ++++++++++
m4/ax_extend_srcdir.m4 | 86 +++
m4/ax_require_defined.m4 | 37 ++
m4/ax_sanitizers.m4 | 130 +++++
pam/i3lock | 6 +
randr.c | 297 ++++++++++
randr.h | 17 +
travis/Dockerfile | 25 +
unlock_indicator.c | 361 ++++++++++++
unlock_indicator.h | 26 +
xcb.c | 434 ++++++++++++++
xcb.h | 17 +
37 files changed, 4867 insertions(+)
create mode 100644 .clang-format
create mode 100644 .github/CONTRIBUTING.md
create mode 100644 .github/ISSUE_TEMPLATE.md
create mode 100644 .gitignore
create mode 100644 .travis.yml
create mode 100644 CHANGELOG
create mode 100644 I3LOCK_VERSION
create mode 100644 LICENSE
create mode 100644 Makefile.am
create mode 100644 README.md
create mode 100644 configure.ac
create mode 100644 cursors.h
create mode 100644 dpi.c
create mode 100644 dpi.h
create mode 100644 i3lock.1
create mode 100644 i3lock.c
create mode 100644 i3lock.h
create mode 100644 m4/ax_append_flag.m4
create mode 100644 m4/ax_cflags_warn_all.m4
create mode 100644 m4/ax_check_compile_flag.m4
create mode 100644 m4/ax_check_enable_debug.m4
create mode 100644 m4/ax_check_gnu_make.m4
create mode 100644 m4/ax_check_link_flag.m4
create mode 100644 m4/ax_code_coverage.m4
create mode 100644 m4/ax_configure_args.m4
create mode 100644 m4/ax_enable_builddir.m4
create mode 100644 m4/ax_extend_srcdir.m4
create mode 100644 m4/ax_require_defined.m4
create mode 100644 m4/ax_sanitizers.m4
create mode 100644 pam/i3lock
create mode 100644 randr.c
create mode 100644 randr.h
create mode 100644 travis/Dockerfile
create mode 100644 unlock_indicator.c
create mode 100644 unlock_indicator.h
create mode 100644 xcb.c
create mode 100644 xcb.h
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..1d84013
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,10 @@
+BasedOnStyle: google
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortBlocksOnASingleLine: false
+AlwaysBreakBeforeMultilineStrings: false
+IndentWidth: 4
+PointerBindsToType: false
+ColumnLimit: 0
+SpaceBeforeParens: ControlStatements
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..ab0456b
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+Please do not send/request features related to image manipulation. i3lock’s
+support for background images is intentionally kept minimal, you should do all
+pre-processing in external tools.
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..591fdfe
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,29 @@
+
+
+## I'm submitting a…
+
+
+[ ] Bug
+[ ] Feature Request
+[ ] Other (Please describe in detail)
+
+
+## Current Behavior
+
+
+## Expected Behavior
+
+
+## Reproduction Instructions
+
+
+## Environment
+Output of `i3lock --version`:
+
+i3lock version:
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..eb37b56
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,31 @@
+/i3lock
+*.o
+tags
+*.swp
+
+################################################################################
+# https://raw.githubusercontent.com/github/gitignore/master/Autotools.gitignore
+################################################################################
+
+# http://www.gnu.org/software/automake
+
+Makefile.in
+/ar-lib
+/test-driver
+
+# http://www.gnu.org/software/autoconf
+
+/autom4te.cache
+/autoscan.log
+/autoscan-*.log
+/aclocal.m4
+/compile
+/config.h.in
+/config.guess
+/config.sub
+/configure
+/configure.scan
+/depcomp
+/install-sh
+/missing
+/stamp-h1
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..15ad1e4
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,17 @@
+# Use Ubuntu 14.04 (trusty), as per http://blog.travis-ci.com/2015-10-14-opening-up-ubuntu-trusty-beta/
+sudo: false
+dist: trusty
+services:
+ - docker
+language: c
+compiler:
+ - gcc
+ - clang
+addons:
+ apt:
+ packages:
+ - clang-format-3.5
+script:
+ - clang-format-3.5 -i *.[ch] && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false)
+ - docker build --pull --no-cache --rm -t=i3lock -f travis/Dockerfile .
+ - docker run -e CC=$CC -v $PWD:/usr/src:rw i3lock /bin/sh -c 'autoreconf -fi && mkdir -p build && cd build && (../configure || (cat config.log; false)) && make -j V=1 CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Werror"'
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
index 0000000..b9de06a
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,187 @@
+2019-07-21 i3lock 2.12
+
+ • remove stray \n from error messages
+ • capitalize unlock indicator contents
+ • set WM_CLASS property
+ • reference modifier as “Super”, not “Win”
+ • add --raw option to read image as raw bytes
+
+2018-10-18 i3lock 2.11.1
+
+ • Fix dist tarball by including I3LOCK_VERSION
+
+2018-10-10 i3lock 2.11
+
+ • Switch to autotools
+ • Display an error when backspace is pressed without any input
+ • Print an error when a non-PNG file is opened
+ (i3lock only supports PNG files) (Thanks eplanet)
+ • Don’t unnecessarily check the xcb_connect return value,
+ it is known never to be NULL (Thanks SegFault42)
+ • Fix memory leak when grabbing fails (Thanks karulont)
+ • Respect Xft.dpi for determining the unlock indicator’s scale factor
+ • Discard pending password verification attempts
+ when a new password is entered (Thanks layus)
+
+2017-11-25 i3lock 2.10
+
+ • Only use -lpam when not on OpenBSD (Thanks Kaashif)
+ • locale: treat empty string same as unset (Thanks Ran)
+ • Fix overwrite of getopt optind (Thanks jakob)
+ • Immediately hide the unlock indicator after ESC / C-u (Thanks Orestis)
+ • Measure wall-clock time instead of CPU time for “locking” indicator.
+ • SetInputFocus to the i3lock window to force-close context menus
+ • Use RandR for learning about attached monitors
+
+2017-06-21 i3lock 2.9.1
+
+ • Fix version number mechanism (for --version)
+ • Revert the fix for composited notifications, as it causes more issues than
+ it solves:
+ https://github.com/i3/i3lock/issues/130
+ https://github.com/i3/i3lock/issues/128
+
+2017-05-26 i3lock 2.9
+
+ • i3lock.1: use signal names without SIG prefix
+ • Removed obsolete inactivity timeout
+ • Added version files for release tarball.
+ • Set font face
+ • Automatically unlock (without having to press ) one attempt which was
+ entered while authenticating
+ • Stop leaking the image_path dup
+ • Displaying locking message when grabbing the pointer/keyboard
+ • Display error message when locking failed
+ • Add Enter on C-m
+ • Change input slices to be exactly pi/3 in size instead of slightly more
+ • Fix covering of composited notifications using the XComposite extension
+ • Remove last traces of DPMS
+ • Use bsd_auth(3) instead of PAM on OpenBSD
+ • Restore intended behaviour and don't use mlock(2) on OpenBSD.
+
+2016-06-04 i3lock 2.8
+
+ • Remove DPMS support in favor of a wrapper script and xset(1).
+ • Indicate that the --inactivity-timeout option takes an argument. (Thanks
+ Kenneth Lyons)
+ • fix pam_securetty: set PAM_TTY to getenv("DISPLAY")
+ • Eat XKB_KEY_Delete and XKB_KEY_KP_Delete (Thanks bebehei)
+ • Show unlock indicator if password was entered during PAM verification
+ • Allow CTRL+J as enter and CTRL+H as backspace (Thanks Karl Tarbe)
+ • Flush xcb connection after opening fullscreen window (Thanks martin)
+ • Add support for `xss-lock --transfer-sleep-lock'
+
+2015-05-20 i3lock 2.7
+
+ • Die when the X11 connection breaks during runtime (Thanks Eduan)
+ • Implement logging the number of failed attempts (Thanks koebi)
+ • Ignore password validation is pam is in wrong state (Thanks Neodyblue)
+ • Get current user with getpwuid() instead of using $ENV{USER} (Thanks Martin)
+ • Add support for Compose and dead-keys with libxkbcommon.
+ Requires libxkbcommon ≥ 0.5.0 (Thanks Daniel)
+ • Format the source using clang-format.
+ • Refresh pam credentials on successful authentication (for Kerberos and the
+ like) (Thanks James)
+ • List pressed modifiers on failed authentication (Thanks Deiz, Alexandre)
+ • Only redraw the screen if the unlock indicator is actually used
+ (Thanks Ingo)
+ • Make pkg-config configurable for cross-compilation (Thanks Nikolay)
+
+2014-07-18 i3lock 2.6
+
+ • NEW DEPENDENCY: use libxkbcommon-x11 instead of libX11
+ This helps us get rid of all code that directly uses libX11
+ • Use cairo_status_to_string for descriptive errors.
+ • Add `-e` option to not validate empty password.
+ • Bugfix: update the initial keyboard modifier state (Thanks lee, Ran)
+ • Re-raise i3lock when obscured in a separate process
+ • Turn on the screen on successful authentication
+ • Delay to turn off the screen after wrong passwd
+ • Discard half-entered passwd after some inactivity
+ • Ignore empty passwd after repeated Enter keypress
+ • Scale the unlock indicator (for retina displays)
+
+2013-06-09 i3lock 2.5
+
+ • NEW DEPENDENCY: Use libxkbcommon for input handling
+ This makes input handling much better for many edge cases.
+ • Bugfix: fix argument parsing on ARM (s/char/int/)
+ • Bugfix: free(reply) to avoid memory leak
+ • Bugfix: Use ev_loop_fork after fork, fixes forking on kqueue based OSes
+ • Bugfix: Fix centering the indicator in the no-xinerama case
+ • Only use mlock() on Linux, FreeBSD (for example) requires root
+ • promote the "could not load image" message from debug to normal
+ • s/pam_message/pam_response/ (Thanks Tucos)
+ • remove support for NOLIBCAIRO, cairo-xcb is widespread by now
+ • Allow XKB_KEY_XF86ScreenSaver as synonym for enter
+ This keysym is generated on convertible tablets by pressing a hardware
+ lock/unlock button.
+ • Allow passwordless PAM conversations (e.g. fingerprint)
+ • Add ctrl+u password reset
+ • Set window name to i3lock
+
+2012-06-02 i3lock 2.4.1
+
+ • Bugfix: Correctly center unlock indicator after reconfiguring screens
+ (Thanks xeen)
+ • Bugfix: Revert shift lock handling (broke uppercase letters)
+ • Bugfix: Skip shift when getting the modifier mask (Thanks SardemFF7)
+
+2012-04-01 i3lock 2.4
+
+ • Bugfix: Fix background color when using cairo (Thanks Pascal)
+ • Only output text when in debug mode (fixes problems with xautolock)
+ • fallback when the image cannot be loaded
+ • Use (void) instead of () for functions without args (Thanks fernandotcl)
+
+2012-03-15 i3lock 2.3.1
+
+ • Fix compilation on some systems
+
+2012-03-15 i3lock 2.3
+
+ • Implement a visual unlock indicator
+ • Support ISO_Level5_Shift and Caps Lock
+ • Lock the password buffer in memory, clear it in RAM after verifying
+ • Fork after the window is visible, not before
+ • Bugfix: Copy the color depth from parent (root) window instead of
+ hardcoding a depth of 24
+
+2011-11-06 i3lock 2.2
+
+ • Don’t re-grab pointer/keyboard on MappingNotify. In some rare situations,
+ this lead to some keypresses "slipping through" to the last focused window.
+ • Correctly handle Mode_switch/ISO_Level3_Shift
+ • Render to a pixmap which is used as background for the window instead of
+ copying contents on every expose event
+ • Handle screen resolution changes while screen is locked
+ • Manpage: document arguments for every option
+
+2011-05-13 i3lock 2.1
+
+ • Accept return/backspace when the buffer of 512 bytes is full
+ • Handle numpad keys correctly
+ • Handle MappingNotify events
+ • Correctly check for errors when connecting to X11
+ • Add i3lock.pam to not rely on debian’s /etc/pam.d/other anymore
+ • don’t display debug output
+ • add NOLIBCAIRO flag to permit compilation without cairo
+
+2010-09-05 i3lock 2.0
+
+ • Complete rewrite of i3lock. Now using xcb instead of Xlib.
+ • When a window obscures i3lock, it pushes itself back to the top again.
+ • Display version when starting with -v
+
+2009-08-02 i3lock 1.1
+
+ • Implement background pictures (-i) and colors (-c)
+
+2009-05-10 i3lock 1.0
+
+ • Implement PAM support
+ • Implement options for forking, beeping, DPMS
+
+2009-05-01 i3lock 0.9
+
+ • First release, forked from slock 0.9
diff --git a/I3LOCK_VERSION b/I3LOCK_VERSION
new file mode 100644
index 0000000..19462c2
--- /dev/null
+++ b/I3LOCK_VERSION
@@ -0,0 +1 @@
+2.12-non-git
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c060226
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright © 2010-2011, Michael Stapelberg
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of Michael Stapelberg nor the
+ names of contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY Michael Stapelberg ''AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL Michael Stapelberg BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..5847975
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,58 @@
+@CODE_COVERAGE_RULES@
+
+echo-version:
+ @echo "@I3LOCK_VERSION@"
+
+bin_PROGRAMS = i3lock
+
+dist_man1_MANS = i3lock.1
+
+pamddir = $(sysconfdir)/pam.d
+pamd_files = pam/i3lock
+pamd_DATA = $(pamd_files)
+
+AM_CPPFLAGS = \
+ @AX_EXTEND_SRCDIR_CPPFLAGS@
+
+i3lock_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(XCB_CFLAGS) \
+ $(XCB_IMAGE_CFLAGS) \
+ $(XCB_UTIL_CFLAGS) \
+ $(XCB_UTIL_XRM_CFLAGS) \
+ $(XKBCOMMON_CFLAGS) \
+ $(CAIRO_CFLAGS) \
+ $(CODE_COVERAGE_CFLAGS)
+
+i3lock_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(CODE_COVERAGE_CPPFLAGS)
+
+i3lock_LDADD = \
+ $(XCB_LIBS) \
+ $(XCB_IMAGE_LIBS) \
+ $(XCB_UTIL_LIBS) \
+ $(XCB_UTIL_XRM_LIBS) \
+ $(XKBCOMMON_LIBS) \
+ $(CAIRO_LIBS) \
+ $(CODE_COVERAGE_LDFLAGS)
+
+i3lock_SOURCES = \
+ cursors.h \
+ dpi.c \
+ dpi.h \
+ i3lock.c \
+ i3lock.h \
+ randr.c \
+ randr.h \
+ unlock_indicator.c \
+ unlock_indicator.h \
+ xcb.c \
+ xcb.h
+
+EXTRA_DIST = \
+ $(pamd_files) \
+ CHANGELOG \
+ LICENSE \
+ README.md \
+ I3LOCK_VERSION
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..cb2cde9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,76 @@
+i3lock - improved screen locker
+===============================
+[i3lock](https://i3wm.org/i3lock/)> is a simple screen locker like slock.
+After starting it, you will see a white screen (you can configure the
+color/an image). You can return to your screen by entering your password.
+
+Many little improvements have been made to i3lock over time:
+
+- i3lock forks, so you can combine it with an alias to suspend to RAM
+ (run "i3lock && echo mem > /sys/power/state" to get a locked screen
+ after waking up your computer from suspend to RAM)
+
+- You can specify either a background color or a PNG image which will be
+ displayed while your screen is locked. Note that i3lock is not an image
+manipulation software. If you need to resize the image to fill the screen
+or similar, use existing tooling to do this before passing it to i3lock.
+
+- You can specify whether i3lock should bell upon a wrong password.
+
+- i3lock uses PAM and therefore is compatible with LDAP etc.
+ On OpenBSD i3lock uses the bsd_auth(3) framework.
+
+Install
+-------
+
+See [the i3lock home page](https://i3wm.org/i3lock/).
+
+Requirements
+------------
+- pkg-config
+- libxcb
+- libxcb-util
+- libpam-dev
+- libcairo-dev
+- libxcb-xinerama
+- libxcb-randr
+- libev
+- libx11-dev
+- libx11-xcb-dev
+- libxkbcommon >= 0.5.0
+- libxkbcommon-x11 >= 0.5.0
+- libxcb-image
+- libxcb-xrm
+
+Running i3lock
+-------------
+Simply invoke the 'i3lock' command. To get out of it, enter your password and
+press enter.
+
+On OpenBSD the `i3lock` binary needs to be setgid `auth` to call the
+authentication helpers, e.g. `/usr/libexec/auth/login_passwd`.
+
+Building i3lock
+---------------
+We recommend you use the provided package from your distribution. Do not build
+i3lock unless you have a reason to do so.
+
+First install the dependencies listed in requirements section, then run these
+commands (might need to be adapted to your OS):
+```
+autoreconf --force --install
+
+rm -rf build/
+mkdir -p build && cd build/
+
+../configure \
+ --prefix=/usr \
+ --sysconfdir=/etc \
+ --disable-sanitizers
+
+make
+```
+
+Upstream
+--------
+Please submit pull requests to https://github.com/i3/i3lock
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..474f750
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,154 @@
+# -*- Autoconf -*-
+# Run autoreconf -fi to generate a configure script from this file.
+
+AC_PREREQ([2.69])
+AC_INIT([i3lock], [2.12], [https://github.com/i3/i3lock/issues])
+# For AX_EXTEND_SRCDIR
+AX_ENABLE_BUILDDIR
+AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2])
+# Default to silent rules, use V=1 to get verbose compilation output.
+AM_SILENT_RULES([yes])
+# Make it possible to disable maintainer mode to disable re-generation of build
+# system files.
+AM_MAINTAINER_MODE([enable])
+AC_CONFIG_SRCDIR([i3lock.c])
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_MACRO_DIR([m4])
+
+dnl Verify macros defined in m4/ such as AX_SANITIZERS are not present in the
+dnl output, i.e. are replaced as expected. This line results in a better error
+dnl message when using aclocal < 1.13 (which does not understand
+dnl AC_CONFIG_MACRO_DIR) without passing the -I m4 parameter.
+m4_pattern_forbid([AX_SANITIZERS])
+
+# Verify we are using GNU make because we use '%'-style pattern rules in
+# Makefile.am, which are a GNU make extension. Pull requests to replace
+# '%'-style pattern rules with a more portable alternative are welcome.
+AX_CHECK_GNU_MAKE
+AS_VAR_IF([_cv_gnu_make_command], [""], [AC_MSG_ERROR([the i3lock Makefile.am requires GNU make])])
+
+AX_EXTEND_SRCDIR
+
+AS_IF([test -d ${srcdir}/.git],
+ [
+ VERSION="$(git -C ${srcdir} describe --tags --abbrev=0)"
+ I3LOCK_VERSION="$(git -C ${srcdir} describe --tags --always) ($(git -C ${srcdir} log --pretty=format:%cd --date=short -n1), branch \\\"$(git -C ${srcdir} describe --tags --always --all | sed s:heads/::)\\\")"
+ # Mirrors what libi3/is_debug_build.c does:
+ is_release=$(test $(echo "${I3LOCK_VERSION}" | cut -d '(' -f 1 | wc -m) -lt 10 && echo yes || echo no)
+ ],
+ [
+ VERSION="$(cut -d '-' -f 1 ${srcdir}/I3LOCK_VERSION | cut -d ' ' -f 1)"
+ I3LOCK_VERSION="$(sed -e 's/@<:@\"?\\@:>@/\\&/g' ${srcdir}/I3LOCK_VERSION)"
+ is_release="$(grep -q non-git ${srcdir}/I3LOCK_VERSION && echo no || echo yes)"
+ ])
+AC_SUBST([I3LOCK_VERSION], [$I3LOCK_VERSION])
+AC_DEFINE_UNQUOTED([I3LOCK_VERSION], ["${I3LOCK_VERSION}"], [i3lock version])
+
+AX_CODE_COVERAGE
+
+dnl is_release must be lowercase because AX_CHECK_ENABLE_DEBUG calls m4_tolower
+dnl on its fourth argument.
+AX_CHECK_ENABLE_DEBUG([yes], , [UNUSED_NDEBUG], [$is_release])
+
+AC_PROG_CC_C99
+
+# For strnlen() and vasprintf().
+AC_USE_SYSTEM_EXTENSIONS
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_CHECK_HEADER_STDBOOL
+dnl The error message should include the specific type which could not be
+dnl found, but I do not see a way to achieve that.
+AC_CHECK_TYPES([mode_t, off_t, pid_t, size_t, ssize_t], , [AC_MSG_FAILURE([cannot find required type])])
+
+# Checks for library functions.
+AC_FUNC_FORK
+AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK
+AC_FUNC_STRNLEN
+AC_CHECK_FUNCS([atexit dup2 ftruncate getcwd gettimeofday localtime_r memchr memset mkdir rmdir setlocale socket strcasecmp strchr strdup strerror strncasecmp strndup strrchr strspn strstr strtol strtoul], , [AC_MSG_FAILURE([cannot find the $ac_func function, which i3lock requires])])
+
+# Checks for libraries.
+
+AC_SEARCH_LIBS([floor], [m], , [AC_MSG_FAILURE([cannot find the required floor() function despite trying to link with -lm])])
+
+# libev does not ship with a pkg-config file :(.
+AC_SEARCH_LIBS([ev_run], [ev], , [AC_MSG_FAILURE([cannot find the required ev_run() function despite trying to link with -lev])])
+
+AC_SEARCH_LIBS([shm_open], [rt])
+
+# Only disable PAM on OpenBSD where i3lock uses BSD Auth instead
+case "$host" in
+ *-openbsd*)
+ # Nothing yet.
+ ;;
+ *)
+ AC_SEARCH_LIBS([pam_authenticate], [pam])
+ ;;
+esac
+
+AC_SEARCH_LIBS([iconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])])
+
+dnl Each prefix corresponds to a source tarball which users might have
+dnl downloaded in a newer version and would like to overwrite.
+PKG_CHECK_MODULES([XCB], [xcb xcb-xkb xcb-xinerama xcb-randr])
+PKG_CHECK_MODULES([XCB_IMAGE], [xcb-image])
+PKG_CHECK_MODULES([XCB_UTIL], [xcb-event xcb-util xcb-atom])
+PKG_CHECK_MODULES([XCB_UTIL_XRM], [xcb-xrm])
+PKG_CHECK_MODULES([XKBCOMMON], [xkbcommon xkbcommon-x11])
+PKG_CHECK_MODULES([CAIRO], [cairo])
+
+# Checks for programs.
+AC_PROG_AWK
+AC_PROG_CPP
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+AC_PROG_RANLIB
+AC_PROG_LN_S
+
+AM_PROG_AR
+
+AX_FLAGS_WARN_ALL
+AX_CHECK_COMPILE_FLAG([-Wunused-value], [AX_APPEND_FLAG([-Wunused-value], [AM_CFLAGS])])
+AC_SUBST(AM_CFLAGS)
+
+# Checks for header files.
+AC_CHECK_HEADERS([fcntl.h float.h inttypes.h limits.h locale.h netinet/in.h paths.h stddef.h stdint.h stdlib.h string.h sys/param.h sys/socket.h sys/time.h unistd.h], , [AC_MSG_FAILURE([cannot find the $ac_header header, which i3lock requires])])
+
+AC_CONFIG_FILES([Makefile])
+
+# Enable address sanitizer for non-release builds. The performance hit is a
+# 50% increase of wallclock time for the testsuite on my machine.
+if test x$is_release = xyes; then
+ default_sanitizers=
+else
+ default_sanitizers=address
+fi
+AX_SANITIZERS(, [$default_sanitizers], [AC_DEFINE([I3LOCK_ASAN_ENABLED], [], [Enable ASAN])])
+
+AC_OUTPUT
+
+in_git_worktree=`git rev-parse --is-inside-work-tree 2>/dev/null`
+if [[ "$in_git_worktree" = "true" ]]; then
+ git_dir=`git rev-parse --git-dir 2>/dev/null`
+ srcdir=`dirname "$git_dir"`
+ exclude_dir=`pwd | sed "s,^$srcdir,,g"`
+ if ! grep -q "^$exclude_dir" "$git_dir/info/exclude"; then
+ echo "$exclude_dir" >> "$git_dir/info/exclude"
+ fi
+fi
+
+echo \
+"--------------------------------------------------------------------------------
+build configured:
+
+AS_HELP_STRING([i3lock version:], [`echo ${I3LOCK_VERSION} | sed 's,\\\\,,g'`])
+AS_HELP_STRING([is release version:], [${is_release}])
+
+AS_HELP_STRING([enable debug flags:], [${ax_enable_debug}])
+AS_HELP_STRING([code coverage:], [${CODE_COVERAGE_ENABLED}])
+AS_HELP_STRING([enabled sanitizers:], [${ax_enabled_sanitizers}])
+
+To compile, run:
+
+ cd `pwd` && make -j8
+--------------------------------------------------------------------------------"
diff --git a/cursors.h b/cursors.h
new file mode 100644
index 0000000..bfce895
--- /dev/null
+++ b/cursors.h
@@ -0,0 +1,8 @@
+#ifndef _CURSORS_H
+#define _CURSORS_H
+
+#define CURS_NONE 0
+#define CURS_WIN 1
+#define CURS_DEFAULT 2
+
+#endif
diff --git a/dpi.c b/dpi.c
new file mode 100644
index 0000000..3cb08ee
--- /dev/null
+++ b/dpi.c
@@ -0,0 +1,109 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include "dpi.h"
+
+#include
+#include
+#include
+#include
+#include "xcb.h"
+#include "i3lock.h"
+
+extern bool debug_mode;
+
+static long dpi;
+
+extern xcb_screen_t *screen;
+
+static long init_dpi_fallback(void) {
+ return (double)screen->height_in_pixels * 25.4 / (double)screen->height_in_millimeters;
+}
+
+/*
+ * Initialize the DPI setting.
+ * This will use the 'Xft.dpi' X resource if available and fall back to
+ * guessing the correct value otherwise.
+ */
+void init_dpi(void) {
+ xcb_xrm_database_t *database = NULL;
+ char *resource = NULL;
+
+ if (conn == NULL) {
+ goto init_dpi_end;
+ }
+
+ database = xcb_xrm_database_from_default(conn);
+ if (database == NULL) {
+ DEBUG("Failed to open the resource database.\n");
+ goto init_dpi_end;
+ }
+
+ xcb_xrm_resource_get_string(database, "Xft.dpi", NULL, &resource);
+ if (resource == NULL) {
+ DEBUG("Resource Xft.dpi not specified, skipping.\n");
+ goto init_dpi_end;
+ }
+
+ char *endptr;
+ double in_dpi = strtod(resource, &endptr);
+ if (in_dpi == HUGE_VAL || dpi < 0 || *endptr != '\0' || endptr == resource) {
+ DEBUG("Xft.dpi = %s is an invalid number and couldn't be parsed.\n", resource);
+ dpi = 0;
+ goto init_dpi_end;
+ }
+ dpi = (long)round(in_dpi);
+
+ DEBUG("Found Xft.dpi = %ld.\n", dpi);
+
+init_dpi_end:
+ if (resource != NULL) {
+ free(resource);
+ }
+
+ if (database != NULL) {
+ xcb_xrm_database_free(database);
+ }
+
+ if (dpi == 0) {
+ DEBUG("Using fallback for calculating DPI.\n");
+ dpi = init_dpi_fallback();
+ DEBUG("Using dpi = %ld\n", dpi);
+ }
+}
+
+/*
+ * This function returns the value of the DPI setting.
+ *
+ */
+long get_dpi_value(void) {
+ return dpi;
+}
+
+/*
+ * Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI
+ * screen) to a corresponding amount of physical pixels on a standard or retina
+ * screen, e.g. 5 pixels on a 227 DPI MacBook Pro 13" Retina screen.
+ *
+ */
+int logical_px(const int logical) {
+ if (screen == NULL) {
+ /* Dpi info may not be available when parsing a config without an X
+ * server, such as for config file validation. */
+ return logical;
+ }
+
+ /* There are many misconfigurations out there, i.e. systems with screens
+ * whose dpi is in fact higher than 96 dpi, but not significantly higher,
+ * so software was never adapted. We could tell people to reconfigure their
+ * systems to 96 dpi in order to get the behavior they expect/are used to,
+ * but since we can easily detect this case in code, let’s do it for them.
+ */
+ if ((dpi / 96.0) < 1.25)
+ return logical;
+ return ceil((dpi / 96.0) * logical);
+}
diff --git a/dpi.h b/dpi.h
new file mode 100644
index 0000000..35840d2
--- /dev/null
+++ b/dpi.h
@@ -0,0 +1,22 @@
+#pragma once
+
+/**
+ * Initialize the DPI setting.
+ * This will use the 'Xft.dpi' X resource if available and fall back to
+ * guessing the correct value otherwise.
+ */
+void init_dpi(void);
+
+/**
+ * This function returns the value of the DPI setting.
+ *
+ */
+long get_dpi_value(void);
+
+/**
+ * Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI
+ * screen) to a corresponding amount of physical pixels on a standard or retina
+ * screen, e.g. 5 pixels on a 227 DPI MacBook Pro 13" Retina screen.
+ *
+ */
+int logical_px(const int logical);
diff --git a/i3lock.1 b/i3lock.1
new file mode 100644
index 0000000..cefd0d8
--- /dev/null
+++ b/i3lock.1
@@ -0,0 +1,173 @@
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+
+.TH i3lock 1 "JANUARY 2012" Linux "User Manuals"
+
+.SH NAME
+i3lock \- improved screen locker
+
+.SH SYNOPSIS
+.B i3lock
+.RB [\|\-v\|]
+.RB [\|\-n\|]
+.RB [\|\-b\|]
+.RB [\|\-i
+.IR image.png \|]
+.RB [\|\-c
+.IR color \|]
+.RB [\|\-t\|]
+.RB [\|\-p
+.IR pointer\|]
+.RB [\|\-u\|]
+.RB [\|\-e\|]
+.RB [\|\-f\|]
+
+.SH DESCRIPTION
+.B i3lock
+is a simple screen locker like slock. After starting it, you will see a white
+screen (you can configure the color/an image). You can return to your screen by
+entering your password.
+
+.SH IMPROVEMENTS
+
+.IP \[bu] 2
+i3lock forks, so you can combine it with an alias to suspend to RAM (run "i3lock && echo mem > /sys/power/state" to get a locked screen after waking up your computer from suspend to RAM)
+.IP \[bu]
+You can specify either a background color or a PNG image which will be displayed while your screen is locked.
+.IP \[bu]
+You can specify whether i3lock should bell upon a wrong password.
+.IP \[bu]
+i3lock uses PAM and therefore is compatible with LDAP, etc.
+
+
+.SH OPTIONS
+.TP
+.B \-v, \-\-version
+Display the version of your
+.B i3lock
+
+.TP
+.B \-n, \-\-nofork
+Don't fork after starting.
+
+.TP
+.B \-b, \-\-beep
+Enable beeping. Be sure to not do this when you are about to annoy other people,
+like when opening your laptop in a boring lecture.
+
+.TP
+.B \-u, \-\-no-unlock-indicator
+Disable the unlock indicator. i3lock will by default show an unlock indicator
+after pressing keys. This will give feedback for every keypress and it will
+show you the current PAM state (whether your password is currently being
+verified or whether it is wrong).
+
+.TP
+.BI \-i\ path \fR,\ \fB\-\-image= path
+Display the given PNG image instead of a blank screen.
+
+.TP
+.BI \fB\-\-raw= format
+Read the image given by \-\-image as a raw image instead of PNG. The argument is the image's format
+as x:. The supported pixel formats are:
+\'native', 'rgb', 'xrgb', 'rgbx', 'bgr', 'xbgr', and 'bgrx'.
+The "native" pixel format expects a pixel as a 32-bit (4-byte) integer in
+the machine's native endianness, with the upper 8 bits unused. Red, green and blue are stored in
+the remaining bits, in that order.
+
+.BR Example:
+.Vb 6
+\& --raw=1920x1080:rgb
+.Ve
+
+.BR
+You can use ImageMagick’s
+.IR convert(1)
+program to feed raw images into i3lock:
+
+.BR
+.Vb 6
+\& convert wallpaper.jpg RGB:- | i3lock --raw 3840x2160:rgb --image /dev/stdin
+.Ve
+
+This allows you to load a variety of image formats without i3lock having to
+support each one explicitly.
+
+.TP
+.BI \-c\ rrggbb \fR,\ \fB\-\-color= rrggbb
+Turn the screen into the given color instead of white. Color must be given in 3-byte
+format: rrggbb (i.e. ff0000 is red).
+
+.TP
+.B \-t, \-\-tiling
+If an image is specified (via \-i) it will display the image tiled all over the screen
+(if it is a multi-monitor setup, the image is visible on all screens).
+
+.TP
+.BI \-p\ win|default \fR,\ \fB\-\-pointer= win|default
+If you specify "default",
+.B i3lock
+does not hide your mouse pointer. If you specify "win",
+.B i3lock
+displays a hardcoded Windows-Pointer (thus enabling you to mess with your
+friends by using a screenshot of a Windows desktop as a locking-screen).
+
+.TP
+.B \-e, \-\-ignore-empty-password
+When an empty password is provided by the user, do not validate
+it. Without this option, the empty password will be provided to PAM
+and, if invalid, the user will have to wait a few seconds before
+another try. This can be useful if the XF86ScreenSaver key is used to
+put a laptop to sleep and bounce on resume or if you happen to wake up
+your computer with the enter key.
+
+.TP
+.B \-f, \-\-show-failed-attempts
+Show the number of failed attempts, if any.
+
+.TP
+.B \-\-debug
+Enables debug logging.
+Note, that this will log the password used for authentication to stdout.
+
+.SH DPMS
+
+The \-d (\-\-dpms) option was removed from i3lock in version 2.8. There were
+plenty of use-cases that were not properly addressed, and plenty of bugs
+surrounding that feature. While features are not normally removed from i3 and
+its tools, we felt the need to make an exception in this case.
+
+Users who wish to explicitly enable DPMS only when their screen is locked can
+use a wrapper script around i3lock like the following:
+
+.Vb 6
+\& #!/bin/sh
+\& revert() {
+\& xset dpms 0 0 0
+\& }
+\& trap revert HUP INT TERM
+\& xset +dpms dpms 5 5 5
+\& i3lock -n
+\& revert
+.Ve
+
+The \-I (-\-inactivity-timeout=seconds) was removed because it only makes sense with DPMS.
+
+.SH SEE ALSO
+.IR xautolock(1)
+\- use i3lock as your screen saver
+
+.IR convert(1)
+\- feed a wide variety of image formats to i3lock
+
+.SH AUTHOR
+Michael Stapelberg
+
+Jan-Erik Rediger
diff --git a/i3lock.c b/i3lock.c
new file mode 100644
index 0000000..134fdda
--- /dev/null
+++ b/i3lock.c
@@ -0,0 +1,1318 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * © 2010 Michael Stapelberg
+ *
+ * See LICENSE for licensing information
+ *
+ */
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#ifdef __OpenBSD__
+#include
+#else
+#include
+#endif
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#ifdef __OpenBSD__
+#include /* explicit_bzero(3) */
+#endif
+#include
+#include
+
+#include "i3lock.h"
+#include "xcb.h"
+#include "cursors.h"
+#include "unlock_indicator.h"
+#include "randr.h"
+#include "dpi.h"
+
+#define TSTAMP_N_SECS(n) (n * 1.0)
+#define TSTAMP_N_MINS(n) (60 * TSTAMP_N_SECS(n))
+#define START_TIMER(timer_obj, timeout, callback) \
+ timer_obj = start_timer(timer_obj, timeout, callback)
+#define STOP_TIMER(timer_obj) \
+ timer_obj = stop_timer(timer_obj)
+
+typedef void (*ev_callback_t)(EV_P_ ev_timer *w, int revents);
+static void input_done(void);
+
+char color[7] = "ffffff";
+uint32_t last_resolution[2];
+xcb_window_t win;
+static xcb_cursor_t cursor;
+#ifndef __OpenBSD__
+static pam_handle_t *pam_handle;
+static bool pam_cleanup;
+#endif
+int input_position = 0;
+/* Holds the password you enter (in UTF-8). */
+static char password[512];
+static bool beep = false;
+bool debug_mode = false;
+bool unlock_indicator = true;
+char *modifier_string = NULL;
+static bool dont_fork = false;
+struct ev_loop *main_loop;
+static struct ev_timer *clear_auth_wrong_timeout;
+static struct ev_timer *clear_indicator_timeout;
+static struct ev_timer *discard_passwd_timeout;
+extern unlock_state_t unlock_state;
+extern auth_state_t auth_state;
+int failed_attempts = 0;
+bool show_failed_attempts = false;
+bool retry_verification = false;
+
+static struct xkb_state *xkb_state;
+static struct xkb_context *xkb_context;
+static struct xkb_keymap *xkb_keymap;
+static struct xkb_compose_table *xkb_compose_table;
+static struct xkb_compose_state *xkb_compose_state;
+static uint8_t xkb_base_event;
+static uint8_t xkb_base_error;
+static int randr_base = -1;
+
+cairo_surface_t *img = NULL;
+bool tile = false;
+bool ignore_empty_password = false;
+bool skip_repeated_empty_password = false;
+
+/* isutf, u8_dec © 2005 Jeff Bezanson, public domain */
+#define isutf(c) (((c)&0xC0) != 0x80)
+
+/*
+ * Decrements i to point to the previous unicode glyph
+ *
+ */
+void u8_dec(char *s, int *i) {
+ (void)(isutf(s[--(*i)]) || isutf(s[--(*i)]) || isutf(s[--(*i)]) || --(*i));
+}
+
+/*
+ * Loads the XKB keymap from the X11 server and feeds it to xkbcommon.
+ * Necessary so that we can properly let xkbcommon track the keyboard state and
+ * translate keypresses to utf-8.
+ *
+ */
+static bool load_keymap(void) {
+ if (xkb_context == NULL) {
+ if ((xkb_context = xkb_context_new(0)) == NULL) {
+ fprintf(stderr, "[i3lock] could not create xkbcommon context\n");
+ return false;
+ }
+ }
+
+ xkb_keymap_unref(xkb_keymap);
+
+ int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn);
+ DEBUG("device = %d\n", device_id);
+ if ((xkb_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) {
+ fprintf(stderr, "[i3lock] xkb_x11_keymap_new_from_device failed\n");
+ return false;
+ }
+
+ struct xkb_state *new_state =
+ xkb_x11_state_new_from_device(xkb_keymap, conn, device_id);
+ if (new_state == NULL) {
+ fprintf(stderr, "[i3lock] xkb_x11_state_new_from_device failed\n");
+ return false;
+ }
+
+ xkb_state_unref(xkb_state);
+ xkb_state = new_state;
+
+ return true;
+}
+
+/*
+ * Loads the XKB compose table from the given locale.
+ *
+ */
+static bool load_compose_table(const char *locale) {
+ xkb_compose_table_unref(xkb_compose_table);
+
+ if ((xkb_compose_table = xkb_compose_table_new_from_locale(xkb_context, locale, 0)) == NULL) {
+ fprintf(stderr, "[i3lock] xkb_compose_table_new_from_locale failed\n");
+ return false;
+ }
+
+ struct xkb_compose_state *new_compose_state = xkb_compose_state_new(xkb_compose_table, 0);
+ if (new_compose_state == NULL) {
+ fprintf(stderr, "[i3lock] xkb_compose_state_new failed\n");
+ return false;
+ }
+
+ xkb_compose_state_unref(xkb_compose_state);
+ xkb_compose_state = new_compose_state;
+
+ return true;
+}
+
+/*
+ * Clears the memory which stored the password to be a bit safer against
+ * cold-boot attacks.
+ *
+ */
+static void clear_password_memory(void) {
+#ifdef __OpenBSD__
+ /* Use explicit_bzero(3) which was explicitly designed not to be
+ * optimized out by the compiler. */
+ explicit_bzero(password, strlen(password));
+#else
+ /* A volatile pointer to the password buffer to prevent the compiler from
+ * optimizing this out. */
+ volatile char *vpassword = password;
+ for (size_t c = 0; c < sizeof(password); c++)
+ /* We store a non-random pattern which consists of the (irrelevant)
+ * index plus (!) the value of the beep variable. This prevents the
+ * compiler from optimizing the calls away, since the value of 'beep'
+ * is not known at compile-time. */
+ vpassword[c] = c + (int)beep;
+#endif
+}
+
+ev_timer *start_timer(ev_timer *timer_obj, ev_tstamp timeout, ev_callback_t callback) {
+ if (timer_obj) {
+ ev_timer_stop(main_loop, timer_obj);
+ ev_timer_set(timer_obj, timeout, 0.);
+ ev_timer_start(main_loop, timer_obj);
+ } else {
+ /* When there is no memory, we just don’t have a timeout. We cannot
+ * exit() here, since that would effectively unlock the screen. */
+ timer_obj = calloc(sizeof(struct ev_timer), 1);
+ if (timer_obj) {
+ ev_timer_init(timer_obj, callback, timeout, 0.);
+ ev_timer_start(main_loop, timer_obj);
+ }
+ }
+ return timer_obj;
+}
+
+ev_timer *stop_timer(ev_timer *timer_obj) {
+ if (timer_obj) {
+ ev_timer_stop(main_loop, timer_obj);
+ free(timer_obj);
+ }
+ return NULL;
+}
+
+/*
+ * Neccessary calls after ending input via enter or others
+ *
+ */
+static void finish_input(void) {
+ password[input_position] = '\0';
+ unlock_state = STATE_KEY_PRESSED;
+ redraw_screen();
+ input_done();
+}
+
+/*
+ * Resets auth_state to STATE_AUTH_IDLE 2 seconds after an unsuccessful
+ * authentication event.
+ *
+ */
+static void clear_auth_wrong(EV_P_ ev_timer *w, int revents) {
+ DEBUG("clearing auth wrong\n");
+ auth_state = STATE_AUTH_IDLE;
+ redraw_screen();
+
+ /* Clear modifier string. */
+ if (modifier_string != NULL) {
+ free(modifier_string);
+ modifier_string = NULL;
+ }
+
+ /* Now free this timeout. */
+ STOP_TIMER(clear_auth_wrong_timeout);
+
+ /* retry with input done during auth verification */
+ if (retry_verification) {
+ retry_verification = false;
+ finish_input();
+ }
+}
+
+static void clear_indicator_cb(EV_P_ ev_timer *w, int revents) {
+ clear_indicator();
+ STOP_TIMER(clear_indicator_timeout);
+}
+
+static void clear_input(void) {
+ input_position = 0;
+ clear_password_memory();
+ password[input_position] = '\0';
+}
+
+static void discard_passwd_cb(EV_P_ ev_timer *w, int revents) {
+ clear_input();
+ STOP_TIMER(discard_passwd_timeout);
+}
+
+static void input_done(void) {
+ STOP_TIMER(clear_auth_wrong_timeout);
+ auth_state = STATE_AUTH_VERIFY;
+ unlock_state = STATE_STARTED;
+ redraw_screen();
+
+#ifdef __OpenBSD__
+ struct passwd *pw;
+
+ if (!(pw = getpwuid(getuid())))
+ errx(1, "unknown uid %u.", getuid());
+
+ if (auth_userokay(pw->pw_name, NULL, NULL, password) != 0) {
+ DEBUG("successfully authenticated\n");
+ clear_password_memory();
+
+ ev_break(EV_DEFAULT, EVBREAK_ALL);
+ return;
+ }
+#else
+ if (pam_authenticate(pam_handle, 0) == PAM_SUCCESS) {
+ DEBUG("successfully authenticated\n");
+ clear_password_memory();
+
+ /* PAM credentials should be refreshed, this will for example update any kerberos tickets.
+ * Related to credentials pam_end() needs to be called to cleanup any temporary
+ * credentials like kerberos /tmp/krb5cc_pam_* files which may of been left behind if the
+ * refresh of the credentials failed. */
+ pam_setcred(pam_handle, PAM_REFRESH_CRED);
+ pam_cleanup = true;
+
+ ev_break(EV_DEFAULT, EVBREAK_ALL);
+ return;
+ }
+#endif
+
+ if (debug_mode)
+ fprintf(stderr, "Authentication failure\n");
+
+ /* Get state of Caps and Num lock modifiers, to be displayed in
+ * STATE_AUTH_WRONG state */
+ xkb_mod_index_t idx, num_mods;
+ const char *mod_name;
+
+ num_mods = xkb_keymap_num_mods(xkb_keymap);
+
+ for (idx = 0; idx < num_mods; idx++) {
+ if (!xkb_state_mod_index_is_active(xkb_state, idx, XKB_STATE_MODS_EFFECTIVE))
+ continue;
+
+ mod_name = xkb_keymap_mod_get_name(xkb_keymap, idx);
+ if (mod_name == NULL)
+ continue;
+
+ /* Replace certain xkb names with nicer, human-readable ones. */
+ if (strcmp(mod_name, XKB_MOD_NAME_CAPS) == 0)
+ mod_name = "Caps Lock";
+ else if (strcmp(mod_name, XKB_MOD_NAME_ALT) == 0)
+ mod_name = "Alt";
+ else if (strcmp(mod_name, XKB_MOD_NAME_NUM) == 0)
+ mod_name = "Num Lock";
+ else if (strcmp(mod_name, XKB_MOD_NAME_LOGO) == 0)
+ mod_name = "Super";
+
+ char *tmp;
+ if (modifier_string == NULL) {
+ if (asprintf(&tmp, "%s", mod_name) != -1)
+ modifier_string = tmp;
+ } else if (asprintf(&tmp, "%s, %s", modifier_string, mod_name) != -1) {
+ free(modifier_string);
+ modifier_string = tmp;
+ }
+ }
+
+ auth_state = STATE_AUTH_WRONG;
+ failed_attempts += 1;
+ clear_input();
+ if (unlock_indicator)
+ redraw_screen();
+
+ /* Clear this state after 2 seconds (unless the user enters another
+ * password during that time). */
+ ev_now_update(main_loop);
+ START_TIMER(clear_auth_wrong_timeout, TSTAMP_N_SECS(2), clear_auth_wrong);
+
+ /* Cancel the clear_indicator_timeout, it would hide the unlock indicator
+ * too early. */
+ STOP_TIMER(clear_indicator_timeout);
+
+ /* beep on authentication failure, if enabled */
+ if (beep) {
+ xcb_bell(conn, 100);
+ xcb_flush(conn);
+ }
+}
+
+static void redraw_timeout(EV_P_ ev_timer *w, int revents) {
+ redraw_screen();
+ STOP_TIMER(w);
+}
+
+static bool skip_without_validation(void) {
+ if (input_position != 0)
+ return false;
+
+ if (skip_repeated_empty_password || ignore_empty_password)
+ return true;
+
+ return false;
+}
+
+/*
+ * Handle key presses. Fixes state, then looks up the key symbol for the
+ * given keycode, then looks up the key symbol (as UCS-2), converts it to
+ * UTF-8 and stores it in the password array.
+ *
+ */
+static void handle_key_press(xcb_key_press_event_t *event) {
+ xkb_keysym_t ksym;
+ char buffer[128];
+ int n;
+ bool ctrl;
+ bool composed = false;
+
+ ksym = xkb_state_key_get_one_sym(xkb_state, event->detail);
+ ctrl = xkb_state_mod_name_is_active(xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED);
+
+ /* The buffer will be null-terminated, so n >= 2 for 1 actual character. */
+ memset(buffer, '\0', sizeof(buffer));
+
+ if (xkb_compose_state && xkb_compose_state_feed(xkb_compose_state, ksym) == XKB_COMPOSE_FEED_ACCEPTED) {
+ switch (xkb_compose_state_get_status(xkb_compose_state)) {
+ case XKB_COMPOSE_NOTHING:
+ break;
+ case XKB_COMPOSE_COMPOSING:
+ return;
+ case XKB_COMPOSE_COMPOSED:
+ /* xkb_compose_state_get_utf8 doesn't include the terminating byte in the return value
+ * as xkb_keysym_to_utf8 does. Adding one makes the variable n consistent. */
+ n = xkb_compose_state_get_utf8(xkb_compose_state, buffer, sizeof(buffer)) + 1;
+ ksym = xkb_compose_state_get_one_sym(xkb_compose_state);
+ composed = true;
+ break;
+ case XKB_COMPOSE_CANCELLED:
+ xkb_compose_state_reset(xkb_compose_state);
+ return;
+ }
+ }
+
+ if (!composed) {
+ n = xkb_keysym_to_utf8(ksym, buffer, sizeof(buffer));
+ }
+
+ switch (ksym) {
+ case XKB_KEY_j:
+ case XKB_KEY_m:
+ case XKB_KEY_Return:
+ case XKB_KEY_KP_Enter:
+ case XKB_KEY_XF86ScreenSaver:
+ if ((ksym == XKB_KEY_j || ksym == XKB_KEY_m) && !ctrl)
+ break;
+
+ if (auth_state == STATE_AUTH_WRONG) {
+ retry_verification = true;
+ return;
+ }
+
+ if (skip_without_validation()) {
+ clear_input();
+ return;
+ }
+ finish_input();
+ skip_repeated_empty_password = true;
+ return;
+ default:
+ skip_repeated_empty_password = false;
+ // A new password is being entered, but a previous one is pending.
+ // Discard the old one and clear the retry_verification flag.
+ if (retry_verification) {
+ retry_verification = false;
+ clear_input();
+ }
+ }
+
+ switch (ksym) {
+ case XKB_KEY_u:
+ case XKB_KEY_Escape:
+ if ((ksym == XKB_KEY_u && ctrl) ||
+ ksym == XKB_KEY_Escape) {
+ DEBUG("C-u pressed\n");
+ clear_input();
+ /* Also hide the unlock indicator */
+ if (unlock_indicator)
+ clear_indicator();
+ return;
+ }
+ break;
+
+ case XKB_KEY_Delete:
+ case XKB_KEY_KP_Delete:
+ /* Deleting forward doesn’t make sense, as i3lock doesn’t allow you
+ * to move the cursor when entering a password. We need to eat this
+ * key press so that it won’t be treated as part of the password,
+ * see issue #50. */
+ return;
+
+ case XKB_KEY_h:
+ case XKB_KEY_BackSpace:
+ if (ksym == XKB_KEY_h && !ctrl)
+ break;
+
+ if (input_position == 0) {
+ START_TIMER(clear_indicator_timeout, 1.0, clear_indicator_cb);
+ unlock_state = STATE_NOTHING_TO_DELETE;
+ redraw_screen();
+ return;
+ }
+
+ /* decrement input_position to point to the previous glyph */
+ u8_dec(password, &input_position);
+ password[input_position] = '\0';
+
+ /* Hide the unlock indicator after a bit if the password buffer is
+ * empty. */
+ START_TIMER(clear_indicator_timeout, 1.0, clear_indicator_cb);
+ unlock_state = STATE_BACKSPACE_ACTIVE;
+ redraw_screen();
+ unlock_state = STATE_KEY_PRESSED;
+ return;
+ }
+
+ if ((input_position + 8) >= (int)sizeof(password))
+ return;
+
+#if 0
+ /* FIXME: handle all of these? */
+ printf("is_keypad_key = %d\n", xcb_is_keypad_key(sym));
+ printf("is_private_keypad_key = %d\n", xcb_is_private_keypad_key(sym));
+ printf("xcb_is_cursor_key = %d\n", xcb_is_cursor_key(sym));
+ printf("xcb_is_pf_key = %d\n", xcb_is_pf_key(sym));
+ printf("xcb_is_function_key = %d\n", xcb_is_function_key(sym));
+ printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym));
+ printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym));
+#endif
+
+ if (n < 2)
+ return;
+
+ /* store it in the password array as UTF-8 */
+ memcpy(password + input_position, buffer, n - 1);
+ input_position += n - 1;
+ DEBUG("current password = %.*s\n", input_position, password);
+
+ if (unlock_indicator) {
+ unlock_state = STATE_KEY_ACTIVE;
+ redraw_screen();
+ unlock_state = STATE_KEY_PRESSED;
+
+ struct ev_timer *timeout = NULL;
+ START_TIMER(timeout, TSTAMP_N_SECS(0.25), redraw_timeout);
+ STOP_TIMER(clear_indicator_timeout);
+ }
+
+ START_TIMER(discard_passwd_timeout, TSTAMP_N_MINS(3), discard_passwd_cb);
+}
+
+/*
+ * A visibility notify event will be received when the visibility (= can the
+ * user view the complete window) changes, so for example when a popup overlays
+ * some area of the i3lock window.
+ *
+ * In this case, we raise our window on top so that the popup (or whatever is
+ * hiding us) gets hidden.
+ *
+ */
+static void handle_visibility_notify(xcb_connection_t *conn,
+ xcb_visibility_notify_event_t *event) {
+ if (event->state != XCB_VISIBILITY_UNOBSCURED) {
+ uint32_t values[] = {XCB_STACK_MODE_ABOVE};
+ xcb_configure_window(conn, event->window, XCB_CONFIG_WINDOW_STACK_MODE, values);
+ xcb_flush(conn);
+ }
+}
+
+/*
+ * Called when the keyboard mapping changes. We update our symbols.
+ *
+ * We ignore errors — if the new keymap cannot be loaded it’s better if the
+ * screen stays locked and the user intervenes by using killall i3lock.
+ *
+ */
+static void process_xkb_event(xcb_generic_event_t *gevent) {
+ union xkb_event {
+ struct {
+ uint8_t response_type;
+ uint8_t xkbType;
+ uint16_t sequence;
+ xcb_timestamp_t time;
+ uint8_t deviceID;
+ } any;
+ xcb_xkb_new_keyboard_notify_event_t new_keyboard_notify;
+ xcb_xkb_map_notify_event_t map_notify;
+ xcb_xkb_state_notify_event_t state_notify;
+ } *event = (union xkb_event *)gevent;
+
+ DEBUG("process_xkb_event for device %d\n", event->any.deviceID);
+
+ if (event->any.deviceID != xkb_x11_get_core_keyboard_device_id(conn))
+ return;
+
+ /*
+ * XkbNewKkdNotify and XkbMapNotify together capture all sorts of keymap
+ * updates (e.g. xmodmap, xkbcomp, setxkbmap), with minimal redundent
+ * recompilations.
+ */
+ switch (event->any.xkbType) {
+ case XCB_XKB_NEW_KEYBOARD_NOTIFY:
+ if (event->new_keyboard_notify.changed & XCB_XKB_NKN_DETAIL_KEYCODES)
+ (void)load_keymap();
+ break;
+
+ case XCB_XKB_MAP_NOTIFY:
+ (void)load_keymap();
+ break;
+
+ case XCB_XKB_STATE_NOTIFY:
+ xkb_state_update_mask(xkb_state,
+ event->state_notify.baseMods,
+ event->state_notify.latchedMods,
+ event->state_notify.lockedMods,
+ event->state_notify.baseGroup,
+ event->state_notify.latchedGroup,
+ event->state_notify.lockedGroup);
+ break;
+ }
+}
+
+/*
+ * Called when the properties on the root window change, e.g. when the screen
+ * resolution changes. If so we update the window to cover the whole screen
+ * and also redraw the image, if any.
+ *
+ */
+void handle_screen_resize(void) {
+ xcb_get_geometry_cookie_t geomc;
+ xcb_get_geometry_reply_t *geom;
+ geomc = xcb_get_geometry(conn, screen->root);
+ if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
+ return;
+
+ if (last_resolution[0] == geom->width &&
+ last_resolution[1] == geom->height) {
+ free(geom);
+ return;
+ }
+
+ last_resolution[0] = geom->width;
+ last_resolution[1] = geom->height;
+
+ free(geom);
+
+ redraw_screen();
+
+ uint32_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
+ xcb_configure_window(conn, win, mask, last_resolution);
+ xcb_flush(conn);
+
+ randr_query(screen->root);
+ redraw_screen();
+}
+
+static ssize_t read_raw_image_native(uint32_t *dest, FILE *src, size_t width, size_t height, int pixstride) {
+ ssize_t count = 0;
+ for (size_t y = 0; y < height; y++) {
+ size_t n = fread(&dest[y * pixstride], 1, width * 4, src);
+ count += n;
+ if (n < (size_t)(width * 4))
+ break;
+ }
+
+ return count;
+}
+
+struct raw_pixel_format {
+ int bpp;
+ int red;
+ int green;
+ int blue;
+};
+
+static ssize_t read_raw_image_fmt(uint32_t *dest, FILE *src, size_t width, size_t height, int pixstride,
+ struct raw_pixel_format fmt) {
+ unsigned char *buf = malloc(width * fmt.bpp);
+ if (buf == NULL)
+ return -1;
+
+ ssize_t count = 0;
+ for (size_t y = 0; y < height; y++) {
+ size_t n = fread(buf, 1, width * fmt.bpp, src);
+ count += n;
+ if (n < (size_t)(width * fmt.bpp))
+ break;
+
+ for (size_t x = 0; x < width; ++x) {
+ int idx = x * fmt.bpp;
+ dest[y * pixstride + x] = 0 |
+ (buf[idx + fmt.red]) << 16 |
+ (buf[idx + fmt.green]) << 8 |
+ (buf[idx + fmt.blue]);
+ }
+ }
+
+ free(buf);
+ return count;
+}
+
+// Pre-defind pixel formats (, , , )
+static const struct raw_pixel_format raw_fmt_rgb = {3, 0, 1, 2};
+static const struct raw_pixel_format raw_fmt_rgbx = {4, 0, 1, 2};
+static const struct raw_pixel_format raw_fmt_xrgb = {4, 1, 2, 3};
+static const struct raw_pixel_format raw_fmt_bgr = {3, 2, 1, 0};
+static const struct raw_pixel_format raw_fmt_bgrx = {4, 2, 1, 0};
+static const struct raw_pixel_format raw_fmt_xbgr = {4, 3, 2, 1};
+
+static cairo_surface_t *read_raw_image(const char *image_path, const char *image_raw_format) {
+ cairo_surface_t *img;
+
+#define RAW_PIXFMT_MAXLEN 6
+#define STRINGIFY1(x) #x
+#define STRINGIFY(x) STRINGIFY1(x)
+ /* Parse format as x: */
+ char pixfmt[RAW_PIXFMT_MAXLEN + 1];
+ size_t w, h;
+ const char *fmt = "%zux%zu:%" STRINGIFY(RAW_PIXFMT_MAXLEN) "s";
+ if (sscanf(image_raw_format, fmt, &w, &h, pixfmt) != 3) {
+ fprintf(stderr, "Invalid image format: \"%s\"\n", image_raw_format);
+ return NULL;
+ }
+#undef RAW_PIXFMT_MAXLEN
+#undef STRINGIFY1
+#undef STRINGIFY
+
+ /* Create image surface */
+ img = cairo_image_surface_create(CAIRO_FORMAT_RGB24, w, h);
+ if (cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) {
+ fprintf(stderr, "Could not create surface: %s\n",
+ cairo_status_to_string(cairo_surface_status(img)));
+ return NULL;
+ }
+ cairo_surface_flush(img);
+
+ /* Use uint32_t* because cairo uses native endianness */
+ uint32_t *data = (uint32_t *)cairo_image_surface_get_data(img);
+ const int pixstride = cairo_image_surface_get_stride(img) / 4;
+
+ FILE *f = fopen(image_path, "r");
+ if (f == NULL) {
+ fprintf(stderr, "Could not open image \"%s\": %s\n",
+ image_path, strerror(errno));
+ cairo_surface_destroy(img);
+ return NULL;
+ }
+
+ /* Read the image, respecting cairo's stride, according to the pixfmt */
+ ssize_t size, count;
+ if (strcmp(pixfmt, "native") == 0) {
+ /* If the pixfmt is 'native', just read each line directly into the buffer */
+ size = w * h * 4;
+ count = read_raw_image_native(data, f, w, h, pixstride);
+ } else {
+ const struct raw_pixel_format *fmt = NULL;
+
+ if (strcmp(pixfmt, "rgb") == 0)
+ fmt = &raw_fmt_rgb;
+ else if (strcmp(pixfmt, "rgbx") == 0)
+ fmt = &raw_fmt_rgbx;
+ else if (strcmp(pixfmt, "xrgb") == 0)
+ fmt = &raw_fmt_xrgb;
+ else if (strcmp(pixfmt, "bgr") == 0)
+ fmt = &raw_fmt_bgr;
+ else if (strcmp(pixfmt, "bgrx") == 0)
+ fmt = &raw_fmt_bgrx;
+ else if (strcmp(pixfmt, "xbgr") == 0)
+ fmt = &raw_fmt_xbgr;
+
+ if (fmt == NULL) {
+ fprintf(stderr, "Unknown raw pixel format: %s\n", pixfmt);
+ fclose(f);
+ cairo_surface_destroy(img);
+ return NULL;
+ }
+
+ size = w * h * fmt->bpp;
+ count = read_raw_image_fmt(data, f, w, h, pixstride, *fmt);
+ }
+
+ cairo_surface_mark_dirty(img);
+
+ if (count < size) {
+ if (count < 0 || ferror(f)) {
+ fprintf(stderr, "Failed to read image \"%s\": %s\n",
+ image_path, strerror(errno));
+ fclose(f);
+ cairo_surface_destroy(img);
+ return NULL;
+ } else {
+ /* Print a warning if the file contains less data than expected,
+ * but don't abort. It's useful to see how the image looks even if it's wrong. */
+ fprintf(stderr, "Warning: expected to read %zi bytes from \"%s\", read %zi\n",
+ size, image_path, count);
+ }
+ }
+
+ fclose(f);
+ return img;
+}
+
+static bool verify_png_image(const char *image_path) {
+ if (!image_path) {
+ return false;
+ }
+
+ /* Check file exists and has correct PNG header */
+ FILE *png_file = fopen(image_path, "r");
+ if (png_file == NULL) {
+ fprintf(stderr, "Image file path \"%s\" cannot be opened: %s\n", image_path, strerror(errno));
+ return false;
+ }
+ unsigned char png_header[8];
+ memset(png_header, '\0', sizeof(png_header));
+ int bytes_read = fread(png_header, 1, sizeof(png_header), png_file);
+ fclose(png_file);
+ if (bytes_read != sizeof(png_header)) {
+ fprintf(stderr, "Could not read PNG header from \"%s\"\n", image_path);
+ return false;
+ }
+
+ // Check PNG header according to the specification, available at:
+ // https://www.w3.org/TR/2003/REC-PNG-20031110/#5PNG-file-signature
+ static unsigned char PNG_REFERENCE_HEADER[8] = {137, 80, 78, 71, 13, 10, 26, 10};
+ if (memcmp(PNG_REFERENCE_HEADER, png_header, sizeof(png_header)) != 0) {
+ fprintf(stderr, "File \"%s\" does not start with a PNG header. i3lock currently only supports loading PNG files.\n", image_path);
+ return false;
+ }
+ return true;
+}
+
+#ifndef __OpenBSD__
+/*
+ * Callback function for PAM. We only react on password request callbacks.
+ *
+ */
+static int conv_callback(int num_msg, const struct pam_message **msg,
+ struct pam_response **resp, void *appdata_ptr) {
+ if (num_msg == 0)
+ return 1;
+
+ /* PAM expects an array of responses, one for each message */
+ if ((*resp = calloc(num_msg, sizeof(struct pam_response))) == NULL) {
+ perror("calloc");
+ return 1;
+ }
+
+ for (int c = 0; c < num_msg; c++) {
+ if (msg[c]->msg_style != PAM_PROMPT_ECHO_OFF &&
+ msg[c]->msg_style != PAM_PROMPT_ECHO_ON)
+ continue;
+
+ /* return code is currently not used but should be set to zero */
+ resp[c]->resp_retcode = 0;
+ if ((resp[c]->resp = strdup(password)) == NULL) {
+ perror("strdup");
+ return 1;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+/*
+ * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
+ * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
+ *
+ */
+static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
+ /* empty, because xcb_prepare_cb and xcb_check_cb are used */
+}
+
+/*
+ * Flush before blocking (and waiting for new events)
+ *
+ */
+static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
+ xcb_flush(conn);
+}
+
+/*
+ * Try closing logind sleep lock fd passed over from xss-lock, in case we're
+ * being run from there.
+ *
+ */
+static void maybe_close_sleep_lock_fd(void) {
+ const char *sleep_lock_fd = getenv("XSS_SLEEP_LOCK_FD");
+ char *endptr;
+ if (sleep_lock_fd && *sleep_lock_fd != 0) {
+ long int fd = strtol(sleep_lock_fd, &endptr, 10);
+ if (*endptr == 0) {
+ close(fd);
+ }
+ }
+}
+
+/*
+ * Instead of polling the X connection socket we leave this to
+ * xcb_poll_for_event() which knows better than we can ever know.
+ *
+ */
+static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
+ xcb_generic_event_t *event;
+
+ if (xcb_connection_has_error(conn))
+ errx(EXIT_FAILURE, "X11 connection broke, did your server terminate?");
+
+ while ((event = xcb_poll_for_event(conn)) != NULL) {
+ if (event->response_type == 0) {
+ xcb_generic_error_t *error = (xcb_generic_error_t *)event;
+ if (debug_mode)
+ fprintf(stderr, "X11 Error received! sequence 0x%x, error_code = %d\n",
+ error->sequence, error->error_code);
+ free(event);
+ continue;
+ }
+
+ /* Strip off the highest bit (set if the event is generated) */
+ int type = (event->response_type & 0x7F);
+
+ switch (type) {
+ case XCB_KEY_PRESS:
+ handle_key_press((xcb_key_press_event_t *)event);
+ break;
+
+ case XCB_VISIBILITY_NOTIFY:
+ handle_visibility_notify(conn, (xcb_visibility_notify_event_t *)event);
+ break;
+
+ case XCB_MAP_NOTIFY:
+ maybe_close_sleep_lock_fd();
+ if (!dont_fork) {
+ /* After the first MapNotify, we never fork again. We don’t
+ * expect to get another MapNotify, but better be sure… */
+ dont_fork = true;
+
+ /* In the parent process, we exit */
+ if (fork() != 0)
+ exit(0);
+
+ ev_loop_fork(EV_DEFAULT);
+ }
+ break;
+
+ case XCB_CONFIGURE_NOTIFY:
+ handle_screen_resize();
+ break;
+
+ default:
+ if (type == xkb_base_event) {
+ process_xkb_event(event);
+ }
+ if (randr_base > -1 &&
+ type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
+ randr_query(screen->root);
+ handle_screen_resize();
+ }
+ }
+
+ free(event);
+ }
+}
+
+/*
+ * This function is called from a fork()ed child and will raise the i3lock
+ * window when the window is obscured, even when the main i3lock process is
+ * blocked due to the authentication backend.
+ *
+ */
+static void raise_loop(xcb_window_t window) {
+ xcb_connection_t *conn;
+ xcb_generic_event_t *event;
+ int screens;
+
+ if (xcb_connection_has_error((conn = xcb_connect(NULL, &screens))) > 0)
+ errx(EXIT_FAILURE, "Cannot open display");
+
+ /* We need to know about the window being obscured or getting destroyed. */
+ xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK,
+ (uint32_t[]){
+ XCB_EVENT_MASK_VISIBILITY_CHANGE |
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY});
+ xcb_flush(conn);
+
+ DEBUG("Watching window 0x%08x\n", window);
+ while ((event = xcb_wait_for_event(conn)) != NULL) {
+ if (event->response_type == 0) {
+ xcb_generic_error_t *error = (xcb_generic_error_t *)event;
+ DEBUG("X11 Error received! sequence 0x%x, error_code = %d\n",
+ error->sequence, error->error_code);
+ free(event);
+ continue;
+ }
+ /* Strip off the highest bit (set if the event is generated) */
+ int type = (event->response_type & 0x7F);
+ DEBUG("Read event of type %d\n", type);
+ switch (type) {
+ case XCB_VISIBILITY_NOTIFY:
+ handle_visibility_notify(conn, (xcb_visibility_notify_event_t *)event);
+ break;
+ case XCB_UNMAP_NOTIFY:
+ DEBUG("UnmapNotify for 0x%08x\n", (((xcb_unmap_notify_event_t *)event)->window));
+ if (((xcb_unmap_notify_event_t *)event)->window == window)
+ exit(EXIT_SUCCESS);
+ break;
+ case XCB_DESTROY_NOTIFY:
+ DEBUG("DestroyNotify for 0x%08x\n", (((xcb_destroy_notify_event_t *)event)->window));
+ if (((xcb_destroy_notify_event_t *)event)->window == window)
+ exit(EXIT_SUCCESS);
+ break;
+ default:
+ DEBUG("Unhandled event type %d\n", type);
+ break;
+ }
+ free(event);
+ }
+}
+
+int main(int argc, char *argv[]) {
+ struct passwd *pw;
+ char *username;
+ char *image_path = NULL;
+ char *image_raw_format = NULL;
+#ifndef __OpenBSD__
+ int ret;
+ struct pam_conv conv = {conv_callback, NULL};
+#endif
+ int curs_choice = CURS_NONE;
+ int o;
+ int longoptind = 0;
+ struct option longopts[] = {
+ {"version", no_argument, NULL, 'v'},
+ {"nofork", no_argument, NULL, 'n'},
+ {"beep", no_argument, NULL, 'b'},
+ {"dpms", no_argument, NULL, 'd'},
+ {"color", required_argument, NULL, 'c'},
+ {"pointer", required_argument, NULL, 'p'},
+ {"debug", no_argument, NULL, 0},
+ {"help", no_argument, NULL, 'h'},
+ {"no-unlock-indicator", no_argument, NULL, 'u'},
+ {"image", required_argument, NULL, 'i'},
+ {"raw", required_argument, NULL, 0},
+ {"tiling", no_argument, NULL, 't'},
+ {"ignore-empty-password", no_argument, NULL, 'e'},
+ {"inactivity-timeout", required_argument, NULL, 'I'},
+ {"show-failed-attempts", no_argument, NULL, 'f'},
+ {NULL, no_argument, NULL, 0}};
+
+ if ((pw = getpwuid(getuid())) == NULL)
+ err(EXIT_FAILURE, "getpwuid() failed");
+ if ((username = pw->pw_name) == NULL)
+ errx(EXIT_FAILURE, "pw->pw_name is NULL.");
+
+ char *optstring = "hvnbdc:p:ui:teI:f";
+ while ((o = getopt_long(argc, argv, optstring, longopts, &longoptind)) != -1) {
+ switch (o) {
+ case 'v':
+ errx(EXIT_SUCCESS, "version " I3LOCK_VERSION " © 2010 Michael Stapelberg");
+ case 'n':
+ dont_fork = true;
+ break;
+ case 'b':
+ beep = true;
+ break;
+ case 'd':
+ fprintf(stderr, "DPMS support has been removed from i3lock. Please see the manpage i3lock(1).\n");
+ break;
+ case 'I': {
+ fprintf(stderr, "Inactivity timeout only makes sense with DPMS, which was removed. Please see the manpage i3lock(1).\n");
+ break;
+ }
+ case 'c': {
+ char *arg = optarg;
+
+ /* Skip # if present */
+ if (arg[0] == '#')
+ arg++;
+
+ if (strlen(arg) != 6 || sscanf(arg, "%06[0-9a-fA-F]", color) != 1)
+ errx(EXIT_FAILURE, "color is invalid, it must be given in 3-byte hexadecimal format: rrggbb");
+
+ break;
+ }
+ case 'u':
+ unlock_indicator = false;
+ break;
+ case 'i':
+ image_path = strdup(optarg);
+ break;
+ case 't':
+ tile = true;
+ break;
+ case 'p':
+ if (!strcmp(optarg, "win")) {
+ curs_choice = CURS_WIN;
+ } else if (!strcmp(optarg, "default")) {
+ curs_choice = CURS_DEFAULT;
+ } else {
+ errx(EXIT_FAILURE, "i3lock: Invalid pointer type given. Expected one of \"win\" or \"default\".");
+ }
+ break;
+ case 'e':
+ ignore_empty_password = true;
+ break;
+ case 0:
+ if (strcmp(longopts[longoptind].name, "debug") == 0)
+ debug_mode = true;
+ else if (strcmp(longopts[longoptind].name, "raw") == 0)
+ image_raw_format = strdup(optarg);
+ break;
+ case 'f':
+ show_failed_attempts = true;
+ break;
+ default:
+ errx(EXIT_FAILURE, "Syntax: i3lock [-v] [-n] [-b] [-d] [-c color] [-u] [-p win|default]"
+ " [-i image.png] [-t] [-e] [-I timeout] [-f]");
+ }
+ }
+
+ /* We need (relatively) random numbers for highlighting a random part of
+ * the unlock indicator upon keypresses. */
+ srand(time(NULL));
+
+#ifndef __OpenBSD__
+ /* Initialize PAM */
+ if ((ret = pam_start("i3lock", username, &conv, &pam_handle)) != PAM_SUCCESS)
+ errx(EXIT_FAILURE, "PAM: %s", pam_strerror(pam_handle, ret));
+
+ if ((ret = pam_set_item(pam_handle, PAM_TTY, getenv("DISPLAY"))) != PAM_SUCCESS)
+ errx(EXIT_FAILURE, "PAM: %s", pam_strerror(pam_handle, ret));
+#endif
+
+/* Using mlock() as non-super-user seems only possible in Linux.
+ * Users of other operating systems should use encrypted swap/no swap
+ * (or remove the ifdef and run i3lock as super-user).
+ * Alas, swap is encrypted by default on OpenBSD so swapping out
+ * is not necessarily an issue. */
+#if defined(__linux__)
+ /* Lock the area where we store the password in memory, we don’t want it to
+ * be swapped to disk. Since Linux 2.6.9, this does not require any
+ * privileges, just enough bytes in the RLIMIT_MEMLOCK limit. */
+ if (mlock(password, sizeof(password)) != 0)
+ err(EXIT_FAILURE, "Could not lock page in memory, check RLIMIT_MEMLOCK");
+#endif
+
+ /* Double checking that connection is good and operatable with xcb */
+ int screennr;
+ if ((conn = xcb_connect(NULL, &screennr)) == NULL ||
+ xcb_connection_has_error(conn))
+ errx(EXIT_FAILURE, "Could not connect to X11, maybe you need to set DISPLAY?");
+
+ if (xkb_x11_setup_xkb_extension(conn,
+ XKB_X11_MIN_MAJOR_XKB_VERSION,
+ XKB_X11_MIN_MINOR_XKB_VERSION,
+ 0,
+ NULL,
+ NULL,
+ &xkb_base_event,
+ &xkb_base_error) != 1)
+ errx(EXIT_FAILURE, "Could not setup XKB extension.");
+
+ static const xcb_xkb_map_part_t required_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 xcb_xkb_event_type_t required_events =
+ (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
+ XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
+ XCB_XKB_EVENT_TYPE_STATE_NOTIFY);
+
+ xcb_xkb_select_events(
+ conn,
+ xkb_x11_get_core_keyboard_device_id(conn),
+ required_events,
+ 0,
+ required_events,
+ required_map_parts,
+ required_map_parts,
+ 0);
+
+ /* When we cannot initially load the keymap, we better exit */
+ if (!load_keymap())
+ errx(EXIT_FAILURE, "Could not load keymap");
+
+ const char *locale = getenv("LC_ALL");
+ if (!locale || !*locale)
+ locale = getenv("LC_CTYPE");
+ if (!locale || !*locale)
+ locale = getenv("LANG");
+ if (!locale || !*locale) {
+ if (debug_mode)
+ fprintf(stderr, "Can't detect your locale, fallback to C\n");
+ locale = "C";
+ }
+
+ load_compose_table(locale);
+
+ screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
+
+ init_dpi();
+
+ randr_init(&randr_base, screen->root);
+ randr_query(screen->root);
+
+ last_resolution[0] = screen->width_in_pixels;
+ last_resolution[1] = screen->height_in_pixels;
+
+ xcb_change_window_attributes(conn, screen->root, XCB_CW_EVENT_MASK,
+ (uint32_t[]){XCB_EVENT_MASK_STRUCTURE_NOTIFY});
+
+ if (image_raw_format != NULL && image_path != NULL) {
+ /* Read image. 'read_raw_image' returns NULL on error,
+ * so we don't have to handle errors here. */
+ img = read_raw_image(image_path, image_raw_format);
+ } else if (verify_png_image(image_path)) {
+ /* Create a pixmap to render on, fill it with the background color */
+ img = cairo_image_surface_create_from_png(image_path);
+ /* In case loading failed, we just pretend no -i was specified. */
+ if (cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) {
+ fprintf(stderr, "Could not load image \"%s\": %s\n",
+ image_path, cairo_status_to_string(cairo_surface_status(img)));
+ img = NULL;
+ }
+ }
+
+ free(image_path);
+ free(image_raw_format);
+
+ /* Pixmap on which the image is rendered to (if any) */
+ xcb_pixmap_t bg_pixmap = draw_image(last_resolution);
+
+ xcb_window_t stolen_focus = find_focused_window(conn, screen->root);
+
+ /* Open the fullscreen window, already with the correct pixmap in place */
+ win = open_fullscreen_window(conn, screen, color, bg_pixmap);
+ xcb_free_pixmap(conn, bg_pixmap);
+
+ cursor = create_cursor(conn, screen, win, curs_choice);
+
+ /* Display the "locking…" message while trying to grab the pointer/keyboard. */
+ auth_state = STATE_AUTH_LOCK;
+ if (!grab_pointer_and_keyboard(conn, screen, cursor, 1000)) {
+ DEBUG("stole focus from X11 window 0x%08x\n", stolen_focus);
+
+ /* Set the focus to i3lock, possibly closing context menus which would
+ * otherwise prevent us from grabbing keyboard/pointer.
+ *
+ * We cannot use set_focused_window because _NET_ACTIVE_WINDOW only
+ * works for managed windows, but i3lock uses an unmanaged window
+ * (override_redirect=1). */
+ xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT /* revert_to */, win, XCB_CURRENT_TIME);
+ if (!grab_pointer_and_keyboard(conn, screen, cursor, 9000)) {
+ auth_state = STATE_I3LOCK_LOCK_FAILED;
+ redraw_screen();
+ sleep(1);
+ errx(EXIT_FAILURE, "Cannot grab pointer/keyboard");
+ }
+ }
+
+ pid_t pid = fork();
+ /* The pid == -1 case is intentionally ignored here:
+ * While the child process is useful for preventing other windows from
+ * popping up while i3lock blocks, it is not critical. */
+ if (pid == 0) {
+ /* Child */
+ close(xcb_get_file_descriptor(conn));
+ maybe_close_sleep_lock_fd();
+ raise_loop(win);
+ exit(EXIT_SUCCESS);
+ }
+
+ /* Load the keymap again to sync the current modifier state. Since we first
+ * loaded the keymap, there might have been changes, but starting from now,
+ * we should get all key presses/releases due to having grabbed the
+ * keyboard. */
+ (void)load_keymap();
+
+ /* Initialize the libev event loop. */
+ main_loop = EV_DEFAULT;
+ if (main_loop == NULL)
+ errx(EXIT_FAILURE, "Could not initialize libev. Bad LIBEV_FLAGS?");
+
+ /* Explicitly call the screen redraw in case "locking…" message was displayed */
+ auth_state = STATE_AUTH_IDLE;
+ redraw_screen();
+
+ struct ev_io *xcb_watcher = calloc(sizeof(struct ev_io), 1);
+ struct ev_check *xcb_check = calloc(sizeof(struct ev_check), 1);
+ struct ev_prepare *xcb_prepare = calloc(sizeof(struct ev_prepare), 1);
+
+ ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
+ ev_io_start(main_loop, xcb_watcher);
+
+ ev_check_init(xcb_check, xcb_check_cb);
+ ev_check_start(main_loop, xcb_check);
+
+ ev_prepare_init(xcb_prepare, xcb_prepare_cb);
+ ev_prepare_start(main_loop, xcb_prepare);
+
+ /* Invoke the event callback once to catch all the events which were
+ * received up until now. ev will only pick up new events (when the X11
+ * file descriptor becomes readable). */
+ ev_invoke(main_loop, xcb_check, 0);
+ ev_loop(main_loop, 0);
+
+#ifndef __OpenBSD__
+ if (pam_cleanup) {
+ pam_end(pam_handle, PAM_SUCCESS);
+ }
+#endif
+
+ if (stolen_focus == XCB_NONE) {
+ return 0;
+ }
+
+ DEBUG("restoring focus to X11 window 0x%08x\n", stolen_focus);
+ xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
+ xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
+ xcb_destroy_window(conn, win);
+ set_focused_window(conn, screen->root, stolen_focus);
+ xcb_aux_sync(conn);
+
+ return 0;
+}
diff --git a/i3lock.h b/i3lock.h
new file mode 100644
index 0000000..57190e5
--- /dev/null
+++ b/i3lock.h
@@ -0,0 +1,14 @@
+#ifndef _I3LOCK_H
+#define _I3LOCK_H
+
+/* This macro will only print debug output when started with --debug.
+ * This is important because xautolock (for example) closes stdout/stderr by
+ * default, so just printing something to stdout will lead to the data ending
+ * up on the X11 socket (!). */
+#define DEBUG(fmt, ...) \
+ do { \
+ if (debug_mode) \
+ printf("[i3lock-debug] " fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#endif
diff --git a/m4/ax_append_flag.m4 b/m4/ax_append_flag.m4
new file mode 100644
index 0000000..08f2e07
--- /dev/null
+++ b/m4/ax_append_flag.m4
@@ -0,0 +1,71 @@
+# ===========================================================================
+# 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
+# Copyright (c) 2011 Maarten Bosmans
+#
+# 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 .
+#
+# 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 6
+
+AC_DEFUN([AX_APPEND_FLAG],
+[dnl
+AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF
+AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])])
+AS_VAR_SET_IF(FLAGS,[
+ AS_CASE([" AS_VAR_GET(FLAGS) "],
+ [*" $1 "*], [AC_RUN_LOG([: FLAGS already contains $1])],
+ [
+ AS_VAR_APPEND(FLAGS,[" $1"])
+ AC_RUN_LOG([: FLAGS="$FLAGS"])
+ ])
+ ],
+ [
+ AS_VAR_SET(FLAGS,[$1])
+ AC_RUN_LOG([: FLAGS="$FLAGS"])
+ ])
+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..1f07799
--- /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
+# Copyright (c) 2010 Rhys Ulerich
+#
+# 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 .
+#
+# 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 15
+
+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
+AX_REQUIRE_DEFINED([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/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4
new file mode 100644
index 0000000..ca36397
--- /dev/null
+++ b/m4/ax_check_compile_flag.m4
@@ -0,0 +1,74 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
+#
+# DESCRIPTION
+#
+# Check whether the given FLAG works with the current language's compiler
+# or gives an error. (Warnings, however, are ignored)
+#
+# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
+# success/failure.
+#
+# 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.
+#
+# INPUT gives an alternative input source to AC_COMPILE_IFELSE.
+#
+# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
+# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Guido U. Draheim
+# Copyright (c) 2011 Maarten Bosmans
+#
+# 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 .
+#
+# 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 4
+
+AC_DEFUN([AX_CHECK_COMPILE_FLAG],
+[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
+AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
+AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
+ ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
+ _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
+ AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
+ [AS_VAR_SET(CACHEVAR,[yes])],
+ [AS_VAR_SET(CACHEVAR,[no])])
+ _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
+AS_VAR_IF(CACHEVAR,yes,
+ [m4_default([$2], :)],
+ [m4_default([$3], :)])
+AS_VAR_POPDEF([CACHEVAR])dnl
+])dnl AX_CHECK_COMPILE_FLAGS
diff --git a/m4/ax_check_enable_debug.m4 b/m4/ax_check_enable_debug.m4
new file mode 100644
index 0000000..f99d75f
--- /dev/null
+++ b/m4/ax_check_enable_debug.m4
@@ -0,0 +1,124 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_check_enable_debug.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_CHECK_ENABLE_DEBUG([enable by default=yes/info/profile/no], [ENABLE DEBUG VARIABLES ...], [DISABLE DEBUG VARIABLES NDEBUG ...], [IS-RELEASE])
+#
+# DESCRIPTION
+#
+# Check for the presence of an --enable-debug option to configure, with
+# the specified default value used when the option is not present. Return
+# the value in the variable $ax_enable_debug.
+#
+# Specifying 'yes' adds '-g -O0' to the compilation flags for all
+# languages. Specifying 'info' adds '-g' to the compilation flags.
+# Specifying 'profile' adds '-g -pg' to the compilation flags and '-pg' to
+# the linking flags. Otherwise, nothing is added.
+#
+# Define the variables listed in the second argument if debug is enabled,
+# defaulting to no variables. Defines the variables listed in the third
+# argument if debug is disabled, defaulting to NDEBUG. All lists of
+# variables should be space-separated.
+#
+# If debug is not enabled, ensure AC_PROG_* will not add debugging flags.
+# Should be invoked prior to any AC_PROG_* compiler checks.
+#
+# IS-RELEASE can be used to change the default to 'no' when making a
+# release. Set IS-RELEASE to 'yes' or 'no' as appropriate. By default, it
+# uses the value of $ax_is_release, so if you are using the AX_IS_RELEASE
+# macro, there is no need to pass this parameter.
+#
+# AX_IS_RELEASE([git-directory])
+# AX_CHECK_ENABLE_DEBUG()
+#
+# LICENSE
+#
+# Copyright (c) 2011 Rhys Ulerich
+# Copyright (c) 2014, 2015 Philip Withnall
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved.
+
+#serial 5
+
+AC_DEFUN([AX_CHECK_ENABLE_DEBUG],[
+ AC_BEFORE([$0],[AC_PROG_CC])dnl
+ AC_BEFORE([$0],[AC_PROG_CXX])dnl
+ AC_BEFORE([$0],[AC_PROG_F77])dnl
+ AC_BEFORE([$0],[AC_PROG_FC])dnl
+
+ AC_MSG_CHECKING(whether to enable debugging)
+
+ ax_enable_debug_default=m4_tolower(m4_normalize(ifelse([$1],,[no],[$1])))
+ ax_enable_debug_is_release=m4_tolower(m4_normalize(ifelse([$4],,
+ [$ax_is_release],
+ [$4])))
+
+ # If this is a release, override the default.
+ AS_IF([test "$ax_enable_debug_is_release" = "yes"],
+ [ax_enable_debug_default="no"])
+
+ m4_define(ax_enable_debug_vars,[m4_normalize(ifelse([$2],,,[$2]))])
+ m4_define(ax_disable_debug_vars,[m4_normalize(ifelse([$3],,[NDEBUG],[$3]))])
+
+ AC_ARG_ENABLE(debug,
+ [AS_HELP_STRING([--enable-debug=]@<:@yes/info/profile/no@:>@,[compile with debugging])],
+ [],enable_debug=$ax_enable_debug_default)
+
+ # empty mean debug yes
+ AS_IF([test "x$enable_debug" = "x"],
+ [enable_debug="yes"])
+
+ # case of debug
+ AS_CASE([$enable_debug],
+ [yes],[
+ AC_MSG_RESULT(yes)
+ CFLAGS="${CFLAGS} -g -O0"
+ CXXFLAGS="${CXXFLAGS} -g -O0"
+ FFLAGS="${FFLAGS} -g -O0"
+ FCFLAGS="${FCFLAGS} -g -O0"
+ OBJCFLAGS="${OBJCFLAGS} -g -O0"
+ ],
+ [info],[
+ AC_MSG_RESULT(info)
+ CFLAGS="${CFLAGS} -g"
+ CXXFLAGS="${CXXFLAGS} -g"
+ FFLAGS="${FFLAGS} -g"
+ FCFLAGS="${FCFLAGS} -g"
+ OBJCFLAGS="${OBJCFLAGS} -g"
+ ],
+ [profile],[
+ AC_MSG_RESULT(profile)
+ CFLAGS="${CFLAGS} -g -pg"
+ CXXFLAGS="${CXXFLAGS} -g -pg"
+ FFLAGS="${FFLAGS} -g -pg"
+ FCFLAGS="${FCFLAGS} -g -pg"
+ OBJCFLAGS="${OBJCFLAGS} -g -pg"
+ LDFLAGS="${LDFLAGS} -pg"
+ ],
+ [
+ AC_MSG_RESULT(no)
+ dnl Ensure AC_PROG_CC/CXX/F77/FC/OBJC will not enable debug flags
+ dnl by setting any unset environment flag variables
+ AS_IF([test "x${CFLAGS+set}" != "xset"],
+ [CFLAGS=""])
+ AS_IF([test "x${CXXFLAGS+set}" != "xset"],
+ [CXXFLAGS=""])
+ AS_IF([test "x${FFLAGS+set}" != "xset"],
+ [FFLAGS=""])
+ AS_IF([test "x${FCFLAGS+set}" != "xset"],
+ [FCFLAGS=""])
+ AS_IF([test "x${OBJCFLAGS+set}" != "xset"],
+ [OBJCFLAGS=""])
+ ])
+
+ dnl Define various variables if debugging is disabled.
+ dnl assert.h is a NOP if NDEBUG is defined, so define it by default.
+ AS_IF([test "x$enable_debug" = "xyes"],
+ [m4_map_args_w(ax_enable_debug_vars, [AC_DEFINE(], [,,[Define if debugging is enabled])])],
+ [m4_map_args_w(ax_disable_debug_vars, [AC_DEFINE(], [,,[Define if debugging is disabled])])])
+ ax_enable_debug=$enable_debug
+])
diff --git a/m4/ax_check_gnu_make.m4 b/m4/ax_check_gnu_make.m4
new file mode 100644
index 0000000..6762e9e
--- /dev/null
+++ b/m4/ax_check_gnu_make.m4
@@ -0,0 +1,84 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_check_gnu_make.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_CHECK_GNU_MAKE()
+#
+# DESCRIPTION
+#
+# This macro searches for a GNU version of make. If a match is found:
+#
+# * The makefile variable `ifGNUmake' is set to the empty string, otherwise
+# it is set to "#". This is useful for including a special features in a
+# Makefile, which cannot be handled by other versions of make.
+# * The variable `_cv_gnu_make_command` is set to the command to invoke
+# GNU make if it exists, the empty string otherwise.
+# * The variable `ax_cv_gnu_make_command` is set to the command to invoke
+# GNU make by copying `_cv_gnu_make_command`, otherwise it is unset.
+# * If GNU Make is found, its version is extracted from the output of
+# `make --version` as the last field of a record of space-separated
+# columns and saved into the variable `ax_check_gnu_make_version`.
+#
+# Here is an example of its use:
+#
+# Makefile.in might contain:
+#
+# # A failsafe way of putting a dependency rule into a makefile
+# $(DEPEND):
+# $(CC) -MM $(srcdir)/*.c > $(DEPEND)
+#
+# @ifGNUmake@ ifeq ($(DEPEND),$(wildcard $(DEPEND)))
+# @ifGNUmake@ include $(DEPEND)
+# @ifGNUmake@ endif
+#
+# Then configure.in would normally contain:
+#
+# AX_CHECK_GNU_MAKE()
+# AC_OUTPUT(Makefile)
+#
+# Then perhaps to cause gnu make to override any other make, we could do
+# something like this (note that GNU make always looks for GNUmakefile
+# first):
+#
+# if ! test x$_cv_gnu_make_command = x ; then
+# mv Makefile GNUmakefile
+# echo .DEFAULT: > Makefile ;
+# echo \ $_cv_gnu_make_command \$@ >> Makefile;
+# fi
+#
+# Then, if any (well almost any) other make is called, and GNU make also
+# exists, then the other make wraps the GNU make.
+#
+# LICENSE
+#
+# Copyright (c) 2008 John Darrington
+# Copyright (c) 2015 Enrico M. Crisostomo
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 8
+
+AC_DEFUN([AX_CHECK_GNU_MAKE],dnl
+ [AC_PROG_AWK
+ AC_CACHE_CHECK([for GNU make],[_cv_gnu_make_command],[dnl
+ _cv_gnu_make_command="" ;
+dnl Search all the common names for GNU make
+ for a in "$MAKE" make gmake gnumake ; do
+ if test -z "$a" ; then continue ; fi ;
+ if "$a" --version 2> /dev/null | grep GNU 2>&1 > /dev/null ; then
+ _cv_gnu_make_command=$a ;
+ AX_CHECK_GNU_MAKE_HEADLINE=$("$a" --version 2> /dev/null | grep "GNU Make")
+ ax_check_gnu_make_version=$(echo ${AX_CHECK_GNU_MAKE_HEADLINE} | ${AWK} -F " " '{ print $(NF); }')
+ break ;
+ fi
+ done ;])
+dnl If there was a GNU version, then set @ifGNUmake@ to the empty string, '#' otherwise
+ AS_VAR_IF([_cv_gnu_make_command], [""], [AS_VAR_SET([ifGNUmake], ["#"])], [AS_VAR_SET([ifGNUmake], [""])])
+ AS_VAR_IF([_cv_gnu_make_command], [""], [AS_UNSET(ax_cv_gnu_make_command)], [AS_VAR_SET([ax_cv_gnu_make_command], [${_cv_gnu_make_command}])])
+ AC_SUBST([ifGNUmake])
+])
diff --git a/m4/ax_check_link_flag.m4 b/m4/ax_check_link_flag.m4
new file mode 100644
index 0000000..eb01a6c
--- /dev/null
+++ b/m4/ax_check_link_flag.m4
@@ -0,0 +1,74 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_check_link_flag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_CHECK_LINK_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
+#
+# DESCRIPTION
+#
+# Check whether the given FLAG works with the linker or gives an error.
+# (Warnings, however, are ignored)
+#
+# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
+# success/failure.
+#
+# If EXTRA-FLAGS is defined, it is added to the linker's default flags
+# when the check is done. The check is thus made with the flags: "LDFLAGS
+# EXTRA-FLAGS FLAG". This can for example be used to force the linker to
+# issue an error when a bad flag is given.
+#
+# INPUT gives an alternative input source to AC_LINK_IFELSE.
+#
+# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
+# macro in sync with AX_CHECK_{PREPROC,COMPILE}_FLAG.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Guido U. Draheim
+# Copyright (c) 2011 Maarten Bosmans
+#
+# 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 .
+#
+# 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 4
+
+AC_DEFUN([AX_CHECK_LINK_FLAG],
+[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
+AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_ldflags_$4_$1])dnl
+AC_CACHE_CHECK([whether the linker accepts $1], CACHEVAR, [
+ ax_check_save_flags=$LDFLAGS
+ LDFLAGS="$LDFLAGS $4 $1"
+ AC_LINK_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
+ [AS_VAR_SET(CACHEVAR,[yes])],
+ [AS_VAR_SET(CACHEVAR,[no])])
+ LDFLAGS=$ax_check_save_flags])
+AS_VAR_IF(CACHEVAR,yes,
+ [m4_default([$2], :)],
+ [m4_default([$3], :)])
+AS_VAR_POPDEF([CACHEVAR])dnl
+])dnl AX_CHECK_LINK_FLAGS
diff --git a/m4/ax_code_coverage.m4 b/m4/ax_code_coverage.m4
new file mode 100644
index 0000000..6c985eb
--- /dev/null
+++ b/m4/ax_code_coverage.m4
@@ -0,0 +1,273 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_code_coverage.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_CODE_COVERAGE()
+#
+# DESCRIPTION
+#
+# Defines CODE_COVERAGE_CPPFLAGS, CODE_COVERAGE_CFLAGS,
+# CODE_COVERAGE_CXXFLAGS and CODE_COVERAGE_LDFLAGS which should be
+# included in the CPPFLAGS, CFLAGS CXXFLAGS and LIBS/LDFLAGS variables of
+# every build target (program or library) which should be built with code
+# coverage support. Also defines CODE_COVERAGE_RULES which should be
+# substituted in your Makefile; and $enable_code_coverage which can be
+# used in subsequent configure output. CODE_COVERAGE_ENABLED is defined
+# and substituted, and corresponds to the value of the
+# --enable-code-coverage option, which defaults to being disabled.
+#
+# Test also for gcov program and create GCOV variable that could be
+# substituted.
+#
+# Note that all optimisation flags in CFLAGS must be disabled when code
+# coverage is enabled.
+#
+# Usage example:
+#
+# configure.ac:
+#
+# AX_CODE_COVERAGE
+#
+# Makefile.am:
+#
+# @CODE_COVERAGE_RULES@
+# my_program_LIBS = ... $(CODE_COVERAGE_LDFLAGS) ...
+# my_program_CPPFLAGS = ... $(CODE_COVERAGE_CPPFLAGS) ...
+# my_program_CFLAGS = ... $(CODE_COVERAGE_CFLAGS) ...
+# my_program_CXXFLAGS = ... $(CODE_COVERAGE_CXXFLAGS) ...
+#
+# This results in a "check-code-coverage" rule being added to any
+# Makefile.am which includes "@CODE_COVERAGE_RULES@" (assuming the module
+# has been configured with --enable-code-coverage). Running `make
+# check-code-coverage` in that directory will run the module's test suite
+# (`make check`) and build a code coverage report detailing the code which
+# was touched, then print the URI for the report.
+#
+# This code was derived from Makefile.decl in GLib, originally licenced
+# under LGPLv2.1+.
+#
+# LICENSE
+#
+# Copyright (c) 2012, 2016 Philip Withnall
+# Copyright (c) 2012 Xan Lopez
+# Copyright (c) 2012 Christian Persch
+# Copyright (c) 2012 Paolo Borelli
+# Copyright (c) 2012 Dan Winship
+# Copyright (c) 2015 Bastien ROUCARIES
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or (at
+# your option) any later version.
+#
+# This library 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 Lesser
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+
+#serial 15
+
+AC_DEFUN([AX_CODE_COVERAGE],[
+ dnl Check for --enable-code-coverage
+ AC_REQUIRE([AC_PROG_SED])
+
+ # allow to override gcov location
+ AC_ARG_WITH([gcov],
+ [AS_HELP_STRING([--with-gcov[=GCOV]], [use given GCOV for coverage (GCOV=gcov).])],
+ [_AX_CODE_COVERAGE_GCOV_PROG_WITH=$with_gcov],
+ [_AX_CODE_COVERAGE_GCOV_PROG_WITH=gcov])
+
+ AC_MSG_CHECKING([whether to build with code coverage support])
+ AC_ARG_ENABLE([code-coverage],
+ AS_HELP_STRING([--enable-code-coverage],
+ [Whether to enable code coverage support]),,
+ enable_code_coverage=no)
+
+ AM_CONDITIONAL([CODE_COVERAGE_ENABLED], [test x$enable_code_coverage = xyes])
+ AC_SUBST([CODE_COVERAGE_ENABLED], [$enable_code_coverage])
+ AC_MSG_RESULT($enable_code_coverage)
+
+ AS_IF([ test "$enable_code_coverage" = "yes" ], [
+ # check for gcov
+ AC_CHECK_TOOL([GCOV],
+ [$_AX_CODE_COVERAGE_GCOV_PROG_WITH],
+ [:])
+ AS_IF([test "X$GCOV" = "X:"],
+ [AC_MSG_ERROR([gcov is needed to do coverage])])
+ AC_SUBST([GCOV])
+
+ dnl Check if gcc is being used
+ AS_IF([ test "$GCC" = "no" ], [
+ AC_MSG_ERROR([not compiling with gcc, which is required for gcov code coverage])
+ ])
+
+ # List of supported lcov versions.
+ lcov_version_list="1.6 1.7 1.8 1.9 1.10 1.11 1.12"
+
+ AC_CHECK_PROG([LCOV], [lcov], [lcov])
+ AC_CHECK_PROG([GENHTML], [genhtml], [genhtml])
+
+ AS_IF([ test "$LCOV" ], [
+ AC_CACHE_CHECK([for lcov version], ax_cv_lcov_version, [
+ ax_cv_lcov_version=invalid
+ lcov_version=`$LCOV -v 2>/dev/null | $SED -e 's/^.* //'`
+ for lcov_check_version in $lcov_version_list; do
+ if test "$lcov_version" = "$lcov_check_version"; then
+ ax_cv_lcov_version="$lcov_check_version (ok)"
+ fi
+ done
+ ])
+ ], [
+ lcov_msg="To enable code coverage reporting you must have one of the following lcov versions installed: $lcov_version_list"
+ AC_MSG_ERROR([$lcov_msg])
+ ])
+
+ case $ax_cv_lcov_version in
+ ""|invalid[)]
+ lcov_msg="You must have one of the following versions of lcov: $lcov_version_list (found: $lcov_version)."
+ AC_MSG_ERROR([$lcov_msg])
+ LCOV="exit 0;"
+ ;;
+ esac
+
+ AS_IF([ test -z "$GENHTML" ], [
+ AC_MSG_ERROR([Could not find genhtml from the lcov package])
+ ])
+
+ dnl Build the code coverage flags
+ CODE_COVERAGE_CPPFLAGS="-DNDEBUG"
+ CODE_COVERAGE_CFLAGS="-O0 -g -fprofile-arcs -ftest-coverage"
+ CODE_COVERAGE_CXXFLAGS="-O0 -g -fprofile-arcs -ftest-coverage"
+ CODE_COVERAGE_LDFLAGS="-lgcov"
+
+ AC_SUBST([CODE_COVERAGE_CPPFLAGS])
+ AC_SUBST([CODE_COVERAGE_CFLAGS])
+ AC_SUBST([CODE_COVERAGE_CXXFLAGS])
+ AC_SUBST([CODE_COVERAGE_LDFLAGS])
+ ])
+
+[CODE_COVERAGE_RULES='
+# Code coverage
+#
+# Optional:
+# - CODE_COVERAGE_DIRECTORY: Top-level directory for code coverage reporting.
+# Multiple directories may be specified, separated by whitespace.
+# (Default: $(top_builddir))
+# - CODE_COVERAGE_OUTPUT_FILE: Filename and path for the .info file generated
+# by lcov for code coverage. (Default:
+# $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info)
+# - CODE_COVERAGE_OUTPUT_DIRECTORY: Directory for generated code coverage
+# reports to be created. (Default:
+# $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage)
+# - CODE_COVERAGE_BRANCH_COVERAGE: Set to 1 to enforce branch coverage,
+# set to 0 to disable it and leave empty to stay with the default.
+# (Default: empty)
+# - CODE_COVERAGE_LCOV_SHOPTS_DEFAULT: Extra options shared between both lcov
+# instances. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE)
+# - CODE_COVERAGE_LCOV_SHOPTS: Extra options to shared between both lcov
+# instances. (Default: $CODE_COVERAGE_LCOV_SHOPTS_DEFAULT)
+# - CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH: --gcov-tool pathtogcov
+# - CODE_COVERAGE_LCOV_OPTIONS_DEFAULT: Extra options to pass to the
+# collecting lcov instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH)
+# - CODE_COVERAGE_LCOV_OPTIONS: Extra options to pass to the collecting lcov
+# instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_DEFAULT)
+# - CODE_COVERAGE_LCOV_RMOPTS_DEFAULT: Extra options to pass to the filtering
+# lcov instance. (Default: empty)
+# - CODE_COVERAGE_LCOV_RMOPTS: Extra options to pass to the filtering lcov
+# instance. (Default: $CODE_COVERAGE_LCOV_RMOPTS_DEFAULT)
+# - CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT: Extra options to pass to the
+# genhtml instance. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE)
+# - CODE_COVERAGE_GENHTML_OPTIONS: Extra options to pass to the genhtml
+# instance. (Default: $CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT)
+# - CODE_COVERAGE_IGNORE_PATTERN: Extra glob pattern of files to ignore
+#
+# The generated report will be titled using the $(PACKAGE_NAME) and
+# $(PACKAGE_VERSION). In order to add the current git hash to the title,
+# use the git-version-gen script, available online.
+
+# Optional variables
+CODE_COVERAGE_DIRECTORY ?= $(top_builddir)
+CODE_COVERAGE_OUTPUT_FILE ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info
+CODE_COVERAGE_OUTPUT_DIRECTORY ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage
+CODE_COVERAGE_BRANCH_COVERAGE ?=
+CODE_COVERAGE_LCOV_SHOPTS_DEFAULT ?= $(if $(CODE_COVERAGE_BRANCH_COVERAGE),\
+--rc lcov_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE))
+CODE_COVERAGE_LCOV_SHOPTS ?= $(CODE_COVERAGE_LCOV_SHOPTS_DEFAULT)
+CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH ?= --gcov-tool "$(GCOV)"
+CODE_COVERAGE_LCOV_OPTIONS_DEFAULT ?= $(CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH)
+CODE_COVERAGE_LCOV_OPTIONS ?= $(CODE_COVERAGE_LCOV_OPTIONS_DEFAULT)
+CODE_COVERAGE_LCOV_RMOPTS_DEFAULT ?=
+CODE_COVERAGE_LCOV_RMOPTS ?= $(CODE_COVERAGE_LCOV_RMOPTS_DEFAULT)
+CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT ?=\
+$(if $(CODE_COVERAGE_BRANCH_COVERAGE),\
+--rc genhtml_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE))
+CODE_COVERAGE_GENHTML_OPTIONS ?= $(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULTS)
+CODE_COVERAGE_IGNORE_PATTERN ?=
+
+code_coverage_v_lcov_cap = $(code_coverage_v_lcov_cap_$(V))
+code_coverage_v_lcov_cap_ = $(code_coverage_v_lcov_cap_$(AM_DEFAULT_VERBOSITY))
+code_coverage_v_lcov_cap_0 = @echo " LCOV --capture"\
+ $(CODE_COVERAGE_OUTPUT_FILE);
+code_coverage_v_lcov_ign = $(code_coverage_v_lcov_ign_$(V))
+code_coverage_v_lcov_ign_ = $(code_coverage_v_lcov_ign_$(AM_DEFAULT_VERBOSITY))
+code_coverage_v_lcov_ign_0 = @echo " LCOV --remove /tmp/*"\
+ $(CODE_COVERAGE_IGNORE_PATTERN);
+code_coverage_v_genhtml = $(code_coverage_v_genhtml_$(V))
+code_coverage_v_genhtml_ = $(code_coverage_v_genhtml_$(AM_DEFAULT_VERBOSITY))
+code_coverage_v_genhtml_0 = @echo " GEN " $(CODE_COVERAGE_OUTPUT_DIRECTORY);
+code_coverage_quiet = $(code_coverage_quiet_$(V))
+code_coverage_quiet_ = $(code_coverage_quiet_$(AM_DEFAULT_VERBOSITY))
+code_coverage_quiet_0 = --quiet
+
+# sanitizes the test-name: replaces with underscores: dashes and dots
+code_coverage_sanitize = $(subst -,_,$(subst .,_,$(1)))
+
+# Use recursive makes in order to ignore errors during check
+check-code-coverage:
+ifeq ($(CODE_COVERAGE_ENABLED),yes)
+ -$(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) -k check
+ $(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) code-coverage-capture
+else
+ @echo "Need to reconfigure with --enable-code-coverage"
+endif
+
+# Capture code coverage data
+code-coverage-capture: code-coverage-capture-hook
+ifeq ($(CODE_COVERAGE_ENABLED),yes)
+ $(code_coverage_v_lcov_cap)$(LCOV) $(code_coverage_quiet) $(addprefix --directory ,$(CODE_COVERAGE_DIRECTORY)) --capture --output-file "$(CODE_COVERAGE_OUTPUT_FILE).tmp" --test-name "$(call code_coverage_sanitize,$(PACKAGE_NAME)-$(PACKAGE_VERSION))" --no-checksum --compat-libtool $(CODE_COVERAGE_LCOV_SHOPTS) $(CODE_COVERAGE_LCOV_OPTIONS)
+ $(code_coverage_v_lcov_ign)$(LCOV) $(code_coverage_quiet) $(addprefix --directory ,$(CODE_COVERAGE_DIRECTORY)) --remove "$(CODE_COVERAGE_OUTPUT_FILE).tmp" "/tmp/*" $(CODE_COVERAGE_IGNORE_PATTERN) --output-file "$(CODE_COVERAGE_OUTPUT_FILE)" $(CODE_COVERAGE_LCOV_SHOPTS) $(CODE_COVERAGE_LCOV_RMOPTS)
+ -@rm -f $(CODE_COVERAGE_OUTPUT_FILE).tmp
+ $(code_coverage_v_genhtml)LANG=C $(GENHTML) $(code_coverage_quiet) $(addprefix --prefix ,$(CODE_COVERAGE_DIRECTORY)) --output-directory "$(CODE_COVERAGE_OUTPUT_DIRECTORY)" --title "$(PACKAGE_NAME)-$(PACKAGE_VERSION) Code Coverage" --legend --show-details "$(CODE_COVERAGE_OUTPUT_FILE)" $(CODE_COVERAGE_GENHTML_OPTIONS)
+ @echo "file://$(abs_builddir)/$(CODE_COVERAGE_OUTPUT_DIRECTORY)/index.html"
+else
+ @echo "Need to reconfigure with --enable-code-coverage"
+endif
+
+# Hook rule executed before code-coverage-capture, overridable by the user
+code-coverage-capture-hook:
+
+ifeq ($(CODE_COVERAGE_ENABLED),yes)
+clean: code-coverage-clean
+code-coverage-clean:
+ -$(LCOV) --directory $(top_builddir) -z
+ -rm -rf $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_FILE).tmp $(CODE_COVERAGE_OUTPUT_DIRECTORY)
+ -find . -name "*.gcda" -o -name "*.gcov" -delete
+endif
+
+GITIGNOREFILES ?=
+GITIGNOREFILES += $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_DIRECTORY)
+
+A''M_DISTCHECK_CONFIGURE_FLAGS ?=
+A''M_DISTCHECK_CONFIGURE_FLAGS += --disable-code-coverage
+
+.PHONY: check-code-coverage code-coverage-capture code-coverage-capture-hook code-coverage-clean
+']
+
+ AC_SUBST([CODE_COVERAGE_RULES])
+ m4_ifdef([_AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE([CODE_COVERAGE_RULES])])
+])
diff --git a/m4/ax_configure_args.m4 b/m4/ax_configure_args.m4
new file mode 100644
index 0000000..0726b1b
--- /dev/null
+++ b/m4/ax_configure_args.m4
@@ -0,0 +1,70 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_configure_args.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_CONFIGURE_ARGS
+#
+# DESCRIPTION
+#
+# Helper macro for AX_ENABLE_BUILDDIR.
+#
+# The traditional way of starting a subdir-configure is running the script
+# with ${1+"$@"} but since autoconf 2.60 this is broken. Instead we have
+# to rely on eval'ing $ac_configure_args however some old autoconf
+# versions do not provide that. To ensure maximum portability of autoconf
+# extension macros this helper can be AC_REQUIRE'd so that
+# $ac_configure_args will alsways be present.
+#
+# Sadly, the traditional "exec $SHELL" of the enable_builddir macros is
+# spoiled now and must be replaced by "eval + exit $?".
+#
+# Example:
+#
+# AC_DEFUN([AX_ENABLE_SUBDIR],[dnl
+# AC_REQUIRE([AX_CONFIGURE_ARGS])dnl
+# eval $SHELL $ac_configure_args || exit $?
+# ...])
+#
+# LICENSE
+#
+# Copyright (c) 2008 Guido U. Draheim
+#
+# 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 .
+#
+# 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 9
+
+AC_DEFUN([AX_CONFIGURE_ARGS],[
+ # [$]@ is unsable in 2.60+ but earlier autoconf had no ac_configure_args
+ if test "${ac_configure_args+set}" != "set" ; then
+ ac_configure_args=
+ for ac_arg in ${1+"[$]@"}; do
+ ac_configure_args="$ac_configure_args '$ac_arg'"
+ done
+ fi
+])
diff --git a/m4/ax_enable_builddir.m4 b/m4/ax_enable_builddir.m4
new file mode 100644
index 0000000..5f4ba32
--- /dev/null
+++ b/m4/ax_enable_builddir.m4
@@ -0,0 +1,302 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_enable_builddir.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_ENABLE_BUILDDIR [(dirstring-or-command [,Makefile.mk [,-all]])]
+#
+# DESCRIPTION
+#
+# If the current configure was run within the srcdir then we move all
+# configure-files into a subdir and let the configure steps continue
+# there. We provide an option --disable-builddir to suppress the move into
+# a separate builddir.
+#
+# Defaults:
+#
+# $1 = $host (overridden with $HOST)
+# $2 = Makefile.mk
+# $3 = -all
+#
+# This macro must be called before AM_INIT_AUTOMAKE. It creates a default
+# toplevel srcdir Makefile from the information found in the created
+# toplevel builddir Makefile. It just copies the variables and
+# rule-targets, each extended with a default rule-execution that recurses
+# into the build directory of the current "HOST". You can override the
+# auto-dection through `config.guess` and build-time of course, as in
+#
+# make HOST=i386-mingw-cross
+#
+# which can of course set at configure time as well using
+#
+# configure --host=i386-mingw-cross
+#
+# After the default has been created, additional rules can be appended
+# that will not just recurse into the subdirectories and only ever exist
+# in the srcdir toplevel makefile - these parts are read from the $2 =
+# Makefile.mk file
+#
+# The automatic rules are usually scanning the toplevel Makefile for lines
+# like '#### $host |$builddir' to recognize the place where to recurse
+# into. Usually, the last one is the only one used. However, almost all
+# targets have an additional "*-all" rule which makes the script to
+# recurse into _all_ variants of the current HOST (!!) setting. The "-all"
+# suffix can be overriden for the macro as well.
+#
+# a special rule is only given for things like "dist" that will copy the
+# tarball from the builddir to the sourcedir (or $(PUB)) for reason of
+# convenience.
+#
+# LICENSE
+#
+# Copyright (c) 2009 Guido U. Draheim
+# Copyright (c) 2009 Alan Jenkins
+#
+# 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 .
+#
+# 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 25
+
+AC_DEFUN([AX_ENABLE_BUILDDIR],[
+AC_REQUIRE([AC_CANONICAL_HOST])[]dnl
+AC_REQUIRE([AC_CANONICAL_TARGET])[]dnl
+AC_REQUIRE([AX_CONFIGURE_ARGS])[]dnl
+AC_REQUIRE([AM_AUX_DIR_EXPAND])[]dnl
+AC_BEFORE([$0],[AM_INIT_AUTOMAKE])dnl
+AS_VAR_PUSHDEF([SUB],[ax_enable_builddir])dnl
+AS_VAR_PUSHDEF([AUX],[ax_enable_builddir_auxdir])dnl
+AS_VAR_PUSHDEF([SED],[ax_enable_builddir_sed])dnl
+SUB="."
+AC_ARG_ENABLE([builddir], AS_HELP_STRING(
+ [--disable-builddir],[disable automatic build in subdir of sources])
+ ,[SUB="$enableval"], [SUB="auto"])
+if test ".$ac_srcdir_defaulted" != ".no" ; then
+if test ".$srcdir" = ".." ; then
+ if test -f config.status ; then
+ AC_MSG_NOTICE(toplevel srcdir already configured... skipping subdir build)
+ else
+ test ".$SUB" = "." && SUB="."
+ test ".$SUB" = ".no" && SUB="."
+ test ".$TARGET" = "." && TARGET="$target"
+ test ".$SUB" = ".auto" && SUB="m4_ifval([$1], [$1],[$TARGET])"
+ if test ".$SUB" != ".." ; then # we know where to go and
+ AS_MKDIR_P([$SUB])
+ echo __.$SUB.__ > $SUB/conftest.tmp
+ cd $SUB
+ if grep __.$SUB.__ conftest.tmp >/dev/null 2>/dev/null ; then
+ rm conftest.tmp
+ AC_MSG_RESULT([continue configure in default builddir "./$SUB"])
+ else
+ AC_MSG_ERROR([could not change to default builddir "./$SUB"])
+ fi
+ srcdir=`echo "$SUB" |
+ sed -e 's,^\./,,;s,[[^/]]$,&/,;s,[[^/]]*/,../,g;s,[[/]]$,,;'`
+ # going to restart from subdirectory location
+ test -f $srcdir/config.log && mv $srcdir/config.log .
+ test -f $srcdir/confdefs.h && mv $srcdir/confdefs.h .
+ test -f $srcdir/conftest.log && mv $srcdir/conftest.log .
+ test -f $srcdir/$cache_file && mv $srcdir/$cache_file .
+ AC_MSG_RESULT(....exec $SHELL $srcdir/[$]0 "--srcdir=$srcdir" "--enable-builddir=$SUB" ${1+"[$]@"})
+ case "[$]0" in # restart
+ [[\\/]]* | ?:[[\\/]]*) # Asbolute name
+ eval $SHELL "'[$]0'" "'--srcdir=$srcdir'" "'--enable-builddir=$SUB'" $ac_configure_args ;;
+ *) eval $SHELL "'$srcdir/[$]0'" "'--srcdir=$srcdir'" "'--enable-builddir=$SUB'" $ac_configure_args ;;
+ esac ; exit $?
+ fi
+ fi
+fi fi
+test ".$SUB" = ".auto" && SUB="."
+dnl ac_path_prog uses "set dummy" to override $@ which would defeat the "exec"
+AC_PATH_PROG(SED,gsed sed, sed)
+AUX="$am_aux_dir"
+AS_VAR_POPDEF([SED])dnl
+AS_VAR_POPDEF([AUX])dnl
+AS_VAR_POPDEF([SUB])dnl
+AC_CONFIG_COMMANDS([buildir],[dnl .............. config.status ..............
+AS_VAR_PUSHDEF([SUB],[ax_enable_builddir])dnl
+AS_VAR_PUSHDEF([TOP],[top_srcdir])dnl
+AS_VAR_PUSHDEF([SRC],[ac_top_srcdir])dnl
+AS_VAR_PUSHDEF([AUX],[ax_enable_builddir_auxdir])dnl
+AS_VAR_PUSHDEF([SED],[ax_enable_builddir_sed])dnl
+pushdef([END],[Makefile.mk])dnl
+pushdef([_ALL],[ifelse([$3],,[-all],[$3])])dnl
+ SRC="$ax_enable_builddir_srcdir"
+ if test ".$SUB" = ".." ; then
+ if test -f "$TOP/Makefile" ; then
+ AC_MSG_NOTICE([skipping TOP/Makefile - left untouched])
+ else
+ AC_MSG_NOTICE([skipping TOP/Makefile - not created])
+ fi
+ else
+ if test -f "$SRC/Makefile" ; then
+ a=`grep "^VERSION " "$SRC/Makefile"` ; b=`grep "^VERSION " Makefile`
+ test "$a" != "$b" && rm "$SRC/Makefile"
+ fi
+ if test -f "$SRC/Makefile" ; then
+ echo "$SRC/Makefile : $SRC/Makefile.in" > $tmp/conftemp.mk
+ echo " []@ echo 'REMOVED,,,' >\$[]@" >> $tmp/conftemp.mk
+ eval "${MAKE-make} -f $tmp/conftemp.mk 2>/dev/null >/dev/null"
+ if grep '^REMOVED,,,' "$SRC/Makefile" >/dev/null
+ then rm $SRC/Makefile ; fi
+ cp $tmp/conftemp.mk $SRC/makefiles.mk~ ## DEBUGGING
+ fi
+ if test ! -f "$SRC/Makefile" ; then
+ AC_MSG_NOTICE([create TOP/Makefile guessed from local Makefile])
+ x='`' ; cat >$tmp/conftemp.sed <<_EOF
+/^\$/n
+x
+/^\$/bS
+x
+/\\\\\$/{H;d;}
+{H;s/.*//;x;}
+bM
+:S
+x
+/\\\\\$/{h;d;}
+{h;s/.*//;x;}
+:M
+s/\\(\\n\\) /\\1 /g
+/^ /d
+/^[[ ]]*[[\\#]]/d
+/^VPATH *=/d
+s/^srcdir *=.*/srcdir = ./
+s/^top_srcdir *=.*/top_srcdir = ./
+/[[:=]]/!d
+/^\\./d
+dnl Now handle rules (i.e. lines containing ":" but not " = ").
+/ = /b
+/ .= /b
+/:/!b
+s/:.*/:/
+s/ / /g
+s/ \\([[a-z]][[a-z-]]*[[a-zA-Z0-9]]\\)\\([[ :]]\\)/ \\1 \\1[]_ALL\\2/g
+s/^\\([[a-z]][[a-z-]]*[[a-zA-Z0-9]]\\)\\([[ :]]\\)/\\1 \\1[]_ALL\\2/
+s/ / /g
+/^all all[]_ALL[[ :]]/i\\
+all-configured : all[]_ALL
+dnl dist-all exists... and would make for dist-all-all
+s/ [[a-zA-Z0-9-]]*[]_ALL [[a-zA-Z0-9-]]*[]_ALL[]_ALL//g
+/[]_ALL[]_ALL/d
+a\\
+ @ HOST="\$(HOST)\" \\\\\\
+ ; test ".\$\$HOST" = "." && HOST=$x sh $AUX/config.guess $x \\\\\\
+ ; BUILD=$x grep "^#### \$\$HOST " Makefile | sed -e 's/.*|//' $x \\\\\\
+ ; use=$x basename "\$\@" _ALL $x; n=$x echo \$\$BUILD | wc -w $x \\\\\\
+ ; echo "MAKE \$\$HOST : \$\$n * \$\@"; if test "\$\$n" -eq "0" ; then : \\\\\\
+ ; BUILD=$x grep "^####.*|" Makefile |tail -1| sed -e 's/.*|//' $x ; fi \\\\\\
+ ; test ".\$\$BUILD" = "." && BUILD="." \\\\\\
+ ; test "\$\$use" = "\$\@" && BUILD=$x echo "\$\$BUILD" | tail -1 $x \\\\\\
+ ; for i in \$\$BUILD ; do test ".\$\$i" = "." && continue \\\\\\
+ ; (cd "\$\$i" && test ! -f configure && \$(MAKE) \$\$use) || exit; done
+dnl special rule add-on: "dist" copies the tarball to $(PUB). (source tree)
+/dist[]_ALL *:/a\\
+ @ HOST="\$(HOST)\" \\\\\\
+ ; test ".\$\$HOST" = "." && HOST=$x sh $AUX/config.guess $x \\\\\\
+ ; BUILD=$x grep "^#### \$\$HOST " Makefile | sed -e 's/.*|//' $x \\\\\\
+ ; found=$x echo \$\$BUILD | wc -w $x \\\\\\
+ ; echo "MAKE \$\$HOST : \$\$found \$(PACKAGE)-\$(VERSION).tar.*" \\\\\\
+ ; if test "\$\$found" -eq "0" ; then : \\\\\\
+ ; BUILD=$x grep "^#### .*|" Makefile |tail -1| sed -e 's/.*|//' $x \\\\\\
+ ; fi ; for i in \$\$BUILD ; do test ".\$\$i" = "." && continue \\\\\\
+ ; for f in \$\$i/\$(PACKAGE)-\$(VERSION).tar.* \\\\\\
+ ; do test -f "\$\$f" && mv "\$\$f" \$(PUB). ; done ; break ; done
+dnl special rule add-on: "dist-foo" copies all the archives to $(PUB). (source tree)
+/dist-[[a-zA-Z0-9]]*[]_ALL *:/a\\
+ @ HOST="\$(HOST)\" \\\\\\
+ ; test ".\$\$HOST" = "." && HOST=$x sh ./config.guess $x \\\\\\
+ ; BUILD=$x grep "^#### \$\$HOST " Makefile | sed -e 's/.*|//' $x \\\\\\
+ ; found=$x echo \$\$BUILD | wc -w $x \\\\\\
+ ; echo "MAKE \$\$HOST : \$\$found \$(PACKAGE)-\$(VERSION).*" \\\\\\
+ ; if test "\$\$found" -eq "0" ; then : \\\\\\
+ ; BUILD=$x grep "^#### .*|" Makefile |tail -1| sed -e 's/.*|//' $x \\\\\\
+ ; fi ; for i in \$\$BUILD ; do test ".\$\$i" = "." && continue \\\\\\
+ ; for f in \$\$i/\$(PACKAGE)-\$(VERSION).* \\\\\\
+ ; do test -f "\$\$f" && mv "\$\$f" \$(PUB). ; done ; break ; done
+dnl special rule add-on: "distclean" removes all local builddirs completely
+/distclean[]_ALL *:/a\\
+ @ HOST="\$(HOST)\" \\\\\\
+ ; test ".\$\$HOST" = "." && HOST=$x sh $AUX/config.guess $x \\\\\\
+ ; BUILD=$x grep "^#### .*|" Makefile | sed -e 's/.*|//' $x \\\\\\
+ ; use=$x basename "\$\@" _ALL $x; n=$x echo \$\$BUILD | wc -w $x \\\\\\
+ ; echo "MAKE \$\$HOST : \$\$n * \$\@ (all local builds)" \\\\\\
+ ; test ".\$\$BUILD" = "." && BUILD="." \\\\\\
+ ; for i in \$\$BUILD ; do test ".\$\$i" = "." && continue \\\\\\
+ ; echo "# rm -r \$\$i"; done ; echo "# (sleep 3)" ; sleep 3 \\\\\\
+ ; for i in \$\$BUILD ; do test ".\$\$i" = "." && continue \\\\\\
+ ; echo "\$\$i" | grep "^/" > /dev/null && continue \\\\\\
+ ; echo "\$\$i" | grep "^../" > /dev/null && continue \\\\\\
+ ; echo "rm -r \$\$i"; (rm -r "\$\$i") ; done ; rm Makefile
+_EOF
+ cp "$tmp/conftemp.sed" "$SRC/makefile.sed~" ## DEBUGGING
+ $SED -f $tmp/conftemp.sed Makefile >$SRC/Makefile
+ if test -f "$SRC/m4_ifval([$2],[$2],[END])" ; then
+ AC_MSG_NOTICE([extend TOP/Makefile with TOP/m4_ifval([$2],[$2],[END])])
+ cat $SRC/END >>$SRC/Makefile
+ fi ; xxxx="####"
+ echo "$xxxx CONFIGURATIONS FOR TOPLEVEL MAKEFILE: " >>$SRC/Makefile
+ # sanity check
+ if grep '^; echo "MAKE ' $SRC/Makefile >/dev/null ; then
+ AC_MSG_NOTICE([buggy sed found - it deletes tab in "a" text parts])
+ $SED -e '/^@ HOST=/s/^/ /' -e '/^; /s/^/ /' $SRC/Makefile \
+ >$SRC/Makefile~
+ (test -s $SRC/Makefile~ && mv $SRC/Makefile~ $SRC/Makefile) 2>/dev/null
+ fi
+ else
+ xxxx="\\#\\#\\#\\#"
+ # echo "/^$xxxx *$ax_enable_builddir_host /d" >$tmp/conftemp.sed
+ echo "s!^$xxxx [[^|]]* | *$SUB *\$!$xxxx ...... $SUB!" >$tmp/conftemp.sed
+ $SED -f "$tmp/conftemp.sed" "$SRC/Makefile" >$tmp/mkfile.tmp
+ cp "$tmp/conftemp.sed" "$SRC/makefiles.sed~" ## DEBUGGING
+ cp "$tmp/mkfile.tmp" "$SRC/makefiles.out~" ## DEBUGGING
+ if cmp -s "$SRC/Makefile" "$tmp/mkfile.tmp" 2>/dev/null ; then
+ AC_MSG_NOTICE([keeping TOP/Makefile from earlier configure])
+ rm "$tmp/mkfile.tmp"
+ else
+ AC_MSG_NOTICE([reusing TOP/Makefile from earlier configure])
+ mv "$tmp/mkfile.tmp" "$SRC/Makefile"
+ fi
+ fi
+ AC_MSG_NOTICE([build in $SUB (HOST=$ax_enable_builddir_host)])
+ xxxx="####"
+ echo "$xxxx" "$ax_enable_builddir_host" "|$SUB" >>$SRC/Makefile
+ fi
+popdef([END])dnl
+AS_VAR_POPDEF([SED])dnl
+AS_VAR_POPDEF([AUX])dnl
+AS_VAR_POPDEF([SRC])dnl
+AS_VAR_POPDEF([TOP])dnl
+AS_VAR_POPDEF([SUB])dnl
+],[dnl
+ax_enable_builddir_srcdir="$srcdir" # $srcdir
+ax_enable_builddir_host="$HOST" # $HOST / $host
+ax_enable_builddir_version="$VERSION" # $VERSION
+ax_enable_builddir_package="$PACKAGE" # $PACKAGE
+ax_enable_builddir_auxdir="$ax_enable_builddir_auxdir" # $AUX
+ax_enable_builddir_sed="$ax_enable_builddir_sed" # $SED
+ax_enable_builddir="$ax_enable_builddir" # $SUB
+])dnl
+])
diff --git a/m4/ax_extend_srcdir.m4 b/m4/ax_extend_srcdir.m4
new file mode 100644
index 0000000..40f3787
--- /dev/null
+++ b/m4/ax_extend_srcdir.m4
@@ -0,0 +1,86 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_extend_srcdir.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_EXTEND_SRCDIR
+#
+# DESCRIPTION
+#
+# The AX_EXTEND_SRCDIR macro extends $srcdir by one path component.
+#
+# As an example, when working in /home/michael/i3-4.12/build and calling
+# ../configure, your $srcdir is "..". After calling AX_EXTEND_SRCDIR,
+# $srcdir will be set to "../../i3-4.12".
+#
+# The result of extending $srcdir is that filenames (e.g. in the output of
+# the "backtrace" gdb command) will include one more path component of the
+# absolute source path. The additional path component makes it easy for
+# users to recognize which files belong to the PACKAGE, and -- provided a
+# dist tarball was unpacked -- which version of PACKAGE was used.
+#
+# As an example, in "backtrace", you will see:
+#
+# #0 main (argc=1, argv=0x7fffffff1fc8) at ../../i3-4.12/src/main.c:187
+#
+# instead of:
+#
+# #0 main (argc=1, argv=0x7fffffff1fc8) at ../src/main.c:187
+#
+# In case your code uses the __FILE__ preprocessor directive to refer to
+# the filename of the current source file (e.g. in debug messages), using
+# the extended path might be undesirable. For this purpose,
+# AX_EXTEND_SRCDIR defines the output variable AX_EXTEND_SRCDIR_CPPFLAGS,
+# which can be added to AM_CPPFLAGS in Makefile.am in order to define the
+# preprocessor directive STRIPPED__FILE__. As an example, when compiling
+# the file "../../i3-4.12/src/main.c", STRIPPED__FILE__ evaluates to
+# "main.c".
+#
+# There are some caveats: When $srcdir is "." (i.e. when ./configure was
+# called instead of ../configure in a separate build directory),
+# AX_EXTEND_SRCDIR will still extend $srcdir, but the intended effect will
+# not be achieved because of the way automake specifies file paths:
+# automake defines COMPILE to use "`test -f '$source' || echo
+# '\$(srcdir)/'`$source" in order to prefer files in the current directory
+# over specifying $srcdir explicitly.
+#
+# The AX_EXTEND_SRCDIR author is not aware of any way to influence this
+# automake behavior. Patches very welcome.
+#
+# To work around this issue, you can use AX_ENABLE_BUILDDIR i.e. by adding
+# the following code to configure.ac:
+#
+# AX_ENABLE_BUILDDIR
+# dnl ...
+# AX_EXTEND_SRCDIR
+#
+# Then also add this bit to Makefile.am (if you wish to use
+# STRIPPED__FILE__ in your code):
+#
+# AM_CPPFLAGS = @AX_EXTEND_SRCDIR_CPPFLAGS@
+#
+# LICENSE
+#
+# Copyright (c) 2016 Michael Stapelberg
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 3
+
+AC_DEFUN([AX_EXTEND_SRCDIR],
+[dnl
+AS_CASE([$srcdir],
+ [.|.*|/*],
+ [
+ # pwd -P is specified in IEEE 1003.1 from 2004
+ as_dir=`cd "$srcdir" && pwd -P`
+ as_base=`AS_BASENAME([$as_dir])`
+ srcdir=${srcdir}/../${as_base}
+
+ AC_SUBST([AX_EXTEND_SRCDIR_CPPFLAGS], ["-DSTRIPPED__FILE__=AS_ESCAPE([\"$$(basename $<)\"])"])
+ ])
+])dnl AX_EXTEND_SRCDIR
diff --git a/m4/ax_require_defined.m4 b/m4/ax_require_defined.m4
new file mode 100644
index 0000000..cae1111
--- /dev/null
+++ b/m4/ax_require_defined.m4
@@ -0,0 +1,37 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_require_defined.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_REQUIRE_DEFINED(MACRO)
+#
+# DESCRIPTION
+#
+# AX_REQUIRE_DEFINED is a simple helper for making sure other macros have
+# been defined and thus are available for use. This avoids random issues
+# where a macro isn't expanded. Instead the configure script emits a
+# non-fatal:
+#
+# ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found
+#
+# It's like AC_REQUIRE except it doesn't expand the required macro.
+#
+# Here's an example:
+#
+# AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG])
+#
+# LICENSE
+#
+# Copyright (c) 2014 Mike Frysinger
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 1
+
+AC_DEFUN([AX_REQUIRE_DEFINED], [dnl
+ m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])])
+])dnl AX_REQUIRE_DEFINED
diff --git a/m4/ax_sanitizers.m4 b/m4/ax_sanitizers.m4
new file mode 100644
index 0000000..836d4af
--- /dev/null
+++ b/m4/ax_sanitizers.m4
@@ -0,0 +1,130 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_sanitizers.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_SANITIZERS([SANITIZERS], [ENABLED-BY-DEFAULT], [ACTION-SUCCESS])
+#
+# DESCRIPTION
+#
+# Offers users to enable one or more sanitizers (see
+# https://github.com/google/sanitizers) with the corresponding
+# --enable--sanitizer option.
+#
+# SANITIZERS is a whitespace-separated list of sanitizers to offer via
+# --enable--sanitizer options, e.g. "address memory" for the
+# address sanitizer and the memory sanitizer. If SANITIZERS is not specified,
+# all known sanitizers to AX_SANITIZERS will be offered, which at the time of
+# writing are "address memory undefined".
+# NOTE that SANITIZERS is expanded at autoconf time, not at configure time,
+# i.e. you cannot use shell variables in SANITIZERS.
+#
+# ENABLED-BY-DEFAULT is a whitespace-separated list of sanitizers which
+# should be enabled by default, e.g. "memory undefined". Note that not all
+# sanitizers can be combined, e.g. memory sanitizer cannot be enabled when
+# address sanitizer is already enabled.
+# Set ENABLED-BY-DEFAULT to a single whitespace in order to disable all
+# sanitizers by default.
+# ENABLED-BY-DEFAULT is expanded at configure time, so you can use shell
+# variables.
+#
+# ACTION-SUCCESS allows to specify shell commands to execute on success, i.e.
+# when one of the sanitizers was successfully enabled. This is a good place
+# to call AC_DEFINE for any precompiler constants you might need to make your
+# code play nice with sanitizers.
+#
+# The variable ax_enabled_sanitizers contains a whitespace-separated list of
+# all enabled sanitizers, so that you can print them at the end of configure,
+# if you wish.
+#
+# The additional --enable-sanitizers option allows users to enable/disable
+# all sanitizers, effectively overriding ENABLED-BY-DEFAULT.
+#
+# EXAMPLES
+#
+# AX_SANITIZERS([address])
+# dnl offer users to enable address sanitizer via --enable-address-sanitizer
+#
+# is_debug_build=…
+# if test "x$is_debug_build" = "xyes"; then
+# default_sanitizers="address memory"
+# else
+# default_sanitizers=
+# fi
+# AX_SANITIZERS([address memory], [$default_sanitizers])
+# dnl enable address sanitizer and memory sanitizer by default for debug
+# dnl builds, e.g. when building from git instead of a dist tarball.
+#
+# AX_SANITIZERS(, , [
+# AC_DEFINE([SANITIZERS_ENABLED],
+# [],
+# [At least one sanitizer was enabled])])
+# dnl enable all sanitizers known to AX_SANITIZERS by default and set the
+# dnl SANITIZERS_ENABLED precompiler constant.
+#
+# AX_SANITIZERS(, [ ])
+# dnl provide all sanitizers, but enable none by default.
+#
+# LICENSE
+#
+# Copyright (c) 2016 Michael Stapelberg
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved. This file is offered as-is,
+# without any warranty.
+
+AC_DEFUN([AX_SANITIZERS],
+[AX_REQUIRE_DEFINED([AX_CHECK_COMPILE_FLAG])
+AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG])
+AX_REQUIRE_DEFINED([AX_APPEND_FLAG])
+AC_ARG_ENABLE(sanitizers,
+ AS_HELP_STRING(
+ [--enable-sanitizers],
+ [enable all known sanitizers]),
+ [ax_sanitizers_default=$enableval],
+ [ax_sanitizers_default=])
+ax_enabled_sanitizers=
+m4_foreach_w([mysan], m4_default($1, [address memory undefined]), [
+ dnl If ax_sanitizers_default is unset, i.e. the user neither explicitly
+ dnl enabled nor explicitly disabled all sanitizers, we get the default value
+ dnl for this sanitizer based on whether it is listed in ENABLED-BY-DEFAULT.
+ AS_IF([test "x$ax_sanitizers_default" = "x"], [dnl
+ ax_sanitizer_default=
+ for mycheck in m4_default([$2], [address memory undefined]); do
+ AS_IF([test "x$mycheck" = "x[]mysan"], [ax_sanitizer_default=yes])
+ done
+ AS_IF([test "x$ax_sanitizer_default" = "x"], [ax_sanitizer_default=no])
+ ],
+ [ax_sanitizer_default=$ax_sanitizers_default])
+ AC_ARG_ENABLE(mysan[]-sanitizer,
+ AS_HELP_STRING(
+ [--enable-[]mysan[]-sanitizer],
+ [enable -fsanitize=mysan]),
+ [ax_sanitizer_enabled=$enableval],
+ [ax_sanitizer_enabled=$ax_sanitizer_default])
+
+AS_IF([test "x$ax_sanitizer_enabled" = "xyes"], [
+dnl Not using AX_APPEND_COMPILE_FLAGS and AX_APPEND_LINK_FLAGS because they
+dnl lack the ability to specify ACTION-SUCCESS.
+ AX_CHECK_COMPILE_FLAG([-fsanitize=[]mysan], [
+ AX_CHECK_LINK_FLAG([-fsanitize=[]mysan], [
+ AX_APPEND_FLAG([-fsanitize=[]mysan], [])
+dnl If and only if libtool is being used, LDFLAGS needs to contain -Wc,-fsanitize=….
+dnl See e.g. https://sources.debian.net/src/systemd/231-7/configure.ac/?hl=128#L135
+dnl TODO: how can recognize that situation and add -Wc,?
+ AX_APPEND_FLAG([-fsanitize=[]mysan], [LDFLAGS])
+dnl TODO: add -fPIE -pie for memory
+ # -fno-omit-frame-pointer results in nicer stack traces in error
+ # messages, see http://clang.llvm.org/docs/AddressSanitizer.html#usage
+ AX_CHECK_COMPILE_FLAG([-fno-omit-frame-pointer], [
+ AX_APPEND_FLAG([-fno-omit-frame-pointer], [])])
+dnl TODO: at least for clang, we should specify exactly -O1, not -O2 or -O0, so that performance is reasonable but stacktraces are not tampered with (due to inlining), see http://clang.llvm.org/docs/AddressSanitizer.html#usage
+ m4_default([$3], :)
+ ax_enabled_sanitizers="[]mysan $ax_enabled_sanitizers"
+ ])
+ ])
+])
+])dnl
+])dnl AX_SANITIZERS
diff --git a/pam/i3lock b/pam/i3lock
new file mode 100644
index 0000000..6eb8e68
--- /dev/null
+++ b/pam/i3lock
@@ -0,0 +1,6 @@
+#
+# PAM configuration file for the i3lock screen locker. By default, it includes
+# the 'login' configuration file (see /etc/pam.d/login)
+#
+
+auth include login
diff --git a/randr.c b/randr.c
new file mode 100644
index 0000000..4b1ea97
--- /dev/null
+++ b/randr.c
@@ -0,0 +1,297 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * © 2010 Michael Stapelberg
+ *
+ * See LICENSE for licensing information
+ *
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "i3lock.h"
+#include "xcb.h"
+#include "randr.h"
+
+/* Number of Xinerama screens which are currently present. */
+int xr_screens = 0;
+
+/* The resolutions of the currently present Xinerama screens. */
+Rect *xr_resolutions = NULL;
+
+static bool xinerama_active;
+static bool has_randr = false;
+static bool has_randr_1_5 = false;
+extern bool debug_mode;
+
+void _xinerama_init(void);
+
+void randr_init(int *event_base, xcb_window_t root) {
+ const xcb_query_extension_reply_t *extreply;
+
+ extreply = xcb_get_extension_data(conn, &xcb_randr_id);
+ if (!extreply->present) {
+ DEBUG("RandR is not present, falling back to Xinerama.\n");
+ _xinerama_init();
+ return;
+ }
+
+ xcb_generic_error_t *err;
+ xcb_randr_query_version_reply_t *randr_version =
+ xcb_randr_query_version_reply(
+ conn, xcb_randr_query_version(conn, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), &err);
+ if (err != NULL) {
+ DEBUG("Could not query RandR version: X11 error code %d\n", err->error_code);
+ _xinerama_init();
+ return;
+ }
+
+ has_randr = true;
+
+ has_randr_1_5 = (randr_version->major_version >= 1) &&
+ (randr_version->minor_version >= 5);
+
+ free(randr_version);
+
+ if (event_base != NULL)
+ *event_base = extreply->first_event;
+
+ xcb_randr_select_input(conn, root,
+ XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
+ XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
+ XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
+ XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
+
+ xcb_flush(conn);
+}
+
+void _xinerama_init(void) {
+ if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) {
+ DEBUG("Xinerama extension not found, disabling.\n");
+ return;
+ }
+
+ xcb_xinerama_is_active_cookie_t cookie;
+ xcb_xinerama_is_active_reply_t *reply;
+
+ cookie = xcb_xinerama_is_active(conn);
+ reply = xcb_xinerama_is_active_reply(conn, cookie, NULL);
+ if (!reply)
+ return;
+
+ if (!reply->state) {
+ free(reply);
+ return;
+ }
+
+ xinerama_active = true;
+ free(reply);
+}
+
+/*
+ * randr_query_outputs_15 uses RandR ≥ 1.5 to update outputs.
+ *
+ */
+static bool _randr_query_monitors_15(xcb_window_t root) {
+#if XCB_RANDR_MINOR_VERSION < 5
+ return false;
+#else
+ /* RandR 1.5 available at compile-time, i.e. libxcb is new enough */
+ if (!has_randr_1_5) {
+ return false;
+ }
+ /* RandR 1.5 available at run-time (supported by the server) */
+ DEBUG("Querying monitors using RandR 1.5\n");
+ xcb_generic_error_t *err;
+ xcb_randr_get_monitors_reply_t *monitors =
+ xcb_randr_get_monitors_reply(
+ conn, xcb_randr_get_monitors(conn, root, true), &err);
+ if (err != NULL) {
+ DEBUG("Could not get RandR monitors: X11 error code %d\n", err->error_code);
+ free(err);
+ /* Fall back to RandR ≤ 1.4 */
+ return false;
+ }
+
+ int screens = xcb_randr_get_monitors_monitors_length(monitors);
+ DEBUG("%d RandR monitors found (timestamp %d)\n",
+ screens, monitors->timestamp);
+
+ Rect *resolutions = malloc(screens * sizeof(Rect));
+ /* No memory? Just keep on using the old information. */
+ if (!resolutions) {
+ free(monitors);
+ return true;
+ }
+
+ xcb_randr_monitor_info_iterator_t iter;
+ int screen;
+ for (iter = xcb_randr_get_monitors_monitors_iterator(monitors), screen = 0;
+ iter.rem;
+ xcb_randr_monitor_info_next(&iter), screen++) {
+ const xcb_randr_monitor_info_t *monitor_info = iter.data;
+
+ resolutions[screen].x = monitor_info->x;
+ resolutions[screen].y = monitor_info->y;
+ resolutions[screen].width = monitor_info->width;
+ resolutions[screen].height = monitor_info->height;
+ DEBUG("found RandR monitor: %d x %d at %d x %d\n",
+ monitor_info->width, monitor_info->height,
+ monitor_info->x, monitor_info->y);
+ }
+ free(xr_resolutions);
+ xr_resolutions = resolutions;
+ xr_screens = screens;
+
+ free(monitors);
+ return true;
+#endif
+}
+
+/*
+ * randr_query_outputs_14 uses RandR ≤ 1.4 to update outputs.
+ *
+ */
+static bool _randr_query_outputs_14(xcb_window_t root) {
+ if (!has_randr) {
+ return false;
+ }
+ DEBUG("Querying outputs using RandR ≤ 1.4\n");
+
+ /* Get screen resources (primary output, crtcs, outputs, modes) */
+ xcb_randr_get_screen_resources_current_cookie_t rcookie;
+ rcookie = xcb_randr_get_screen_resources_current(conn, root);
+
+ xcb_randr_get_screen_resources_current_reply_t *res =
+ xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL);
+ if (res == NULL) {
+ DEBUG("Could not query screen resources.\n");
+ return false;
+ }
+
+ /* timestamp of the configuration so that we get consistent replies to all
+ * requests (if the configuration changes between our different calls) */
+ const xcb_timestamp_t cts = res->config_timestamp;
+
+ const int len = xcb_randr_get_screen_resources_current_outputs_length(res);
+
+ /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */
+ xcb_randr_output_t *randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
+
+ /* Request information for each output */
+ xcb_randr_get_output_info_cookie_t ocookie[len];
+ for (int i = 0; i < len; i++) {
+ ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts);
+ }
+ Rect *resolutions = malloc(len * sizeof(Rect));
+ /* No memory? Just keep on using the old information. */
+ if (!resolutions) {
+ free(res);
+ return true;
+ }
+
+ /* Loop through all outputs available for this X11 screen */
+ int screen = 0;
+
+ for (int i = 0; i < len; i++) {
+ xcb_randr_get_output_info_reply_t *output;
+
+ if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) {
+ continue;
+ }
+
+ if (output->crtc == XCB_NONE) {
+ free(output);
+ continue;
+ }
+
+ xcb_randr_get_crtc_info_cookie_t icookie;
+ xcb_randr_get_crtc_info_reply_t *crtc;
+ icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts);
+ if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
+ DEBUG("Skipping output: could not get CRTC (0x%08x)\n", output->crtc);
+ free(output);
+ continue;
+ }
+
+ resolutions[screen].x = crtc->x;
+ resolutions[screen].y = crtc->y;
+ resolutions[screen].width = crtc->width;
+ resolutions[screen].height = crtc->height;
+
+ DEBUG("found RandR output: %d x %d at %d x %d\n",
+ crtc->width, crtc->height,
+ crtc->x, crtc->y);
+
+ screen++;
+
+ free(crtc);
+
+ free(output);
+ }
+ free(xr_resolutions);
+ xr_resolutions = resolutions;
+ xr_screens = screen;
+ free(res);
+ return true;
+}
+
+void _xinerama_query_screens(void) {
+ if (!xinerama_active) {
+ return;
+ }
+
+ xcb_xinerama_query_screens_cookie_t cookie;
+ xcb_xinerama_query_screens_reply_t *reply;
+ xcb_xinerama_screen_info_t *screen_info;
+ xcb_generic_error_t *err;
+ cookie = xcb_xinerama_query_screens_unchecked(conn);
+ reply = xcb_xinerama_query_screens_reply(conn, cookie, &err);
+ if (!reply) {
+ DEBUG("Couldn't get Xinerama screens: X11 error code %d\n", err->error_code);
+ free(err);
+ return;
+ }
+ screen_info = xcb_xinerama_query_screens_screen_info(reply);
+ int screens = xcb_xinerama_query_screens_screen_info_length(reply);
+
+ Rect *resolutions = malloc(screens * sizeof(Rect));
+ /* No memory? Just keep on using the old information. */
+ if (!resolutions) {
+ free(reply);
+ return;
+ }
+
+ for (int screen = 0; screen < xr_screens; screen++) {
+ resolutions[screen].x = screen_info[screen].x_org;
+ resolutions[screen].y = screen_info[screen].y_org;
+ resolutions[screen].width = screen_info[screen].width;
+ resolutions[screen].height = screen_info[screen].height;
+ DEBUG("found Xinerama screen: %d x %d at %d x %d\n",
+ screen_info[screen].width, screen_info[screen].height,
+ screen_info[screen].x_org, screen_info[screen].y_org);
+ }
+
+ free(xr_resolutions);
+ xr_resolutions = resolutions;
+ xr_screens = screens;
+
+ free(reply);
+}
+
+void randr_query(xcb_window_t root) {
+ if (_randr_query_monitors_15(root)) {
+ return;
+ }
+
+ if (_randr_query_outputs_14(root)) {
+ return;
+ }
+
+ _xinerama_query_screens();
+}
diff --git a/randr.h b/randr.h
new file mode 100644
index 0000000..510a7cb
--- /dev/null
+++ b/randr.h
@@ -0,0 +1,17 @@
+#ifndef _XINERAMA_H
+#define _XINERAMA_H
+
+typedef struct Rect {
+ int16_t x;
+ int16_t y;
+ uint16_t width;
+ uint16_t height;
+} Rect;
+
+extern int xr_screens;
+extern Rect *xr_resolutions;
+
+void randr_init(int *event_base, xcb_window_t root);
+void randr_query(xcb_window_t root);
+
+#endif
diff --git a/travis/Dockerfile b/travis/Dockerfile
new file mode 100644
index 0000000..5381276
--- /dev/null
+++ b/travis/Dockerfile
@@ -0,0 +1,25 @@
+# vim:ft=Dockerfile
+FROM debian:sid
+
+RUN echo force-unsafe-io > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup
+# Paper over occasional network flakiness of some mirrors.
+RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
+
+# NOTE: I tried exclusively using gce_debian_mirror.storage.googleapis.com
+# instead of httpredir.debian.org, but the results (Fetched 123 MB in 36s (3357
+# kB/s)) are not any better than httpredir.debian.org (Fetched 123 MB in 34s
+# (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
+
+# Install mk-build-deps (for installing the i3 build dependencies),
+# clang and clang-format-3.8 (for checking formatting and building with clang),
+# lintian (for checking spelling errors),
+# test suite dependencies (for running tests)
+RUN apt-get update && \
+ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
+ build-essential clang git autoconf automake libxcb-randr0-dev pkg-config libpam0g-dev \
+ libcairo2-dev libxcb1-dev libxcb-dpms0-dev libxcb-image0-dev libxcb-util0-dev \
+ libxcb-xrm-dev libev-dev libxcb-xinerama0-dev libxcb-xkb-dev libxkbcommon-dev \
+ libxkbcommon-x11-dev && \
+ rm -rf /var/lib/apt/lists/*
+
+WORKDIR /usr/src
diff --git a/unlock_indicator.c b/unlock_indicator.c
new file mode 100644
index 0000000..d6237ed
--- /dev/null
+++ b/unlock_indicator.c
@@ -0,0 +1,361 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * © 2010 Michael Stapelberg
+ *
+ * See LICENSE for licensing information
+ *
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "i3lock.h"
+#include "xcb.h"
+#include "unlock_indicator.h"
+#include "randr.h"
+#include "dpi.h"
+
+#define BUTTON_RADIUS 90
+#define BUTTON_SPACE (BUTTON_RADIUS + 5)
+#define BUTTON_CENTER (BUTTON_RADIUS + 5)
+#define BUTTON_DIAMETER (2 * BUTTON_SPACE)
+
+/*******************************************************************************
+ * Variables defined in i3lock.c.
+ ******************************************************************************/
+
+extern bool debug_mode;
+
+/* The current position in the input buffer. Useful to determine if any
+ * characters of the password have already been entered or not. */
+extern int input_position;
+
+/* The lock window. */
+extern xcb_window_t win;
+
+/* The current resolution of the X11 root window. */
+extern uint32_t last_resolution[2];
+
+/* Whether the unlock indicator is enabled (defaults to true). */
+extern bool unlock_indicator;
+
+/* List of pressed modifiers, or NULL if none are pressed. */
+extern char *modifier_string;
+
+/* A Cairo surface containing the specified image (-i), if any. */
+extern cairo_surface_t *img;
+
+/* Whether the image should be tiled. */
+extern bool tile;
+/* The background color to use (in hex). */
+extern char color[7];
+
+/* Whether the failed attempts should be displayed. */
+extern bool show_failed_attempts;
+/* Number of failed unlock attempts. */
+extern int failed_attempts;
+
+/*******************************************************************************
+ * Variables defined in xcb.c.
+ ******************************************************************************/
+
+/* The root screen, to determine the DPI. */
+extern xcb_screen_t *screen;
+
+/*******************************************************************************
+ * Local variables.
+ ******************************************************************************/
+
+/* Cache the screen’s visual, necessary for creating a Cairo context. */
+static xcb_visualtype_t *vistype;
+
+/* Maintain the current unlock/PAM state to draw the appropriate unlock
+ * indicator. */
+unlock_state_t unlock_state;
+auth_state_t auth_state;
+
+/*
+ * Draws global image with fill color onto a pixmap with the given
+ * resolution and returns it.
+ *
+ */
+xcb_pixmap_t draw_image(uint32_t *resolution) {
+ xcb_pixmap_t bg_pixmap = XCB_NONE;
+ const double scaling_factor = get_dpi_value() / 96.0;
+ int button_diameter_physical = ceil(scaling_factor * BUTTON_DIAMETER);
+ DEBUG("scaling_factor is %.f, physical diameter is %d px\n",
+ scaling_factor, button_diameter_physical);
+
+ if (!vistype)
+ vistype = get_root_visual_type(screen);
+ bg_pixmap = create_bg_pixmap(conn, screen, resolution, color);
+ /* Initialize cairo: Create one in-memory surface to render the unlock
+ * indicator on, create one XCB surface to actually draw (one or more,
+ * depending on the amount of screens) unlock indicators on. */
+ cairo_surface_t *output = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, button_diameter_physical, button_diameter_physical);
+ cairo_t *ctx = cairo_create(output);
+
+ cairo_surface_t *xcb_output = cairo_xcb_surface_create(conn, bg_pixmap, vistype, resolution[0], resolution[1]);
+ cairo_t *xcb_ctx = cairo_create(xcb_output);
+
+ if (img) {
+ if (!tile) {
+ cairo_set_source_surface(xcb_ctx, img, 0, 0);
+ cairo_paint(xcb_ctx);
+ } else {
+ /* create a pattern and fill a rectangle as big as the screen */
+ cairo_pattern_t *pattern;
+ pattern = cairo_pattern_create_for_surface(img);
+ cairo_set_source(xcb_ctx, pattern);
+ cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
+ cairo_rectangle(xcb_ctx, 0, 0, resolution[0], resolution[1]);
+ cairo_fill(xcb_ctx);
+ cairo_pattern_destroy(pattern);
+ }
+ } else {
+ char strgroups[3][3] = {{color[0], color[1], '\0'},
+ {color[2], color[3], '\0'},
+ {color[4], color[5], '\0'}};
+ uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
+ (strtol(strgroups[1], NULL, 16)),
+ (strtol(strgroups[2], NULL, 16))};
+ cairo_set_source_rgb(xcb_ctx, rgb16[0] / 255.0, rgb16[1] / 255.0, rgb16[2] / 255.0);
+ cairo_rectangle(xcb_ctx, 0, 0, resolution[0], resolution[1]);
+ cairo_fill(xcb_ctx);
+ }
+
+ if (unlock_indicator &&
+ (unlock_state >= STATE_KEY_PRESSED || auth_state > STATE_AUTH_IDLE)) {
+ cairo_scale(ctx, scaling_factor, scaling_factor);
+ /* Draw a (centered) circle with transparent background. */
+ cairo_set_line_width(ctx, 10.0);
+ cairo_arc(ctx,
+ BUTTON_CENTER /* x */,
+ BUTTON_CENTER /* y */,
+ BUTTON_RADIUS /* radius */,
+ 0 /* start */,
+ 2 * M_PI /* end */);
+
+ /* Use the appropriate color for the different PAM states
+ * (currently verifying, wrong password, or default) */
+ switch (auth_state) {
+ case STATE_AUTH_VERIFY:
+ case STATE_AUTH_LOCK:
+ cairo_set_source_rgba(ctx, 0, 114.0 / 255, 255.0 / 255, 0.75);
+ break;
+ case STATE_AUTH_WRONG:
+ case STATE_I3LOCK_LOCK_FAILED:
+ cairo_set_source_rgba(ctx, 250.0 / 255, 0, 0, 0.75);
+ break;
+ default:
+ if (unlock_state == STATE_NOTHING_TO_DELETE) {
+ cairo_set_source_rgba(ctx, 250.0 / 255, 0, 0, 0.75);
+ break;
+ }
+ cairo_set_source_rgba(ctx, 0, 0, 0, 0.75);
+ break;
+ }
+ cairo_fill_preserve(ctx);
+
+ switch (auth_state) {
+ case STATE_AUTH_VERIFY:
+ case STATE_AUTH_LOCK:
+ cairo_set_source_rgb(ctx, 51.0 / 255, 0, 250.0 / 255);
+ break;
+ case STATE_AUTH_WRONG:
+ case STATE_I3LOCK_LOCK_FAILED:
+ cairo_set_source_rgb(ctx, 125.0 / 255, 51.0 / 255, 0);
+ break;
+ case STATE_AUTH_IDLE:
+ if (unlock_state == STATE_NOTHING_TO_DELETE) {
+ cairo_set_source_rgb(ctx, 125.0 / 255, 51.0 / 255, 0);
+ break;
+ }
+
+ cairo_set_source_rgb(ctx, 51.0 / 255, 125.0 / 255, 0);
+ break;
+ }
+ cairo_stroke(ctx);
+
+ /* Draw an inner seperator line. */
+ cairo_set_source_rgb(ctx, 0, 0, 0);
+ cairo_set_line_width(ctx, 2.0);
+ cairo_arc(ctx,
+ BUTTON_CENTER /* x */,
+ BUTTON_CENTER /* y */,
+ BUTTON_RADIUS - 5 /* radius */,
+ 0,
+ 2 * M_PI);
+ cairo_stroke(ctx);
+
+ cairo_set_line_width(ctx, 10.0);
+
+ /* Display a (centered) text of the current PAM state. */
+ char *text = NULL;
+ /* We don't want to show more than a 3-digit number. */
+ char buf[4];
+
+ cairo_set_source_rgb(ctx, 0, 0, 0);
+ cairo_select_font_face(ctx, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+ cairo_set_font_size(ctx, 28.0);
+ switch (auth_state) {
+ case STATE_AUTH_VERIFY:
+ text = "Verifying…";
+ break;
+ case STATE_AUTH_LOCK:
+ text = "Locking…";
+ break;
+ case STATE_AUTH_WRONG:
+ text = "Wrong!";
+ break;
+ case STATE_I3LOCK_LOCK_FAILED:
+ text = "Lock failed!";
+ break;
+ default:
+ if (unlock_state == STATE_NOTHING_TO_DELETE) {
+ text = "No input";
+ }
+ if (show_failed_attempts && failed_attempts > 0) {
+ if (failed_attempts > 999) {
+ text = "> 999";
+ } else {
+ snprintf(buf, sizeof(buf), "%d", failed_attempts);
+ text = buf;
+ }
+ cairo_set_source_rgb(ctx, 1, 0, 0);
+ cairo_set_font_size(ctx, 32.0);
+ }
+ break;
+ }
+
+ if (text) {
+ cairo_text_extents_t extents;
+ double x, y;
+
+ cairo_text_extents(ctx, text, &extents);
+ x = BUTTON_CENTER - ((extents.width / 2) + extents.x_bearing);
+ y = BUTTON_CENTER - ((extents.height / 2) + extents.y_bearing);
+
+ cairo_move_to(ctx, x, y);
+ cairo_show_text(ctx, text);
+ cairo_close_path(ctx);
+ }
+
+ if (auth_state == STATE_AUTH_WRONG && (modifier_string != NULL)) {
+ cairo_text_extents_t extents;
+ double x, y;
+
+ cairo_set_font_size(ctx, 14.0);
+
+ cairo_text_extents(ctx, modifier_string, &extents);
+ x = BUTTON_CENTER - ((extents.width / 2) + extents.x_bearing);
+ y = BUTTON_CENTER - ((extents.height / 2) + extents.y_bearing) + 28.0;
+
+ cairo_move_to(ctx, x, y);
+ cairo_show_text(ctx, modifier_string);
+ cairo_close_path(ctx);
+ }
+
+ /* After the user pressed any valid key or the backspace key, we
+ * highlight a random part of the unlock indicator to confirm this
+ * keypress. */
+ if (unlock_state == STATE_KEY_ACTIVE ||
+ unlock_state == STATE_BACKSPACE_ACTIVE) {
+ cairo_new_sub_path(ctx);
+ double highlight_start = (rand() % (int)(2 * M_PI * 100)) / 100.0;
+ cairo_arc(ctx,
+ BUTTON_CENTER /* x */,
+ BUTTON_CENTER /* y */,
+ BUTTON_RADIUS /* radius */,
+ highlight_start,
+ highlight_start + (M_PI / 3.0));
+ if (unlock_state == STATE_KEY_ACTIVE) {
+ /* For normal keys, we use a lighter green. */
+ cairo_set_source_rgb(ctx, 51.0 / 255, 219.0 / 255, 0);
+ } else {
+ /* For backspace, we use red. */
+ cairo_set_source_rgb(ctx, 219.0 / 255, 51.0 / 255, 0);
+ }
+ cairo_stroke(ctx);
+
+ /* Draw two little separators for the highlighted part of the
+ * unlock indicator. */
+ cairo_set_source_rgb(ctx, 0, 0, 0);
+ cairo_arc(ctx,
+ BUTTON_CENTER /* x */,
+ BUTTON_CENTER /* y */,
+ BUTTON_RADIUS /* radius */,
+ highlight_start /* start */,
+ highlight_start + (M_PI / 128.0) /* end */);
+ cairo_stroke(ctx);
+ cairo_arc(ctx,
+ BUTTON_CENTER /* x */,
+ BUTTON_CENTER /* y */,
+ BUTTON_RADIUS /* radius */,
+ (highlight_start + (M_PI / 3.0)) - (M_PI / 128.0) /* start */,
+ highlight_start + (M_PI / 3.0) /* end */);
+ cairo_stroke(ctx);
+ }
+ }
+
+ if (xr_screens > 0) {
+ /* Composite the unlock indicator in the middle of each screen. */
+ for (int screen = 0; screen < xr_screens; screen++) {
+ int x = (xr_resolutions[screen].x + ((xr_resolutions[screen].width / 2) - (button_diameter_physical / 2)));
+ int y = (xr_resolutions[screen].y + ((xr_resolutions[screen].height / 2) - (button_diameter_physical / 2)));
+ cairo_set_source_surface(xcb_ctx, output, x, y);
+ cairo_rectangle(xcb_ctx, x, y, button_diameter_physical, button_diameter_physical);
+ cairo_fill(xcb_ctx);
+ }
+ } else {
+ /* We have no information about the screen sizes/positions, so we just
+ * place the unlock indicator in the middle of the X root window and
+ * hope for the best. */
+ int x = (last_resolution[0] / 2) - (button_diameter_physical / 2);
+ int y = (last_resolution[1] / 2) - (button_diameter_physical / 2);
+ cairo_set_source_surface(xcb_ctx, output, x, y);
+ cairo_rectangle(xcb_ctx, x, y, button_diameter_physical, button_diameter_physical);
+ cairo_fill(xcb_ctx);
+ }
+
+ cairo_surface_destroy(xcb_output);
+ cairo_surface_destroy(output);
+ cairo_destroy(ctx);
+ cairo_destroy(xcb_ctx);
+ return bg_pixmap;
+}
+
+/*
+ * Calls draw_image on a new pixmap and swaps that with the current pixmap
+ *
+ */
+void redraw_screen(void) {
+ DEBUG("redraw_screen(unlock_state = %d, auth_state = %d)\n", unlock_state, auth_state);
+ xcb_pixmap_t bg_pixmap = draw_image(last_resolution);
+ xcb_change_window_attributes(conn, win, XCB_CW_BACK_PIXMAP, (uint32_t[1]){bg_pixmap});
+ /* XXX: Possible optimization: Only update the area in the middle of the
+ * screen instead of the whole screen. */
+ xcb_clear_area(conn, 0, win, 0, 0, last_resolution[0], last_resolution[1]);
+ xcb_free_pixmap(conn, bg_pixmap);
+ xcb_flush(conn);
+}
+
+/*
+ * Hides the unlock indicator completely when there is no content in the
+ * password buffer.
+ *
+ */
+void clear_indicator(void) {
+ if (input_position == 0) {
+ unlock_state = STATE_STARTED;
+ } else
+ unlock_state = STATE_KEY_PRESSED;
+ redraw_screen();
+}
diff --git a/unlock_indicator.h b/unlock_indicator.h
new file mode 100644
index 0000000..93c1de8
--- /dev/null
+++ b/unlock_indicator.h
@@ -0,0 +1,26 @@
+#ifndef _UNLOCK_INDICATOR_H
+#define _UNLOCK_INDICATOR_H
+
+typedef enum {
+ STATE_STARTED = 0, /* default state */
+ STATE_KEY_PRESSED = 1, /* key was pressed, show unlock indicator */
+ STATE_KEY_ACTIVE = 2, /* a key was pressed recently, highlight part
+ of the unlock indicator. */
+ STATE_BACKSPACE_ACTIVE = 3, /* backspace was pressed recently, highlight
+ part of the unlock indicator in red. */
+ STATE_NOTHING_TO_DELETE = 4, /* backspace was pressed, but there is nothing to delete. */
+} unlock_state_t;
+
+typedef enum {
+ STATE_AUTH_IDLE = 0, /* no authenticator interaction at the moment */
+ STATE_AUTH_VERIFY = 1, /* currently verifying the password via authenticator */
+ STATE_AUTH_LOCK = 2, /* currently locking the screen */
+ STATE_AUTH_WRONG = 3, /* the password was wrong */
+ STATE_I3LOCK_LOCK_FAILED = 4, /* i3lock failed to load */
+} auth_state_t;
+
+xcb_pixmap_t draw_image(uint32_t* resolution);
+void redraw_screen(void);
+void clear_indicator(void);
+
+#endif
diff --git a/xcb.c b/xcb.c
new file mode 100644
index 0000000..2867a47
--- /dev/null
+++ b/xcb.c
@@ -0,0 +1,434 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * © 2010 Michael Stapelberg
+ *
+ * xcb.c: contains all functions which use XCB to talk to X11. Mostly wrappers
+ * around the rather complicated/ugly parts of the XCB API.
+ *
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "cursors.h"
+#include "unlock_indicator.h"
+
+extern auth_state_t auth_state;
+
+xcb_connection_t *conn;
+xcb_screen_t *screen;
+
+static xcb_atom_t _NET_WM_BYPASS_COMPOSITOR = XCB_NONE;
+void _init_net_wm_bypass_compositor(xcb_connection_t *conn) {
+ if (_NET_WM_BYPASS_COMPOSITOR != XCB_NONE) {
+ /* already initialized */
+ return;
+ }
+ xcb_generic_error_t *err;
+ xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply(
+ conn,
+ xcb_intern_atom(conn, 0, strlen("_NET_WM_BYPASS_COMPOSITOR"), "_NET_WM_BYPASS_COMPOSITOR"),
+ &err);
+ if (atom_reply == NULL) {
+ fprintf(stderr, "X11 Error %d\n", err->error_code);
+ free(err);
+ return;
+ }
+ _NET_WM_BYPASS_COMPOSITOR = atom_reply->atom;
+ free(atom_reply);
+}
+
+#define curs_invisible_width 8
+#define curs_invisible_height 8
+
+static unsigned char curs_invisible_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+#define curs_windows_width 11
+#define curs_windows_height 19
+
+static unsigned char curs_windows_bits[] = {
+ 0xfe, 0x07, 0xfc, 0x07, 0xfa, 0x07, 0xf6, 0x07, 0xee, 0x07, 0xde, 0x07,
+ 0xbe, 0x07, 0x7e, 0x07, 0xfe, 0x06, 0xfe, 0x05, 0x3e, 0x00, 0xb6, 0x07,
+ 0x6a, 0x07, 0x6c, 0x07, 0xde, 0x06, 0xdf, 0x06, 0xbf, 0x05, 0xbf, 0x05,
+ 0x7f, 0x06};
+
+#define mask_windows_width 11
+#define mask_windows_height 19
+
+static unsigned char mask_windows_bits[] = {
+ 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0f, 0x00, 0x1f, 0x00, 0x3f, 0x00,
+ 0x7f, 0x00, 0xff, 0x00, 0xff, 0x01, 0xff, 0x03, 0xff, 0x07, 0x7f, 0x00,
+ 0xf7, 0x00, 0xf3, 0x00, 0xe1, 0x01, 0xe0, 0x01, 0xc0, 0x03, 0xc0, 0x03,
+ 0x80, 0x01};
+
+static uint32_t get_colorpixel(char *hex) {
+ char strgroups[3][3] = {{hex[0], hex[1], '\0'},
+ {hex[2], hex[3], '\0'},
+ {hex[4], hex[5], '\0'}};
+ uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
+ (strtol(strgroups[1], NULL, 16)),
+ (strtol(strgroups[2], NULL, 16))};
+
+ return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
+}
+
+xcb_visualtype_t *get_root_visual_type(xcb_screen_t *screen) {
+ xcb_visualtype_t *visual_type = NULL;
+ xcb_depth_iterator_t depth_iter;
+ xcb_visualtype_iterator_t visual_iter;
+
+ for (depth_iter = xcb_screen_allowed_depths_iterator(screen);
+ depth_iter.rem;
+ xcb_depth_next(&depth_iter)) {
+ for (visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
+ visual_iter.rem;
+ xcb_visualtype_next(&visual_iter)) {
+ if (screen->root_visual != visual_iter.data->visual_id)
+ continue;
+
+ visual_type = visual_iter.data;
+ return visual_type;
+ }
+ }
+
+ return NULL;
+}
+
+xcb_pixmap_t create_bg_pixmap(xcb_connection_t *conn, xcb_screen_t *scr, u_int32_t *resolution, char *color) {
+ xcb_pixmap_t bg_pixmap = xcb_generate_id(conn);
+ xcb_create_pixmap(conn, scr->root_depth, bg_pixmap, scr->root,
+ resolution[0], resolution[1]);
+
+ /* Generate a Graphics Context and fill the pixmap with background color
+ * (for images that are smaller than your screen) */
+ xcb_gcontext_t gc = xcb_generate_id(conn);
+ uint32_t values[] = {get_colorpixel(color)};
+ xcb_create_gc(conn, gc, bg_pixmap, XCB_GC_FOREGROUND, values);
+ xcb_rectangle_t rect = {0, 0, resolution[0], resolution[1]};
+ xcb_poly_fill_rectangle(conn, bg_pixmap, gc, 1, &rect);
+ xcb_free_gc(conn, gc);
+
+ return bg_pixmap;
+}
+
+xcb_window_t open_fullscreen_window(xcb_connection_t *conn, xcb_screen_t *scr, char *color, xcb_pixmap_t pixmap) {
+ uint32_t mask = 0;
+ uint32_t values[3];
+ xcb_window_t win = xcb_generate_id(conn);
+
+ if (pixmap == XCB_NONE) {
+ mask |= XCB_CW_BACK_PIXEL;
+ values[0] = get_colorpixel(color);
+ } else {
+ mask |= XCB_CW_BACK_PIXMAP;
+ values[0] = pixmap;
+ }
+
+ mask |= XCB_CW_OVERRIDE_REDIRECT;
+ values[1] = 1;
+
+ mask |= XCB_CW_EVENT_MASK;
+ values[2] = XCB_EVENT_MASK_EXPOSURE |
+ XCB_EVENT_MASK_KEY_PRESS |
+ XCB_EVENT_MASK_KEY_RELEASE |
+ XCB_EVENT_MASK_VISIBILITY_CHANGE |
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY;
+
+ xcb_create_window(conn,
+ XCB_COPY_FROM_PARENT,
+ win, /* the window id */
+ scr->root, /* parent == root */
+ 0, 0,
+ scr->width_in_pixels,
+ scr->height_in_pixels, /* dimensions */
+ 0, /* border = 0, we draw our own */
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
+ mask,
+ values);
+
+ char *name = "i3lock";
+ xcb_change_property(conn,
+ XCB_PROP_MODE_REPLACE,
+ win,
+ XCB_ATOM_WM_NAME,
+ XCB_ATOM_STRING,
+ 8,
+ strlen(name),
+ name);
+
+ xcb_change_property(conn,
+ XCB_PROP_MODE_REPLACE,
+ win,
+ XCB_ATOM_WM_CLASS,
+ XCB_ATOM_STRING,
+ 8,
+ 2 * (strlen("i3lock") + 1),
+ "i3lock\0i3lock\0");
+
+ const uint32_t bypass_compositor = 1; /* disable compositing */
+ _init_net_wm_bypass_compositor(conn);
+ xcb_change_property(conn,
+ XCB_PROP_MODE_REPLACE,
+ win,
+ _NET_WM_BYPASS_COMPOSITOR,
+ XCB_ATOM_CARDINAL,
+ 32,
+ 1,
+ &bypass_compositor);
+
+ /* Map the window (= make it visible) */
+ xcb_map_window(conn, win);
+
+ /* Raise window (put it on top) */
+ values[0] = XCB_STACK_MODE_ABOVE;
+ xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_STACK_MODE, values);
+
+ /* Ensure that the window is created and set up before returning */
+ xcb_aux_sync(conn);
+
+ return win;
+}
+
+/*
+ * Repeatedly tries to grab pointer and keyboard (up to the specified number of
+ * tries).
+ *
+ * Returns true if the grab succeeded, false if not.
+ *
+ */
+bool grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_t cursor, int tries) {
+ xcb_grab_pointer_cookie_t pcookie;
+ xcb_grab_pointer_reply_t *preply;
+
+ xcb_grab_keyboard_cookie_t kcookie;
+ xcb_grab_keyboard_reply_t *kreply;
+
+ const suseconds_t screen_redraw_timeout = 100000; /* 100ms */
+
+ /* Using few variables to trigger a redraw_screen() if too many tries */
+ bool redrawn = false;
+ struct timeval start;
+ if (gettimeofday(&start, NULL) == -1) {
+ err(EXIT_FAILURE, "gettimeofday");
+ }
+
+ while (tries-- > 0) {
+ pcookie = xcb_grab_pointer(
+ conn,
+ false, /* get all pointer events specified by the following mask */
+ screen->root, /* grab the root window */
+ XCB_NONE, /* which events to let through */
+ XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
+ XCB_GRAB_MODE_ASYNC, /* keyboard mode */
+ XCB_NONE, /* confine_to = in which window should the cursor stay */
+ cursor, /* we change the cursor to whatever the user wanted */
+ XCB_CURRENT_TIME);
+
+ if ((preply = xcb_grab_pointer_reply(conn, pcookie, NULL)) &&
+ preply->status == XCB_GRAB_STATUS_SUCCESS) {
+ free(preply);
+ break;
+ }
+
+ /* In case the grab failed, we still need to free the reply */
+ free(preply);
+
+ /* Make this quite a bit slower */
+ usleep(50);
+
+ struct timeval now;
+ if (gettimeofday(&now, NULL) == -1) {
+ err(EXIT_FAILURE, "gettimeofday");
+ }
+
+ struct timeval elapsed;
+ timersub(&now, &start, &elapsed);
+
+ if (!redrawn &&
+ (tries % 100) == 0 &&
+ elapsed.tv_usec >= screen_redraw_timeout) {
+ redraw_screen();
+ redrawn = true;
+ }
+ }
+
+ while (tries-- > 0) {
+ kcookie = xcb_grab_keyboard(
+ conn,
+ true, /* report events */
+ screen->root, /* grab the root window */
+ XCB_CURRENT_TIME,
+ XCB_GRAB_MODE_ASYNC, /* process events as normal, do not require sync */
+ XCB_GRAB_MODE_ASYNC);
+
+ if ((kreply = xcb_grab_keyboard_reply(conn, kcookie, NULL)) &&
+ kreply->status == XCB_GRAB_STATUS_SUCCESS) {
+ free(kreply);
+ break;
+ }
+
+ /* In case the grab failed, we still need to free the reply */
+ free(kreply);
+
+ /* Make this quite a bit slower */
+ usleep(50);
+
+ struct timeval now;
+ if (gettimeofday(&now, NULL) == -1) {
+ err(EXIT_FAILURE, "gettimeofday");
+ }
+
+ struct timeval elapsed;
+ timersub(&now, &start, &elapsed);
+
+ /* Trigger a screen redraw if 100ms elapsed */
+ if (!redrawn &&
+ (tries % 100) == 0 &&
+ elapsed.tv_usec >= screen_redraw_timeout) {
+ redraw_screen();
+ redrawn = true;
+ }
+ }
+
+ return (tries > 0);
+}
+
+xcb_cursor_t create_cursor(xcb_connection_t *conn, xcb_screen_t *screen, xcb_window_t win, int choice) {
+ xcb_pixmap_t bitmap;
+ xcb_pixmap_t mask;
+ xcb_cursor_t cursor;
+
+ unsigned char *curs_bits;
+ unsigned char *mask_bits;
+ int curs_w, curs_h;
+
+ switch (choice) {
+ case CURS_NONE:
+ curs_bits = curs_invisible_bits;
+ mask_bits = curs_invisible_bits;
+ curs_w = curs_invisible_width;
+ curs_h = curs_invisible_height;
+ break;
+ case CURS_WIN:
+ curs_bits = curs_windows_bits;
+ mask_bits = mask_windows_bits;
+ curs_w = curs_windows_width;
+ curs_h = curs_windows_height;
+ break;
+ case CURS_DEFAULT:
+ default:
+ return XCB_NONE; /* XCB_NONE is xcb's way of saying "don't change the cursor" */
+ }
+
+ bitmap = xcb_create_pixmap_from_bitmap_data(conn,
+ win,
+ curs_bits,
+ curs_w,
+ curs_h,
+ 1,
+ screen->white_pixel,
+ screen->black_pixel,
+ NULL);
+
+ mask = xcb_create_pixmap_from_bitmap_data(conn,
+ win,
+ mask_bits,
+ curs_w,
+ curs_h,
+ 1,
+ screen->white_pixel,
+ screen->black_pixel,
+ NULL);
+
+ cursor = xcb_generate_id(conn);
+
+ xcb_create_cursor(conn,
+ cursor,
+ bitmap,
+ mask,
+ 65535, 65535, 65535,
+ 0, 0, 0,
+ 0, 0);
+
+ xcb_free_pixmap(conn, bitmap);
+ xcb_free_pixmap(conn, mask);
+
+ return cursor;
+}
+
+static xcb_atom_t _NET_ACTIVE_WINDOW = XCB_NONE;
+void _init_net_active_window(xcb_connection_t *conn) {
+ if (_NET_ACTIVE_WINDOW != XCB_NONE) {
+ /* already initialized */
+ return;
+ }
+ xcb_generic_error_t *err;
+ xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply(
+ conn,
+ xcb_intern_atom(conn, 0, strlen("_NET_ACTIVE_WINDOW"), "_NET_ACTIVE_WINDOW"),
+ &err);
+ if (atom_reply == NULL) {
+ fprintf(stderr, "X11 Error %d\n", err->error_code);
+ free(err);
+ return;
+ }
+ _NET_ACTIVE_WINDOW = atom_reply->atom;
+ free(atom_reply);
+}
+
+xcb_window_t find_focused_window(xcb_connection_t *conn, const xcb_window_t root) {
+ xcb_window_t result = XCB_NONE;
+
+ _init_net_active_window(conn);
+
+ xcb_get_property_reply_t *prop_reply = xcb_get_property_reply(
+ conn,
+ xcb_get_property_unchecked(
+ conn, false, root, _NET_ACTIVE_WINDOW, XCB_GET_PROPERTY_TYPE_ANY, 0, 1 /* word */),
+ NULL);
+ if (prop_reply == NULL) {
+ goto out;
+ }
+ if (xcb_get_property_value_length(prop_reply) == 0) {
+ goto out_prop;
+ }
+ if (prop_reply->type != XCB_ATOM_WINDOW) {
+ goto out_prop;
+ }
+
+ result = *((xcb_window_t *)xcb_get_property_value(prop_reply));
+
+out_prop:
+ free(prop_reply);
+out:
+ return result;
+}
+
+void set_focused_window(xcb_connection_t *conn, const xcb_window_t root, const xcb_window_t window) {
+ xcb_client_message_event_t ev;
+ memset(&ev, '\0', sizeof(xcb_client_message_event_t));
+
+ _init_net_active_window(conn);
+
+ ev.response_type = XCB_CLIENT_MESSAGE;
+ ev.window = window;
+ ev.type = _NET_ACTIVE_WINDOW;
+ ev.format = 32;
+ ev.data.data32[0] = 2; /* 2 = pager */
+
+ xcb_send_event(conn, false, root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev);
+ xcb_flush(conn);
+}
diff --git a/xcb.h b/xcb.h
new file mode 100644
index 0000000..3555c58
--- /dev/null
+++ b/xcb.h
@@ -0,0 +1,17 @@
+#ifndef _XCB_H
+#define _XCB_H
+
+#include
+
+extern xcb_connection_t *conn;
+extern xcb_screen_t *screen;
+
+xcb_visualtype_t *get_root_visual_type(xcb_screen_t *s);
+xcb_pixmap_t create_bg_pixmap(xcb_connection_t *conn, xcb_screen_t *scr, u_int32_t *resolution, char *color);
+xcb_window_t open_fullscreen_window(xcb_connection_t *conn, xcb_screen_t *scr, char *color, xcb_pixmap_t pixmap);
+bool grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_t cursor, int tries);
+xcb_cursor_t create_cursor(xcb_connection_t *conn, xcb_screen_t *screen, xcb_window_t win, int choice);
+xcb_window_t find_focused_window(xcb_connection_t *conn, const xcb_window_t root);
+void set_focused_window(xcb_connection_t *conn, const xcb_window_t root, const xcb_window_t window);
+
+#endif
--
cgit v1.3