diff options
| author | Simeon Simeonov | 2020-03-12 12:30:51 +0100 |
|---|---|---|
| committer | Simeon Simeonov | 2020-03-12 12:30:51 +0100 |
| commit | 69a05d516bd0c7e369a0ba1fb78aac57a5ea26f2 (patch) | |
| tree | 87e37110ea0e328533a41c3b16b64036ff132641 | |
Initial commit for the last i3lock commit at this time f3b061233e28ad0cf920c9421adc2459b6920812
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 @@ | |||
| 1 | BasedOnStyle: google | ||
| 2 | AllowShortIfStatementsOnASingleLine: false | ||
| 3 | AllowShortLoopsOnASingleLine: false | ||
| 4 | AllowShortFunctionsOnASingleLine: None | ||
| 5 | AllowShortBlocksOnASingleLine: false | ||
| 6 | AlwaysBreakBeforeMultilineStrings: false | ||
| 7 | IndentWidth: 4 | ||
| 8 | PointerBindsToType: false | ||
| 9 | ColumnLimit: 0 | ||
| 10 | SpaceBeforeParens: ControlStatements | ||
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..ab0456b --- /dev/null +++ b/.github/CONTRIBUTING.md | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | Please do not send/request features related to image manipulation. i3lock’s | ||
| 2 | support for background images is intentionally kept minimal, you should do all | ||
| 3 | pre-processing in external tools. | ||
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..591fdfe --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | <!-- | ||
| 2 | PLEASE 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 | <!-- | ||
| 21 | For bug reports, please provide detailed instructions on how the bug can be reproduced. | ||
| 22 | For feature requests, you can remove this section. | ||
| 23 | --> | ||
| 24 | |||
| 25 | ## Environment | ||
| 26 | Output of `i3lock --version`: | ||
| 27 | <pre> | ||
| 28 | i3lock 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 | ||
| 3 | tags | ||
| 4 | *.swp | ||
| 5 | |||
| 6 | ################################################################################ | ||
| 7 | # https://raw.githubusercontent.com/github/gitignore/master/Autotools.gitignore | ||
| 8 | ################################################################################ | ||
| 9 | |||
| 10 | # http://www.gnu.org/software/automake | ||
| 11 | |||
| 12 | Makefile.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/ | ||
| 2 | sudo: false | ||
| 3 | dist: trusty | ||
| 4 | services: | ||
| 5 | - docker | ||
| 6 | language: c | ||
| 7 | compiler: | ||
| 8 | - gcc | ||
| 9 | - clang | ||
| 10 | addons: | ||
| 11 | apt: | ||
| 12 | packages: | ||
| 13 | - clang-format-3.5 | ||
| 14 | script: | ||
| 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 @@ | |||
| 1 | 2019-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 | |||
| 9 | 2018-10-18 i3lock 2.11.1 | ||
| 10 | |||
| 11 | • Fix dist tarball by including I3LOCK_VERSION | ||
| 12 | |||
| 13 | 2018-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 | |||
| 26 | 2017-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 | |||
| 36 | 2017-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 | |||
| 44 | 2017-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 | |||
| 62 | 2016-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 | |||
| 74 | 2015-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 | |||
| 90 | 2014-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 | |||
| 104 | 2013-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 | |||
| 123 | 2012-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 | |||
| 130 | 2012-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 | |||
| 137 | 2012-03-15 i3lock 2.3.1 | ||
| 138 | |||
| 139 | • Fix compilation on some systems | ||
| 140 | |||
| 141 | 2012-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 | |||
| 150 | 2011-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 | |||
| 160 | 2011-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 | |||
| 170 | 2010-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 | |||
| 176 | 2009-08-02 i3lock 1.1 | ||
| 177 | |||
| 178 | • Implement background pictures (-i) and colors (-c) | ||
| 179 | |||
| 180 | 2009-05-10 i3lock 1.0 | ||
| 181 | |||
| 182 | • Implement PAM support | ||
| 183 | • Implement options for forking, beeping, DPMS | ||
| 184 | |||
| 185 | 2009-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 | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | Copyright © 2010-2011, Michael Stapelberg | ||
| 2 | All rights reserved. | ||
| 3 | |||
| 4 | Redistribution and use in source and binary forms, with or without | ||
| 5 | modification, 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 | |||
| 18 | THIS SOFTWARE IS PROVIDED BY Michael Stapelberg ''AS IS'' AND ANY | ||
| 19 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
| 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
| 21 | DISCLAIMED. IN NO EVENT SHALL Michael Stapelberg BE LIABLE FOR ANY | ||
| 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
| 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
| 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
| 25 | ON 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 | ||
| 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..5847975 --- /dev/null +++ b/Makefile.am | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | @CODE_COVERAGE_RULES@ | ||
| 2 | |||
| 3 | echo-version: | ||
| 4 | @echo "@I3LOCK_VERSION@" | ||
| 5 | |||
| 6 | bin_PROGRAMS = i3lock | ||
| 7 | |||
| 8 | dist_man1_MANS = i3lock.1 | ||
| 9 | |||
| 10 | pamddir = $(sysconfdir)/pam.d | ||
| 11 | pamd_files = pam/i3lock | ||
| 12 | pamd_DATA = $(pamd_files) | ||
| 13 | |||
| 14 | AM_CPPFLAGS = \ | ||
| 15 | @AX_EXTEND_SRCDIR_CPPFLAGS@ | ||
| 16 | |||
| 17 | i3lock_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 | |||
| 27 | i3lock_CPPFLAGS = \ | ||
| 28 | $(AM_CPPFLAGS) \ | ||
| 29 | $(CODE_COVERAGE_CPPFLAGS) | ||
| 30 | |||
| 31 | i3lock_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 | |||
| 40 | i3lock_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 | |||
| 53 | EXTRA_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 @@ | |||
| 1 | i3lock - improved screen locker | ||
| 2 | =============================== | ||
| 3 | [i3lock](https://i3wm.org/i3lock/)> is a simple screen locker like slock. | ||
| 4 | After starting it, you will see a white screen (you can configure the | ||
| 5 | color/an image). You can return to your screen by entering your password. | ||
| 6 | |||
| 7 | Many 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 | ||
| 15 | manipulation software. If you need to resize the image to fill the screen | ||
| 16 | or 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 | |||
| 23 | Install | ||
| 24 | ------- | ||
| 25 | |||
| 26 | See [the i3lock home page](https://i3wm.org/i3lock/). | ||
| 27 | |||
| 28 | Requirements | ||
| 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 | |||
| 45 | Running i3lock | ||
| 46 | ------------- | ||
| 47 | Simply invoke the 'i3lock' command. To get out of it, enter your password and | ||
| 48 | press enter. | ||
| 49 | |||
| 50 | On OpenBSD the `i3lock` binary needs to be setgid `auth` to call the | ||
| 51 | authentication helpers, e.g. `/usr/libexec/auth/login_passwd`. | ||
| 52 | |||
| 53 | Building i3lock | ||
| 54 | --------------- | ||
| 55 | We recommend you use the provided package from your distribution. Do not build | ||
| 56 | i3lock unless you have a reason to do so. | ||
| 57 | |||
| 58 | First install the dependencies listed in requirements section, then run these | ||
| 59 | commands (might need to be adapted to your OS): | ||
| 60 | ``` | ||
| 61 | autoreconf --force --install | ||
| 62 | |||
| 63 | rm -rf build/ | ||
| 64 | mkdir -p build && cd build/ | ||
| 65 | |||
| 66 | ../configure \ | ||
| 67 | --prefix=/usr \ | ||
| 68 | --sysconfdir=/etc \ | ||
| 69 | --disable-sanitizers | ||
| 70 | |||
| 71 | make | ||
| 72 | ``` | ||
| 73 | |||
| 74 | Upstream | ||
| 75 | -------- | ||
| 76 | Please submit pull requests to https://github.com/i3/i3lock | ||
diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..474f750 --- /dev/null +++ b/configure.ac | |||
| @@ -0,0 +1,154 @@ | |||
| 1 | # -*- Autoconf -*- | ||
| 2 | # Run autoreconf -fi to generate a configure script from this file. | ||
| 3 | |||
| 4 | AC_PREREQ([2.69]) | ||
| 5 | AC_INIT([i3lock], [2.12], [https://github.com/i3/i3lock/issues]) | ||
| 6 | # For AX_EXTEND_SRCDIR | ||
| 7 | AX_ENABLE_BUILDDIR | ||
| 8 | AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2]) | ||
| 9 | # Default to silent rules, use V=1 to get verbose compilation output. | ||
| 10 | AM_SILENT_RULES([yes]) | ||
| 11 | # Make it possible to disable maintainer mode to disable re-generation of build | ||
| 12 | # system files. | ||
| 13 | AM_MAINTAINER_MODE([enable]) | ||
| 14 | AC_CONFIG_SRCDIR([i3lock.c]) | ||
| 15 | AC_CONFIG_HEADERS([config.h]) | ||
| 16 | AC_CONFIG_MACRO_DIR([m4]) | ||
| 17 | |||
| 18 | dnl Verify macros defined in m4/ such as AX_SANITIZERS are not present in the | ||
| 19 | dnl output, i.e. are replaced as expected. This line results in a better error | ||
| 20 | dnl message when using aclocal < 1.13 (which does not understand | ||
| 21 | dnl AC_CONFIG_MACRO_DIR) without passing the -I m4 parameter. | ||
| 22 | m4_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. | ||
| 27 | AX_CHECK_GNU_MAKE | ||
| 28 | AS_VAR_IF([_cv_gnu_make_command], [""], [AC_MSG_ERROR([the i3lock Makefile.am requires GNU make])]) | ||
| 29 | |||
| 30 | AX_EXTEND_SRCDIR | ||
| 31 | |||
| 32 | AS_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 | ]) | ||
| 44 | AC_SUBST([I3LOCK_VERSION], [$I3LOCK_VERSION]) | ||
| 45 | AC_DEFINE_UNQUOTED([I3LOCK_VERSION], ["${I3LOCK_VERSION}"], [i3lock version]) | ||
| 46 | |||
| 47 | AX_CODE_COVERAGE | ||
| 48 | |||
| 49 | dnl is_release must be lowercase because AX_CHECK_ENABLE_DEBUG calls m4_tolower | ||
| 50 | dnl on its fourth argument. | ||
| 51 | AX_CHECK_ENABLE_DEBUG([yes], , [UNUSED_NDEBUG], [$is_release]) | ||
| 52 | |||
| 53 | AC_PROG_CC_C99 | ||
| 54 | |||
| 55 | # For strnlen() and vasprintf(). | ||
| 56 | AC_USE_SYSTEM_EXTENSIONS | ||
| 57 | |||
| 58 | # Checks for typedefs, structures, and compiler characteristics. | ||
| 59 | AC_CHECK_HEADER_STDBOOL | ||
| 60 | dnl The error message should include the specific type which could not be | ||
| 61 | dnl found, but I do not see a way to achieve that. | ||
| 62 | AC_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. | ||
| 65 | AC_FUNC_FORK | ||
| 66 | AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK | ||
| 67 | AC_FUNC_STRNLEN | ||
| 68 | AC_CHECK_FUNCS([atexit dup2 ftruncate getcwd gettimeofday localtime_r memchr memset mkdir rmdir setlocale socket strcasecmp strchr strdup strerror strncasecmp strndup strrchr strspn strstr strtol strtoul], , [AC_MSG_FAILURE([cannot find the $ac_func function, which i3lock requires])]) | ||
| 69 | |||
| 70 | # Checks for libraries. | ||
| 71 | |||
| 72 | AC_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 :(. | ||
| 75 | AC_SEARCH_LIBS([ev_run], [ev], , [AC_MSG_FAILURE([cannot find the required ev_run() function despite trying to link with -lev])]) | ||
| 76 | |||
| 77 | AC_SEARCH_LIBS([shm_open], [rt]) | ||
| 78 | |||
| 79 | # Only disable PAM on OpenBSD where i3lock uses BSD Auth instead | ||
| 80 | case "$host" in | ||
| 81 | *-openbsd*) | ||
| 82 | # Nothing yet. | ||
| 83 | ;; | ||
| 84 | *) | ||
| 85 | AC_SEARCH_LIBS([pam_authenticate], [pam]) | ||
| 86 | ;; | ||
| 87 | esac | ||
| 88 | |||
| 89 | AC_SEARCH_LIBS([iconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])]) | ||
| 90 | |||
| 91 | dnl Each prefix corresponds to a source tarball which users might have | ||
| 92 | dnl downloaded in a newer version and would like to overwrite. | ||
| 93 | PKG_CHECK_MODULES([XCB], [xcb xcb-xkb xcb-xinerama xcb-randr]) | ||
| 94 | PKG_CHECK_MODULES([XCB_IMAGE], [xcb-image]) | ||
| 95 | PKG_CHECK_MODULES([XCB_UTIL], [xcb-event xcb-util xcb-atom]) | ||
| 96 | PKG_CHECK_MODULES([XCB_UTIL_XRM], [xcb-xrm]) | ||
| 97 | PKG_CHECK_MODULES([XKBCOMMON], [xkbcommon xkbcommon-x11]) | ||
| 98 | PKG_CHECK_MODULES([CAIRO], [cairo]) | ||
| 99 | |||
| 100 | # Checks for programs. | ||
| 101 | AC_PROG_AWK | ||
| 102 | AC_PROG_CPP | ||
| 103 | AC_PROG_INSTALL | ||
| 104 | AC_PROG_MAKE_SET | ||
| 105 | AC_PROG_RANLIB | ||
| 106 | AC_PROG_LN_S | ||
| 107 | |||
| 108 | AM_PROG_AR | ||
| 109 | |||
| 110 | AX_FLAGS_WARN_ALL | ||
| 111 | AX_CHECK_COMPILE_FLAG([-Wunused-value], [AX_APPEND_FLAG([-Wunused-value], [AM_CFLAGS])]) | ||
| 112 | AC_SUBST(AM_CFLAGS) | ||
| 113 | |||
| 114 | # Checks for header files. | ||
| 115 | AC_CHECK_HEADERS([fcntl.h float.h inttypes.h limits.h locale.h netinet/in.h paths.h stddef.h stdint.h stdlib.h string.h sys/param.h sys/socket.h sys/time.h unistd.h], , [AC_MSG_FAILURE([cannot find the $ac_header header, which i3lock requires])]) | ||
| 116 | |||
| 117 | AC_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. | ||
| 121 | if test x$is_release = xyes; then | ||
| 122 | default_sanitizers= | ||
| 123 | else | ||
| 124 | default_sanitizers=address | ||
| 125 | fi | ||
| 126 | AX_SANITIZERS(, [$default_sanitizers], [AC_DEFINE([I3LOCK_ASAN_ENABLED], [], [Enable ASAN])]) | ||
| 127 | |||
| 128 | AC_OUTPUT | ||
| 129 | |||
| 130 | in_git_worktree=`git rev-parse --is-inside-work-tree 2>/dev/null` | ||
| 131 | if [[ "$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 | ||
| 138 | fi | ||
| 139 | |||
| 140 | echo \ | ||
| 141 | "-------------------------------------------------------------------------------- | ||
| 142 | build configured: | ||
| 143 | |||
| 144 | AS_HELP_STRING([i3lock version:], [`echo ${I3LOCK_VERSION} | sed 's,\\\\,,g'`]) | ||
| 145 | AS_HELP_STRING([is release version:], [${is_release}]) | ||
| 146 | |||
| 147 | AS_HELP_STRING([enable debug flags:], [${ax_enable_debug}]) | ||
| 148 | AS_HELP_STRING([code coverage:], [${CODE_COVERAGE_ENABLED}]) | ||
| 149 | AS_HELP_STRING([enabled sanitizers:], [${ax_enabled_sanitizers}]) | ||
| 150 | |||
| 151 | To 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 | ||
| @@ -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 | |||
| 17 | extern bool debug_mode; | ||
| 18 | |||
| 19 | static long dpi; | ||
| 20 | |||
| 21 | extern xcb_screen_t *screen; | ||
| 22 | |||
| 23 | static 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 | */ | ||
| 32 | void 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 | |||
| 63 | init_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 | */ | ||
| 83 | long 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 | */ | ||
| 93 | int 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 | } | ||
| @@ -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 | */ | ||
| 8 | void init_dpi(void); | ||
| 9 | |||
| 10 | /** | ||
| 11 | * This function returns the value of the DPI setting. | ||
| 12 | * | ||
| 13 | */ | ||
| 14 | long 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 | */ | ||
| 22 | int logical_px(const int logical); | ||
diff --git a/i3lock.1 b/i3lock.1 new file mode 100644 index 0000000..cefd0d8 --- /dev/null +++ b/i3lock.1 | |||
| @@ -0,0 +1,173 @@ | |||
| 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 | ||
| 14 | i3lock \- 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 | ||
| 34 | is a simple screen locker like slock. After starting it, you will see a white | ||
| 35 | screen (you can configure the color/an image). You can return to your screen by | ||
| 36 | entering your password. | ||
| 37 | |||
| 38 | .SH IMPROVEMENTS | ||
| 39 | |||
| 40 | .IP \[bu] 2 | ||
| 41 | i3lock forks, so you can combine it with an alias to suspend to RAM (run "i3lock && echo mem > /sys/power/state" to get a locked screen after waking up your computer from suspend to RAM) | ||
| 42 | .IP \[bu] | ||
| 43 | You can specify either a background color or a PNG image which will be displayed while your screen is locked. | ||
| 44 | .IP \[bu] | ||
| 45 | You can specify whether i3lock should bell upon a wrong password. | ||
| 46 | .IP \[bu] | ||
| 47 | i3lock uses PAM and therefore is compatible with LDAP, etc. | ||
| 48 | |||
| 49 | |||
| 50 | .SH OPTIONS | ||
| 51 | .TP | ||
| 52 | .B \-v, \-\-version | ||
| 53 | Display the version of your | ||
| 54 | .B i3lock | ||
| 55 | |||
| 56 | .TP | ||
| 57 | .B \-n, \-\-nofork | ||
| 58 | Don't fork after starting. | ||
| 59 | |||
| 60 | .TP | ||
| 61 | .B \-b, \-\-beep | ||
| 62 | Enable beeping. Be sure to not do this when you are about to annoy other people, | ||
| 63 | like when opening your laptop in a boring lecture. | ||
| 64 | |||
| 65 | .TP | ||
| 66 | .B \-u, \-\-no-unlock-indicator | ||
| 67 | Disable the unlock indicator. i3lock will by default show an unlock indicator | ||
| 68 | after pressing keys. This will give feedback for every keypress and it will | ||
| 69 | show you the current PAM state (whether your password is currently being | ||
| 70 | verified or whether it is wrong). | ||
| 71 | |||
| 72 | .TP | ||
| 73 | .BI \-i\ path \fR,\ \fB\-\-image= path | ||
| 74 | Display the given PNG image instead of a blank screen. | ||
| 75 | |||
| 76 | .TP | ||
| 77 | .BI \fB\-\-raw= format | ||
| 78 | Read the image given by \-\-image as a raw image instead of PNG. The argument is the image's format | ||
| 79 | as <width>x<height>:<pixfmt>. The supported pixel formats are: | ||
| 80 | \'native', 'rgb', 'xrgb', 'rgbx', 'bgr', 'xbgr', and 'bgrx'. | ||
| 81 | The "native" pixel format expects a pixel as a 32-bit (4-byte) integer in | ||
| 82 | the machine's native endianness, with the upper 8 bits unused. Red, green and blue are stored in | ||
| 83 | the remaining bits, in that order. | ||
| 84 | |||
| 85 | .BR Example: | ||
| 86 | .Vb 6 | ||
| 87 | \& --raw=1920x1080:rgb | ||
| 88 | .Ve | ||
| 89 | |||
| 90 | .BR | ||
| 91 | You can use ImageMagick’s | ||
| 92 | .IR convert(1) | ||
| 93 | program 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 | |||
| 100 | This allows you to load a variety of image formats without i3lock having to | ||
| 101 | support each one explicitly. | ||
| 102 | |||
| 103 | .TP | ||
| 104 | .BI \-c\ rrggbb \fR,\ \fB\-\-color= rrggbb | ||
| 105 | Turn the screen into the given color instead of white. Color must be given in 3-byte | ||
| 106 | format: rrggbb (i.e. ff0000 is red). | ||
| 107 | |||
| 108 | .TP | ||
| 109 | .B \-t, \-\-tiling | ||
| 110 | If 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 | ||
| 115 | If you specify "default", | ||
| 116 | .B i3lock | ||
| 117 | does not hide your mouse pointer. If you specify "win", | ||
| 118 | .B i3lock | ||
| 119 | displays a hardcoded Windows-Pointer (thus enabling you to mess with your | ||
| 120 | friends by using a screenshot of a Windows desktop as a locking-screen). | ||
| 121 | |||
| 122 | .TP | ||
| 123 | .B \-e, \-\-ignore-empty-password | ||
| 124 | When an empty password is provided by the user, do not validate | ||
| 125 | it. Without this option, the empty password will be provided to PAM | ||
| 126 | and, if invalid, the user will have to wait a few seconds before | ||
| 127 | another try. This can be useful if the XF86ScreenSaver key is used to | ||
| 128 | put a laptop to sleep and bounce on resume or if you happen to wake up | ||
| 129 | your computer with the enter key. | ||
| 130 | |||
| 131 | .TP | ||
| 132 | .B \-f, \-\-show-failed-attempts | ||
| 133 | Show the number of failed attempts, if any. | ||
| 134 | |||
| 135 | .TP | ||
| 136 | .B \-\-debug | ||
| 137 | Enables debug logging. | ||
| 138 | Note, that this will log the password used for authentication to stdout. | ||
| 139 | |||
| 140 | .SH DPMS | ||
| 141 | |||
| 142 | The \-d (\-\-dpms) option was removed from i3lock in version 2.8. There were | ||
| 143 | plenty of use-cases that were not properly addressed, and plenty of bugs | ||
| 144 | surrounding that feature. While features are not normally removed from i3 and | ||
| 145 | its tools, we felt the need to make an exception in this case. | ||
| 146 | |||
| 147 | Users who wish to explicitly enable DPMS only when their screen is locked can | ||
| 148 | use 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 | |||
| 161 | The \-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 | ||
| 171 | Michael Stapelberg <michael+i3lock at stapelberg dot de> | ||
| 172 | |||
| 173 | Jan-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 | |||
| 58 | typedef void (*ev_callback_t)(EV_P_ ev_timer *w, int revents); | ||
| 59 | static void input_done(void); | ||
| 60 | |||
| 61 | char color[7] = "ffffff"; | ||
| 62 | uint32_t last_resolution[2]; | ||
| 63 | xcb_window_t win; | ||
| 64 | static xcb_cursor_t cursor; | ||
| 65 | #ifndef __OpenBSD__ | ||
| 66 | static pam_handle_t *pam_handle; | ||
| 67 | static bool pam_cleanup; | ||
| 68 | #endif | ||
| 69 | int input_position = 0; | ||
| 70 | /* Holds the password you enter (in UTF-8). */ | ||
| 71 | static char password[512]; | ||
| 72 | static bool beep = false; | ||
| 73 | bool debug_mode = false; | ||
| 74 | bool unlock_indicator = true; | ||
| 75 | char *modifier_string = NULL; | ||
| 76 | static bool dont_fork = false; | ||
| 77 | struct ev_loop *main_loop; | ||
| 78 | static struct ev_timer *clear_auth_wrong_timeout; | ||
| 79 | static struct ev_timer *clear_indicator_timeout; | ||
| 80 | static struct ev_timer *discard_passwd_timeout; | ||
| 81 | extern unlock_state_t unlock_state; | ||
| 82 | extern auth_state_t auth_state; | ||
| 83 | int failed_attempts = 0; | ||
| 84 | bool show_failed_attempts = false; | ||
| 85 | bool retry_verification = false; | ||
| 86 | |||
| 87 | static struct xkb_state *xkb_state; | ||
| 88 | static struct xkb_context *xkb_context; | ||
| 89 | static struct xkb_keymap *xkb_keymap; | ||
| 90 | static struct xkb_compose_table *xkb_compose_table; | ||
| 91 | static struct xkb_compose_state *xkb_compose_state; | ||
| 92 | static uint8_t xkb_base_event; | ||
| 93 | static uint8_t xkb_base_error; | ||
| 94 | static int randr_base = -1; | ||
| 95 | |||
| 96 | cairo_surface_t *img = NULL; | ||
| 97 | bool tile = false; | ||
| 98 | bool ignore_empty_password = false; | ||
| 99 | bool 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 | */ | ||
| 108 | void 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 | */ | ||
| 118 | static 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 | */ | ||
| 152 | static 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 | */ | ||
| 177 | static 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 | |||
| 195 | ev_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 | |||
| 212 | ev_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 | */ | ||
| 224 | static 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 | */ | ||
| 236 | static 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 | |||
| 257 | static void clear_indicator_cb(EV_P_ ev_timer *w, int revents) { | ||
| 258 | clear_indicator(); | ||
| 259 | STOP_TIMER(clear_indicator_timeout); | ||
| 260 | } | ||
| 261 | |||
| 262 | static void clear_input(void) { | ||
| 263 | input_position = 0; | ||
| 264 | clear_password_memory(); | ||
| 265 | password[input_position] = '\0'; | ||
| 266 | } | ||
| 267 | |||
| 268 | static void discard_passwd_cb(EV_P_ ev_timer *w, int revents) { | ||
| 269 | clear_input(); | ||
| 270 | STOP_TIMER(discard_passwd_timeout); | ||
| 271 | } | ||
| 272 | |||
| 273 | static 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 | |||
| 369 | static void redraw_timeout(EV_P_ ev_timer *w, int revents) { | ||
| 370 | redraw_screen(); | ||
| 371 | STOP_TIMER(w); | ||
| 372 | } | ||
| 373 | |||
| 374 | static 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 | */ | ||
| 390 | static 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 | */ | ||
| 548 | static 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 | */ | ||
| 564 | static 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 | */ | ||
| 616 | void 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 | |||
| 644 | static 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 | |||
| 656 | struct raw_pixel_format { | ||
| 657 | int bpp; | ||
| 658 | int red; | ||
| 659 | int green; | ||
| 660 | int blue; | ||
| 661 | }; | ||
| 662 | |||
| 663 | static 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>) | ||
| 690 | static const struct raw_pixel_format raw_fmt_rgb = {3, 0, 1, 2}; | ||
| 691 | static const struct raw_pixel_format raw_fmt_rgbx = {4, 0, 1, 2}; | ||
| 692 | static const struct raw_pixel_format raw_fmt_xrgb = {4, 1, 2, 3}; | ||
| 693 | static const struct raw_pixel_format raw_fmt_bgr = {3, 2, 1, 0}; | ||
| 694 | static const struct raw_pixel_format raw_fmt_bgrx = {4, 2, 1, 0}; | ||
| 695 | static const struct raw_pixel_format raw_fmt_xbgr = {4, 3, 2, 1}; | ||
| 696 | |||
| 697 | static 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 | |||
| 790 | static 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 | */ | ||
| 825 | static 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 | */ | ||
| 858 | static 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 | */ | ||
| 866 | static 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 | */ | ||
| 875 | static 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 | */ | ||
| 891 | static 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 | */ | ||
| 959 | static 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 | |||
| 1008 | int 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 | |||
| 54 | AC_DEFUN([AX_APPEND_FLAG], | ||
| 55 | [dnl | ||
| 56 | AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF | ||
| 57 | AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])]) | ||
| 58 | AS_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 | ]) | ||
| 70 | AS_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 | |||
| 63 | AC_DEFUN([AX_FLAGS_WARN_ALL],[dnl | ||
| 64 | AS_VAR_PUSHDEF([FLAGS],[_AC_LANG_PREFIX[]FLAGS])dnl | ||
| 65 | AS_VAR_PUSHDEF([VAR],[ac_cv_[]_AC_LANG_ABBREV[]flags_warn_all])dnl | ||
| 66 | AC_CACHE_CHECK([m4_ifval($1,$1,FLAGS) for maximum warnings], | ||
| 67 | VAR,[VAR="no, unknown" | ||
| 68 | ac_save_[]FLAGS="$[]FLAGS" | ||
| 69 | for ac_arg dnl | ||
| 70 | in "-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 | # | ||
| 80 | do 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]) | ||
| 83 | done | ||
| 84 | FLAGS="$ac_save_[]FLAGS" | ||
| 85 | ]) | ||
| 86 | AS_VAR_POPDEF([FLAGS])dnl | ||
| 87 | AX_REQUIRE_DEFINED([AX_APPEND_FLAG]) | ||
| 88 | case ".$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])]) ;; | ||
| 92 | esac | ||
| 93 | AS_VAR_POPDEF([VAR])dnl | ||
| 94 | ])dnl AX_FLAGS_WARN_ALL | ||
| 95 | dnl implementation tactics: | ||
| 96 | dnl the for-argument contains a list of options. The first part of | ||
| 97 | dnl these does only exist to detect the compiler - usually it is | ||
| 98 | dnl a global option to enable -ansi or -extrawarnings. All other | ||
| 99 | dnl compilers will fail about it. That was needed since a lot of | ||
| 100 | dnl compilers will give false positives for some option-syntax | ||
| 101 | dnl like -Woption or -Xoption as they think of it is a pass-through | ||
| 102 | dnl to later compile stages or something. The "%" is used as a | ||
| 103 | dnl delimiter. A non-option comment can be given after "%%" marks | ||
| 104 | dnl which will be shown but not added to the respective C/CXXFLAGS. | ||
| 105 | |||
| 106 | AC_DEFUN([AX_CFLAGS_WARN_ALL],[dnl | ||
| 107 | AC_LANG_PUSH([C]) | ||
| 108 | AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) | ||
| 109 | AC_LANG_POP([C]) | ||
| 110 | ]) | ||
| 111 | |||
| 112 | AC_DEFUN([AX_CXXFLAGS_WARN_ALL],[dnl | ||
| 113 | AC_LANG_PUSH([C++]) | ||
| 114 | AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) | ||
| 115 | AC_LANG_POP([C++]) | ||
| 116 | ]) | ||
| 117 | |||
| 118 | AC_DEFUN([AX_FCFLAGS_WARN_ALL],[dnl | ||
| 119 | AC_LANG_PUSH([Fortran]) | ||
| 120 | AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) | ||
| 121 | AC_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 | |||
| 60 | AC_DEFUN([AX_CHECK_COMPILE_FLAG], | ||
| 61 | [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF | ||
| 62 | AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl | ||
| 63 | AC_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]) | ||
| 70 | AS_VAR_IF(CACHEVAR,yes, | ||
| 71 | [m4_default([$2], :)], | ||
| 72 | [m4_default([$3], :)]) | ||
| 73 | AS_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 | |||
| 47 | AC_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 | |||
| 66 | AC_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="" ; | ||
| 70 | dnl 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 ;]) | ||
| 80 | dnl 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 | |||
| 60 | AC_DEFUN([AX_CHECK_LINK_FLAG], | ||
| 61 | [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF | ||
| 62 | AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_ldflags_$4_$1])dnl | ||
| 63 | AC_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]) | ||
| 70 | AS_VAR_IF(CACHEVAR,yes, | ||
| 71 | [m4_default([$2], :)], | ||
| 72 | [m4_default([$3], :)]) | ||
| 73 | AS_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 | |||
| 75 | AC_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 | ||
| 194 | CODE_COVERAGE_DIRECTORY ?= $(top_builddir) | ||
| 195 | CODE_COVERAGE_OUTPUT_FILE ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info | ||
| 196 | CODE_COVERAGE_OUTPUT_DIRECTORY ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage | ||
| 197 | CODE_COVERAGE_BRANCH_COVERAGE ?= | ||
| 198 | CODE_COVERAGE_LCOV_SHOPTS_DEFAULT ?= $(if $(CODE_COVERAGE_BRANCH_COVERAGE),\ | ||
| 199 | --rc lcov_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE)) | ||
| 200 | CODE_COVERAGE_LCOV_SHOPTS ?= $(CODE_COVERAGE_LCOV_SHOPTS_DEFAULT) | ||
| 201 | CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH ?= --gcov-tool "$(GCOV)" | ||
| 202 | CODE_COVERAGE_LCOV_OPTIONS_DEFAULT ?= $(CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH) | ||
| 203 | CODE_COVERAGE_LCOV_OPTIONS ?= $(CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) | ||
| 204 | CODE_COVERAGE_LCOV_RMOPTS_DEFAULT ?= | ||
| 205 | CODE_COVERAGE_LCOV_RMOPTS ?= $(CODE_COVERAGE_LCOV_RMOPTS_DEFAULT) | ||
| 206 | CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT ?=\ | ||
| 207 | $(if $(CODE_COVERAGE_BRANCH_COVERAGE),\ | ||
| 208 | --rc genhtml_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE)) | ||
| 209 | CODE_COVERAGE_GENHTML_OPTIONS ?= $(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULTS) | ||
| 210 | CODE_COVERAGE_IGNORE_PATTERN ?= | ||
| 211 | |||
| 212 | code_coverage_v_lcov_cap = $(code_coverage_v_lcov_cap_$(V)) | ||
| 213 | code_coverage_v_lcov_cap_ = $(code_coverage_v_lcov_cap_$(AM_DEFAULT_VERBOSITY)) | ||
| 214 | code_coverage_v_lcov_cap_0 = @echo " LCOV --capture"\ | ||
| 215 | $(CODE_COVERAGE_OUTPUT_FILE); | ||
| 216 | code_coverage_v_lcov_ign = $(code_coverage_v_lcov_ign_$(V)) | ||
| 217 | code_coverage_v_lcov_ign_ = $(code_coverage_v_lcov_ign_$(AM_DEFAULT_VERBOSITY)) | ||
| 218 | code_coverage_v_lcov_ign_0 = @echo " LCOV --remove /tmp/*"\ | ||
| 219 | $(CODE_COVERAGE_IGNORE_PATTERN); | ||
| 220 | code_coverage_v_genhtml = $(code_coverage_v_genhtml_$(V)) | ||
| 221 | code_coverage_v_genhtml_ = $(code_coverage_v_genhtml_$(AM_DEFAULT_VERBOSITY)) | ||
| 222 | code_coverage_v_genhtml_0 = @echo " GEN " $(CODE_COVERAGE_OUTPUT_DIRECTORY); | ||
| 223 | code_coverage_quiet = $(code_coverage_quiet_$(V)) | ||
| 224 | code_coverage_quiet_ = $(code_coverage_quiet_$(AM_DEFAULT_VERBOSITY)) | ||
| 225 | code_coverage_quiet_0 = --quiet | ||
| 226 | |||
| 227 | # sanitizes the test-name: replaces with underscores: dashes and dots | ||
| 228 | code_coverage_sanitize = $(subst -,_,$(subst .,_,$(1))) | ||
| 229 | |||
| 230 | # Use recursive makes in order to ignore errors during check | ||
| 231 | check-code-coverage: | ||
| 232 | ifeq ($(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 | ||
| 235 | else | ||
| 236 | @echo "Need to reconfigure with --enable-code-coverage" | ||
| 237 | endif | ||
| 238 | |||
| 239 | # Capture code coverage data | ||
| 240 | code-coverage-capture: code-coverage-capture-hook | ||
| 241 | ifeq ($(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" | ||
| 247 | else | ||
| 248 | @echo "Need to reconfigure with --enable-code-coverage" | ||
| 249 | endif | ||
| 250 | |||
| 251 | # Hook rule executed before code-coverage-capture, overridable by the user | ||
| 252 | code-coverage-capture-hook: | ||
| 253 | |||
| 254 | ifeq ($(CODE_COVERAGE_ENABLED),yes) | ||
| 255 | clean: code-coverage-clean | ||
| 256 | code-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 | ||
| 260 | endif | ||
| 261 | |||
| 262 | GITIGNOREFILES ?= | ||
| 263 | GITIGNOREFILES += $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_DIRECTORY) | ||
| 264 | |||
| 265 | A''M_DISTCHECK_CONFIGURE_FLAGS ?= | ||
| 266 | A''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 | |||
| 62 | AC_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 | |||
| 84 | AC_DEFUN([AX_ENABLE_BUILDDIR],[ | ||
| 85 | AC_REQUIRE([AC_CANONICAL_HOST])[]dnl | ||
| 86 | AC_REQUIRE([AC_CANONICAL_TARGET])[]dnl | ||
| 87 | AC_REQUIRE([AX_CONFIGURE_ARGS])[]dnl | ||
| 88 | AC_REQUIRE([AM_AUX_DIR_EXPAND])[]dnl | ||
| 89 | AC_BEFORE([$0],[AM_INIT_AUTOMAKE])dnl | ||
| 90 | AS_VAR_PUSHDEF([SUB],[ax_enable_builddir])dnl | ||
| 91 | AS_VAR_PUSHDEF([AUX],[ax_enable_builddir_auxdir])dnl | ||
| 92 | AS_VAR_PUSHDEF([SED],[ax_enable_builddir_sed])dnl | ||
| 93 | SUB="." | ||
| 94 | AC_ARG_ENABLE([builddir], AS_HELP_STRING( | ||
| 95 | [--disable-builddir],[disable automatic build in subdir of sources]) | ||
| 96 | ,[SUB="$enableval"], [SUB="auto"]) | ||
| 97 | if test ".$ac_srcdir_defaulted" != ".no" ; then | ||
| 98 | if 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 | ||
| 131 | fi fi | ||
| 132 | test ".$SUB" = ".auto" && SUB="." | ||
| 133 | dnl ac_path_prog uses "set dummy" to override $@ which would defeat the "exec" | ||
| 134 | AC_PATH_PROG(SED,gsed sed, sed) | ||
| 135 | AUX="$am_aux_dir" | ||
| 136 | AS_VAR_POPDEF([SED])dnl | ||
| 137 | AS_VAR_POPDEF([AUX])dnl | ||
| 138 | AS_VAR_POPDEF([SUB])dnl | ||
| 139 | AC_CONFIG_COMMANDS([buildir],[dnl .............. config.status .............. | ||
| 140 | AS_VAR_PUSHDEF([SUB],[ax_enable_builddir])dnl | ||
| 141 | AS_VAR_PUSHDEF([TOP],[top_srcdir])dnl | ||
| 142 | AS_VAR_PUSHDEF([SRC],[ac_top_srcdir])dnl | ||
| 143 | AS_VAR_PUSHDEF([AUX],[ax_enable_builddir_auxdir])dnl | ||
| 144 | AS_VAR_PUSHDEF([SED],[ax_enable_builddir_sed])dnl | ||
| 145 | pushdef([END],[Makefile.mk])dnl | ||
| 146 | pushdef([_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 | ||
| 171 | x | ||
| 172 | /^\$/bS | ||
| 173 | x | ||
| 174 | /\\\\\$/{H;d;} | ||
| 175 | {H;s/.*//;x;} | ||
| 176 | bM | ||
| 177 | :S | ||
| 178 | x | ||
| 179 | /\\\\\$/{h;d;} | ||
| 180 | {h;s/.*//;x;} | ||
| 181 | :M | ||
| 182 | s/\\(\\n\\) /\\1 /g | ||
| 183 | /^ /d | ||
| 184 | /^[[ ]]*[[\\#]]/d | ||
| 185 | /^VPATH *=/d | ||
| 186 | s/^srcdir *=.*/srcdir = ./ | ||
| 187 | s/^top_srcdir *=.*/top_srcdir = ./ | ||
| 188 | /[[:=]]/!d | ||
| 189 | /^\\./d | ||
| 190 | dnl Now handle rules (i.e. lines containing ":" but not " = "). | ||
| 191 | / = /b | ||
| 192 | / .= /b | ||
| 193 | /:/!b | ||
| 194 | s/:.*/:/ | ||
| 195 | s/ / /g | ||
| 196 | s/ \\([[a-z]][[a-z-]]*[[a-zA-Z0-9]]\\)\\([[ :]]\\)/ \\1 \\1[]_ALL\\2/g | ||
| 197 | s/^\\([[a-z]][[a-z-]]*[[a-zA-Z0-9]]\\)\\([[ :]]\\)/\\1 \\1[]_ALL\\2/ | ||
| 198 | s/ / /g | ||
| 199 | /^all all[]_ALL[[ :]]/i\\ | ||
| 200 | all-configured : all[]_ALL | ||
| 201 | dnl dist-all exists... and would make for dist-all-all | ||
| 202 | s/ [[a-zA-Z0-9-]]*[]_ALL [[a-zA-Z0-9-]]*[]_ALL[]_ALL//g | ||
| 203 | /[]_ALL[]_ALL/d | ||
| 204 | a\\ | ||
| 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 | ||
| 215 | dnl 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 | ||
| 227 | dnl 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 | ||
| 239 | dnl 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 | ||
| 287 | popdef([END])dnl | ||
| 288 | AS_VAR_POPDEF([SED])dnl | ||
| 289 | AS_VAR_POPDEF([AUX])dnl | ||
| 290 | AS_VAR_POPDEF([SRC])dnl | ||
| 291 | AS_VAR_POPDEF([TOP])dnl | ||
| 292 | AS_VAR_POPDEF([SUB])dnl | ||
| 293 | ],[dnl | ||
| 294 | ax_enable_builddir_srcdir="$srcdir" # $srcdir | ||
| 295 | ax_enable_builddir_host="$HOST" # $HOST / $host | ||
| 296 | ax_enable_builddir_version="$VERSION" # $VERSION | ||
| 297 | ax_enable_builddir_package="$PACKAGE" # $PACKAGE | ||
| 298 | ax_enable_builddir_auxdir="$ax_enable_builddir_auxdir" # $AUX | ||
| 299 | ax_enable_builddir_sed="$ax_enable_builddir_sed" # $SED | ||
| 300 | ax_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 | |||
| 74 | AC_DEFUN([AX_EXTEND_SRCDIR], | ||
| 75 | [dnl | ||
| 76 | AS_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 | |||
| 35 | AC_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 | |||
| 78 | AC_DEFUN([AX_SANITIZERS], | ||
| 79 | [AX_REQUIRE_DEFINED([AX_CHECK_COMPILE_FLAG]) | ||
| 80 | AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG]) | ||
| 81 | AX_REQUIRE_DEFINED([AX_APPEND_FLAG]) | ||
| 82 | AC_ARG_ENABLE(sanitizers, | ||
| 83 | AS_HELP_STRING( | ||
| 84 | [--enable-sanitizers], | ||
| 85 | [enable all known sanitizers]), | ||
| 86 | [ax_sanitizers_default=$enableval], | ||
| 87 | [ax_sanitizers_default=]) | ||
| 88 | ax_enabled_sanitizers= | ||
| 89 | m4_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 | |||
| 108 | AS_IF([test "x$ax_sanitizer_enabled" = "xyes"], [ | ||
| 109 | dnl Not using AX_APPEND_COMPILE_FLAGS and AX_APPEND_LINK_FLAGS because they | ||
| 110 | dnl 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], []) | ||
| 114 | dnl If and only if libtool is being used, LDFLAGS needs to contain -Wc,-fsanitize=…. | ||
| 115 | dnl See e.g. https://sources.debian.net/src/systemd/231-7/configure.ac/?hl=128#L135 | ||
| 116 | dnl TODO: how can recognize that situation and add -Wc,? | ||
| 117 | AX_APPEND_FLAG([-fsanitize=[]mysan], [LDFLAGS]) | ||
| 118 | dnl 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], [])]) | ||
| 123 | dnl TODO: at least for clang, we should specify exactly -O1, not -O2 or -O0, so that performance is reasonable but stacktraces are not tampered with (due to inlining), see http://clang.llvm.org/docs/AddressSanitizer.html#usage | ||
| 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 | |||
| 6 | auth include login | ||
| @@ -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. */ | ||
| 22 | int xr_screens = 0; | ||
| 23 | |||
| 24 | /* The resolutions of the currently present Xinerama screens. */ | ||
| 25 | Rect *xr_resolutions = NULL; | ||
| 26 | |||
| 27 | static bool xinerama_active; | ||
| 28 | static bool has_randr = false; | ||
| 29 | static bool has_randr_1_5 = false; | ||
| 30 | extern bool debug_mode; | ||
| 31 | |||
| 32 | void _xinerama_init(void); | ||
| 33 | |||
| 34 | void 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 | |||
| 73 | void _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 | */ | ||
| 100 | static 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 | */ | ||
| 160 | static 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 | |||
| 244 | void _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 | |||
| 287 | void 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 | } | ||
| @@ -0,0 +1,17 @@ | |||
| 1 | #ifndef _XINERAMA_H | ||
| 2 | #define _XINERAMA_H | ||
| 3 | |||
| 4 | typedef struct Rect { | ||
| 5 | int16_t x; | ||
| 6 | int16_t y; | ||
| 7 | uint16_t width; | ||
| 8 | uint16_t height; | ||
| 9 | } Rect; | ||
| 10 | |||
| 11 | extern int xr_screens; | ||
| 12 | extern Rect *xr_resolutions; | ||
| 13 | |||
| 14 | void randr_init(int *event_base, xcb_window_t root); | ||
| 15 | void 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 | ||
| 2 | FROM debian:sid | ||
| 3 | |||
| 4 | RUN echo force-unsafe-io > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup | ||
| 5 | # Paper over occasional network flakiness of some mirrors. | ||
| 6 | RUN 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) | ||
| 17 | RUN 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 | |||
| 25 | WORKDIR /usr/src | ||
diff --git a/unlock_indicator.c b/unlock_indicator.c new file mode 100644 index 0000000..d6237ed --- /dev/null +++ b/unlock_indicator.c | |||
| @@ -0,0 +1,361 @@ | |||
| 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 | |||
| 34 | extern 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. */ | ||
| 38 | extern int input_position; | ||
| 39 | |||
| 40 | /* The lock window. */ | ||
| 41 | extern xcb_window_t win; | ||
| 42 | |||
| 43 | /* The current resolution of the X11 root window. */ | ||
| 44 | extern uint32_t last_resolution[2]; | ||
| 45 | |||
| 46 | /* Whether the unlock indicator is enabled (defaults to true). */ | ||
| 47 | extern bool unlock_indicator; | ||
| 48 | |||
| 49 | /* List of pressed modifiers, or NULL if none are pressed. */ | ||
| 50 | extern char *modifier_string; | ||
| 51 | |||
| 52 | /* A Cairo surface containing the specified image (-i), if any. */ | ||
| 53 | extern cairo_surface_t *img; | ||
| 54 | |||
| 55 | /* Whether the image should be tiled. */ | ||
| 56 | extern bool tile; | ||
| 57 | /* The background color to use (in hex). */ | ||
| 58 | extern char color[7]; | ||
| 59 | |||
| 60 | /* Whether the failed attempts should be displayed. */ | ||
| 61 | extern bool show_failed_attempts; | ||
| 62 | /* Number of failed unlock attempts. */ | ||
| 63 | extern int failed_attempts; | ||
| 64 | |||
| 65 | /******************************************************************************* | ||
| 66 | * Variables defined in xcb.c. | ||
| 67 | ******************************************************************************/ | ||
| 68 | |||
| 69 | /* The root screen, to determine the DPI. */ | ||
| 70 | extern xcb_screen_t *screen; | ||
| 71 | |||
| 72 | /******************************************************************************* | ||
| 73 | * Local variables. | ||
| 74 | ******************************************************************************/ | ||
| 75 | |||
| 76 | /* Cache the screen’s visual, necessary for creating a Cairo context. */ | ||
| 77 | static xcb_visualtype_t *vistype; | ||
| 78 | |||
| 79 | /* Maintain the current unlock/PAM state to draw the appropriate unlock | ||
| 80 | * indicator. */ | ||
| 81 | unlock_state_t unlock_state; | ||
| 82 | auth_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 | */ | ||
| 89 | xcb_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 | */ | ||
| 339 | void 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 | */ | ||
| 355 | void 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 | |||
| 4 | typedef 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 | |||
| 14 | typedef 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 | |||
| 22 | xcb_pixmap_t draw_image(uint32_t* resolution); | ||
| 23 | void redraw_screen(void); | ||
| 24 | void clear_indicator(void); | ||
| 25 | |||
| 26 | #endif | ||
| @@ -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 | |||
| 27 | extern auth_state_t auth_state; | ||
| 28 | |||
| 29 | xcb_connection_t *conn; | ||
| 30 | xcb_screen_t *screen; | ||
| 31 | |||
| 32 | static xcb_atom_t _NET_WM_BYPASS_COMPOSITOR = XCB_NONE; | ||
| 33 | void _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 | |||
| 55 | static 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 | |||
| 61 | static 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 | |||
| 70 | static 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 | |||
| 76 | static 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 | |||
| 87 | xcb_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 | |||
| 109 | xcb_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 | |||
| 126 | xcb_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 | */ | ||
| 212 | bool 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 | |||
| 309 | xcb_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 | |||
| 372 | static xcb_atom_t _NET_ACTIVE_WINDOW = XCB_NONE; | ||
| 373 | void _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 | |||
| 392 | xcb_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 | |||
| 414 | out_prop: | ||
| 415 | free(prop_reply); | ||
| 416 | out: | ||
| 417 | return result; | ||
| 418 | } | ||
| 419 | |||
| 420 | void 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 | } | ||
| @@ -0,0 +1,17 @@ | |||
| 1 | #ifndef _XCB_H | ||
| 2 | #define _XCB_H | ||
| 3 | |||
| 4 | #include <xcb/xcb.h> | ||
| 5 | |||
| 6 | extern xcb_connection_t *conn; | ||
| 7 | extern xcb_screen_t *screen; | ||
| 8 | |||
| 9 | xcb_visualtype_t *get_root_visual_type(xcb_screen_t *s); | ||
| 10 | xcb_pixmap_t create_bg_pixmap(xcb_connection_t *conn, xcb_screen_t *scr, u_int32_t *resolution, char *color); | ||
| 11 | xcb_window_t open_fullscreen_window(xcb_connection_t *conn, xcb_screen_t *scr, char *color, xcb_pixmap_t pixmap); | ||
| 12 | bool grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_t cursor, int tries); | ||
| 13 | xcb_cursor_t create_cursor(xcb_connection_t *conn, xcb_screen_t *screen, xcb_window_t win, int choice); | ||
| 14 | xcb_window_t find_focused_window(xcb_connection_t *conn, const xcb_window_t root); | ||
| 15 | void set_focused_window(xcb_connection_t *conn, const xcb_window_t root, const xcb_window_t window); | ||
| 16 | |||
| 17 | #endif | ||
