Initial commit.
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
BasedOnStyle: google
|
||||
ColumnLimit: 0
|
||||
IndentWidth: 4
|
||||
InsertBraces: true
|
||||
PointerBindsToType: false
|
||||
SortIncludes: false
|
||||
SpaceBeforeParens: ControlStatements
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/i38lock
|
||||
*.o
|
||||
tags
|
||||
*.swp
|
||||
|
||||
# We recommend building in a subdirectory called build.
|
||||
# If you chose a different directory name,
|
||||
# it is up to you to arrange for it to be ignored by git,
|
||||
# e.g. by listing your directory in .git/info/exclude.
|
||||
/build
|
||||
@@ -0,0 +1,40 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
|
||||
I38Lock is an accessible C screen-locking utility based on i3lock. Core sources live at the repository root: `i3lock.c` handles the main program, while `xcb.c`, `randr.c`, `dpi.c`, `unlock_indicator.c`, and `accessibility_feedback.c` separate display, UI, and screen-reader concerns. Public project headers are under `include/`. The PAM service file is `pam/i38lock`, the manual page is `i38lock.1`, and packaging or CI support lives in `meson/`, `ci/`, and `.github/`.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
|
||||
Install the dependencies listed in `README.md`, then build out of tree:
|
||||
|
||||
```sh
|
||||
meson setup build -Dprefix=/usr
|
||||
ninja -C build
|
||||
```
|
||||
|
||||
To reproduce CI's stricter compile check, configure a fresh build directory:
|
||||
|
||||
```sh
|
||||
CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Werror" meson setup build-ci
|
||||
ninja -C build-ci
|
||||
```
|
||||
|
||||
Run `nix develop` first if using the provided Nix development shell. Avoid installing from a development build unless installation behavior is the focus of the change.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
|
||||
The project uses C11 and the checked-in `.clang-format`. Use four-space indentation, braces for control statements, and existing snake_case naming patterns. Format C sources and headers with:
|
||||
|
||||
```sh
|
||||
clang-format-15 -i $(git ls-files '*.c' 'include/*.h')
|
||||
clang-format-15 --dry-run --Werror $(git ls-files '*.c' 'include/*.h')
|
||||
```
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
There is no automated runtime test suite. Every change must compile with the strict warning flags above. For behavior changes, run `timeout --foreground 30s build/i38lock -n` from an X11 session, verify the affected workflow, then unlock by entering your password and pressing Enter. Install `pam/i38lock` first and keep the watchdog enabled to avoid locking yourself out of an active development session.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
Recent commits use short imperative subjects, often scoped when useful, such as `meson: use explicit_bzero if it is available`. Keep each commit focused. Pull requests should explain the behavior change, include reproduction and verification steps, and link relevant issues. Include `i38lock --version` and environment details for bug fixes. Do not propose image-manipulation features; preprocess background images with external tools.
|
||||
@@ -0,0 +1,234 @@
|
||||
2025-10-31 i3lock 2.16
|
||||
|
||||
• fix crash when the user changes the XKB configuration
|
||||
• when started on Wayland, display an error and usage
|
||||
• switch to clang-format 15 (with InsertBraces)
|
||||
• fix -Werror=calloc-transposed-args by swapping calloc args
|
||||
• reword: remove "dynamic" TWM
|
||||
• update meson setup command in README
|
||||
• do not increase failed_attempts beyond 999
|
||||
• i3lock.1 man page: fix acute accent
|
||||
• declare a development shell in flake.nix
|
||||
• fix in_dpi variable checking
|
||||
• meson: use explicit_bzero if it is available
|
||||
|
||||
2024-03-16 i3lock 2.15
|
||||
|
||||
• unlock indicator: display current keyboard layout when
|
||||
user types password (with --show-keyboard-layout flag).
|
||||
|
||||
2022-06-21 i3lock 2.14.1
|
||||
|
||||
• unlock indicator: display only caps lock and num lock,
|
||||
not all modifiers like shift (which can leak information
|
||||
about your password to bystanders)
|
||||
|
||||
2022-05-28 i3lock 2.14
|
||||
|
||||
• Change default background color to #a3a3a3
|
||||
See https://github.com/i3/i3lock/pull/300 for extensive
|
||||
discussion and rationale for this change.
|
||||
• Recommend using xss-lock to start i3lock in the README
|
||||
and i3lock.1 man page. xss-lock is the best way to ensure
|
||||
your screen truly is locked before your computer suspends.
|
||||
• Display modifier key warning before unlocking, too,
|
||||
not just on failed attempts like before.
|
||||
• Switch build system from autotools to meson.
|
||||
|
||||
2020-10-27 i3lock 2.13
|
||||
|
||||
• Throw error when trying to start on Wayland
|
||||
• Use explicit_bzero() where available, not just on OpenBSD
|
||||
• avoid pixmap allocations in the redraw path
|
||||
• make --debug output go to stderr
|
||||
• unlock_indicator.c: fix build failure against gcc-10
|
||||
• fix: call pam_end in cleanup in main, not in event loop
|
||||
• set _NET_WM_BYPASS_COMPOSITOR hint to avoid flickering
|
||||
|
||||
2019-07-21 i3lock 2.12
|
||||
|
||||
• remove stray \n from error messages
|
||||
• capitalize unlock indicator contents
|
||||
• set WM_CLASS property
|
||||
• reference modifier as “Super”, not “Win”
|
||||
• add --raw option to read image as raw bytes
|
||||
|
||||
2018-10-18 i3lock 2.11.1
|
||||
|
||||
• Fix dist tarball by including I3LOCK_VERSION
|
||||
|
||||
2018-10-10 i3lock 2.11
|
||||
|
||||
• Switch to autotools
|
||||
• Display an error when backspace is pressed without any input
|
||||
• Print an error when a non-PNG file is opened
|
||||
(i3lock only supports PNG files) (Thanks eplanet)
|
||||
• Don’t unnecessarily check the xcb_connect return value,
|
||||
it is known never to be NULL (Thanks SegFault42)
|
||||
• Fix memory leak when grabbing fails (Thanks karulont)
|
||||
• Respect Xft.dpi for determining the unlock indicator’s scale factor
|
||||
• Discard pending password verification attempts
|
||||
when a new password is entered (Thanks layus)
|
||||
|
||||
2017-11-25 i3lock 2.10
|
||||
|
||||
• Only use -lpam when not on OpenBSD (Thanks Kaashif)
|
||||
• locale: treat empty string same as unset (Thanks Ran)
|
||||
• Fix overwrite of getopt optind (Thanks jakob)
|
||||
• Immediately hide the unlock indicator after ESC / C-u (Thanks Orestis)
|
||||
• Measure wall-clock time instead of CPU time for “locking” indicator.
|
||||
• SetInputFocus to the i3lock window to force-close context menus
|
||||
• Use RandR for learning about attached monitors
|
||||
|
||||
2017-06-21 i3lock 2.9.1
|
||||
|
||||
• Fix version number mechanism (for --version)
|
||||
• Revert the fix for composited notifications, as it causes more issues than
|
||||
it solves:
|
||||
https://github.com/i3/i3lock/issues/130
|
||||
https://github.com/i3/i3lock/issues/128
|
||||
|
||||
2017-05-26 i3lock 2.9
|
||||
|
||||
• i3lock.1: use signal names without SIG prefix
|
||||
• Removed obsolete inactivity timeout
|
||||
• Added version files for release tarball.
|
||||
• Set font face
|
||||
• Automatically unlock (without having to press <Enter>) one attempt which was
|
||||
entered while authenticating
|
||||
• Stop leaking the image_path dup
|
||||
• Displaying locking message when grabbing the pointer/keyboard
|
||||
• Display error message when locking failed
|
||||
• Add Enter on C-m
|
||||
• Change input slices to be exactly pi/3 in size instead of slightly more
|
||||
• Fix covering of composited notifications using the XComposite extension
|
||||
• Remove last traces of DPMS
|
||||
• Use bsd_auth(3) instead of PAM on OpenBSD
|
||||
• Restore intended behaviour and don't use mlock(2) on OpenBSD.
|
||||
|
||||
2016-06-04 i3lock 2.8
|
||||
|
||||
• Remove DPMS support in favor of a wrapper script and xset(1).
|
||||
• Indicate that the --inactivity-timeout option takes an argument. (Thanks
|
||||
Kenneth Lyons)
|
||||
• fix pam_securetty: set PAM_TTY to getenv("DISPLAY")
|
||||
• Eat XKB_KEY_Delete and XKB_KEY_KP_Delete (Thanks bebehei)
|
||||
• Show unlock indicator if password was entered during PAM verification
|
||||
• Allow CTRL+J as enter and CTRL+H as backspace (Thanks Karl Tarbe)
|
||||
• Flush xcb connection after opening fullscreen window (Thanks martin)
|
||||
• Add support for `xss-lock --transfer-sleep-lock'
|
||||
|
||||
2015-05-20 i3lock 2.7
|
||||
|
||||
• Die when the X11 connection breaks during runtime (Thanks Eduan)
|
||||
• Implement logging the number of failed attempts (Thanks koebi)
|
||||
• Ignore password validation is pam is in wrong state (Thanks Neodyblue)
|
||||
• Get current user with getpwuid() instead of using $ENV{USER} (Thanks Martin)
|
||||
• Add support for Compose and dead-keys with libxkbcommon.
|
||||
Requires libxkbcommon ≥ 0.5.0 (Thanks Daniel)
|
||||
• Format the source using clang-format.
|
||||
• Refresh pam credentials on successful authentication (for Kerberos and the
|
||||
like) (Thanks James)
|
||||
• List pressed modifiers on failed authentication (Thanks Deiz, Alexandre)
|
||||
• Only redraw the screen if the unlock indicator is actually used
|
||||
(Thanks Ingo)
|
||||
• Make pkg-config configurable for cross-compilation (Thanks Nikolay)
|
||||
|
||||
2014-07-18 i3lock 2.6
|
||||
|
||||
• NEW DEPENDENCY: use libxkbcommon-x11 instead of libX11
|
||||
This helps us get rid of all code that directly uses libX11
|
||||
• Use cairo_status_to_string for descriptive errors.
|
||||
• Add `-e` option to not validate empty password.
|
||||
• Bugfix: update the initial keyboard modifier state (Thanks lee, Ran)
|
||||
• Re-raise i3lock when obscured in a separate process
|
||||
• Turn on the screen on successful authentication
|
||||
• Delay to turn off the screen after wrong passwd
|
||||
• Discard half-entered passwd after some inactivity
|
||||
• Ignore empty passwd after repeated Enter keypress
|
||||
• Scale the unlock indicator (for retina displays)
|
||||
|
||||
2013-06-09 i3lock 2.5
|
||||
|
||||
• NEW DEPENDENCY: Use libxkbcommon for input handling
|
||||
This makes input handling much better for many edge cases.
|
||||
• Bugfix: fix argument parsing on ARM (s/char/int/)
|
||||
• Bugfix: free(reply) to avoid memory leak
|
||||
• Bugfix: Use ev_loop_fork after fork, fixes forking on kqueue based OSes
|
||||
• Bugfix: Fix centering the indicator in the no-xinerama case
|
||||
• Only use mlock() on Linux, FreeBSD (for example) requires root
|
||||
• promote the "could not load image" message from debug to normal
|
||||
• s/pam_message/pam_response/ (Thanks Tucos)
|
||||
• remove support for NOLIBCAIRO, cairo-xcb is widespread by now
|
||||
• Allow XKB_KEY_XF86ScreenSaver as synonym for enter
|
||||
This keysym is generated on convertible tablets by pressing a hardware
|
||||
lock/unlock button.
|
||||
• Allow passwordless PAM conversations (e.g. fingerprint)
|
||||
• Add ctrl+u password reset
|
||||
• Set window name to i3lock
|
||||
|
||||
2012-06-02 i3lock 2.4.1
|
||||
|
||||
• Bugfix: Correctly center unlock indicator after reconfiguring screens
|
||||
(Thanks xeen)
|
||||
• Bugfix: Revert shift lock handling (broke uppercase letters)
|
||||
• Bugfix: Skip shift when getting the modifier mask (Thanks SardemFF7)
|
||||
|
||||
2012-04-01 i3lock 2.4
|
||||
|
||||
• Bugfix: Fix background color when using cairo (Thanks Pascal)
|
||||
• Only output text when in debug mode (fixes problems with xautolock)
|
||||
• fallback when the image cannot be loaded
|
||||
• Use (void) instead of () for functions without args (Thanks fernandotcl)
|
||||
|
||||
2012-03-15 i3lock 2.3.1
|
||||
|
||||
• Fix compilation on some systems
|
||||
|
||||
2012-03-15 i3lock 2.3
|
||||
|
||||
• Implement a visual unlock indicator
|
||||
• Support ISO_Level5_Shift and Caps Lock
|
||||
• Lock the password buffer in memory, clear it in RAM after verifying
|
||||
• Fork after the window is visible, not before
|
||||
• Bugfix: Copy the color depth from parent (root) window instead of
|
||||
hardcoding a depth of 24
|
||||
|
||||
2011-11-06 i3lock 2.2
|
||||
|
||||
• Don’t re-grab pointer/keyboard on MappingNotify. In some rare situations,
|
||||
this lead to some keypresses "slipping through" to the last focused window.
|
||||
• Correctly handle Mode_switch/ISO_Level3_Shift
|
||||
• Render to a pixmap which is used as background for the window instead of
|
||||
copying contents on every expose event
|
||||
• Handle screen resolution changes while screen is locked
|
||||
• Manpage: document arguments for every option
|
||||
|
||||
2011-05-13 i3lock 2.1
|
||||
|
||||
• Accept return/backspace when the buffer of 512 bytes is full
|
||||
• Handle numpad keys correctly
|
||||
• Handle MappingNotify events
|
||||
• Correctly check for errors when connecting to X11
|
||||
• Add i3lock.pam to not rely on debian’s /etc/pam.d/other anymore
|
||||
• don’t display debug output
|
||||
• add NOLIBCAIRO flag to permit compilation without cairo
|
||||
|
||||
2010-09-05 i3lock 2.0
|
||||
|
||||
• Complete rewrite of i3lock. Now using xcb instead of Xlib.
|
||||
• When a window obscures i3lock, it pushes itself back to the top again.
|
||||
• Display version when starting with -v
|
||||
|
||||
2009-08-02 i3lock 1.1
|
||||
|
||||
• Implement background pictures (-i) and colors (-c)
|
||||
|
||||
2009-05-10 i3lock 1.0
|
||||
|
||||
• Implement PAM support
|
||||
• Implement options for forking, beeping, DPMS
|
||||
|
||||
2009-05-01 i3lock 0.9
|
||||
|
||||
• First release, forked from slock 0.9
|
||||
@@ -0,0 +1,26 @@
|
||||
Copyright © 2010, Michael Stapelberg and contributors
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,91 @@
|
||||
I38Lock - accessible screen locker for I38
|
||||
==========================================
|
||||
I38Lock is an accessible screen locker for I38, based on
|
||||
[i3lock](https://i3wm.org/i3lock/).
|
||||
After starting it, you will see a white screen (you can configure the
|
||||
color/an image). You can return to your screen by entering your password.
|
||||
|
||||
It retains i3lock's behavior while adding blind-accessible password feedback:
|
||||
|
||||
- I38Lock forks, so you can combine it with an alias to suspend to RAM
|
||||
(run "i38lock && echo mem > /sys/power/state" to get a locked screen
|
||||
after waking up your computer from suspend to RAM)
|
||||
|
||||
- You can specify either a background color or a PNG image which will be
|
||||
displayed while your screen is locked. Note that I38Lock is not an image
|
||||
manipulation software. If you need to resize the image to fill the screen
|
||||
or similar, use existing tooling to do this before passing it to I38Lock.
|
||||
|
||||
- You can specify whether I38Lock should bell upon a wrong password.
|
||||
|
||||
- When Cthulhu or Orca is running, I38Lock sends password-entry feedback to the
|
||||
screen reader over its D-Bus remote-controller interface. Empty passwords are
|
||||
rejected locally without invoking PAM.
|
||||
|
||||
- I38Lock uses PAM and therefore is compatible with LDAP etc.
|
||||
On OpenBSD I38Lock uses the bsd_auth(3) framework.
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
See [the upstream i3lock home page](https://i3wm.org/i3lock/).
|
||||
|
||||
Requirements
|
||||
------------
|
||||
- pkg-config
|
||||
- libxcb
|
||||
- libxcb-util
|
||||
- libpam-dev
|
||||
- libcairo-dev
|
||||
- libglib2.0-dev
|
||||
- libxcb-xinerama
|
||||
- libxcb-randr
|
||||
- libev
|
||||
- libx11-dev
|
||||
- libx11-xcb-dev
|
||||
- libxkbcommon >= 0.5.0
|
||||
- libxkbcommon-x11 >= 0.5.0
|
||||
- libxcb-image
|
||||
- libxcb-xrm
|
||||
|
||||
Running I38Lock
|
||||
---------------
|
||||
|
||||
To test I38Lock, you can directly run the `i38lock` command. To get out of it,
|
||||
enter your password and press enter.
|
||||
|
||||
For a more permanent setup, we strongly recommend using `xss-lock` so that the
|
||||
screen is locked *before* your laptop suspends:
|
||||
|
||||
```
|
||||
xss-lock --transfer-sleep-lock -- i38lock --nofork
|
||||
```
|
||||
|
||||
On OpenBSD the `i38lock` binary needs to be setgid `auth` to call the
|
||||
authentication helpers, e.g. `/usr/libexec/auth/login_passwd`.
|
||||
|
||||
Building I38Lock
|
||||
----------------
|
||||
We recommend you use the provided package from your distribution. Do not build
|
||||
I38Lock unless you have a reason to do so.
|
||||
|
||||
First install the dependencies listed in requirements section, then run these
|
||||
commands (might need to be adapted to your OS):
|
||||
```
|
||||
rm -rf build/
|
||||
mkdir -p build && cd build/
|
||||
|
||||
meson setup -Dprefix=/usr
|
||||
ninja
|
||||
```
|
||||
|
||||
Before testing a locally built binary on Linux, install the PAM policy:
|
||||
```
|
||||
sudo install -Dm644 ../pam/i38lock /etc/pam.d/i38lock
|
||||
```
|
||||
Without this file, I38Lock refuses to lock rather than risk creating a session
|
||||
that cannot be unlocked.
|
||||
|
||||
Upstream
|
||||
--------
|
||||
I38Lock is based on https://github.com/i3/i3lock.
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* See LICENSE for licensing information
|
||||
*
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <gio/gio.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "accessibility_feedback.h"
|
||||
|
||||
static int feedback_fd = -1;
|
||||
|
||||
struct screen_reader {
|
||||
const char *service;
|
||||
const char *path;
|
||||
const char *interface;
|
||||
};
|
||||
|
||||
static const struct screen_reader screen_readers[] = {
|
||||
{
|
||||
"org.stormux.Cthulhu1.Service",
|
||||
"/org/stormux/Cthulhu1/Service",
|
||||
"org.stormux.Cthulhu1.Service",
|
||||
},
|
||||
{
|
||||
"org.gnome.Orca1.Service",
|
||||
"/org/gnome/Orca1/Service",
|
||||
"org.gnome.Orca1.Service",
|
||||
},
|
||||
};
|
||||
|
||||
static const char *feedback_message(accessibility_feedback_t feedback) {
|
||||
switch (feedback) {
|
||||
case ACCESSIBILITY_SCREEN_LOCKED:
|
||||
return "Screen locked. Enter password.";
|
||||
case ACCESSIBILITY_ENTER_PASSWORD:
|
||||
return "Enter password.";
|
||||
case ACCESSIBILITY_CHARACTER_TYPED:
|
||||
return "star";
|
||||
case ACCESSIBILITY_CHARACTER_ERASED:
|
||||
return "backspace star";
|
||||
case ACCESSIBILITY_NOTHING_TO_ERASE:
|
||||
return "backspace blank";
|
||||
case ACCESSIBILITY_PASSWORD_CLEARED:
|
||||
return "Password cleared.";
|
||||
case ACCESSIBILITY_VERIFYING:
|
||||
return "Verifying.";
|
||||
case ACCESSIBILITY_AUTHENTICATED:
|
||||
return "Authenticated.";
|
||||
case ACCESSIBILITY_INCORRECT_PASSWORD:
|
||||
return "Incorrect password. Enter password.";
|
||||
case ACCESSIBILITY_CAPS_LOCK_ON:
|
||||
return "Caps lock on.";
|
||||
case ACCESSIBILITY_CAPS_LOCK_OFF:
|
||||
return "Caps lock off.";
|
||||
case ACCESSIBILITY_NUM_LOCK_ON:
|
||||
return "Num lock on.";
|
||||
case ACCESSIBILITY_NUM_LOCK_OFF:
|
||||
return "Num lock off.";
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool service_has_owner(GDBusConnection *connection, const char *service) {
|
||||
GError *error = NULL;
|
||||
GVariant *result = g_dbus_connection_call_sync(
|
||||
connection,
|
||||
"org.freedesktop.DBus",
|
||||
"/org/freedesktop/DBus",
|
||||
"org.freedesktop.DBus",
|
||||
"NameHasOwner",
|
||||
g_variant_new("(s)", service),
|
||||
G_VARIANT_TYPE("(b)"),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
250,
|
||||
NULL,
|
||||
&error);
|
||||
|
||||
if (result == NULL) {
|
||||
g_clear_error(&error);
|
||||
return false;
|
||||
}
|
||||
|
||||
gboolean has_owner = false;
|
||||
g_variant_get(result, "(b)", &has_owner);
|
||||
g_variant_unref(result);
|
||||
return has_owner;
|
||||
}
|
||||
|
||||
static void present_message(GDBusConnection *connection, const char *message) {
|
||||
for (size_t i = 0; i < sizeof(screen_readers) / sizeof(screen_readers[0]); i++) {
|
||||
const struct screen_reader *reader = &screen_readers[i];
|
||||
if (!service_has_owner(connection, reader->service)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GError *error = NULL;
|
||||
GVariant *result = g_dbus_connection_call_sync(
|
||||
connection,
|
||||
reader->service,
|
||||
reader->path,
|
||||
reader->interface,
|
||||
"PresentMessage",
|
||||
g_variant_new("(s)", message),
|
||||
G_VARIANT_TYPE("(b)"),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
500,
|
||||
NULL,
|
||||
&error);
|
||||
|
||||
if (result != NULL) {
|
||||
g_variant_unref(result);
|
||||
}
|
||||
g_clear_error(&error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void flush_stars(GDBusConnection *connection, size_t count) {
|
||||
char message[640];
|
||||
size_t position = 0;
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const char *separator = (i == 0 ? "" : " ");
|
||||
position += snprintf(message + position, sizeof(message) - position, "%sstar", separator);
|
||||
}
|
||||
|
||||
present_message(connection, message);
|
||||
}
|
||||
|
||||
static void close_sleep_lock_fd(void) {
|
||||
const char *sleep_lock_fd = getenv("XSS_SLEEP_LOCK_FD");
|
||||
if (sleep_lock_fd == NULL || *sleep_lock_fd == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
char *endptr;
|
||||
long int fd = strtol(sleep_lock_fd, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
static void feedback_worker(int read_fd, int inherited_xcb_fd) {
|
||||
if (inherited_xcb_fd >= 0) {
|
||||
close(inherited_xcb_fd);
|
||||
}
|
||||
close_sleep_lock_fd();
|
||||
|
||||
GError *error = NULL;
|
||||
GDBusConnection *connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
|
||||
if (connection == NULL) {
|
||||
g_clear_error(&error);
|
||||
}
|
||||
|
||||
accessibility_feedback_t feedback[128];
|
||||
ssize_t bytes_read;
|
||||
while ((bytes_read = read(read_fd, feedback, sizeof(feedback))) != 0) {
|
||||
if (bytes_read < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
size_t count = (size_t)bytes_read / sizeof(feedback[0]);
|
||||
size_t stars = 0;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (feedback[i] == ACCESSIBILITY_CHARACTER_TYPED) {
|
||||
stars++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (connection != NULL && stars > 0) {
|
||||
flush_stars(connection, stars);
|
||||
}
|
||||
stars = 0;
|
||||
|
||||
const char *message = feedback_message(feedback[i]);
|
||||
if (connection != NULL && message != NULL) {
|
||||
present_message(connection, message);
|
||||
}
|
||||
}
|
||||
|
||||
if (connection != NULL && stars > 0) {
|
||||
flush_stars(connection, stars);
|
||||
}
|
||||
}
|
||||
|
||||
if (connection != NULL) {
|
||||
g_object_unref(connection);
|
||||
}
|
||||
close(read_fd);
|
||||
}
|
||||
|
||||
void accessibility_feedback_start(int inherited_xcb_fd) {
|
||||
int pipe_fds[2];
|
||||
if (pipe(pipe_fds) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int flags = fcntl(pipe_fds[1], F_GETFL);
|
||||
if (flags == -1 || fcntl(pipe_fds[1], F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||
close(pipe_fds[0]);
|
||||
close(pipe_fds[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
close(pipe_fds[0]);
|
||||
close(pipe_fds[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
close(pipe_fds[1]);
|
||||
feedback_worker(pipe_fds[0], inherited_xcb_fd);
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
close(pipe_fds[0]);
|
||||
feedback_fd = pipe_fds[1];
|
||||
}
|
||||
|
||||
void accessibility_feedback_notify(accessibility_feedback_t feedback) {
|
||||
if (feedback_fd == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t result;
|
||||
do {
|
||||
result = write(feedback_fd, &feedback, sizeof(feedback));
|
||||
} while (result == -1 && errno == EINTR);
|
||||
|
||||
if (result == -1 && errno == EPIPE) {
|
||||
close(feedback_fd);
|
||||
feedback_fd = -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# vim:ft=Dockerfile
|
||||
FROM debian:sid
|
||||
|
||||
RUN echo force-unsafe-io > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup
|
||||
# Paper over occasional network flakiness of some mirrors.
|
||||
RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
|
||||
|
||||
# NOTE: I tried exclusively using gce_debian_mirror.storage.googleapis.com
|
||||
# instead of httpredir.debian.org, but the results (Fetched 123 MB in 36s (3357
|
||||
# kB/s)) are not any better than httpredir.debian.org (Fetched 123 MB in 34s
|
||||
# (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
|
||||
|
||||
# Install mk-build-deps (for installing the i3 build dependencies),
|
||||
# clang (for building),
|
||||
# lintian (for checking spelling errors),
|
||||
# test suite dependencies (for running tests)
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
build-essential clang git meson libxcb-randr0-dev pkg-config libpam0g-dev \
|
||||
libcairo2-dev libxcb1-dev libxcb-dpms0-dev libxcb-image0-dev libxcb-util0-dev \
|
||||
libxcb-xrm-dev libev-dev libxcb-xinerama0-dev libxcb-xkb-dev libxkbcommon-dev \
|
||||
libxkbcommon-x11-dev libglib2.0-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /usr/src
|
||||
@@ -0,0 +1,5 @@
|
||||
/i38lock/
|
||||
/pkg/
|
||||
/src/
|
||||
/*.pkg.tar.*
|
||||
/*.src.tar.*
|
||||
@@ -0,0 +1,4 @@
|
||||
[i38lock]
|
||||
source = "git"
|
||||
git = "https://git.stormux.org/storm/i38lock.git"
|
||||
use_max_tag = true
|
||||
@@ -0,0 +1,12 @@
|
||||
Copyright Arch Linux Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for
|
||||
any purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
|
||||
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
|
||||
FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
|
||||
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
@@ -0,0 +1 @@
|
||||
../LICENSE
|
||||
@@ -0,0 +1,46 @@
|
||||
# Maintainer: Storm Dragon <storm_dragon@stormux.org>
|
||||
|
||||
pkgname=i38lock
|
||||
pkgver=2026.06.02
|
||||
pkgrel=1
|
||||
pkgdesc='X11 screen locker for I38'
|
||||
url='https://git.stormux.org/storm/i38lock'
|
||||
arch=('aarch64' 'x86_64')
|
||||
license=('BSD-3-Clause')
|
||||
depends=(
|
||||
'cairo' 'libcairo.so'
|
||||
'glib2'
|
||||
'glibc'
|
||||
'libev' 'libev.so'
|
||||
'libxcb'
|
||||
'libxkbcommon' 'libxkbcommon.so'
|
||||
'libxkbcommon-x11' 'libxkbcommon-x11.so'
|
||||
'pam' 'libpam.so'
|
||||
'xcb-util'
|
||||
'xcb-util-image'
|
||||
'xcb-util-xrm'
|
||||
)
|
||||
makedepends=(
|
||||
'git'
|
||||
'meson'
|
||||
)
|
||||
options=('docs')
|
||||
backup=('etc/pam.d/i38lock')
|
||||
source=("$pkgname::git+$url.git#tag=$pkgver")
|
||||
sha512sums=('SKIP')
|
||||
|
||||
build() {
|
||||
arch-meson "$pkgname" build
|
||||
meson compile -C build
|
||||
}
|
||||
|
||||
package() {
|
||||
# Fix ticket FS#31544, sed line taken from gentoo
|
||||
sed -i -e 's:login:system-auth:g' "$pkgname/pam/i38lock"
|
||||
|
||||
DESTDIR="$pkgdir" meson install -C build --no-rebuild
|
||||
|
||||
install -Dm644 "$pkgname/LICENSE" -t "$pkgdir/usr/share/licenses/$pkgname"
|
||||
}
|
||||
|
||||
# vim: ts=2 sw=2 et:
|
||||
@@ -0,0 +1,22 @@
|
||||
version = 1
|
||||
|
||||
[[annotations]]
|
||||
path = [
|
||||
"PKGBUILD",
|
||||
"README.md",
|
||||
"keys/**",
|
||||
".SRCINFO",
|
||||
".nvchecker.toml",
|
||||
"*.install",
|
||||
"*.sysusers",
|
||||
"*.tmpfiles",
|
||||
"*.logrotate",
|
||||
"*.pam",
|
||||
"*.service",
|
||||
"*.socket",
|
||||
"*.timer",
|
||||
"*.desktop",
|
||||
"*.hook",
|
||||
]
|
||||
SPDX-FileCopyrightText = "Arch Linux contributors"
|
||||
SPDX-License-Identifier = "0BSD"
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
*/
|
||||
#include "dpi.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <xcb/xcb_xrm.h>
|
||||
#include "xcb.h"
|
||||
#include "i3lock.h"
|
||||
|
||||
extern bool debug_mode;
|
||||
|
||||
static long dpi;
|
||||
|
||||
extern xcb_screen_t *screen;
|
||||
|
||||
static long init_dpi_fallback(void) {
|
||||
return (double)screen->height_in_pixels * 25.4 / (double)screen->height_in_millimeters;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the DPI setting.
|
||||
* This will use the 'Xft.dpi' X resource if available and fall back to
|
||||
* guessing the correct value otherwise.
|
||||
*/
|
||||
void init_dpi(void) {
|
||||
xcb_xrm_database_t *database = NULL;
|
||||
char *resource = NULL;
|
||||
|
||||
if (conn == NULL) {
|
||||
goto init_dpi_end;
|
||||
}
|
||||
|
||||
database = xcb_xrm_database_from_default(conn);
|
||||
if (database == NULL) {
|
||||
DEBUG("Failed to open the resource database.\n");
|
||||
goto init_dpi_end;
|
||||
}
|
||||
|
||||
xcb_xrm_resource_get_string(database, "Xft.dpi", NULL, &resource);
|
||||
if (resource == NULL) {
|
||||
DEBUG("Resource Xft.dpi not specified, skipping.\n");
|
||||
goto init_dpi_end;
|
||||
}
|
||||
|
||||
char *endptr;
|
||||
double in_dpi = strtod(resource, &endptr);
|
||||
if (in_dpi == HUGE_VAL || in_dpi < 0 || *endptr != '\0' || endptr == resource) {
|
||||
DEBUG("Xft.dpi = %s is an invalid number and couldn't be parsed.\n", resource);
|
||||
dpi = 0;
|
||||
goto init_dpi_end;
|
||||
}
|
||||
dpi = (long)round(in_dpi);
|
||||
|
||||
DEBUG("Found Xft.dpi = %ld.\n", dpi);
|
||||
|
||||
init_dpi_end:
|
||||
if (resource != NULL) {
|
||||
free(resource);
|
||||
}
|
||||
|
||||
if (database != NULL) {
|
||||
xcb_xrm_database_free(database);
|
||||
}
|
||||
|
||||
if (dpi == 0) {
|
||||
DEBUG("Using fallback for calculating DPI.\n");
|
||||
dpi = init_dpi_fallback();
|
||||
DEBUG("Using dpi = %ld\n", dpi);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function returns the value of the DPI setting.
|
||||
*
|
||||
*/
|
||||
long get_dpi_value(void) {
|
||||
return dpi;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI
|
||||
* screen) to a corresponding amount of physical pixels on a standard or retina
|
||||
* screen, e.g. 5 pixels on a 227 DPI MacBook Pro 13" Retina screen.
|
||||
*
|
||||
*/
|
||||
int logical_px(const int logical) {
|
||||
if (screen == NULL) {
|
||||
/* Dpi info may not be available when parsing a config without an X
|
||||
* server, such as for config file validation. */
|
||||
return logical;
|
||||
}
|
||||
|
||||
/* There are many misconfigurations out there, i.e. systems with screens
|
||||
* whose dpi is in fact higher than 96 dpi, but not significantly higher,
|
||||
* so software was never adapted. We could tell people to reconfigure their
|
||||
* systems to 96 dpi in order to get the behavior they expect/are used to,
|
||||
* but since we can easily detect this case in code, let’s do it for them.
|
||||
*/
|
||||
if ((dpi / 96.0) < 1.25) {
|
||||
return logical;
|
||||
}
|
||||
return ceil((dpi / 96.0) * logical);
|
||||
}
|
||||
Generated
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1758791193,
|
||||
"narHash": "sha256-F8WmEwFoHsnix7rt290R0rFXNJiMbClMZyIC/e+HYf0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "25e53aa156d47bad5082ff7618f5feb1f5e02d01",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-25.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
description = "development environment for I38Lock";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in {
|
||||
devShells.${system}.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
cairo
|
||||
glib
|
||||
libev
|
||||
libxkbcommon
|
||||
pam
|
||||
pkg-config
|
||||
xcbutilxrm
|
||||
xorg.libX11
|
||||
xorg.libxcb
|
||||
xorg.xcbutil
|
||||
xorg.xcbutilimage
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
.de Vb \" Begin verbatim text
|
||||
.ft CW
|
||||
.nf
|
||||
.ne \\$1
|
||||
..
|
||||
.de Ve \" End verbatim text
|
||||
.ft R
|
||||
.fi
|
||||
..
|
||||
|
||||
.TH i38lock 1 "JUNE 2026" Linux "User Manuals"
|
||||
|
||||
.SH NAME
|
||||
I38Lock \- accessible screen locker for I38
|
||||
|
||||
.SH SYNOPSIS
|
||||
.B i38lock
|
||||
.RB [\|\-v\|]
|
||||
.RB [\|\-n\|]
|
||||
.RB [\|\-b\|]
|
||||
.RB [\|\-i
|
||||
.IR image.png \|]
|
||||
.RB [\|\-c
|
||||
.IR color \|]
|
||||
.RB [\|\-t\|]
|
||||
.RB [\|\-p
|
||||
.IR pointer\|]
|
||||
.RB [\|\-u\|]
|
||||
.RB [\|\-f\|]
|
||||
|
||||
.SH RECOMMENDED USAGE
|
||||
.RB xss-lock
|
||||
.RB --transfer-sleep-lock
|
||||
.RB --
|
||||
.RB i38lock
|
||||
.RB --nofork
|
||||
|
||||
Using
|
||||
.B xss-lock
|
||||
ensures that your screen is locked before your laptop suspends.
|
||||
|
||||
Notably, using a systemd service file is not adequate, as it will not delay
|
||||
suspend until your screen is locked.
|
||||
|
||||
.SH DESCRIPTION
|
||||
.B I38Lock
|
||||
is an accessible screen locker based on i3lock. After starting it, you will see a white
|
||||
screen (you can configure the color/an image). You can return to your screen by
|
||||
entering your password.
|
||||
|
||||
.SH IMPROVEMENTS
|
||||
|
||||
.IP \[bu] 2
|
||||
I38Lock forks, so you can combine it with an alias to suspend to RAM (run "i38lock && echo mem > /sys/power/state" to get a locked screen after waking up your computer from suspend to RAM)
|
||||
.IP \[bu]
|
||||
You can specify either a background color or a PNG image which will be displayed while your screen is locked.
|
||||
.IP \[bu]
|
||||
You can specify whether I38Lock should bell upon a wrong password.
|
||||
.IP \[bu]
|
||||
I38Lock uses PAM and therefore is compatible with LDAP, etc.
|
||||
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B \-v, \-\-version
|
||||
Display the version of your
|
||||
.B i38lock
|
||||
|
||||
.TP
|
||||
.B \-n, \-\-nofork
|
||||
Don't fork after starting.
|
||||
|
||||
.TP
|
||||
.B \-b, \-\-beep
|
||||
Enable beeping. Be sure to not do this when you are about to annoy other people,
|
||||
like when opening your laptop in a boring lecture.
|
||||
|
||||
.TP
|
||||
.B \-u, \-\-no-unlock-indicator
|
||||
Disable the unlock indicator. I38Lock will by default show an unlock indicator
|
||||
after pressing keys. This will give feedback for every keypress and it will
|
||||
show you the current PAM state (whether your password is currently being
|
||||
verified or whether it is wrong).
|
||||
|
||||
.TP
|
||||
.BI \-i\ path \fR,\ \fB\-\-image= path
|
||||
Display the given PNG image instead of a blank screen.
|
||||
|
||||
.TP
|
||||
.BI \fB\-\-raw= format
|
||||
Read the image given by \-\-image as a raw image instead of PNG. The argument is the image's format
|
||||
as <width>x<height>:<pixfmt>. The supported pixel formats are:
|
||||
\(aqnative', \(aqrgb', \(aqxrgb', \(aqrgbx', \(aqbgr', \(aqxbgr', and \(aqbgrx'.
|
||||
The "native" pixel format expects a pixel as a 32-bit (4-byte) integer in
|
||||
the machine's native endianness, with the upper 8 bits unused. Red, green and blue are stored in
|
||||
the remaining bits, in that order.
|
||||
|
||||
.BR Example:
|
||||
.Vb 6
|
||||
\& --raw=1920x1080:rgb
|
||||
.Ve
|
||||
|
||||
.BR
|
||||
You can use ImageMagick’s
|
||||
.IR convert(1)
|
||||
program to feed raw images into I38Lock:
|
||||
|
||||
.BR
|
||||
.Vb 6
|
||||
\& convert wallpaper.jpg RGB:- | i38lock --raw 3840x2160:rgb --image /dev/stdin
|
||||
.Ve
|
||||
|
||||
This allows you to load a variety of image formats without I38Lock having to
|
||||
support each one explicitly.
|
||||
|
||||
.TP
|
||||
.BI \-c\ rrggbb \fR,\ \fB\-\-color= rrggbb
|
||||
Turn the screen into the given color instead of white. Color must be given in 3-byte
|
||||
format: rrggbb (i.e. ff0000 is red).
|
||||
|
||||
.TP
|
||||
.B \-t, \-\-tiling
|
||||
If an image is specified (via \-i) it will display the image tiled all over the screen
|
||||
(if it is a multi-monitor setup, the image is visible on all screens).
|
||||
|
||||
.TP
|
||||
.BI \-p\ win|default \fR,\ \fB\-\-pointer= win|default
|
||||
If you specify "default",
|
||||
.B I38Lock
|
||||
does not hide your mouse pointer. If you specify "win",
|
||||
.B I38Lock
|
||||
displays a hardcoded Windows-Pointer (thus enabling you to mess with your
|
||||
friends by using a screenshot of a Windows desktop as a locking-screen).
|
||||
|
||||
.TP
|
||||
.B \-f, \-\-show-failed-attempts
|
||||
Show the number of failed attempts, if any.
|
||||
|
||||
.TP
|
||||
.B \-k, \-\-show-keyboard-layout
|
||||
Show the current keyboard layout.
|
||||
|
||||
.TP
|
||||
.B \-\-debug
|
||||
Enables debug logging.
|
||||
Note, that this will log the password used for authentication to stdout.
|
||||
|
||||
.SH ACCESSIBILITY
|
||||
When Cthulhu or Orca is running, I38Lock automatically sends password-entry
|
||||
feedback to the screen reader over its D-Bus remote-controller interface.
|
||||
Feedback includes lock state, accepted characters as the word "star",
|
||||
backspace results, modifier-lock changes, verification, and authentication
|
||||
failure. Password characters are never sent. Empty passwords are rejected
|
||||
locally without invoking PAM.
|
||||
|
||||
.SH PAM CONFIGURATION
|
||||
On Linux,
|
||||
.B /etc/pam.d/i38lock
|
||||
must exist and be readable before I38Lock will lock the screen. This prevents
|
||||
an absent service policy from creating a session that cannot be unlocked.
|
||||
|
||||
.SH DPMS
|
||||
|
||||
The \-d (\-\-dpms) option was removed from i3lock in version 2.8. There were
|
||||
plenty of use-cases that were not properly addressed, and plenty of bugs
|
||||
surrounding that feature. While features are not normally removed from i3 and
|
||||
its tools, we felt the need to make an exception in this case.
|
||||
|
||||
Users who wish to explicitly enable DPMS only when their screen is locked can
|
||||
use a wrapper script around I38Lock like the following:
|
||||
|
||||
.Vb 6
|
||||
\& #!/bin/sh
|
||||
\& revert() {
|
||||
\& xset dpms 0 0 0
|
||||
\& }
|
||||
\& trap revert HUP INT TERM
|
||||
\& xset +dpms dpms 5 5 5
|
||||
\& i38lock -n
|
||||
\& revert
|
||||
.Ve
|
||||
|
||||
The \-I (-\-inactivity-timeout=seconds) was removed because it only makes sense with DPMS.
|
||||
|
||||
.SH SEE ALSO
|
||||
.IR xss-lock(1)
|
||||
\- hooks up I38Lock to the systemd login manager
|
||||
|
||||
.IR convert(1)
|
||||
\- feed a wide variety of image formats into I38Lock
|
||||
|
||||
.SH AUTHOR
|
||||
Michael Stapelberg <michael+i3lock at stapelberg dot de>
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef _ACCESSIBILITY_FEEDBACK_H
|
||||
#define _ACCESSIBILITY_FEEDBACK_H
|
||||
|
||||
typedef enum {
|
||||
ACCESSIBILITY_SCREEN_LOCKED = 0,
|
||||
ACCESSIBILITY_ENTER_PASSWORD,
|
||||
ACCESSIBILITY_CHARACTER_TYPED,
|
||||
ACCESSIBILITY_CHARACTER_ERASED,
|
||||
ACCESSIBILITY_NOTHING_TO_ERASE,
|
||||
ACCESSIBILITY_PASSWORD_CLEARED,
|
||||
ACCESSIBILITY_VERIFYING,
|
||||
ACCESSIBILITY_AUTHENTICATED,
|
||||
ACCESSIBILITY_INCORRECT_PASSWORD,
|
||||
ACCESSIBILITY_CAPS_LOCK_ON,
|
||||
ACCESSIBILITY_CAPS_LOCK_OFF,
|
||||
ACCESSIBILITY_NUM_LOCK_ON,
|
||||
ACCESSIBILITY_NUM_LOCK_OFF,
|
||||
} accessibility_feedback_t;
|
||||
|
||||
void accessibility_feedback_start(int inherited_xcb_fd);
|
||||
void accessibility_feedback_notify(accessibility_feedback_t feedback);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef _CURSORS_H
|
||||
#define _CURSORS_H
|
||||
|
||||
#define CURS_NONE 0
|
||||
#define CURS_WIN 1
|
||||
#define CURS_DEFAULT 2
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* Initialize the DPI setting.
|
||||
* This will use the 'Xft.dpi' X resource if available and fall back to
|
||||
* guessing the correct value otherwise.
|
||||
*/
|
||||
void init_dpi(void);
|
||||
|
||||
/**
|
||||
* This function returns the value of the DPI setting.
|
||||
*
|
||||
*/
|
||||
long get_dpi_value(void);
|
||||
|
||||
/**
|
||||
* Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI
|
||||
* screen) to a corresponding amount of physical pixels on a standard or retina
|
||||
* screen, e.g. 5 pixels on a 227 DPI MacBook Pro 13" Retina screen.
|
||||
*
|
||||
*/
|
||||
int logical_px(const int logical);
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef _I3LOCK_H
|
||||
#define _I3LOCK_H
|
||||
|
||||
/* This macro will only print debug output when started with --debug.
|
||||
* This is important because xautolock (for example) closes stdout/stderr by
|
||||
* default, so just printing something to stdout will lead to the data ending
|
||||
* up on the X11 socket (!). */
|
||||
#define DEBUG(fmt, ...) \
|
||||
do { \
|
||||
if (debug_mode) { \
|
||||
fprintf(stderr, "[i38lock-debug] " fmt, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef _XINERAMA_H
|
||||
#define _XINERAMA_H
|
||||
|
||||
typedef struct Rect {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
} Rect;
|
||||
|
||||
extern int xr_screens;
|
||||
extern Rect *xr_resolutions;
|
||||
|
||||
void randr_init(int *event_base, xcb_window_t root);
|
||||
void randr_query(xcb_window_t root);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef _UNLOCK_INDICATOR_H
|
||||
#define _UNLOCK_INDICATOR_H
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
typedef enum {
|
||||
STATE_STARTED = 0, /* default state */
|
||||
STATE_KEY_PRESSED = 1, /* key was pressed, show unlock indicator */
|
||||
STATE_KEY_ACTIVE = 2, /* a key was pressed recently, highlight part
|
||||
of the unlock indicator. */
|
||||
STATE_BACKSPACE_ACTIVE = 3, /* backspace was pressed recently, highlight
|
||||
part of the unlock indicator in red. */
|
||||
STATE_NOTHING_TO_DELETE = 4, /* backspace was pressed, but there is nothing to delete. */
|
||||
} unlock_state_t;
|
||||
|
||||
typedef enum {
|
||||
STATE_AUTH_IDLE = 0, /* no authenticator interaction at the moment */
|
||||
STATE_AUTH_VERIFY = 1, /* currently verifying the password via authenticator */
|
||||
STATE_AUTH_LOCK = 2, /* currently locking the screen */
|
||||
STATE_AUTH_WRONG = 3, /* the password was wrong */
|
||||
STATE_I38LOCK_LOCK_FAILED = 4, /* I38Lock failed to load */
|
||||
} auth_state_t;
|
||||
|
||||
void free_bg_pixmap(void);
|
||||
void draw_image(xcb_pixmap_t bg_pixmap, uint32_t *resolution);
|
||||
void redraw_screen(void);
|
||||
void clear_indicator(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef _XCB_H
|
||||
#define _XCB_H
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
extern xcb_connection_t *conn;
|
||||
extern xcb_screen_t *screen;
|
||||
|
||||
xcb_visualtype_t *get_root_visual_type(xcb_screen_t *s);
|
||||
xcb_pixmap_t create_bg_pixmap(xcb_connection_t *conn, xcb_screen_t *scr, u_int32_t *resolution, char *color);
|
||||
xcb_window_t open_fullscreen_window(xcb_connection_t *conn, xcb_screen_t *scr, char *color, xcb_pixmap_t pixmap);
|
||||
bool grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_t cursor, int tries);
|
||||
xcb_cursor_t create_cursor(xcb_connection_t *conn, xcb_screen_t *screen, xcb_window_t win, int choice);
|
||||
xcb_window_t find_focused_window(xcb_connection_t *conn, const xcb_window_t root);
|
||||
void set_focused_window(xcb_connection_t *conn, const xcb_window_t root, const xcb_window_t window);
|
||||
|
||||
#endif
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
# -*- mode: meson -*-
|
||||
|
||||
# Style objective: be consistent with what mesonbuild.com documents/uses, and/or
|
||||
# the meson book: https://meson-manual.com/
|
||||
|
||||
project(
|
||||
'i38lock',
|
||||
'c',
|
||||
version: '2026.06.02',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
'sysconfdir=/etc',
|
||||
'warning_level=1', # enable all warnings (-Wall)
|
||||
# TODO(https://github.com/i3/i3/issues/4087): switch to
|
||||
# 'buildtype=debugoptimized',
|
||||
],
|
||||
# Ubuntu 18.04 (supported until 2023) has meson 0.45.
|
||||
# We can revisit our minimum supported meson version
|
||||
# if it turns out to be too hard to maintain.
|
||||
meson_version: '>=0.45.0',
|
||||
)
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
add_project_arguments(cc.get_supported_arguments(['-Wunused-value']), language: 'c')
|
||||
|
||||
if meson.version().version_compare('>=0.48.0')
|
||||
# https://github.com/mesonbuild/meson/issues/2166#issuecomment-629696911
|
||||
meson.add_dist_script('meson/meson-dist-script')
|
||||
else
|
||||
message('meson <0.48.0 detected, dist tarballs will not be filtered')
|
||||
endif
|
||||
|
||||
################################################################################
|
||||
# Version handling
|
||||
################################################################################
|
||||
|
||||
cdata = configuration_data()
|
||||
|
||||
version_array = meson.project_version().split('.')
|
||||
cdata.set_quoted('I38LOCK_VERSION', '@VCS_TAG@')
|
||||
cdata.set_quoted('SYSCONFDIR', join_paths(get_option('prefix'), get_option('sysconfdir')))
|
||||
|
||||
if get_option('b_sanitize').split(',').contains('address')
|
||||
cdata.set('I38LOCK_ASAN_ENABLED', 1)
|
||||
endif
|
||||
|
||||
cdata.set('HAVE_STRNDUP', cc.has_function('strndup'))
|
||||
cdata.set('HAVE_MKDIRP', cc.has_function('mkdirp'))
|
||||
cdata.set('HAVE_EXPLICIT_BZERO', cc.has_function('explicit_bzero'))
|
||||
|
||||
# Instead of generating config.h directly, make vcs_tag generate it so that
|
||||
# @VCS_TAG@ is replaced.
|
||||
config_h_in = configure_file(
|
||||
output: 'config.h.in',
|
||||
configuration: cdata,
|
||||
)
|
||||
config_h = declare_dependency(
|
||||
sources: vcs_tag(
|
||||
input: config_h_in,
|
||||
output: 'config.h',
|
||||
command: ['git', 'describe', '--tags', '--match', '20*', '--always', '--dirty=+'],
|
||||
fallback: meson.project_version() + '-non-git',
|
||||
)
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# manpages
|
||||
################################################################################
|
||||
|
||||
install_man('i38lock.1')
|
||||
|
||||
# Required for e.g. struct ucred to be defined as per unix(7).
|
||||
add_project_arguments('-D_GNU_SOURCE', language: 'c')
|
||||
|
||||
# https://mesonbuild.com/howtox.html#add-math-library-lm-portably
|
||||
m_dep = cc.find_library('m', required: false)
|
||||
rt_dep = cc.find_library('rt', required: false)
|
||||
|
||||
xcb_dep = dependency('xcb', method: 'pkg-config')
|
||||
xcb_xkb_dep = dependency('xcb-xkb', method: 'pkg-config')
|
||||
xcb_xinerama_dep = dependency('xcb-xinerama', method: 'pkg-config')
|
||||
xcb_randr_dep = dependency('xcb-randr', method: 'pkg-config')
|
||||
xcb_image_dep = dependency('xcb-image', method: 'pkg-config')
|
||||
xcb_util_dep = dependency('xcb-util', method: 'pkg-config')
|
||||
xcb_util_xrm_dep = dependency('xcb-xrm', method: 'pkg-config')
|
||||
xkbcommon_dep = dependency('xkbcommon', method: 'pkg-config')
|
||||
xkbcommon_x11_dep = dependency('xkbcommon-x11', method: 'pkg-config')
|
||||
cairo_dep = dependency('cairo', version: '>=1.14.4', method: 'pkg-config')
|
||||
gio_dep = dependency('gio-2.0', method: 'pkg-config')
|
||||
|
||||
i38lock_srcs = [
|
||||
'accessibility_feedback.c',
|
||||
'dpi.c',
|
||||
'i3lock.c',
|
||||
'randr.c',
|
||||
'unlock_indicator.c',
|
||||
'xcb.c',
|
||||
]
|
||||
|
||||
ev_dep = cc.find_library('ev')
|
||||
|
||||
thread_dep = dependency('threads')
|
||||
|
||||
i38lock_deps = [
|
||||
thread_dep,
|
||||
m_dep,
|
||||
rt_dep,
|
||||
ev_dep,
|
||||
config_h,
|
||||
cairo_dep,
|
||||
gio_dep,
|
||||
xcb_dep,
|
||||
xcb_xkb_dep,
|
||||
xcb_xinerama_dep,
|
||||
xcb_randr_dep,
|
||||
xcb_image_dep,
|
||||
xcb_util_dep,
|
||||
xcb_util_xrm_dep,
|
||||
xkbcommon_dep,
|
||||
xkbcommon_x11_dep,
|
||||
]
|
||||
|
||||
host_os = host_machine.system()
|
||||
if host_os != 'openbsd'
|
||||
pam_dep = cc.find_library('pam', required: true)
|
||||
i38lock_deps += [pam_dep]
|
||||
endif
|
||||
|
||||
inc = include_directories('include')
|
||||
|
||||
executable(
|
||||
'i38lock',
|
||||
i38lock_srcs,
|
||||
install: true,
|
||||
include_directories: inc,
|
||||
dependencies: i38lock_deps,
|
||||
)
|
||||
|
||||
install_subdir(
|
||||
'pam',
|
||||
strip_directory: true,
|
||||
install_dir: join_paths(get_option('sysconfdir'), 'pam.d'),
|
||||
)
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
cd "${MESON_DIST_ROOT}"
|
||||
|
||||
# Delete everything we do not want to have in the release tarballs:
|
||||
rm -rf \
|
||||
.clang-format \
|
||||
.editorconfig \
|
||||
ci \
|
||||
.github
|
||||
@@ -0,0 +1 @@
|
||||
# -*- mode: meson -*-
|
||||
@@ -0,0 +1,6 @@
|
||||
#
|
||||
# PAM configuration file for the I38Lock screen locker. By default, it includes
|
||||
# the 'login' configuration file (see /etc/pam.d/login)
|
||||
#
|
||||
|
||||
auth include login
|
||||
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* © 2010 Michael Stapelberg
|
||||
*
|
||||
* See LICENSE for licensing information
|
||||
*
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xinerama.h>
|
||||
#include <xcb/randr.h>
|
||||
|
||||
#include "i3lock.h"
|
||||
#include "xcb.h"
|
||||
#include "randr.h"
|
||||
|
||||
/* Number of Xinerama screens which are currently present. */
|
||||
int xr_screens = 0;
|
||||
|
||||
/* The resolutions of the currently present Xinerama screens. */
|
||||
Rect *xr_resolutions = NULL;
|
||||
|
||||
static bool xinerama_active;
|
||||
static bool has_randr = false;
|
||||
static bool has_randr_1_5 = false;
|
||||
extern bool debug_mode;
|
||||
|
||||
void _xinerama_init(void);
|
||||
|
||||
void randr_init(int *event_base, xcb_window_t root) {
|
||||
const xcb_query_extension_reply_t *extreply;
|
||||
|
||||
extreply = xcb_get_extension_data(conn, &xcb_randr_id);
|
||||
if (!extreply->present) {
|
||||
DEBUG("RandR is not present, falling back to Xinerama.\n");
|
||||
_xinerama_init();
|
||||
return;
|
||||
}
|
||||
|
||||
xcb_generic_error_t *err;
|
||||
xcb_randr_query_version_reply_t *randr_version =
|
||||
xcb_randr_query_version_reply(
|
||||
conn, xcb_randr_query_version(conn, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), &err);
|
||||
if (err != NULL) {
|
||||
DEBUG("Could not query RandR version: X11 error code %d\n", err->error_code);
|
||||
_xinerama_init();
|
||||
return;
|
||||
}
|
||||
|
||||
has_randr = true;
|
||||
|
||||
has_randr_1_5 = (randr_version->major_version >= 1) &&
|
||||
(randr_version->minor_version >= 5);
|
||||
|
||||
free(randr_version);
|
||||
|
||||
if (event_base != NULL) {
|
||||
*event_base = extreply->first_event;
|
||||
}
|
||||
|
||||
xcb_randr_select_input(conn, root,
|
||||
XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
|
||||
XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
|
||||
XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
|
||||
XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
void _xinerama_init(void) {
|
||||
if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) {
|
||||
DEBUG("Xinerama extension not found, disabling.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
xcb_xinerama_is_active_cookie_t cookie;
|
||||
xcb_xinerama_is_active_reply_t *reply;
|
||||
|
||||
cookie = xcb_xinerama_is_active(conn);
|
||||
reply = xcb_xinerama_is_active_reply(conn, cookie, NULL);
|
||||
if (!reply) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!reply->state) {
|
||||
free(reply);
|
||||
return;
|
||||
}
|
||||
|
||||
xinerama_active = true;
|
||||
free(reply);
|
||||
}
|
||||
|
||||
/*
|
||||
* randr_query_outputs_15 uses RandR ≥ 1.5 to update outputs.
|
||||
*
|
||||
*/
|
||||
static bool _randr_query_monitors_15(xcb_window_t root) {
|
||||
#if XCB_RANDR_MINOR_VERSION < 5
|
||||
return false;
|
||||
#else
|
||||
/* RandR 1.5 available at compile-time, i.e. libxcb is new enough */
|
||||
if (!has_randr_1_5) {
|
||||
return false;
|
||||
}
|
||||
/* RandR 1.5 available at run-time (supported by the server) */
|
||||
DEBUG("Querying monitors using RandR 1.5\n");
|
||||
xcb_generic_error_t *err;
|
||||
xcb_randr_get_monitors_reply_t *monitors =
|
||||
xcb_randr_get_monitors_reply(
|
||||
conn, xcb_randr_get_monitors(conn, root, true), &err);
|
||||
if (err != NULL) {
|
||||
DEBUG("Could not get RandR monitors: X11 error code %d\n", err->error_code);
|
||||
free(err);
|
||||
/* Fall back to RandR ≤ 1.4 */
|
||||
return false;
|
||||
}
|
||||
|
||||
int screens = xcb_randr_get_monitors_monitors_length(monitors);
|
||||
DEBUG("%d RandR monitors found (timestamp %d)\n",
|
||||
screens, monitors->timestamp);
|
||||
|
||||
Rect *resolutions = malloc(screens * sizeof(Rect));
|
||||
/* No memory? Just keep on using the old information. */
|
||||
if (!resolutions) {
|
||||
free(monitors);
|
||||
return true;
|
||||
}
|
||||
|
||||
xcb_randr_monitor_info_iterator_t iter;
|
||||
int screen;
|
||||
for (iter = xcb_randr_get_monitors_monitors_iterator(monitors), screen = 0;
|
||||
iter.rem;
|
||||
xcb_randr_monitor_info_next(&iter), screen++) {
|
||||
const xcb_randr_monitor_info_t *monitor_info = iter.data;
|
||||
|
||||
resolutions[screen].x = monitor_info->x;
|
||||
resolutions[screen].y = monitor_info->y;
|
||||
resolutions[screen].width = monitor_info->width;
|
||||
resolutions[screen].height = monitor_info->height;
|
||||
DEBUG("found RandR monitor: %d x %d at %d x %d\n",
|
||||
monitor_info->width, monitor_info->height,
|
||||
monitor_info->x, monitor_info->y);
|
||||
}
|
||||
free(xr_resolutions);
|
||||
xr_resolutions = resolutions;
|
||||
xr_screens = screens;
|
||||
|
||||
free(monitors);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* randr_query_outputs_14 uses RandR ≤ 1.4 to update outputs.
|
||||
*
|
||||
*/
|
||||
static bool _randr_query_outputs_14(xcb_window_t root) {
|
||||
if (!has_randr) {
|
||||
return false;
|
||||
}
|
||||
DEBUG("Querying outputs using RandR ≤ 1.4\n");
|
||||
|
||||
/* Get screen resources (primary output, crtcs, outputs, modes) */
|
||||
xcb_randr_get_screen_resources_current_cookie_t rcookie;
|
||||
rcookie = xcb_randr_get_screen_resources_current(conn, root);
|
||||
|
||||
xcb_randr_get_screen_resources_current_reply_t *res =
|
||||
xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL);
|
||||
if (res == NULL) {
|
||||
DEBUG("Could not query screen resources.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* timestamp of the configuration so that we get consistent replies to all
|
||||
* requests (if the configuration changes between our different calls) */
|
||||
const xcb_timestamp_t cts = res->config_timestamp;
|
||||
|
||||
const int len = xcb_randr_get_screen_resources_current_outputs_length(res);
|
||||
|
||||
/* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */
|
||||
xcb_randr_output_t *randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
|
||||
|
||||
/* Request information for each output */
|
||||
xcb_randr_get_output_info_cookie_t ocookie[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts);
|
||||
}
|
||||
Rect *resolutions = malloc(len * sizeof(Rect));
|
||||
/* No memory? Just keep on using the old information. */
|
||||
if (!resolutions) {
|
||||
free(res);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Loop through all outputs available for this X11 screen */
|
||||
int screen = 0;
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
xcb_randr_get_output_info_reply_t *output;
|
||||
|
||||
if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (output->crtc == XCB_NONE) {
|
||||
free(output);
|
||||
continue;
|
||||
}
|
||||
|
||||
xcb_randr_get_crtc_info_cookie_t icookie;
|
||||
xcb_randr_get_crtc_info_reply_t *crtc;
|
||||
icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts);
|
||||
if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
|
||||
DEBUG("Skipping output: could not get CRTC (0x%08x)\n", output->crtc);
|
||||
free(output);
|
||||
continue;
|
||||
}
|
||||
|
||||
resolutions[screen].x = crtc->x;
|
||||
resolutions[screen].y = crtc->y;
|
||||
resolutions[screen].width = crtc->width;
|
||||
resolutions[screen].height = crtc->height;
|
||||
|
||||
DEBUG("found RandR output: %d x %d at %d x %d\n",
|
||||
crtc->width, crtc->height,
|
||||
crtc->x, crtc->y);
|
||||
|
||||
screen++;
|
||||
|
||||
free(crtc);
|
||||
|
||||
free(output);
|
||||
}
|
||||
free(xr_resolutions);
|
||||
xr_resolutions = resolutions;
|
||||
xr_screens = screen;
|
||||
free(res);
|
||||
return true;
|
||||
}
|
||||
|
||||
void _xinerama_query_screens(void) {
|
||||
if (!xinerama_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
xcb_xinerama_query_screens_cookie_t cookie;
|
||||
xcb_xinerama_query_screens_reply_t *reply;
|
||||
xcb_xinerama_screen_info_t *screen_info;
|
||||
xcb_generic_error_t *err;
|
||||
cookie = xcb_xinerama_query_screens_unchecked(conn);
|
||||
reply = xcb_xinerama_query_screens_reply(conn, cookie, &err);
|
||||
if (!reply) {
|
||||
DEBUG("Couldn't get Xinerama screens: X11 error code %d\n", err->error_code);
|
||||
free(err);
|
||||
return;
|
||||
}
|
||||
screen_info = xcb_xinerama_query_screens_screen_info(reply);
|
||||
int screens = xcb_xinerama_query_screens_screen_info_length(reply);
|
||||
|
||||
Rect *resolutions = malloc(screens * sizeof(Rect));
|
||||
/* No memory? Just keep on using the old information. */
|
||||
if (!resolutions) {
|
||||
free(reply);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int screen = 0; screen < xr_screens; screen++) {
|
||||
resolutions[screen].x = screen_info[screen].x_org;
|
||||
resolutions[screen].y = screen_info[screen].y_org;
|
||||
resolutions[screen].width = screen_info[screen].width;
|
||||
resolutions[screen].height = screen_info[screen].height;
|
||||
DEBUG("found Xinerama screen: %d x %d at %d x %d\n",
|
||||
screen_info[screen].width, screen_info[screen].height,
|
||||
screen_info[screen].x_org, screen_info[screen].y_org);
|
||||
}
|
||||
|
||||
free(xr_resolutions);
|
||||
xr_resolutions = resolutions;
|
||||
xr_screens = screens;
|
||||
|
||||
free(reply);
|
||||
}
|
||||
|
||||
void randr_query(xcb_window_t root) {
|
||||
if (_randr_query_monitors_15(root)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_randr_query_outputs_14(root)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_xinerama_query_screens();
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* © 2010 Michael Stapelberg
|
||||
*
|
||||
* See LICENSE for licensing information
|
||||
*
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <ev.h>
|
||||
#include <cairo.h>
|
||||
#include <cairo/cairo-xcb.h>
|
||||
|
||||
#include "i3lock.h"
|
||||
#include "xcb.h"
|
||||
#include "unlock_indicator.h"
|
||||
#include "randr.h"
|
||||
#include "dpi.h"
|
||||
|
||||
#define BUTTON_RADIUS 90
|
||||
#define BUTTON_SPACE (BUTTON_RADIUS + 5)
|
||||
#define BUTTON_CENTER (BUTTON_RADIUS + 5)
|
||||
#define BUTTON_DIAMETER (2 * BUTTON_SPACE)
|
||||
|
||||
/*******************************************************************************
|
||||
* Variables defined in i3lock.c.
|
||||
******************************************************************************/
|
||||
|
||||
extern bool debug_mode;
|
||||
|
||||
/* The current position in the input buffer. Useful to determine if any
|
||||
* characters of the password have already been entered or not. */
|
||||
extern int input_position;
|
||||
|
||||
/* The lock window. */
|
||||
extern xcb_window_t win;
|
||||
|
||||
/* The current resolution of the X11 root window. */
|
||||
extern uint32_t last_resolution[2];
|
||||
|
||||
/* Whether the unlock indicator is enabled (defaults to true). */
|
||||
extern bool unlock_indicator;
|
||||
|
||||
/* List of pressed modifiers, or NULL if none are pressed. */
|
||||
extern char *modifier_string;
|
||||
/* Name of the current keyboard layout or NULL if not initialized. */
|
||||
char *layout_string = NULL;
|
||||
|
||||
/* A Cairo surface containing the specified image (-i), if any. */
|
||||
extern cairo_surface_t *img;
|
||||
|
||||
/* Whether the image should be tiled. */
|
||||
extern bool tile;
|
||||
/* The background color to use (in hex). */
|
||||
extern char color[7];
|
||||
|
||||
/* Whether the failed attempts should be displayed. */
|
||||
extern bool show_failed_attempts;
|
||||
/* Whether keyboard layout should be displayed. */
|
||||
extern bool show_keyboard_layout;
|
||||
/* Number of failed unlock attempts. */
|
||||
extern int failed_attempts;
|
||||
|
||||
extern struct xkb_keymap *xkb_keymap;
|
||||
extern struct xkb_state *xkb_state;
|
||||
|
||||
/*******************************************************************************
|
||||
* Variables defined in xcb.c.
|
||||
******************************************************************************/
|
||||
|
||||
/* The root screen, to determine the DPI. */
|
||||
extern xcb_screen_t *screen;
|
||||
|
||||
/*******************************************************************************
|
||||
* Local variables.
|
||||
******************************************************************************/
|
||||
|
||||
/* Cache the screen’s visual, necessary for creating a Cairo context. */
|
||||
static xcb_visualtype_t *vistype;
|
||||
|
||||
/* Maintain the current unlock/PAM state to draw the appropriate unlock
|
||||
* indicator. */
|
||||
unlock_state_t unlock_state;
|
||||
auth_state_t auth_state;
|
||||
|
||||
static void string_append(char **string_ptr, const char *appended) {
|
||||
char *tmp = NULL;
|
||||
if (*string_ptr == NULL) {
|
||||
if (asprintf(&tmp, "%s", appended) != -1) {
|
||||
*string_ptr = tmp;
|
||||
}
|
||||
} else if (asprintf(&tmp, "%s, %s", *string_ptr, appended) != -1) {
|
||||
free(*string_ptr);
|
||||
*string_ptr = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static void display_button_text(
|
||||
cairo_t *ctx, const char *text, double y_offset, bool use_dark_text) {
|
||||
cairo_text_extents_t extents;
|
||||
double x, y;
|
||||
|
||||
cairo_text_extents(ctx, text, &extents);
|
||||
x = BUTTON_CENTER - ((extents.width / 2) + extents.x_bearing);
|
||||
y = BUTTON_CENTER - ((extents.height / 2) + extents.y_bearing) + y_offset;
|
||||
|
||||
cairo_move_to(ctx, x, y);
|
||||
if (use_dark_text) {
|
||||
cairo_set_source_rgb(ctx, 0., 0., 0.);
|
||||
} else {
|
||||
cairo_set_source_rgb(ctx, 1., 1., 1.);
|
||||
}
|
||||
cairo_show_text(ctx, text);
|
||||
cairo_close_path(ctx);
|
||||
}
|
||||
|
||||
static void update_layout_string() {
|
||||
if (layout_string) {
|
||||
free(layout_string);
|
||||
layout_string = NULL;
|
||||
}
|
||||
xkb_layout_index_t num_layouts = xkb_keymap_num_layouts(xkb_keymap);
|
||||
for (xkb_layout_index_t i = 0; i < num_layouts; ++i) {
|
||||
if (xkb_state_layout_index_is_active(xkb_state, i, XKB_STATE_LAYOUT_EFFECTIVE)) {
|
||||
const char *name = xkb_keymap_layout_get_name(xkb_keymap, i);
|
||||
if (name) {
|
||||
string_append(&layout_string, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* check_modifier_keys describes the currently active modifiers (Caps Lock, Alt,
|
||||
Num Lock or Super) in the modifier_string variable. */
|
||||
static void check_modifier_keys(void) {
|
||||
xkb_mod_index_t idx, num_mods;
|
||||
const char *mod_name;
|
||||
|
||||
num_mods = xkb_keymap_num_mods(xkb_keymap);
|
||||
|
||||
for (idx = 0; idx < num_mods; idx++) {
|
||||
if (!xkb_state_mod_index_is_active(xkb_state, idx, XKB_STATE_MODS_EFFECTIVE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mod_name = xkb_keymap_mod_get_name(xkb_keymap, idx);
|
||||
if (mod_name == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Replace certain xkb names with nicer, human-readable ones. */
|
||||
if (strcmp(mod_name, XKB_MOD_NAME_CAPS) == 0) {
|
||||
mod_name = "Caps Lock";
|
||||
} else if (strcmp(mod_name, XKB_MOD_NAME_NUM) == 0) {
|
||||
mod_name = "Num Lock";
|
||||
} else {
|
||||
/* Show only Caps Lock and Num Lock, other modifiers (e.g. Shift)
|
||||
* leak state about the password. */
|
||||
continue;
|
||||
}
|
||||
string_append(&modifier_string, mod_name);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Draws global image with fill color onto a pixmap with the given
|
||||
* resolution and returns it.
|
||||
*
|
||||
*/
|
||||
void draw_image(xcb_pixmap_t bg_pixmap, uint32_t *resolution) {
|
||||
const double scaling_factor = get_dpi_value() / 96.0;
|
||||
int button_diameter_physical = ceil(scaling_factor * BUTTON_DIAMETER);
|
||||
DEBUG("scaling_factor is %.f, physical diameter is %d px\n",
|
||||
scaling_factor, button_diameter_physical);
|
||||
|
||||
if (!vistype) {
|
||||
vistype = get_root_visual_type(screen);
|
||||
}
|
||||
|
||||
/* Initialize cairo: Create one in-memory surface to render the unlock
|
||||
* indicator on, create one XCB surface to actually draw (one or more,
|
||||
* depending on the amount of screens) unlock indicators on. */
|
||||
cairo_surface_t *output = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, button_diameter_physical, button_diameter_physical);
|
||||
cairo_t *ctx = cairo_create(output);
|
||||
|
||||
cairo_surface_t *xcb_output = cairo_xcb_surface_create(conn, bg_pixmap, vistype, resolution[0], resolution[1]);
|
||||
cairo_t *xcb_ctx = cairo_create(xcb_output);
|
||||
|
||||
/* After the first iteration, the pixmap will still contain the previous
|
||||
* contents. Explicitly clear the entire pixmap with the background color
|
||||
* first to get back into a defined state: */
|
||||
char strgroups[3][3] = {{color[0], color[1], '\0'},
|
||||
{color[2], color[3], '\0'},
|
||||
{color[4], color[5], '\0'}};
|
||||
uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
|
||||
(strtol(strgroups[1], NULL, 16)),
|
||||
(strtol(strgroups[2], NULL, 16))};
|
||||
cairo_set_source_rgb(xcb_ctx, rgb16[0] / 255.0, rgb16[1] / 255.0, rgb16[2] / 255.0);
|
||||
cairo_rectangle(xcb_ctx, 0, 0, resolution[0], resolution[1]);
|
||||
cairo_fill(xcb_ctx);
|
||||
|
||||
if (img) {
|
||||
if (!tile) {
|
||||
cairo_set_source_surface(xcb_ctx, img, 0, 0);
|
||||
cairo_paint(xcb_ctx);
|
||||
} else {
|
||||
/* create a pattern and fill a rectangle as big as the screen */
|
||||
cairo_pattern_t *pattern;
|
||||
pattern = cairo_pattern_create_for_surface(img);
|
||||
cairo_set_source(xcb_ctx, pattern);
|
||||
cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
|
||||
cairo_rectangle(xcb_ctx, 0, 0, resolution[0], resolution[1]);
|
||||
cairo_fill(xcb_ctx);
|
||||
cairo_pattern_destroy(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
if (unlock_indicator &&
|
||||
(unlock_state >= STATE_KEY_PRESSED || auth_state > STATE_AUTH_IDLE)) {
|
||||
cairo_scale(ctx, scaling_factor, scaling_factor);
|
||||
/* Draw a (centered) circle with transparent background. */
|
||||
cairo_set_line_width(ctx, 10.0);
|
||||
cairo_arc(ctx,
|
||||
BUTTON_CENTER /* x */,
|
||||
BUTTON_CENTER /* y */,
|
||||
BUTTON_RADIUS /* radius */,
|
||||
0 /* start */,
|
||||
2 * M_PI /* end */);
|
||||
|
||||
/* Use the appropriate color for the different PAM states
|
||||
* (currently verifying, wrong password, or default) */
|
||||
switch (auth_state) {
|
||||
case STATE_AUTH_VERIFY:
|
||||
case STATE_AUTH_LOCK:
|
||||
cairo_set_source_rgba(ctx, 0, 114.0 / 255, 255.0 / 255, 0.75);
|
||||
break;
|
||||
case STATE_AUTH_WRONG:
|
||||
case STATE_I38LOCK_LOCK_FAILED:
|
||||
cairo_set_source_rgba(ctx, 250.0 / 255, 0, 0, 0.75);
|
||||
break;
|
||||
default:
|
||||
if (unlock_state == STATE_NOTHING_TO_DELETE) {
|
||||
cairo_set_source_rgba(ctx, 250.0 / 255, 0, 0, 0.75);
|
||||
break;
|
||||
}
|
||||
cairo_set_source_rgba(ctx, 0, 0, 0, 0.75);
|
||||
break;
|
||||
}
|
||||
cairo_fill_preserve(ctx);
|
||||
|
||||
bool use_dark_text = true;
|
||||
|
||||
switch (auth_state) {
|
||||
case STATE_AUTH_VERIFY:
|
||||
case STATE_AUTH_LOCK:
|
||||
cairo_set_source_rgb(ctx, 51.0 / 255, 0, 250.0 / 255);
|
||||
break;
|
||||
case STATE_AUTH_WRONG:
|
||||
case STATE_I38LOCK_LOCK_FAILED:
|
||||
cairo_set_source_rgb(ctx, 125.0 / 255, 51.0 / 255, 0);
|
||||
break;
|
||||
case STATE_AUTH_IDLE:
|
||||
if (unlock_state == STATE_NOTHING_TO_DELETE) {
|
||||
cairo_set_source_rgb(ctx, 125.0 / 255, 51.0 / 255, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
cairo_set_source_rgb(ctx, 51.0 / 255, 125.0 / 255, 0);
|
||||
use_dark_text = false;
|
||||
break;
|
||||
}
|
||||
cairo_stroke(ctx);
|
||||
|
||||
/* Draw an inner seperator line. */
|
||||
cairo_set_source_rgb(ctx, 0, 0, 0);
|
||||
cairo_set_line_width(ctx, 2.0);
|
||||
cairo_arc(ctx,
|
||||
BUTTON_CENTER /* x */,
|
||||
BUTTON_CENTER /* y */,
|
||||
BUTTON_RADIUS - 5 /* radius */,
|
||||
0,
|
||||
2 * M_PI);
|
||||
cairo_stroke(ctx);
|
||||
|
||||
cairo_set_line_width(ctx, 10.0);
|
||||
|
||||
/* Display a (centered) text of the current PAM state. */
|
||||
char *text = NULL;
|
||||
/* We don't want to show more than a 3-digit number. */
|
||||
char buf[4];
|
||||
|
||||
cairo_set_source_rgb(ctx, 0, 0, 0);
|
||||
cairo_select_font_face(ctx, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
|
||||
cairo_set_font_size(ctx, 28.0);
|
||||
switch (auth_state) {
|
||||
case STATE_AUTH_VERIFY:
|
||||
text = "Verifying…";
|
||||
break;
|
||||
case STATE_AUTH_LOCK:
|
||||
text = "Locking…";
|
||||
break;
|
||||
case STATE_AUTH_WRONG:
|
||||
text = "Wrong!";
|
||||
break;
|
||||
case STATE_I38LOCK_LOCK_FAILED:
|
||||
text = "Lock failed!";
|
||||
break;
|
||||
default:
|
||||
if (unlock_state == STATE_NOTHING_TO_DELETE) {
|
||||
text = "No input";
|
||||
}
|
||||
if (show_failed_attempts && failed_attempts > 0) {
|
||||
if (failed_attempts > 999) {
|
||||
text = "> 999";
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "%d", failed_attempts);
|
||||
text = buf;
|
||||
}
|
||||
cairo_set_source_rgb(ctx, 1, 0, 0);
|
||||
cairo_set_font_size(ctx, 32.0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (text) {
|
||||
display_button_text(ctx, text, 0., use_dark_text);
|
||||
}
|
||||
|
||||
if (modifier_string != NULL) {
|
||||
cairo_set_font_size(ctx, 14.0);
|
||||
display_button_text(ctx, modifier_string, 28., use_dark_text);
|
||||
}
|
||||
if (show_keyboard_layout && layout_string != NULL) {
|
||||
cairo_set_font_size(ctx, 14.0);
|
||||
display_button_text(ctx, layout_string, -28., use_dark_text);
|
||||
}
|
||||
|
||||
/* After the user pressed any valid key or the backspace key, we
|
||||
* highlight a random part of the unlock indicator to confirm this
|
||||
* keypress. */
|
||||
if (unlock_state == STATE_KEY_ACTIVE ||
|
||||
unlock_state == STATE_BACKSPACE_ACTIVE) {
|
||||
cairo_new_sub_path(ctx);
|
||||
double highlight_start = (rand() % (int)(2 * M_PI * 100)) / 100.0;
|
||||
cairo_arc(ctx,
|
||||
BUTTON_CENTER /* x */,
|
||||
BUTTON_CENTER /* y */,
|
||||
BUTTON_RADIUS /* radius */,
|
||||
highlight_start,
|
||||
highlight_start + (M_PI / 3.0));
|
||||
if (unlock_state == STATE_KEY_ACTIVE) {
|
||||
/* For normal keys, we use a lighter green. */
|
||||
cairo_set_source_rgb(ctx, 51.0 / 255, 219.0 / 255, 0);
|
||||
} else {
|
||||
/* For backspace, we use red. */
|
||||
cairo_set_source_rgb(ctx, 219.0 / 255, 51.0 / 255, 0);
|
||||
}
|
||||
cairo_stroke(ctx);
|
||||
|
||||
/* Draw two little separators for the highlighted part of the
|
||||
* unlock indicator. */
|
||||
cairo_set_source_rgb(ctx, 0, 0, 0);
|
||||
cairo_arc(ctx,
|
||||
BUTTON_CENTER /* x */,
|
||||
BUTTON_CENTER /* y */,
|
||||
BUTTON_RADIUS /* radius */,
|
||||
highlight_start /* start */,
|
||||
highlight_start + (M_PI / 128.0) /* end */);
|
||||
cairo_stroke(ctx);
|
||||
cairo_arc(ctx,
|
||||
BUTTON_CENTER /* x */,
|
||||
BUTTON_CENTER /* y */,
|
||||
BUTTON_RADIUS /* radius */,
|
||||
(highlight_start + (M_PI / 3.0)) - (M_PI / 128.0) /* start */,
|
||||
highlight_start + (M_PI / 3.0) /* end */);
|
||||
cairo_stroke(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
if (xr_screens > 0) {
|
||||
/* Composite the unlock indicator in the middle of each screen. */
|
||||
for (int screen = 0; screen < xr_screens; screen++) {
|
||||
int x = (xr_resolutions[screen].x + ((xr_resolutions[screen].width / 2) - (button_diameter_physical / 2)));
|
||||
int y = (xr_resolutions[screen].y + ((xr_resolutions[screen].height / 2) - (button_diameter_physical / 2)));
|
||||
cairo_set_source_surface(xcb_ctx, output, x, y);
|
||||
cairo_rectangle(xcb_ctx, x, y, button_diameter_physical, button_diameter_physical);
|
||||
cairo_fill(xcb_ctx);
|
||||
}
|
||||
} else {
|
||||
/* We have no information about the screen sizes/positions, so we just
|
||||
* place the unlock indicator in the middle of the X root window and
|
||||
* hope for the best. */
|
||||
int x = (last_resolution[0] / 2) - (button_diameter_physical / 2);
|
||||
int y = (last_resolution[1] / 2) - (button_diameter_physical / 2);
|
||||
cairo_set_source_surface(xcb_ctx, output, x, y);
|
||||
cairo_rectangle(xcb_ctx, x, y, button_diameter_physical, button_diameter_physical);
|
||||
cairo_fill(xcb_ctx);
|
||||
}
|
||||
|
||||
cairo_surface_destroy(xcb_output);
|
||||
cairo_surface_destroy(output);
|
||||
cairo_destroy(ctx);
|
||||
cairo_destroy(xcb_ctx);
|
||||
}
|
||||
|
||||
static xcb_pixmap_t bg_pixmap = XCB_NONE;
|
||||
|
||||
/*
|
||||
* Releases the current background pixmap so that the next redraw_screen() call
|
||||
* will allocate a new one with the updated resolution.
|
||||
*
|
||||
*/
|
||||
void free_bg_pixmap(void) {
|
||||
xcb_free_pixmap(conn, bg_pixmap);
|
||||
bg_pixmap = XCB_NONE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calls draw_image on a new pixmap and swaps that with the current pixmap
|
||||
*
|
||||
*/
|
||||
void redraw_screen(void) {
|
||||
DEBUG("redraw_screen(unlock_state = %d, auth_state = %d)\n", unlock_state, auth_state);
|
||||
|
||||
if (modifier_string) {
|
||||
free(modifier_string);
|
||||
modifier_string = NULL;
|
||||
}
|
||||
check_modifier_keys();
|
||||
update_layout_string();
|
||||
|
||||
if (bg_pixmap == XCB_NONE) {
|
||||
DEBUG("allocating pixmap for %d x %d px\n", last_resolution[0], last_resolution[1]);
|
||||
bg_pixmap = create_bg_pixmap(conn, screen, last_resolution, color);
|
||||
}
|
||||
|
||||
draw_image(bg_pixmap, last_resolution);
|
||||
xcb_change_window_attributes(conn, win, XCB_CW_BACK_PIXMAP, (uint32_t[1]){bg_pixmap});
|
||||
/* XXX: Possible optimization: Only update the area in the middle of the
|
||||
* screen instead of the whole screen. */
|
||||
xcb_clear_area(conn, 0, win, 0, 0, last_resolution[0], last_resolution[1]);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Hides the unlock indicator completely when there is no content in the
|
||||
* password buffer.
|
||||
*
|
||||
*/
|
||||
void clear_indicator(void) {
|
||||
if (input_position == 0) {
|
||||
unlock_state = STATE_STARTED;
|
||||
} else {
|
||||
unlock_state = STATE_KEY_PRESSED;
|
||||
}
|
||||
redraw_screen();
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* © 2010 Michael Stapelberg
|
||||
*
|
||||
* xcb.c: contains all functions which use XCB to talk to X11. Mostly wrappers
|
||||
* around the rather complicated/ugly parts of the XCB API.
|
||||
*
|
||||
*/
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_image.h>
|
||||
#include <xcb/xcb_atom.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "cursors.h"
|
||||
#include "unlock_indicator.h"
|
||||
|
||||
extern auth_state_t auth_state;
|
||||
|
||||
xcb_connection_t *conn;
|
||||
xcb_screen_t *screen;
|
||||
|
||||
static xcb_atom_t _NET_WM_BYPASS_COMPOSITOR = XCB_NONE;
|
||||
void _init_net_wm_bypass_compositor(xcb_connection_t *conn) {
|
||||
if (_NET_WM_BYPASS_COMPOSITOR != XCB_NONE) {
|
||||
/* already initialized */
|
||||
return;
|
||||
}
|
||||
xcb_generic_error_t *err;
|
||||
xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply(
|
||||
conn,
|
||||
xcb_intern_atom(conn, 0, strlen("_NET_WM_BYPASS_COMPOSITOR"), "_NET_WM_BYPASS_COMPOSITOR"),
|
||||
&err);
|
||||
if (atom_reply == NULL) {
|
||||
fprintf(stderr, "X11 Error %d\n", err->error_code);
|
||||
free(err);
|
||||
return;
|
||||
}
|
||||
_NET_WM_BYPASS_COMPOSITOR = atom_reply->atom;
|
||||
free(atom_reply);
|
||||
}
|
||||
|
||||
#define curs_invisible_width 8
|
||||
#define curs_invisible_height 8
|
||||
|
||||
static unsigned char curs_invisible_bits[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
#define curs_windows_width 11
|
||||
#define curs_windows_height 19
|
||||
|
||||
static unsigned char curs_windows_bits[] = {
|
||||
0xfe, 0x07, 0xfc, 0x07, 0xfa, 0x07, 0xf6, 0x07, 0xee, 0x07, 0xde, 0x07,
|
||||
0xbe, 0x07, 0x7e, 0x07, 0xfe, 0x06, 0xfe, 0x05, 0x3e, 0x00, 0xb6, 0x07,
|
||||
0x6a, 0x07, 0x6c, 0x07, 0xde, 0x06, 0xdf, 0x06, 0xbf, 0x05, 0xbf, 0x05,
|
||||
0x7f, 0x06};
|
||||
|
||||
#define mask_windows_width 11
|
||||
#define mask_windows_height 19
|
||||
|
||||
static unsigned char mask_windows_bits[] = {
|
||||
0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0f, 0x00, 0x1f, 0x00, 0x3f, 0x00,
|
||||
0x7f, 0x00, 0xff, 0x00, 0xff, 0x01, 0xff, 0x03, 0xff, 0x07, 0x7f, 0x00,
|
||||
0xf7, 0x00, 0xf3, 0x00, 0xe1, 0x01, 0xe0, 0x01, 0xc0, 0x03, 0xc0, 0x03,
|
||||
0x80, 0x01};
|
||||
|
||||
static uint32_t get_colorpixel(char *hex) {
|
||||
char strgroups[3][3] = {{hex[0], hex[1], '\0'},
|
||||
{hex[2], hex[3], '\0'},
|
||||
{hex[4], hex[5], '\0'}};
|
||||
uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
|
||||
(strtol(strgroups[1], NULL, 16)),
|
||||
(strtol(strgroups[2], NULL, 16))};
|
||||
|
||||
return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
|
||||
}
|
||||
|
||||
xcb_visualtype_t *get_root_visual_type(xcb_screen_t *screen) {
|
||||
xcb_visualtype_t *visual_type = NULL;
|
||||
xcb_depth_iterator_t depth_iter;
|
||||
xcb_visualtype_iterator_t visual_iter;
|
||||
|
||||
for (depth_iter = xcb_screen_allowed_depths_iterator(screen);
|
||||
depth_iter.rem;
|
||||
xcb_depth_next(&depth_iter)) {
|
||||
for (visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
|
||||
visual_iter.rem;
|
||||
xcb_visualtype_next(&visual_iter)) {
|
||||
if (screen->root_visual != visual_iter.data->visual_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
visual_type = visual_iter.data;
|
||||
return visual_type;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
xcb_pixmap_t create_bg_pixmap(xcb_connection_t *conn, xcb_screen_t *scr, u_int32_t *resolution, char *color) {
|
||||
xcb_pixmap_t bg_pixmap = xcb_generate_id(conn);
|
||||
xcb_create_pixmap(conn, scr->root_depth, bg_pixmap, scr->root,
|
||||
resolution[0], resolution[1]);
|
||||
|
||||
/* Generate a Graphics Context and fill the pixmap with background color
|
||||
* (for images that are smaller than your screen) */
|
||||
xcb_gcontext_t gc = xcb_generate_id(conn);
|
||||
uint32_t values[] = {get_colorpixel(color)};
|
||||
xcb_create_gc(conn, gc, bg_pixmap, XCB_GC_FOREGROUND, values);
|
||||
xcb_rectangle_t rect = {0, 0, resolution[0], resolution[1]};
|
||||
xcb_poly_fill_rectangle(conn, bg_pixmap, gc, 1, &rect);
|
||||
xcb_free_gc(conn, gc);
|
||||
|
||||
return bg_pixmap;
|
||||
}
|
||||
|
||||
xcb_window_t open_fullscreen_window(xcb_connection_t *conn, xcb_screen_t *scr, char *color, xcb_pixmap_t pixmap) {
|
||||
uint32_t mask = 0;
|
||||
uint32_t values[3];
|
||||
xcb_window_t win = xcb_generate_id(conn);
|
||||
|
||||
if (pixmap == XCB_NONE) {
|
||||
mask |= XCB_CW_BACK_PIXEL;
|
||||
values[0] = get_colorpixel(color);
|
||||
} else {
|
||||
mask |= XCB_CW_BACK_PIXMAP;
|
||||
values[0] = pixmap;
|
||||
}
|
||||
|
||||
mask |= XCB_CW_OVERRIDE_REDIRECT;
|
||||
values[1] = 1;
|
||||
|
||||
mask |= XCB_CW_EVENT_MASK;
|
||||
values[2] = XCB_EVENT_MASK_EXPOSURE |
|
||||
XCB_EVENT_MASK_KEY_PRESS |
|
||||
XCB_EVENT_MASK_KEY_RELEASE |
|
||||
XCB_EVENT_MASK_VISIBILITY_CHANGE |
|
||||
XCB_EVENT_MASK_STRUCTURE_NOTIFY;
|
||||
|
||||
xcb_create_window(conn,
|
||||
XCB_COPY_FROM_PARENT,
|
||||
win, /* the window id */
|
||||
scr->root, /* parent == root */
|
||||
0, 0,
|
||||
scr->width_in_pixels,
|
||||
scr->height_in_pixels, /* dimensions */
|
||||
0, /* border = 0, we draw our own */
|
||||
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||
XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
|
||||
mask,
|
||||
values);
|
||||
|
||||
char *name = "i38lock";
|
||||
xcb_change_property(conn,
|
||||
XCB_PROP_MODE_REPLACE,
|
||||
win,
|
||||
XCB_ATOM_WM_NAME,
|
||||
XCB_ATOM_STRING,
|
||||
8,
|
||||
strlen(name),
|
||||
name);
|
||||
|
||||
xcb_change_property(conn,
|
||||
XCB_PROP_MODE_REPLACE,
|
||||
win,
|
||||
XCB_ATOM_WM_CLASS,
|
||||
XCB_ATOM_STRING,
|
||||
8,
|
||||
2 * (strlen("i38lock") + 1),
|
||||
"i38lock\0i38lock\0");
|
||||
|
||||
const uint32_t bypass_compositor = 1; /* disable compositing */
|
||||
_init_net_wm_bypass_compositor(conn);
|
||||
xcb_change_property(conn,
|
||||
XCB_PROP_MODE_REPLACE,
|
||||
win,
|
||||
_NET_WM_BYPASS_COMPOSITOR,
|
||||
XCB_ATOM_CARDINAL,
|
||||
32,
|
||||
1,
|
||||
&bypass_compositor);
|
||||
|
||||
/* Map the window (= make it visible) */
|
||||
xcb_map_window(conn, win);
|
||||
|
||||
/* Raise window (put it on top) */
|
||||
values[0] = XCB_STACK_MODE_ABOVE;
|
||||
xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
|
||||
/* Ensure that the window is created and set up before returning */
|
||||
xcb_aux_sync(conn);
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
/*
|
||||
* Repeatedly tries to grab pointer and keyboard (up to the specified number of
|
||||
* tries).
|
||||
*
|
||||
* Returns true if the grab succeeded, false if not.
|
||||
*
|
||||
*/
|
||||
bool grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_t cursor, int tries) {
|
||||
xcb_grab_pointer_cookie_t pcookie;
|
||||
xcb_grab_pointer_reply_t *preply;
|
||||
|
||||
xcb_grab_keyboard_cookie_t kcookie;
|
||||
xcb_grab_keyboard_reply_t *kreply;
|
||||
|
||||
const suseconds_t screen_redraw_timeout = 100000; /* 100ms */
|
||||
|
||||
/* Using few variables to trigger a redraw_screen() if too many tries */
|
||||
bool redrawn = false;
|
||||
struct timeval start;
|
||||
if (gettimeofday(&start, NULL) == -1) {
|
||||
err(EXIT_FAILURE, "gettimeofday");
|
||||
}
|
||||
|
||||
while (tries-- > 0) {
|
||||
pcookie = xcb_grab_pointer(
|
||||
conn,
|
||||
false, /* get all pointer events specified by the following mask */
|
||||
screen->root, /* grab the root window */
|
||||
XCB_NONE, /* which events to let through */
|
||||
XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
|
||||
XCB_GRAB_MODE_ASYNC, /* keyboard mode */
|
||||
XCB_NONE, /* confine_to = in which window should the cursor stay */
|
||||
cursor, /* we change the cursor to whatever the user wanted */
|
||||
XCB_CURRENT_TIME);
|
||||
|
||||
if ((preply = xcb_grab_pointer_reply(conn, pcookie, NULL)) &&
|
||||
preply->status == XCB_GRAB_STATUS_SUCCESS) {
|
||||
free(preply);
|
||||
break;
|
||||
}
|
||||
|
||||
/* In case the grab failed, we still need to free the reply */
|
||||
free(preply);
|
||||
|
||||
/* Make this quite a bit slower */
|
||||
usleep(50);
|
||||
|
||||
struct timeval now;
|
||||
if (gettimeofday(&now, NULL) == -1) {
|
||||
err(EXIT_FAILURE, "gettimeofday");
|
||||
}
|
||||
|
||||
struct timeval elapsed;
|
||||
timersub(&now, &start, &elapsed);
|
||||
|
||||
if (!redrawn &&
|
||||
(tries % 100) == 0 &&
|
||||
elapsed.tv_usec >= screen_redraw_timeout) {
|
||||
redraw_screen();
|
||||
redrawn = true;
|
||||
}
|
||||
}
|
||||
|
||||
while (tries-- > 0) {
|
||||
kcookie = xcb_grab_keyboard(
|
||||
conn,
|
||||
true, /* report events */
|
||||
screen->root, /* grab the root window */
|
||||
XCB_CURRENT_TIME,
|
||||
XCB_GRAB_MODE_ASYNC, /* process events as normal, do not require sync */
|
||||
XCB_GRAB_MODE_ASYNC);
|
||||
|
||||
if ((kreply = xcb_grab_keyboard_reply(conn, kcookie, NULL)) &&
|
||||
kreply->status == XCB_GRAB_STATUS_SUCCESS) {
|
||||
free(kreply);
|
||||
break;
|
||||
}
|
||||
|
||||
/* In case the grab failed, we still need to free the reply */
|
||||
free(kreply);
|
||||
|
||||
/* Make this quite a bit slower */
|
||||
usleep(50);
|
||||
|
||||
struct timeval now;
|
||||
if (gettimeofday(&now, NULL) == -1) {
|
||||
err(EXIT_FAILURE, "gettimeofday");
|
||||
}
|
||||
|
||||
struct timeval elapsed;
|
||||
timersub(&now, &start, &elapsed);
|
||||
|
||||
/* Trigger a screen redraw if 100ms elapsed */
|
||||
if (!redrawn &&
|
||||
(tries % 100) == 0 &&
|
||||
elapsed.tv_usec >= screen_redraw_timeout) {
|
||||
redraw_screen();
|
||||
redrawn = true;
|
||||
}
|
||||
}
|
||||
|
||||
return (tries > 0);
|
||||
}
|
||||
|
||||
xcb_cursor_t create_cursor(xcb_connection_t *conn, xcb_screen_t *screen, xcb_window_t win, int choice) {
|
||||
xcb_pixmap_t bitmap;
|
||||
xcb_pixmap_t mask;
|
||||
xcb_cursor_t cursor;
|
||||
|
||||
unsigned char *curs_bits;
|
||||
unsigned char *mask_bits;
|
||||
int curs_w, curs_h;
|
||||
|
||||
switch (choice) {
|
||||
case CURS_NONE:
|
||||
curs_bits = curs_invisible_bits;
|
||||
mask_bits = curs_invisible_bits;
|
||||
curs_w = curs_invisible_width;
|
||||
curs_h = curs_invisible_height;
|
||||
break;
|
||||
case CURS_WIN:
|
||||
curs_bits = curs_windows_bits;
|
||||
mask_bits = mask_windows_bits;
|
||||
curs_w = curs_windows_width;
|
||||
curs_h = curs_windows_height;
|
||||
break;
|
||||
case CURS_DEFAULT:
|
||||
default:
|
||||
return XCB_NONE; /* XCB_NONE is xcb's way of saying "don't change the cursor" */
|
||||
}
|
||||
|
||||
bitmap = xcb_create_pixmap_from_bitmap_data(conn,
|
||||
win,
|
||||
curs_bits,
|
||||
curs_w,
|
||||
curs_h,
|
||||
1,
|
||||
screen->white_pixel,
|
||||
screen->black_pixel,
|
||||
NULL);
|
||||
|
||||
mask = xcb_create_pixmap_from_bitmap_data(conn,
|
||||
win,
|
||||
mask_bits,
|
||||
curs_w,
|
||||
curs_h,
|
||||
1,
|
||||
screen->white_pixel,
|
||||
screen->black_pixel,
|
||||
NULL);
|
||||
|
||||
cursor = xcb_generate_id(conn);
|
||||
|
||||
xcb_create_cursor(conn,
|
||||
cursor,
|
||||
bitmap,
|
||||
mask,
|
||||
65535, 65535, 65535,
|
||||
0, 0, 0,
|
||||
0, 0);
|
||||
|
||||
xcb_free_pixmap(conn, bitmap);
|
||||
xcb_free_pixmap(conn, mask);
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
static xcb_atom_t _NET_ACTIVE_WINDOW = XCB_NONE;
|
||||
void _init_net_active_window(xcb_connection_t *conn) {
|
||||
if (_NET_ACTIVE_WINDOW != XCB_NONE) {
|
||||
/* already initialized */
|
||||
return;
|
||||
}
|
||||
xcb_generic_error_t *err;
|
||||
xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply(
|
||||
conn,
|
||||
xcb_intern_atom(conn, 0, strlen("_NET_ACTIVE_WINDOW"), "_NET_ACTIVE_WINDOW"),
|
||||
&err);
|
||||
if (atom_reply == NULL) {
|
||||
fprintf(stderr, "X11 Error %d\n", err->error_code);
|
||||
free(err);
|
||||
return;
|
||||
}
|
||||
_NET_ACTIVE_WINDOW = atom_reply->atom;
|
||||
free(atom_reply);
|
||||
}
|
||||
|
||||
xcb_window_t find_focused_window(xcb_connection_t *conn, const xcb_window_t root) {
|
||||
xcb_window_t result = XCB_NONE;
|
||||
|
||||
_init_net_active_window(conn);
|
||||
|
||||
xcb_get_property_reply_t *prop_reply = xcb_get_property_reply(
|
||||
conn,
|
||||
xcb_get_property_unchecked(
|
||||
conn, false, root, _NET_ACTIVE_WINDOW, XCB_GET_PROPERTY_TYPE_ANY, 0, 1 /* word */),
|
||||
NULL);
|
||||
if (prop_reply == NULL) {
|
||||
goto out;
|
||||
}
|
||||
if (xcb_get_property_value_length(prop_reply) == 0) {
|
||||
goto out_prop;
|
||||
}
|
||||
if (prop_reply->type != XCB_ATOM_WINDOW) {
|
||||
goto out_prop;
|
||||
}
|
||||
|
||||
result = *((xcb_window_t *)xcb_get_property_value(prop_reply));
|
||||
|
||||
out_prop:
|
||||
free(prop_reply);
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
|
||||
void set_focused_window(xcb_connection_t *conn, const xcb_window_t root, const xcb_window_t window) {
|
||||
xcb_client_message_event_t ev;
|
||||
memset(&ev, '\0', sizeof(xcb_client_message_event_t));
|
||||
|
||||
_init_net_active_window(conn);
|
||||
|
||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
||||
ev.window = window;
|
||||
ev.type = _NET_ACTIVE_WINDOW;
|
||||
ev.format = 32;
|
||||
ev.data.data32[0] = 2; /* 2 = pager */
|
||||
|
||||
xcb_send_event(conn, false, root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
Reference in New Issue
Block a user