diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..8da9495 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + CompilationDatabase: builddir/ # Search builddir/ directory for compile_commands.json diff --git a/.gitignore b/.gitignore index 565f5ae..e55a014 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,2 @@ -/build/ -/.cache/ -/gtklock -/gtklock.1 -compile_flags.txt -compile_commands.json -*-client-protocol.h -*-client-protocol.c -*.o +/builddir/ +/subprojects/*/ diff --git a/README.md b/README.md index 1145f5a..7042772 100644 --- a/README.md +++ b/README.md @@ -4,36 +4,38 @@ GTK-based lockscreen for Wayland. ![screenshot](https://user-images.githubusercontent.com/21199271/169707623-2ac5f02b-b6ed-461a-b9a3-5d96440843a2.png) ## About gtklock is a lockscreen based on [gtkgreet](https://git.sr.ht/~kennylevinsen/gtkgreet). -It uses the wlr-layer-shell and wlr-input-inhibitor Wayland protocols. +It uses the ext-session-lock Wayland protocol. Works on sway and other wlroots-based compositors. ℹ️ __For documentation, check out the [man page](https://man.voidlinux.org/gtklock) and [wiki](https://github.com/jovanlanik/gtklock/wiki).__ -Available on these repositories: + +## Installing gtklock from a repository +gtklock is available on these repositories: [![Packaging status](https://repology.org/badge/vertical-allrepos/gtklock.svg)](https://repology.org/project/gtklock/versions) -### Installing gtklock -- Arch: `# yay -S gtklock` (using [Yay](https://github.com/Jguer/yay)) + +- Arch: `# pacman -S gtklock` - Gentoo: `# emerge --ask gui-apps/gtklock` (in [GURU repository](https://wiki.gentoo.org/wiki/Project:GURU)) - Void: `# xbps-install gtklock` ❤️ __Please submit an installation command for your distro!__ ## Building from source ``` -$ make -# make install +$ meson setup builddir +$ ninja -C builddir +# meson install -C builddir ``` ### Dependencies -- GNU Make (build-time) +- Meson (build-time) - pkg-config (build-time) -- scdoc (build-time) +- scdoc (optional, build-time) - PAM -- wayland-client - gtk+3.0 -- gtk-layer-shell -### Install dependencies -- Arch: `# pacman -S gcc make pkgconf scdoc pam wayland gtk3 gtk-layer-shell` -- Fedora: `# dnf install gcc make pkgconf scdoc pam-devel wayland-devel gtk3-devel gtk-layer-shell-devel` -- Void: `# xbps-install gcc make pkgconf scdoc pam-devel wayland-devel gtk+3-devel gtk-layer-shell-devel` +- [gtk-session-lock](https://github.com/Cu3PO42/gtk-session-lock) +### Installing build dependencies +- Arch: `# pacman -S gcc meson pkgconf scdoc pam wayland gtk3 gtk-session-lock` +- Fedora: `# dnf install gcc meson pkgconf scdoc pam-devel wayland-devel gtk3-devel`, install gtk-session-lock manually +- Void: `# xbps-install gcc meson pkgconf scdoc pam-devel wayland-devel gtk+3-devel gtk-session-lock-devel` ❤️ __Please submit an dependency installation command for your distro!__ diff --git a/debian/changelog b/debian/changelog index af48d37..5434278 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,51 @@ +gtklock (4.0.0-1) UNRELEASED; urgency=medium + + [ Jovan Lanik ] + * add layout loading + * Wait for activation before detaching + * remove makefile + * change build to builddir + * split man build into separate file + * add header to meson files + * initial impl of ext-session-lock + * remove idle-inhibitor + * exec lock command after locking and creating surfaces + * update module support + * add monitor priority option + * hide cursor when idle + * fix regression + * update --lock-command + * suppress contex menu on entry + * update date feature + * add follow focus option + * add support for translation + * add serbian translation + * add gtk-session-lock lock signal handlers + * exec lock-command only after receiving gtk-session-lock lock signal + + [ Jianhua Lu ] + * Introduce meson build system + * add .clangd in order to clangd search compile_commands.json under build directory + + [ Zephyr Lykos ] + * build: fetch gtk-session-lock as a subproject when not present on system + * modules: handle multiarch systems' libdir + * auth: guard against race condition with messages + + [ Bhaskar Khoraja ] + * feat: date module + + [ Matthias Geiger ] + * Added german translation for gtklock + + [ Robin Candau ] + * Update installation instructions with the official Arch packages gtklock and all its build dependencies are now in the official Arch [extra] repository. This commit updates the installation instructions accordingly. + + [ Soumya Ranjan Patnaik ] + * chore: update package dependencies + + -- Soumya Ranjan Patnaik Fri, 20 Feb 2026 16:28:34 +0530 + gtklock (2.2.2) focal; urgency=medium [ Khosrow Moossavi ] diff --git a/debian/control b/debian/control index c3489e3..33c9d3b 100644 --- a/debian/control +++ b/debian/control @@ -5,10 +5,15 @@ Maintainer: Soumya Ranjan Patnaik Rules-Requires-Root: no Build-Depends: debhelper-compat (= 11), + meson, + cmake, build-essential, libgtk-layer-shell0, libgtk-layer-shell-dev, + libgtk-session-lock-dev, + pkg-config, libpam0g-dev, + libaudit-dev, libgtk-3-0, libgtk-3-dev, gir1.2-gtklayershell-0.1, @@ -24,7 +29,8 @@ Depends: ${misc:Depends}, libpam0g, libgtk-layer-shell0, + libgtk-session-lock0, libgtk-3-0 -Description: GTK-based lockscreen for Wayland. Works +Description: GTK-based lockscreen for Wayland. Works on sway and other wlroots-based compositors. diff --git a/debian/patches/01-suppress-unused-return-warning.patch b/debian/patches/01-suppress-unused-return-warning.patch new file mode 100644 index 0000000..96c5337 --- /dev/null +++ b/debian/patches/01-suppress-unused-return-warning.patch @@ -0,0 +1,28 @@ +Description: Suppress compiler warnings about ignoring return values of write and fopen in auth.c +Author: Soumya Ranjan Patnaik + +--- +Last-Update: 2026-02-20 + +--- gtklock-4.0.0.orig/src/auth.c ++++ gtklock-4.0.0/src/auth.c +@@ -36,8 +36,8 @@ char *auth_get_message(void) { + + static void send_msg(const char *msg, int fd) { + size_t len = strlen(msg); +- write(fd, &len, sizeof(size_t)); +- write(fd, msg, len); ++ (void)!write(fd, &len, sizeof(size_t)); ++ (void)!write(fd, msg, len); + } + + static int conversation( +@@ -133,7 +133,7 @@ enum pwcheck auth_pw_check(const char *s + else if(pid == 0) { + close(err_pipe[PIPE_PARENT]); + close(out_pipe[PIPE_PARENT]); +- freopen("/dev/null", "r", stdin); ++ (void)!freopen("/dev/null", "r", stdin); + auth_child(s, err_pipe, out_pipe); + } + close(err_pipe[PIPE_CHILD]); diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..786c89e --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +01-suppress-unused-return-warning.patch diff --git a/debian/source/options b/debian/source/options new file mode 100644 index 0000000..0ecb221 --- /dev/null +++ b/debian/source/options @@ -0,0 +1,2 @@ +# Ignore .github, and local changes to src/auth.c +extend-diff-ignore = "\.github" diff --git a/include/gtklock.h b/include/gtklock.h index 7e6af4b..64c2f72 100644 --- a/include/gtklock.h +++ b/include/gtklock.h @@ -1,16 +1,20 @@ // gtklock -// Copyright (c) 2022 Kenny Levinsen, Jovan Lanik +// Copyright (c) 2022 Kenny Levinsen, Jovan Lanik, Bhaskar Khoraja // gtklock application #pragma once #include +#include struct Window; struct GtkLock { GtkApplication *app; + GtkSessionLockLock *lock; + pid_t parent; + GArray *windows; GArray *messages; GArray *errors; @@ -19,16 +23,20 @@ struct GtkLock { gboolean hidden; guint idle_timeout; - guint draw_clock_source; + guint draw_time_source; guint idle_hide_source; - gboolean use_layer_shell; - gboolean use_input_inhibit; + gboolean follow_focus; gboolean use_idle_hide; char *time; + char *date; char *time_format; + char *date_format; char *config_path; + char *layout_path; + char *lock_command; + char *unlock_command; GArray *modules; }; @@ -36,10 +44,9 @@ struct GtkLock { void gtklock_remove_window(struct GtkLock *gtklock, struct Window *win); void gtklock_focus_window(struct GtkLock *gtklock, struct Window *win); void gtklock_update_clocks(struct GtkLock *gtklock); +void gtklock_update_dates(struct GtkLock *gtklock); void gtklock_idle_hide(struct GtkLock *gtklock); void gtklock_idle_show(struct GtkLock *gtklock); -struct GtkLock *create_gtklock(void); void gtklock_activate(struct GtkLock *gtklock); void gtklock_shutdown(struct GtkLock *gtklock); -void gtklock_destroy(struct GtkLock *gtklock); diff --git a/include/input-inhibitor.h b/include/input-inhibitor.h deleted file mode 100644 index bf52086..0000000 --- a/include/input-inhibitor.h +++ /dev/null @@ -1,10 +0,0 @@ -// gtklock -// Copyright (c) 2022 Jovan Lanik - -// wlr-input-inhibitor - -#pragma once - -void input_inhibitor_get(void); -void input_inhibitor_destroy(void); - diff --git a/include/module.h b/include/module.h index edebf94..283e098 100644 --- a/include/module.h +++ b/include/module.h @@ -11,6 +11,7 @@ GModule *module_load(const char *name); void module_on_activation(struct GtkLock *gtklock); +void module_on_locked(struct GtkLock *gtklock); void module_on_output_change(struct GtkLock *gtklock); void module_on_focus_change(struct GtkLock *gtklock, struct Window *win, struct Window *old); void module_on_idle_hide(struct GtkLock *gtklock); diff --git a/include/window.h b/include/window.h index b6cf2e6..693850a 100644 --- a/include/window.h +++ b/include/window.h @@ -1,5 +1,5 @@ // gtklock -// Copyright (c) 2022 Kenny Levinsen, Jovan Lanik +// Copyright (c) 2022 Kenny Levinsen, Jovan Lanik, Bhaskar Khoraja // Window functions @@ -17,22 +17,27 @@ struct Window { GtkWidget *body_grid; GtkWidget *input_label; GtkWidget *input_field; + GtkWidget *message_revealer; + GtkWidget *message_scrolled_window; GtkWidget *message_box; GtkWidget *unlock_button; GtkWidget *error_label; GtkWidget *warning_label; + GtkWidget *info_box; + GtkWidget *time_box; GtkWidget *clock_label; - - gulong enter_notify_handler; + GtkWidget *date_label; void *module_data[]; }; struct Window *window_by_widget(GtkWidget *window); struct Window *window_by_monitor(GdkMonitor *monitor); +struct Window *window_last_active(void); struct Window *create_window(GdkMonitor *monitor); void window_idle_hide(struct Window *win); void window_idle_show(struct Window *win); void window_update_clock(struct Window *ctx); +void window_update_date(struct Window *ctx); void window_swap_focus(struct Window *win, struct Window *old); diff --git a/makefile b/makefile deleted file mode 100644 index 8bea91a..0000000 --- a/makefile +++ /dev/null @@ -1,93 +0,0 @@ -# gtklock -# Copyright (c) 2022 Jovan Lanik - -# Makefile - -NAME := gtklock -MAJOR_VERSION := 2 -MINOR_VERSION := 1 -MICRO_VERSION := 0 - -PREFIX?= /usr -SYSCONFDIR = $(PREFIX)/etc - -ifeq '$(shell uname)' 'Linux' - SYSCONFDIR = /etc -endif - -INSTALL = install - -LIBS := wayland-client gtk+-wayland-3.0 gtk-layer-shell-0 gmodule-export-2.0 - -PAMFLAGS := $(shell pkg-config --cflags pam) -PAMLIBS := $(shell pkg-config --libs pam) -ifneq '$(.SHELLSTATUS)' '0' - PAMLIBS := -lpam -endif - -PKGFLAGS := $(shell pkg-config --cflags $(LIBS)) -ifneq '$(.SHELLSTATUS)' '0' - $(error pkg-config failed) -endif - -PKGLIBS := $(shell pkg-config --libs $(LIBS)) -ifneq '$(.SHELLSTATUS)' '0' - $(error pkg-config failed) -endif - -CFLAGS += -std=c11 -Iinclude -DPREFIX=$(PREFIX) $(PAMFLAGS) $(PKGFLAGS) -CFLAGS += -DMAJOR_VERSION=$(MAJOR_VERSION) -DMINOR_VERSION=$(MINOR_VERSION) -DMICRO_VERSION=$(MICRO_VERSION) -LDLIBS += -Wl,--export-dynamic $(PAMLIBS) $(PKGLIBS) - -OBJ = wlr-input-inhibitor-unstable-v1-client-protocol.o -OBJ += $(patsubst %.c, %.o, $(wildcard src/*.c)) -OBJ += $(patsubst res/%.gresource.xml, %.gresource.o, $(wildcard res/*.gresource.xml)) - -TRASH = $(OBJ) $(NAME) $(NAME).1 -TRASH += $(wildcard *.gresource.c) $(wildcard *.gresource.h) -TRASH += $(wildcard *-client-protocol.c) $(wildcard include/*-client-protocol.h) - -VPATH = src -.PHONY: all clean install install-bin install-data uninstall - -all: $(NAME) $(NAME).1 - -clean: - @rm $(TRASH) | true - -install-bin: - $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin - $(INSTALL) $(NAME) $(DESTDIR)$(PREFIX)/bin/$(NAME) - -install-data: - $(INSTALL) -d $(DESTDIR)$(SYSCONFDIR)/pam.d - $(INSTALL) -m644 pam/$(NAME) $(DESTDIR)$(SYSCONFDIR)/pam.d/$(NAME) - $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/man/man1 - $(INSTALL) -m644 $(NAME).1 $(DESTDIR)$(PREFIX)/share/man/man1/$(NAME).1 - -install: install-bin install-data - -uninstall: - rm -f $(DESTDIR)$(PREFIX)/bin/$(NAME) - rm -f $(DESTDIR)$(SYSCONFDIR)/pam.d/$(NAME) - rm -r $(DESTDIR)$(PREFIX)/share/man/man1/$(NAME).1 - -$(NAME): $(OBJ) - $(LINK.c) $^ $(LDLIBS) -o $@ - -%.gresource.c: res/%.gresource.xml - glib-compile-resources --generate-source $< --target=$@ --sourcedir=res - -%.gresource.h: res/%.gresource.xml - glib-compile-resources --generate-header $< --target=$@ --sourcedir=res - -%-client-protocol.c: wayland/%.xml - wayland-scanner private-code $< $@ - -include/%-client-protocol.h: wayland/%.xml - wayland-scanner client-header $< $@ - -src/input-inhibitor.c: include/wlr-input-inhibitor-unstable-v1-client-protocol.h - -%.1: man/%.1.scd - scdoc < $< > $@ diff --git a/man/gtklock.1.scd b/man/gtklock.1.scd index c773fde..5673a0e 100644 --- a/man/gtklock.1.scd +++ b/man/gtklock.1.scd @@ -39,6 +39,9 @@ All available options are: *-s, --style* Load CSS style file. +*-x, --layout* + Load XML layout file. + *-m, --modules* Load gtklock modules. Module name can be an absolute path, relative path (starting with ./ or ../) or name in system directory (such as @@ -51,6 +54,13 @@ All available options are: *-t, --time-format* Set time format. See date(1). +*-d, --date-format* + Set date format. See date(1). + +*-f, --follow-focus* + Follow focus between monitors. May not work for all compositors, + recommended for hyprland. + *-H, --idle-hide* Hide input form when idle. @@ -61,11 +71,14 @@ All available options are: Start with input form hidden. *-L, --lock-command* - Command to execute before locking. Command is executed asynchronously. + Command to execute after locking. Command is executed asynchronously. *-U, --unlock-command* Command to execute after unlocking. Command is executed asynchronously. +*-M, --monitor-priority* + Set monitor focus priority. Option can be specified multiple times. + # EXAMPLE Example config: diff --git a/man/meson.build b/man/meson.build new file mode 100644 index 0000000..40d02db --- /dev/null +++ b/man/meson.build @@ -0,0 +1,27 @@ +# gtklock +# Copyright (c) 2023 Jianhua Lu, Jovan Lanik + +scdoc = find_program('scdoc', required: get_option('man-pages')) + +if scdoc.found() + mandir = get_option('mandir') + man_files = [ + 'gtklock.1.scd', + ] + foreach filename : man_files + topic = filename.split('.')[-3].split('/')[-1] + section = filename.split('.')[-2] + output = '@0@.@1@'.format(topic, section) + + custom_target( + output, + input: filename, + output: output, + command: scdoc, + feed: true, + capture: true, + install: true, + install_dir: '@0@/man@1@'.format(mandir, section) + ) + endforeach +endif diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..c64ac52 --- /dev/null +++ b/meson.build @@ -0,0 +1,61 @@ +# gtklock +# Copyright (c) 2023 Jianhua Lu, Jovan Lanik, Zephyr Lykos + +project( + 'gtklock', 'c', + version : '4.0.0', + license : 'GPLv3', + default_options : ['c_std=c11'] +) + +version = meson.project_version().split('.') +major_version = version[0] +minor_version = version[1] +micro_version = version[2] + +gtk = dependency('gtk+-3.0') +gtk_session_lock = dependency('gtk-session-lock-0') +gmodule_export = dependency('gmodule-export-2.0') +pam = dependency('pam') + +dependencies = [ + gtk, + gtk_session_lock, + gmodule_export, + pam, +] + +subdir('po') +subdir('man') +subdir('res') +subdir('src') + +gtklock_set = [ + gtklock_sources, + ui_resources, +] + +if import('fs').is_absolute(get_option('libdir')) + libdir = get_option('libdir') +else + libdir = get_option('prefix') / get_option('libdir') +endif + +executable( + meson.project_name(), + gtklock_set, + include_directories : 'include', + dependencies : dependencies, + c_args : [ + '-DLIBDIR=' + libdir, + '-DMAJOR_VERSION=' + major_version, + '-DMINOR_VERSION=' + minor_version, + '-DMICRO_VERSION=' + micro_version, + ], + install : true +) + +install_data( + 'pam/gtklock', + install_dir : get_option('sysconfdir') / 'pam.d' +) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..e40a23d --- /dev/null +++ b/meson_options.txt @@ -0,0 +1 @@ +option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') diff --git a/po/LINGUAS b/po/LINGUAS new file mode 100644 index 0000000..38e3cb1 --- /dev/null +++ b/po/LINGUAS @@ -0,0 +1,4 @@ +cs +de +sr +sr@latin diff --git a/po/POTFILES b/po/POTFILES new file mode 100644 index 0000000..4e4fcef --- /dev/null +++ b/po/POTFILES @@ -0,0 +1,2 @@ +src/window.c +res/gtklock.ui diff --git a/po/cs.po b/po/cs.po new file mode 100644 index 0000000..8bca260 --- /dev/null +++ b/po/cs.po @@ -0,0 +1,34 @@ +# Czech translations for gtklock package. +# Copyright (C) 2024 THE gtklock'S COPYRIGHT HOLDER +# This file is distributed under the same license as the gtklock package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: gtklock\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-07-07 13:48+0200\n" +"PO-Revision-Date: 2024-06-27 22:18+0200\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: cs\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + +#: src/window.c:129 +msgid "Login failed" +msgstr "Přihlášení selhalo" + +#: src/window.c:264 +msgid "Caps Lock is on" +msgstr "Je zapnutý Caps Lock" + +#: res/gtklock.ui:35 +msgid "Password:" +msgstr "Heslo:" + +#: res/gtklock.ui:86 +msgid "Unlock" +msgstr "Odemknout" diff --git a/po/de.po b/po/de.po new file mode 100644 index 0000000..e09dc74 --- /dev/null +++ b/po/de.po @@ -0,0 +1,35 @@ +# German translations for gtklock +# Copyright (C) 2024 gtklocks copyright holder +# This file is distributed under the same license as the gtklock package. +# Matthias Geiger , 2024 +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: gtklock\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-07-07 13:48+0200\n" +"PO-Revision-Date: 2024-07-06 22:08+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: de_DE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.4.4\n" + +#: src/window.c:129 +msgid "Login failed" +msgstr "Anmeldung fehlgeschlagen" + +#: src/window.c:264 +msgid "Caps Lock is on" +msgstr "Feststelltaste aktiviert" + +#: res/gtklock.ui:35 +msgid "Password:" +msgstr "Passwort:" + +#: res/gtklock.ui:86 +msgid "Unlock" +msgstr "Entsperren" diff --git a/po/gtklock.pot b/po/gtklock.pot new file mode 100644 index 0000000..38d07cf --- /dev/null +++ b/po/gtklock.pot @@ -0,0 +1,34 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the gtklock package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: gtklock\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-07-07 13:48+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/window.c:129 +msgid "Login failed" +msgstr "" + +#: src/window.c:264 +msgid "Caps Lock is on" +msgstr "" + +#: res/gtklock.ui:35 +msgid "Password:" +msgstr "" + +#: res/gtklock.ui:86 +msgid "Unlock" +msgstr "" diff --git a/po/meson.build b/po/meson.build new file mode 100644 index 0000000..8a712bc --- /dev/null +++ b/po/meson.build @@ -0,0 +1,21 @@ +# gtklock +# Copyright (c) 2023 Kenny Levinsen, Jovan Lanik + +if import('fs').is_absolute(get_option('localedir')) + localedir = get_option('localedir') +else + localedir = get_option('prefix') / get_option('localedir') +endif + +add_project_arguments( + '-DGETTEXT_PACKAGE="' + meson.project_name() + '"', + '-DLOCALEDIR="' + localedir + '"', + language:'c' +) + +i18n = import('i18n') + +i18n.gettext(meson.project_name(), + args: '--directory=' + meson.source_root(), + preset: 'glib' +) diff --git a/po/sr.po b/po/sr.po new file mode 100644 index 0000000..81a10d2 --- /dev/null +++ b/po/sr.po @@ -0,0 +1,35 @@ +# Serbian translations for gtklock package. +# Copyright (C) 2024 THE gtklock'S COPYRIGHT HOLDER +# This file is distributed under the same license as the gtklock package. +# Jovan Lanik , 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: gtklock\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-07-07 13:48+0200\n" +"PO-Revision-Date: 2024-07-07 13:02+0200\n" +"Last-Translator: Jovan Lanik \n" +"Language-Team: Serbian <(nothing)>\n" +"Language: sr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: src/window.c:129 +msgid "Login failed" +msgstr "Пријава није успела" + +#: src/window.c:264 +msgid "Caps Lock is on" +msgstr "Укључен је Цапс Лоцк" + +#: res/gtklock.ui:35 +msgid "Password:" +msgstr "Лозинка:" + +#: res/gtklock.ui:86 +msgid "Unlock" +msgstr "Откључај" diff --git a/po/sr@latin.po b/po/sr@latin.po new file mode 100644 index 0000000..8a5fe1b --- /dev/null +++ b/po/sr@latin.po @@ -0,0 +1,35 @@ +# Serbian translations for gtklock package. +# Copyright (C) 2024 THE gtklock'S COPYRIGHT HOLDER +# This file is distributed under the same license as the gtklock package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: gtklock\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-07-07 13:48+0200\n" +"PO-Revision-Date: 2024-07-07 13:48+0200\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: sr@latin\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: src/window.c:129 +msgid "Login failed" +msgstr "Prijava nije uspela" + +#: src/window.c:264 +msgid "Caps Lock is on" +msgstr "Uključen je Caps Lock" + +#: res/gtklock.ui:35 +msgid "Password:" +msgstr "Lozinka:" + +#: res/gtklock.ui:86 +msgid "Unlock" +msgstr "Otključaj" diff --git a/res/gtklock.ui b/res/gtklock.ui index 0de25f8..4a2ca89 100644 --- a/res/gtklock.ui +++ b/res/gtklock.ui @@ -6,18 +6,38 @@ center center vertical - 5 + 10 - - clock-label + + info-box center - 10 + vertical + 5 + + + time-box + center + vertical + + + clock-label + center + + + + + date-label + center + + + + none - 1 + 0 5 @@ -25,7 +45,7 @@ input-label - Password: + Password: 0 @@ -50,8 +70,26 @@ - + + none 1 + + + never + 256 + 1 + + + + + vertical + 1 + + + + + + 1 @@ -76,7 +114,7 @@ unlock-button - Unlock + Unlock diff --git a/res/meson.build b/res/meson.build new file mode 100644 index 0000000..f1d6b29 --- /dev/null +++ b/res/meson.build @@ -0,0 +1,9 @@ +# gtklock +# Copyright (c) 2023 Jianhua Lu, Jovan Lanik + +gnome = import('gnome') + +ui_resources = gnome.compile_resources( + 'ui-resources', 'ui.gresource.xml', + c_name : 'ui', +) diff --git a/src/auth.c b/src/auth.c index b3fe29a..53e6628 100644 --- a/src/auth.c +++ b/src/auth.c @@ -1,5 +1,5 @@ // gtklock -// Copyright (c) 2022 Jovan Lanik +// Copyright (c) 2022 Jovan Lanik, Zephyr Lykos // PAM Authentication @@ -36,8 +36,8 @@ char *auth_get_message(void) { static void send_msg(const char *msg, int fd) { size_t len = strlen(msg); - (void)!write(fd, &len, sizeof(size_t)); - (void)!write(fd, msg, len); + write(fd, &len, sizeof(size_t)); + write(fd, msg, len); } static int conversation( @@ -133,7 +133,7 @@ enum pwcheck auth_pw_check(const char *s) { else if(pid == 0) { close(err_pipe[PIPE_PARENT]); close(out_pipe[PIPE_PARENT]); - (void)!freopen("/dev/null", "r", stdin); + freopen("/dev/null", "r", stdin); auth_child(s, err_pipe, out_pipe); } close(err_pipe[PIPE_CHILD]); @@ -148,15 +148,15 @@ enum pwcheck auth_pw_check(const char *s) { size_t len; ssize_t nread; nread = read(err_pipe[PIPE_PARENT], &len, sizeof(size_t)); - if(nread > 0) { - error_string = malloc(len+1); + if(nread > 0 && len <= PAM_MAX_MSG_SIZE) { + error_string = malloc(PAM_MAX_MSG_SIZE); nread = read(err_pipe[PIPE_PARENT], error_string, len); error_string[nread] = '\0'; return PW_ERROR; } nread = read(out_pipe[PIPE_PARENT], &len, sizeof(size_t)); - if(nread > 0) { - message_string = malloc(len+1); + if(nread > 0 && len <= PAM_MAX_MSG_SIZE) { + message_string = malloc(PAM_MAX_MSG_SIZE); nread = read(out_pipe[PIPE_PARENT], message_string, len); message_string[nread] = '\0'; return PW_MESSAGE; diff --git a/src/gtklock.c b/src/gtklock.c index 745ca2a..512b5ca 100644 --- a/src/gtklock.c +++ b/src/gtklock.c @@ -1,21 +1,21 @@ // gtklock -// Copyright (c) 2022 Kenny Levinsen, Jovan Lanik +// Copyright (c) 2022 Kenny Levinsen, Jovan Lanik, Bhaskar Khoraja // gtklock application +#include #include #include "util.h" #include "window.h" #include "gtklock.h" #include "module.h" -#include "input-inhibitor.h" void gtklock_remove_window(struct GtkLock *gtklock, struct Window *win) { for(guint idx = 0; idx < gtklock->windows->len; idx++) { struct Window *ctx = g_array_index(gtklock->windows, struct Window *, idx); if(ctx == win) { - g_array_remove_index_fast(gtklock->windows, idx); + g_array_remove_index(gtklock->windows, idx); g_free(ctx); return; } @@ -42,9 +42,23 @@ void gtklock_update_clocks(struct GtkLock *gtklock) { } } -static int gtklock_update_clocks_handler(gpointer data) { +void gtklock_update_dates(struct GtkLock *gtklock) { + GDateTime *date = g_date_time_new_now_local(); + if(date == NULL) return; + if(gtklock->date) g_free(gtklock->date); + gtklock->date = g_date_time_format(date, gtklock->date_format ? gtklock->date_format : "%a, %b %d"); + g_date_time_unref(date); + + for(guint idx = 0; idx < gtklock->windows->len; idx++) { + struct Window *ctx = g_array_index(gtklock->windows, struct Window *, idx); + window_update_date(ctx); + } +} + +static int gtklock_update_time_handler(gpointer data) { struct GtkLock *gtklock = (struct GtkLock *)data; gtklock_update_clocks(gtklock); + gtklock_update_dates(gtklock); return G_SOURCE_CONTINUE; } @@ -82,46 +96,61 @@ void gtklock_idle_show(struct GtkLock *gtklock) { gtklock->idle_hide_source = g_timeout_add_seconds(gtklock->idle_timeout, G_SOURCE_FUNC(gtklock_idle_handler), gtklock); } -#if GLIB_CHECK_VERSION(2, 74, 0) - #define GTKLOCK_FLAGS G_APPLICATION_DEFAULT_FLAGS -#else - #define GTKLOCK_FLAGS G_APPLICATION_FLAGS_NONE -#endif - -struct GtkLock* create_gtklock(void) { - struct GtkLock *gtklock = g_malloc0(sizeof(struct GtkLock)); - if(!gtklock) report_error_and_exit("Failed allocation"); - gtklock->app = gtk_application_new(NULL, GTKLOCK_FLAGS); - gtklock->windows = g_array_new(FALSE, TRUE, sizeof(struct Window *)); - gtklock->messages = g_array_new(FALSE, TRUE, sizeof(char *)); - gtklock->errors = g_array_new(FALSE, TRUE, sizeof(char *)); - return gtklock; +static void exec_command(const gchar *command) { + GError *err = NULL; + g_spawn_command_line_async(command, &err); + if(err != NULL) { + g_warning("Executing `%s` failed: %s", command, err->message); + g_error_free(err); + } +} + +static void locked(GtkSessionLockLock *lock, void *data) { + struct GtkLock *gtklock = (struct GtkLock *)data; + module_on_locked(gtklock); + if(gtklock->parent > 0) kill(gtklock->parent, SIGUSR1); + if(gtklock->lock_command) exec_command(gtklock->lock_command); + return; +} + +static void finished(GtkSessionLockLock *lock, void *data) { + gtk_session_lock_lock_destroy(lock); + report_error_and_exit("Failed to lock session"); } void gtklock_activate(struct GtkLock *gtklock) { - gtklock->draw_clock_source = g_timeout_add(1000, G_SOURCE_FUNC(gtklock_update_clocks_handler), gtklock); + g_application_hold(G_APPLICATION(gtklock->app)); + + if(!gtk_session_lock_is_supported()) + report_error_and_exit("Your compositor doesn't support ext-session-lock"); + gtklock->lock = gtk_session_lock_prepare_lock(); + + g_signal_connect(gtklock->lock, "locked", G_CALLBACK(locked), gtklock); + g_signal_connect(gtklock->lock, "finished", G_CALLBACK(finished), NULL); + + gtk_session_lock_lock_lock(gtklock->lock); + + + gtklock->draw_time_source = g_timeout_add(1000, G_SOURCE_FUNC(gtklock_update_time_handler), gtklock); gtklock_update_clocks(gtklock); + gtklock_update_dates(gtklock); if(gtklock->use_idle_hide) gtklock->idle_hide_source = g_timeout_add_seconds(gtklock->idle_timeout, G_SOURCE_FUNC(gtklock_idle_handler), gtklock); - if(gtklock->use_layer_shell) g_application_hold(G_APPLICATION(gtklock->app)); - if(gtklock->use_input_inhibit) input_inhibitor_get(); } void gtklock_shutdown(struct GtkLock *gtklock) { - if(gtklock->draw_clock_source > 0) { - g_source_remove(gtklock->draw_clock_source); - gtklock->draw_clock_source = 0; + if(gtklock->draw_time_source > 0) { + g_source_remove(gtklock->draw_time_source); + gtklock->draw_time_source = 0; } if(gtklock->idle_hide_source > 0) { g_source_remove(gtklock->idle_hide_source); gtklock->idle_hide_source = 0; } - if(gtklock->use_input_inhibit) input_inhibitor_destroy(); -} -void gtklock_destroy(struct GtkLock *gtklock) { - g_object_unref(gtklock->app); - g_array_unref(gtklock->windows); - g_free(gtklock); + gtk_session_lock_lock_unlock_and_destroy(gtklock->lock); + gdk_display_sync(gdk_display_get_default()); + + if(gtklock->unlock_command) exec_command(gtklock->unlock_command); } diff --git a/src/input-inhibitor.c b/src/input-inhibitor.c deleted file mode 100644 index fa2423d..0000000 --- a/src/input-inhibitor.c +++ /dev/null @@ -1,87 +0,0 @@ -// gtklock -// Copyright (c) 2022 Sophie Winter, Jovan Lanik - -// wlr-input-inhibitor - -#include - -#include "util.h" -#include "wlr-input-inhibitor-unstable-v1-client-protocol.h" - -static struct wl_display *display = NULL; -static struct wl_registry *registry_global = NULL; -static struct zwlr_input_inhibit_manager_v1 *input_inhibit_manager_global = NULL; - -static void registry_handle_global( - void *data, - struct wl_registry *registry, - uint32_t id, - const char *interface, - uint32_t version -) { - // pull out needed globals - if(strcmp(interface, zwlr_input_inhibit_manager_v1_interface.name) != 0) return; - input_inhibit_manager_global = wl_registry_bind( - registry, - id, - &zwlr_input_inhibit_manager_v1_interface, - 1 - ); -} - -static void registry_handle_global_remove(void *_data, struct wl_registry *_registry, uint32_t _id) { } - -static const struct wl_registry_listener registry_listener = { - .global = registry_handle_global, - .global_remove = registry_handle_global_remove, -}; - -void input_inhibitor_get(void) { - GdkDisplay *gdk_display = gdk_display_get_default(); - if(gdk_display == NULL) return; - if(GDK_IS_WAYLAND_DISPLAY(gdk_display) == FALSE) return; - - display = gdk_wayland_display_get_wl_display(gdk_display); - registry_global = wl_display_get_registry(display); - wl_registry_add_listener(registry_global, ®istry_listener, NULL); - wl_display_roundtrip(display); - - if(!input_inhibit_manager_global) - report_error_and_exit("Your compositor doesn't support wlr-input-inhibitor"); - - zwlr_input_inhibit_manager_v1_get_inhibitor(input_inhibit_manager_global); - if(wl_display_roundtrip(display) == -1 && input_inhibit_manager_global) - report_error_and_exit("Failed to inhibit input. Is another lockscreen already running?"); -} - -void input_inhibitor_destroy(void) { - zwlr_input_inhibit_manager_v1_destroy(input_inhibit_manager_global); - wl_display_roundtrip(display); -} - -/* - -MIT License - -Copyright (c) 2020 Sophie Winter - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..6531739 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,13 @@ +# gtklock +# Copyright (c) 2023 Jianhua Lu, Jovan Lanik + +gtklock_sources = files([ + 'auth.c', + 'config.c', + 'gtklock.c', + 'module.c', + 'source.c', + 'util.c', + 'window.c', + 'xdg.c', +]) diff --git a/src/module.c b/src/module.c index 5810eee..da0c710 100644 --- a/src/module.c +++ b/src/module.c @@ -1,14 +1,23 @@ // gtklock -// Copyright (c) 2022 Jovan Lanik +// Copyright (c) 2022 Jovan Lanik, Zephyr Lykos // Module support #include "util.h" #include "module.h" +#ifndef LIBDIR + +#warning LIBDIR not defined. + #ifndef PREFIX #warning PREFIX not defined. -#define PREFIX /usr/local +#define LIBDIR /usr/local/lib +#else +#warning PREFIX is soft-deprecated. Define LIBDIR instead. +#define LIBDIR PREFIX/lib +#endif + #endif #ifndef MAJOR_VERSION @@ -36,7 +45,7 @@ GModule *module_load(const char *name) { if(g_file_test(name, G_FILE_TEST_IS_REGULAR)) path = g_strdup(name); else { g_free(path); - path = g_build_path("/", STR(PREFIX)"/lib/gtklock", name, NULL); + path = g_build_path("/", STR(LIBDIR) "/gtklock", name, NULL); } } @@ -60,12 +69,11 @@ GModule *module_load(const char *name) { g_warning("%s: module has mismatched minor version (%u), may be incompatible", name, *minor); } else { - const gchar *gtklock_version = "v" STR(MAJOR_VERSION) "." STR(MINOR_VERSION) "." STR(MICRO_VERSION); const gchar *module_version = NULL; - if(g_module_symbol(module, "module_version", (gpointer *)&module_version)) { - if(g_strcmp0(gtklock_version, module_version) != 0) - g_warning("%s: module has mismatched version, may be incompatible", name); - } else g_warning("%s: module has no version info, may be incompatible", name); + if(g_module_symbol(module, "module_version", (gpointer *)&module_version)) + report_error_and_exit("%s: module has legacy version info (%s), is incompatible", name, module_version); + else + report_error_and_exit("%s: module has no version info, is incompatible", name); } return module; @@ -79,6 +87,14 @@ void module_on_activation(struct GtkLock *gtklock) { } } +void module_on_locked(struct GtkLock *gtklock) { + for(guint idx = 0; idx < gtklock->modules->len; idx++) { + void (*fn)(struct GtkLock *) = NULL; + GModule *module = g_array_index(gtklock->modules, GModule *, idx); + if(g_module_symbol(module, "on_locked", (gpointer *)&fn)) fn(gtklock); + } +} + void module_on_output_change(struct GtkLock *gtklock) { for(guint idx = 0; idx < gtklock->modules->len; idx++) { void (*fn)(struct GtkLock *) = NULL; diff --git a/src/source.c b/src/source.c index ee3cf90..cceab5e 100644 --- a/src/source.c +++ b/src/source.c @@ -1,16 +1,16 @@ // gtklock -// Copyright (c) 2022 Kenny Levinsen, Jovan Lanik +// Copyright (c) 2022 Kenny Levinsen, Jovan Lanik, Bhaskar Khoraja #define _POSIX_C_SOURCE 200809L -#include #include #include +#include #include +#include #include #include "util.h" -#include "auth.h" #include "window.h" #include "gtklock.h" #include "config.h" @@ -37,8 +37,7 @@ struct GtkLock *gtklock = NULL; static gboolean show_version = FALSE; static gboolean should_daemonize = FALSE; -static gboolean no_layer_shell = FALSE; -static gboolean no_input_inhibit = FALSE; +static gboolean follow_focus = FALSE; static gboolean idle_hide = FALSE; static gboolean start_hidden = FALSE; @@ -47,11 +46,14 @@ static gint idle_timeout = 15; static gchar *gtk_theme = NULL; static gchar *config_path = NULL; static gchar *style_path = NULL; +static gchar *layout_path = NULL; static gchar **module_path = NULL; static gchar *background_path = NULL; static gchar *time_format = NULL; +static gchar *date_format = NULL; static gchar *lock_command = NULL; static gchar *unlock_command = NULL; +static gchar **monitor_priority = NULL; static GOptionEntry main_entries[] = { { "version", 'v', 0, G_OPTION_ARG_NONE, &show_version, "Show version", NULL }, @@ -63,92 +65,121 @@ static GOptionEntry main_entries[] = { static GOptionEntry config_entries[] = { { "gtk-theme", 'g', 0, G_OPTION_ARG_STRING, >k_theme, "Set GTK theme", NULL }, { "style", 's', 0, G_OPTION_ARG_FILENAME, &style_path, "Load CSS style file", NULL }, + { "layout", 'x', 0, G_OPTION_ARG_FILENAME, &layout_path, "Load XML layout file", NULL }, { "modules", 'm', 0, G_OPTION_ARG_FILENAME_ARRAY, &module_path, "Load gtklock modules", NULL }, { "background", 'b', 0, G_OPTION_ARG_FILENAME, &background_path, "Load background", NULL }, { "time-format", 't', 0, G_OPTION_ARG_STRING, &time_format, "Set time format", NULL }, + { "date-format", 'D', 0, G_OPTION_ARG_STRING, &date_format, "Set date format", NULL }, + { "follow-focus", 'f', 0, G_OPTION_ARG_NONE, &follow_focus, "Follow focus between monitors", NULL }, { "idle-hide", 'H', 0, G_OPTION_ARG_NONE, &idle_hide, "Hide form when idle", NULL }, { "idle-timeout", 'T', 0, G_OPTION_ARG_INT, &idle_timeout, "Idle timeout in seconds", NULL }, { "start-hidden", 'S', 0, G_OPTION_ARG_NONE, &start_hidden, "Start with hidden form", NULL }, - { "lock-command", 'L', 0, G_OPTION_ARG_STRING, &lock_command, "Command to execute before locking", NULL }, + { "lock-command", 'L', 0, G_OPTION_ARG_STRING, &lock_command, "Command to execute after locking", NULL }, { "unlock-command", 'U', 0, G_OPTION_ARG_STRING, &unlock_command, "Command to execute after unlocking", NULL }, + { "monitor-priority", 'M', 0, G_OPTION_ARG_STRING_ARRAY, &monitor_priority, "Set monitor focus priority", NULL }, { NULL }, }; static GOptionEntry debug_entries[] = { - { "no-layer-shell", 'l', 0, G_OPTION_ARG_NONE, &no_layer_shell, "Don't use wlr-layer-shell", NULL }, - { "no-input-inhibit", 'i', 0, G_OPTION_ARG_NONE, &no_input_inhibit, "Don't use wlr-input-inhibitor", NULL }, { NULL }, }; static pid_t parent = -2; -static void reload_outputs(void) { - GdkDisplay *display = gdk_display_get_default(); - - // Make note of all existing windows - GArray *dead_windows = g_array_new(FALSE, TRUE, sizeof(struct Window*)); - for(guint idx = 0; idx < gtklock->windows->len; idx++) { - struct Window *ctx = g_array_index(gtklock->windows, struct Window*, idx); - g_array_append_val(dead_windows, ctx); - } - - // Go through all monitors - struct Window *new = NULL; - for(int i = 0; i < gdk_display_get_n_monitors(display); i++) { - GdkMonitor *monitor = gdk_display_get_monitor(display, i); - struct Window *w = window_by_monitor(monitor); - if(w != NULL) { - // We already have this monitor, remove from dead_windows list - for(guint idx = 0; idx < dead_windows->len; idx++) { - if(w == g_array_index(dead_windows, struct Window*, idx)) { - g_array_remove_index_fast(dead_windows, idx); - break; - } - } - } else { - w = create_window(monitor); - gtklock_focus_window(gtklock, w); - } - new = w; - } +static void monitors_added(GdkDisplay *display, GdkMonitor *monitor, gpointer user_data) { + struct Window *w = NULL; + if(window_by_monitor(monitor) == NULL) w = create_window(monitor); + if(w == g_array_index(gtklock->windows, struct Window*, 0)) gtklock_focus_window(gtklock, w); + module_on_output_change(gtklock); +} - // Remove all windows left behind - for(guint idx = 0; idx < dead_windows->len; idx++) { - struct Window *w = g_array_index(dead_windows, struct Window*, idx); +static void monitors_removed(GdkDisplay *display, GdkMonitor *monitor, gpointer user_data) { + struct Window *w = window_by_monitor(monitor); + if(w != NULL) { if(gtklock->focused_window == w) { + struct Window *any_window = g_array_index(gtklock->windows, struct Window*, 0); + if(any_window == w) any_window = g_array_index(gtklock->windows, struct Window*, 1); + gtklock->focused_window = NULL; - if(new) window_swap_focus(new, w); + if(any_window) window_swap_focus(any_window, w); } + gtk_session_lock_unmap_lock_window(GTK_WINDOW(w->window)); gtk_widget_destroy(w->window); } - - g_array_unref(dead_windows); module_on_output_change(gtklock); } -static void monitors_changed(GdkDisplay *display, GdkMonitor *monitor) { - reload_outputs(); +static gboolean find_priority_monitor(char *name) { + if(monitor_priority) { + for(guint i = 0; monitor_priority[i] != NULL; ++i) + if(g_strcmp0(name, monitor_priority[i]) == 0) return TRUE; + } + return FALSE; } -static gboolean setup_layer_shell(void) { - if(!gtklock->use_layer_shell) return FALSE; - - reload_outputs(); - - GdkDisplay *display = gdk_display_get_default(); - g_signal_connect(display, "monitor-added", G_CALLBACK(monitors_changed), NULL); - g_signal_connect(display, "monitor-removed", G_CALLBACK(monitors_changed), NULL); - return TRUE; +static gint compare_priority_monitors(gconstpointer first_ptr, gconstpointer second_ptr, gpointer user_data) { + const void *first = *(const void **)first_ptr; + const void *second = *(const void **)second_ptr; + if(first != second && monitor_priority) { + GHashTable *names = user_data; + char *first_name = g_hash_table_lookup(names, first); + char *second_name = g_hash_table_lookup(names, second); + gint first_index = -1; + gint second_index = -1; + for(guint i = 0; monitor_priority[i] != NULL; ++i) { + if(g_strcmp0(first_name, monitor_priority[i]) != 0) continue; + first_index = i; + break; + } + for(guint i = 0; monitor_priority[i] != NULL; ++i) { + if(g_strcmp0(second_name, monitor_priority[i]) != 0) continue; + second_index = i; + break; + } + return first_index - second_index; + } + return 0; } +// See comment in window.c +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + static void activate(GtkApplication *app, gpointer user_data) { gtklock_activate(gtklock); module_on_activation(gtklock); - if(!setup_layer_shell()) { - struct Window *win = create_window(NULL); - gtklock_focus_window(gtklock, win); + + GdkDisplay *display = gdk_display_get_default(); + g_signal_connect(display, "monitor-added", G_CALLBACK(monitors_added), NULL); + g_signal_connect(display, "monitor-removed", G_CALLBACK(monitors_removed), NULL); + + GArray *monitors = g_array_new(FALSE, TRUE, sizeof(GdkMonitor *)); + GArray *priority_monitors = g_array_new(FALSE, TRUE, sizeof(GdkMonitor *)); + GHashTable *priority_monitor_names = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); + + for(int i = 0; i < gdk_display_get_n_monitors(display); ++i) { + char *name = gdk_screen_get_monitor_plug_name(gdk_screen_get_default(), i); + GdkMonitor *monitor = gdk_display_get_monitor(display, i); + + if(find_priority_monitor(name)) { + g_hash_table_insert(priority_monitor_names, monitor, name); + g_array_append_val(priority_monitors, monitor); + } else { + g_array_append_val(monitors, monitor); + g_free(name); + } } - if(parent > 0) kill(parent, SIGINT); + + g_array_sort_with_data(priority_monitors, compare_priority_monitors, priority_monitor_names); + gsize len; + gpointer data = g_array_steal(priority_monitors, &len); + g_array_prepend_vals(monitors, data, len); + + for(guint idx = 0; idx < monitors->len; idx++) create_window(g_array_index(monitors, GdkMonitor *, idx)); + if(gtklock->windows->len) gtklock_focus_window(gtklock, g_array_index(gtklock->windows, struct Window *, 0)); + + g_array_unref(monitors); + g_array_unref(priority_monitors); + g_hash_table_unref(priority_monitor_names); } static void shutdown(GtkApplication *app, gpointer user_data) { @@ -203,8 +234,16 @@ static void daemonize(void) { else if(pid != 0) { int status; waitpid(pid, &status, 0); - if(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS) - exit(EXIT_SUCCESS); + if(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS) { + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + sigprocmask(SIG_BLOCK, &set, NULL); + int ret = sigtimedwait(&set, NULL, &(struct timespec){ 1, 0 }); + if(ret == SIGUSR1) + exit(EXIT_SUCCESS); + exit(EXIT_FAILURE); + } report_error_and_exit("Failed to daemonize!\n"); } @@ -214,22 +253,23 @@ static void daemonize(void) { else if(pid != 0) exit(EXIT_SUCCESS); } -static void exec_command(const gchar *command) { - GError *err = NULL; - - g_spawn_command_line_async(command, &err); - if(err != NULL) { - g_warning("Executing `%s` failed: %s", command, err->message); - g_error_free(err); - } -} - static gboolean signal_handler(gpointer data) { g_application_quit(G_APPLICATION(gtklock->app)); return G_SOURCE_REMOVE; } +#if GLIB_CHECK_VERSION(2, 74, 0) + #define GTKLOCK_FLAGS G_APPLICATION_DEFAULT_FLAGS +#else + #define GTKLOCK_FLAGS G_APPLICATION_FLAGS_NONE +#endif + int main(int argc, char **argv) { + setlocale(LC_ALL, ""); + bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + textdomain(GETTEXT_PACKAGE); + GOptionContext *option_context = g_option_context_new("- GTK-based lockscreen for sway"); g_option_context_add_main_entries(option_context, main_entries, NULL); g_option_context_set_help_enabled(option_context, FALSE); @@ -286,19 +326,35 @@ int main(int argc, char **argv) { if(!g_option_context_parse(option_context, &argc, &argv, &error)) report_error_and_exit("Option parsing failed: %s\n", error->message); - if(lock_command) exec_command(lock_command); - if(gtk_theme) { GtkSettings *settings = gtk_settings_get_default(); g_object_set(settings, "gtk-theme-name", gtk_theme, NULL); } - gtklock = create_gtklock(); - gtklock->use_layer_shell = !no_layer_shell; - gtklock->use_input_inhibit = !no_input_inhibit; - gtklock->use_idle_hide = idle_hide; - gtklock->idle_timeout = (guint)idle_timeout; + struct GtkLock g = {}; + gtklock = &g; + + gtklock->app = gtk_application_new(NULL, GTKLOCK_FLAGS); + gtklock->parent = parent; + + gtklock->windows = g_array_new(FALSE, TRUE, sizeof(struct Window *)); + gtklock->messages = g_array_new(FALSE, TRUE, sizeof(char *)); + gtklock->errors = g_array_new(FALSE, TRUE, sizeof(char *)); + gtklock->hidden = start_hidden; + gtklock->idle_timeout = (guint)idle_timeout; + + gtklock->follow_focus = follow_focus; + gtklock->use_idle_hide = idle_hide; + + gtklock->time_format = time_format; + gtklock->date_format = date_format; + gtklock->config_path = config_path; + gtklock->layout_path = layout_path; + gtklock->lock_command = lock_command; + gtklock->unlock_command = unlock_command; + + gtklock->modules = modules; if(background_path != NULL) { GFile *file = g_file_new_for_path(background_path); @@ -321,6 +377,12 @@ int main(int argc, char **argv) { "window.focused:not(.hidden) #clock-label {" "font-size: 32pt;" "}" + "window #date-label {" + "font-size: 28px;" + "}" + "window.focused:not(.hidden) #date-label {" + "font-size: 12pt;" + "}" "#error-label {" "color: red;" "}" @@ -332,18 +394,14 @@ int main(int argc, char **argv) { g_free(style_path); } - gtklock->modules = modules; - - gtklock->time_format = time_format; - gtklock->config_path = config_path; - g_signal_connect(gtklock->app, "activate", G_CALLBACK(activate), NULL); g_signal_connect(gtklock->app, "shutdown", G_CALLBACK(shutdown), NULL); g_unix_signal_add(SIGTERM, G_SOURCE_FUNC(signal_handler), NULL); int status = g_application_run(G_APPLICATION(gtklock->app), argc, argv); - gtklock_destroy(gtklock); - if(unlock_command) exec_command(unlock_command); + g_object_unref(gtklock->app); + g_array_unref(gtklock->windows); + return status; } diff --git a/src/window.c b/src/window.c index 1896f3c..b5859cb 100644 --- a/src/window.c +++ b/src/window.c @@ -1,13 +1,13 @@ // gtklock -// Copyright (c) 2022 Kenny Levinsen, Jovan Lanik, Erik Reider, Melih Darcan +// Copyright (c) 2022 Kenny Levinsen, Jovan Lanik, Erik Reider, Melih Darcan, Bhaskar Khoraja // Window functions #include -#include +#include #include -#include +#include #include "util.h" #include "window.h" @@ -33,36 +33,20 @@ struct Window *window_by_monitor(GdkMonitor *monitor) { return NULL; } -static gboolean window_enter_notify(GtkWidget *widget, gpointer data) { - struct Window *win = window_by_widget(widget); - gtk_entry_grab_focus_without_selecting(GTK_ENTRY(win->input_field)); - gtklock_focus_window(gtklock, win); - return FALSE; -} - -static void window_setup_layer_shell(struct Window *ctx) { - gtk_widget_add_events(ctx->window, GDK_ENTER_NOTIFY_MASK); - if(ctx->enter_notify_handler > 0) { - g_signal_handler_disconnect(ctx->window, ctx->enter_notify_handler); - ctx->enter_notify_handler = 0; - } - ctx->enter_notify_handler = g_signal_connect(ctx->window, "enter-notify-event", G_CALLBACK(window_enter_notify), NULL); - - gtk_layer_init_for_window(GTK_WINDOW(ctx->window)); - gtk_layer_set_layer(GTK_WINDOW(ctx->window), GTK_LAYER_SHELL_LAYER_OVERLAY); - gtk_layer_set_monitor(GTK_WINDOW(ctx->window), ctx->monitor); - gtk_layer_set_exclusive_zone(GTK_WINDOW(ctx->window), -1); - gtk_layer_set_keyboard_mode(GTK_WINDOW(ctx->window), GTK_LAYER_SHELL_KEYBOARD_MODE_EXCLUSIVE); - gtk_layer_set_anchor(GTK_WINDOW(ctx->window), GTK_LAYER_SHELL_EDGE_LEFT, TRUE); - gtk_layer_set_anchor(GTK_WINDOW(ctx->window), GTK_LAYER_SHELL_EDGE_RIGHT, TRUE); - gtk_layer_set_anchor(GTK_WINDOW(ctx->window), GTK_LAYER_SHELL_EDGE_TOP, TRUE); - gtk_layer_set_anchor(GTK_WINDOW(ctx->window), GTK_LAYER_SHELL_EDGE_BOTTOM, TRUE); +struct Window *window_last_active(void) { + GtkWindow *window = gtk_application_get_active_window(gtklock->app); + if(window) return window_by_widget(GTK_WIDGET(window)); + return NULL; } void window_update_clock(struct Window *ctx) { gtk_label_set_text(GTK_LABEL(ctx->clock_label), gtklock->time); } +void window_update_date(struct Window *ctx) { + gtk_label_set_text(GTK_LABEL(ctx->date_label), gtklock->date); +} + static void window_setup_messages(struct Window *ctx); static void window_close_message(GtkInfoBar *bar, gint response, gpointer data) { @@ -71,7 +55,7 @@ static void window_close_message(GtkInfoBar *bar, gint response, gpointer data) for(guint idx = 0; idx < gtklock->errors->len; idx++) { char *err = g_array_index(gtklock->errors, char *, idx); if(err == data) { - g_array_remove_index_fast(gtklock->errors, idx); + g_array_remove_index(gtklock->errors, idx); g_free(err); window_setup_messages(ctx); return; @@ -80,7 +64,7 @@ static void window_close_message(GtkInfoBar *bar, gint response, gpointer data) for(guint idx = 0; idx < gtklock->messages->len; idx++) { char *msg = g_array_index(gtklock->messages, char *, idx); if(msg == data) { - g_array_remove_index_fast(gtklock->messages, idx); + g_array_remove_index(gtklock->messages, idx); g_free(msg); window_setup_messages(ctx); return; @@ -102,27 +86,34 @@ static GtkInfoBar *window_new_message(struct Window *ctx, char *msg) { return GTK_INFO_BAR(bar); } +static void destroy_callback(GtkWidget* widget, gpointer _data) { + gtk_widget_destroy(widget); +} + static void window_setup_messages(struct Window *ctx) { - if(ctx->message_box != NULL) { - gtk_widget_destroy(ctx->message_box); - ctx->message_box = NULL; - } - ctx->message_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_set_no_show_all(ctx->message_box, TRUE); - gtk_grid_attach(GTK_GRID(ctx->body_grid), ctx->message_box, 1, 1, 2, 1); + gtk_container_foreach(GTK_CONTAINER(ctx->message_box), destroy_callback, NULL); + gtk_revealer_set_reveal_child(GTK_REVEALER(ctx->message_revealer), FALSE); + gtk_widget_hide(ctx->message_revealer); for(guint idx = 0; idx < gtklock->errors->len; idx++) { char *err = g_array_index(gtklock->errors, char *, idx); GtkInfoBar *bar = window_new_message(ctx, err); gtk_info_bar_set_message_type(bar, GTK_MESSAGE_WARNING); - gtk_widget_show(ctx->message_box); + + gtk_revealer_set_reveal_child(GTK_REVEALER(ctx->message_revealer), TRUE); + gtk_widget_show(ctx->message_revealer); + gtk_widget_show_all(ctx->message_scrolled_window); } for(guint idx = 0; idx < gtklock->messages->len; idx++) { char *msg = g_array_index(gtklock->messages, char *, idx); GtkInfoBar *bar = window_new_message(ctx, msg); gtk_info_bar_set_message_type(bar, GTK_MESSAGE_INFO); - gtk_widget_show(ctx->message_box); + + gtk_revealer_set_reveal_child(GTK_REVEALER(ctx->message_revealer), TRUE); + gtk_widget_show(ctx->message_revealer); + gtk_widget_show_all(ctx->message_scrolled_window); } + } static void window_set_busy(struct Window *ctx, gboolean busy) { @@ -142,7 +133,7 @@ static gboolean window_pw_failure(gpointer data) { window_set_busy(ctx, FALSE); gtk_entry_set_text(GTK_ENTRY(ctx->input_field), ""); gtk_entry_grab_focus_without_selecting(GTK_ENTRY(ctx->input_field)); - gtk_label_set_text(GTK_LABEL(ctx->error_label), "Login failed"); + gtk_label_set_text(GTK_LABEL(ctx->error_label), _("Login failed")); return G_SOURCE_REMOVE; } @@ -213,18 +204,12 @@ static void window_destroy_notify(GtkWidget *widget, gpointer data) { void window_swap_focus(struct Window *win, struct Window *old) { if(!gtklock->hidden) gtk_revealer_set_reveal_child(GTK_REVEALER(win->body_revealer), TRUE); - if(gtklock->use_layer_shell) - gtk_layer_set_keyboard_mode(GTK_WINDOW(win->window), GTK_LAYER_SHELL_KEYBOARD_MODE_EXCLUSIVE); - GtkStyleContext *win_context = gtk_widget_get_style_context(win->window); gtk_style_context_add_class(win_context, "focused"); if(old != NULL && old != win) { gtk_revealer_set_reveal_child(GTK_REVEALER(old->body_revealer), FALSE); - if(gtklock->use_layer_shell) - gtk_layer_set_keyboard_mode(GTK_WINDOW(old->window), GTK_LAYER_SHELL_KEYBOARD_MODE_NONE); - GtkStyleContext *old_context = gtk_widget_get_style_context(old->window); gtk_style_context_remove_class(old_context, "focused"); @@ -245,12 +230,17 @@ void window_swap_focus(struct Window *win, struct Window *old) { window_pw_set_vis(GTK_ENTRY(win->input_field), gtk_entry_get_visibility(GTK_ENTRY(old->input_field))); } } + + gtk_entry_grab_focus_without_selecting(GTK_ENTRY(win->input_field)); } void window_idle_hide(struct Window *ctx) { GtkStyleContext *context = gtk_widget_get_style_context(ctx->window); gtk_style_context_add_class(context, "hidden"); gtk_revealer_set_reveal_child(GTK_REVEALER(ctx->body_revealer), FALSE); + GdkCursor *cursor = gdk_cursor_new_for_display(gtk_widget_get_display(ctx->window), GDK_BLANK_CURSOR); + gdk_window_set_cursor(gtk_widget_get_window(ctx->window), cursor); + g_object_unref(cursor); } void window_idle_show(struct Window *ctx) { @@ -260,6 +250,9 @@ void window_idle_show(struct Window *ctx) { gtk_revealer_set_reveal_child(GTK_REVEALER(ctx->body_revealer), TRUE); gtk_entry_grab_focus_without_selecting(GTK_ENTRY(ctx->input_field)); } + GdkCursor *cursor = gdk_cursor_new_from_name(gtk_widget_get_display(ctx->window), "default"); + gdk_window_set_cursor(gtk_widget_get_window(ctx->window), cursor); + g_object_unref(cursor); } static gboolean window_idle_key(GtkWidget *self, GdkEventKey event, gpointer user_data) { @@ -271,14 +264,26 @@ static gboolean window_idle_motion(GtkWidget *self, GdkEventMotion event, gpoint return FALSE; } -void window_caps_state_changed(GdkKeymap *self, gpointer user_data) { +static void window_caps_state_changed(GdkKeymap *self, gpointer user_data) { struct Window *w = gtklock->focused_window; if(!w || !w->warning_label) return; - if(gdk_keymap_get_caps_lock_state(self)) gtk_label_set_text(GTK_LABEL(w->warning_label), "Caps Lock is on"); + if(gdk_keymap_get_caps_lock_state(self)) gtk_label_set_text(GTK_LABEL(w->warning_label), _("Caps Lock is on")); else gtk_label_set_text(GTK_LABEL(w->warning_label), ""); } +static gboolean entry_button_press(GtkWidget *widget, GdkEventButton *event, gpointer data) { + if(event->button != 1) return TRUE; + return FALSE; +} + +static gboolean window_enter_notify(GtkWidget *widget, gpointer data) { + struct Window *win = window_by_widget(widget); + gtk_entry_grab_focus_without_selecting(GTK_ENTRY(win->input_field)); + gtklock_focus_window(gtklock, win); + return FALSE; +} + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" struct Window *create_window(GdkMonitor *monitor) { @@ -290,7 +295,10 @@ struct Window *create_window(GdkMonitor *monitor) { w->window = gtk_application_window_new(gtklock->app); g_signal_connect(w->window, "destroy", G_CALLBACK(window_destroy_notify), NULL); + if(gtklock->follow_focus) + g_signal_connect(w->window, "enter-notify-event", G_CALLBACK(window_enter_notify), NULL); if(gtklock->use_idle_hide || gtklock->hidden) { + gtk_widget_add_events(w->window, GDK_POINTER_MOTION_MASK); g_signal_connect(w->window, "key-press-event", G_CALLBACK(window_idle_key), NULL); g_signal_connect(w->window, "motion-notify-event", G_CALLBACK(window_idle_motion), NULL); } @@ -313,16 +321,22 @@ struct Window *create_window(GdkMonitor *monitor) { GdkKeymap *keymap = gdk_keymap_get_for_display(display); g_signal_connect(keymap, "state-changed", G_CALLBACK(window_caps_state_changed), NULL); - if(name) gtk_widget_set_name(w->window, name); + if(name) { + gtk_widget_set_name(w->window, name); + g_free(name); + } gtk_window_set_title(GTK_WINDOW(w->window), "Lockscreen"); gtk_window_set_decorated(GTK_WINDOW(w->window), FALSE); gtk_widget_realize(w->window); - if(gtklock->use_layer_shell) window_setup_layer_shell(w); + gtk_session_lock_lock_new_surface(gtklock->lock, GTK_WINDOW(w->window), monitor); w->overlay = gtk_overlay_new(); gtk_container_add(GTK_CONTAINER(w->window), w->overlay); - GtkBuilder *builder = gtk_builder_new_from_resource("/gtklock/gtklock.ui"); + GtkBuilder *builder = NULL; + if(gtklock->layout_path) builder = gtk_builder_new_from_file(gtklock->layout_path); + else builder = gtk_builder_new_from_resource("/gtklock/gtklock.ui"); + gtk_builder_connect_signals(builder, w); w->window_box = GTK_WIDGET(gtk_builder_get_object(builder, "window-box")); @@ -333,15 +347,24 @@ struct Window *create_window(GdkMonitor *monitor) { w->input_label = GTK_WIDGET(gtk_builder_get_object(builder, "input-label")); w->input_field = GTK_WIDGET(gtk_builder_get_object(builder, "input-field")); + g_signal_connect(w->input_field, "button-press-event", G_CALLBACK(entry_button_press), NULL); + w->message_revealer = GTK_WIDGET(gtk_builder_get_object(builder, "message-revealer")); + w->message_scrolled_window = GTK_WIDGET(gtk_builder_get_object(builder, "message-scrolled-window")); w->message_box = GTK_WIDGET(gtk_builder_get_object(builder, "message-box")); w->unlock_button = GTK_WIDGET(gtk_builder_get_object(builder, "unlock-button")); w->error_label = GTK_WIDGET(gtk_builder_get_object(builder, "error-label")); w->warning_label = GTK_WIDGET(gtk_builder_get_object(builder, "warning-label")); + + w->info_box = GTK_WIDGET(gtk_builder_get_object(builder, "info-box")); + w->time_box = GTK_WIDGET(gtk_builder_get_object(builder, "time-box")); w->clock_label = GTK_WIDGET(gtk_builder_get_object(builder, "clock-label")); window_update_clock(w); + w->date_label = GTK_WIDGET(gtk_builder_get_object(builder, "date-label")); + window_update_date(w); + if(gtklock->hidden) window_idle_hide(w); module_on_window_create(gtklock, w); gtk_widget_show_all(w->window); diff --git a/subprojects/gtk-session-lock.wrap b/subprojects/gtk-session-lock.wrap new file mode 100644 index 0000000..e62c9b8 --- /dev/null +++ b/subprojects/gtk-session-lock.wrap @@ -0,0 +1,8 @@ +[wrap-git] +directory = gtk-session-lock +url = https://github.com/Cu3PO42/gtk-session-lock +revision = b3544f361498d716b1ceef1ad6ac9bdf024bf782 + +[provide] +dependency_names = gtk-session-lock-0 +gtk-session-lock-0 = gtk_session_lock diff --git a/wayland/wlr-input-inhibitor-unstable-v1.xml b/wayland/wlr-input-inhibitor-unstable-v1.xml deleted file mode 100644 index 6f4c246..0000000 --- a/wayland/wlr-input-inhibitor-unstable-v1.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - Copyright © 2018 Drew DeVault - - Permission to use, copy, modify, distribute, and sell this - software and its documentation for any purpose is hereby granted - without fee, provided that the above copyright notice appear in - all copies and that both that copyright notice and this permission - notice appear in supporting documentation, and that the name of - the copyright holders not be used in advertising or publicity - pertaining to distribution of the software without specific, - written prior permission. The copyright holders make no - representations about the suitability of this software for any - purpose. It is provided "as is" without express or implied - warranty. - - THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS - SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY - SPECIAL, 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. - - - - - Clients can use this interface to prevent input events from being sent to - any surfaces but its own, which is useful for example in lock screen - software. It is assumed that access to this interface will be locked down - to whitelisted clients by the compositor. - - - - - Activates the input inhibitor. As long as the inhibitor is active, the - compositor will not send input events to other clients. - - - - - - - - - - - - While this resource exists, input to clients other than the owner of the - inhibitor resource will not receive input events. Any client which - previously had focus will receive a leave event and will not be given - focus again. The client that owns this resource will receive all input - events normally. The compositor will also disable all of its own input - processing (such as keyboard shortcuts) while the inhibitor is active. - - The compositor may continue to send input events to selected clients, - such as an on-screen keyboard (via the input-method protocol). - - - - - Destroy the inhibitor and allow other clients to receive input. - - - -