├── .clang-format
├── .github
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
└── workflows
│ └── main.yml
├── .gitignore
├── CHANGELOG
├── LICENSE
├── README.md
├── ci
└── Dockerfile
├── dpi.c
├── i3lock.1
├── i3lock.c
├── include
├── cursors.h
├── dpi.h
├── i3lock.h
├── randr.h
├── unlock_indicator.h
└── xcb.h
├── meson.build
├── meson
└── meson-dist-script
├── meson_options.txt
├── pam
└── i3lock
├── randr.c
├── unlock_indicator.c
└── xcb.c
/.clang-format:
--------------------------------------------------------------------------------
1 | AllowShortBlocksOnASingleLine: false
2 | AllowShortFunctionsOnASingleLine: None
3 | AllowShortIfStatementsOnASingleLine: false
4 | AllowShortLoopsOnASingleLine: false
5 | AlwaysBreakBeforeMultilineStrings: false
6 | BasedOnStyle: google
7 | ColumnLimit: 0
8 | IndentWidth: 4
9 | InsertBraces: true
10 | PointerBindsToType: false
11 | SortIncludes: false
12 | SpaceBeforeParens: ControlStatements
13 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
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.
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
4 |
5 | ## I'm submitting a…
6 |
7 |
8 | [ ] Bug
9 | [ ] Feature Request
10 | [ ] Other (Please describe in detail)
11 |
12 |
13 | ## Current Behavior
14 |
15 |
16 | ## Expected Behavior
17 |
18 |
19 | ## Reproduction Instructions
20 |
24 |
25 | ## Environment
26 | Output of `i3lock --version`:
27 |
28 | i3lock version:
29 |
30 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: GitHub Actions
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 | jobs:
8 | build:
9 | name: build and test
10 | runs-on: ubuntu-latest
11 | strategy:
12 | fail-fast: false
13 | matrix:
14 | compiler: [gcc, clang]
15 | env:
16 | CC: ${{ matrix.compiler }}
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 | - run: git fetch --prune --unshallow
21 | - name: fetch or build Docker container
22 | run: |
23 | docker build --pull --no-cache --rm -t=i3lock -f ci/Dockerfile .
24 | docker run -e CC -v $PWD:/usr/src:rw i3lock /bin/sh -c 'git config --global --add safe.directory /usr/src && mkdir build && cd build && CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Werror" meson .. && ninja'
25 | formatting:
26 | name: Check formatting
27 | runs-on: ubuntu-latest
28 | steps:
29 | - uses: actions/checkout@v2
30 | - name: Install dependencies
31 | run: |
32 | sudo apt-get install -y clang-format-15
33 | - name: Check formatting
34 | run: clang-format-15 --dry-run --Werror $(git ls-files '*.c' 'include/*.h')
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /i3lock
2 | *.o
3 | tags
4 | *.swp
5 |
6 | # We recommend building in a subdirectory called build.
7 | # If you chose a different directory name,
8 | # it is up to you to arrange for it to be ignored by git,
9 | # e.g. by listing your directory in .git/info/exclude.
10 | /build
11 |
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 | 2024-03-16 i3lock 2.15
2 |
3 | • unlock indicator: display current keyboard layout when
4 | user types password (with --show-keyboard-layout flag).
5 |
6 | 2022-06-21 i3lock 2.14.1
7 |
8 | • unlock indicator: display only caps lock and num lock,
9 | not all modifiers like shift (which can leak information
10 | about your password to bystanders)
11 |
12 | 2022-05-28 i3lock 2.14
13 |
14 | • Change default background color to #a3a3a3
15 | See https://github.com/i3/i3lock/pull/300 for extensive
16 | discussion and rationale for this change.
17 | • Recommend using xss-lock to start i3lock in the README
18 | and i3lock.1 man page. xss-lock is the best way to ensure
19 | your screen truly is locked before your computer suspends.
20 | • Display modifier key warning before unlocking, too,
21 | not just on failed attempts like before.
22 | • Switch build system from autotools to meson.
23 |
24 | 2020-10-27 i3lock 2.13
25 |
26 | • Throw error when trying to start on Wayland
27 | • Use explicit_bzero() where available, not just on OpenBSD
28 | • avoid pixmap allocations in the redraw path
29 | • make --debug output go to stderr
30 | • unlock_indicator.c: fix build failure against gcc-10
31 | • fix: call pam_end in cleanup in main, not in event loop
32 | • set _NET_WM_BYPASS_COMPOSITOR hint to avoid flickering
33 |
34 | 2019-07-21 i3lock 2.12
35 |
36 | • remove stray \n from error messages
37 | • capitalize unlock indicator contents
38 | • set WM_CLASS property
39 | • reference modifier as “Super”, not “Win”
40 | • add --raw option to read image as raw bytes
41 |
42 | 2018-10-18 i3lock 2.11.1
43 |
44 | • Fix dist tarball by including I3LOCK_VERSION
45 |
46 | 2018-10-10 i3lock 2.11
47 |
48 | • Switch to autotools
49 | • Display an error when backspace is pressed without any input
50 | • Print an error when a non-PNG file is opened
51 | (i3lock only supports PNG files) (Thanks eplanet)
52 | • Don’t unnecessarily check the xcb_connect return value,
53 | it is known never to be NULL (Thanks SegFault42)
54 | • Fix memory leak when grabbing fails (Thanks karulont)
55 | • Respect Xft.dpi for determining the unlock indicator’s scale factor
56 | • Discard pending password verification attempts
57 | when a new password is entered (Thanks layus)
58 |
59 | 2017-11-25 i3lock 2.10
60 |
61 | • Only use -lpam when not on OpenBSD (Thanks Kaashif)
62 | • locale: treat empty string same as unset (Thanks Ran)
63 | • Fix overwrite of getopt optind (Thanks jakob)
64 | • Immediately hide the unlock indicator after ESC / C-u (Thanks Orestis)
65 | • Measure wall-clock time instead of CPU time for “locking” indicator.
66 | • SetInputFocus to the i3lock window to force-close context menus
67 | • Use RandR for learning about attached monitors
68 |
69 | 2017-06-21 i3lock 2.9.1
70 |
71 | • Fix version number mechanism (for --version)
72 | • Revert the fix for composited notifications, as it causes more issues than
73 | it solves:
74 | https://github.com/i3/i3lock/issues/130
75 | https://github.com/i3/i3lock/issues/128
76 |
77 | 2017-05-26 i3lock 2.9
78 |
79 | • i3lock.1: use signal names without SIG prefix
80 | • Removed obsolete inactivity timeout
81 | • Added version files for release tarball.
82 | • Set font face
83 | • Automatically unlock (without having to press ) one attempt which was
84 | entered while authenticating
85 | • Stop leaking the image_path dup
86 | • Displaying locking message when grabbing the pointer/keyboard
87 | • Display error message when locking failed
88 | • Add Enter on C-m
89 | • Change input slices to be exactly pi/3 in size instead of slightly more
90 | • Fix covering of composited notifications using the XComposite extension
91 | • Remove last traces of DPMS
92 | • Use bsd_auth(3) instead of PAM on OpenBSD
93 | • Restore intended behaviour and don't use mlock(2) on OpenBSD.
94 |
95 | 2016-06-04 i3lock 2.8
96 |
97 | • Remove DPMS support in favor of a wrapper script and xset(1).
98 | • Indicate that the --inactivity-timeout option takes an argument. (Thanks
99 | Kenneth Lyons)
100 | • fix pam_securetty: set PAM_TTY to getenv("DISPLAY")
101 | • Eat XKB_KEY_Delete and XKB_KEY_KP_Delete (Thanks bebehei)
102 | • Show unlock indicator if password was entered during PAM verification
103 | • Allow CTRL+J as enter and CTRL+H as backspace (Thanks Karl Tarbe)
104 | • Flush xcb connection after opening fullscreen window (Thanks martin)
105 | • Add support for `xss-lock --transfer-sleep-lock'
106 |
107 | 2015-05-20 i3lock 2.7
108 |
109 | • Die when the X11 connection breaks during runtime (Thanks Eduan)
110 | • Implement logging the number of failed attempts (Thanks koebi)
111 | • Ignore password validation is pam is in wrong state (Thanks Neodyblue)
112 | • Get current user with getpwuid() instead of using $ENV{USER} (Thanks Martin)
113 | • Add support for Compose and dead-keys with libxkbcommon.
114 | Requires libxkbcommon ≥ 0.5.0 (Thanks Daniel)
115 | • Format the source using clang-format.
116 | • Refresh pam credentials on successful authentication (for Kerberos and the
117 | like) (Thanks James)
118 | • List pressed modifiers on failed authentication (Thanks Deiz, Alexandre)
119 | • Only redraw the screen if the unlock indicator is actually used
120 | (Thanks Ingo)
121 | • Make pkg-config configurable for cross-compilation (Thanks Nikolay)
122 |
123 | 2014-07-18 i3lock 2.6
124 |
125 | • NEW DEPENDENCY: use libxkbcommon-x11 instead of libX11
126 | This helps us get rid of all code that directly uses libX11
127 | • Use cairo_status_to_string for descriptive errors.
128 | • Add `-e` option to not validate empty password.
129 | • Bugfix: update the initial keyboard modifier state (Thanks lee, Ran)
130 | • Re-raise i3lock when obscured in a separate process
131 | • Turn on the screen on successful authentication
132 | • Delay to turn off the screen after wrong passwd
133 | • Discard half-entered passwd after some inactivity
134 | • Ignore empty passwd after repeated Enter keypress
135 | • Scale the unlock indicator (for retina displays)
136 |
137 | 2013-06-09 i3lock 2.5
138 |
139 | • NEW DEPENDENCY: Use libxkbcommon for input handling
140 | This makes input handling much better for many edge cases.
141 | • Bugfix: fix argument parsing on ARM (s/char/int/)
142 | • Bugfix: free(reply) to avoid memory leak
143 | • Bugfix: Use ev_loop_fork after fork, fixes forking on kqueue based OSes
144 | • Bugfix: Fix centering the indicator in the no-xinerama case
145 | • Only use mlock() on Linux, FreeBSD (for example) requires root
146 | • promote the "could not load image" message from debug to normal
147 | • s/pam_message/pam_response/ (Thanks Tucos)
148 | • remove support for NOLIBCAIRO, cairo-xcb is widespread by now
149 | • Allow XKB_KEY_XF86ScreenSaver as synonym for enter
150 | This keysym is generated on convertible tablets by pressing a hardware
151 | lock/unlock button.
152 | • Allow passwordless PAM conversations (e.g. fingerprint)
153 | • Add ctrl+u password reset
154 | • Set window name to i3lock
155 |
156 | 2012-06-02 i3lock 2.4.1
157 |
158 | • Bugfix: Correctly center unlock indicator after reconfiguring screens
159 | (Thanks xeen)
160 | • Bugfix: Revert shift lock handling (broke uppercase letters)
161 | • Bugfix: Skip shift when getting the modifier mask (Thanks SardemFF7)
162 |
163 | 2012-04-01 i3lock 2.4
164 |
165 | • Bugfix: Fix background color when using cairo (Thanks Pascal)
166 | • Only output text when in debug mode (fixes problems with xautolock)
167 | • fallback when the image cannot be loaded
168 | • Use (void) instead of () for functions without args (Thanks fernandotcl)
169 |
170 | 2012-03-15 i3lock 2.3.1
171 |
172 | • Fix compilation on some systems
173 |
174 | 2012-03-15 i3lock 2.3
175 |
176 | • Implement a visual unlock indicator
177 | • Support ISO_Level5_Shift and Caps Lock
178 | • Lock the password buffer in memory, clear it in RAM after verifying
179 | • Fork after the window is visible, not before
180 | • Bugfix: Copy the color depth from parent (root) window instead of
181 | hardcoding a depth of 24
182 |
183 | 2011-11-06 i3lock 2.2
184 |
185 | • Don’t re-grab pointer/keyboard on MappingNotify. In some rare situations,
186 | this lead to some keypresses "slipping through" to the last focused window.
187 | • Correctly handle Mode_switch/ISO_Level3_Shift
188 | • Render to a pixmap which is used as background for the window instead of
189 | copying contents on every expose event
190 | • Handle screen resolution changes while screen is locked
191 | • Manpage: document arguments for every option
192 |
193 | 2011-05-13 i3lock 2.1
194 |
195 | • Accept return/backspace when the buffer of 512 bytes is full
196 | • Handle numpad keys correctly
197 | • Handle MappingNotify events
198 | • Correctly check for errors when connecting to X11
199 | • Add i3lock.pam to not rely on debian’s /etc/pam.d/other anymore
200 | • don’t display debug output
201 | • add NOLIBCAIRO flag to permit compilation without cairo
202 |
203 | 2010-09-05 i3lock 2.0
204 |
205 | • Complete rewrite of i3lock. Now using xcb instead of Xlib.
206 | • When a window obscures i3lock, it pushes itself back to the top again.
207 | • Display version when starting with -v
208 |
209 | 2009-08-02 i3lock 1.1
210 |
211 | • Implement background pictures (-i) and colors (-c)
212 |
213 | 2009-05-10 i3lock 1.0
214 |
215 | • Implement PAM support
216 | • Implement options for forking, beeping, DPMS
217 |
218 | 2009-05-01 i3lock 0.9
219 |
220 | • First release, forked from slock 0.9
221 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright © 2010, Michael Stapelberg and contributors
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions
5 | are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright
10 | notice, this list of conditions and the following disclaimer in the
11 | documentation and/or other materials provided with the distribution.
12 | 3. Neither the name of the copyright holder nor the names of its
13 | contributors may be used to endorse or promote products derived
14 | from this software without specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
22 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
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 |
48 | To test i3lock, you can directly run the `i3lock` command. To get out of it,
49 | enter your password and press enter.
50 |
51 | For a more permanent setup, we strongly recommend using `xss-lock` so that the
52 | screen is locked *before* your laptop suspends:
53 |
54 | ```
55 | xss-lock --transfer-sleep-lock -- i3lock --nofork
56 | ```
57 |
58 | On OpenBSD the `i3lock` binary needs to be setgid `auth` to call the
59 | authentication helpers, e.g. `/usr/libexec/auth/login_passwd`.
60 |
61 | Building i3lock
62 | ---------------
63 | We recommend you use the provided package from your distribution. Do not build
64 | i3lock unless you have a reason to do so.
65 |
66 | First install the dependencies listed in requirements section, then run these
67 | commands (might need to be adapted to your OS):
68 | ```
69 | rm -rf build/
70 | mkdir -p build && cd build/
71 |
72 | meson setup -Dprefix=/usr
73 | ninja
74 | ```
75 |
76 | Upstream
77 | --------
78 | Please submit pull requests to https://github.com/i3/i3lock
79 |
--------------------------------------------------------------------------------
/ci/Dockerfile:
--------------------------------------------------------------------------------
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 (for building),
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 meson 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
26 |
--------------------------------------------------------------------------------
/dpi.c:
--------------------------------------------------------------------------------
1 | /*
2 | * vim:ts=4:sw=4:expandtab
3 | *
4 | * i3 - an improved tiling window manager
5 | * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
6 | *
7 | */
8 | #include "dpi.h"
9 |
10 | #include
11 | #include
12 | #include
13 | #include
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 | }
109 | return ceil((dpi / 96.0) * logical);
110 | }
111 |
--------------------------------------------------------------------------------
/i3lock.1:
--------------------------------------------------------------------------------
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 RECOMMENDED USAGE
33 | .RB xss-lock
34 | .RB --transfer-sleep-lock
35 | .RB --
36 | .RB i3lock
37 | .RB --nofork
38 |
39 | Using
40 | .B xss-lock
41 | ensures that your screen is locked before your laptop suspends.
42 |
43 | Notably, using a systemd service file is not adequate, as it will not delay
44 | suspend until your screen is locked.
45 |
46 | .SH DESCRIPTION
47 | .B i3lock
48 | is a simple screen locker like slock. After starting it, you will see a white
49 | screen (you can configure the color/an image). You can return to your screen by
50 | entering your password.
51 |
52 | .SH IMPROVEMENTS
53 |
54 | .IP \[bu] 2
55 | 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)
56 | .IP \[bu]
57 | You can specify either a background color or a PNG image which will be displayed while your screen is locked.
58 | .IP \[bu]
59 | You can specify whether i3lock should bell upon a wrong password.
60 | .IP \[bu]
61 | i3lock uses PAM and therefore is compatible with LDAP, etc.
62 |
63 |
64 | .SH OPTIONS
65 | .TP
66 | .B \-v, \-\-version
67 | Display the version of your
68 | .B i3lock
69 |
70 | .TP
71 | .B \-n, \-\-nofork
72 | Don't fork after starting.
73 |
74 | .TP
75 | .B \-b, \-\-beep
76 | Enable beeping. Be sure to not do this when you are about to annoy other people,
77 | like when opening your laptop in a boring lecture.
78 |
79 | .TP
80 | .B \-u, \-\-no-unlock-indicator
81 | Disable the unlock indicator. i3lock will by default show an unlock indicator
82 | after pressing keys. This will give feedback for every keypress and it will
83 | show you the current PAM state (whether your password is currently being
84 | verified or whether it is wrong).
85 |
86 | .TP
87 | .BI \-i\ path \fR,\ \fB\-\-image= path
88 | Display the given PNG image instead of a blank screen.
89 |
90 | .TP
91 | .BI \fB\-\-raw= format
92 | Read the image given by \-\-image as a raw image instead of PNG. The argument is the image's format
93 | as x:. The supported pixel formats are:
94 | \(aqnative', \(aqrgb', \(aqxrgb', \(aqrgbx', \(aqbgr', \(aqxbgr', and \(aqbgrx'.
95 | The "native" pixel format expects a pixel as a 32-bit (4-byte) integer in
96 | the machine's native endianness, with the upper 8 bits unused. Red, green and blue are stored in
97 | the remaining bits, in that order.
98 |
99 | .BR Example:
100 | .Vb 6
101 | \& --raw=1920x1080:rgb
102 | .Ve
103 |
104 | .BR
105 | You can use ImageMagick’s
106 | .IR convert(1)
107 | program to feed raw images into i3lock:
108 |
109 | .BR
110 | .Vb 6
111 | \& convert wallpaper.jpg RGB:- | i3lock --raw 3840x2160:rgb --image /dev/stdin
112 | .Ve
113 |
114 | This allows you to load a variety of image formats without i3lock having to
115 | support each one explicitly.
116 |
117 | .TP
118 | .BI \-c\ rrggbb \fR,\ \fB\-\-color= rrggbb
119 | Turn the screen into the given color instead of white. Color must be given in 3-byte
120 | format: rrggbb (i.e. ff0000 is red).
121 |
122 | .TP
123 | .B \-t, \-\-tiling
124 | If an image is specified (via \-i) it will display the image tiled all over the screen
125 | (if it is a multi-monitor setup, the image is visible on all screens).
126 |
127 | .TP
128 | .BI \-p\ win|default \fR,\ \fB\-\-pointer= win|default
129 | If you specify "default",
130 | .B i3lock
131 | does not hide your mouse pointer. If you specify "win",
132 | .B i3lock
133 | displays a hardcoded Windows-Pointer (thus enabling you to mess with your
134 | friends by using a screenshot of a Windows desktop as a locking-screen).
135 |
136 | .TP
137 | .B \-e, \-\-ignore-empty-password
138 | When an empty password is provided by the user, do not validate
139 | it. Without this option, the empty password will be provided to PAM
140 | and, if invalid, the user will have to wait a few seconds before
141 | another try. This can be useful if the XF86ScreenSaver key is used to
142 | put a laptop to sleep and bounce on resume or if you happen to wake up
143 | your computer with the enter key.
144 |
145 | .TP
146 | .B \-f, \-\-show-failed-attempts
147 | Show the number of failed attempts, if any.
148 |
149 | .TP
150 | .B \-k, \-\-show-keyboard-layout
151 | Show the current keyboard layout.
152 |
153 | .TP
154 | .B \-\-debug
155 | Enables debug logging.
156 | Note, that this will log the password used for authentication to stdout.
157 |
158 | .SH DPMS
159 |
160 | The \-d (\-\-dpms) option was removed from i3lock in version 2.8. There were
161 | plenty of use-cases that were not properly addressed, and plenty of bugs
162 | surrounding that feature. While features are not normally removed from i3 and
163 | its tools, we felt the need to make an exception in this case.
164 |
165 | Users who wish to explicitly enable DPMS only when their screen is locked can
166 | use a wrapper script around i3lock like the following:
167 |
168 | .Vb 6
169 | \& #!/bin/sh
170 | \& revert() {
171 | \& xset dpms 0 0 0
172 | \& }
173 | \& trap revert HUP INT TERM
174 | \& xset +dpms dpms 5 5 5
175 | \& i3lock -n
176 | \& revert
177 | .Ve
178 |
179 | The \-I (-\-inactivity-timeout=seconds) was removed because it only makes sense with DPMS.
180 |
181 | .SH SEE ALSO
182 | .IR xss-lock(1)
183 | \- hooks up i3lock to the systemd login manager
184 |
185 | .IR convert(1)
186 | \- feed a wide variety of image formats to i3lock
187 |
188 | .SH AUTHOR
189 | Michael Stapelberg
190 |
--------------------------------------------------------------------------------
/i3lock.c:
--------------------------------------------------------------------------------
1 | /*
2 | * vim:ts=4:sw=4:expandtab
3 | *
4 | * © 2010 Michael Stapelberg
5 | *
6 | * See LICENSE for licensing information
7 | *
8 | */
9 | #include
10 |
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #ifdef __OpenBSD__
25 | #include
26 | #else
27 | #include
28 | #endif
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 | #ifdef HAVE_EXPLICIT_BZERO
38 | #include /* explicit_bzero(3) */
39 | #endif
40 | #include
41 | #include
42 |
43 | #include "i3lock.h"
44 | #include "xcb.h"
45 | #include "cursors.h"
46 | #include "unlock_indicator.h"
47 | #include "randr.h"
48 | #include "dpi.h"
49 |
50 | #define TSTAMP_N_SECS(n) (n * 1.0)
51 | #define TSTAMP_N_MINS(n) (60 * TSTAMP_N_SECS(n))
52 | #define START_TIMER(timer_obj, timeout, callback) \
53 | timer_obj = start_timer(timer_obj, timeout, callback)
54 | #define STOP_TIMER(timer_obj) \
55 | timer_obj = stop_timer(timer_obj)
56 |
57 | typedef void (*ev_callback_t)(EV_P_ ev_timer *w, int revents);
58 | static void input_done(void);
59 |
60 | char color[7] = "a3a3a3";
61 | uint32_t last_resolution[2];
62 | xcb_window_t win;
63 | static xcb_cursor_t cursor;
64 | #ifndef __OpenBSD__
65 | static pam_handle_t *pam_handle;
66 | static bool pam_cleanup;
67 | #endif
68 | int input_position = 0;
69 | /* Holds the password you enter (in UTF-8). */
70 | static char password[512];
71 | static bool beep = false;
72 | bool debug_mode = false;
73 | bool unlock_indicator = true;
74 | char *modifier_string = NULL;
75 | static bool dont_fork = false;
76 | struct ev_loop *main_loop;
77 | static struct ev_timer *clear_auth_wrong_timeout;
78 | static struct ev_timer *clear_indicator_timeout;
79 | static struct ev_timer *discard_passwd_timeout;
80 | extern unlock_state_t unlock_state;
81 | extern auth_state_t auth_state;
82 | int failed_attempts = 0;
83 | bool show_failed_attempts = false;
84 | bool show_keyboard_layout = false;
85 | bool retry_verification = false;
86 |
87 | struct xkb_state *xkb_state;
88 | static struct xkb_context *xkb_context;
89 | 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 | static 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 | * This function can be called when the user changes the XKB configuration,
118 | * so it must not leave unusable global state behind
119 | *
120 | */
121 | static bool load_keymap(void) {
122 | if (xkb_context == NULL) {
123 | if ((xkb_context = xkb_context_new(0)) == NULL) {
124 | fprintf(stderr, "[i3lock] could not create xkbcommon context\n");
125 | return false;
126 | }
127 | }
128 |
129 | int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn);
130 | DEBUG("device = %d\n", device_id);
131 | struct xkb_keymap *new_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0);
132 | if (new_keymap == NULL) {
133 | fprintf(stderr, "[i3lock] xkb_x11_keymap_new_from_device failed\n");
134 | return false;
135 | }
136 |
137 | struct xkb_state *new_state =
138 | xkb_x11_state_new_from_device(new_keymap, conn, device_id);
139 | if (new_state == NULL) {
140 | fprintf(stderr, "[i3lock] xkb_x11_state_new_from_device failed\n");
141 | return false;
142 | }
143 |
144 | /* Only update global state on success */
145 | xkb_state_unref(xkb_state);
146 | xkb_keymap_unref(xkb_keymap);
147 | xkb_state = new_state;
148 | xkb_keymap = new_keymap;
149 | return true;
150 | }
151 |
152 | /*
153 | * Loads the XKB compose table from the given locale.
154 | *
155 | */
156 | static bool load_compose_table(const char *locale) {
157 | xkb_compose_table_unref(xkb_compose_table);
158 |
159 | if ((xkb_compose_table = xkb_compose_table_new_from_locale(xkb_context, locale, 0)) == NULL) {
160 | fprintf(stderr, "[i3lock] xkb_compose_table_new_from_locale failed\n");
161 | return false;
162 | }
163 |
164 | struct xkb_compose_state *new_compose_state = xkb_compose_state_new(xkb_compose_table, 0);
165 | if (new_compose_state == NULL) {
166 | fprintf(stderr, "[i3lock] xkb_compose_state_new failed\n");
167 | return false;
168 | }
169 |
170 | xkb_compose_state_unref(xkb_compose_state);
171 | xkb_compose_state = new_compose_state;
172 |
173 | return true;
174 | }
175 |
176 | /*
177 | * Clears the memory which stored the password to be a bit safer against
178 | * cold-boot attacks.
179 | *
180 | */
181 | static void clear_password_memory(void) {
182 | #ifdef HAVE_EXPLICIT_BZERO
183 | /* Use explicit_bzero(3) which was explicitly designed not to be
184 | * optimized out by the compiler. */
185 | explicit_bzero(password, strlen(password));
186 | #else
187 | /* A volatile pointer to the password buffer to prevent the compiler from
188 | * optimizing this out. */
189 | volatile char *vpassword = password;
190 | for (size_t c = 0; c < sizeof(password); c++) {
191 | /* We store a non-random pattern which consists of the (irrelevant)
192 | * index plus (!) the value of the beep variable. This prevents the
193 | * compiler from optimizing the calls away, since the value of 'beep'
194 | * is not known at compile-time. */
195 | vpassword[c] = c + (int)beep;
196 | }
197 | #endif
198 | }
199 |
200 | ev_timer *start_timer(ev_timer *timer_obj, ev_tstamp timeout, ev_callback_t callback) {
201 | if (timer_obj) {
202 | ev_timer_stop(main_loop, timer_obj);
203 | ev_timer_set(timer_obj, timeout, 0.);
204 | ev_timer_start(main_loop, timer_obj);
205 | } else {
206 | /* When there is no memory, we just don’t have a timeout. We cannot
207 | * exit() here, since that would effectively unlock the screen. */
208 | timer_obj = calloc(1, sizeof(struct ev_timer));
209 | if (timer_obj) {
210 | ev_timer_init(timer_obj, callback, timeout, 0.);
211 | ev_timer_start(main_loop, timer_obj);
212 | }
213 | }
214 | return timer_obj;
215 | }
216 |
217 | ev_timer *stop_timer(ev_timer *timer_obj) {
218 | if (timer_obj) {
219 | ev_timer_stop(main_loop, timer_obj);
220 | free(timer_obj);
221 | }
222 | return NULL;
223 | }
224 |
225 | /*
226 | * Neccessary calls after ending input via enter or others
227 | *
228 | */
229 | static void finish_input(void) {
230 | password[input_position] = '\0';
231 | unlock_state = STATE_KEY_PRESSED;
232 | redraw_screen();
233 | input_done();
234 | }
235 |
236 | /*
237 | * Resets auth_state to STATE_AUTH_IDLE 2 seconds after an unsuccessful
238 | * authentication event.
239 | *
240 | */
241 | static void clear_auth_wrong(EV_P_ ev_timer *w, int revents) {
242 | DEBUG("clearing auth wrong\n");
243 | auth_state = STATE_AUTH_IDLE;
244 | redraw_screen();
245 |
246 | /* Clear modifier string. */
247 | if (modifier_string != NULL) {
248 | free(modifier_string);
249 | modifier_string = NULL;
250 | }
251 |
252 | /* Now free this timeout. */
253 | STOP_TIMER(clear_auth_wrong_timeout);
254 |
255 | /* retry with input done during auth verification */
256 | if (retry_verification) {
257 | retry_verification = false;
258 | finish_input();
259 | }
260 | }
261 |
262 | static void clear_indicator_cb(EV_P_ ev_timer *w, int revents) {
263 | clear_indicator();
264 | STOP_TIMER(clear_indicator_timeout);
265 | }
266 |
267 | static void clear_input(void) {
268 | input_position = 0;
269 | clear_password_memory();
270 | password[input_position] = '\0';
271 | }
272 |
273 | static void discard_passwd_cb(EV_P_ ev_timer *w, int revents) {
274 | clear_input();
275 | STOP_TIMER(discard_passwd_timeout);
276 | }
277 |
278 | static void input_done(void) {
279 | STOP_TIMER(clear_auth_wrong_timeout);
280 | auth_state = STATE_AUTH_VERIFY;
281 | unlock_state = STATE_STARTED;
282 | redraw_screen();
283 |
284 | #ifdef __OpenBSD__
285 | struct passwd *pw;
286 |
287 | if (!(pw = getpwuid(getuid()))) {
288 | errx(1, "unknown uid %u.", getuid());
289 | }
290 |
291 | if (auth_userokay(pw->pw_name, NULL, NULL, password) != 0) {
292 | DEBUG("successfully authenticated\n");
293 | clear_password_memory();
294 |
295 | ev_break(EV_DEFAULT, EVBREAK_ALL);
296 | return;
297 | }
298 | #else
299 | if (pam_authenticate(pam_handle, 0) == PAM_SUCCESS) {
300 | DEBUG("successfully authenticated\n");
301 | clear_password_memory();
302 |
303 | /* PAM credentials should be refreshed, this will for example update any kerberos tickets.
304 | * Related to credentials pam_end() needs to be called to cleanup any temporary
305 | * credentials like kerberos /tmp/krb5cc_pam_* files which may of been left behind if the
306 | * refresh of the credentials failed. */
307 | pam_setcred(pam_handle, PAM_REFRESH_CRED);
308 | pam_cleanup = true;
309 |
310 | ev_break(EV_DEFAULT, EVBREAK_ALL);
311 | return;
312 | }
313 | #endif
314 |
315 | if (debug_mode) {
316 | fprintf(stderr, "Authentication failure\n");
317 | }
318 |
319 | auth_state = STATE_AUTH_WRONG;
320 | /* The unlock indicator displays the number of failed attempts,
321 | * but caps the attempts to 999, so only increment up to 999. */
322 | if (failed_attempts < 999) {
323 | failed_attempts += 1;
324 | }
325 | clear_input();
326 | if (unlock_indicator) {
327 | redraw_screen();
328 | }
329 |
330 | /* Clear this state after 2 seconds (unless the user enters another
331 | * password during that time). */
332 | ev_now_update(main_loop);
333 | START_TIMER(clear_auth_wrong_timeout, TSTAMP_N_SECS(2), clear_auth_wrong);
334 |
335 | /* Cancel the clear_indicator_timeout, it would hide the unlock indicator
336 | * too early. */
337 | STOP_TIMER(clear_indicator_timeout);
338 |
339 | /* beep on authentication failure, if enabled */
340 | if (beep) {
341 | xcb_bell(conn, 100);
342 | xcb_flush(conn);
343 | }
344 | }
345 |
346 | static void redraw_timeout(EV_P_ ev_timer *w, int revents) {
347 | redraw_screen();
348 | STOP_TIMER(w);
349 | }
350 |
351 | static bool skip_without_validation(void) {
352 | if (input_position != 0) {
353 | return false;
354 | }
355 |
356 | if (skip_repeated_empty_password || ignore_empty_password) {
357 | return true;
358 | }
359 |
360 | return false;
361 | }
362 |
363 | /*
364 | * Handle key presses. Fixes state, then looks up the key symbol for the
365 | * given keycode, then looks up the key symbol (as UCS-2), converts it to
366 | * UTF-8 and stores it in the password array.
367 | *
368 | */
369 | static void handle_key_press(xcb_key_press_event_t *event) {
370 | xkb_keysym_t ksym;
371 | char buffer[128];
372 | int n;
373 | bool ctrl;
374 | bool composed = false;
375 |
376 | ksym = xkb_state_key_get_one_sym(xkb_state, event->detail);
377 | ctrl = xkb_state_mod_name_is_active(xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED);
378 |
379 | /* The buffer will be null-terminated, so n >= 2 for 1 actual character. */
380 | memset(buffer, '\0', sizeof(buffer));
381 |
382 | if (xkb_compose_state && xkb_compose_state_feed(xkb_compose_state, ksym) == XKB_COMPOSE_FEED_ACCEPTED) {
383 | switch (xkb_compose_state_get_status(xkb_compose_state)) {
384 | case XKB_COMPOSE_NOTHING:
385 | break;
386 | case XKB_COMPOSE_COMPOSING:
387 | return;
388 | case XKB_COMPOSE_COMPOSED:
389 | /* xkb_compose_state_get_utf8 doesn't include the terminating byte in the return value
390 | * as xkb_keysym_to_utf8 does. Adding one makes the variable n consistent. */
391 | n = xkb_compose_state_get_utf8(xkb_compose_state, buffer, sizeof(buffer)) + 1;
392 | ksym = xkb_compose_state_get_one_sym(xkb_compose_state);
393 | composed = true;
394 | break;
395 | case XKB_COMPOSE_CANCELLED:
396 | xkb_compose_state_reset(xkb_compose_state);
397 | return;
398 | }
399 | }
400 |
401 | if (!composed) {
402 | n = xkb_keysym_to_utf8(ksym, buffer, sizeof(buffer));
403 | }
404 |
405 | switch (ksym) {
406 | case XKB_KEY_j:
407 | case XKB_KEY_m:
408 | case XKB_KEY_Return:
409 | case XKB_KEY_KP_Enter:
410 | case XKB_KEY_XF86ScreenSaver:
411 | if ((ksym == XKB_KEY_j || ksym == XKB_KEY_m) && !ctrl) {
412 | break;
413 | }
414 |
415 | if (auth_state == STATE_AUTH_WRONG) {
416 | retry_verification = true;
417 | return;
418 | }
419 |
420 | if (skip_without_validation()) {
421 | clear_input();
422 | return;
423 | }
424 | finish_input();
425 | skip_repeated_empty_password = true;
426 | return;
427 | default:
428 | skip_repeated_empty_password = false;
429 | // A new password is being entered, but a previous one is pending.
430 | // Discard the old one and clear the retry_verification flag.
431 | if (retry_verification) {
432 | retry_verification = false;
433 | clear_input();
434 | }
435 | }
436 |
437 | switch (ksym) {
438 | case XKB_KEY_u:
439 | case XKB_KEY_Escape:
440 | if ((ksym == XKB_KEY_u && ctrl) ||
441 | ksym == XKB_KEY_Escape) {
442 | DEBUG("C-u pressed\n");
443 | clear_input();
444 | /* Also hide the unlock indicator */
445 | if (unlock_indicator) {
446 | clear_indicator();
447 | }
448 | return;
449 | }
450 | break;
451 |
452 | case XKB_KEY_Delete:
453 | case XKB_KEY_KP_Delete:
454 | /* Deleting forward doesn’t make sense, as i3lock doesn’t allow you
455 | * to move the cursor when entering a password. We need to eat this
456 | * key press so that it won’t be treated as part of the password,
457 | * see issue #50. */
458 | return;
459 |
460 | case XKB_KEY_h:
461 | case XKB_KEY_BackSpace:
462 | if (ksym == XKB_KEY_h && !ctrl) {
463 | break;
464 | }
465 |
466 | if (input_position == 0) {
467 | START_TIMER(clear_indicator_timeout, 1.0, clear_indicator_cb);
468 | unlock_state = STATE_NOTHING_TO_DELETE;
469 | redraw_screen();
470 | return;
471 | }
472 |
473 | /* decrement input_position to point to the previous glyph */
474 | u8_dec(password, &input_position);
475 | password[input_position] = '\0';
476 |
477 | /* Hide the unlock indicator after a bit if the password buffer is
478 | * empty. */
479 | START_TIMER(clear_indicator_timeout, 1.0, clear_indicator_cb);
480 | unlock_state = STATE_BACKSPACE_ACTIVE;
481 | redraw_screen();
482 | unlock_state = STATE_KEY_PRESSED;
483 | return;
484 | }
485 |
486 | if ((input_position + 8) >= (int)sizeof(password)) {
487 | return;
488 | }
489 |
490 | #if 0
491 | /* FIXME: handle all of these? */
492 | printf("is_keypad_key = %d\n", xcb_is_keypad_key(sym));
493 | printf("is_private_keypad_key = %d\n", xcb_is_private_keypad_key(sym));
494 | printf("xcb_is_cursor_key = %d\n", xcb_is_cursor_key(sym));
495 | printf("xcb_is_pf_key = %d\n", xcb_is_pf_key(sym));
496 | printf("xcb_is_function_key = %d\n", xcb_is_function_key(sym));
497 | printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym));
498 | printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym));
499 | #endif
500 |
501 | if (n < 2) {
502 | return;
503 | }
504 |
505 | /* store it in the password array as UTF-8 */
506 | memcpy(password + input_position, buffer, n - 1);
507 | input_position += n - 1;
508 | DEBUG("current password = %.*s\n", input_position, password);
509 |
510 | if (unlock_indicator) {
511 | unlock_state = STATE_KEY_ACTIVE;
512 | redraw_screen();
513 | unlock_state = STATE_KEY_PRESSED;
514 |
515 | struct ev_timer *timeout = NULL;
516 | START_TIMER(timeout, TSTAMP_N_SECS(0.25), redraw_timeout);
517 | STOP_TIMER(clear_indicator_timeout);
518 | }
519 |
520 | START_TIMER(discard_passwd_timeout, TSTAMP_N_MINS(3), discard_passwd_cb);
521 | }
522 |
523 | /*
524 | * A visibility notify event will be received when the visibility (= can the
525 | * user view the complete window) changes, so for example when a popup overlays
526 | * some area of the i3lock window.
527 | *
528 | * In this case, we raise our window on top so that the popup (or whatever is
529 | * hiding us) gets hidden.
530 | *
531 | */
532 | static void handle_visibility_notify(xcb_connection_t *conn,
533 | xcb_visibility_notify_event_t *event) {
534 | if (event->state != XCB_VISIBILITY_UNOBSCURED) {
535 | uint32_t values[] = {XCB_STACK_MODE_ABOVE};
536 | xcb_configure_window(conn, event->window, XCB_CONFIG_WINDOW_STACK_MODE, values);
537 | xcb_flush(conn);
538 | }
539 | }
540 |
541 | /*
542 | * Called when the keyboard mapping changes. We update our symbols.
543 | *
544 | * We ignore errors — if the new keymap cannot be loaded it’s better if the
545 | * screen stays locked and the user intervenes by using killall i3lock.
546 | *
547 | */
548 | static void process_xkb_event(xcb_generic_event_t *gevent) {
549 | union xkb_event {
550 | struct {
551 | uint8_t response_type;
552 | uint8_t xkbType;
553 | uint16_t sequence;
554 | xcb_timestamp_t time;
555 | uint8_t deviceID;
556 | } any;
557 | xcb_xkb_new_keyboard_notify_event_t new_keyboard_notify;
558 | xcb_xkb_map_notify_event_t map_notify;
559 | xcb_xkb_state_notify_event_t state_notify;
560 | } *event = (union xkb_event *)gevent;
561 |
562 | DEBUG("process_xkb_event for device %d\n", event->any.deviceID);
563 |
564 | if (event->any.deviceID != xkb_x11_get_core_keyboard_device_id(conn)) {
565 | return;
566 | }
567 |
568 | /*
569 | * XkbNewKkdNotify and XkbMapNotify together capture all sorts of keymap
570 | * updates (e.g. xmodmap, xkbcomp, setxkbmap), with minimal redundent
571 | * recompilations.
572 | */
573 | switch (event->any.xkbType) {
574 | case XCB_XKB_NEW_KEYBOARD_NOTIFY:
575 | if (event->new_keyboard_notify.changed & XCB_XKB_NKN_DETAIL_KEYCODES) {
576 | (void)load_keymap();
577 | }
578 | break;
579 |
580 | case XCB_XKB_MAP_NOTIFY:
581 | (void)load_keymap();
582 | break;
583 |
584 | case XCB_XKB_STATE_NOTIFY:
585 | xkb_state_update_mask(xkb_state,
586 | event->state_notify.baseMods,
587 | event->state_notify.latchedMods,
588 | event->state_notify.lockedMods,
589 | event->state_notify.baseGroup,
590 | event->state_notify.latchedGroup,
591 | event->state_notify.lockedGroup);
592 | break;
593 | }
594 | }
595 |
596 | /*
597 | * Called when the properties on the root window change, e.g. when the screen
598 | * resolution changes. If so we update the window to cover the whole screen
599 | * and also redraw the image, if any.
600 | *
601 | */
602 | static void handle_screen_resize(void) {
603 | xcb_get_geometry_cookie_t geomc;
604 | xcb_get_geometry_reply_t *geom;
605 | geomc = xcb_get_geometry(conn, screen->root);
606 | if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) {
607 | return;
608 | }
609 |
610 | if (last_resolution[0] == geom->width &&
611 | last_resolution[1] == geom->height) {
612 | free(geom);
613 | return;
614 | }
615 |
616 | last_resolution[0] = geom->width;
617 | last_resolution[1] = geom->height;
618 |
619 | free(geom);
620 |
621 | redraw_screen();
622 |
623 | uint32_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
624 | xcb_configure_window(conn, win, mask, last_resolution);
625 | xcb_flush(conn);
626 |
627 | randr_query(screen->root);
628 | redraw_screen();
629 | }
630 |
631 | static ssize_t read_raw_image_native(uint32_t *dest, FILE *src, size_t width, size_t height, int pixstride) {
632 | ssize_t count = 0;
633 | for (size_t y = 0; y < height; y++) {
634 | size_t n = fread(&dest[y * pixstride], 1, width * 4, src);
635 | count += n;
636 | if (n < (size_t)(width * 4)) {
637 | break;
638 | }
639 | }
640 |
641 | return count;
642 | }
643 |
644 | struct raw_pixel_format {
645 | int bpp;
646 | int red;
647 | int green;
648 | int blue;
649 | };
650 |
651 | static ssize_t read_raw_image_fmt(uint32_t *dest, FILE *src, size_t width, size_t height, int pixstride,
652 | struct raw_pixel_format fmt) {
653 | unsigned char *buf = malloc(width * fmt.bpp);
654 | if (buf == NULL) {
655 | return -1;
656 | }
657 |
658 | ssize_t count = 0;
659 | for (size_t y = 0; y < height; y++) {
660 | size_t n = fread(buf, 1, width * fmt.bpp, src);
661 | count += n;
662 | if (n < (size_t)(width * fmt.bpp)) {
663 | break;
664 | }
665 |
666 | for (size_t x = 0; x < width; ++x) {
667 | int idx = x * fmt.bpp;
668 | dest[y * pixstride + x] = 0 |
669 | (buf[idx + fmt.red]) << 16 |
670 | (buf[idx + fmt.green]) << 8 |
671 | (buf[idx + fmt.blue]);
672 | }
673 | }
674 |
675 | free(buf);
676 | return count;
677 | }
678 |
679 | // Pre-defind pixel formats (, , , )
680 | static const struct raw_pixel_format raw_fmt_rgb = {3, 0, 1, 2};
681 | static const struct raw_pixel_format raw_fmt_rgbx = {4, 0, 1, 2};
682 | static const struct raw_pixel_format raw_fmt_xrgb = {4, 1, 2, 3};
683 | static const struct raw_pixel_format raw_fmt_bgr = {3, 2, 1, 0};
684 | static const struct raw_pixel_format raw_fmt_bgrx = {4, 2, 1, 0};
685 | static const struct raw_pixel_format raw_fmt_xbgr = {4, 3, 2, 1};
686 |
687 | static cairo_surface_t *read_raw_image(const char *image_path, const char *image_raw_format) {
688 | cairo_surface_t *img;
689 |
690 | #define RAW_PIXFMT_MAXLEN 6
691 | #define STRINGIFY1(x) #x
692 | #define STRINGIFY(x) STRINGIFY1(x)
693 | /* Parse format as x: */
694 | char pixfmt[RAW_PIXFMT_MAXLEN + 1];
695 | size_t w, h;
696 | const char *fmt = "%zux%zu:%" STRINGIFY(RAW_PIXFMT_MAXLEN) "s";
697 | if (sscanf(image_raw_format, fmt, &w, &h, pixfmt) != 3) {
698 | fprintf(stderr, "Invalid image format: \"%s\"\n", image_raw_format);
699 | return NULL;
700 | }
701 | #undef RAW_PIXFMT_MAXLEN
702 | #undef STRINGIFY1
703 | #undef STRINGIFY
704 |
705 | /* Create image surface */
706 | img = cairo_image_surface_create(CAIRO_FORMAT_RGB24, w, h);
707 | if (cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) {
708 | fprintf(stderr, "Could not create surface: %s\n",
709 | cairo_status_to_string(cairo_surface_status(img)));
710 | return NULL;
711 | }
712 | cairo_surface_flush(img);
713 |
714 | /* Use uint32_t* because cairo uses native endianness */
715 | uint32_t *data = (uint32_t *)cairo_image_surface_get_data(img);
716 | const int pixstride = cairo_image_surface_get_stride(img) / 4;
717 |
718 | FILE *f = fopen(image_path, "r");
719 | if (f == NULL) {
720 | fprintf(stderr, "Could not open image \"%s\": %s\n",
721 | image_path, strerror(errno));
722 | cairo_surface_destroy(img);
723 | return NULL;
724 | }
725 |
726 | /* Read the image, respecting cairo's stride, according to the pixfmt */
727 | ssize_t size, count;
728 | if (strcmp(pixfmt, "native") == 0) {
729 | /* If the pixfmt is 'native', just read each line directly into the buffer */
730 | size = w * h * 4;
731 | count = read_raw_image_native(data, f, w, h, pixstride);
732 | } else {
733 | const struct raw_pixel_format *fmt = NULL;
734 |
735 | if (strcmp(pixfmt, "rgb") == 0) {
736 | fmt = &raw_fmt_rgb;
737 | } else if (strcmp(pixfmt, "rgbx") == 0) {
738 | fmt = &raw_fmt_rgbx;
739 | } else if (strcmp(pixfmt, "xrgb") == 0) {
740 | fmt = &raw_fmt_xrgb;
741 | } else if (strcmp(pixfmt, "bgr") == 0) {
742 | fmt = &raw_fmt_bgr;
743 | } else if (strcmp(pixfmt, "bgrx") == 0) {
744 | fmt = &raw_fmt_bgrx;
745 | } else if (strcmp(pixfmt, "xbgr") == 0) {
746 | fmt = &raw_fmt_xbgr;
747 | }
748 |
749 | if (fmt == NULL) {
750 | fprintf(stderr, "Unknown raw pixel format: %s\n", pixfmt);
751 | fclose(f);
752 | cairo_surface_destroy(img);
753 | return NULL;
754 | }
755 |
756 | size = w * h * fmt->bpp;
757 | count = read_raw_image_fmt(data, f, w, h, pixstride, *fmt);
758 | }
759 |
760 | cairo_surface_mark_dirty(img);
761 |
762 | if (count < size) {
763 | if (count < 0 || ferror(f)) {
764 | fprintf(stderr, "Failed to read image \"%s\": %s\n",
765 | image_path, strerror(errno));
766 | fclose(f);
767 | cairo_surface_destroy(img);
768 | return NULL;
769 | } else {
770 | /* Print a warning if the file contains less data than expected,
771 | * but don't abort. It's useful to see how the image looks even if it's wrong. */
772 | fprintf(stderr, "Warning: expected to read %zi bytes from \"%s\", read %zi\n",
773 | size, image_path, count);
774 | }
775 | }
776 |
777 | fclose(f);
778 | return img;
779 | }
780 |
781 | static bool verify_png_image(const char *image_path) {
782 | if (!image_path) {
783 | return false;
784 | }
785 |
786 | /* Check file exists and has correct PNG header */
787 | FILE *png_file = fopen(image_path, "r");
788 | if (png_file == NULL) {
789 | fprintf(stderr, "Image file path \"%s\" cannot be opened: %s\n", image_path, strerror(errno));
790 | return false;
791 | }
792 | unsigned char png_header[8];
793 | memset(png_header, '\0', sizeof(png_header));
794 | int bytes_read = fread(png_header, 1, sizeof(png_header), png_file);
795 | fclose(png_file);
796 | if (bytes_read != sizeof(png_header)) {
797 | fprintf(stderr, "Could not read PNG header from \"%s\"\n", image_path);
798 | return false;
799 | }
800 |
801 | // Check PNG header according to the specification, available at:
802 | // https://www.w3.org/TR/2003/REC-PNG-20031110/#5PNG-file-signature
803 | static unsigned char PNG_REFERENCE_HEADER[8] = {137, 80, 78, 71, 13, 10, 26, 10};
804 | if (memcmp(PNG_REFERENCE_HEADER, png_header, sizeof(png_header)) != 0) {
805 | fprintf(stderr, "File \"%s\" does not start with a PNG header. i3lock currently only supports loading PNG files.\n", image_path);
806 | return false;
807 | }
808 | return true;
809 | }
810 |
811 | #ifndef __OpenBSD__
812 | /*
813 | * Callback function for PAM. We only react on password request callbacks.
814 | *
815 | */
816 | static int conv_callback(int num_msg, const struct pam_message **msg,
817 | struct pam_response **resp, void *appdata_ptr) {
818 | if (num_msg == 0) {
819 | return 1;
820 | }
821 |
822 | /* PAM expects an array of responses, one for each message */
823 | if ((*resp = calloc(num_msg, sizeof(struct pam_response))) == NULL) {
824 | perror("calloc");
825 | return 1;
826 | }
827 |
828 | for (int c = 0; c < num_msg; c++) {
829 | if (msg[c]->msg_style != PAM_PROMPT_ECHO_OFF &&
830 | msg[c]->msg_style != PAM_PROMPT_ECHO_ON) {
831 | continue;
832 | }
833 |
834 | /* return code is currently not used but should be set to zero */
835 | resp[c]->resp_retcode = 0;
836 | if ((resp[c]->resp = strdup(password)) == NULL) {
837 | perror("strdup");
838 | return 1;
839 | }
840 | }
841 |
842 | return 0;
843 | }
844 | #endif
845 |
846 | /*
847 | * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
848 | * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
849 | *
850 | */
851 | static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
852 | /* empty, because xcb_prepare_cb and xcb_check_cb are used */
853 | }
854 |
855 | /*
856 | * Flush before blocking (and waiting for new events)
857 | *
858 | */
859 | static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
860 | xcb_flush(conn);
861 | }
862 |
863 | /*
864 | * Try closing logind sleep lock fd passed over from xss-lock, in case we're
865 | * being run from there.
866 | *
867 | */
868 | static void maybe_close_sleep_lock_fd(void) {
869 | const char *sleep_lock_fd = getenv("XSS_SLEEP_LOCK_FD");
870 | char *endptr;
871 | if (sleep_lock_fd && *sleep_lock_fd != 0) {
872 | long int fd = strtol(sleep_lock_fd, &endptr, 10);
873 | if (*endptr == 0) {
874 | close(fd);
875 | }
876 | }
877 | }
878 |
879 | /*
880 | * Instead of polling the X connection socket we leave this to
881 | * xcb_poll_for_event() which knows better than we can ever know.
882 | *
883 | */
884 | static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
885 | xcb_generic_event_t *event;
886 |
887 | if (xcb_connection_has_error(conn)) {
888 | errx(EXIT_FAILURE, "X11 connection broke, did your server terminate?");
889 | }
890 |
891 | while ((event = xcb_poll_for_event(conn)) != NULL) {
892 | if (event->response_type == 0) {
893 | xcb_generic_error_t *error = (xcb_generic_error_t *)event;
894 | if (debug_mode) {
895 | fprintf(stderr, "X11 Error received! sequence 0x%x, error_code = %d\n",
896 | error->sequence, error->error_code);
897 | }
898 | free(event);
899 | continue;
900 | }
901 |
902 | /* Strip off the highest bit (set if the event is generated) */
903 | int type = (event->response_type & 0x7F);
904 |
905 | switch (type) {
906 | case XCB_KEY_PRESS:
907 | handle_key_press((xcb_key_press_event_t *)event);
908 | break;
909 |
910 | case XCB_VISIBILITY_NOTIFY:
911 | handle_visibility_notify(conn, (xcb_visibility_notify_event_t *)event);
912 | break;
913 |
914 | case XCB_MAP_NOTIFY:
915 | maybe_close_sleep_lock_fd();
916 | if (!dont_fork) {
917 | /* After the first MapNotify, we never fork again. We don’t
918 | * expect to get another MapNotify, but better be sure… */
919 | dont_fork = true;
920 |
921 | /* In the parent process, we exit */
922 | if (fork() != 0) {
923 | exit(0);
924 | }
925 |
926 | ev_loop_fork(EV_DEFAULT);
927 | }
928 | break;
929 |
930 | case XCB_CONFIGURE_NOTIFY:
931 | handle_screen_resize();
932 | break;
933 |
934 | default:
935 | if (type == xkb_base_event) {
936 | process_xkb_event(event);
937 | redraw_screen();
938 | }
939 | if (randr_base > -1 &&
940 | type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
941 | randr_query(screen->root);
942 | handle_screen_resize();
943 | }
944 | }
945 |
946 | free(event);
947 | }
948 | }
949 |
950 | /*
951 | * This function is called from a fork()ed child and will raise the i3lock
952 | * window when the window is obscured, even when the main i3lock process is
953 | * blocked due to the authentication backend.
954 | *
955 | */
956 | static void raise_loop(xcb_window_t window) {
957 | xcb_connection_t *conn;
958 | xcb_generic_event_t *event;
959 | int screens;
960 |
961 | if (xcb_connection_has_error((conn = xcb_connect(NULL, &screens))) > 0) {
962 | errx(EXIT_FAILURE, "Cannot open display");
963 | }
964 |
965 | /* We need to know about the window being obscured or getting destroyed. */
966 | xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK,
967 | (uint32_t[]){
968 | XCB_EVENT_MASK_VISIBILITY_CHANGE |
969 | XCB_EVENT_MASK_STRUCTURE_NOTIFY});
970 | xcb_flush(conn);
971 |
972 | DEBUG("Watching window 0x%08x\n", window);
973 | while ((event = xcb_wait_for_event(conn)) != NULL) {
974 | if (event->response_type == 0) {
975 | xcb_generic_error_t *error = (xcb_generic_error_t *)event;
976 | DEBUG("X11 Error received! sequence 0x%x, error_code = %d\n",
977 | error->sequence, error->error_code);
978 | free(event);
979 | continue;
980 | }
981 | /* Strip off the highest bit (set if the event is generated) */
982 | int type = (event->response_type & 0x7F);
983 | DEBUG("Read event of type %d\n", type);
984 | switch (type) {
985 | case XCB_VISIBILITY_NOTIFY:
986 | handle_visibility_notify(conn, (xcb_visibility_notify_event_t *)event);
987 | break;
988 | case XCB_UNMAP_NOTIFY:
989 | DEBUG("UnmapNotify for 0x%08x\n", (((xcb_unmap_notify_event_t *)event)->window));
990 | if (((xcb_unmap_notify_event_t *)event)->window == window) {
991 | exit(EXIT_SUCCESS);
992 | }
993 | break;
994 | case XCB_DESTROY_NOTIFY:
995 | DEBUG("DestroyNotify for 0x%08x\n", (((xcb_destroy_notify_event_t *)event)->window));
996 | if (((xcb_destroy_notify_event_t *)event)->window == window) {
997 | exit(EXIT_SUCCESS);
998 | }
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 | {"show-keyboard-layout", no_argument, NULL, 'k'},
1037 | {NULL, no_argument, NULL, 0}};
1038 |
1039 | int code = EXIT_FAILURE;
1040 | char *optstring = "hvnbdc:p:ui:teI:fk";
1041 | while ((o = getopt_long(argc, argv, optstring, longopts, &longoptind)) != -1) {
1042 | switch (o) {
1043 | case 'v':
1044 | errx(EXIT_SUCCESS, "version " I3LOCK_VERSION " © 2010 Michael Stapelberg");
1045 | case 'n':
1046 | dont_fork = true;
1047 | break;
1048 | case 'b':
1049 | beep = true;
1050 | break;
1051 | case 'd':
1052 | fprintf(stderr, "DPMS support has been removed from i3lock. Please see the manpage i3lock(1).\n");
1053 | break;
1054 | case 'I': {
1055 | fprintf(stderr, "Inactivity timeout only makes sense with DPMS, which was removed. Please see the manpage i3lock(1).\n");
1056 | break;
1057 | }
1058 | case 'c': {
1059 | char *arg = optarg;
1060 |
1061 | /* Skip # if present */
1062 | if (arg[0] == '#') {
1063 | arg++;
1064 | }
1065 |
1066 | if (strlen(arg) != 6 || sscanf(arg, "%06[0-9a-fA-F]", color) != 1) {
1067 | errx(EXIT_FAILURE, "color is invalid, it must be given in 3-byte hexadecimal format: rrggbb");
1068 | }
1069 |
1070 | break;
1071 | }
1072 | case 'u':
1073 | unlock_indicator = false;
1074 | break;
1075 | case 'i':
1076 | image_path = strdup(optarg);
1077 | break;
1078 | case 't':
1079 | tile = true;
1080 | break;
1081 | case 'p':
1082 | if (!strcmp(optarg, "win")) {
1083 | curs_choice = CURS_WIN;
1084 | } else if (!strcmp(optarg, "default")) {
1085 | curs_choice = CURS_DEFAULT;
1086 | } else {
1087 | errx(EXIT_FAILURE, "i3lock: Invalid pointer type given. Expected one of \"win\" or \"default\".");
1088 | }
1089 | break;
1090 | case 'e':
1091 | ignore_empty_password = true;
1092 | break;
1093 | case 0:
1094 | if (strcmp(longopts[longoptind].name, "debug") == 0) {
1095 | debug_mode = true;
1096 | } else if (strcmp(longopts[longoptind].name, "raw") == 0) {
1097 | image_raw_format = strdup(optarg);
1098 | }
1099 | break;
1100 | case 'f':
1101 | show_failed_attempts = true;
1102 | break;
1103 | case 'k':
1104 | show_keyboard_layout = true;
1105 | break;
1106 | case 'h':
1107 | code = EXIT_SUCCESS;
1108 | /* fallthrough */
1109 | default:
1110 | errx(code, "Syntax: i3lock [-v] [-n] [-b] [-d] [-c color] [-u] [-p win|default]"
1111 | " [-i image.png] [-t] [-e] [-I timeout] [-f] [-k]");
1112 | }
1113 | }
1114 |
1115 | if ((pw = getpwuid(getuid())) == NULL) {
1116 | err(EXIT_FAILURE, "getpwuid() failed");
1117 | }
1118 | if ((username = pw->pw_name) == NULL) {
1119 | errx(EXIT_FAILURE, "pw->pw_name is NULL.");
1120 | }
1121 | if (getenv("WAYLAND_DISPLAY") != NULL) {
1122 | errx(EXIT_FAILURE, "i3lock is a program for X11 and does not work on Wayland. Try https://github.com/swaywm/swaylock instead");
1123 | }
1124 |
1125 | /* We need (relatively) random numbers for highlighting a random part of
1126 | * the unlock indicator upon keypresses. */
1127 | srand(time(NULL));
1128 |
1129 | #ifndef __OpenBSD__
1130 | /* Initialize PAM */
1131 | if ((ret = pam_start("i3lock", username, &conv, &pam_handle)) != PAM_SUCCESS) {
1132 | errx(EXIT_FAILURE, "PAM: %s", pam_strerror(pam_handle, ret));
1133 | }
1134 |
1135 | if ((ret = pam_set_item(pam_handle, PAM_TTY, getenv("DISPLAY"))) != PAM_SUCCESS) {
1136 | errx(EXIT_FAILURE, "PAM: %s", pam_strerror(pam_handle, ret));
1137 | }
1138 | #endif
1139 |
1140 | /* Using mlock() as non-super-user seems only possible in Linux.
1141 | * Users of other operating systems should use encrypted swap/no swap
1142 | * (or remove the ifdef and run i3lock as super-user).
1143 | * Alas, swap is encrypted by default on OpenBSD so swapping out
1144 | * is not necessarily an issue. */
1145 | #if defined(__linux__)
1146 | /* Lock the area where we store the password in memory, we don’t want it to
1147 | * be swapped to disk. Since Linux 2.6.9, this does not require any
1148 | * privileges, just enough bytes in the RLIMIT_MEMLOCK limit. */
1149 | if (mlock(password, sizeof(password)) != 0) {
1150 | err(EXIT_FAILURE, "Could not lock page in memory, check RLIMIT_MEMLOCK");
1151 | }
1152 | #endif
1153 |
1154 | /* Double checking that connection is good and operatable with xcb */
1155 | int screennr;
1156 | if ((conn = xcb_connect(NULL, &screennr)) == NULL ||
1157 | xcb_connection_has_error(conn)) {
1158 | errx(EXIT_FAILURE, "Could not connect to X11, maybe you need to set DISPLAY?");
1159 | }
1160 |
1161 | if (xkb_x11_setup_xkb_extension(conn,
1162 | XKB_X11_MIN_MAJOR_XKB_VERSION,
1163 | XKB_X11_MIN_MINOR_XKB_VERSION,
1164 | 0,
1165 | NULL,
1166 | NULL,
1167 | &xkb_base_event,
1168 | &xkb_base_error) != 1) {
1169 | errx(EXIT_FAILURE, "Could not setup XKB extension.");
1170 | }
1171 |
1172 | static const xcb_xkb_map_part_t required_map_parts =
1173 | (XCB_XKB_MAP_PART_KEY_TYPES |
1174 | XCB_XKB_MAP_PART_KEY_SYMS |
1175 | XCB_XKB_MAP_PART_MODIFIER_MAP |
1176 | XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
1177 | XCB_XKB_MAP_PART_KEY_ACTIONS |
1178 | XCB_XKB_MAP_PART_VIRTUAL_MODS |
1179 | XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP);
1180 |
1181 | static const xcb_xkb_event_type_t required_events =
1182 | (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
1183 | XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
1184 | XCB_XKB_EVENT_TYPE_STATE_NOTIFY);
1185 |
1186 | xcb_xkb_select_events(
1187 | conn,
1188 | xkb_x11_get_core_keyboard_device_id(conn),
1189 | required_events,
1190 | 0,
1191 | required_events,
1192 | required_map_parts,
1193 | required_map_parts,
1194 | 0);
1195 |
1196 | /* When we cannot initially load the keymap, we better exit */
1197 | if (!load_keymap()) {
1198 | errx(EXIT_FAILURE, "Could not load keymap");
1199 | }
1200 |
1201 | const char *locale = getenv("LC_ALL");
1202 | if (!locale || !*locale) {
1203 | locale = getenv("LC_CTYPE");
1204 | }
1205 | if (!locale || !*locale) {
1206 | locale = getenv("LANG");
1207 | }
1208 | if (!locale || !*locale) {
1209 | if (debug_mode) {
1210 | fprintf(stderr, "Can't detect your locale, fallback to C\n");
1211 | }
1212 | locale = "C";
1213 | }
1214 |
1215 | load_compose_table(locale);
1216 |
1217 | screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
1218 |
1219 | init_dpi();
1220 |
1221 | randr_init(&randr_base, screen->root);
1222 | randr_query(screen->root);
1223 |
1224 | last_resolution[0] = screen->width_in_pixels;
1225 | last_resolution[1] = screen->height_in_pixels;
1226 |
1227 | xcb_change_window_attributes(conn, screen->root, XCB_CW_EVENT_MASK,
1228 | (uint32_t[]){XCB_EVENT_MASK_STRUCTURE_NOTIFY});
1229 |
1230 | if (image_raw_format != NULL && image_path != NULL) {
1231 | /* Read image. 'read_raw_image' returns NULL on error,
1232 | * so we don't have to handle errors here. */
1233 | img = read_raw_image(image_path, image_raw_format);
1234 | } else if (verify_png_image(image_path)) {
1235 | /* Create a pixmap to render on, fill it with the background color */
1236 | img = cairo_image_surface_create_from_png(image_path);
1237 | /* In case loading failed, we just pretend no -i was specified. */
1238 | if (cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) {
1239 | fprintf(stderr, "Could not load image \"%s\": %s\n",
1240 | image_path, cairo_status_to_string(cairo_surface_status(img)));
1241 | img = NULL;
1242 | }
1243 | }
1244 |
1245 | free(image_path);
1246 | free(image_raw_format);
1247 |
1248 | /* Pixmap on which the image is rendered to (if any) */
1249 | xcb_pixmap_t bg_pixmap = create_bg_pixmap(conn, screen, last_resolution, color);
1250 | draw_image(bg_pixmap, last_resolution);
1251 |
1252 | xcb_window_t stolen_focus = find_focused_window(conn, screen->root);
1253 |
1254 | /* Open the fullscreen window, already with the correct pixmap in place */
1255 | win = open_fullscreen_window(conn, screen, color, bg_pixmap);
1256 | xcb_free_pixmap(conn, bg_pixmap);
1257 |
1258 | cursor = create_cursor(conn, screen, win, curs_choice);
1259 |
1260 | /* Display the "locking…" message while trying to grab the pointer/keyboard. */
1261 | auth_state = STATE_AUTH_LOCK;
1262 | if (!grab_pointer_and_keyboard(conn, screen, cursor, 1000)) {
1263 | DEBUG("stole focus from X11 window 0x%08x\n", stolen_focus);
1264 |
1265 | /* Set the focus to i3lock, possibly closing context menus which would
1266 | * otherwise prevent us from grabbing keyboard/pointer.
1267 | *
1268 | * We cannot use set_focused_window because _NET_ACTIVE_WINDOW only
1269 | * works for managed windows, but i3lock uses an unmanaged window
1270 | * (override_redirect=1). */
1271 | xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT /* revert_to */, win, XCB_CURRENT_TIME);
1272 | if (!grab_pointer_and_keyboard(conn, screen, cursor, 9000)) {
1273 | auth_state = STATE_I3LOCK_LOCK_FAILED;
1274 | redraw_screen();
1275 | sleep(1);
1276 | errx(EXIT_FAILURE, "Cannot grab pointer/keyboard");
1277 | }
1278 | }
1279 |
1280 | pid_t pid = fork();
1281 | /* The pid == -1 case is intentionally ignored here:
1282 | * While the child process is useful for preventing other windows from
1283 | * popping up while i3lock blocks, it is not critical. */
1284 | if (pid == 0) {
1285 | /* Child */
1286 | close(xcb_get_file_descriptor(conn));
1287 | maybe_close_sleep_lock_fd();
1288 | raise_loop(win);
1289 | exit(EXIT_SUCCESS);
1290 | }
1291 |
1292 | /* Load the keymap again to sync the current modifier state. Since we first
1293 | * loaded the keymap, there might have been changes, but starting from now,
1294 | * we should get all key presses/releases due to having grabbed the
1295 | * keyboard. */
1296 | (void)load_keymap();
1297 |
1298 | /* Initialize the libev event loop. */
1299 | main_loop = EV_DEFAULT;
1300 | if (main_loop == NULL) {
1301 | errx(EXIT_FAILURE, "Could not initialize libev. Bad LIBEV_FLAGS?");
1302 | }
1303 |
1304 | /* Explicitly call the screen redraw in case "locking…" message was displayed */
1305 | auth_state = STATE_AUTH_IDLE;
1306 | redraw_screen();
1307 |
1308 | struct ev_io *xcb_watcher = calloc(1, sizeof(struct ev_io));
1309 | struct ev_check *xcb_check = calloc(1, sizeof(struct ev_check));
1310 | struct ev_prepare *xcb_prepare = calloc(1, sizeof(struct ev_prepare));
1311 |
1312 | ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
1313 | ev_io_start(main_loop, xcb_watcher);
1314 |
1315 | ev_check_init(xcb_check, xcb_check_cb);
1316 | ev_check_start(main_loop, xcb_check);
1317 |
1318 | ev_prepare_init(xcb_prepare, xcb_prepare_cb);
1319 | ev_prepare_start(main_loop, xcb_prepare);
1320 |
1321 | /* Invoke the event callback once to catch all the events which were
1322 | * received up until now. ev will only pick up new events (when the X11
1323 | * file descriptor becomes readable). */
1324 | ev_invoke(main_loop, xcb_check, 0);
1325 | ev_loop(main_loop, 0);
1326 |
1327 | #ifndef __OpenBSD__
1328 | if (pam_cleanup) {
1329 | pam_end(pam_handle, PAM_SUCCESS);
1330 | }
1331 | #endif
1332 |
1333 | if (stolen_focus == XCB_NONE) {
1334 | return 0;
1335 | }
1336 |
1337 | DEBUG("restoring focus to X11 window 0x%08x\n", stolen_focus);
1338 | xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
1339 | xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
1340 | xcb_destroy_window(conn, win);
1341 | set_focused_window(conn, screen->root, stolen_focus);
1342 | xcb_aux_sync(conn);
1343 |
1344 | return 0;
1345 | }
1346 |
--------------------------------------------------------------------------------
/include/cursors.h:
--------------------------------------------------------------------------------
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
9 |
--------------------------------------------------------------------------------
/include/dpi.h:
--------------------------------------------------------------------------------
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);
23 |
--------------------------------------------------------------------------------
/include/i3lock.h:
--------------------------------------------------------------------------------
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 | fprintf(stderr, "[i3lock-debug] " fmt, ##__VA_ARGS__); \
12 | } \
13 | } while (0)
14 |
15 | #endif
16 |
--------------------------------------------------------------------------------
/include/randr.h:
--------------------------------------------------------------------------------
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
18 |
--------------------------------------------------------------------------------
/include/unlock_indicator.h:
--------------------------------------------------------------------------------
1 | #ifndef _UNLOCK_INDICATOR_H
2 | #define _UNLOCK_INDICATOR_H
3 |
4 | #include
5 |
6 | typedef enum {
7 | STATE_STARTED = 0, /* default state */
8 | STATE_KEY_PRESSED = 1, /* key was pressed, show unlock indicator */
9 | STATE_KEY_ACTIVE = 2, /* a key was pressed recently, highlight part
10 | of the unlock indicator. */
11 | STATE_BACKSPACE_ACTIVE = 3, /* backspace was pressed recently, highlight
12 | part of the unlock indicator in red. */
13 | STATE_NOTHING_TO_DELETE = 4, /* backspace was pressed, but there is nothing to delete. */
14 | } unlock_state_t;
15 |
16 | typedef enum {
17 | STATE_AUTH_IDLE = 0, /* no authenticator interaction at the moment */
18 | STATE_AUTH_VERIFY = 1, /* currently verifying the password via authenticator */
19 | STATE_AUTH_LOCK = 2, /* currently locking the screen */
20 | STATE_AUTH_WRONG = 3, /* the password was wrong */
21 | STATE_I3LOCK_LOCK_FAILED = 4, /* i3lock failed to load */
22 | } auth_state_t;
23 |
24 | void free_bg_pixmap(void);
25 | void draw_image(xcb_pixmap_t bg_pixmap, uint32_t* resolution);
26 | void redraw_screen(void);
27 | void clear_indicator(void);
28 |
29 | #endif
30 |
--------------------------------------------------------------------------------
/include/xcb.h:
--------------------------------------------------------------------------------
1 | #ifndef _XCB_H
2 | #define _XCB_H
3 |
4 | #include
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
18 |
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | # -*- mode: meson -*-
2 |
3 | # Style objective: be consistent with what mesonbuild.com documents/uses, and/or
4 | # the meson book: https://meson-manual.com/
5 |
6 | project(
7 | 'i3lock',
8 | 'c',
9 | version: '2.15',
10 | default_options: [
11 | 'c_std=c11',
12 | 'warning_level=1', # enable all warnings (-Wall)
13 | # TODO(https://github.com/i3/i3/issues/4087): switch to
14 | # 'buildtype=debugoptimized',
15 | ],
16 | # Ubuntu 18.04 (supported until 2023) has meson 0.45.
17 | # We can revisit our minimum supported meson version
18 | # if it turns out to be too hard to maintain.
19 | meson_version: '>=0.45.0',
20 | )
21 |
22 | cc = meson.get_compiler('c')
23 | add_project_arguments(cc.get_supported_arguments(['-Wunused-value']), language: 'c')
24 |
25 | if meson.version().version_compare('>=0.48.0')
26 | # https://github.com/mesonbuild/meson/issues/2166#issuecomment-629696911
27 | meson.add_dist_script('meson/meson-dist-script')
28 | else
29 | message('meson <0.48.0 detected, dist tarballs will not be filtered')
30 | endif
31 |
32 | ################################################################################
33 | # Version handling
34 | ################################################################################
35 |
36 | cdata = configuration_data()
37 |
38 | version_array = meson.project_version().split('.')
39 | cdata.set_quoted('I3LOCK_VERSION', '@VCS_TAG@')
40 | cdata.set_quoted('SYSCONFDIR', join_paths(get_option('prefix'), get_option('sysconfdir')))
41 |
42 | if get_option('b_sanitize').split(',').contains('address')
43 | cdata.set('I3LOCK_ASAN_ENABLED', 1)
44 | endif
45 |
46 | cdata.set('HAVE_STRNDUP', cc.has_function('strndup'))
47 | cdata.set('HAVE_MKDIRP', cc.has_function('mkdirp'))
48 |
49 | # Instead of generating config.h directly, make vcs_tag generate it so that
50 | # @VCS_TAG@ is replaced.
51 | config_h_in = configure_file(
52 | output: 'config.h.in',
53 | configuration: cdata,
54 | )
55 | config_h = declare_dependency(
56 | sources: vcs_tag(
57 | input: config_h_in,
58 | output: 'config.h',
59 | fallback: meson.project_version() + '-non-git',
60 | )
61 | )
62 |
63 | ################################################################################
64 | # manpages
65 | ################################################################################
66 |
67 | install_man('i3lock.1')
68 |
69 | # Required for e.g. struct ucred to be defined as per unix(7).
70 | add_project_arguments('-D_GNU_SOURCE', language: 'c')
71 |
72 | # https://mesonbuild.com/howtox.html#add-math-library-lm-portably
73 | m_dep = cc.find_library('m', required: false)
74 | rt_dep = cc.find_library('rt', required: false)
75 |
76 | xcb_dep = dependency('xcb', method: 'pkg-config')
77 | xcb_xkb_dep = dependency('xcb-xkb', method: 'pkg-config')
78 | xcb_xinerama_dep = dependency('xcb-xinerama', method: 'pkg-config')
79 | xcb_randr_dep = dependency('xcb-randr', method: 'pkg-config')
80 | xcb_image_dep = dependency('xcb-image', method: 'pkg-config')
81 | xcb_util_dep = dependency('xcb-util', method: 'pkg-config')
82 | xcb_util_xrm_dep = dependency('xcb-xrm', method: 'pkg-config')
83 | xkbcommon_dep = dependency('xkbcommon', method: 'pkg-config')
84 | xkbcommon_x11_dep = dependency('xkbcommon-x11', method: 'pkg-config')
85 | cairo_dep = dependency('cairo', version: '>=1.14.4', method: 'pkg-config')
86 |
87 | i3lock_srcs = [
88 | 'dpi.c',
89 | 'i3lock.c',
90 | 'randr.c',
91 | 'unlock_indicator.c',
92 | 'xcb.c',
93 | ]
94 |
95 | ev_dep = cc.find_library('ev')
96 |
97 | thread_dep = dependency('threads')
98 |
99 | i3lock_deps = [
100 | thread_dep,
101 | m_dep,
102 | rt_dep,
103 | ev_dep,
104 | config_h,
105 | cairo_dep,
106 | xcb_dep,
107 | xcb_xkb_dep,
108 | xcb_xinerama_dep,
109 | xcb_randr_dep,
110 | xcb_image_dep,
111 | xcb_util_dep,
112 | xcb_util_xrm_dep,
113 | xkbcommon_dep,
114 | xkbcommon_x11_dep,
115 | ]
116 |
117 | host_os = host_machine.system()
118 | if host_os != 'openbsd'
119 | pam_dep = cc.find_library('pam', required: true)
120 | i3lock_deps += [pam_dep]
121 | endif
122 |
123 | inc = include_directories('include')
124 |
125 | executable(
126 | 'i3lock',
127 | i3lock_srcs,
128 | install: true,
129 | include_directories: inc,
130 | dependencies: i3lock_deps,
131 | )
132 |
133 | install_subdir(
134 | 'pam',
135 | strip_directory: true,
136 | install_dir: join_paths(get_option('sysconfdir'), 'pam.d'),
137 | )
138 |
--------------------------------------------------------------------------------
/meson/meson-dist-script:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -eu
4 |
5 | cd "${MESON_DIST_ROOT}"
6 |
7 | # Delete everything we do not want to have in the release tarballs:
8 | rm -rf \
9 | .clang-format \
10 | .editorconfig \
11 | ci \
12 | .github
13 |
--------------------------------------------------------------------------------
/meson_options.txt:
--------------------------------------------------------------------------------
1 | # -*- mode: meson -*-
2 |
--------------------------------------------------------------------------------
/pam/i3lock:
--------------------------------------------------------------------------------
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
7 |
--------------------------------------------------------------------------------
/randr.c:
--------------------------------------------------------------------------------
1 | /*
2 | * vim:ts=4:sw=4:expandtab
3 | *
4 | * © 2010 Michael Stapelberg
5 | *
6 | * See LICENSE for licensing information
7 | *
8 | */
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
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 |
65 | xcb_randr_select_input(conn, root,
66 | XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
67 | XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
68 | XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
69 | XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
70 |
71 | xcb_flush(conn);
72 | }
73 |
74 | void _xinerama_init(void) {
75 | if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) {
76 | DEBUG("Xinerama extension not found, disabling.\n");
77 | return;
78 | }
79 |
80 | xcb_xinerama_is_active_cookie_t cookie;
81 | xcb_xinerama_is_active_reply_t *reply;
82 |
83 | cookie = xcb_xinerama_is_active(conn);
84 | reply = xcb_xinerama_is_active_reply(conn, cookie, NULL);
85 | if (!reply) {
86 | return;
87 | }
88 |
89 | if (!reply->state) {
90 | free(reply);
91 | return;
92 | }
93 |
94 | xinerama_active = true;
95 | free(reply);
96 | }
97 |
98 | /*
99 | * randr_query_outputs_15 uses RandR ≥ 1.5 to update outputs.
100 | *
101 | */
102 | static bool _randr_query_monitors_15(xcb_window_t root) {
103 | #if XCB_RANDR_MINOR_VERSION < 5
104 | return false;
105 | #else
106 | /* RandR 1.5 available at compile-time, i.e. libxcb is new enough */
107 | if (!has_randr_1_5) {
108 | return false;
109 | }
110 | /* RandR 1.5 available at run-time (supported by the server) */
111 | DEBUG("Querying monitors using RandR 1.5\n");
112 | xcb_generic_error_t *err;
113 | xcb_randr_get_monitors_reply_t *monitors =
114 | xcb_randr_get_monitors_reply(
115 | conn, xcb_randr_get_monitors(conn, root, true), &err);
116 | if (err != NULL) {
117 | DEBUG("Could not get RandR monitors: X11 error code %d\n", err->error_code);
118 | free(err);
119 | /* Fall back to RandR ≤ 1.4 */
120 | return false;
121 | }
122 |
123 | int screens = xcb_randr_get_monitors_monitors_length(monitors);
124 | DEBUG("%d RandR monitors found (timestamp %d)\n",
125 | screens, monitors->timestamp);
126 |
127 | Rect *resolutions = malloc(screens * sizeof(Rect));
128 | /* No memory? Just keep on using the old information. */
129 | if (!resolutions) {
130 | free(monitors);
131 | return true;
132 | }
133 |
134 | xcb_randr_monitor_info_iterator_t iter;
135 | int screen;
136 | for (iter = xcb_randr_get_monitors_monitors_iterator(monitors), screen = 0;
137 | iter.rem;
138 | xcb_randr_monitor_info_next(&iter), screen++) {
139 | const xcb_randr_monitor_info_t *monitor_info = iter.data;
140 |
141 | resolutions[screen].x = monitor_info->x;
142 | resolutions[screen].y = monitor_info->y;
143 | resolutions[screen].width = monitor_info->width;
144 | resolutions[screen].height = monitor_info->height;
145 | DEBUG("found RandR monitor: %d x %d at %d x %d\n",
146 | monitor_info->width, monitor_info->height,
147 | monitor_info->x, monitor_info->y);
148 | }
149 | free(xr_resolutions);
150 | xr_resolutions = resolutions;
151 | xr_screens = screens;
152 |
153 | free(monitors);
154 | return true;
155 | #endif
156 | }
157 |
158 | /*
159 | * randr_query_outputs_14 uses RandR ≤ 1.4 to update outputs.
160 | *
161 | */
162 | static bool _randr_query_outputs_14(xcb_window_t root) {
163 | if (!has_randr) {
164 | return false;
165 | }
166 | DEBUG("Querying outputs using RandR ≤ 1.4\n");
167 |
168 | /* Get screen resources (primary output, crtcs, outputs, modes) */
169 | xcb_randr_get_screen_resources_current_cookie_t rcookie;
170 | rcookie = xcb_randr_get_screen_resources_current(conn, root);
171 |
172 | xcb_randr_get_screen_resources_current_reply_t *res =
173 | xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL);
174 | if (res == NULL) {
175 | DEBUG("Could not query screen resources.\n");
176 | return false;
177 | }
178 |
179 | /* timestamp of the configuration so that we get consistent replies to all
180 | * requests (if the configuration changes between our different calls) */
181 | const xcb_timestamp_t cts = res->config_timestamp;
182 |
183 | const int len = xcb_randr_get_screen_resources_current_outputs_length(res);
184 |
185 | /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */
186 | xcb_randr_output_t *randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
187 |
188 | /* Request information for each output */
189 | xcb_randr_get_output_info_cookie_t ocookie[len];
190 | for (int i = 0; i < len; i++) {
191 | ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts);
192 | }
193 | Rect *resolutions = malloc(len * sizeof(Rect));
194 | /* No memory? Just keep on using the old information. */
195 | if (!resolutions) {
196 | free(res);
197 | return true;
198 | }
199 |
200 | /* Loop through all outputs available for this X11 screen */
201 | int screen = 0;
202 |
203 | for (int i = 0; i < len; i++) {
204 | xcb_randr_get_output_info_reply_t *output;
205 |
206 | if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) {
207 | continue;
208 | }
209 |
210 | if (output->crtc == XCB_NONE) {
211 | free(output);
212 | continue;
213 | }
214 |
215 | xcb_randr_get_crtc_info_cookie_t icookie;
216 | xcb_randr_get_crtc_info_reply_t *crtc;
217 | icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts);
218 | if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
219 | DEBUG("Skipping output: could not get CRTC (0x%08x)\n", output->crtc);
220 | free(output);
221 | continue;
222 | }
223 |
224 | resolutions[screen].x = crtc->x;
225 | resolutions[screen].y = crtc->y;
226 | resolutions[screen].width = crtc->width;
227 | resolutions[screen].height = crtc->height;
228 |
229 | DEBUG("found RandR output: %d x %d at %d x %d\n",
230 | crtc->width, crtc->height,
231 | crtc->x, crtc->y);
232 |
233 | screen++;
234 |
235 | free(crtc);
236 |
237 | free(output);
238 | }
239 | free(xr_resolutions);
240 | xr_resolutions = resolutions;
241 | xr_screens = screen;
242 | free(res);
243 | return true;
244 | }
245 |
246 | void _xinerama_query_screens(void) {
247 | if (!xinerama_active) {
248 | return;
249 | }
250 |
251 | xcb_xinerama_query_screens_cookie_t cookie;
252 | xcb_xinerama_query_screens_reply_t *reply;
253 | xcb_xinerama_screen_info_t *screen_info;
254 | xcb_generic_error_t *err;
255 | cookie = xcb_xinerama_query_screens_unchecked(conn);
256 | reply = xcb_xinerama_query_screens_reply(conn, cookie, &err);
257 | if (!reply) {
258 | DEBUG("Couldn't get Xinerama screens: X11 error code %d\n", err->error_code);
259 | free(err);
260 | return;
261 | }
262 | screen_info = xcb_xinerama_query_screens_screen_info(reply);
263 | int screens = xcb_xinerama_query_screens_screen_info_length(reply);
264 |
265 | Rect *resolutions = malloc(screens * sizeof(Rect));
266 | /* No memory? Just keep on using the old information. */
267 | if (!resolutions) {
268 | free(reply);
269 | return;
270 | }
271 |
272 | for (int screen = 0; screen < xr_screens; screen++) {
273 | resolutions[screen].x = screen_info[screen].x_org;
274 | resolutions[screen].y = screen_info[screen].y_org;
275 | resolutions[screen].width = screen_info[screen].width;
276 | resolutions[screen].height = screen_info[screen].height;
277 | DEBUG("found Xinerama screen: %d x %d at %d x %d\n",
278 | screen_info[screen].width, screen_info[screen].height,
279 | screen_info[screen].x_org, screen_info[screen].y_org);
280 | }
281 |
282 | free(xr_resolutions);
283 | xr_resolutions = resolutions;
284 | xr_screens = screens;
285 |
286 | free(reply);
287 | }
288 |
289 | void randr_query(xcb_window_t root) {
290 | if (_randr_query_monitors_15(root)) {
291 | return;
292 | }
293 |
294 | if (_randr_query_outputs_14(root)) {
295 | return;
296 | }
297 |
298 | _xinerama_query_screens();
299 | }
300 |
--------------------------------------------------------------------------------
/unlock_indicator.c:
--------------------------------------------------------------------------------
1 | /*
2 | * vim:ts=4:sw=4:expandtab
3 | *
4 | * © 2010 Michael Stapelberg
5 | *
6 | * See LICENSE for licensing information
7 | *
8 | */
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 | #include "i3lock.h"
21 | #include "xcb.h"
22 | #include "unlock_indicator.h"
23 | #include "randr.h"
24 | #include "dpi.h"
25 |
26 | #define BUTTON_RADIUS 90
27 | #define BUTTON_SPACE (BUTTON_RADIUS + 5)
28 | #define BUTTON_CENTER (BUTTON_RADIUS + 5)
29 | #define BUTTON_DIAMETER (2 * BUTTON_SPACE)
30 |
31 | /*******************************************************************************
32 | * Variables defined in i3lock.c.
33 | ******************************************************************************/
34 |
35 | extern bool debug_mode;
36 |
37 | /* The current position in the input buffer. Useful to determine if any
38 | * characters of the password have already been entered or not. */
39 | extern int input_position;
40 |
41 | /* The lock window. */
42 | extern xcb_window_t win;
43 |
44 | /* The current resolution of the X11 root window. */
45 | extern uint32_t last_resolution[2];
46 |
47 | /* Whether the unlock indicator is enabled (defaults to true). */
48 | extern bool unlock_indicator;
49 |
50 | /* List of pressed modifiers, or NULL if none are pressed. */
51 | extern char *modifier_string;
52 | /* Name of the current keyboard layout or NULL if not initialized. */
53 | char *layout_string = NULL;
54 |
55 | /* A Cairo surface containing the specified image (-i), if any. */
56 | extern cairo_surface_t *img;
57 |
58 | /* Whether the image should be tiled. */
59 | extern bool tile;
60 | /* The background color to use (in hex). */
61 | extern char color[7];
62 |
63 | /* Whether the failed attempts should be displayed. */
64 | extern bool show_failed_attempts;
65 | /* Whether keyboard layout should be displayed. */
66 | extern bool show_keyboard_layout;
67 | /* Number of failed unlock attempts. */
68 | extern int failed_attempts;
69 |
70 | extern struct xkb_keymap *xkb_keymap;
71 | extern struct xkb_state *xkb_state;
72 |
73 | /*******************************************************************************
74 | * Variables defined in xcb.c.
75 | ******************************************************************************/
76 |
77 | /* The root screen, to determine the DPI. */
78 | extern xcb_screen_t *screen;
79 |
80 | /*******************************************************************************
81 | * Local variables.
82 | ******************************************************************************/
83 |
84 | /* Cache the screen’s visual, necessary for creating a Cairo context. */
85 | static xcb_visualtype_t *vistype;
86 |
87 | /* Maintain the current unlock/PAM state to draw the appropriate unlock
88 | * indicator. */
89 | unlock_state_t unlock_state;
90 | auth_state_t auth_state;
91 |
92 | static void string_append(char **string_ptr, const char *appended) {
93 | char *tmp = NULL;
94 | if (*string_ptr == NULL) {
95 | if (asprintf(&tmp, "%s", appended) != -1) {
96 | *string_ptr = tmp;
97 | }
98 | } else if (asprintf(&tmp, "%s, %s", *string_ptr, appended) != -1) {
99 | free(*string_ptr);
100 | *string_ptr = tmp;
101 | }
102 | }
103 |
104 | static void display_button_text(
105 | cairo_t *ctx, const char *text, double y_offset, bool use_dark_text) {
106 | cairo_text_extents_t extents;
107 | double x, y;
108 |
109 | cairo_text_extents(ctx, text, &extents);
110 | x = BUTTON_CENTER - ((extents.width / 2) + extents.x_bearing);
111 | y = BUTTON_CENTER - ((extents.height / 2) + extents.y_bearing) + y_offset;
112 |
113 | cairo_move_to(ctx, x, y);
114 | if (use_dark_text) {
115 | cairo_set_source_rgb(ctx, 0., 0., 0.);
116 | } else {
117 | cairo_set_source_rgb(ctx, 1., 1., 1.);
118 | }
119 | cairo_show_text(ctx, text);
120 | cairo_close_path(ctx);
121 | }
122 |
123 | static void update_layout_string() {
124 | if (layout_string) {
125 | free(layout_string);
126 | layout_string = NULL;
127 | }
128 | xkb_layout_index_t num_layouts = xkb_keymap_num_layouts(xkb_keymap);
129 | for (xkb_layout_index_t i = 0; i < num_layouts; ++i) {
130 | if (xkb_state_layout_index_is_active(xkb_state, i, XKB_STATE_LAYOUT_EFFECTIVE)) {
131 | const char *name = xkb_keymap_layout_get_name(xkb_keymap, i);
132 | if (name) {
133 | string_append(&layout_string, name);
134 | }
135 | }
136 | }
137 | }
138 |
139 | /* check_modifier_keys describes the currently active modifiers (Caps Lock, Alt,
140 | Num Lock or Super) in the modifier_string variable. */
141 | static void check_modifier_keys(void) {
142 | xkb_mod_index_t idx, num_mods;
143 | const char *mod_name;
144 |
145 | num_mods = xkb_keymap_num_mods(xkb_keymap);
146 |
147 | for (idx = 0; idx < num_mods; idx++) {
148 | if (!xkb_state_mod_index_is_active(xkb_state, idx, XKB_STATE_MODS_EFFECTIVE)) {
149 | continue;
150 | }
151 |
152 | mod_name = xkb_keymap_mod_get_name(xkb_keymap, idx);
153 | if (mod_name == NULL) {
154 | continue;
155 | }
156 |
157 | /* Replace certain xkb names with nicer, human-readable ones. */
158 | if (strcmp(mod_name, XKB_MOD_NAME_CAPS) == 0) {
159 | mod_name = "Caps Lock";
160 | } else if (strcmp(mod_name, XKB_MOD_NAME_NUM) == 0) {
161 | mod_name = "Num Lock";
162 | } else {
163 | /* Show only Caps Lock and Num Lock, other modifiers (e.g. Shift)
164 | * leak state about the password. */
165 | continue;
166 | }
167 | string_append(&modifier_string, mod_name);
168 | }
169 | }
170 |
171 | /*
172 | * Draws global image with fill color onto a pixmap with the given
173 | * resolution and returns it.
174 | *
175 | */
176 | void draw_image(xcb_pixmap_t bg_pixmap, uint32_t *resolution) {
177 | const double scaling_factor = get_dpi_value() / 96.0;
178 | int button_diameter_physical = ceil(scaling_factor * BUTTON_DIAMETER);
179 | DEBUG("scaling_factor is %.f, physical diameter is %d px\n",
180 | scaling_factor, button_diameter_physical);
181 |
182 | if (!vistype) {
183 | vistype = get_root_visual_type(screen);
184 | }
185 |
186 | /* Initialize cairo: Create one in-memory surface to render the unlock
187 | * indicator on, create one XCB surface to actually draw (one or more,
188 | * depending on the amount of screens) unlock indicators on. */
189 | cairo_surface_t *output = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, button_diameter_physical, button_diameter_physical);
190 | cairo_t *ctx = cairo_create(output);
191 |
192 | cairo_surface_t *xcb_output = cairo_xcb_surface_create(conn, bg_pixmap, vistype, resolution[0], resolution[1]);
193 | cairo_t *xcb_ctx = cairo_create(xcb_output);
194 |
195 | /* After the first iteration, the pixmap will still contain the previous
196 | * contents. Explicitly clear the entire pixmap with the background color
197 | * first to get back into a defined state: */
198 | char strgroups[3][3] = {{color[0], color[1], '\0'},
199 | {color[2], color[3], '\0'},
200 | {color[4], color[5], '\0'}};
201 | uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
202 | (strtol(strgroups[1], NULL, 16)),
203 | (strtol(strgroups[2], NULL, 16))};
204 | cairo_set_source_rgb(xcb_ctx, rgb16[0] / 255.0, rgb16[1] / 255.0, rgb16[2] / 255.0);
205 | cairo_rectangle(xcb_ctx, 0, 0, resolution[0], resolution[1]);
206 | cairo_fill(xcb_ctx);
207 |
208 | if (img) {
209 | if (!tile) {
210 | cairo_set_source_surface(xcb_ctx, img, 0, 0);
211 | cairo_paint(xcb_ctx);
212 | } else {
213 | /* create a pattern and fill a rectangle as big as the screen */
214 | cairo_pattern_t *pattern;
215 | pattern = cairo_pattern_create_for_surface(img);
216 | cairo_set_source(xcb_ctx, pattern);
217 | cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
218 | cairo_rectangle(xcb_ctx, 0, 0, resolution[0], resolution[1]);
219 | cairo_fill(xcb_ctx);
220 | cairo_pattern_destroy(pattern);
221 | }
222 | }
223 |
224 | if (unlock_indicator &&
225 | (unlock_state >= STATE_KEY_PRESSED || auth_state > STATE_AUTH_IDLE)) {
226 | cairo_scale(ctx, scaling_factor, scaling_factor);
227 | /* Draw a (centered) circle with transparent background. */
228 | cairo_set_line_width(ctx, 10.0);
229 | cairo_arc(ctx,
230 | BUTTON_CENTER /* x */,
231 | BUTTON_CENTER /* y */,
232 | BUTTON_RADIUS /* radius */,
233 | 0 /* start */,
234 | 2 * M_PI /* end */);
235 |
236 | /* Use the appropriate color for the different PAM states
237 | * (currently verifying, wrong password, or default) */
238 | switch (auth_state) {
239 | case STATE_AUTH_VERIFY:
240 | case STATE_AUTH_LOCK:
241 | cairo_set_source_rgba(ctx, 0, 114.0 / 255, 255.0 / 255, 0.75);
242 | break;
243 | case STATE_AUTH_WRONG:
244 | case STATE_I3LOCK_LOCK_FAILED:
245 | cairo_set_source_rgba(ctx, 250.0 / 255, 0, 0, 0.75);
246 | break;
247 | default:
248 | if (unlock_state == STATE_NOTHING_TO_DELETE) {
249 | cairo_set_source_rgba(ctx, 250.0 / 255, 0, 0, 0.75);
250 | break;
251 | }
252 | cairo_set_source_rgba(ctx, 0, 0, 0, 0.75);
253 | break;
254 | }
255 | cairo_fill_preserve(ctx);
256 |
257 | bool use_dark_text = true;
258 |
259 | switch (auth_state) {
260 | case STATE_AUTH_VERIFY:
261 | case STATE_AUTH_LOCK:
262 | cairo_set_source_rgb(ctx, 51.0 / 255, 0, 250.0 / 255);
263 | break;
264 | case STATE_AUTH_WRONG:
265 | case STATE_I3LOCK_LOCK_FAILED:
266 | cairo_set_source_rgb(ctx, 125.0 / 255, 51.0 / 255, 0);
267 | break;
268 | case STATE_AUTH_IDLE:
269 | if (unlock_state == STATE_NOTHING_TO_DELETE) {
270 | cairo_set_source_rgb(ctx, 125.0 / 255, 51.0 / 255, 0);
271 | break;
272 | }
273 |
274 | cairo_set_source_rgb(ctx, 51.0 / 255, 125.0 / 255, 0);
275 | use_dark_text = false;
276 | break;
277 | }
278 | cairo_stroke(ctx);
279 |
280 | /* Draw an inner seperator line. */
281 | cairo_set_source_rgb(ctx, 0, 0, 0);
282 | cairo_set_line_width(ctx, 2.0);
283 | cairo_arc(ctx,
284 | BUTTON_CENTER /* x */,
285 | BUTTON_CENTER /* y */,
286 | BUTTON_RADIUS - 5 /* radius */,
287 | 0,
288 | 2 * M_PI);
289 | cairo_stroke(ctx);
290 |
291 | cairo_set_line_width(ctx, 10.0);
292 |
293 | /* Display a (centered) text of the current PAM state. */
294 | char *text = NULL;
295 | /* We don't want to show more than a 3-digit number. */
296 | char buf[4];
297 |
298 | cairo_set_source_rgb(ctx, 0, 0, 0);
299 | cairo_select_font_face(ctx, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
300 | cairo_set_font_size(ctx, 28.0);
301 | switch (auth_state) {
302 | case STATE_AUTH_VERIFY:
303 | text = "Verifying…";
304 | break;
305 | case STATE_AUTH_LOCK:
306 | text = "Locking…";
307 | break;
308 | case STATE_AUTH_WRONG:
309 | text = "Wrong!";
310 | break;
311 | case STATE_I3LOCK_LOCK_FAILED:
312 | text = "Lock failed!";
313 | break;
314 | default:
315 | if (unlock_state == STATE_NOTHING_TO_DELETE) {
316 | text = "No input";
317 | }
318 | if (show_failed_attempts && failed_attempts > 0) {
319 | if (failed_attempts > 999) {
320 | text = "> 999";
321 | } else {
322 | snprintf(buf, sizeof(buf), "%d", failed_attempts);
323 | text = buf;
324 | }
325 | cairo_set_source_rgb(ctx, 1, 0, 0);
326 | cairo_set_font_size(ctx, 32.0);
327 | }
328 | break;
329 | }
330 |
331 | if (text) {
332 | display_button_text(ctx, text, 0., use_dark_text);
333 | }
334 |
335 | if (modifier_string != NULL) {
336 | cairo_set_font_size(ctx, 14.0);
337 | display_button_text(ctx, modifier_string, 28., use_dark_text);
338 | }
339 | if (show_keyboard_layout && layout_string != NULL) {
340 | cairo_set_font_size(ctx, 14.0);
341 | display_button_text(ctx, layout_string, -28., use_dark_text);
342 | }
343 |
344 | /* After the user pressed any valid key or the backspace key, we
345 | * highlight a random part of the unlock indicator to confirm this
346 | * keypress. */
347 | if (unlock_state == STATE_KEY_ACTIVE ||
348 | unlock_state == STATE_BACKSPACE_ACTIVE) {
349 | cairo_new_sub_path(ctx);
350 | double highlight_start = (rand() % (int)(2 * M_PI * 100)) / 100.0;
351 | cairo_arc(ctx,
352 | BUTTON_CENTER /* x */,
353 | BUTTON_CENTER /* y */,
354 | BUTTON_RADIUS /* radius */,
355 | highlight_start,
356 | highlight_start + (M_PI / 3.0));
357 | if (unlock_state == STATE_KEY_ACTIVE) {
358 | /* For normal keys, we use a lighter green. */
359 | cairo_set_source_rgb(ctx, 51.0 / 255, 219.0 / 255, 0);
360 | } else {
361 | /* For backspace, we use red. */
362 | cairo_set_source_rgb(ctx, 219.0 / 255, 51.0 / 255, 0);
363 | }
364 | cairo_stroke(ctx);
365 |
366 | /* Draw two little separators for the highlighted part of the
367 | * unlock indicator. */
368 | cairo_set_source_rgb(ctx, 0, 0, 0);
369 | cairo_arc(ctx,
370 | BUTTON_CENTER /* x */,
371 | BUTTON_CENTER /* y */,
372 | BUTTON_RADIUS /* radius */,
373 | highlight_start /* start */,
374 | highlight_start + (M_PI / 128.0) /* end */);
375 | cairo_stroke(ctx);
376 | cairo_arc(ctx,
377 | BUTTON_CENTER /* x */,
378 | BUTTON_CENTER /* y */,
379 | BUTTON_RADIUS /* radius */,
380 | (highlight_start + (M_PI / 3.0)) - (M_PI / 128.0) /* start */,
381 | highlight_start + (M_PI / 3.0) /* end */);
382 | cairo_stroke(ctx);
383 | }
384 | }
385 |
386 | if (xr_screens > 0) {
387 | /* Composite the unlock indicator in the middle of each screen. */
388 | for (int screen = 0; screen < xr_screens; screen++) {
389 | int x = (xr_resolutions[screen].x + ((xr_resolutions[screen].width / 2) - (button_diameter_physical / 2)));
390 | int y = (xr_resolutions[screen].y + ((xr_resolutions[screen].height / 2) - (button_diameter_physical / 2)));
391 | cairo_set_source_surface(xcb_ctx, output, x, y);
392 | cairo_rectangle(xcb_ctx, x, y, button_diameter_physical, button_diameter_physical);
393 | cairo_fill(xcb_ctx);
394 | }
395 | } else {
396 | /* We have no information about the screen sizes/positions, so we just
397 | * place the unlock indicator in the middle of the X root window and
398 | * hope for the best. */
399 | int x = (last_resolution[0] / 2) - (button_diameter_physical / 2);
400 | int y = (last_resolution[1] / 2) - (button_diameter_physical / 2);
401 | cairo_set_source_surface(xcb_ctx, output, x, y);
402 | cairo_rectangle(xcb_ctx, x, y, button_diameter_physical, button_diameter_physical);
403 | cairo_fill(xcb_ctx);
404 | }
405 |
406 | cairo_surface_destroy(xcb_output);
407 | cairo_surface_destroy(output);
408 | cairo_destroy(ctx);
409 | cairo_destroy(xcb_ctx);
410 | }
411 |
412 | static xcb_pixmap_t bg_pixmap = XCB_NONE;
413 |
414 | /*
415 | * Releases the current background pixmap so that the next redraw_screen() call
416 | * will allocate a new one with the updated resolution.
417 | *
418 | */
419 | void free_bg_pixmap(void) {
420 | xcb_free_pixmap(conn, bg_pixmap);
421 | bg_pixmap = XCB_NONE;
422 | }
423 |
424 | /*
425 | * Calls draw_image on a new pixmap and swaps that with the current pixmap
426 | *
427 | */
428 | void redraw_screen(void) {
429 | DEBUG("redraw_screen(unlock_state = %d, auth_state = %d)\n", unlock_state, auth_state);
430 |
431 | if (modifier_string) {
432 | free(modifier_string);
433 | modifier_string = NULL;
434 | }
435 | check_modifier_keys();
436 | update_layout_string();
437 |
438 | if (bg_pixmap == XCB_NONE) {
439 | DEBUG("allocating pixmap for %d x %d px\n", last_resolution[0], last_resolution[1]);
440 | bg_pixmap = create_bg_pixmap(conn, screen, last_resolution, color);
441 | }
442 |
443 | draw_image(bg_pixmap, last_resolution);
444 | xcb_change_window_attributes(conn, win, XCB_CW_BACK_PIXMAP, (uint32_t[1]){bg_pixmap});
445 | /* XXX: Possible optimization: Only update the area in the middle of the
446 | * screen instead of the whole screen. */
447 | xcb_clear_area(conn, 0, win, 0, 0, last_resolution[0], last_resolution[1]);
448 | xcb_flush(conn);
449 | }
450 |
451 | /*
452 | * Hides the unlock indicator completely when there is no content in the
453 | * password buffer.
454 | *
455 | */
456 | void clear_indicator(void) {
457 | if (input_position == 0) {
458 | unlock_state = STATE_STARTED;
459 | } else {
460 | unlock_state = STATE_KEY_PRESSED;
461 | }
462 | redraw_screen();
463 | }
464 |
--------------------------------------------------------------------------------
/xcb.c:
--------------------------------------------------------------------------------
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
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
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 |
102 | visual_type = visual_iter.data;
103 | return visual_type;
104 | }
105 | }
106 |
107 | return NULL;
108 | }
109 |
110 | xcb_pixmap_t create_bg_pixmap(xcb_connection_t *conn, xcb_screen_t *scr, u_int32_t *resolution, char *color) {
111 | xcb_pixmap_t bg_pixmap = xcb_generate_id(conn);
112 | xcb_create_pixmap(conn, scr->root_depth, bg_pixmap, scr->root,
113 | resolution[0], resolution[1]);
114 |
115 | /* Generate a Graphics Context and fill the pixmap with background color
116 | * (for images that are smaller than your screen) */
117 | xcb_gcontext_t gc = xcb_generate_id(conn);
118 | uint32_t values[] = {get_colorpixel(color)};
119 | xcb_create_gc(conn, gc, bg_pixmap, XCB_GC_FOREGROUND, values);
120 | xcb_rectangle_t rect = {0, 0, resolution[0], resolution[1]};
121 | xcb_poly_fill_rectangle(conn, bg_pixmap, gc, 1, &rect);
122 | xcb_free_gc(conn, gc);
123 |
124 | return bg_pixmap;
125 | }
126 |
127 | xcb_window_t open_fullscreen_window(xcb_connection_t *conn, xcb_screen_t *scr, char *color, xcb_pixmap_t pixmap) {
128 | uint32_t mask = 0;
129 | uint32_t values[3];
130 | xcb_window_t win = xcb_generate_id(conn);
131 |
132 | if (pixmap == XCB_NONE) {
133 | mask |= XCB_CW_BACK_PIXEL;
134 | values[0] = get_colorpixel(color);
135 | } else {
136 | mask |= XCB_CW_BACK_PIXMAP;
137 | values[0] = pixmap;
138 | }
139 |
140 | mask |= XCB_CW_OVERRIDE_REDIRECT;
141 | values[1] = 1;
142 |
143 | mask |= XCB_CW_EVENT_MASK;
144 | values[2] = XCB_EVENT_MASK_EXPOSURE |
145 | XCB_EVENT_MASK_KEY_PRESS |
146 | XCB_EVENT_MASK_KEY_RELEASE |
147 | XCB_EVENT_MASK_VISIBILITY_CHANGE |
148 | XCB_EVENT_MASK_STRUCTURE_NOTIFY;
149 |
150 | xcb_create_window(conn,
151 | XCB_COPY_FROM_PARENT,
152 | win, /* the window id */
153 | scr->root, /* parent == root */
154 | 0, 0,
155 | scr->width_in_pixels,
156 | scr->height_in_pixels, /* dimensions */
157 | 0, /* border = 0, we draw our own */
158 | XCB_WINDOW_CLASS_INPUT_OUTPUT,
159 | XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
160 | mask,
161 | values);
162 |
163 | char *name = "i3lock";
164 | xcb_change_property(conn,
165 | XCB_PROP_MODE_REPLACE,
166 | win,
167 | XCB_ATOM_WM_NAME,
168 | XCB_ATOM_STRING,
169 | 8,
170 | strlen(name),
171 | name);
172 |
173 | xcb_change_property(conn,
174 | XCB_PROP_MODE_REPLACE,
175 | win,
176 | XCB_ATOM_WM_CLASS,
177 | XCB_ATOM_STRING,
178 | 8,
179 | 2 * (strlen("i3lock") + 1),
180 | "i3lock\0i3lock\0");
181 |
182 | const uint32_t bypass_compositor = 1; /* disable compositing */
183 | _init_net_wm_bypass_compositor(conn);
184 | xcb_change_property(conn,
185 | XCB_PROP_MODE_REPLACE,
186 | win,
187 | _NET_WM_BYPASS_COMPOSITOR,
188 | XCB_ATOM_CARDINAL,
189 | 32,
190 | 1,
191 | &bypass_compositor);
192 |
193 | /* Map the window (= make it visible) */
194 | xcb_map_window(conn, win);
195 |
196 | /* Raise window (put it on top) */
197 | values[0] = XCB_STACK_MODE_ABOVE;
198 | xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_STACK_MODE, values);
199 |
200 | /* Ensure that the window is created and set up before returning */
201 | xcb_aux_sync(conn);
202 |
203 | return win;
204 | }
205 |
206 | /*
207 | * Repeatedly tries to grab pointer and keyboard (up to the specified number of
208 | * tries).
209 | *
210 | * Returns true if the grab succeeded, false if not.
211 | *
212 | */
213 | bool grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_t cursor, int tries) {
214 | xcb_grab_pointer_cookie_t pcookie;
215 | xcb_grab_pointer_reply_t *preply;
216 |
217 | xcb_grab_keyboard_cookie_t kcookie;
218 | xcb_grab_keyboard_reply_t *kreply;
219 |
220 | const suseconds_t screen_redraw_timeout = 100000; /* 100ms */
221 |
222 | /* Using few variables to trigger a redraw_screen() if too many tries */
223 | bool redrawn = false;
224 | struct timeval start;
225 | if (gettimeofday(&start, NULL) == -1) {
226 | err(EXIT_FAILURE, "gettimeofday");
227 | }
228 |
229 | while (tries-- > 0) {
230 | pcookie = xcb_grab_pointer(
231 | conn,
232 | false, /* get all pointer events specified by the following mask */
233 | screen->root, /* grab the root window */
234 | XCB_NONE, /* which events to let through */
235 | XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
236 | XCB_GRAB_MODE_ASYNC, /* keyboard mode */
237 | XCB_NONE, /* confine_to = in which window should the cursor stay */
238 | cursor, /* we change the cursor to whatever the user wanted */
239 | XCB_CURRENT_TIME);
240 |
241 | if ((preply = xcb_grab_pointer_reply(conn, pcookie, NULL)) &&
242 | preply->status == XCB_GRAB_STATUS_SUCCESS) {
243 | free(preply);
244 | break;
245 | }
246 |
247 | /* In case the grab failed, we still need to free the reply */
248 | free(preply);
249 |
250 | /* Make this quite a bit slower */
251 | usleep(50);
252 |
253 | struct timeval now;
254 | if (gettimeofday(&now, NULL) == -1) {
255 | err(EXIT_FAILURE, "gettimeofday");
256 | }
257 |
258 | struct timeval elapsed;
259 | timersub(&now, &start, &elapsed);
260 |
261 | if (!redrawn &&
262 | (tries % 100) == 0 &&
263 | elapsed.tv_usec >= screen_redraw_timeout) {
264 | redraw_screen();
265 | redrawn = true;
266 | }
267 | }
268 |
269 | while (tries-- > 0) {
270 | kcookie = xcb_grab_keyboard(
271 | conn,
272 | true, /* report events */
273 | screen->root, /* grab the root window */
274 | XCB_CURRENT_TIME,
275 | XCB_GRAB_MODE_ASYNC, /* process events as normal, do not require sync */
276 | XCB_GRAB_MODE_ASYNC);
277 |
278 | if ((kreply = xcb_grab_keyboard_reply(conn, kcookie, NULL)) &&
279 | kreply->status == XCB_GRAB_STATUS_SUCCESS) {
280 | free(kreply);
281 | break;
282 | }
283 |
284 | /* In case the grab failed, we still need to free the reply */
285 | free(kreply);
286 |
287 | /* Make this quite a bit slower */
288 | usleep(50);
289 |
290 | struct timeval now;
291 | if (gettimeofday(&now, NULL) == -1) {
292 | err(EXIT_FAILURE, "gettimeofday");
293 | }
294 |
295 | struct timeval elapsed;
296 | timersub(&now, &start, &elapsed);
297 |
298 | /* Trigger a screen redraw if 100ms elapsed */
299 | if (!redrawn &&
300 | (tries % 100) == 0 &&
301 | elapsed.tv_usec >= screen_redraw_timeout) {
302 | redraw_screen();
303 | redrawn = true;
304 | }
305 | }
306 |
307 | return (tries > 0);
308 | }
309 |
310 | xcb_cursor_t create_cursor(xcb_connection_t *conn, xcb_screen_t *screen, xcb_window_t win, int choice) {
311 | xcb_pixmap_t bitmap;
312 | xcb_pixmap_t mask;
313 | xcb_cursor_t cursor;
314 |
315 | unsigned char *curs_bits;
316 | unsigned char *mask_bits;
317 | int curs_w, curs_h;
318 |
319 | switch (choice) {
320 | case CURS_NONE:
321 | curs_bits = curs_invisible_bits;
322 | mask_bits = curs_invisible_bits;
323 | curs_w = curs_invisible_width;
324 | curs_h = curs_invisible_height;
325 | break;
326 | case CURS_WIN:
327 | curs_bits = curs_windows_bits;
328 | mask_bits = mask_windows_bits;
329 | curs_w = curs_windows_width;
330 | curs_h = curs_windows_height;
331 | break;
332 | case CURS_DEFAULT:
333 | default:
334 | return XCB_NONE; /* XCB_NONE is xcb's way of saying "don't change the cursor" */
335 | }
336 |
337 | bitmap = xcb_create_pixmap_from_bitmap_data(conn,
338 | win,
339 | curs_bits,
340 | curs_w,
341 | curs_h,
342 | 1,
343 | screen->white_pixel,
344 | screen->black_pixel,
345 | NULL);
346 |
347 | mask = xcb_create_pixmap_from_bitmap_data(conn,
348 | win,
349 | mask_bits,
350 | curs_w,
351 | curs_h,
352 | 1,
353 | screen->white_pixel,
354 | screen->black_pixel,
355 | NULL);
356 |
357 | cursor = xcb_generate_id(conn);
358 |
359 | xcb_create_cursor(conn,
360 | cursor,
361 | bitmap,
362 | mask,
363 | 65535, 65535, 65535,
364 | 0, 0, 0,
365 | 0, 0);
366 |
367 | xcb_free_pixmap(conn, bitmap);
368 | xcb_free_pixmap(conn, mask);
369 |
370 | return cursor;
371 | }
372 |
373 | static xcb_atom_t _NET_ACTIVE_WINDOW = XCB_NONE;
374 | void _init_net_active_window(xcb_connection_t *conn) {
375 | if (_NET_ACTIVE_WINDOW != XCB_NONE) {
376 | /* already initialized */
377 | return;
378 | }
379 | xcb_generic_error_t *err;
380 | xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply(
381 | conn,
382 | xcb_intern_atom(conn, 0, strlen("_NET_ACTIVE_WINDOW"), "_NET_ACTIVE_WINDOW"),
383 | &err);
384 | if (atom_reply == NULL) {
385 | fprintf(stderr, "X11 Error %d\n", err->error_code);
386 | free(err);
387 | return;
388 | }
389 | _NET_ACTIVE_WINDOW = atom_reply->atom;
390 | free(atom_reply);
391 | }
392 |
393 | xcb_window_t find_focused_window(xcb_connection_t *conn, const xcb_window_t root) {
394 | xcb_window_t result = XCB_NONE;
395 |
396 | _init_net_active_window(conn);
397 |
398 | xcb_get_property_reply_t *prop_reply = xcb_get_property_reply(
399 | conn,
400 | xcb_get_property_unchecked(
401 | conn, false, root, _NET_ACTIVE_WINDOW, XCB_GET_PROPERTY_TYPE_ANY, 0, 1 /* word */),
402 | NULL);
403 | if (prop_reply == NULL) {
404 | goto out;
405 | }
406 | if (xcb_get_property_value_length(prop_reply) == 0) {
407 | goto out_prop;
408 | }
409 | if (prop_reply->type != XCB_ATOM_WINDOW) {
410 | goto out_prop;
411 | }
412 |
413 | result = *((xcb_window_t *)xcb_get_property_value(prop_reply));
414 |
415 | out_prop:
416 | free(prop_reply);
417 | out:
418 | return result;
419 | }
420 |
421 | void set_focused_window(xcb_connection_t *conn, const xcb_window_t root, const xcb_window_t window) {
422 | xcb_client_message_event_t ev;
423 | memset(&ev, '\0', sizeof(xcb_client_message_event_t));
424 |
425 | _init_net_active_window(conn);
426 |
427 | ev.response_type = XCB_CLIENT_MESSAGE;
428 | ev.window = window;
429 | ev.type = _NET_ACTIVE_WINDOW;
430 | ev.format = 32;
431 | ev.data.data32[0] = 2; /* 2 = pager */
432 |
433 | xcb_send_event(conn, false, root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev);
434 | xcb_flush(conn);
435 | }
436 |
--------------------------------------------------------------------------------