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