summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimeon Simeonov2020-03-12 12:30:51 +0100
committerSimeon Simeonov2020-03-12 12:30:51 +0100
commit69a05d516bd0c7e369a0ba1fb78aac57a5ea26f2 (patch)
tree87e37110ea0e328533a41c3b16b64036ff132641
Initial commit for the last i3lock commit at this time f3b061233e28ad0cf920c9421adc2459b6920812
-rw-r--r--.clang-format10
-rw-r--r--.github/CONTRIBUTING.md3
-rw-r--r--.github/ISSUE_TEMPLATE.md29
-rw-r--r--.gitignore31
-rw-r--r--.travis.yml17
-rw-r--r--CHANGELOG187
-rw-r--r--I3LOCK_VERSION1
-rw-r--r--LICENSE27
-rw-r--r--Makefile.am58
-rw-r--r--README.md76
-rw-r--r--configure.ac154
-rw-r--r--cursors.h8
-rw-r--r--dpi.c109
-rw-r--r--dpi.h22
-rw-r--r--i3lock.1173
-rw-r--r--i3lock.c1318
-rw-r--r--i3lock.h14
-rw-r--r--m4/ax_append_flag.m471
-rw-r--r--m4/ax_cflags_warn_all.m4122
-rw-r--r--m4/ax_check_compile_flag.m474
-rw-r--r--m4/ax_check_enable_debug.m4124
-rw-r--r--m4/ax_check_gnu_make.m484
-rw-r--r--m4/ax_check_link_flag.m474
-rw-r--r--m4/ax_code_coverage.m4273
-rw-r--r--m4/ax_configure_args.m470
-rw-r--r--m4/ax_enable_builddir.m4302
-rw-r--r--m4/ax_extend_srcdir.m486
-rw-r--r--m4/ax_require_defined.m437
-rw-r--r--m4/ax_sanitizers.m4130
-rw-r--r--pam/i3lock6
-rw-r--r--randr.c297
-rw-r--r--randr.h17
-rw-r--r--travis/Dockerfile25
-rw-r--r--unlock_indicator.c361
-rw-r--r--unlock_indicator.h26
-rw-r--r--xcb.c434
-rw-r--r--xcb.h17
37 files changed, 4867 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..1d84013
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,10 @@
1BasedOnStyle: google
2AllowShortIfStatementsOnASingleLine: false
3AllowShortLoopsOnASingleLine: false
4AllowShortFunctionsOnASingleLine: None
5AllowShortBlocksOnASingleLine: false
6AlwaysBreakBeforeMultilineStrings: false
7IndentWidth: 4
8PointerBindsToType: false
9ColumnLimit: 0
10SpaceBeforeParens: 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 @@
1Please do not send/request features related to image manipulation. i3lock’s
2support for background images is intentionally kept minimal, you should do all
3pre-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 @@
1<!--
2PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
3-->
4
5## I'm submitting a…
6<!-- Please check one of the following options with "x" -->
7<pre>
8[ ] Bug
9[ ] Feature Request
10[ ] Other (Please describe in detail)
11</pre>
12
13## Current Behavior
14<!-- Describe the current behavior -->
15
16## Expected Behavior
17<!-- Describe the desired behavior you expect after mitigation of the issue -->
18
19## Reproduction Instructions
20<!--
21For bug reports, please provide detailed instructions on how the bug can be reproduced.
22For feature requests, you can remove this section.
23-->
24
25## Environment
26Output of `i3lock --version`:
27<pre>
28i3lock version:
29</pre>
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..eb37b56
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,31 @@
1/i3lock
2*.o
3tags
4*.swp
5
6################################################################################
7# https://raw.githubusercontent.com/github/gitignore/master/Autotools.gitignore
8################################################################################
9
10# http://www.gnu.org/software/automake
11
12Makefile.in
13/ar-lib
14/test-driver
15
16# http://www.gnu.org/software/autoconf
17
18/autom4te.cache
19/autoscan.log
20/autoscan-*.log
21/aclocal.m4
22/compile
23/config.h.in
24/config.guess
25/config.sub
26/configure
27/configure.scan
28/depcomp
29/install-sh
30/missing
31/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 @@
1# Use Ubuntu 14.04 (trusty), as per http://blog.travis-ci.com/2015-10-14-opening-up-ubuntu-trusty-beta/
2sudo: false
3dist: trusty
4services:
5 - docker
6language: c
7compiler:
8 - gcc
9 - clang
10addons:
11 apt:
12 packages:
13 - clang-format-3.5
14script:
15 - clang-format-3.5 -i *.[ch] && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false)
16 - docker build --pull --no-cache --rm -t=i3lock -f travis/Dockerfile .
17 - 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 @@
12019-07-21 i3lock 2.12
2
3 • remove stray \n from error messages
4 • capitalize unlock indicator contents
5 • set WM_CLASS property
6 • reference modifier as “Super”, not “Win”
7 • add --raw option to read image as raw bytes
8
92018-10-18 i3lock 2.11.1
10
11 • Fix dist tarball by including I3LOCK_VERSION
12
132018-10-10 i3lock 2.11
14
15 • Switch to autotools
16 • Display an error when backspace is pressed without any input
17 • Print an error when a non-PNG file is opened
18 (i3lock only supports PNG files) (Thanks eplanet)
19 • Don’t unnecessarily check the xcb_connect return value,
20 it is known never to be NULL (Thanks SegFault42)
21 • Fix memory leak when grabbing fails (Thanks karulont)
22 • Respect Xft.dpi for determining the unlock indicator’s scale factor
23 • Discard pending password verification attempts
24 when a new password is entered (Thanks layus)
25
262017-11-25 i3lock 2.10
27
28 • Only use -lpam when not on OpenBSD (Thanks Kaashif)
29 • locale: treat empty string same as unset (Thanks Ran)
30 • Fix overwrite of getopt optind (Thanks jakob)
31 • Immediately hide the unlock indicator after ESC / C-u (Thanks Orestis)
32 • Measure wall-clock time instead of CPU time for “locking” indicator.
33 • SetInputFocus to the i3lock window to force-close context menus
34 • Use RandR for learning about attached monitors
35
362017-06-21 i3lock 2.9.1
37
38 • Fix version number mechanism (for --version)
39 • Revert the fix for composited notifications, as it causes more issues than
40 it solves:
41 https://github.com/i3/i3lock/issues/130
42 https://github.com/i3/i3lock/issues/128
43
442017-05-26 i3lock 2.9
45
46 • i3lock.1: use signal names without SIG prefix
47 • Removed obsolete inactivity timeout
48 • Added version files for release tarball.
49 • Set font face
50 • Automatically unlock (without having to press <Enter>) one attempt which was
51 entered while authenticating
52 • Stop leaking the image_path dup
53 • Displaying locking message when grabbing the pointer/keyboard
54 • Display error message when locking failed
55 • Add Enter on C-m
56 • Change input slices to be exactly pi/3 in size instead of slightly more
57 • Fix covering of composited notifications using the XComposite extension
58 • Remove last traces of DPMS
59 • Use bsd_auth(3) instead of PAM on OpenBSD
60 • Restore intended behaviour and don't use mlock(2) on OpenBSD.
61
622016-06-04 i3lock 2.8
63
64 • Remove DPMS support in favor of a wrapper script and xset(1).
65 • Indicate that the --inactivity-timeout option takes an argument. (Thanks
66 Kenneth Lyons)
67 • fix pam_securetty: set PAM_TTY to getenv("DISPLAY")
68 • Eat XKB_KEY_Delete and XKB_KEY_KP_Delete (Thanks bebehei)
69 • Show unlock indicator if password was entered during PAM verification
70 • Allow CTRL+J as enter and CTRL+H as backspace (Thanks Karl Tarbe)
71 • Flush xcb connection after opening fullscreen window (Thanks martin)
72 • Add support for `xss-lock --transfer-sleep-lock'
73
742015-05-20 i3lock 2.7
75
76 • Die when the X11 connection breaks during runtime (Thanks Eduan)
77 • Implement logging the number of failed attempts (Thanks koebi)
78 • Ignore password validation is pam is in wrong state (Thanks Neodyblue)
79 • Get current user with getpwuid() instead of using $ENV{USER} (Thanks Martin)
80 • Add support for Compose and dead-keys with libxkbcommon.
81 Requires libxkbcommon ≥ 0.5.0 (Thanks Daniel)
82 • Format the source using clang-format.
83 • Refresh pam credentials on successful authentication (for Kerberos and the
84 like) (Thanks James)
85 • List pressed modifiers on failed authentication (Thanks Deiz, Alexandre)
86 • Only redraw the screen if the unlock indicator is actually used
87 (Thanks Ingo)
88 • Make pkg-config configurable for cross-compilation (Thanks Nikolay)
89
902014-07-18 i3lock 2.6
91
92 • NEW DEPENDENCY: use libxkbcommon-x11 instead of libX11
93 This helps us get rid of all code that directly uses libX11
94 • Use cairo_status_to_string for descriptive errors.
95 • Add `-e` option to not validate empty password.
96 • Bugfix: update the initial keyboard modifier state (Thanks lee, Ran)
97 • Re-raise i3lock when obscured in a separate process
98 • Turn on the screen on successful authentication
99 • Delay to turn off the screen after wrong passwd
100 • Discard half-entered passwd after some inactivity
101 • Ignore empty passwd after repeated Enter keypress
102 • Scale the unlock indicator (for retina displays)
103
1042013-06-09 i3lock 2.5
105
106 • NEW DEPENDENCY: Use libxkbcommon for input handling
107 This makes input handling much better for many edge cases.
108 • Bugfix: fix argument parsing on ARM (s/char/int/)
109 • Bugfix: free(reply) to avoid memory leak
110 • Bugfix: Use ev_loop_fork after fork, fixes forking on kqueue based OSes
111 • Bugfix: Fix centering the indicator in the no-xinerama case
112 • Only use mlock() on Linux, FreeBSD (for example) requires root
113 • promote the "could not load image" message from debug to normal
114 • s/pam_message/pam_response/ (Thanks Tucos)
115 • remove support for NOLIBCAIRO, cairo-xcb is widespread by now
116 • Allow XKB_KEY_XF86ScreenSaver as synonym for enter
117 This keysym is generated on convertible tablets by pressing a hardware
118 lock/unlock button.
119 • Allow passwordless PAM conversations (e.g. fingerprint)
120 • Add ctrl+u password reset
121 • Set window name to i3lock
122
1232012-06-02 i3lock 2.4.1
124
125 • Bugfix: Correctly center unlock indicator after reconfiguring screens
126 (Thanks xeen)
127 • Bugfix: Revert shift lock handling (broke uppercase letters)
128 • Bugfix: Skip shift when getting the modifier mask (Thanks SardemFF7)
129
1302012-04-01 i3lock 2.4
131
132 • Bugfix: Fix background color when using cairo (Thanks Pascal)
133 • Only output text when in debug mode (fixes problems with xautolock)
134 • fallback when the image cannot be loaded
135 • Use (void) instead of () for functions without args (Thanks fernandotcl)
136
1372012-03-15 i3lock 2.3.1
138
139 • Fix compilation on some systems
140
1412012-03-15 i3lock 2.3
142
143 • Implement a visual unlock indicator
144 • Support ISO_Level5_Shift and Caps Lock
145 • Lock the password buffer in memory, clear it in RAM after verifying
146 • Fork after the window is visible, not before
147 • Bugfix: Copy the color depth from parent (root) window instead of
148 hardcoding a depth of 24
149
1502011-11-06 i3lock 2.2
151
152 • Don’t re-grab pointer/keyboard on MappingNotify. In some rare situations,
153 this lead to some keypresses "slipping through" to the last focused window.
154 • Correctly handle Mode_switch/ISO_Level3_Shift
155 • Render to a pixmap which is used as background for the window instead of
156 copying contents on every expose event
157 • Handle screen resolution changes while screen is locked
158 • Manpage: document arguments for every option
159
1602011-05-13 i3lock 2.1
161
162 • Accept return/backspace when the buffer of 512 bytes is full
163 • Handle numpad keys correctly
164 • Handle MappingNotify events
165 • Correctly check for errors when connecting to X11
166 • Add i3lock.pam to not rely on debian’s /etc/pam.d/other anymore
167 • don’t display debug output
168 • add NOLIBCAIRO flag to permit compilation without cairo
169
1702010-09-05 i3lock 2.0
171
172 • Complete rewrite of i3lock. Now using xcb instead of Xlib.
173 • When a window obscures i3lock, it pushes itself back to the top again.
174 • Display version when starting with -v
175
1762009-08-02 i3lock 1.1
177
178 • Implement background pictures (-i) and colors (-c)
179
1802009-05-10 i3lock 1.0
181
182 • Implement PAM support
183 • Implement options for forking, beeping, DPMS
184
1852009-05-01 i3lock 0.9
186
187 • 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 @@
1Copyright © 2010-2011, Michael Stapelberg
2All rights reserved.
3
4Redistribution and use in source and binary forms, with or without
5modification, are permitted provided that the following conditions are met:
6
7 * Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9
10 * Redistributions in binary form must reproduce the above copyright
11 notice, this list of conditions and the following disclaimer in the
12 documentation and/or other materials provided with the distribution.
13
14 * Neither the name of Michael Stapelberg nor the
15 names of contributors may be used to endorse or promote products
16 derived from this software without specific prior written permission.
17
18THIS SOFTWARE IS PROVIDED BY Michael Stapelberg ''AS IS'' AND ANY
19EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21DISCLAIMED. IN NO EVENT SHALL Michael Stapelberg BE LIABLE FOR ANY
22DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27SOFTWARE, 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 @@
1@CODE_COVERAGE_RULES@
2
3echo-version:
4 @echo "@I3LOCK_VERSION@"
5
6bin_PROGRAMS = i3lock
7
8dist_man1_MANS = i3lock.1
9
10pamddir = $(sysconfdir)/pam.d
11pamd_files = pam/i3lock
12pamd_DATA = $(pamd_files)
13
14AM_CPPFLAGS = \
15 @AX_EXTEND_SRCDIR_CPPFLAGS@
16
17i3lock_CFLAGS = \
18 $(AM_CFLAGS) \
19 $(XCB_CFLAGS) \
20 $(XCB_IMAGE_CFLAGS) \
21 $(XCB_UTIL_CFLAGS) \
22 $(XCB_UTIL_XRM_CFLAGS) \
23 $(XKBCOMMON_CFLAGS) \
24 $(CAIRO_CFLAGS) \
25 $(CODE_COVERAGE_CFLAGS)
26
27i3lock_CPPFLAGS = \
28 $(AM_CPPFLAGS) \
29 $(CODE_COVERAGE_CPPFLAGS)
30
31i3lock_LDADD = \
32 $(XCB_LIBS) \
33 $(XCB_IMAGE_LIBS) \
34 $(XCB_UTIL_LIBS) \
35 $(XCB_UTIL_XRM_LIBS) \
36 $(XKBCOMMON_LIBS) \
37 $(CAIRO_LIBS) \
38 $(CODE_COVERAGE_LDFLAGS)
39
40i3lock_SOURCES = \
41 cursors.h \
42 dpi.c \
43 dpi.h \
44 i3lock.c \
45 i3lock.h \
46 randr.c \
47 randr.h \
48 unlock_indicator.c \
49 unlock_indicator.h \
50 xcb.c \
51 xcb.h
52
53EXTRA_DIST = \
54 $(pamd_files) \
55 CHANGELOG \
56 LICENSE \
57 README.md \
58 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 @@
1i3lock - improved screen locker
2===============================
3[i3lock](https://i3wm.org/i3lock/)> is a simple screen locker like slock.
4After starting it, you will see a white screen (you can configure the
5color/an image). You can return to your screen by entering your password.
6
7Many little improvements have been made to i3lock over time:
8
9- i3lock forks, so you can combine it with an alias to suspend to RAM
10 (run "i3lock && echo mem > /sys/power/state" to get a locked screen
11 after waking up your computer from suspend to RAM)
12
13- You can specify either a background color or a PNG image which will be
14 displayed while your screen is locked. Note that i3lock is not an image
15manipulation software. If you need to resize the image to fill the screen
16or similar, use existing tooling to do this before passing it to i3lock.
17
18- You can specify whether i3lock should bell upon a wrong password.
19
20- i3lock uses PAM and therefore is compatible with LDAP etc.
21 On OpenBSD i3lock uses the bsd_auth(3) framework.
22
23Install
24-------
25
26See [the i3lock home page](https://i3wm.org/i3lock/).
27
28Requirements
29------------
30- pkg-config
31- libxcb
32- libxcb-util
33- libpam-dev
34- libcairo-dev
35- libxcb-xinerama
36- libxcb-randr
37- libev
38- libx11-dev
39- libx11-xcb-dev
40- libxkbcommon >= 0.5.0
41- libxkbcommon-x11 >= 0.5.0
42- libxcb-image
43- libxcb-xrm
44
45Running i3lock
46-------------
47Simply invoke the 'i3lock' command. To get out of it, enter your password and
48press enter.
49
50On OpenBSD the `i3lock` binary needs to be setgid `auth` to call the
51authentication helpers, e.g. `/usr/libexec/auth/login_passwd`.
52
53Building i3lock
54---------------
55We recommend you use the provided package from your distribution. Do not build
56i3lock unless you have a reason to do so.
57
58First install the dependencies listed in requirements section, then run these
59commands (might need to be adapted to your OS):
60```
61autoreconf --force --install
62
63rm -rf build/
64mkdir -p build && cd build/
65
66../configure \
67 --prefix=/usr \
68 --sysconfdir=/etc \
69 --disable-sanitizers
70
71make
72```
73
74Upstream
75--------
76Please 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 @@
1# -*- Autoconf -*-
2# Run autoreconf -fi to generate a configure script from this file.
3
4AC_PREREQ([2.69])
5AC_INIT([i3lock], [2.12], [https://github.com/i3/i3lock/issues])
6# For AX_EXTEND_SRCDIR
7AX_ENABLE_BUILDDIR
8AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2])
9# Default to silent rules, use V=1 to get verbose compilation output.
10AM_SILENT_RULES([yes])
11# Make it possible to disable maintainer mode to disable re-generation of build
12# system files.
13AM_MAINTAINER_MODE([enable])
14AC_CONFIG_SRCDIR([i3lock.c])
15AC_CONFIG_HEADERS([config.h])
16AC_CONFIG_MACRO_DIR([m4])
17
18dnl Verify macros defined in m4/ such as AX_SANITIZERS are not present in the
19dnl output, i.e. are replaced as expected. This line results in a better error
20dnl message when using aclocal < 1.13 (which does not understand
21dnl AC_CONFIG_MACRO_DIR) without passing the -I m4 parameter.
22m4_pattern_forbid([AX_SANITIZERS])
23
24# Verify we are using GNU make because we use '%'-style pattern rules in
25# Makefile.am, which are a GNU make extension. Pull requests to replace
26# '%'-style pattern rules with a more portable alternative are welcome.
27AX_CHECK_GNU_MAKE
28AS_VAR_IF([_cv_gnu_make_command], [""], [AC_MSG_ERROR([the i3lock Makefile.am requires GNU make])])
29
30AX_EXTEND_SRCDIR
31
32AS_IF([test -d ${srcdir}/.git],
33 [
34 VERSION="$(git -C ${srcdir} describe --tags --abbrev=0)"
35 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/::)\\\")"
36 # Mirrors what libi3/is_debug_build.c does:
37 is_release=$(test $(echo "${I3LOCK_VERSION}" | cut -d '(' -f 1 | wc -m) -lt 10 && echo yes || echo no)
38 ],
39 [
40 VERSION="$(cut -d '-' -f 1 ${srcdir}/I3LOCK_VERSION | cut -d ' ' -f 1)"
41 I3LOCK_VERSION="$(sed -e 's/@<:@\"?\\@:>@/\\&/g' ${srcdir}/I3LOCK_VERSION)"
42 is_release="$(grep -q non-git ${srcdir}/I3LOCK_VERSION && echo no || echo yes)"
43 ])
44AC_SUBST([I3LOCK_VERSION], [$I3LOCK_VERSION])
45AC_DEFINE_UNQUOTED([I3LOCK_VERSION], ["${I3LOCK_VERSION}"], [i3lock version])
46
47AX_CODE_COVERAGE
48
49dnl is_release must be lowercase because AX_CHECK_ENABLE_DEBUG calls m4_tolower
50dnl on its fourth argument.
51AX_CHECK_ENABLE_DEBUG([yes], , [UNUSED_NDEBUG], [$is_release])
52
53AC_PROG_CC_C99
54
55# For strnlen() and vasprintf().
56AC_USE_SYSTEM_EXTENSIONS
57
58# Checks for typedefs, structures, and compiler characteristics.
59AC_CHECK_HEADER_STDBOOL
60dnl The error message should include the specific type which could not be
61dnl found, but I do not see a way to achieve that.
62AC_CHECK_TYPES([mode_t, off_t, pid_t, size_t, ssize_t], , [AC_MSG_FAILURE([cannot find required type])])
63
64# Checks for library functions.
65AC_FUNC_FORK
66AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK
67AC_FUNC_STRNLEN
68AC_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])])
69
70# Checks for libraries.
71
72AC_SEARCH_LIBS([floor], [m], , [AC_MSG_FAILURE([cannot find the required floor() function despite trying to link with -lm])])
73
74# libev does not ship with a pkg-config file :(.
75AC_SEARCH_LIBS([ev_run], [ev], , [AC_MSG_FAILURE([cannot find the required ev_run() function despite trying to link with -lev])])
76
77AC_SEARCH_LIBS([shm_open], [rt])
78
79# Only disable PAM on OpenBSD where i3lock uses BSD Auth instead
80case "$host" in
81 *-openbsd*)
82 # Nothing yet.
83 ;;
84 *)
85 AC_SEARCH_LIBS([pam_authenticate], [pam])
86 ;;
87esac
88
89AC_SEARCH_LIBS([iconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])])
90
91dnl Each prefix corresponds to a source tarball which users might have
92dnl downloaded in a newer version and would like to overwrite.
93PKG_CHECK_MODULES([XCB], [xcb xcb-xkb xcb-xinerama xcb-randr])
94PKG_CHECK_MODULES([XCB_IMAGE], [xcb-image])
95PKG_CHECK_MODULES([XCB_UTIL], [xcb-event xcb-util xcb-atom])
96PKG_CHECK_MODULES([XCB_UTIL_XRM], [xcb-xrm])
97PKG_CHECK_MODULES([XKBCOMMON], [xkbcommon xkbcommon-x11])
98PKG_CHECK_MODULES([CAIRO], [cairo])
99
100# Checks for programs.
101AC_PROG_AWK
102AC_PROG_CPP
103AC_PROG_INSTALL
104AC_PROG_MAKE_SET
105AC_PROG_RANLIB
106AC_PROG_LN_S
107
108AM_PROG_AR
109
110AX_FLAGS_WARN_ALL
111AX_CHECK_COMPILE_FLAG([-Wunused-value], [AX_APPEND_FLAG([-Wunused-value], [AM_CFLAGS])])
112AC_SUBST(AM_CFLAGS)
113
114# Checks for header files.
115AC_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])])
116
117AC_CONFIG_FILES([Makefile])
118
119# Enable address sanitizer for non-release builds. The performance hit is a
120# 50% increase of wallclock time for the testsuite on my machine.
121if test x$is_release = xyes; then
122 default_sanitizers=
123else
124 default_sanitizers=address
125fi
126AX_SANITIZERS(, [$default_sanitizers], [AC_DEFINE([I3LOCK_ASAN_ENABLED], [], [Enable ASAN])])
127
128AC_OUTPUT
129
130in_git_worktree=`git rev-parse --is-inside-work-tree 2>/dev/null`
131if [[ "$in_git_worktree" = "true" ]]; then
132 git_dir=`git rev-parse --git-dir 2>/dev/null`
133 srcdir=`dirname "$git_dir"`
134 exclude_dir=`pwd | sed "s,^$srcdir,,g"`
135 if ! grep -q "^$exclude_dir" "$git_dir/info/exclude"; then
136 echo "$exclude_dir" >> "$git_dir/info/exclude"
137 fi
138fi
139
140echo \
141"--------------------------------------------------------------------------------
142build configured:
143
144AS_HELP_STRING([i3lock version:], [`echo ${I3LOCK_VERSION} | sed 's,\\\\,,g'`])
145AS_HELP_STRING([is release version:], [${is_release}])
146
147AS_HELP_STRING([enable debug flags:], [${ax_enable_debug}])
148AS_HELP_STRING([code coverage:], [${CODE_COVERAGE_ENABLED}])
149AS_HELP_STRING([enabled sanitizers:], [${ax_enabled_sanitizers}])
150
151To compile, run:
152
153 cd `pwd` && make -j8
154--------------------------------------------------------------------------------"
diff --git a/cursors.h b/cursors.h
new file mode 100644
index 0000000..bfce895
--- /dev/null
+++ b/cursors.h
@@ -0,0 +1,8 @@
1#ifndef _CURSORS_H
2#define _CURSORS_H
3
4#define CURS_NONE 0
5#define CURS_WIN 1
6#define CURS_DEFAULT 2
7
8#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 @@
1/*
2 * vim:ts=4:sw=4:expandtab
3 *
4 * i3 - an improved dynamic tiling window manager
5 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
6 *
7 */
8#include "dpi.h"
9
10#include <math.h>
11#include <stdlib.h>
12#include <stdio.h>
13#include <xcb/xcb_xrm.h>
14#include "xcb.h"
15#include "i3lock.h"
16
17extern bool debug_mode;
18
19static long dpi;
20
21extern xcb_screen_t *screen;
22
23static long init_dpi_fallback(void) {
24 return (double)screen->height_in_pixels * 25.4 / (double)screen->height_in_millimeters;
25}
26
27/*
28 * Initialize the DPI setting.
29 * This will use the 'Xft.dpi' X resource if available and fall back to
30 * guessing the correct value otherwise.
31 */
32void init_dpi(void) {
33 xcb_xrm_database_t *database = NULL;
34 char *resource = NULL;
35
36 if (conn == NULL) {
37 goto init_dpi_end;
38 }
39
40 database = xcb_xrm_database_from_default(conn);
41 if (database == NULL) {
42 DEBUG("Failed to open the resource database.\n");
43 goto init_dpi_end;
44 }
45
46 xcb_xrm_resource_get_string(database, "Xft.dpi", NULL, &resource);
47 if (resource == NULL) {
48 DEBUG("Resource Xft.dpi not specified, skipping.\n");
49 goto init_dpi_end;
50 }
51
52 char *endptr;
53 double in_dpi = strtod(resource, &endptr);
54 if (in_dpi == HUGE_VAL || dpi < 0 || *endptr != '\0' || endptr == resource) {
55 DEBUG("Xft.dpi = %s is an invalid number and couldn't be parsed.\n", resource);
56 dpi = 0;
57 goto init_dpi_end;
58 }
59 dpi = (long)round(in_dpi);
60
61 DEBUG("Found Xft.dpi = %ld.\n", dpi);
62
63init_dpi_end:
64 if (resource != NULL) {
65 free(resource);
66 }
67
68 if (database != NULL) {
69 xcb_xrm_database_free(database);
70 }
71
72 if (dpi == 0) {
73 DEBUG("Using fallback for calculating DPI.\n");
74 dpi = init_dpi_fallback();
75 DEBUG("Using dpi = %ld\n", dpi);
76 }
77}
78
79/*
80 * This function returns the value of the DPI setting.
81 *
82 */
83long get_dpi_value(void) {
84 return dpi;
85}
86
87/*
88 * Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI
89 * screen) to a corresponding amount of physical pixels on a standard or retina
90 * screen, e.g. 5 pixels on a 227 DPI MacBook Pro 13" Retina screen.
91 *
92 */
93int logical_px(const int logical) {
94 if (screen == NULL) {
95 /* Dpi info may not be available when parsing a config without an X
96 * server, such as for config file validation. */
97 return logical;
98 }
99
100 /* There are many misconfigurations out there, i.e. systems with screens
101 * whose dpi is in fact higher than 96 dpi, but not significantly higher,
102 * so software was never adapted. We could tell people to reconfigure their
103 * systems to 96 dpi in order to get the behavior they expect/are used to,
104 * but since we can easily detect this case in code, let’s do it for them.
105 */
106 if ((dpi / 96.0) < 1.25)
107 return logical;
108 return ceil((dpi / 96.0) * logical);
109}
diff --git a/dpi.h b/dpi.h
new file mode 100644
index 0000000..35840d2
--- /dev/null
+++ b/dpi.h
@@ -0,0 +1,22 @@
1#pragma once
2
3/**
4 * Initialize the DPI setting.
5 * This will use the 'Xft.dpi' X resource if available and fall back to
6 * guessing the correct value otherwise.
7 */
8void init_dpi(void);
9
10/**
11 * This function returns the value of the DPI setting.
12 *
13 */
14long get_dpi_value(void);
15
16/**
17 * Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI
18 * screen) to a corresponding amount of physical pixels on a standard or retina
19 * screen, e.g. 5 pixels on a 227 DPI MacBook Pro 13" Retina screen.
20 *
21 */
22int 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 @@
1.de Vb \" Begin verbatim text
2.ft CW
3.nf
4.ne \\$1
5..
6.de Ve \" End verbatim text
7.ft R
8.fi
9..
10
11.TH i3lock 1 "JANUARY 2012" Linux "User Manuals"
12
13.SH NAME
14i3lock \- improved screen locker
15
16.SH SYNOPSIS
17.B i3lock
18.RB [\|\-v\|]
19.RB [\|\-n\|]
20.RB [\|\-b\|]
21.RB [\|\-i
22.IR image.png \|]
23.RB [\|\-c
24.IR color \|]
25.RB [\|\-t\|]
26.RB [\|\-p
27.IR pointer\|]
28.RB [\|\-u\|]
29.RB [\|\-e\|]
30.RB [\|\-f\|]
31
32.SH DESCRIPTION
33.B i3lock
34is a simple screen locker like slock. After starting it, you will see a white
35screen (you can configure the color/an image). You can return to your screen by
36entering your password.
37
38.SH IMPROVEMENTS
39
40.IP \[bu] 2
41i3lock 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)
42.IP \[bu]
43You can specify either a background color or a PNG image which will be displayed while your screen is locked.
44.IP \[bu]
45You can specify whether i3lock should bell upon a wrong password.
46.IP \[bu]
47i3lock uses PAM and therefore is compatible with LDAP, etc.
48
49
50.SH OPTIONS
51.TP
52.B \-v, \-\-version
53Display the version of your
54.B i3lock
55
56.TP
57.B \-n, \-\-nofork
58Don't fork after starting.
59
60.TP
61.B \-b, \-\-beep
62Enable beeping. Be sure to not do this when you are about to annoy other people,
63like when opening your laptop in a boring lecture.
64
65.TP
66.B \-u, \-\-no-unlock-indicator
67Disable the unlock indicator. i3lock will by default show an unlock indicator
68after pressing keys. This will give feedback for every keypress and it will
69show you the current PAM state (whether your password is currently being
70verified or whether it is wrong).
71
72.TP
73.BI \-i\ path \fR,\ \fB\-\-image= path
74Display the given PNG image instead of a blank screen.
75
76.TP
77.BI \fB\-\-raw= format
78Read the image given by \-\-image as a raw image instead of PNG. The argument is the image's format
79as <width>x<height>:<pixfmt>. The supported pixel formats are:
80\'native', 'rgb', 'xrgb', 'rgbx', 'bgr', 'xbgr', and 'bgrx'.
81The "native" pixel format expects a pixel as a 32-bit (4-byte) integer in
82the machine's native endianness, with the upper 8 bits unused. Red, green and blue are stored in
83the remaining bits, in that order.
84
85.BR Example:
86.Vb 6
87\& --raw=1920x1080:rgb
88.Ve
89
90.BR
91You can use ImageMagick’s
92.IR convert(1)
93program to feed raw images into i3lock:
94
95.BR
96.Vb 6
97\& convert wallpaper.jpg RGB:- | i3lock --raw 3840x2160:rgb --image /dev/stdin
98.Ve
99
100This allows you to load a variety of image formats without i3lock having to
101support each one explicitly.
102
103.TP
104.BI \-c\ rrggbb \fR,\ \fB\-\-color= rrggbb
105Turn the screen into the given color instead of white. Color must be given in 3-byte
106format: rrggbb (i.e. ff0000 is red).
107
108.TP
109.B \-t, \-\-tiling
110If an image is specified (via \-i) it will display the image tiled all over the screen
111(if it is a multi-monitor setup, the image is visible on all screens).
112
113.TP
114.BI \-p\ win|default \fR,\ \fB\-\-pointer= win|default
115If you specify "default",
116.B i3lock
117does not hide your mouse pointer. If you specify "win",
118.B i3lock
119displays a hardcoded Windows-Pointer (thus enabling you to mess with your
120friends by using a screenshot of a Windows desktop as a locking-screen).
121
122.TP
123.B \-e, \-\-ignore-empty-password
124When an empty password is provided by the user, do not validate
125it. Without this option, the empty password will be provided to PAM
126and, if invalid, the user will have to wait a few seconds before
127another try. This can be useful if the XF86ScreenSaver key is used to
128put a laptop to sleep and bounce on resume or if you happen to wake up
129your computer with the enter key.
130
131.TP
132.B \-f, \-\-show-failed-attempts
133Show the number of failed attempts, if any.
134
135.TP
136.B \-\-debug
137Enables debug logging.
138Note, that this will log the password used for authentication to stdout.
139
140.SH DPMS
141
142The \-d (\-\-dpms) option was removed from i3lock in version 2.8. There were
143plenty of use-cases that were not properly addressed, and plenty of bugs
144surrounding that feature. While features are not normally removed from i3 and
145its tools, we felt the need to make an exception in this case.
146
147Users who wish to explicitly enable DPMS only when their screen is locked can
148use a wrapper script around i3lock like the following:
149
150.Vb 6
151\& #!/bin/sh
152\& revert() {
153\& xset dpms 0 0 0
154\& }
155\& trap revert HUP INT TERM
156\& xset +dpms dpms 5 5 5
157\& i3lock -n
158\& revert
159.Ve
160
161The \-I (-\-inactivity-timeout=seconds) was removed because it only makes sense with DPMS.
162
163.SH SEE ALSO
164.IR xautolock(1)
165\- use i3lock as your screen saver
166
167.IR convert(1)
168\- feed a wide variety of image formats to i3lock
169
170.SH AUTHOR
171Michael Stapelberg <michael+i3lock at stapelberg dot de>
172
173Jan-Erik Rediger <badboy at archlinux.us>
diff --git a/i3lock.c b/i3lock.c
new file mode 100644
index 0000000..134fdda
--- /dev/null
+++ b/i3lock.c
@@ -0,0 +1,1318 @@
1/*
2 * vim:ts=4:sw=4:expandtab
3 *
4 * © 2010 Michael Stapelberg
5 *
6 * See LICENSE for licensing information
7 *
8 */
9#include <config.h>
10
11#include <stdio.h>
12#include <stdlib.h>
13#include <pwd.h>
14#include <sys/types.h>
15#include <string.h>
16#include <unistd.h>
17#include <stdbool.h>
18#include <stdint.h>
19#include <xcb/xcb.h>
20#include <xcb/xkb.h>
21#include <err.h>
22#include <errno.h>
23#include <assert.h>
24#ifdef __OpenBSD__
25#include <bsd_auth.h>
26#else
27#include <security/pam_appl.h>
28#endif
29#include <getopt.h>
30#include <string.h>
31#include <ev.h>
32#include <sys/mman.h>
33#include <xkbcommon/xkbcommon.h>
34#include <xkbcommon/xkbcommon-compose.h>
35#include <xkbcommon/xkbcommon-x11.h>
36#include <cairo.h>
37#include <cairo/cairo-xcb.h>
38#ifdef __OpenBSD__
39#include <strings.h> /* explicit_bzero(3) */
40#endif
41#include <xcb/xcb_aux.h>
42#include <xcb/randr.h>
43
44#include "i3lock.h"
45#include "xcb.h"
46#include "cursors.h"
47#include "unlock_indicator.h"
48#include "randr.h"
49#include "dpi.h"
50
51#define TSTAMP_N_SECS(n) (n * 1.0)
52#define TSTAMP_N_MINS(n) (60 * TSTAMP_N_SECS(n))
53#define START_TIMER(timer_obj, timeout, callback) \
54 timer_obj = start_timer(timer_obj, timeout, callback)
55#define STOP_TIMER(timer_obj) \
56 timer_obj = stop_timer(timer_obj)
57
58typedef void (*ev_callback_t)(EV_P_ ev_timer *w, int revents);
59static void input_done(void);
60
61char color[7] = "ffffff";
62uint32_t last_resolution[2];
63xcb_window_t win;
64static xcb_cursor_t cursor;
65#ifndef __OpenBSD__
66static pam_handle_t *pam_handle;
67static bool pam_cleanup;
68#endif
69int input_position = 0;
70/* Holds the password you enter (in UTF-8). */
71static char password[512];
72static bool beep = false;
73bool debug_mode = false;
74bool unlock_indicator = true;
75char *modifier_string = NULL;
76static bool dont_fork = false;
77struct ev_loop *main_loop;
78static struct ev_timer *clear_auth_wrong_timeout;
79static struct ev_timer *clear_indicator_timeout;
80static struct ev_timer *discard_passwd_timeout;
81extern unlock_state_t unlock_state;
82extern auth_state_t auth_state;
83int failed_attempts = 0;
84bool show_failed_attempts = false;
85bool retry_verification = false;
86
87static struct xkb_state *xkb_state;
88static struct xkb_context *xkb_context;
89static struct xkb_keymap *xkb_keymap;
90static struct xkb_compose_table *xkb_compose_table;
91static struct xkb_compose_state *xkb_compose_state;
92static uint8_t xkb_base_event;
93static uint8_t xkb_base_error;
94static int randr_base = -1;
95
96cairo_surface_t *img = NULL;
97bool tile = false;
98bool ignore_empty_password = false;
99bool skip_repeated_empty_password = false;
100
101/* isutf, u8_dec © 2005 Jeff Bezanson, public domain */
102#define isutf(c) (((c)&0xC0) != 0x80)
103
104/*
105 * Decrements i to point to the previous unicode glyph
106 *
107 */
108void u8_dec(char *s, int *i) {
109 (void)(isutf(s[--(*i)]) || isutf(s[--(*i)]) || isutf(s[--(*i)]) || --(*i));
110}
111
112/*
113 * Loads the XKB keymap from the X11 server and feeds it to xkbcommon.
114 * Necessary so that we can properly let xkbcommon track the keyboard state and
115 * translate keypresses to utf-8.
116 *
117 */
118static bool load_keymap(void) {
119 if (xkb_context == NULL) {
120 if ((xkb_context = xkb_context_new(0)) == NULL) {
121 fprintf(stderr, "[i3lock] could not create xkbcommon context\n");
122 return false;
123 }
124 }
125
126 xkb_keymap_unref(xkb_keymap);
127
128 int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn);
129 DEBUG("device = %d\n", device_id);
130 if ((xkb_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) {
131 fprintf(stderr, "[i3lock] xkb_x11_keymap_new_from_device failed\n");
132 return false;
133 }
134
135 struct xkb_state *new_state =
136 xkb_x11_state_new_from_device(xkb_keymap, conn, device_id);
137 if (new_state == NULL) {
138 fprintf(stderr, "[i3lock] xkb_x11_state_new_from_device failed\n");
139 return false;
140 }
141
142 xkb_state_unref(xkb_state);
143 xkb_state = new_state;
144
145 return true;
146}
147
148/*
149 * Loads the XKB compose table from the given locale.
150 *
151 */
152static bool load_compose_table(const char *locale) {
153 xkb_compose_table_unref(xkb_compose_table);
154
155 if ((xkb_compose_table = xkb_compose_table_new_from_locale(xkb_context, locale, 0)) == NULL) {
156 fprintf(stderr, "[i3lock] xkb_compose_table_new_from_locale failed\n");
157 return false;
158 }
159
160 struct xkb_compose_state *new_compose_state = xkb_compose_state_new(xkb_compose_table, 0);
161 if (new_compose_state == NULL) {
162 fprintf(stderr, "[i3lock] xkb_compose_state_new failed\n");
163 return false;
164 }
165
166 xkb_compose_state_unref(xkb_compose_state);
167 xkb_compose_state = new_compose_state;
168
169 return true;
170}
171
172/*
173 * Clears the memory which stored the password to be a bit safer against
174 * cold-boot attacks.
175 *
176 */
177static void clear_password_memory(void) {
178#ifdef __OpenBSD__
179 /* Use explicit_bzero(3) which was explicitly designed not to be
180 * optimized out by the compiler. */
181 explicit_bzero(password, strlen(password));
182#else
183 /* A volatile pointer to the password buffer to prevent the compiler from
184 * optimizing this out. */
185 volatile char *vpassword = password;
186 for (size_t c = 0; c < sizeof(password); c++)
187 /* We store a non-random pattern which consists of the (irrelevant)
188 * index plus (!) the value of the beep variable. This prevents the
189 * compiler from optimizing the calls away, since the value of 'beep'
190 * is not known at compile-time. */
191 vpassword[c] = c + (int)beep;
192#endif
193}
194
195ev_timer *start_timer(ev_timer *timer_obj, ev_tstamp timeout, ev_callback_t callback) {
196 if (timer_obj) {
197 ev_timer_stop(main_loop, timer_obj);
198 ev_timer_set(timer_obj, timeout, 0.);
199 ev_timer_start(main_loop, timer_obj);
200 } else {
201 /* When there is no memory, we just don’t have a timeout. We cannot
202 * exit() here, since that would effectively unlock the screen. */
203 timer_obj = calloc(sizeof(struct ev_timer), 1);
204 if (timer_obj) {
205 ev_timer_init(timer_obj, callback, timeout, 0.);
206 ev_timer_start(main_loop, timer_obj);
207 }
208 }
209 return timer_obj;
210}
211
212ev_timer *stop_timer(ev_timer *timer_obj) {
213 if (timer_obj) {
214 ev_timer_stop(main_loop, timer_obj);
215 free(timer_obj);
216 }
217 return NULL;
218}
219
220/*
221 * Neccessary calls after ending input via enter or others
222 *
223 */
224static void finish_input(void) {
225 password[input_position] = '\0';
226 unlock_state = STATE_KEY_PRESSED;
227 redraw_screen();
228 input_done();
229}
230
231/*
232 * Resets auth_state to STATE_AUTH_IDLE 2 seconds after an unsuccessful
233 * authentication event.
234 *
235 */
236static void clear_auth_wrong(EV_P_ ev_timer *w, int revents) {
237 DEBUG("clearing auth wrong\n");
238 auth_state = STATE_AUTH_IDLE;
239 redraw_screen();
240
241 /* Clear modifier string. */
242 if (modifier_string != NULL) {
243 free(modifier_string);
244 modifier_string = NULL;
245 }
246
247 /* Now free this timeout. */
248 STOP_TIMER(clear_auth_wrong_timeout);
249
250 /* retry with input done during auth verification */
251 if (retry_verification) {
252 retry_verification = false;
253 finish_input();
254 }
255}
256
257static void clear_indicator_cb(EV_P_ ev_timer *w, int revents) {
258 clear_indicator();
259 STOP_TIMER(clear_indicator_timeout);
260}
261
262static void clear_input(void) {
263 input_position = 0;
264 clear_password_memory();
265 password[input_position] = '\0';
266}
267
268static void discard_passwd_cb(EV_P_ ev_timer *w, int revents) {
269 clear_input();
270 STOP_TIMER(discard_passwd_timeout);
271}
272
273static void input_done(void) {
274 STOP_TIMER(clear_auth_wrong_timeout);
275 auth_state = STATE_AUTH_VERIFY;
276 unlock_state = STATE_STARTED;
277 redraw_screen();
278
279#ifdef __OpenBSD__
280 struct passwd *pw;
281
282 if (!(pw = getpwuid(getuid())))
283 errx(1, "unknown uid %u.", getuid());
284
285 if (auth_userokay(pw->pw_name, NULL, NULL, password) != 0) {
286 DEBUG("successfully authenticated\n");
287 clear_password_memory();
288
289 ev_break(EV_DEFAULT, EVBREAK_ALL);
290 return;
291 }
292#else
293 if (pam_authenticate(pam_handle, 0) == PAM_SUCCESS) {
294 DEBUG("successfully authenticated\n");
295 clear_password_memory();
296
297 /* PAM credentials should be refreshed, this will for example update any kerberos tickets.
298 * Related to credentials pam_end() needs to be called to cleanup any temporary
299 * credentials like kerberos /tmp/krb5cc_pam_* files which may of been left behind if the
300 * refresh of the credentials failed. */
301 pam_setcred(pam_handle, PAM_REFRESH_CRED);
302 pam_cleanup = true;
303
304 ev_break(EV_DEFAULT, EVBREAK_ALL);
305 return;
306 }
307#endif
308
309 if (debug_mode)
310 fprintf(stderr, "Authentication failure\n");
311
312 /* Get state of Caps and Num lock modifiers, to be displayed in
313 * STATE_AUTH_WRONG state */
314 xkb_mod_index_t idx, num_mods;
315 const char *mod_name;
316
317 num_mods = xkb_keymap_num_mods(xkb_keymap);
318
319 for (idx = 0; idx < num_mods; idx++) {
320 if (!xkb_state_mod_index_is_active(xkb_state, idx, XKB_STATE_MODS_EFFECTIVE))
321 continue;
322
323 mod_name = xkb_keymap_mod_get_name(xkb_keymap, idx);
324 if (mod_name == NULL)
325 continue;
326
327 /* Replace certain xkb names with nicer, human-readable ones. */
328 if (strcmp(mod_name, XKB_MOD_NAME_CAPS) == 0)
329 mod_name = "Caps Lock";
330 else if (strcmp(mod_name, XKB_MOD_NAME_ALT) == 0)
331 mod_name = "Alt";
332 else if (strcmp(mod_name, XKB_MOD_NAME_NUM) == 0)
333 mod_name = "Num Lock";
334 else if (strcmp(mod_name, XKB_MOD_NAME_LOGO) == 0)
335 mod_name = "Super";
336
337 char *tmp;
338 if (modifier_string == NULL) {
339 if (asprintf(&tmp, "%s", mod_name) != -1)
340 modifier_string = tmp;
341 } else if (asprintf(&tmp, "%s, %s", modifier_string, mod_name) != -1) {
342 free(modifier_string);
343 modifier_string = tmp;
344 }
345 }
346
347 auth_state = STATE_AUTH_WRONG;
348 failed_attempts += 1;
349 clear_input();
350 if (unlock_indicator)
351 redraw_screen();
352
353 /* Clear this state after 2 seconds (unless the user enters another
354 * password during that time). */
355 ev_now_update(main_loop);
356 START_TIMER(clear_auth_wrong_timeout, TSTAMP_N_SECS(2), clear_auth_wrong);
357
358 /* Cancel the clear_indicator_timeout, it would hide the unlock indicator
359 * too early. */
360 STOP_TIMER(clear_indicator_timeout);
361
362 /* beep on authentication failure, if enabled */
363 if (beep) {
364 xcb_bell(conn, 100);
365 xcb_flush(conn);
366 }
367}
368
369static void redraw_timeout(EV_P_ ev_timer *w, int revents) {
370 redraw_screen();
371 STOP_TIMER(w);
372}
373
374static bool skip_without_validation(void) {
375 if (input_position != 0)
376 return false;
377
378 if (skip_repeated_empty_password || ignore_empty_password)
379 return true;
380
381 return false;
382}
383
384/*
385 * Handle key presses. Fixes state, then looks up the key symbol for the
386 * given keycode, then looks up the key symbol (as UCS-2), converts it to
387 * UTF-8 and stores it in the password array.
388 *
389 */
390static void handle_key_press(xcb_key_press_event_t *event) {
391 xkb_keysym_t ksym;
392 char buffer[128];
393 int n;
394 bool ctrl;
395 bool composed = false;
396
397 ksym = xkb_state_key_get_one_sym(xkb_state, event->detail);
398 ctrl = xkb_state_mod_name_is_active(xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED);
399
400 /* The buffer will be null-terminated, so n >= 2 for 1 actual character. */
401 memset(buffer, '\0', sizeof(buffer));
402
403 if (xkb_compose_state && xkb_compose_state_feed(xkb_compose_state, ksym) == XKB_COMPOSE_FEED_ACCEPTED) {
404 switch (xkb_compose_state_get_status(xkb_compose_state)) {
405 case XKB_COMPOSE_NOTHING:
406 break;
407 case XKB_COMPOSE_COMPOSING:
408 return;
409 case XKB_COMPOSE_COMPOSED:
410 /* xkb_compose_state_get_utf8 doesn't include the terminating byte in the return value
411 * as xkb_keysym_to_utf8 does. Adding one makes the variable n consistent. */
412 n = xkb_compose_state_get_utf8(xkb_compose_state, buffer, sizeof(buffer)) + 1;
413 ksym = xkb_compose_state_get_one_sym(xkb_compose_state);
414 composed = true;
415 break;
416 case XKB_COMPOSE_CANCELLED:
417 xkb_compose_state_reset(xkb_compose_state);
418 return;
419 }
420 }
421
422 if (!composed) {
423 n = xkb_keysym_to_utf8(ksym, buffer, sizeof(buffer));
424 }
425
426 switch (ksym) {
427 case XKB_KEY_j:
428 case XKB_KEY_m:
429 case XKB_KEY_Return:
430 case XKB_KEY_KP_Enter:
431 case XKB_KEY_XF86ScreenSaver:
432 if ((ksym == XKB_KEY_j || ksym == XKB_KEY_m) && !ctrl)
433 break;
434
435 if (auth_state == STATE_AUTH_WRONG) {
436 retry_verification = true;
437 return;
438 }
439
440 if (skip_without_validation()) {
441 clear_input();
442 return;
443 }
444 finish_input();
445 skip_repeated_empty_password = true;
446 return;
447 default:
448 skip_repeated_empty_password = false;
449 // A new password is being entered, but a previous one is pending.
450 // Discard the old one and clear the retry_verification flag.
451 if (retry_verification) {
452 retry_verification = false;
453 clear_input();
454 }
455 }
456
457 switch (ksym) {
458 case XKB_KEY_u:
459 case XKB_KEY_Escape:
460 if ((ksym == XKB_KEY_u && ctrl) ||
461 ksym == XKB_KEY_Escape) {
462 DEBUG("C-u pressed\n");
463 clear_input();
464 /* Also hide the unlock indicator */
465 if (unlock_indicator)
466 clear_indicator();
467 return;
468 }
469 break;
470
471 case XKB_KEY_Delete:
472 case XKB_KEY_KP_Delete:
473 /* Deleting forward doesn’t make sense, as i3lock doesn’t allow you
474 * to move the cursor when entering a password. We need to eat this
475 * key press so that it won’t be treated as part of the password,
476 * see issue #50. */
477 return;
478
479 case XKB_KEY_h:
480 case XKB_KEY_BackSpace:
481 if (ksym == XKB_KEY_h && !ctrl)
482 break;
483
484 if (input_position == 0) {
485 START_TIMER(clear_indicator_timeout, 1.0, clear_indicator_cb);
486 unlock_state = STATE_NOTHING_TO_DELETE;
487 redraw_screen();
488 return;
489 }
490
491 /* decrement input_position to point to the previous glyph */
492 u8_dec(password, &input_position);
493 password[input_position] = '\0';
494
495 /* Hide the unlock indicator after a bit if the password buffer is
496 * empty. */
497 START_TIMER(clear_indicator_timeout, 1.0, clear_indicator_cb);
498 unlock_state = STATE_BACKSPACE_ACTIVE;
499 redraw_screen();
500 unlock_state = STATE_KEY_PRESSED;
501 return;
502 }
503
504 if ((input_position + 8) >= (int)sizeof(password))
505 return;
506
507#if 0
508 /* FIXME: handle all of these? */
509 printf("is_keypad_key = %d\n", xcb_is_keypad_key(sym));
510 printf("is_private_keypad_key = %d\n", xcb_is_private_keypad_key(sym));
511 printf("xcb_is_cursor_key = %d\n", xcb_is_cursor_key(sym));
512 printf("xcb_is_pf_key = %d\n", xcb_is_pf_key(sym));
513 printf("xcb_is_function_key = %d\n", xcb_is_function_key(sym));
514 printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym));
515 printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym));
516#endif
517
518 if (n < 2)
519 return;
520
521 /* store it in the password array as UTF-8 */
522 memcpy(password + input_position, buffer, n - 1);
523 input_position += n - 1;
524 DEBUG("current password = %.*s\n", input_position, password);
525
526 if (unlock_indicator) {
527 unlock_state = STATE_KEY_ACTIVE;
528 redraw_screen();
529 unlock_state = STATE_KEY_PRESSED;
530
531 struct ev_timer *timeout = NULL;
532 START_TIMER(timeout, TSTAMP_N_SECS(0.25), redraw_timeout);
533 STOP_TIMER(clear_indicator_timeout);
534 }
535
536 START_TIMER(discard_passwd_timeout, TSTAMP_N_MINS(3), discard_passwd_cb);
537}
538
539/*
540 * A visibility notify event will be received when the visibility (= can the
541 * user view the complete window) changes, so for example when a popup overlays
542 * some area of the i3lock window.
543 *
544 * In this case, we raise our window on top so that the popup (or whatever is
545 * hiding us) gets hidden.
546 *
547 */
548static void handle_visibility_notify(xcb_connection_t *conn,
549 xcb_visibility_notify_event_t *event) {
550 if (event->state != XCB_VISIBILITY_UNOBSCURED) {
551 uint32_t values[] = {XCB_STACK_MODE_ABOVE};
552 xcb_configure_window(conn, event->window, XCB_CONFIG_WINDOW_STACK_MODE, values);
553 xcb_flush(conn);
554 }
555}
556
557/*
558 * Called when the keyboard mapping changes. We update our symbols.
559 *
560 * We ignore errors — if the new keymap cannot be loaded it’s better if the
561 * screen stays locked and the user intervenes by using killall i3lock.
562 *
563 */
564static void process_xkb_event(xcb_generic_event_t *gevent) {
565 union xkb_event {
566 struct {
567 uint8_t response_type;
568 uint8_t xkbType;
569 uint16_t sequence;
570 xcb_timestamp_t time;
571 uint8_t deviceID;
572 } any;
573 xcb_xkb_new_keyboard_notify_event_t new_keyboard_notify;
574 xcb_xkb_map_notify_event_t map_notify;
575 xcb_xkb_state_notify_event_t state_notify;
576 } *event = (union xkb_event *)gevent;
577
578 DEBUG("process_xkb_event for device %d\n", event->any.deviceID);
579
580 if (event->any.deviceID != xkb_x11_get_core_keyboard_device_id(conn))
581 return;
582
583 /*
584 * XkbNewKkdNotify and XkbMapNotify together capture all sorts of keymap
585 * updates (e.g. xmodmap, xkbcomp, setxkbmap), with minimal redundent
586 * recompilations.
587 */
588 switch (event->any.xkbType) {
589 case XCB_XKB_NEW_KEYBOARD_NOTIFY:
590 if (event->new_keyboard_notify.changed & XCB_XKB_NKN_DETAIL_KEYCODES)
591 (void)load_keymap();
592 break;
593
594 case XCB_XKB_MAP_NOTIFY:
595 (void)load_keymap();
596 break;
597
598 case XCB_XKB_STATE_NOTIFY:
599 xkb_state_update_mask(xkb_state,
600 event->state_notify.baseMods,
601 event->state_notify.latchedMods,
602 event->state_notify.lockedMods,
603 event->state_notify.baseGroup,
604 event->state_notify.latchedGroup,
605 event->state_notify.lockedGroup);
606 break;
607 }
608}
609
610/*
611 * Called when the properties on the root window change, e.g. when the screen
612 * resolution changes. If so we update the window to cover the whole screen
613 * and also redraw the image, if any.
614 *
615 */
616void handle_screen_resize(void) {
617 xcb_get_geometry_cookie_t geomc;
618 xcb_get_geometry_reply_t *geom;
619 geomc = xcb_get_geometry(conn, screen->root);
620 if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
621 return;
622
623 if (last_resolution[0] == geom->width &&
624 last_resolution[1] == geom->height) {
625 free(geom);
626 return;
627 }
628
629 last_resolution[0] = geom->width;
630 last_resolution[1] = geom->height;
631
632 free(geom);
633
634 redraw_screen();
635
636 uint32_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
637 xcb_configure_window(conn, win, mask, last_resolution);
638 xcb_flush(conn);
639
640 randr_query(screen->root);
641 redraw_screen();
642}
643
644static ssize_t read_raw_image_native(uint32_t *dest, FILE *src, size_t width, size_t height, int pixstride) {
645 ssize_t count = 0;
646 for (size_t y = 0; y < height; y++) {
647 size_t n = fread(&dest[y * pixstride], 1, width * 4, src);
648 count += n;
649 if (n < (size_t)(width * 4))
650 break;
651 }
652
653 return count;
654}
655
656struct raw_pixel_format {
657 int bpp;
658 int red;
659 int green;
660 int blue;
661};
662
663static ssize_t read_raw_image_fmt(uint32_t *dest, FILE *src, size_t width, size_t height, int pixstride,
664 struct raw_pixel_format fmt) {
665 unsigned char *buf = malloc(width * fmt.bpp);
666 if (buf == NULL)
667 return -1;
668
669 ssize_t count = 0;
670 for (size_t y = 0; y < height; y++) {
671 size_t n = fread(buf, 1, width * fmt.bpp, src);
672 count += n;
673 if (n < (size_t)(width * fmt.bpp))
674 break;
675
676 for (size_t x = 0; x < width; ++x) {
677 int idx = x * fmt.bpp;
678 dest[y * pixstride + x] = 0 |
679 (buf[idx + fmt.red]) << 16 |
680 (buf[idx + fmt.green]) << 8 |
681 (buf[idx + fmt.blue]);
682 }
683 }
684
685 free(buf);
686 return count;
687}
688
689// Pre-defind pixel formats (<bytes per pixel>, <red pixel>, <green pixel>, <blue pixel>)
690static const struct raw_pixel_format raw_fmt_rgb = {3, 0, 1, 2};
691static const struct raw_pixel_format raw_fmt_rgbx = {4, 0, 1, 2};
692static const struct raw_pixel_format raw_fmt_xrgb = {4, 1, 2, 3};
693static const struct raw_pixel_format raw_fmt_bgr = {3, 2, 1, 0};
694static const struct raw_pixel_format raw_fmt_bgrx = {4, 2, 1, 0};
695static const struct raw_pixel_format raw_fmt_xbgr = {4, 3, 2, 1};
696
697static cairo_surface_t *read_raw_image(const char *image_path, const char *image_raw_format) {
698 cairo_surface_t *img;
699
700#define RAW_PIXFMT_MAXLEN 6
701#define STRINGIFY1(x) #x
702#define STRINGIFY(x) STRINGIFY1(x)
703 /* Parse format as <width>x<height>:<pixfmt> */
704 char pixfmt[RAW_PIXFMT_MAXLEN + 1];
705 size_t w, h;
706 const char *fmt = "%zux%zu:%" STRINGIFY(RAW_PIXFMT_MAXLEN) "s";
707 if (sscanf(image_raw_format, fmt, &w, &h, pixfmt) != 3) {
708 fprintf(stderr, "Invalid image format: \"%s\"\n", image_raw_format);
709 return NULL;
710 }
711#undef RAW_PIXFMT_MAXLEN
712#undef STRINGIFY1
713#undef STRINGIFY
714
715 /* Create image surface */
716 img = cairo_image_surface_create(CAIRO_FORMAT_RGB24, w, h);
717 if (cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) {
718 fprintf(stderr, "Could not create surface: %s\n",
719 cairo_status_to_string(cairo_surface_status(img)));
720 return NULL;
721 }
722 cairo_surface_flush(img);
723
724 /* Use uint32_t* because cairo uses native endianness */
725 uint32_t *data = (uint32_t *)cairo_image_surface_get_data(img);
726 const int pixstride = cairo_image_surface_get_stride(img) / 4;
727
728 FILE *f = fopen(image_path, "r");
729 if (f == NULL) {
730 fprintf(stderr, "Could not open image \"%s\": %s\n",
731 image_path, strerror(errno));
732 cairo_surface_destroy(img);
733 return NULL;
734 }
735
736 /* Read the image, respecting cairo's stride, according to the pixfmt */
737 ssize_t size, count;
738 if (strcmp(pixfmt, "native") == 0) {
739 /* If the pixfmt is 'native', just read each line directly into the buffer */
740 size = w * h * 4;
741 count = read_raw_image_native(data, f, w, h, pixstride);
742 } else {
743 const struct raw_pixel_format *fmt = NULL;
744
745 if (strcmp(pixfmt, "rgb") == 0)
746 fmt = &raw_fmt_rgb;
747 else if (strcmp(pixfmt, "rgbx") == 0)
748 fmt = &raw_fmt_rgbx;
749 else if (strcmp(pixfmt, "xrgb") == 0)
750 fmt = &raw_fmt_xrgb;
751 else if (strcmp(pixfmt, "bgr") == 0)
752 fmt = &raw_fmt_bgr;
753 else if (strcmp(pixfmt, "bgrx") == 0)
754 fmt = &raw_fmt_bgrx;
755 else if (strcmp(pixfmt, "xbgr") == 0)
756 fmt = &raw_fmt_xbgr;
757
758 if (fmt == NULL) {
759 fprintf(stderr, "Unknown raw pixel format: %s\n", pixfmt);
760 fclose(f);
761 cairo_surface_destroy(img);
762 return NULL;
763 }
764
765 size = w * h * fmt->bpp;
766 count = read_raw_image_fmt(data, f, w, h, pixstride, *fmt);
767 }
768
769 cairo_surface_mark_dirty(img);
770
771 if (count < size) {
772 if (count < 0 || ferror(f)) {
773 fprintf(stderr, "Failed to read image \"%s\": %s\n",
774 image_path, strerror(errno));
775 fclose(f);
776 cairo_surface_destroy(img);
777 return NULL;
778 } else {
779 /* Print a warning if the file contains less data than expected,
780 * but don't abort. It's useful to see how the image looks even if it's wrong. */
781 fprintf(stderr, "Warning: expected to read %zi bytes from \"%s\", read %zi\n",
782 size, image_path, count);
783 }
784 }
785
786 fclose(f);
787 return img;
788}
789
790static bool verify_png_image(const char *image_path) {
791 if (!image_path) {
792 return false;
793 }
794
795 /* Check file exists and has correct PNG header */
796 FILE *png_file = fopen(image_path, "r");
797 if (png_file == NULL) {
798 fprintf(stderr, "Image file path \"%s\" cannot be opened: %s\n", image_path, strerror(errno));
799 return false;
800 }
801 unsigned char png_header[8];
802 memset(png_header, '\0', sizeof(png_header));
803 int bytes_read = fread(png_header, 1, sizeof(png_header), png_file);
804 fclose(png_file);
805 if (bytes_read != sizeof(png_header)) {
806 fprintf(stderr, "Could not read PNG header from \"%s\"\n", image_path);
807 return false;
808 }
809
810 // Check PNG header according to the specification, available at:
811 // https://www.w3.org/TR/2003/REC-PNG-20031110/#5PNG-file-signature
812 static unsigned char PNG_REFERENCE_HEADER[8] = {137, 80, 78, 71, 13, 10, 26, 10};
813 if (memcmp(PNG_REFERENCE_HEADER, png_header, sizeof(png_header)) != 0) {
814 fprintf(stderr, "File \"%s\" does not start with a PNG header. i3lock currently only supports loading PNG files.\n", image_path);
815 return false;
816 }
817 return true;
818}
819
820#ifndef __OpenBSD__
821/*
822 * Callback function for PAM. We only react on password request callbacks.
823 *
824 */
825static int conv_callback(int num_msg, const struct pam_message **msg,
826 struct pam_response **resp, void *appdata_ptr) {
827 if (num_msg == 0)
828 return 1;
829
830 /* PAM expects an array of responses, one for each message */
831 if ((*resp = calloc(num_msg, sizeof(struct pam_response))) == NULL) {
832 perror("calloc");
833 return 1;
834 }
835
836 for (int c = 0; c < num_msg; c++) {
837 if (msg[c]->msg_style != PAM_PROMPT_ECHO_OFF &&
838 msg[c]->msg_style != PAM_PROMPT_ECHO_ON)
839 continue;
840
841 /* return code is currently not used but should be set to zero */
842 resp[c]->resp_retcode = 0;
843 if ((resp[c]->resp = strdup(password)) == NULL) {
844 perror("strdup");
845 return 1;
846 }
847 }
848
849 return 0;
850}
851#endif
852
853/*
854 * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
855 * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
856 *
857 */
858static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
859 /* empty, because xcb_prepare_cb and xcb_check_cb are used */
860}
861
862/*
863 * Flush before blocking (and waiting for new events)
864 *
865 */
866static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
867 xcb_flush(conn);
868}
869
870/*
871 * Try closing logind sleep lock fd passed over from xss-lock, in case we're
872 * being run from there.
873 *
874 */
875static void maybe_close_sleep_lock_fd(void) {
876 const char *sleep_lock_fd = getenv("XSS_SLEEP_LOCK_FD");
877 char *endptr;
878 if (sleep_lock_fd && *sleep_lock_fd != 0) {
879 long int fd = strtol(sleep_lock_fd, &endptr, 10);
880 if (*endptr == 0) {
881 close(fd);
882 }
883 }
884}
885
886/*
887 * Instead of polling the X connection socket we leave this to
888 * xcb_poll_for_event() which knows better than we can ever know.
889 *
890 */
891static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
892 xcb_generic_event_t *event;
893
894 if (xcb_connection_has_error(conn))
895 errx(EXIT_FAILURE, "X11 connection broke, did your server terminate?");
896
897 while ((event = xcb_poll_for_event(conn)) != NULL) {
898 if (event->response_type == 0) {
899 xcb_generic_error_t *error = (xcb_generic_error_t *)event;
900 if (debug_mode)
901 fprintf(stderr, "X11 Error received! sequence 0x%x, error_code = %d\n",
902 error->sequence, error->error_code);
903 free(event);
904 continue;
905 }
906
907 /* Strip off the highest bit (set if the event is generated) */
908 int type = (event->response_type & 0x7F);
909
910 switch (type) {
911 case XCB_KEY_PRESS:
912 handle_key_press((xcb_key_press_event_t *)event);
913 break;
914
915 case XCB_VISIBILITY_NOTIFY:
916 handle_visibility_notify(conn, (xcb_visibility_notify_event_t *)event);
917 break;
918
919 case XCB_MAP_NOTIFY:
920 maybe_close_sleep_lock_fd();
921 if (!dont_fork) {
922 /* After the first MapNotify, we never fork again. We don’t
923 * expect to get another MapNotify, but better be sure… */
924 dont_fork = true;
925
926 /* In the parent process, we exit */
927 if (fork() != 0)
928 exit(0);
929
930 ev_loop_fork(EV_DEFAULT);
931 }
932 break;
933
934 case XCB_CONFIGURE_NOTIFY:
935 handle_screen_resize();
936 break;
937
938 default:
939 if (type == xkb_base_event) {
940 process_xkb_event(event);
941 }
942 if (randr_base > -1 &&
943 type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
944 randr_query(screen->root);
945 handle_screen_resize();
946 }
947 }
948
949 free(event);
950 }
951}
952
953/*
954 * This function is called from a fork()ed child and will raise the i3lock
955 * window when the window is obscured, even when the main i3lock process is
956 * blocked due to the authentication backend.
957 *
958 */
959static void raise_loop(xcb_window_t window) {
960 xcb_connection_t *conn;
961 xcb_generic_event_t *event;
962 int screens;
963
964 if (xcb_connection_has_error((conn = xcb_connect(NULL, &screens))) > 0)
965 errx(EXIT_FAILURE, "Cannot open display");
966
967 /* We need to know about the window being obscured or getting destroyed. */
968 xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK,
969 (uint32_t[]){
970 XCB_EVENT_MASK_VISIBILITY_CHANGE |
971 XCB_EVENT_MASK_STRUCTURE_NOTIFY});
972 xcb_flush(conn);
973
974 DEBUG("Watching window 0x%08x\n", window);
975 while ((event = xcb_wait_for_event(conn)) != NULL) {
976 if (event->response_type == 0) {
977 xcb_generic_error_t *error = (xcb_generic_error_t *)event;
978 DEBUG("X11 Error received! sequence 0x%x, error_code = %d\n",
979 error->sequence, error->error_code);
980 free(event);
981 continue;
982 }
983 /* Strip off the highest bit (set if the event is generated) */
984 int type = (event->response_type & 0x7F);
985 DEBUG("Read event of type %d\n", type);
986 switch (type) {
987 case XCB_VISIBILITY_NOTIFY:
988 handle_visibility_notify(conn, (xcb_visibility_notify_event_t *)event);
989 break;
990 case XCB_UNMAP_NOTIFY:
991 DEBUG("UnmapNotify for 0x%08x\n", (((xcb_unmap_notify_event_t *)event)->window));
992 if (((xcb_unmap_notify_event_t *)event)->window == window)
993 exit(EXIT_SUCCESS);
994 break;
995 case XCB_DESTROY_NOTIFY:
996 DEBUG("DestroyNotify for 0x%08x\n", (((xcb_destroy_notify_event_t *)event)->window));
997 if (((xcb_destroy_notify_event_t *)event)->window == window)
998 exit(EXIT_SUCCESS);
999 break;
1000 default:
1001 DEBUG("Unhandled event type %d\n", type);
1002 break;
1003 }
1004 free(event);
1005 }
1006}
1007
1008int main(int argc, char *argv[]) {
1009 struct passwd *pw;
1010 char *username;
1011 char *image_path = NULL;
1012 char *image_raw_format = NULL;
1013#ifndef __OpenBSD__
1014 int ret;
1015 struct pam_conv conv = {conv_callback, NULL};
1016#endif
1017 int curs_choice = CURS_NONE;
1018 int o;
1019 int longoptind = 0;
1020 struct option longopts[] = {
1021 {"version", no_argument, NULL, 'v'},
1022 {"nofork", no_argument, NULL, 'n'},
1023 {"beep", no_argument, NULL, 'b'},
1024 {"dpms", no_argument, NULL, 'd'},
1025 {"color", required_argument, NULL, 'c'},
1026 {"pointer", required_argument, NULL, 'p'},
1027 {"debug", no_argument, NULL, 0},
1028 {"help", no_argument, NULL, 'h'},
1029 {"no-unlock-indicator", no_argument, NULL, 'u'},
1030 {"image", required_argument, NULL, 'i'},
1031 {"raw", required_argument, NULL, 0},
1032 {"tiling", no_argument, NULL, 't'},
1033 {"ignore-empty-password", no_argument, NULL, 'e'},
1034 {"inactivity-timeout", required_argument, NULL, 'I'},
1035 {"show-failed-attempts", no_argument, NULL, 'f'},
1036 {NULL, no_argument, NULL, 0}};
1037
1038 if ((pw = getpwuid(getuid())) == NULL)
1039 err(EXIT_FAILURE, "getpwuid() failed");
1040 if ((username = pw->pw_name) == NULL)
1041 errx(EXIT_FAILURE, "pw->pw_name is NULL.");
1042
1043 char *optstring = "hvnbdc:p:ui:teI:f";
1044 while ((o = getopt_long(argc, argv, optstring, longopts, &longoptind)) != -1) {
1045 switch (o) {
1046 case 'v':
1047 errx(EXIT_SUCCESS, "version " I3LOCK_VERSION " © 2010 Michael Stapelberg");
1048 case 'n':
1049 dont_fork = true;
1050 break;
1051 case 'b':
1052 beep = true;
1053 break;
1054 case 'd':
1055 fprintf(stderr, "DPMS support has been removed from i3lock. Please see the manpage i3lock(1).\n");
1056 break;
1057 case 'I': {
1058 fprintf(stderr, "Inactivity timeout only makes sense with DPMS, which was removed. Please see the manpage i3lock(1).\n");
1059 break;
1060 }
1061 case 'c': {
1062 char *arg = optarg;
1063
1064 /* Skip # if present */
1065 if (arg[0] == '#')
1066 arg++;
1067
1068 if (strlen(arg) != 6 || sscanf(arg, "%06[0-9a-fA-F]", color) != 1)
1069 errx(EXIT_FAILURE, "color is invalid, it must be given in 3-byte hexadecimal format: rrggbb");
1070
1071 break;
1072 }
1073 case 'u':
1074 unlock_indicator = false;
1075 break;
1076 case 'i':
1077 image_path = strdup(optarg);
1078 break;
1079 case 't':
1080 tile = true;
1081 break;
1082 case 'p':
1083 if (!strcmp(optarg, "win")) {
1084 curs_choice = CURS_WIN;
1085 } else if (!strcmp(optarg, "default")) {
1086 curs_choice = CURS_DEFAULT;
1087 } else {
1088 errx(EXIT_FAILURE, "i3lock: Invalid pointer type given. Expected one of \"win\" or \"default\".");
1089 }
1090 break;
1091 case 'e':
1092 ignore_empty_password = true;
1093 break;
1094 case 0:
1095 if (strcmp(longopts[longoptind].name, "debug") == 0)
1096 debug_mode = true;
1097 else if (strcmp(longopts[longoptind].name, "raw") == 0)
1098 image_raw_format = strdup(optarg);
1099 break;
1100 case 'f':
1101 show_failed_attempts = true;
1102 break;
1103 default:
1104 errx(EXIT_FAILURE, "Syntax: i3lock [-v] [-n] [-b] [-d] [-c color] [-u] [-p win|default]"
1105 " [-i image.png] [-t] [-e] [-I timeout] [-f]");
1106 }
1107 }
1108
1109 /* We need (relatively) random numbers for highlighting a random part of
1110 * the unlock indicator upon keypresses. */
1111 srand(time(NULL));
1112
1113#ifndef __OpenBSD__
1114 /* Initialize PAM */
1115 if ((ret = pam_start("i3lock", username, &conv, &pam_handle)) != PAM_SUCCESS)
1116 errx(EXIT_FAILURE, "PAM: %s", pam_strerror(pam_handle, ret));
1117
1118 if ((ret = pam_set_item(pam_handle, PAM_TTY, getenv("DISPLAY"))) != PAM_SUCCESS)
1119 errx(EXIT_FAILURE, "PAM: %s", pam_strerror(pam_handle, ret));
1120#endif
1121
1122/* Using mlock() as non-super-user seems only possible in Linux.
1123 * Users of other operating systems should use encrypted swap/no swap
1124 * (or remove the ifdef and run i3lock as super-user).
1125 * Alas, swap is encrypted by default on OpenBSD so swapping out
1126 * is not necessarily an issue. */
1127#if defined(__linux__)
1128 /* Lock the area where we store the password in memory, we don’t want it to
1129 * be swapped to disk. Since Linux 2.6.9, this does not require any
1130 * privileges, just enough bytes in the RLIMIT_MEMLOCK limit. */
1131 if (mlock(password, sizeof(password)) != 0)
1132 err(EXIT_FAILURE, "Could not lock page in memory, check RLIMIT_MEMLOCK");
1133#endif
1134
1135 /* Double checking that connection is good and operatable with xcb */
1136 int screennr;
1137 if ((conn = xcb_connect(NULL, &screennr)) == NULL ||
1138 xcb_connection_has_error(conn))
1139 errx(EXIT_FAILURE, "Could not connect to X11, maybe you need to set DISPLAY?");
1140
1141 if (xkb_x11_setup_xkb_extension(conn,
1142 XKB_X11_MIN_MAJOR_XKB_VERSION,
1143 XKB_X11_MIN_MINOR_XKB_VERSION,
1144 0,
1145 NULL,
1146 NULL,
1147 &xkb_base_event,
1148 &xkb_base_error) != 1)
1149 errx(EXIT_FAILURE, "Could not setup XKB extension.");
1150
1151 static const xcb_xkb_map_part_t required_map_parts =
1152 (XCB_XKB_MAP_PART_KEY_TYPES |
1153 XCB_XKB_MAP_PART_KEY_SYMS |
1154 XCB_XKB_MAP_PART_MODIFIER_MAP |
1155 XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
1156 XCB_XKB_MAP_PART_KEY_ACTIONS |
1157 XCB_XKB_MAP_PART_VIRTUAL_MODS |
1158 XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP);
1159
1160 static const xcb_xkb_event_type_t required_events =
1161 (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
1162 XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
1163 XCB_XKB_EVENT_TYPE_STATE_NOTIFY);
1164
1165 xcb_xkb_select_events(
1166 conn,
1167 xkb_x11_get_core_keyboard_device_id(conn),
1168 required_events,
1169 0,
1170 required_events,
1171 required_map_parts,
1172 required_map_parts,
1173 0);
1174
1175 /* When we cannot initially load the keymap, we better exit */
1176 if (!load_keymap())
1177 errx(EXIT_FAILURE, "Could not load keymap");
1178
1179 const char *locale = getenv("LC_ALL");
1180 if (!locale || !*locale)
1181 locale = getenv("LC_CTYPE");
1182 if (!locale || !*locale)
1183 locale = getenv("LANG");
1184 if (!locale || !*locale) {
1185 if (debug_mode)
1186 fprintf(stderr, "Can't detect your locale, fallback to C\n");
1187 locale = "C";
1188 }
1189
1190 load_compose_table(locale);
1191
1192 screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
1193
1194 init_dpi();
1195
1196 randr_init(&randr_base, screen->root);
1197 randr_query(screen->root);
1198
1199 last_resolution[0] = screen->width_in_pixels;
1200 last_resolution[1] = screen->height_in_pixels;
1201
1202 xcb_change_window_attributes(conn, screen->root, XCB_CW_EVENT_MASK,
1203 (uint32_t[]){XCB_EVENT_MASK_STRUCTURE_NOTIFY});
1204
1205 if (image_raw_format != NULL && image_path != NULL) {
1206 /* Read image. 'read_raw_image' returns NULL on error,
1207 * so we don't have to handle errors here. */
1208 img = read_raw_image(image_path, image_raw_format);
1209 } else if (verify_png_image(image_path)) {
1210 /* Create a pixmap to render on, fill it with the background color */
1211 img = cairo_image_surface_create_from_png(image_path);
1212 /* In case loading failed, we just pretend no -i was specified. */
1213 if (cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) {
1214 fprintf(stderr, "Could not load image \"%s\": %s\n",
1215 image_path, cairo_status_to_string(cairo_surface_status(img)));
1216 img = NULL;
1217 }
1218 }
1219
1220 free(image_path);
1221 free(image_raw_format);
1222
1223 /* Pixmap on which the image is rendered to (if any) */
1224 xcb_pixmap_t bg_pixmap = draw_image(last_resolution);
1225
1226 xcb_window_t stolen_focus = find_focused_window(conn, screen->root);
1227
1228 /* Open the fullscreen window, already with the correct pixmap in place */
1229 win = open_fullscreen_window(conn, screen, color, bg_pixmap);
1230 xcb_free_pixmap(conn, bg_pixmap);
1231
1232 cursor = create_cursor(conn, screen, win, curs_choice);
1233
1234 /* Display the "locking…" message while trying to grab the pointer/keyboard. */
1235 auth_state = STATE_AUTH_LOCK;
1236 if (!grab_pointer_and_keyboard(conn, screen, cursor, 1000)) {
1237 DEBUG("stole focus from X11 window 0x%08x\n", stolen_focus);
1238
1239 /* Set the focus to i3lock, possibly closing context menus which would
1240 * otherwise prevent us from grabbing keyboard/pointer.
1241 *
1242 * We cannot use set_focused_window because _NET_ACTIVE_WINDOW only
1243 * works for managed windows, but i3lock uses an unmanaged window
1244 * (override_redirect=1). */
1245 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT /* revert_to */, win, XCB_CURRENT_TIME);
1246 if (!grab_pointer_and_keyboard(conn, screen, cursor, 9000)) {
1247 auth_state = STATE_I3LOCK_LOCK_FAILED;
1248 redraw_screen();
1249 sleep(1);
1250 errx(EXIT_FAILURE, "Cannot grab pointer/keyboard");
1251 }
1252 }
1253
1254 pid_t pid = fork();
1255 /* The pid == -1 case is intentionally ignored here:
1256 * While the child process is useful for preventing other windows from
1257 * popping up while i3lock blocks, it is not critical. */
1258 if (pid == 0) {
1259 /* Child */
1260 close(xcb_get_file_descriptor(conn));
1261 maybe_close_sleep_lock_fd();
1262 raise_loop(win);
1263 exit(EXIT_SUCCESS);
1264 }
1265
1266 /* Load the keymap again to sync the current modifier state. Since we first
1267 * loaded the keymap, there might have been changes, but starting from now,
1268 * we should get all key presses/releases due to having grabbed the
1269 * keyboard. */
1270 (void)load_keymap();
1271
1272 /* Initialize the libev event loop. */
1273 main_loop = EV_DEFAULT;
1274 if (main_loop == NULL)
1275 errx(EXIT_FAILURE, "Could not initialize libev. Bad LIBEV_FLAGS?");
1276
1277 /* Explicitly call the screen redraw in case "locking…" message was displayed */
1278 auth_state = STATE_AUTH_IDLE;
1279 redraw_screen();
1280
1281 struct ev_io *xcb_watcher = calloc(sizeof(struct ev_io), 1);
1282 struct ev_check *xcb_check = calloc(sizeof(struct ev_check), 1);
1283 struct ev_prepare *xcb_prepare = calloc(sizeof(struct ev_prepare), 1);
1284
1285 ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
1286 ev_io_start(main_loop, xcb_watcher);
1287
1288 ev_check_init(xcb_check, xcb_check_cb);
1289 ev_check_start(main_loop, xcb_check);
1290
1291 ev_prepare_init(xcb_prepare, xcb_prepare_cb);
1292 ev_prepare_start(main_loop, xcb_prepare);
1293
1294 /* Invoke the event callback once to catch all the events which were
1295 * received up until now. ev will only pick up new events (when the X11
1296 * file descriptor becomes readable). */
1297 ev_invoke(main_loop, xcb_check, 0);
1298 ev_loop(main_loop, 0);
1299
1300#ifndef __OpenBSD__
1301 if (pam_cleanup) {
1302 pam_end(pam_handle, PAM_SUCCESS);
1303 }
1304#endif
1305
1306 if (stolen_focus == XCB_NONE) {
1307 return 0;
1308 }
1309
1310 DEBUG("restoring focus to X11 window 0x%08x\n", stolen_focus);
1311 xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
1312 xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
1313 xcb_destroy_window(conn, win);
1314 set_focused_window(conn, screen->root, stolen_focus);
1315 xcb_aux_sync(conn);
1316
1317 return 0;
1318}
diff --git a/i3lock.h b/i3lock.h
new file mode 100644
index 0000000..57190e5
--- /dev/null
+++ b/i3lock.h
@@ -0,0 +1,14 @@
1#ifndef _I3LOCK_H
2#define _I3LOCK_H
3
4/* This macro will only print debug output when started with --debug.
5 * This is important because xautolock (for example) closes stdout/stderr by
6 * default, so just printing something to stdout will lead to the data ending
7 * up on the X11 socket (!). */
8#define DEBUG(fmt, ...) \
9 do { \
10 if (debug_mode) \
11 printf("[i3lock-debug] " fmt, ##__VA_ARGS__); \
12 } while (0)
13
14#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 @@
1# ===========================================================================
2# http://www.gnu.org/software/autoconf-archive/ax_append_flag.html
3# ===========================================================================
4#
5# SYNOPSIS
6#
7# AX_APPEND_FLAG(FLAG, [FLAGS-VARIABLE])
8#
9# DESCRIPTION
10#
11# FLAG is appended to the FLAGS-VARIABLE shell variable, with a space
12# added in between.
13#
14# If FLAGS-VARIABLE is not specified, the current language's flags (e.g.
15# CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains
16# FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly
17# FLAG.
18#
19# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION.
20#
21# LICENSE
22#
23# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
24# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
25#
26# This program is free software: you can redistribute it and/or modify it
27# under the terms of the GNU General Public License as published by the
28# Free Software Foundation, either version 3 of the License, or (at your
29# option) any later version.
30#
31# This program is distributed in the hope that it will be useful, but
32# WITHOUT ANY WARRANTY; without even the implied warranty of
33# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
34# Public License for more details.
35#
36# You should have received a copy of the GNU General Public License along
37# with this program. If not, see <http://www.gnu.org/licenses/>.
38#
39# As a special exception, the respective Autoconf Macro's copyright owner
40# gives unlimited permission to copy, distribute and modify the configure
41# scripts that are the output of Autoconf when processing the Macro. You
42# need not follow the terms of the GNU General Public License when using
43# or distributing such scripts, even though portions of the text of the
44# Macro appear in them. The GNU General Public License (GPL) does govern
45# all other use of the material that constitutes the Autoconf Macro.
46#
47# This special exception to the GPL applies to versions of the Autoconf
48# Macro released by the Autoconf Archive. When you make and distribute a
49# modified version of the Autoconf Macro, you may extend this special
50# exception to the GPL to apply to your modified version as well.
51
52#serial 6
53
54AC_DEFUN([AX_APPEND_FLAG],
55[dnl
56AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF
57AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])])
58AS_VAR_SET_IF(FLAGS,[
59 AS_CASE([" AS_VAR_GET(FLAGS) "],
60 [*" $1 "*], [AC_RUN_LOG([: FLAGS already contains $1])],
61 [
62 AS_VAR_APPEND(FLAGS,[" $1"])
63 AC_RUN_LOG([: FLAGS="$FLAGS"])
64 ])
65 ],
66 [
67 AS_VAR_SET(FLAGS,[$1])
68 AC_RUN_LOG([: FLAGS="$FLAGS"])
69 ])
70AS_VAR_POPDEF([FLAGS])dnl
71])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 @@
1# ===========================================================================
2# http://www.gnu.org/software/autoconf-archive/ax_cflags_warn_all.html
3# ===========================================================================
4#
5# SYNOPSIS
6#
7# AX_CFLAGS_WARN_ALL [(shellvar [,default, [A/NA]])]
8# AX_CXXFLAGS_WARN_ALL [(shellvar [,default, [A/NA]])]
9# AX_FCFLAGS_WARN_ALL [(shellvar [,default, [A/NA]])]
10#
11# DESCRIPTION
12#
13# Try to find a compiler option that enables most reasonable warnings.
14#
15# For the GNU compiler it will be -Wall (and -ansi -pedantic) The result
16# is added to the shellvar being CFLAGS, CXXFLAGS, or FCFLAGS by default.
17#
18# Currently this macro knows about the GCC, Solaris, Digital Unix, AIX,
19# HP-UX, IRIX, NEC SX-5 (Super-UX 10), Cray J90 (Unicos 10.0.0.8), and
20# Intel compilers. For a given compiler, the Fortran flags are much more
21# experimental than their C equivalents.
22#
23# - $1 shell-variable-to-add-to : CFLAGS, CXXFLAGS, or FCFLAGS
24# - $2 add-value-if-not-found : nothing
25# - $3 action-if-found : add value to shellvariable
26# - $4 action-if-not-found : nothing
27#
28# NOTE: These macros depend on AX_APPEND_FLAG.
29#
30# LICENSE
31#
32# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
33# Copyright (c) 2010 Rhys Ulerich <rhys.ulerich@gmail.com>
34#
35# This program is free software; you can redistribute it and/or modify it
36# under the terms of the GNU General Public License as published by the
37# Free Software Foundation; either version 3 of the License, or (at your
38# option) any later version.
39#
40# This program is distributed in the hope that it will be useful, but
41# WITHOUT ANY WARRANTY; without even the implied warranty of
42# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
43# Public License for more details.
44#
45# You should have received a copy of the GNU General Public License along
46# with this program. If not, see <http://www.gnu.org/licenses/>.
47#
48# As a special exception, the respective Autoconf Macro's copyright owner
49# gives unlimited permission to copy, distribute and modify the configure
50# scripts that are the output of Autoconf when processing the Macro. You
51# need not follow the terms of the GNU General Public License when using
52# or distributing such scripts, even though portions of the text of the
53# Macro appear in them. The GNU General Public License (GPL) does govern
54# all other use of the material that constitutes the Autoconf Macro.
55#
56# This special exception to the GPL applies to versions of the Autoconf
57# Macro released by the Autoconf Archive. When you make and distribute a
58# modified version of the Autoconf Macro, you may extend this special
59# exception to the GPL to apply to your modified version as well.
60
61#serial 15
62
63AC_DEFUN([AX_FLAGS_WARN_ALL],[dnl
64AS_VAR_PUSHDEF([FLAGS],[_AC_LANG_PREFIX[]FLAGS])dnl
65AS_VAR_PUSHDEF([VAR],[ac_cv_[]_AC_LANG_ABBREV[]flags_warn_all])dnl
66AC_CACHE_CHECK([m4_ifval($1,$1,FLAGS) for maximum warnings],
67VAR,[VAR="no, unknown"
68ac_save_[]FLAGS="$[]FLAGS"
69for ac_arg dnl
70in "-warn all % -warn all" dnl Intel
71 "-pedantic % -Wall" dnl GCC
72 "-xstrconst % -v" dnl Solaris C
73 "-std1 % -verbose -w0 -warnprotos" dnl Digital Unix
74 "-qlanglvl=ansi % -qsrcmsg -qinfo=all:noppt:noppc:noobs:nocnd" dnl AIX
75 "-ansi -ansiE % -fullwarn" dnl IRIX
76 "+ESlit % +w1" dnl HP-UX C
77 "-Xc % -pvctl[,]fullmsg" dnl NEC SX-5 (Super-UX 10)
78 "-h conform % -h msglevel 2" dnl Cray C (Unicos)
79 #
80do FLAGS="$ac_save_[]FLAGS "`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'`
81 AC_COMPILE_IFELSE([AC_LANG_PROGRAM],
82 [VAR=`echo $ac_arg | sed -e 's,.*% *,,'` ; break])
83done
84FLAGS="$ac_save_[]FLAGS"
85])
86AS_VAR_POPDEF([FLAGS])dnl
87AX_REQUIRE_DEFINED([AX_APPEND_FLAG])
88case ".$VAR" in
89 .ok|.ok,*) m4_ifvaln($3,$3) ;;
90 .|.no|.no,*) m4_default($4,[m4_ifval($2,[AX_APPEND_FLAG([$2], [$1])])]) ;;
91 *) m4_default($3,[AX_APPEND_FLAG([$VAR], [$1])]) ;;
92esac
93AS_VAR_POPDEF([VAR])dnl
94])dnl AX_FLAGS_WARN_ALL
95dnl implementation tactics:
96dnl the for-argument contains a list of options. The first part of
97dnl these does only exist to detect the compiler - usually it is
98dnl a global option to enable -ansi or -extrawarnings. All other
99dnl compilers will fail about it. That was needed since a lot of
100dnl compilers will give false positives for some option-syntax
101dnl like -Woption or -Xoption as they think of it is a pass-through
102dnl to later compile stages or something. The "%" is used as a
103dnl delimiter. A non-option comment can be given after "%%" marks
104dnl which will be shown but not added to the respective C/CXXFLAGS.
105
106AC_DEFUN([AX_CFLAGS_WARN_ALL],[dnl
107AC_LANG_PUSH([C])
108AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4])
109AC_LANG_POP([C])
110])
111
112AC_DEFUN([AX_CXXFLAGS_WARN_ALL],[dnl
113AC_LANG_PUSH([C++])
114AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4])
115AC_LANG_POP([C++])
116])
117
118AC_DEFUN([AX_FCFLAGS_WARN_ALL],[dnl
119AC_LANG_PUSH([Fortran])
120AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4])
121AC_LANG_POP([Fortran])
122])
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 @@
1# ===========================================================================
2# http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
3# ===========================================================================
4#
5# SYNOPSIS
6#
7# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
8#
9# DESCRIPTION
10#
11# Check whether the given FLAG works with the current language's compiler
12# or gives an error. (Warnings, however, are ignored)
13#
14# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
15# success/failure.
16#
17# If EXTRA-FLAGS is defined, it is added to the current language's default
18# flags (e.g. CFLAGS) when the check is done. The check is thus made with
19# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to
20# force the compiler to issue an error when a bad flag is given.
21#
22# INPUT gives an alternative input source to AC_COMPILE_IFELSE.
23#
24# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
25# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
26#
27# LICENSE
28#
29# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
30# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
31#
32# This program is free software: you can redistribute it and/or modify it
33# under the terms of the GNU General Public License as published by the
34# Free Software Foundation, either version 3 of the License, or (at your
35# option) any later version.
36#
37# This program is distributed in the hope that it will be useful, but
38# WITHOUT ANY WARRANTY; without even the implied warranty of
39# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
40# Public License for more details.
41#
42# You should have received a copy of the GNU General Public License along
43# with this program. If not, see <http://www.gnu.org/licenses/>.
44#
45# As a special exception, the respective Autoconf Macro's copyright owner
46# gives unlimited permission to copy, distribute and modify the configure
47# scripts that are the output of Autoconf when processing the Macro. You
48# need not follow the terms of the GNU General Public License when using
49# or distributing such scripts, even though portions of the text of the
50# Macro appear in them. The GNU General Public License (GPL) does govern
51# all other use of the material that constitutes the Autoconf Macro.
52#
53# This special exception to the GPL applies to versions of the Autoconf
54# Macro released by the Autoconf Archive. When you make and distribute a
55# modified version of the Autoconf Macro, you may extend this special
56# exception to the GPL to apply to your modified version as well.
57
58#serial 4
59
60AC_DEFUN([AX_CHECK_COMPILE_FLAG],
61[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
62AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
63AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
64 ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
65 _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
66 AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
67 [AS_VAR_SET(CACHEVAR,[yes])],
68 [AS_VAR_SET(CACHEVAR,[no])])
69 _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
70AS_VAR_IF(CACHEVAR,yes,
71 [m4_default([$2], :)],
72 [m4_default([$3], :)])
73AS_VAR_POPDEF([CACHEVAR])dnl
74])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 @@
1# ===========================================================================
2# http://www.gnu.org/software/autoconf-archive/ax_check_enable_debug.html
3# ===========================================================================
4#
5# SYNOPSIS
6#
7# AX_CHECK_ENABLE_DEBUG([enable by default=yes/info/profile/no], [ENABLE DEBUG VARIABLES ...], [DISABLE DEBUG VARIABLES NDEBUG ...], [IS-RELEASE])
8#
9# DESCRIPTION
10#
11# Check for the presence of an --enable-debug option to configure, with
12# the specified default value used when the option is not present. Return
13# the value in the variable $ax_enable_debug.
14#
15# Specifying 'yes' adds '-g -O0' to the compilation flags for all
16# languages. Specifying 'info' adds '-g' to the compilation flags.
17# Specifying 'profile' adds '-g -pg' to the compilation flags and '-pg' to
18# the linking flags. Otherwise, nothing is added.
19#
20# Define the variables listed in the second argument if debug is enabled,
21# defaulting to no variables. Defines the variables listed in the third
22# argument if debug is disabled, defaulting to NDEBUG. All lists of
23# variables should be space-separated.
24#
25# If debug is not enabled, ensure AC_PROG_* will not add debugging flags.
26# Should be invoked prior to any AC_PROG_* compiler checks.
27#
28# IS-RELEASE can be used to change the default to 'no' when making a
29# release. Set IS-RELEASE to 'yes' or 'no' as appropriate. By default, it
30# uses the value of $ax_is_release, so if you are using the AX_IS_RELEASE
31# macro, there is no need to pass this parameter.
32#
33# AX_IS_RELEASE([git-directory])
34# AX_CHECK_ENABLE_DEBUG()
35#
36# LICENSE
37#
38# Copyright (c) 2011 Rhys Ulerich <rhys.ulerich@gmail.com>
39# Copyright (c) 2014, 2015 Philip Withnall <philip@tecnocode.co.uk>
40#
41# Copying and distribution of this file, with or without modification, are
42# permitted in any medium without royalty provided the copyright notice
43# and this notice are preserved.
44
45#serial 5
46
47AC_DEFUN([AX_CHECK_ENABLE_DEBUG],[
48 AC_BEFORE([$0],[AC_PROG_CC])dnl
49 AC_BEFORE([$0],[AC_PROG_CXX])dnl
50 AC_BEFORE([$0],[AC_PROG_F77])dnl
51 AC_BEFORE([$0],[AC_PROG_FC])dnl
52
53 AC_MSG_CHECKING(whether to enable debugging)
54
55 ax_enable_debug_default=m4_tolower(m4_normalize(ifelse([$1],,[no],[$1])))
56 ax_enable_debug_is_release=m4_tolower(m4_normalize(ifelse([$4],,
57 [$ax_is_release],
58 [$4])))
59
60 # If this is a release, override the default.
61 AS_IF([test "$ax_enable_debug_is_release" = "yes"],
62 [ax_enable_debug_default="no"])
63
64 m4_define(ax_enable_debug_vars,[m4_normalize(ifelse([$2],,,[$2]))])
65 m4_define(ax_disable_debug_vars,[m4_normalize(ifelse([$3],,[NDEBUG],[$3]))])
66
67 AC_ARG_ENABLE(debug,
68 [AS_HELP_STRING([--enable-debug=]@<:@yes/info/profile/no@:>@,[compile with debugging])],
69 [],enable_debug=$ax_enable_debug_default)
70
71 # empty mean debug yes
72 AS_IF([test "x$enable_debug" = "x"],
73 [enable_debug="yes"])
74
75 # case of debug
76 AS_CASE([$enable_debug],
77 [yes],[
78 AC_MSG_RESULT(yes)
79 CFLAGS="${CFLAGS} -g -O0"
80 CXXFLAGS="${CXXFLAGS} -g -O0"
81 FFLAGS="${FFLAGS} -g -O0"
82 FCFLAGS="${FCFLAGS} -g -O0"
83 OBJCFLAGS="${OBJCFLAGS} -g -O0"
84 ],
85 [info],[
86 AC_MSG_RESULT(info)
87 CFLAGS="${CFLAGS} -g"
88 CXXFLAGS="${CXXFLAGS} -g"
89 FFLAGS="${FFLAGS} -g"
90 FCFLAGS="${FCFLAGS} -g"
91 OBJCFLAGS="${OBJCFLAGS} -g"
92 ],
93 [profile],[
94 AC_MSG_RESULT(profile)
95 CFLAGS="${CFLAGS} -g -pg"
96 CXXFLAGS="${CXXFLAGS} -g -pg"
97 FFLAGS="${FFLAGS} -g -pg"
98 FCFLAGS="${FCFLAGS} -g -pg"
99 OBJCFLAGS="${OBJCFLAGS} -g -pg"
100 LDFLAGS="${LDFLAGS} -pg"
101 ],
102 [
103 AC_MSG_RESULT(no)
104 dnl Ensure AC_PROG_CC/CXX/F77/FC/OBJC will not enable debug flags
105 dnl by setting any unset environment flag variables
106 AS_IF([test "x${CFLAGS+set}" != "xset"],
107 [CFLAGS=""])
108 AS_IF([test "x${CXXFLAGS+set}" != "xset"],
109 [CXXFLAGS=""])
110 AS_IF([test "x${FFLAGS+set}" != "xset"],
111 [FFLAGS=""])
112 AS_IF([test "x${FCFLAGS+set}" != "xset"],
113 [FCFLAGS=""])
114 AS_IF([test "x${OBJCFLAGS+set}" != "xset"],
115 [OBJCFLAGS=""])
116 ])
117
118 dnl Define various variables if debugging is disabled.
119 dnl assert.h is a NOP if NDEBUG is defined, so define it by default.
120 AS_IF([test "x$enable_debug" = "xyes"],
121 [m4_map_args_w(ax_enable_debug_vars, [AC_DEFINE(], [,,[Define if debugging is enabled])])],
122 [m4_map_args_w(ax_disable_debug_vars, [AC_DEFINE(], [,,[Define if debugging is disabled])])])
123 ax_enable_debug=$enable_debug
124])
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 @@
1# ===========================================================================
2# http://www.gnu.org/software/autoconf-archive/ax_check_gnu_make.html
3# ===========================================================================
4#
5# SYNOPSIS
6#
7# AX_CHECK_GNU_MAKE()
8#
9# DESCRIPTION
10#
11# This macro searches for a GNU version of make. If a match is found:
12#
13# * The makefile variable `ifGNUmake' is set to the empty string, otherwise
14# it is set to "#". This is useful for including a special features in a
15# Makefile, which cannot be handled by other versions of make.
16# * The variable `_cv_gnu_make_command` is set to the command to invoke
17# GNU make if it exists, the empty string otherwise.
18# * The variable `ax_cv_gnu_make_command` is set to the command to invoke
19# GNU make by copying `_cv_gnu_make_command`, otherwise it is unset.
20# * If GNU Make is found, its version is extracted from the output of
21# `make --version` as the last field of a record of space-separated
22# columns and saved into the variable `ax_check_gnu_make_version`.
23#
24# Here is an example of its use:
25#
26# Makefile.in might contain:
27#
28# # A failsafe way of putting a dependency rule into a makefile
29# $(DEPEND):
30# $(CC) -MM $(srcdir)/*.c > $(DEPEND)
31#
32# @ifGNUmake@ ifeq ($(DEPEND),$(wildcard $(DEPEND)))
33# @ifGNUmake@ include $(DEPEND)
34# @ifGNUmake@ endif
35#
36# Then configure.in would normally contain:
37#
38# AX_CHECK_GNU_MAKE()
39# AC_OUTPUT(Makefile)
40#
41# Then perhaps to cause gnu make to override any other make, we could do
42# something like this (note that GNU make always looks for GNUmakefile
43# first):
44#
45# if ! test x$_cv_gnu_make_command = x ; then
46# mv Makefile GNUmakefile
47# echo .DEFAULT: > Makefile ;
48# echo \ $_cv_gnu_make_command \$@ >> Makefile;
49# fi
50#
51# Then, if any (well almost any) other make is called, and GNU make also
52# exists, then the other make wraps the GNU make.
53#
54# LICENSE
55#
56# Copyright (c) 2008 John Darrington <j.darrington@elvis.murdoch.edu.au>
57# Copyright (c) 2015 Enrico M. Crisostomo <enrico.m.crisostomo@gmail.com>
58#
59# Copying and distribution of this file, with or without modification, are
60# permitted in any medium without royalty provided the copyright notice
61# and this notice are preserved. This file is offered as-is, without any
62# warranty.
63
64#serial 8
65
66AC_DEFUN([AX_CHECK_GNU_MAKE],dnl
67 [AC_PROG_AWK
68 AC_CACHE_CHECK([for GNU make],[_cv_gnu_make_command],[dnl
69 _cv_gnu_make_command="" ;
70dnl Search all the common names for GNU make
71 for a in "$MAKE" make gmake gnumake ; do
72 if test -z "$a" ; then continue ; fi ;
73 if "$a" --version 2> /dev/null | grep GNU 2>&1 > /dev/null ; then
74 _cv_gnu_make_command=$a ;
75 AX_CHECK_GNU_MAKE_HEADLINE=$("$a" --version 2> /dev/null | grep "GNU Make")
76 ax_check_gnu_make_version=$(echo ${AX_CHECK_GNU_MAKE_HEADLINE} | ${AWK} -F " " '{ print $(NF); }')
77 break ;
78 fi
79 done ;])
80dnl If there was a GNU version, then set @ifGNUmake@ to the empty string, '#' otherwise
81 AS_VAR_IF([_cv_gnu_make_command], [""], [AS_VAR_SET([ifGNUmake], ["#"])], [AS_VAR_SET([ifGNUmake], [""])])
82 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}])])
83 AC_SUBST([ifGNUmake])
84])
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 @@
1# ===========================================================================
2# http://www.gnu.org/software/autoconf-archive/ax_check_link_flag.html
3# ===========================================================================
4#
5# SYNOPSIS
6#
7# AX_CHECK_LINK_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
8#
9# DESCRIPTION
10#
11# Check whether the given FLAG works with the linker or gives an error.
12# (Warnings, however, are ignored)
13#
14# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
15# success/failure.
16#
17# If EXTRA-FLAGS is defined, it is added to the linker's default flags
18# when the check is done. The check is thus made with the flags: "LDFLAGS
19# EXTRA-FLAGS FLAG". This can for example be used to force the linker to
20# issue an error when a bad flag is given.
21#
22# INPUT gives an alternative input source to AC_LINK_IFELSE.
23#
24# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
25# macro in sync with AX_CHECK_{PREPROC,COMPILE}_FLAG.
26#
27# LICENSE
28#
29# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
30# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
31#
32# This program is free software: you can redistribute it and/or modify it
33# under the terms of the GNU General Public License as published by the
34# Free Software Foundation, either version 3 of the License, or (at your
35# option) any later version.
36#
37# This program is distributed in the hope that it will be useful, but
38# WITHOUT ANY WARRANTY; without even the implied warranty of
39# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
40# Public License for more details.
41#
42# You should have received a copy of the GNU General Public License along
43# with this program. If not, see <http://www.gnu.org/licenses/>.
44#
45# As a special exception, the respective Autoconf Macro's copyright owner
46# gives unlimited permission to copy, distribute and modify the configure
47# scripts that are the output of Autoconf when processing the Macro. You
48# need not follow the terms of the GNU General Public License when using
49# or distributing such scripts, even though portions of the text of the
50# Macro appear in them. The GNU General Public License (GPL) does govern
51# all other use of the material that constitutes the Autoconf Macro.
52#
53# This special exception to the GPL applies to versions of the Autoconf
54# Macro released by the Autoconf Archive. When you make and distribute a
55# modified version of the Autoconf Macro, you may extend this special
56# exception to the GPL to apply to your modified version as well.
57
58#serial 4
59
60AC_DEFUN([AX_CHECK_LINK_FLAG],
61[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
62AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_ldflags_$4_$1])dnl
63AC_CACHE_CHECK([whether the linker accepts $1], CACHEVAR, [
64 ax_check_save_flags=$LDFLAGS
65 LDFLAGS="$LDFLAGS $4 $1"
66 AC_LINK_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
67 [AS_VAR_SET(CACHEVAR,[yes])],
68 [AS_VAR_SET(CACHEVAR,[no])])
69 LDFLAGS=$ax_check_save_flags])
70AS_VAR_IF(CACHEVAR,yes,
71 [m4_default([$2], :)],
72 [m4_default([$3], :)])
73AS_VAR_POPDEF([CACHEVAR])dnl
74])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 @@
1# ===========================================================================
2# http://www.gnu.org/software/autoconf-archive/ax_code_coverage.html
3# ===========================================================================
4#
5# SYNOPSIS
6#
7# AX_CODE_COVERAGE()
8#
9# DESCRIPTION
10#
11# Defines CODE_COVERAGE_CPPFLAGS, CODE_COVERAGE_CFLAGS,
12# CODE_COVERAGE_CXXFLAGS and CODE_COVERAGE_LDFLAGS which should be
13# included in the CPPFLAGS, CFLAGS CXXFLAGS and LIBS/LDFLAGS variables of
14# every build target (program or library) which should be built with code
15# coverage support. Also defines CODE_COVERAGE_RULES which should be
16# substituted in your Makefile; and $enable_code_coverage which can be
17# used in subsequent configure output. CODE_COVERAGE_ENABLED is defined
18# and substituted, and corresponds to the value of the
19# --enable-code-coverage option, which defaults to being disabled.
20#
21# Test also for gcov program and create GCOV variable that could be
22# substituted.
23#
24# Note that all optimisation flags in CFLAGS must be disabled when code
25# coverage is enabled.
26#
27# Usage example:
28#
29# configure.ac:
30#
31# AX_CODE_COVERAGE
32#
33# Makefile.am:
34#
35# @CODE_COVERAGE_RULES@
36# my_program_LIBS = ... $(CODE_COVERAGE_LDFLAGS) ...
37# my_program_CPPFLAGS = ... $(CODE_COVERAGE_CPPFLAGS) ...
38# my_program_CFLAGS = ... $(CODE_COVERAGE_CFLAGS) ...
39# my_program_CXXFLAGS = ... $(CODE_COVERAGE_CXXFLAGS) ...
40#
41# This results in a "check-code-coverage" rule being added to any
42# Makefile.am which includes "@CODE_COVERAGE_RULES@" (assuming the module
43# has been configured with --enable-code-coverage). Running `make
44# check-code-coverage` in that directory will run the module's test suite
45# (`make check`) and build a code coverage report detailing the code which
46# was touched, then print the URI for the report.
47#
48# This code was derived from Makefile.decl in GLib, originally licenced
49# under LGPLv2.1+.
50#
51# LICENSE
52#
53# Copyright (c) 2012, 2016 Philip Withnall
54# Copyright (c) 2012 Xan Lopez
55# Copyright (c) 2012 Christian Persch
56# Copyright (c) 2012 Paolo Borelli
57# Copyright (c) 2012 Dan Winship
58# Copyright (c) 2015 Bastien ROUCARIES
59#
60# This library is free software; you can redistribute it and/or modify it
61# under the terms of the GNU Lesser General Public License as published by
62# the Free Software Foundation; either version 2.1 of the License, or (at
63# your option) any later version.
64#
65# This library is distributed in the hope that it will be useful, but
66# WITHOUT ANY WARRANTY; without even the implied warranty of
67# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
68# General Public License for more details.
69#
70# You should have received a copy of the GNU Lesser General Public License
71# along with this program. If not, see <http://www.gnu.org/licenses/>.
72
73#serial 15
74
75AC_DEFUN([AX_CODE_COVERAGE],[
76 dnl Check for --enable-code-coverage
77 AC_REQUIRE([AC_PROG_SED])
78
79 # allow to override gcov location
80 AC_ARG_WITH([gcov],
81 [AS_HELP_STRING([--with-gcov[=GCOV]], [use given GCOV for coverage (GCOV=gcov).])],
82 [_AX_CODE_COVERAGE_GCOV_PROG_WITH=$with_gcov],
83 [_AX_CODE_COVERAGE_GCOV_PROG_WITH=gcov])
84
85 AC_MSG_CHECKING([whether to build with code coverage support])
86 AC_ARG_ENABLE([code-coverage],
87 AS_HELP_STRING([--enable-code-coverage],
88 [Whether to enable code coverage support]),,
89 enable_code_coverage=no)
90
91 AM_CONDITIONAL([CODE_COVERAGE_ENABLED], [test x$enable_code_coverage = xyes])
92 AC_SUBST([CODE_COVERAGE_ENABLED], [$enable_code_coverage])
93 AC_MSG_RESULT($enable_code_coverage)
94
95 AS_IF([ test "$enable_code_coverage" = "yes" ], [
96 # check for gcov
97 AC_CHECK_TOOL([GCOV],
98 [$_AX_CODE_COVERAGE_GCOV_PROG_WITH],
99 [:])
100 AS_IF([test "X$GCOV" = "X:"],
101 [AC_MSG_ERROR([gcov is needed to do coverage])])
102 AC_SUBST([GCOV])
103
104 dnl Check if gcc is being used
105 AS_IF([ test "$GCC" = "no" ], [
106 AC_MSG_ERROR([not compiling with gcc, which is required for gcov code coverage])
107 ])
108
109 # List of supported lcov versions.
110 lcov_version_list="1.6 1.7 1.8 1.9 1.10 1.11 1.12"
111
112 AC_CHECK_PROG([LCOV], [lcov], [lcov])
113 AC_CHECK_PROG([GENHTML], [genhtml], [genhtml])
114
115 AS_IF([ test "$LCOV" ], [
116 AC_CACHE_CHECK([for lcov version], ax_cv_lcov_version, [
117 ax_cv_lcov_version=invalid
118 lcov_version=`$LCOV -v 2>/dev/null | $SED -e 's/^.* //'`
119 for lcov_check_version in $lcov_version_list; do
120 if test "$lcov_version" = "$lcov_check_version"; then
121 ax_cv_lcov_version="$lcov_check_version (ok)"
122 fi
123 done
124 ])
125 ], [
126 lcov_msg="To enable code coverage reporting you must have one of the following lcov versions installed: $lcov_version_list"
127 AC_MSG_ERROR([$lcov_msg])
128 ])
129
130 case $ax_cv_lcov_version in
131 ""|invalid[)]
132 lcov_msg="You must have one of the following versions of lcov: $lcov_version_list (found: $lcov_version)."
133 AC_MSG_ERROR([$lcov_msg])
134 LCOV="exit 0;"
135 ;;
136 esac
137
138 AS_IF([ test -z "$GENHTML" ], [
139 AC_MSG_ERROR([Could not find genhtml from the lcov package])
140 ])
141
142 dnl Build the code coverage flags
143 CODE_COVERAGE_CPPFLAGS="-DNDEBUG"
144 CODE_COVERAGE_CFLAGS="-O0 -g -fprofile-arcs -ftest-coverage"
145 CODE_COVERAGE_CXXFLAGS="-O0 -g -fprofile-arcs -ftest-coverage"
146 CODE_COVERAGE_LDFLAGS="-lgcov"
147
148 AC_SUBST([CODE_COVERAGE_CPPFLAGS])
149 AC_SUBST([CODE_COVERAGE_CFLAGS])
150 AC_SUBST([CODE_COVERAGE_CXXFLAGS])
151 AC_SUBST([CODE_COVERAGE_LDFLAGS])
152 ])
153
154[CODE_COVERAGE_RULES='
155# Code coverage
156#
157# Optional:
158# - CODE_COVERAGE_DIRECTORY: Top-level directory for code coverage reporting.
159# Multiple directories may be specified, separated by whitespace.
160# (Default: $(top_builddir))
161# - CODE_COVERAGE_OUTPUT_FILE: Filename and path for the .info file generated
162# by lcov for code coverage. (Default:
163# $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info)
164# - CODE_COVERAGE_OUTPUT_DIRECTORY: Directory for generated code coverage
165# reports to be created. (Default:
166# $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage)
167# - CODE_COVERAGE_BRANCH_COVERAGE: Set to 1 to enforce branch coverage,
168# set to 0 to disable it and leave empty to stay with the default.
169# (Default: empty)
170# - CODE_COVERAGE_LCOV_SHOPTS_DEFAULT: Extra options shared between both lcov
171# instances. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE)
172# - CODE_COVERAGE_LCOV_SHOPTS: Extra options to shared between both lcov
173# instances. (Default: $CODE_COVERAGE_LCOV_SHOPTS_DEFAULT)
174# - CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH: --gcov-tool pathtogcov
175# - CODE_COVERAGE_LCOV_OPTIONS_DEFAULT: Extra options to pass to the
176# collecting lcov instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH)
177# - CODE_COVERAGE_LCOV_OPTIONS: Extra options to pass to the collecting lcov
178# instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_DEFAULT)
179# - CODE_COVERAGE_LCOV_RMOPTS_DEFAULT: Extra options to pass to the filtering
180# lcov instance. (Default: empty)
181# - CODE_COVERAGE_LCOV_RMOPTS: Extra options to pass to the filtering lcov
182# instance. (Default: $CODE_COVERAGE_LCOV_RMOPTS_DEFAULT)
183# - CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT: Extra options to pass to the
184# genhtml instance. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE)
185# - CODE_COVERAGE_GENHTML_OPTIONS: Extra options to pass to the genhtml
186# instance. (Default: $CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT)
187# - CODE_COVERAGE_IGNORE_PATTERN: Extra glob pattern of files to ignore
188#
189# The generated report will be titled using the $(PACKAGE_NAME) and
190# $(PACKAGE_VERSION). In order to add the current git hash to the title,
191# use the git-version-gen script, available online.
192
193# Optional variables
194CODE_COVERAGE_DIRECTORY ?= $(top_builddir)
195CODE_COVERAGE_OUTPUT_FILE ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info
196CODE_COVERAGE_OUTPUT_DIRECTORY ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage
197CODE_COVERAGE_BRANCH_COVERAGE ?=
198CODE_COVERAGE_LCOV_SHOPTS_DEFAULT ?= $(if $(CODE_COVERAGE_BRANCH_COVERAGE),\
199--rc lcov_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE))
200CODE_COVERAGE_LCOV_SHOPTS ?= $(CODE_COVERAGE_LCOV_SHOPTS_DEFAULT)
201CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH ?= --gcov-tool "$(GCOV)"
202CODE_COVERAGE_LCOV_OPTIONS_DEFAULT ?= $(CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH)
203CODE_COVERAGE_LCOV_OPTIONS ?= $(CODE_COVERAGE_LCOV_OPTIONS_DEFAULT)
204CODE_COVERAGE_LCOV_RMOPTS_DEFAULT ?=
205CODE_COVERAGE_LCOV_RMOPTS ?= $(CODE_COVERAGE_LCOV_RMOPTS_DEFAULT)
206CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT ?=\
207$(if $(CODE_COVERAGE_BRANCH_COVERAGE),\
208--rc genhtml_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE))
209CODE_COVERAGE_GENHTML_OPTIONS ?= $(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULTS)
210CODE_COVERAGE_IGNORE_PATTERN ?=
211
212code_coverage_v_lcov_cap = $(code_coverage_v_lcov_cap_$(V))
213code_coverage_v_lcov_cap_ = $(code_coverage_v_lcov_cap_$(AM_DEFAULT_VERBOSITY))
214code_coverage_v_lcov_cap_0 = @echo " LCOV --capture"\
215 $(CODE_COVERAGE_OUTPUT_FILE);
216code_coverage_v_lcov_ign = $(code_coverage_v_lcov_ign_$(V))
217code_coverage_v_lcov_ign_ = $(code_coverage_v_lcov_ign_$(AM_DEFAULT_VERBOSITY))
218code_coverage_v_lcov_ign_0 = @echo " LCOV --remove /tmp/*"\
219 $(CODE_COVERAGE_IGNORE_PATTERN);
220code_coverage_v_genhtml = $(code_coverage_v_genhtml_$(V))
221code_coverage_v_genhtml_ = $(code_coverage_v_genhtml_$(AM_DEFAULT_VERBOSITY))
222code_coverage_v_genhtml_0 = @echo " GEN " $(CODE_COVERAGE_OUTPUT_DIRECTORY);
223code_coverage_quiet = $(code_coverage_quiet_$(V))
224code_coverage_quiet_ = $(code_coverage_quiet_$(AM_DEFAULT_VERBOSITY))
225code_coverage_quiet_0 = --quiet
226
227# sanitizes the test-name: replaces with underscores: dashes and dots
228code_coverage_sanitize = $(subst -,_,$(subst .,_,$(1)))
229
230# Use recursive makes in order to ignore errors during check
231check-code-coverage:
232ifeq ($(CODE_COVERAGE_ENABLED),yes)
233 -$(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) -k check
234 $(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) code-coverage-capture
235else
236 @echo "Need to reconfigure with --enable-code-coverage"
237endif
238
239# Capture code coverage data
240code-coverage-capture: code-coverage-capture-hook
241ifeq ($(CODE_COVERAGE_ENABLED),yes)
242 $(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)
243 $(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)
244 -@rm -f $(CODE_COVERAGE_OUTPUT_FILE).tmp
245 $(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)
246 @echo "file://$(abs_builddir)/$(CODE_COVERAGE_OUTPUT_DIRECTORY)/index.html"
247else
248 @echo "Need to reconfigure with --enable-code-coverage"
249endif
250
251# Hook rule executed before code-coverage-capture, overridable by the user
252code-coverage-capture-hook:
253
254ifeq ($(CODE_COVERAGE_ENABLED),yes)
255clean: code-coverage-clean
256code-coverage-clean:
257 -$(LCOV) --directory $(top_builddir) -z
258 -rm -rf $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_FILE).tmp $(CODE_COVERAGE_OUTPUT_DIRECTORY)
259 -find . -name "*.gcda" -o -name "*.gcov" -delete
260endif
261
262GITIGNOREFILES ?=
263GITIGNOREFILES += $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_DIRECTORY)
264
265A''M_DISTCHECK_CONFIGURE_FLAGS ?=
266A''M_DISTCHECK_CONFIGURE_FLAGS += --disable-code-coverage
267
268.PHONY: check-code-coverage code-coverage-capture code-coverage-capture-hook code-coverage-clean
269']
270
271 AC_SUBST([CODE_COVERAGE_RULES])
272 m4_ifdef([_AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE([CODE_COVERAGE_RULES])])
273])
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 @@
1# ===========================================================================
2# http://www.gnu.org/software/autoconf-archive/ax_configure_args.html
3# ===========================================================================
4#
5# SYNOPSIS
6#
7# AX_CONFIGURE_ARGS
8#
9# DESCRIPTION
10#
11# Helper macro for AX_ENABLE_BUILDDIR.
12#
13# The traditional way of starting a subdir-configure is running the script
14# with ${1+"$@"} but since autoconf 2.60 this is broken. Instead we have
15# to rely on eval'ing $ac_configure_args however some old autoconf
16# versions do not provide that. To ensure maximum portability of autoconf
17# extension macros this helper can be AC_REQUIRE'd so that
18# $ac_configure_args will alsways be present.
19#
20# Sadly, the traditional "exec $SHELL" of the enable_builddir macros is
21# spoiled now and must be replaced by "eval + exit $?".
22#
23# Example:
24#
25# AC_DEFUN([AX_ENABLE_SUBDIR],[dnl
26# AC_REQUIRE([AX_CONFIGURE_ARGS])dnl
27# eval $SHELL $ac_configure_args || exit $?
28# ...])
29#
30# LICENSE
31#
32# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
33#
34# This program is free software; you can redistribute it and/or modify it
35# under the terms of the GNU General Public License as published by the
36# Free Software Foundation; either version 3 of the License, or (at your
37# option) any later version.
38#
39# This program is distributed in the hope that it will be useful, but
40# WITHOUT ANY WARRANTY; without even the implied warranty of
41# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
42# Public License for more details.
43#
44# You should have received a copy of the GNU General Public License along
45# with this program. If not, see <http://www.gnu.org/licenses/>.
46#
47# As a special exception, the respective Autoconf Macro's copyright owner
48# gives unlimited permission to copy, distribute and modify the configure
49# scripts that are the output of Autoconf when processing the Macro. You
50# need not follow the terms of the GNU General Public License when using
51# or distributing such scripts, even though portions of the text of the
52# Macro appear in them. The GNU General Public License (GPL) does govern
53# all other use of the material that constitutes the Autoconf Macro.
54#
55# This special exception to the GPL applies to versions of the Autoconf
56# Macro released by the Autoconf Archive. When you make and distribute a
57# modified version of the Autoconf Macro, you may extend this special
58# exception to the GPL to apply to your modified version as well.
59
60#serial 9
61
62AC_DEFUN([AX_CONFIGURE_ARGS],[
63 # [$]@ is unsable in 2.60+ but earlier autoconf had no ac_configure_args
64 if test "${ac_configure_args+set}" != "set" ; then
65 ac_configure_args=
66 for ac_arg in ${1+"[$]@"}; do
67 ac_configure_args="$ac_configure_args '$ac_arg'"
68 done
69 fi
70])
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 @@
1# ===========================================================================
2# http://www.gnu.org/software/autoconf-archive/ax_enable_builddir.html
3# ===========================================================================
4#
5# SYNOPSIS
6#
7# AX_ENABLE_BUILDDIR [(dirstring-or-command [,Makefile.mk [,-all]])]
8#
9# DESCRIPTION
10#
11# If the current configure was run within the srcdir then we move all
12# configure-files into a subdir and let the configure steps continue
13# there. We provide an option --disable-builddir to suppress the move into
14# a separate builddir.
15#
16# Defaults:
17#
18# $1 = $host (overridden with $HOST)
19# $2 = Makefile.mk
20# $3 = -all
21#
22# This macro must be called before AM_INIT_AUTOMAKE. It creates a default
23# toplevel srcdir Makefile from the information found in the created
24# toplevel builddir Makefile. It just copies the variables and
25# rule-targets, each extended with a default rule-execution that recurses
26# into the build directory of the current "HOST". You can override the
27# auto-dection through `config.guess` and build-time of course, as in
28#
29# make HOST=i386-mingw-cross
30#
31# which can of course set at configure time as well using
32#
33# configure --host=i386-mingw-cross
34#
35# After the default has been created, additional rules can be appended
36# that will not just recurse into the subdirectories and only ever exist
37# in the srcdir toplevel makefile - these parts are read from the $2 =
38# Makefile.mk file
39#
40# The automatic rules are usually scanning the toplevel Makefile for lines
41# like '#### $host |$builddir' to recognize the place where to recurse
42# into. Usually, the last one is the only one used. However, almost all
43# targets have an additional "*-all" rule which makes the script to
44# recurse into _all_ variants of the current HOST (!!) setting. The "-all"
45# suffix can be overriden for the macro as well.
46#
47# a special rule is only given for things like "dist" that will copy the
48# tarball from the builddir to the sourcedir (or $(PUB)) for reason of
49# convenience.
50#
51# LICENSE
52#
53# Copyright (c) 2009 Guido U. Draheim <guidod@gmx.de>
54# Copyright (c) 2009 Alan Jenkins <alan-jenkins@tuffmail.co.uk>
55#
56# This program is free software; you can redistribute it and/or modify it
57# under the terms of the GNU General Public License as published by the
58# Free Software Foundation; either version 3 of the License, or (at your
59# option) any later version.
60#
61# This program is distributed in the hope that it will be useful, but
62# WITHOUT ANY WARRANTY; without even the implied warranty of
63# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
64# Public License for more details.
65#
66# You should have received a copy of the GNU General Public License along
67# with this program. If not, see <http://www.gnu.org/licenses/>.
68#
69# As a special exception, the respective Autoconf Macro's copyright owner
70# gives unlimited permission to copy, distribute and modify the configure
71# scripts that are the output of Autoconf when processing the Macro. You
72# need not follow the terms of the GNU General Public License when using
73# or distributing such scripts, even though portions of the text of the
74# Macro appear in them. The GNU General Public License (GPL) does govern
75# all other use of the material that constitutes the Autoconf Macro.
76#
77# This special exception to the GPL applies to versions of the Autoconf
78# Macro released by the Autoconf Archive. When you make and distribute a
79# modified version of the Autoconf Macro, you may extend this special
80# exception to the GPL to apply to your modified version as well.
81
82#serial 25
83
84AC_DEFUN([AX_ENABLE_BUILDDIR],[
85AC_REQUIRE([AC_CANONICAL_HOST])[]dnl
86AC_REQUIRE([AC_CANONICAL_TARGET])[]dnl
87AC_REQUIRE([AX_CONFIGURE_ARGS])[]dnl
88AC_REQUIRE([AM_AUX_DIR_EXPAND])[]dnl
89AC_BEFORE([$0],[AM_INIT_AUTOMAKE])dnl
90AS_VAR_PUSHDEF([SUB],[ax_enable_builddir])dnl
91AS_VAR_PUSHDEF([AUX],[ax_enable_builddir_auxdir])dnl
92AS_VAR_PUSHDEF([SED],[ax_enable_builddir_sed])dnl
93SUB="."
94AC_ARG_ENABLE([builddir], AS_HELP_STRING(
95 [--disable-builddir],[disable automatic build in subdir of sources])
96 ,[SUB="$enableval"], [SUB="auto"])
97if test ".$ac_srcdir_defaulted" != ".no" ; then
98if test ".$srcdir" = ".." ; then
99 if test -f config.status ; then
100 AC_MSG_NOTICE(toplevel srcdir already configured... skipping subdir build)
101 else
102 test ".$SUB" = "." && SUB="."
103 test ".$SUB" = ".no" && SUB="."
104 test ".$TARGET" = "." && TARGET="$target"
105 test ".$SUB" = ".auto" && SUB="m4_ifval([$1], [$1],[$TARGET])"
106 if test ".$SUB" != ".." ; then # we know where to go and
107 AS_MKDIR_P([$SUB])
108 echo __.$SUB.__ > $SUB/conftest.tmp
109 cd $SUB
110 if grep __.$SUB.__ conftest.tmp >/dev/null 2>/dev/null ; then
111 rm conftest.tmp
112 AC_MSG_RESULT([continue configure in default builddir "./$SUB"])
113 else
114 AC_MSG_ERROR([could not change to default builddir "./$SUB"])
115 fi
116 srcdir=`echo "$SUB" |
117 sed -e 's,^\./,,;s,[[^/]]$,&/,;s,[[^/]]*/,../,g;s,[[/]]$,,;'`
118 # going to restart from subdirectory location
119 test -f $srcdir/config.log && mv $srcdir/config.log .
120 test -f $srcdir/confdefs.h && mv $srcdir/confdefs.h .
121 test -f $srcdir/conftest.log && mv $srcdir/conftest.log .
122 test -f $srcdir/$cache_file && mv $srcdir/$cache_file .
123 AC_MSG_RESULT(....exec $SHELL $srcdir/[$]0 "--srcdir=$srcdir" "--enable-builddir=$SUB" ${1+"[$]@"})
124 case "[$]0" in # restart
125 [[\\/]]* | ?:[[\\/]]*) # Asbolute name
126 eval $SHELL "'[$]0'" "'--srcdir=$srcdir'" "'--enable-builddir=$SUB'" $ac_configure_args ;;
127 *) eval $SHELL "'$srcdir/[$]0'" "'--srcdir=$srcdir'" "'--enable-builddir=$SUB'" $ac_configure_args ;;
128 esac ; exit $?
129 fi
130 fi
131fi fi
132test ".$SUB" = ".auto" && SUB="."
133dnl ac_path_prog uses "set dummy" to override $@ which would defeat the "exec"
134AC_PATH_PROG(SED,gsed sed, sed)
135AUX="$am_aux_dir"
136AS_VAR_POPDEF([SED])dnl
137AS_VAR_POPDEF([AUX])dnl
138AS_VAR_POPDEF([SUB])dnl
139AC_CONFIG_COMMANDS([buildir],[dnl .............. config.status ..............
140AS_VAR_PUSHDEF([SUB],[ax_enable_builddir])dnl
141AS_VAR_PUSHDEF([TOP],[top_srcdir])dnl
142AS_VAR_PUSHDEF([SRC],[ac_top_srcdir])dnl
143AS_VAR_PUSHDEF([AUX],[ax_enable_builddir_auxdir])dnl
144AS_VAR_PUSHDEF([SED],[ax_enable_builddir_sed])dnl
145pushdef([END],[Makefile.mk])dnl
146pushdef([_ALL],[ifelse([$3],,[-all],[$3])])dnl
147 SRC="$ax_enable_builddir_srcdir"
148 if test ".$SUB" = ".." ; then
149 if test -f "$TOP/Makefile" ; then
150 AC_MSG_NOTICE([skipping TOP/Makefile - left untouched])
151 else
152 AC_MSG_NOTICE([skipping TOP/Makefile - not created])
153 fi
154 else
155 if test -f "$SRC/Makefile" ; then
156 a=`grep "^VERSION " "$SRC/Makefile"` ; b=`grep "^VERSION " Makefile`
157 test "$a" != "$b" && rm "$SRC/Makefile"
158 fi
159 if test -f "$SRC/Makefile" ; then
160 echo "$SRC/Makefile : $SRC/Makefile.in" > $tmp/conftemp.mk
161 echo " []@ echo 'REMOVED,,,' >\$[]@" >> $tmp/conftemp.mk
162 eval "${MAKE-make} -f $tmp/conftemp.mk 2>/dev/null >/dev/null"
163 if grep '^REMOVED,,,' "$SRC/Makefile" >/dev/null
164 then rm $SRC/Makefile ; fi
165 cp $tmp/conftemp.mk $SRC/makefiles.mk~ ## DEBUGGING
166 fi
167 if test ! -f "$SRC/Makefile" ; then
168 AC_MSG_NOTICE([create TOP/Makefile guessed from local Makefile])
169 x='`' ; cat >$tmp/conftemp.sed <<_EOF
170/^\$/n
171x
172/^\$/bS
173x
174/\\\\\$/{H;d;}
175{H;s/.*//;x;}
176bM
177:S
178x
179/\\\\\$/{h;d;}
180{h;s/.*//;x;}
181:M
182s/\\(\\n\\) /\\1 /g
183/^ /d
184/^[[ ]]*[[\\#]]/d
185/^VPATH *=/d
186s/^srcdir *=.*/srcdir = ./
187s/^top_srcdir *=.*/top_srcdir = ./
188/[[:=]]/!d
189/^\\./d
190dnl Now handle rules (i.e. lines containing ":" but not " = ").
191/ = /b
192/ .= /b
193/:/!b
194s/:.*/:/
195s/ / /g
196s/ \\([[a-z]][[a-z-]]*[[a-zA-Z0-9]]\\)\\([[ :]]\\)/ \\1 \\1[]_ALL\\2/g
197s/^\\([[a-z]][[a-z-]]*[[a-zA-Z0-9]]\\)\\([[ :]]\\)/\\1 \\1[]_ALL\\2/
198s/ / /g
199/^all all[]_ALL[[ :]]/i\\
200all-configured : all[]_ALL
201dnl dist-all exists... and would make for dist-all-all
202s/ [[a-zA-Z0-9-]]*[]_ALL [[a-zA-Z0-9-]]*[]_ALL[]_ALL//g
203/[]_ALL[]_ALL/d
204a\\
205 @ HOST="\$(HOST)\" \\\\\\
206 ; test ".\$\$HOST" = "." && HOST=$x sh $AUX/config.guess $x \\\\\\
207 ; BUILD=$x grep "^#### \$\$HOST " Makefile | sed -e 's/.*|//' $x \\\\\\
208 ; use=$x basename "\$\@" _ALL $x; n=$x echo \$\$BUILD | wc -w $x \\\\\\
209 ; echo "MAKE \$\$HOST : \$\$n * \$\@"; if test "\$\$n" -eq "0" ; then : \\\\\\
210 ; BUILD=$x grep "^####.*|" Makefile |tail -1| sed -e 's/.*|//' $x ; fi \\\\\\
211 ; test ".\$\$BUILD" = "." && BUILD="." \\\\\\
212 ; test "\$\$use" = "\$\@" && BUILD=$x echo "\$\$BUILD" | tail -1 $x \\\\\\
213 ; for i in \$\$BUILD ; do test ".\$\$i" = "." && continue \\\\\\
214 ; (cd "\$\$i" && test ! -f configure && \$(MAKE) \$\$use) || exit; done
215dnl special rule add-on: "dist" copies the tarball to $(PUB). (source tree)
216/dist[]_ALL *:/a\\
217 @ HOST="\$(HOST)\" \\\\\\
218 ; test ".\$\$HOST" = "." && HOST=$x sh $AUX/config.guess $x \\\\\\
219 ; BUILD=$x grep "^#### \$\$HOST " Makefile | sed -e 's/.*|//' $x \\\\\\
220 ; found=$x echo \$\$BUILD | wc -w $x \\\\\\
221 ; echo "MAKE \$\$HOST : \$\$found \$(PACKAGE)-\$(VERSION).tar.*" \\\\\\
222 ; if test "\$\$found" -eq "0" ; then : \\\\\\
223 ; BUILD=$x grep "^#### .*|" Makefile |tail -1| sed -e 's/.*|//' $x \\\\\\
224 ; fi ; for i in \$\$BUILD ; do test ".\$\$i" = "." && continue \\\\\\
225 ; for f in \$\$i/\$(PACKAGE)-\$(VERSION).tar.* \\\\\\
226 ; do test -f "\$\$f" && mv "\$\$f" \$(PUB). ; done ; break ; done
227dnl special rule add-on: "dist-foo" copies all the archives to $(PUB). (source tree)
228/dist-[[a-zA-Z0-9]]*[]_ALL *:/a\\
229 @ HOST="\$(HOST)\" \\\\\\
230 ; test ".\$\$HOST" = "." && HOST=$x sh ./config.guess $x \\\\\\
231 ; BUILD=$x grep "^#### \$\$HOST " Makefile | sed -e 's/.*|//' $x \\\\\\
232 ; found=$x echo \$\$BUILD | wc -w $x \\\\\\
233 ; echo "MAKE \$\$HOST : \$\$found \$(PACKAGE)-\$(VERSION).*" \\\\\\
234 ; if test "\$\$found" -eq "0" ; then : \\\\\\
235 ; BUILD=$x grep "^#### .*|" Makefile |tail -1| sed -e 's/.*|//' $x \\\\\\
236 ; fi ; for i in \$\$BUILD ; do test ".\$\$i" = "." && continue \\\\\\
237 ; for f in \$\$i/\$(PACKAGE)-\$(VERSION).* \\\\\\
238 ; do test -f "\$\$f" && mv "\$\$f" \$(PUB). ; done ; break ; done
239dnl special rule add-on: "distclean" removes all local builddirs completely
240/distclean[]_ALL *:/a\\
241 @ HOST="\$(HOST)\" \\\\\\
242 ; test ".\$\$HOST" = "." && HOST=$x sh $AUX/config.guess $x \\\\\\
243 ; BUILD=$x grep "^#### .*|" Makefile | sed -e 's/.*|//' $x \\\\\\
244 ; use=$x basename "\$\@" _ALL $x; n=$x echo \$\$BUILD | wc -w $x \\\\\\
245 ; echo "MAKE \$\$HOST : \$\$n * \$\@ (all local builds)" \\\\\\
246 ; test ".\$\$BUILD" = "." && BUILD="." \\\\\\
247 ; for i in \$\$BUILD ; do test ".\$\$i" = "." && continue \\\\\\
248 ; echo "# rm -r \$\$i"; done ; echo "# (sleep 3)" ; sleep 3 \\\\\\
249 ; for i in \$\$BUILD ; do test ".\$\$i" = "." && continue \\\\\\
250 ; echo "\$\$i" | grep "^/" > /dev/null && continue \\\\\\
251 ; echo "\$\$i" | grep "^../" > /dev/null && continue \\\\\\
252 ; echo "rm -r \$\$i"; (rm -r "\$\$i") ; done ; rm Makefile
253_EOF
254 cp "$tmp/conftemp.sed" "$SRC/makefile.sed~" ## DEBUGGING
255 $SED -f $tmp/conftemp.sed Makefile >$SRC/Makefile
256 if test -f "$SRC/m4_ifval([$2],[$2],[END])" ; then
257 AC_MSG_NOTICE([extend TOP/Makefile with TOP/m4_ifval([$2],[$2],[END])])
258 cat $SRC/END >>$SRC/Makefile
259 fi ; xxxx="####"
260 echo "$xxxx CONFIGURATIONS FOR TOPLEVEL MAKEFILE: " >>$SRC/Makefile
261 # sanity check
262 if grep '^; echo "MAKE ' $SRC/Makefile >/dev/null ; then
263 AC_MSG_NOTICE([buggy sed found - it deletes tab in "a" text parts])
264 $SED -e '/^@ HOST=/s/^/ /' -e '/^; /s/^/ /' $SRC/Makefile \
265 >$SRC/Makefile~
266 (test -s $SRC/Makefile~ && mv $SRC/Makefile~ $SRC/Makefile) 2>/dev/null
267 fi
268 else
269 xxxx="\\#\\#\\#\\#"
270 # echo "/^$xxxx *$ax_enable_builddir_host /d" >$tmp/conftemp.sed
271 echo "s!^$xxxx [[^|]]* | *$SUB *\$!$xxxx ...... $SUB!" >$tmp/conftemp.sed
272 $SED -f "$tmp/conftemp.sed" "$SRC/Makefile" >$tmp/mkfile.tmp
273 cp "$tmp/conftemp.sed" "$SRC/makefiles.sed~" ## DEBUGGING
274 cp "$tmp/mkfile.tmp" "$SRC/makefiles.out~" ## DEBUGGING
275 if cmp -s "$SRC/Makefile" "$tmp/mkfile.tmp" 2>/dev/null ; then
276 AC_MSG_NOTICE([keeping TOP/Makefile from earlier configure])
277 rm "$tmp/mkfile.tmp"
278 else
279 AC_MSG_NOTICE([reusing TOP/Makefile from earlier configure])
280 mv "$tmp/mkfile.tmp" "$SRC/Makefile"
281 fi
282 fi
283 AC_MSG_NOTICE([build in $SUB (HOST=$ax_enable_builddir_host)])
284 xxxx="####"
285 echo "$xxxx" "$ax_enable_builddir_host" "|$SUB" >>$SRC/Makefile
286 fi
287popdef([END])dnl
288AS_VAR_POPDEF([SED])dnl
289AS_VAR_POPDEF([AUX])dnl
290AS_VAR_POPDEF([SRC])dnl
291AS_VAR_POPDEF([TOP])dnl
292AS_VAR_POPDEF([SUB])dnl
293],[dnl
294ax_enable_builddir_srcdir="$srcdir" # $srcdir
295ax_enable_builddir_host="$HOST" # $HOST / $host
296ax_enable_builddir_version="$VERSION" # $VERSION
297ax_enable_builddir_package="$PACKAGE" # $PACKAGE
298ax_enable_builddir_auxdir="$ax_enable_builddir_auxdir" # $AUX
299ax_enable_builddir_sed="$ax_enable_builddir_sed" # $SED
300ax_enable_builddir="$ax_enable_builddir" # $SUB
301])dnl
302])
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 @@
1# ===========================================================================
2# http://www.gnu.org/software/autoconf-archive/ax_extend_srcdir.html
3# ===========================================================================
4#
5# SYNOPSIS
6#
7# AX_EXTEND_SRCDIR
8#
9# DESCRIPTION
10#
11# The AX_EXTEND_SRCDIR macro extends $srcdir by one path component.
12#
13# As an example, when working in /home/michael/i3-4.12/build and calling
14# ../configure, your $srcdir is "..". After calling AX_EXTEND_SRCDIR,
15# $srcdir will be set to "../../i3-4.12".
16#
17# The result of extending $srcdir is that filenames (e.g. in the output of
18# the "backtrace" gdb command) will include one more path component of the
19# absolute source path. The additional path component makes it easy for
20# users to recognize which files belong to the PACKAGE, and -- provided a
21# dist tarball was unpacked -- which version of PACKAGE was used.
22#
23# As an example, in "backtrace", you will see:
24#
25# #0 main (argc=1, argv=0x7fffffff1fc8) at ../../i3-4.12/src/main.c:187
26#
27# instead of:
28#
29# #0 main (argc=1, argv=0x7fffffff1fc8) at ../src/main.c:187
30#
31# In case your code uses the __FILE__ preprocessor directive to refer to
32# the filename of the current source file (e.g. in debug messages), using
33# the extended path might be undesirable. For this purpose,
34# AX_EXTEND_SRCDIR defines the output variable AX_EXTEND_SRCDIR_CPPFLAGS,
35# which can be added to AM_CPPFLAGS in Makefile.am in order to define the
36# preprocessor directive STRIPPED__FILE__. As an example, when compiling
37# the file "../../i3-4.12/src/main.c", STRIPPED__FILE__ evaluates to
38# "main.c".
39#
40# There are some caveats: When $srcdir is "." (i.e. when ./configure was
41# called instead of ../configure in a separate build directory),
42# AX_EXTEND_SRCDIR will still extend $srcdir, but the intended effect will
43# not be achieved because of the way automake specifies file paths:
44# automake defines COMPILE to use "`test -f '$source' || echo
45# '\$(srcdir)/'`$source" in order to prefer files in the current directory
46# over specifying $srcdir explicitly.
47#
48# The AX_EXTEND_SRCDIR author is not aware of any way to influence this
49# automake behavior. Patches very welcome.
50#
51# To work around this issue, you can use AX_ENABLE_BUILDDIR i.e. by adding
52# the following code to configure.ac:
53#
54# AX_ENABLE_BUILDDIR
55# dnl ...
56# AX_EXTEND_SRCDIR
57#
58# Then also add this bit to Makefile.am (if you wish to use
59# STRIPPED__FILE__ in your code):
60#
61# AM_CPPFLAGS = @AX_EXTEND_SRCDIR_CPPFLAGS@
62#
63# LICENSE
64#
65# Copyright (c) 2016 Michael Stapelberg <michael@i3wm.org>
66#
67# Copying and distribution of this file, with or without modification, are
68# permitted in any medium without royalty provided the copyright notice
69# and this notice are preserved. This file is offered as-is, without any
70# warranty.
71
72#serial 3
73
74AC_DEFUN([AX_EXTEND_SRCDIR],
75[dnl
76AS_CASE([$srcdir],
77 [.|.*|/*],
78 [
79 # pwd -P is specified in IEEE 1003.1 from 2004
80 as_dir=`cd "$srcdir" && pwd -P`
81 as_base=`AS_BASENAME([$as_dir])`
82 srcdir=${srcdir}/../${as_base}
83
84 AC_SUBST([AX_EXTEND_SRCDIR_CPPFLAGS], ["-DSTRIPPED__FILE__=AS_ESCAPE([\"$$(basename $<)\"])"])
85 ])
86])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 @@
1# ===========================================================================
2# http://www.gnu.org/software/autoconf-archive/ax_require_defined.html
3# ===========================================================================
4#
5# SYNOPSIS
6#
7# AX_REQUIRE_DEFINED(MACRO)
8#
9# DESCRIPTION
10#
11# AX_REQUIRE_DEFINED is a simple helper for making sure other macros have
12# been defined and thus are available for use. This avoids random issues
13# where a macro isn't expanded. Instead the configure script emits a
14# non-fatal:
15#
16# ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found
17#
18# It's like AC_REQUIRE except it doesn't expand the required macro.
19#
20# Here's an example:
21#
22# AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG])
23#
24# LICENSE
25#
26# Copyright (c) 2014 Mike Frysinger <vapier@gentoo.org>
27#
28# Copying and distribution of this file, with or without modification, are
29# permitted in any medium without royalty provided the copyright notice
30# and this notice are preserved. This file is offered as-is, without any
31# warranty.
32
33#serial 1
34
35AC_DEFUN([AX_REQUIRE_DEFINED], [dnl
36 m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])])
37])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 @@
1# ===========================================================================
2# http://www.gnu.org/software/autoconf-archive/ax_sanitizers.html
3# ===========================================================================
4#
5# SYNOPSIS
6#
7# AX_SANITIZERS([SANITIZERS], [ENABLED-BY-DEFAULT], [ACTION-SUCCESS])
8#
9# DESCRIPTION
10#
11# Offers users to enable one or more sanitizers (see
12# https://github.com/google/sanitizers) with the corresponding
13# --enable-<sanitizer>-sanitizer option.
14#
15# SANITIZERS is a whitespace-separated list of sanitizers to offer via
16# --enable-<sanitizer>-sanitizer options, e.g. "address memory" for the
17# address sanitizer and the memory sanitizer. If SANITIZERS is not specified,
18# all known sanitizers to AX_SANITIZERS will be offered, which at the time of
19# writing are "address memory undefined".
20# NOTE that SANITIZERS is expanded at autoconf time, not at configure time,
21# i.e. you cannot use shell variables in SANITIZERS.
22#
23# ENABLED-BY-DEFAULT is a whitespace-separated list of sanitizers which
24# should be enabled by default, e.g. "memory undefined". Note that not all
25# sanitizers can be combined, e.g. memory sanitizer cannot be enabled when
26# address sanitizer is already enabled.
27# Set ENABLED-BY-DEFAULT to a single whitespace in order to disable all
28# sanitizers by default.
29# ENABLED-BY-DEFAULT is expanded at configure time, so you can use shell
30# variables.
31#
32# ACTION-SUCCESS allows to specify shell commands to execute on success, i.e.
33# when one of the sanitizers was successfully enabled. This is a good place
34# to call AC_DEFINE for any precompiler constants you might need to make your
35# code play nice with sanitizers.
36#
37# The variable ax_enabled_sanitizers contains a whitespace-separated list of
38# all enabled sanitizers, so that you can print them at the end of configure,
39# if you wish.
40#
41# The additional --enable-sanitizers option allows users to enable/disable
42# all sanitizers, effectively overriding ENABLED-BY-DEFAULT.
43#
44# EXAMPLES
45#
46# AX_SANITIZERS([address])
47# dnl offer users to enable address sanitizer via --enable-address-sanitizer
48#
49# is_debug_build=…
50# if test "x$is_debug_build" = "xyes"; then
51# default_sanitizers="address memory"
52# else
53# default_sanitizers=
54# fi
55# AX_SANITIZERS([address memory], [$default_sanitizers])
56# dnl enable address sanitizer and memory sanitizer by default for debug
57# dnl builds, e.g. when building from git instead of a dist tarball.
58#
59# AX_SANITIZERS(, , [
60# AC_DEFINE([SANITIZERS_ENABLED],
61# [],
62# [At least one sanitizer was enabled])])
63# dnl enable all sanitizers known to AX_SANITIZERS by default and set the
64# dnl SANITIZERS_ENABLED precompiler constant.
65#
66# AX_SANITIZERS(, [ ])
67# dnl provide all sanitizers, but enable none by default.
68#
69# LICENSE
70#
71# Copyright (c) 2016 Michael Stapelberg <michael@i3wm.org>
72#
73# Copying and distribution of this file, with or without modification,
74# are permitted in any medium without royalty provided the copyright
75# notice and this notice are preserved. This file is offered as-is,
76# without any warranty.
77
78AC_DEFUN([AX_SANITIZERS],
79[AX_REQUIRE_DEFINED([AX_CHECK_COMPILE_FLAG])
80AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG])
81AX_REQUIRE_DEFINED([AX_APPEND_FLAG])
82AC_ARG_ENABLE(sanitizers,
83 AS_HELP_STRING(
84 [--enable-sanitizers],
85 [enable all known sanitizers]),
86 [ax_sanitizers_default=$enableval],
87 [ax_sanitizers_default=])
88ax_enabled_sanitizers=
89m4_foreach_w([mysan], m4_default($1, [address memory undefined]), [
90 dnl If ax_sanitizers_default is unset, i.e. the user neither explicitly
91 dnl enabled nor explicitly disabled all sanitizers, we get the default value
92 dnl for this sanitizer based on whether it is listed in ENABLED-BY-DEFAULT.
93 AS_IF([test "x$ax_sanitizers_default" = "x"], [dnl
94 ax_sanitizer_default=
95 for mycheck in m4_default([$2], [address memory undefined]); do
96 AS_IF([test "x$mycheck" = "x[]mysan"], [ax_sanitizer_default=yes])
97 done
98 AS_IF([test "x$ax_sanitizer_default" = "x"], [ax_sanitizer_default=no])
99 ],
100 [ax_sanitizer_default=$ax_sanitizers_default])
101 AC_ARG_ENABLE(mysan[]-sanitizer,
102 AS_HELP_STRING(
103 [--enable-[]mysan[]-sanitizer],
104 [enable -fsanitize=mysan]),
105 [ax_sanitizer_enabled=$enableval],
106 [ax_sanitizer_enabled=$ax_sanitizer_default])
107
108AS_IF([test "x$ax_sanitizer_enabled" = "xyes"], [
109dnl Not using AX_APPEND_COMPILE_FLAGS and AX_APPEND_LINK_FLAGS because they
110dnl lack the ability to specify ACTION-SUCCESS.
111 AX_CHECK_COMPILE_FLAG([-fsanitize=[]mysan], [
112 AX_CHECK_LINK_FLAG([-fsanitize=[]mysan], [
113 AX_APPEND_FLAG([-fsanitize=[]mysan], [])
114dnl If and only if libtool is being used, LDFLAGS needs to contain -Wc,-fsanitize=….
115dnl See e.g. https://sources.debian.net/src/systemd/231-7/configure.ac/?hl=128#L135
116dnl TODO: how can recognize that situation and add -Wc,?
117 AX_APPEND_FLAG([-fsanitize=[]mysan], [LDFLAGS])
118dnl TODO: add -fPIE -pie for memory
119 # -fno-omit-frame-pointer results in nicer stack traces in error
120 # messages, see http://clang.llvm.org/docs/AddressSanitizer.html#usage
121 AX_CHECK_COMPILE_FLAG([-fno-omit-frame-pointer], [
122 AX_APPEND_FLAG([-fno-omit-frame-pointer], [])])
123dnl 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
124 m4_default([$3], :)
125 ax_enabled_sanitizers="[]mysan $ax_enabled_sanitizers"
126 ])
127 ])
128])
129])dnl
130])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 @@
1#
2# PAM configuration file for the i3lock screen locker. By default, it includes
3# the 'login' configuration file (see /etc/pam.d/login)
4#
5
6auth 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 @@
1/*
2 * vim:ts=4:sw=4:expandtab
3 *
4 * © 2010 Michael Stapelberg
5 *
6 * See LICENSE for licensing information
7 *
8 */
9#include <stdbool.h>
10#include <stdlib.h>
11#include <stdio.h>
12#include <math.h>
13#include <xcb/xcb.h>
14#include <xcb/xinerama.h>
15#include <xcb/randr.h>
16
17#include "i3lock.h"
18#include "xcb.h"
19#include "randr.h"
20
21/* Number of Xinerama screens which are currently present. */
22int xr_screens = 0;
23
24/* The resolutions of the currently present Xinerama screens. */
25Rect *xr_resolutions = NULL;
26
27static bool xinerama_active;
28static bool has_randr = false;
29static bool has_randr_1_5 = false;
30extern bool debug_mode;
31
32void _xinerama_init(void);
33
34void randr_init(int *event_base, xcb_window_t root) {
35 const xcb_query_extension_reply_t *extreply;
36
37 extreply = xcb_get_extension_data(conn, &xcb_randr_id);
38 if (!extreply->present) {
39 DEBUG("RandR is not present, falling back to Xinerama.\n");
40 _xinerama_init();
41 return;
42 }
43
44 xcb_generic_error_t *err;
45 xcb_randr_query_version_reply_t *randr_version =
46 xcb_randr_query_version_reply(
47 conn, xcb_randr_query_version(conn, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), &err);
48 if (err != NULL) {
49 DEBUG("Could not query RandR version: X11 error code %d\n", err->error_code);
50 _xinerama_init();
51 return;
52 }
53
54 has_randr = true;
55
56 has_randr_1_5 = (randr_version->major_version >= 1) &&
57 (randr_version->minor_version >= 5);
58
59 free(randr_version);
60
61 if (event_base != NULL)
62 *event_base = extreply->first_event;
63
64 xcb_randr_select_input(conn, root,
65 XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
66 XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
67 XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
68 XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
69
70 xcb_flush(conn);
71}
72
73void _xinerama_init(void) {
74 if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) {
75 DEBUG("Xinerama extension not found, disabling.\n");
76 return;
77 }
78
79 xcb_xinerama_is_active_cookie_t cookie;
80 xcb_xinerama_is_active_reply_t *reply;
81
82 cookie = xcb_xinerama_is_active(conn);
83 reply = xcb_xinerama_is_active_reply(conn, cookie, NULL);
84 if (!reply)
85 return;
86
87 if (!reply->state) {
88 free(reply);
89 return;
90 }
91
92 xinerama_active = true;
93 free(reply);
94}
95
96/*
97 * randr_query_outputs_15 uses RandR ≥ 1.5 to update outputs.
98 *
99 */
100static bool _randr_query_monitors_15(xcb_window_t root) {
101#if XCB_RANDR_MINOR_VERSION < 5
102 return false;
103#else
104 /* RandR 1.5 available at compile-time, i.e. libxcb is new enough */
105 if (!has_randr_1_5) {
106 return false;
107 }
108 /* RandR 1.5 available at run-time (supported by the server) */
109 DEBUG("Querying monitors using RandR 1.5\n");
110 xcb_generic_error_t *err;
111 xcb_randr_get_monitors_reply_t *monitors =
112 xcb_randr_get_monitors_reply(
113 conn, xcb_randr_get_monitors(conn, root, true), &err);
114 if (err != NULL) {
115 DEBUG("Could not get RandR monitors: X11 error code %d\n", err->error_code);
116 free(err);
117 /* Fall back to RandR ≤ 1.4 */
118 return false;
119 }
120
121 int screens = xcb_randr_get_monitors_monitors_length(monitors);
122 DEBUG("%d RandR monitors found (timestamp %d)\n",
123 screens, monitors->timestamp);
124
125 Rect *resolutions = malloc(screens * sizeof(Rect));
126 /* No memory? Just keep on using the old information. */
127 if (!resolutions) {
128 free(monitors);
129 return true;
130 }
131
132 xcb_randr_monitor_info_iterator_t iter;
133 int screen;
134 for (iter = xcb_randr_get_monitors_monitors_iterator(monitors), screen = 0;
135 iter.rem;
136 xcb_randr_monitor_info_next(&iter), screen++) {
137 const xcb_randr_monitor_info_t *monitor_info = iter.data;
138
139 resolutions[screen].x = monitor_info->x;
140 resolutions[screen].y = monitor_info->y;
141 resolutions[screen].width = monitor_info->width;
142 resolutions[screen].height = monitor_info->height;
143 DEBUG("found RandR monitor: %d x %d at %d x %d\n",
144 monitor_info->width, monitor_info->height,
145 monitor_info->x, monitor_info->y);
146 }
147 free(xr_resolutions);
148 xr_resolutions = resolutions;
149 xr_screens = screens;
150
151 free(monitors);
152 return true;
153#endif
154}
155
156/*
157 * randr_query_outputs_14 uses RandR ≤ 1.4 to update outputs.
158 *
159 */
160static bool _randr_query_outputs_14(xcb_window_t root) {
161 if (!has_randr) {
162 return false;
163 }
164 DEBUG("Querying outputs using RandR ≤ 1.4\n");
165
166 /* Get screen resources (primary output, crtcs, outputs, modes) */
167 xcb_randr_get_screen_resources_current_cookie_t rcookie;
168 rcookie = xcb_randr_get_screen_resources_current(conn, root);
169
170 xcb_randr_get_screen_resources_current_reply_t *res =
171 xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL);
172 if (res == NULL) {
173 DEBUG("Could not query screen resources.\n");
174 return false;
175 }
176
177 /* timestamp of the configuration so that we get consistent replies to all
178 * requests (if the configuration changes between our different calls) */
179 const xcb_timestamp_t cts = res->config_timestamp;
180
181 const int len = xcb_randr_get_screen_resources_current_outputs_length(res);
182
183 /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */
184 xcb_randr_output_t *randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
185
186 /* Request information for each output */
187 xcb_randr_get_output_info_cookie_t ocookie[len];
188 for (int i = 0; i < len; i++) {
189 ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts);
190 }
191 Rect *resolutions = malloc(len * sizeof(Rect));
192 /* No memory? Just keep on using the old information. */
193 if (!resolutions) {
194 free(res);
195 return true;
196 }
197
198 /* Loop through all outputs available for this X11 screen */
199 int screen = 0;
200
201 for (int i = 0; i < len; i++) {
202 xcb_randr_get_output_info_reply_t *output;
203
204 if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) {
205 continue;
206 }
207
208 if (output->crtc == XCB_NONE) {
209 free(output);
210 continue;
211 }
212
213 xcb_randr_get_crtc_info_cookie_t icookie;
214 xcb_randr_get_crtc_info_reply_t *crtc;
215 icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts);
216 if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
217 DEBUG("Skipping output: could not get CRTC (0x%08x)\n", output->crtc);
218 free(output);
219 continue;
220 }
221
222 resolutions[screen].x = crtc->x;
223 resolutions[screen].y = crtc->y;
224 resolutions[screen].width = crtc->width;
225 resolutions[screen].height = crtc->height;
226
227 DEBUG("found RandR output: %d x %d at %d x %d\n",
228 crtc->width, crtc->height,
229 crtc->x, crtc->y);
230
231 screen++;
232
233 free(crtc);
234
235 free(output);
236 }
237 free(xr_resolutions);
238 xr_resolutions = resolutions;
239 xr_screens = screen;
240 free(res);
241 return true;
242}
243
244void _xinerama_query_screens(void) {
245 if (!xinerama_active) {
246 return;
247 }
248
249 xcb_xinerama_query_screens_cookie_t cookie;
250 xcb_xinerama_query_screens_reply_t *reply;
251 xcb_xinerama_screen_info_t *screen_info;
252 xcb_generic_error_t *err;
253 cookie = xcb_xinerama_query_screens_unchecked(conn);
254 reply = xcb_xinerama_query_screens_reply(conn, cookie, &err);
255 if (!reply) {
256 DEBUG("Couldn't get Xinerama screens: X11 error code %d\n", err->error_code);
257 free(err);
258 return;
259 }
260 screen_info = xcb_xinerama_query_screens_screen_info(reply);
261 int screens = xcb_xinerama_query_screens_screen_info_length(reply);
262
263 Rect *resolutions = malloc(screens * sizeof(Rect));
264 /* No memory? Just keep on using the old information. */
265 if (!resolutions) {
266 free(reply);
267 return;
268 }
269
270 for (int screen = 0; screen < xr_screens; screen++) {
271 resolutions[screen].x = screen_info[screen].x_org;
272 resolutions[screen].y = screen_info[screen].y_org;
273 resolutions[screen].width = screen_info[screen].width;
274 resolutions[screen].height = screen_info[screen].height;
275 DEBUG("found Xinerama screen: %d x %d at %d x %d\n",
276 screen_info[screen].width, screen_info[screen].height,
277 screen_info[screen].x_org, screen_info[screen].y_org);
278 }
279
280 free(xr_resolutions);
281 xr_resolutions = resolutions;
282 xr_screens = screens;
283
284 free(reply);
285}
286
287void randr_query(xcb_window_t root) {
288 if (_randr_query_monitors_15(root)) {
289 return;
290 }
291
292 if (_randr_query_outputs_14(root)) {
293 return;
294 }
295
296 _xinerama_query_screens();
297}
diff --git a/randr.h b/randr.h
new file mode 100644
index 0000000..510a7cb
--- /dev/null
+++ b/randr.h
@@ -0,0 +1,17 @@
1#ifndef _XINERAMA_H
2#define _XINERAMA_H
3
4typedef struct Rect {
5 int16_t x;
6 int16_t y;
7 uint16_t width;
8 uint16_t height;
9} Rect;
10
11extern int xr_screens;
12extern Rect *xr_resolutions;
13
14void randr_init(int *event_base, xcb_window_t root);
15void randr_query(xcb_window_t root);
16
17#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 @@
1# vim:ft=Dockerfile
2FROM debian:sid
3
4RUN echo force-unsafe-io > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup
5# Paper over occasional network flakiness of some mirrors.
6RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
7
8# NOTE: I tried exclusively using gce_debian_mirror.storage.googleapis.com
9# instead of httpredir.debian.org, but the results (Fetched 123 MB in 36s (3357
10# kB/s)) are not any better than httpredir.debian.org (Fetched 123 MB in 34s
11# (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
12
13# Install mk-build-deps (for installing the i3 build dependencies),
14# clang and clang-format-3.8 (for checking formatting and building with clang),
15# lintian (for checking spelling errors),
16# test suite dependencies (for running tests)
17RUN apt-get update && \
18 DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
19 build-essential clang git autoconf automake libxcb-randr0-dev pkg-config libpam0g-dev \
20 libcairo2-dev libxcb1-dev libxcb-dpms0-dev libxcb-image0-dev libxcb-util0-dev \
21 libxcb-xrm-dev libev-dev libxcb-xinerama0-dev libxcb-xkb-dev libxkbcommon-dev \
22 libxkbcommon-x11-dev && \
23 rm -rf /var/lib/apt/lists/*
24
25WORKDIR /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 @@
1/*
2 * vim:ts=4:sw=4:expandtab
3 *
4 * © 2010 Michael Stapelberg
5 *
6 * See LICENSE for licensing information
7 *
8 */
9#include <stdbool.h>
10#include <stdlib.h>
11#include <stdio.h>
12#include <string.h>
13#include <math.h>
14#include <xcb/xcb.h>
15#include <ev.h>
16#include <cairo.h>
17#include <cairo/cairo-xcb.h>
18
19#include "i3lock.h"
20#include "xcb.h"
21#include "unlock_indicator.h"
22#include "randr.h"
23#include "dpi.h"
24
25#define BUTTON_RADIUS 90
26#define BUTTON_SPACE (BUTTON_RADIUS + 5)
27#define BUTTON_CENTER (BUTTON_RADIUS + 5)
28#define BUTTON_DIAMETER (2 * BUTTON_SPACE)
29
30/*******************************************************************************
31 * Variables defined in i3lock.c.
32 ******************************************************************************/
33
34extern bool debug_mode;
35
36/* The current position in the input buffer. Useful to determine if any
37 * characters of the password have already been entered or not. */
38extern int input_position;
39
40/* The lock window. */
41extern xcb_window_t win;
42
43/* The current resolution of the X11 root window. */
44extern uint32_t last_resolution[2];
45
46/* Whether the unlock indicator is enabled (defaults to true). */
47extern bool unlock_indicator;
48
49/* List of pressed modifiers, or NULL if none are pressed. */
50extern char *modifier_string;
51
52/* A Cairo surface containing the specified image (-i), if any. */
53extern cairo_surface_t *img;
54
55/* Whether the image should be tiled. */
56extern bool tile;
57/* The background color to use (in hex). */
58extern char color[7];
59
60/* Whether the failed attempts should be displayed. */
61extern bool show_failed_attempts;
62/* Number of failed unlock attempts. */
63extern int failed_attempts;
64
65/*******************************************************************************
66 * Variables defined in xcb.c.
67 ******************************************************************************/
68
69/* The root screen, to determine the DPI. */
70extern xcb_screen_t *screen;
71
72/*******************************************************************************
73 * Local variables.
74 ******************************************************************************/
75
76/* Cache the screen’s visual, necessary for creating a Cairo context. */
77static xcb_visualtype_t *vistype;
78
79/* Maintain the current unlock/PAM state to draw the appropriate unlock
80 * indicator. */
81unlock_state_t unlock_state;
82auth_state_t auth_state;
83
84/*
85 * Draws global image with fill color onto a pixmap with the given
86 * resolution and returns it.
87 *
88 */
89xcb_pixmap_t draw_image(uint32_t *resolution) {
90 xcb_pixmap_t bg_pixmap = XCB_NONE;
91 const double scaling_factor = get_dpi_value() / 96.0;
92 int button_diameter_physical = ceil(scaling_factor * BUTTON_DIAMETER);
93 DEBUG("scaling_factor is %.f, physical diameter is %d px\n",
94 scaling_factor, button_diameter_physical);
95
96 if (!vistype)
97 vistype = get_root_visual_type(screen);
98 bg_pixmap = create_bg_pixmap(conn, screen, resolution, color);
99 /* Initialize cairo: Create one in-memory surface to render the unlock
100 * indicator on, create one XCB surface to actually draw (one or more,
101 * depending on the amount of screens) unlock indicators on. */
102 cairo_surface_t *output = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, button_diameter_physical, button_diameter_physical);
103 cairo_t *ctx = cairo_create(output);
104
105 cairo_surface_t *xcb_output = cairo_xcb_surface_create(conn, bg_pixmap, vistype, resolution[0], resolution[1]);
106 cairo_t *xcb_ctx = cairo_create(xcb_output);
107
108 if (img) {
109 if (!tile) {
110 cairo_set_source_surface(xcb_ctx, img, 0, 0);
111 cairo_paint(xcb_ctx);
112 } else {
113 /* create a pattern and fill a rectangle as big as the screen */
114 cairo_pattern_t *pattern;
115 pattern = cairo_pattern_create_for_surface(img);
116 cairo_set_source(xcb_ctx, pattern);
117 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
118 cairo_rectangle(xcb_ctx, 0, 0, resolution[0], resolution[1]);
119 cairo_fill(xcb_ctx);
120 cairo_pattern_destroy(pattern);
121 }
122 } else {
123 char strgroups[3][3] = {{color[0], color[1], '\0'},
124 {color[2], color[3], '\0'},
125 {color[4], color[5], '\0'}};
126 uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
127 (strtol(strgroups[1], NULL, 16)),
128 (strtol(strgroups[2], NULL, 16))};
129 cairo_set_source_rgb(xcb_ctx, rgb16[0] / 255.0, rgb16[1] / 255.0, rgb16[2] / 255.0);
130 cairo_rectangle(xcb_ctx, 0, 0, resolution[0], resolution[1]);
131 cairo_fill(xcb_ctx);
132 }
133
134 if (unlock_indicator &&
135 (unlock_state >= STATE_KEY_PRESSED || auth_state > STATE_AUTH_IDLE)) {
136 cairo_scale(ctx, scaling_factor, scaling_factor);
137 /* Draw a (centered) circle with transparent background. */
138 cairo_set_line_width(ctx, 10.0);
139 cairo_arc(ctx,
140 BUTTON_CENTER /* x */,
141 BUTTON_CENTER /* y */,
142 BUTTON_RADIUS /* radius */,
143 0 /* start */,
144 2 * M_PI /* end */);
145
146 /* Use the appropriate color for the different PAM states
147 * (currently verifying, wrong password, or default) */
148 switch (auth_state) {
149 case STATE_AUTH_VERIFY:
150 case STATE_AUTH_LOCK:
151 cairo_set_source_rgba(ctx, 0, 114.0 / 255, 255.0 / 255, 0.75);
152 break;
153 case STATE_AUTH_WRONG:
154 case STATE_I3LOCK_LOCK_FAILED:
155 cairo_set_source_rgba(ctx, 250.0 / 255, 0, 0, 0.75);
156 break;
157 default:
158 if (unlock_state == STATE_NOTHING_TO_DELETE) {
159 cairo_set_source_rgba(ctx, 250.0 / 255, 0, 0, 0.75);
160 break;
161 }
162 cairo_set_source_rgba(ctx, 0, 0, 0, 0.75);
163 break;
164 }
165 cairo_fill_preserve(ctx);
166
167 switch (auth_state) {
168 case STATE_AUTH_VERIFY:
169 case STATE_AUTH_LOCK:
170 cairo_set_source_rgb(ctx, 51.0 / 255, 0, 250.0 / 255);
171 break;
172 case STATE_AUTH_WRONG:
173 case STATE_I3LOCK_LOCK_FAILED:
174 cairo_set_source_rgb(ctx, 125.0 / 255, 51.0 / 255, 0);
175 break;
176 case STATE_AUTH_IDLE:
177 if (unlock_state == STATE_NOTHING_TO_DELETE) {
178 cairo_set_source_rgb(ctx, 125.0 / 255, 51.0 / 255, 0);
179 break;
180 }
181
182 cairo_set_source_rgb(ctx, 51.0 / 255, 125.0 / 255, 0);
183 break;
184 }
185 cairo_stroke(ctx);
186
187 /* Draw an inner seperator line. */
188 cairo_set_source_rgb(ctx, 0, 0, 0);
189 cairo_set_line_width(ctx, 2.0);
190 cairo_arc(ctx,
191 BUTTON_CENTER /* x */,
192 BUTTON_CENTER /* y */,
193 BUTTON_RADIUS - 5 /* radius */,
194 0,
195 2 * M_PI);
196 cairo_stroke(ctx);
197
198 cairo_set_line_width(ctx, 10.0);
199
200 /* Display a (centered) text of the current PAM state. */
201 char *text = NULL;
202 /* We don't want to show more than a 3-digit number. */
203 char buf[4];
204
205 cairo_set_source_rgb(ctx, 0, 0, 0);
206 cairo_select_font_face(ctx, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
207 cairo_set_font_size(ctx, 28.0);
208 switch (auth_state) {
209 case STATE_AUTH_VERIFY:
210 text = "Verifying…";
211 break;
212 case STATE_AUTH_LOCK:
213 text = "Locking…";
214 break;
215 case STATE_AUTH_WRONG:
216 text = "Wrong!";
217 break;
218 case STATE_I3LOCK_LOCK_FAILED:
219 text = "Lock failed!";
220 break;
221 default:
222 if (unlock_state == STATE_NOTHING_TO_DELETE) {
223 text = "No input";
224 }
225 if (show_failed_attempts && failed_attempts > 0) {
226 if (failed_attempts > 999) {
227 text = "> 999";
228 } else {
229 snprintf(buf, sizeof(buf), "%d", failed_attempts);
230 text = buf;
231 }
232 cairo_set_source_rgb(ctx, 1, 0, 0);
233 cairo_set_font_size(ctx, 32.0);
234 }
235 break;
236 }
237
238 if (text) {
239 cairo_text_extents_t extents;
240 double x, y;
241
242 cairo_text_extents(ctx, text, &extents);
243 x = BUTTON_CENTER - ((extents.width / 2) + extents.x_bearing);
244 y = BUTTON_CENTER - ((extents.height / 2) + extents.y_bearing);
245
246 cairo_move_to(ctx, x, y);
247 cairo_show_text(ctx, text);
248 cairo_close_path(ctx);
249 }
250
251 if (auth_state == STATE_AUTH_WRONG && (modifier_string != NULL)) {
252 cairo_text_extents_t extents;
253 double x, y;
254
255 cairo_set_font_size(ctx, 14.0);
256
257 cairo_text_extents(ctx, modifier_string, &extents);
258 x = BUTTON_CENTER - ((extents.width / 2) + extents.x_bearing);
259 y = BUTTON_CENTER - ((extents.height / 2) + extents.y_bearing) + 28.0;
260
261 cairo_move_to(ctx, x, y);
262 cairo_show_text(ctx, modifier_string);
263 cairo_close_path(ctx);
264 }
265
266 /* After the user pressed any valid key or the backspace key, we
267 * highlight a random part of the unlock indicator to confirm this
268 * keypress. */
269 if (unlock_state == STATE_KEY_ACTIVE ||
270 unlock_state == STATE_BACKSPACE_ACTIVE) {
271 cairo_new_sub_path(ctx);
272 double highlight_start = (rand() % (int)(2 * M_PI * 100)) / 100.0;
273 cairo_arc(ctx,
274 BUTTON_CENTER /* x */,
275 BUTTON_CENTER /* y */,
276 BUTTON_RADIUS /* radius */,
277 highlight_start,
278 highlight_start + (M_PI / 3.0));
279 if (unlock_state == STATE_KEY_ACTIVE) {
280 /* For normal keys, we use a lighter green. */
281 cairo_set_source_rgb(ctx, 51.0 / 255, 219.0 / 255, 0);
282 } else {
283 /* For backspace, we use red. */
284 cairo_set_source_rgb(ctx, 219.0 / 255, 51.0 / 255, 0);
285 }
286 cairo_stroke(ctx);
287
288 /* Draw two little separators for the highlighted part of the
289 * unlock indicator. */
290 cairo_set_source_rgb(ctx, 0, 0, 0);
291 cairo_arc(ctx,
292 BUTTON_CENTER /* x */,
293 BUTTON_CENTER /* y */,
294 BUTTON_RADIUS /* radius */,
295 highlight_start /* start */,
296 highlight_start + (M_PI / 128.0) /* end */);
297 cairo_stroke(ctx);
298 cairo_arc(ctx,
299 BUTTON_CENTER /* x */,
300 BUTTON_CENTER /* y */,
301 BUTTON_RADIUS /* radius */,
302 (highlight_start + (M_PI / 3.0)) - (M_PI / 128.0) /* start */,
303 highlight_start + (M_PI / 3.0) /* end */);
304 cairo_stroke(ctx);
305 }
306 }
307
308 if (xr_screens > 0) {
309 /* Composite the unlock indicator in the middle of each screen. */
310 for (int screen = 0; screen < xr_screens; screen++) {
311 int x = (xr_resolutions[screen].x + ((xr_resolutions[screen].width / 2) - (button_diameter_physical / 2)));
312 int y = (xr_resolutions[screen].y + ((xr_resolutions[screen].height / 2) - (button_diameter_physical / 2)));
313 cairo_set_source_surface(xcb_ctx, output, x, y);
314 cairo_rectangle(xcb_ctx, x, y, button_diameter_physical, button_diameter_physical);
315 cairo_fill(xcb_ctx);
316 }
317 } else {
318 /* We have no information about the screen sizes/positions, so we just
319 * place the unlock indicator in the middle of the X root window and
320 * hope for the best. */
321 int x = (last_resolution[0] / 2) - (button_diameter_physical / 2);
322 int y = (last_resolution[1] / 2) - (button_diameter_physical / 2);
323 cairo_set_source_surface(xcb_ctx, output, x, y);
324 cairo_rectangle(xcb_ctx, x, y, button_diameter_physical, button_diameter_physical);
325 cairo_fill(xcb_ctx);
326 }
327
328 cairo_surface_destroy(xcb_output);
329 cairo_surface_destroy(output);
330 cairo_destroy(ctx);
331 cairo_destroy(xcb_ctx);
332 return bg_pixmap;
333}
334
335/*
336 * Calls draw_image on a new pixmap and swaps that with the current pixmap
337 *
338 */
339void redraw_screen(void) {
340 DEBUG("redraw_screen(unlock_state = %d, auth_state = %d)\n", unlock_state, auth_state);
341 xcb_pixmap_t bg_pixmap = draw_image(last_resolution);
342 xcb_change_window_attributes(conn, win, XCB_CW_BACK_PIXMAP, (uint32_t[1]){bg_pixmap});
343 /* XXX: Possible optimization: Only update the area in the middle of the
344 * screen instead of the whole screen. */
345 xcb_clear_area(conn, 0, win, 0, 0, last_resolution[0], last_resolution[1]);
346 xcb_free_pixmap(conn, bg_pixmap);
347 xcb_flush(conn);
348}
349
350/*
351 * Hides the unlock indicator completely when there is no content in the
352 * password buffer.
353 *
354 */
355void clear_indicator(void) {
356 if (input_position == 0) {
357 unlock_state = STATE_STARTED;
358 } else
359 unlock_state = STATE_KEY_PRESSED;
360 redraw_screen();
361}
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 @@
1#ifndef _UNLOCK_INDICATOR_H
2#define _UNLOCK_INDICATOR_H
3
4typedef enum {
5 STATE_STARTED = 0, /* default state */
6 STATE_KEY_PRESSED = 1, /* key was pressed, show unlock indicator */
7 STATE_KEY_ACTIVE = 2, /* a key was pressed recently, highlight part
8 of the unlock indicator. */
9 STATE_BACKSPACE_ACTIVE = 3, /* backspace was pressed recently, highlight
10 part of the unlock indicator in red. */
11 STATE_NOTHING_TO_DELETE = 4, /* backspace was pressed, but there is nothing to delete. */
12} unlock_state_t;
13
14typedef enum {
15 STATE_AUTH_IDLE = 0, /* no authenticator interaction at the moment */
16 STATE_AUTH_VERIFY = 1, /* currently verifying the password via authenticator */
17 STATE_AUTH_LOCK = 2, /* currently locking the screen */
18 STATE_AUTH_WRONG = 3, /* the password was wrong */
19 STATE_I3LOCK_LOCK_FAILED = 4, /* i3lock failed to load */
20} auth_state_t;
21
22xcb_pixmap_t draw_image(uint32_t* resolution);
23void redraw_screen(void);
24void clear_indicator(void);
25
26#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 @@
1/*
2 * vim:ts=4:sw=4:expandtab
3 *
4 * © 2010 Michael Stapelberg
5 *
6 * xcb.c: contains all functions which use XCB to talk to X11. Mostly wrappers
7 * around the rather complicated/ugly parts of the XCB API.
8 *
9 */
10#include <xcb/xcb.h>
11#include <xcb/xcb_image.h>
12#include <xcb/xcb_atom.h>
13#include <xcb/xcb_aux.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <stdbool.h>
17#include <string.h>
18#include <unistd.h>
19#include <assert.h>
20#include <err.h>
21#include <time.h>
22#include <sys/time.h>
23
24#include "cursors.h"
25#include "unlock_indicator.h"
26
27extern auth_state_t auth_state;
28
29xcb_connection_t *conn;
30xcb_screen_t *screen;
31
32static xcb_atom_t _NET_WM_BYPASS_COMPOSITOR = XCB_NONE;
33void _init_net_wm_bypass_compositor(xcb_connection_t *conn) {
34 if (_NET_WM_BYPASS_COMPOSITOR != XCB_NONE) {
35 /* already initialized */
36 return;
37 }
38 xcb_generic_error_t *err;
39 xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply(
40 conn,
41 xcb_intern_atom(conn, 0, strlen("_NET_WM_BYPASS_COMPOSITOR"), "_NET_WM_BYPASS_COMPOSITOR"),
42 &err);
43 if (atom_reply == NULL) {
44 fprintf(stderr, "X11 Error %d\n", err->error_code);
45 free(err);
46 return;
47 }
48 _NET_WM_BYPASS_COMPOSITOR = atom_reply->atom;
49 free(atom_reply);
50}
51
52#define curs_invisible_width 8
53#define curs_invisible_height 8
54
55static unsigned char curs_invisible_bits[] = {
56 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
57
58#define curs_windows_width 11
59#define curs_windows_height 19
60
61static unsigned char curs_windows_bits[] = {
62 0xfe, 0x07, 0xfc, 0x07, 0xfa, 0x07, 0xf6, 0x07, 0xee, 0x07, 0xde, 0x07,
63 0xbe, 0x07, 0x7e, 0x07, 0xfe, 0x06, 0xfe, 0x05, 0x3e, 0x00, 0xb6, 0x07,
64 0x6a, 0x07, 0x6c, 0x07, 0xde, 0x06, 0xdf, 0x06, 0xbf, 0x05, 0xbf, 0x05,
65 0x7f, 0x06};
66
67#define mask_windows_width 11
68#define mask_windows_height 19
69
70static unsigned char mask_windows_bits[] = {
71 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0f, 0x00, 0x1f, 0x00, 0x3f, 0x00,
72 0x7f, 0x00, 0xff, 0x00, 0xff, 0x01, 0xff, 0x03, 0xff, 0x07, 0x7f, 0x00,
73 0xf7, 0x00, 0xf3, 0x00, 0xe1, 0x01, 0xe0, 0x01, 0xc0, 0x03, 0xc0, 0x03,
74 0x80, 0x01};
75
76static uint32_t get_colorpixel(char *hex) {
77 char strgroups[3][3] = {{hex[0], hex[1], '\0'},
78 {hex[2], hex[3], '\0'},
79 {hex[4], hex[5], '\0'}};
80 uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
81 (strtol(strgroups[1], NULL, 16)),
82 (strtol(strgroups[2], NULL, 16))};
83
84 return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
85}
86
87xcb_visualtype_t *get_root_visual_type(xcb_screen_t *screen) {
88 xcb_visualtype_t *visual_type = NULL;
89 xcb_depth_iterator_t depth_iter;
90 xcb_visualtype_iterator_t visual_iter;
91
92 for (depth_iter = xcb_screen_allowed_depths_iterator(screen);
93 depth_iter.rem;
94 xcb_depth_next(&depth_iter)) {
95 for (visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
96 visual_iter.rem;
97 xcb_visualtype_next(&visual_iter)) {
98 if (screen->root_visual != visual_iter.data->visual_id)
99 continue;
100
101 visual_type = visual_iter.data;
102 return visual_type;
103 }
104 }
105
106 return NULL;
107}
108
109xcb_pixmap_t create_bg_pixmap(xcb_connection_t *conn, xcb_screen_t *scr, u_int32_t *resolution, char *color) {
110 xcb_pixmap_t bg_pixmap = xcb_generate_id(conn);
111 xcb_create_pixmap(conn, scr->root_depth, bg_pixmap, scr->root,
112 resolution[0], resolution[1]);
113
114 /* Generate a Graphics Context and fill the pixmap with background color
115 * (for images that are smaller than your screen) */
116 xcb_gcontext_t gc = xcb_generate_id(conn);
117 uint32_t values[] = {get_colorpixel(color)};
118 xcb_create_gc(conn, gc, bg_pixmap, XCB_GC_FOREGROUND, values);
119 xcb_rectangle_t rect = {0, 0, resolution[0], resolution[1]};
120 xcb_poly_fill_rectangle(conn, bg_pixmap, gc, 1, &rect);
121 xcb_free_gc(conn, gc);
122
123 return bg_pixmap;
124}
125
126xcb_window_t open_fullscreen_window(xcb_connection_t *conn, xcb_screen_t *scr, char *color, xcb_pixmap_t pixmap) {
127 uint32_t mask = 0;
128 uint32_t values[3];
129 xcb_window_t win = xcb_generate_id(conn);
130
131 if (pixmap == XCB_NONE) {
132 mask |= XCB_CW_BACK_PIXEL;
133 values[0] = get_colorpixel(color);
134 } else {
135 mask |= XCB_CW_BACK_PIXMAP;
136 values[0] = pixmap;
137 }
138
139 mask |= XCB_CW_OVERRIDE_REDIRECT;
140 values[1] = 1;
141
142 mask |= XCB_CW_EVENT_MASK;
143 values[2] = XCB_EVENT_MASK_EXPOSURE |
144 XCB_EVENT_MASK_KEY_PRESS |
145 XCB_EVENT_MASK_KEY_RELEASE |
146 XCB_EVENT_MASK_VISIBILITY_CHANGE |
147 XCB_EVENT_MASK_STRUCTURE_NOTIFY;
148
149 xcb_create_window(conn,
150 XCB_COPY_FROM_PARENT,
151 win, /* the window id */
152 scr->root, /* parent == root */
153 0, 0,
154 scr->width_in_pixels,
155 scr->height_in_pixels, /* dimensions */
156 0, /* border = 0, we draw our own */
157 XCB_WINDOW_CLASS_INPUT_OUTPUT,
158 XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
159 mask,
160 values);
161
162 char *name = "i3lock";
163 xcb_change_property(conn,
164 XCB_PROP_MODE_REPLACE,
165 win,
166 XCB_ATOM_WM_NAME,
167 XCB_ATOM_STRING,
168 8,
169 strlen(name),
170 name);
171
172 xcb_change_property(conn,
173 XCB_PROP_MODE_REPLACE,
174 win,
175 XCB_ATOM_WM_CLASS,
176 XCB_ATOM_STRING,
177 8,
178 2 * (strlen("i3lock") + 1),
179 "i3lock\0i3lock\0");
180
181 const uint32_t bypass_compositor = 1; /* disable compositing */
182 _init_net_wm_bypass_compositor(conn);
183 xcb_change_property(conn,
184 XCB_PROP_MODE_REPLACE,
185 win,
186 _NET_WM_BYPASS_COMPOSITOR,
187 XCB_ATOM_CARDINAL,
188 32,
189 1,
190 &bypass_compositor);
191
192 /* Map the window (= make it visible) */
193 xcb_map_window(conn, win);
194
195 /* Raise window (put it on top) */
196 values[0] = XCB_STACK_MODE_ABOVE;
197 xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_STACK_MODE, values);
198
199 /* Ensure that the window is created and set up before returning */
200 xcb_aux_sync(conn);
201
202 return win;
203}
204
205/*
206 * Repeatedly tries to grab pointer and keyboard (up to the specified number of
207 * tries).
208 *
209 * Returns true if the grab succeeded, false if not.
210 *
211 */
212bool grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_t cursor, int tries) {
213 xcb_grab_pointer_cookie_t pcookie;
214 xcb_grab_pointer_reply_t *preply;
215
216 xcb_grab_keyboard_cookie_t kcookie;
217 xcb_grab_keyboard_reply_t *kreply;
218
219 const suseconds_t screen_redraw_timeout = 100000; /* 100ms */
220
221 /* Using few variables to trigger a redraw_screen() if too many tries */
222 bool redrawn = false;
223 struct timeval start;
224 if (gettimeofday(&start, NULL) == -1) {
225 err(EXIT_FAILURE, "gettimeofday");
226 }
227
228 while (tries-- > 0) {
229 pcookie = xcb_grab_pointer(
230 conn,
231 false, /* get all pointer events specified by the following mask */
232 screen->root, /* grab the root window */
233 XCB_NONE, /* which events to let through */
234 XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
235 XCB_GRAB_MODE_ASYNC, /* keyboard mode */
236 XCB_NONE, /* confine_to = in which window should the cursor stay */
237 cursor, /* we change the cursor to whatever the user wanted */
238 XCB_CURRENT_TIME);
239
240 if ((preply = xcb_grab_pointer_reply(conn, pcookie, NULL)) &&
241 preply->status == XCB_GRAB_STATUS_SUCCESS) {
242 free(preply);
243 break;
244 }
245
246 /* In case the grab failed, we still need to free the reply */
247 free(preply);
248
249 /* Make this quite a bit slower */
250 usleep(50);
251
252 struct timeval now;
253 if (gettimeofday(&now, NULL) == -1) {
254 err(EXIT_FAILURE, "gettimeofday");
255 }
256
257 struct timeval elapsed;
258 timersub(&now, &start, &elapsed);
259
260 if (!redrawn &&
261 (tries % 100) == 0 &&
262 elapsed.tv_usec >= screen_redraw_timeout) {
263 redraw_screen();
264 redrawn = true;
265 }
266 }
267
268 while (tries-- > 0) {
269 kcookie = xcb_grab_keyboard(
270 conn,
271 true, /* report events */
272 screen->root, /* grab the root window */
273 XCB_CURRENT_TIME,
274 XCB_GRAB_MODE_ASYNC, /* process events as normal, do not require sync */
275 XCB_GRAB_MODE_ASYNC);
276
277 if ((kreply = xcb_grab_keyboard_reply(conn, kcookie, NULL)) &&
278 kreply->status == XCB_GRAB_STATUS_SUCCESS) {
279 free(kreply);
280 break;
281 }
282
283 /* In case the grab failed, we still need to free the reply */
284 free(kreply);
285
286 /* Make this quite a bit slower */
287 usleep(50);
288
289 struct timeval now;
290 if (gettimeofday(&now, NULL) == -1) {
291 err(EXIT_FAILURE, "gettimeofday");
292 }
293
294 struct timeval elapsed;
295 timersub(&now, &start, &elapsed);
296
297 /* Trigger a screen redraw if 100ms elapsed */
298 if (!redrawn &&
299 (tries % 100) == 0 &&
300 elapsed.tv_usec >= screen_redraw_timeout) {
301 redraw_screen();
302 redrawn = true;
303 }
304 }
305
306 return (tries > 0);
307}
308
309xcb_cursor_t create_cursor(xcb_connection_t *conn, xcb_screen_t *screen, xcb_window_t win, int choice) {
310 xcb_pixmap_t bitmap;
311 xcb_pixmap_t mask;
312 xcb_cursor_t cursor;
313
314 unsigned char *curs_bits;
315 unsigned char *mask_bits;
316 int curs_w, curs_h;
317
318 switch (choice) {
319 case CURS_NONE:
320 curs_bits = curs_invisible_bits;
321 mask_bits = curs_invisible_bits;
322 curs_w = curs_invisible_width;
323 curs_h = curs_invisible_height;
324 break;
325 case CURS_WIN:
326 curs_bits = curs_windows_bits;
327 mask_bits = mask_windows_bits;
328 curs_w = curs_windows_width;
329 curs_h = curs_windows_height;
330 break;
331 case CURS_DEFAULT:
332 default:
333 return XCB_NONE; /* XCB_NONE is xcb's way of saying "don't change the cursor" */
334 }
335
336 bitmap = xcb_create_pixmap_from_bitmap_data(conn,
337 win,
338 curs_bits,
339 curs_w,
340 curs_h,
341 1,
342 screen->white_pixel,
343 screen->black_pixel,
344 NULL);
345
346 mask = xcb_create_pixmap_from_bitmap_data(conn,
347 win,
348 mask_bits,
349 curs_w,
350 curs_h,
351 1,
352 screen->white_pixel,
353 screen->black_pixel,
354 NULL);
355
356 cursor = xcb_generate_id(conn);
357
358 xcb_create_cursor(conn,
359 cursor,
360 bitmap,
361 mask,
362 65535, 65535, 65535,
363 0, 0, 0,
364 0, 0);
365
366 xcb_free_pixmap(conn, bitmap);
367 xcb_free_pixmap(conn, mask);
368
369 return cursor;
370}
371
372static xcb_atom_t _NET_ACTIVE_WINDOW = XCB_NONE;
373void _init_net_active_window(xcb_connection_t *conn) {
374 if (_NET_ACTIVE_WINDOW != XCB_NONE) {
375 /* already initialized */
376 return;
377 }
378 xcb_generic_error_t *err;
379 xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply(
380 conn,
381 xcb_intern_atom(conn, 0, strlen("_NET_ACTIVE_WINDOW"), "_NET_ACTIVE_WINDOW"),
382 &err);
383 if (atom_reply == NULL) {
384 fprintf(stderr, "X11 Error %d\n", err->error_code);
385 free(err);
386 return;
387 }
388 _NET_ACTIVE_WINDOW = atom_reply->atom;
389 free(atom_reply);
390}
391
392xcb_window_t find_focused_window(xcb_connection_t *conn, const xcb_window_t root) {
393 xcb_window_t result = XCB_NONE;
394
395 _init_net_active_window(conn);
396
397 xcb_get_property_reply_t *prop_reply = xcb_get_property_reply(
398 conn,
399 xcb_get_property_unchecked(
400 conn, false, root, _NET_ACTIVE_WINDOW, XCB_GET_PROPERTY_TYPE_ANY, 0, 1 /* word */),
401 NULL);
402 if (prop_reply == NULL) {
403 goto out;
404 }
405 if (xcb_get_property_value_length(prop_reply) == 0) {
406 goto out_prop;
407 }
408 if (prop_reply->type != XCB_ATOM_WINDOW) {
409 goto out_prop;
410 }
411
412 result = *((xcb_window_t *)xcb_get_property_value(prop_reply));
413
414out_prop:
415 free(prop_reply);
416out:
417 return result;
418}
419
420void set_focused_window(xcb_connection_t *conn, const xcb_window_t root, const xcb_window_t window) {
421 xcb_client_message_event_t ev;
422 memset(&ev, '\0', sizeof(xcb_client_message_event_t));
423
424 _init_net_active_window(conn);
425
426 ev.response_type = XCB_CLIENT_MESSAGE;
427 ev.window = window;
428 ev.type = _NET_ACTIVE_WINDOW;
429 ev.format = 32;
430 ev.data.data32[0] = 2; /* 2 = pager */
431
432 xcb_send_event(conn, false, root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev);
433 xcb_flush(conn);
434}
diff --git a/xcb.h b/xcb.h
new file mode 100644
index 0000000..3555c58
--- /dev/null
+++ b/xcb.h
@@ -0,0 +1,17 @@
1#ifndef _XCB_H
2#define _XCB_H
3
4#include <xcb/xcb.h>
5
6extern xcb_connection_t *conn;
7extern xcb_screen_t *screen;
8
9xcb_visualtype_t *get_root_visual_type(xcb_screen_t *s);
10xcb_pixmap_t create_bg_pixmap(xcb_connection_t *conn, xcb_screen_t *scr, u_int32_t *resolution, char *color);
11xcb_window_t open_fullscreen_window(xcb_connection_t *conn, xcb_screen_t *scr, char *color, xcb_pixmap_t pixmap);
12bool grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_t cursor, int tries);
13xcb_cursor_t create_cursor(xcb_connection_t *conn, xcb_screen_t *screen, xcb_window_t win, int choice);
14xcb_window_t find_focused_window(xcb_connection_t *conn, const xcb_window_t root);
15void set_focused_window(xcb_connection_t *conn, const xcb_window_t root, const xcb_window_t window);
16
17#endif