diff --git a/I38.md b/I38.md index 0591a20..72c29de 100644 --- a/I38.md +++ b/I38.md @@ -184,6 +184,12 @@ If required dependencies are installed, you can use OCR to read text from images *GNOME/MATE comparison:* OCR features are typically not integrated into GNOME/MATE by default. +### Screen Lock + +If you enabled the I38 privacy screen lock during setup, press `Control` + `Alt` + `Tab` to enter panel mode, then press `Control` + `MODKEY` + `l` to lock the screen. This is a privacy screen for I38, not a secure system lock. + +When a screen lock PIN is configured, I38 can also autolock after an idle timeout. The default is never. Autolock is X11/i3-only and requires `xprintidle`; if `playerctl` is installed, I38 will avoid autolocking during likely video playback. + ### Sound Effects I38 provides audio feedback for many actions: diff --git a/README.md b/README.md index a8b6b6e..f46184e 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ An uppercase I looks like a 1, 3 from i3, and 8 because the song [We Are 138](ht scrot: For OCR - sox: for sounds. - tesseract: For OCR +- xprintidle: [optional] For screen lock autolock on X11/i3 - tesseract-data-eng: For OCR - udiskie: [optional] for automatically mounting removable storage - x11bell: [optional] Bell support if you do not have a PC speaker. Available from https://github.com/jovanlanik/x11bell diff --git a/i38.sh b/i38.sh index a45d765..96c2b95 100755 --- a/i38.sh +++ b/i38.sh @@ -220,6 +220,27 @@ select_personal_mode_key() { normalize_ratpoison_key "$selectedKey" } +select_screenlock_autolock_seconds() { + local selectedAutolock + + if ! selectedAutolock="$(dialog --title "I38" \ + --backtitle "Use the arrow keys to choose how long I38 should wait before locking the screen automatically." \ + --clear \ + --default-item "0" \ + --menu "Autolock screen after:" 0 0 0 \ + "0" "Never" \ + "180" "3 minutes" \ + "300" "5 minutes" \ + "600" "10 minutes" \ + "1800" "30 minutes" \ + "3600" "1 hour" \ + --stdout)"; then + selectedAutolock="0" + fi + + echo "$selectedAutolock" +} + update_personal_customizations() { local customizationsPath="${i3Path}/customizations" local startMarker="# I38 Personal mode start" @@ -322,6 +343,7 @@ sounds="$sounds" # Screen lock screenlockPinHash="$screenlockPinHash" +screenlockAutolockSeconds="${screenlockAutolockSeconds:-0}" # Personal mode personalModeEnabled="${personalModeEnabled:-0}" @@ -799,6 +821,18 @@ if [[ -z "$screenlockPinHash" ]]; then done fi fi +if [[ -n "$screenlockPinHash" ]]; then + if command -v xprintidle &> /dev/null; then + if [[ -z "${screenlockAutolockSeconds+x}" ]] || [[ ! "$screenlockAutolockSeconds" =~ ^[0-9]+$ ]]; then + configChanged=1 + screenlockAutolockSeconds="$(select_screenlock_autolock_seconds)" + fi + elif [[ -z "${screenlockAutolockSeconds+x}" ]] || [[ ! "$screenlockAutolockSeconds" =~ ^[0-9]+$ ]]; then + screenlockAutolockSeconds=0 + fi +else + screenlockAutolockSeconds=0 +fi # Personal mode personalModeExists=1 if personal_mode_exists; then @@ -1257,6 +1291,9 @@ fi if [[ $batteryAlert -eq 0 ]]; then echo "exec_always --no-startup-id ${i3Path}/scripts/battery_alert.sh" fi +if [[ $usingSway -ne 0 ]] && [[ -n "$screenlockPinHash" ]] && [[ "${screenlockAutolockSeconds:-0}" -gt 0 ]] && command -v xprintidle &> /dev/null; then + echo "exec_always --no-startup-id ${i3Path}/scripts/screenlock_autolock.sh ${screenlockAutolockSeconds}" +fi # WayTray system tray daemon if command -v waytray-daemon &> /dev/null ; then echo 'exec_always --no-startup-id bash -c "pgrep -x waytray-daemon > /dev/null || waytray-daemon"' @@ -1379,7 +1416,37 @@ while IFS= read -r line; do lastComment="" fi -done < <(sed -n '/^mode "/,/^}$/!p' "${i3Path}/config" | grep -E '^#|^bindsym') +done < <( + awk ' + /^mode "/ { + inMode = 1 + previousComment = "" + next + } + inMode && /^}$/ { + inMode = 0 + next + } + inMode { + next + } + /^#/ { + previousComment = $0 + next + } + /^bindsym/ { + if (previousComment != "") { + print previousComment + previousComment = "" + } + print + next + } + { + previousComment = "" + } + ' "${i3Path}/config" +) # Build HTML tables { diff --git a/scripts/screenlock_autolock.sh b/scripts/screenlock_autolock.sh new file mode 100755 index 0000000..9c4039b --- /dev/null +++ b/scripts/screenlock_autolock.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash + +# This file is part of I38. + +# I38 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. + +# I38 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with I38. If not, see . + +autolockSeconds="${1:-0}" +scriptPath="$(readlink -f "$0")" +scriptDir="${scriptPath%/*}" +runtimeDir="${XDG_RUNTIME_DIR:-/tmp}" +lockDir="${runtimeDir}/i38-screenlock-autolock" +pollSeconds=5 + +notify_autolock_disabled() { + local message="$1" + + if command -v notify-send &> /dev/null; then + notify-send "I38 autolock disabled" "$message" + fi +} + +video_is_playing() { + local playerStatus playerUrl + + if ! command -v playerctl &> /dev/null; then + return 1 + fi + + while IFS='|' read -r _ playerStatus playerUrl; do + if [[ "$playerStatus" != "Playing" ]]; then + continue + fi + + if [[ "$playerUrl" =~ \.(avi|flv|m4v|mkv|mov|mp4|mpeg|mpg|ogm|ogv|webm|wmv)(\?.*)?$ ]]; then + return 0 + fi + done < <(playerctl -a metadata --format '{{playerName}}|{{status}}|{{xesam:url}}' 2> /dev/null || true) + + return 1 +} + +focused_window_is_fullscreen() { + if ! command -v i3-msg &> /dev/null || ! command -v jq &> /dev/null; then + return 1 + fi + + i3-msg -t get_tree \ + | jq -e 'any(.. | objects; (.focused? == true) and ((.fullscreen_mode? // 0) != 0))' \ + &> /dev/null +} + +screenlock_is_running() { + pgrep -f "${scriptDir}/screenlock.sh" &> /dev/null +} + +cleanup() { + rm -rf "$lockDir" +} + +stop_autolock() { + cleanup + exit 0 +} + +if [[ ! "$autolockSeconds" =~ ^[0-9]+$ ]] || [[ "$autolockSeconds" -le 0 ]]; then + exit 0 +fi + +if [[ -z "${DISPLAY:-}" ]]; then + notify_autolock_disabled "No X display was found." + exit 0 +fi + +if ! command -v xprintidle &> /dev/null; then + notify_autolock_disabled "Install xprintidle to use screen lock autolock." + exit 0 +fi + +if [[ -d "$lockDir" ]]; then + if [[ -f "${lockDir}/pid" ]]; then + read -r existingPid < "${lockDir}/pid" + if [[ "$existingPid" =~ ^[0-9]+$ ]] && kill -0 "$existingPid" 2> /dev/null; then + exit 0 + fi + fi + rm -rf "$lockDir" +fi + +if ! mkdir "$lockDir" 2> /dev/null; then + exit 0 +fi +printf "%s\n" "$$" > "${lockDir}/pid" +trap cleanup EXIT +trap stop_autolock INT TERM + +while : ; do + idleMilliseconds="$(xprintidle 2> /dev/null || echo 0)" + if [[ "$idleMilliseconds" =~ ^[0-9]+$ ]] && [[ "$idleMilliseconds" -ge $((autolockSeconds * 1000)) ]]; then + if ! screenlock_is_running && ! video_is_playing && ! focused_window_is_fullscreen; then + "${scriptDir}/screenlock.sh" + sleep "$pollSeconds" + fi + fi + + sleep "$pollSeconds" +done