├── LICENSE ├── Makefile ├── README.md ├── config.conf ├── preview.gif ├── src ├── auth.cpp ├── auth.hpp ├── config.hpp ├── config_parser.cpp ├── config_parser.hpp ├── css.cpp ├── css.hpp ├── graphics.cpp ├── keypad.cpp ├── keypad.hpp ├── main.cpp ├── main.hpp ├── tap_to_wake.cpp ├── tap_to_wake.hpp ├── window.cpp └── window.hpp └── style.css /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BINS = syslock 2 | LIBS = libsyslock.so 3 | PKGS = gtkmm-4.0 gtk4-layer-shell-0 pam wayland-client 4 | SRCS = $(filter-out src/tap_to_wake.cpp, $(wildcard src/*.cpp)) 5 | 6 | PREFIX ?= /usr/local 7 | BINDIR ?= $(PREFIX)/bin 8 | LIBDIR ?= $(PREFIX)/lib 9 | DATADIR ?= $(PREFIX)/share 10 | BUILDDIR = build 11 | 12 | PROTOS = ext-session-lock-v1 13 | PROTO_DIR = /usr/share/wayland-protocols/staging/ext-session-lock 14 | 15 | PROTO_HDRS = $(addprefix src/, $(addsuffix .h, $(notdir $(PROTOS)))) 16 | PROTO_SRCS = $(addprefix src/, $(addsuffix .c, $(notdir $(PROTOS)))) 17 | PROTO_OBJS = $(PROTO_SRCS:.c=.o) 18 | 19 | # Features 20 | ifneq (, $(shell grep -E '^#define FEATURE_TAP_TO_WAKE' src/config.hpp)) 21 | SRCS += src/tap_to_wake.cpp 22 | PKGS += libevdev 23 | CXXFLAGS += -std=c++20 24 | endif 25 | 26 | OBJS = $(patsubst src/%,$(BUILDDIR)/%, $(SRCS:.cpp=.o)) 27 | 28 | CXXFLAGS += -Oz -s -Wall -flto=auto -fno-exceptions -fPIC 29 | LDFLAGS += -Wl,--as-needed,-z,now,-z,pack-relative-relocs 30 | 31 | CXXFLAGS += $(shell pkg-config --cflags $(PKGS)) 32 | LDFLAGS += $(shell pkg-config --libs $(PKGS)) 33 | 34 | $(shell mkdir -p $(BUILDDIR)) 35 | JOB_COUNT := $(BINS) $(LIBS) $(PROTO_HDRS) $(PROTO_SRCS) $(PROTO_OBJS) $(OBJS) src/git_info.hpp 36 | JOBS_DONE := $(shell ls -l $(JOB_COUNT) 2> /dev/null | wc -l) 37 | 38 | define progress 39 | $(eval JOBS_DONE := $(shell echo $$(($(JOBS_DONE) + 1)))) 40 | @printf "[$(JOBS_DONE)/$(shell echo $(JOB_COUNT) | wc -w)] %s %s\n" $(1) $(2) 41 | endef 42 | 43 | all: $(BINS) $(LIBS) 44 | 45 | install: $(all) 46 | @echo "Installing..." 47 | @install -D -t $(DESTDIR)$(BINDIR) $(BUILDDIR)/$(BINS) 48 | @install -D -t $(DESTDIR)$(LIBDIR) $(BUILDDIR)/$(LIBS) 49 | @install -D -t $(DESTDIR)$(DATADIR)/sys64/lock config.conf style.css 50 | 51 | clean: 52 | @echo "Cleaning up" 53 | @rm -rf $(BUILDDIR) \ 54 | src/git_info.hpp \ 55 | $(PROTO_OBJS) \ 56 | $(PROTO_SRCS) \ 57 | $(PROTO_HDRS) 58 | 59 | $(BINS): src/git_info.hpp $(BUILDDIR)/main.o $(BUILDDIR)/config_parser.o 60 | $(call progress, Linking $@) 61 | @$(CXX) -o $(BUILDDIR)/$(BINS) \ 62 | $(BUILDDIR)/main.o \ 63 | $(BUILDDIR)/config_parser.o \ 64 | $(CXXFLAGS) \ 65 | $(shell pkg-config --libs gtkmm-4.0 gtk4-layer-shell-0) 66 | 67 | $(LIBS): $(PROTO_HDRS) $(PROTO_SRCS) $(PROTO_OBJS) $(OBJS) 68 | $(call progress, Linking $@) 69 | @$(CXX) -o $(BUILDDIR)/$(LIBS) \ 70 | $(filter-out $(BUILDDIR)/main.o, $(OBJS)) \ 71 | $(PROTO_OBJS) \ 72 | $(CXXFLAGS) \ 73 | $(LDFLAGS) \ 74 | -shared 75 | 76 | $(BUILDDIR)/%.o: src/%.cpp 77 | $(call progress, Compiling $@) 78 | @$(CXX) -c $< -o $@ $(CXXFLAGS) 79 | 80 | $(BUILDDIR)/%.o: src/%.c 81 | $(call progress, Compiling $@) 82 | @$(CC) -c $< -o $@ $(CFLAGS) 83 | 84 | $(PROTO_HDRS): src/%.h : $(PROTO_DIR)/$(PROTOS).xml 85 | $(call progress, Creating $@) 86 | @wayland-scanner client-header $< src/$(notdir $(basename $<)).h 87 | 88 | $(PROTO_SRCS): src/%.c : $(PROTO_DIR)/$(PROTOS).xml 89 | $(call progress, Creating $@) 90 | @wayland-scanner public-code $< src/$(notdir $(basename $<)).c 91 | 92 | src/git_info.hpp: 93 | $(call progress, Creating $@) 94 | @commit_hash=$$(git rev-parse HEAD); \ 95 | commit_date=$$(git show -s --format=%cd --date=short $$commit_hash); \ 96 | commit_message=$$(git show -s --format="%s" $$commit_hash | sed 's/"/\\\"/g'); \ 97 | echo "#define GIT_COMMIT_MESSAGE \"$$commit_message\"" > src/git_info.hpp; \ 98 | echo "#define GIT_COMMIT_DATE \"$$commit_date\"" >> src/git_info.hpp 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Syslock 2 | Syslock is a simple lockscreen for wayland written in gtkmm4
3 | ![preview](https://github.com/System64fumo/syslock/blob/main/preview.gif "preview") 4 | 5 | [![Packaging status](https://repology.org/badge/vertical-allrepos/syslock.svg)](https://repology.org/project/syslock/versions) 6 | 7 | > [!CAUTION] 8 | > This program does not use the ext session lock protocol. *Yet*
9 | > Additional info at the bottom.
10 | 11 | # Configuration 12 | syslock can be configured in 2 ways
13 | 1: By changing config.hpp and recompiling (Suckless style)
14 | 2: Using a config file (~/.config/sys64/lock/config.conf)
15 | 3: Using launch arguments
16 | ``` 17 | arguments: 18 | -s Start unlocked 19 | -k Enable the keypad 20 | -l Set password length (For automatic unlocks) 21 | -m Set primary monitor 22 | -e Enable experimental session lock 23 | -d Enable debug mode 24 | -v Prints version info 25 | ``` 26 | 27 | # Signals 28 | You can send a signal to show the window/s again.
29 | ``pkill -10 syslock``
30 | 31 | # Theming 32 | syslock uses your gtk4 theme by default, However it can be also load custom css,
33 | Just copy the included style.css file to ~/.config/sys64/lock/style.css
34 | 35 | # Session lock 36 | Currently the session lock protocol is implemented but disabled by default due to instability and generally being broken.
37 | Though if you are curious and wish to give it a try you can enable it by using the -e launch flag.
38 | 39 | Things that will happen when the experimental session lock option is enabled:
40 | * syslock will crash upon succesfully authenticating.
41 | * The primary window gets mirrored to all displays.
42 | * Touch inputs will not work.
43 | 44 | Help with this would be greatly appreciated. 45 | -------------------------------------------------------------------------------- /config.conf: -------------------------------------------------------------------------------- 1 | [main] 2 | start-unlocked=false 3 | keypad=false 4 | password-length=-1 5 | main-monitor=HDMI-A-1 6 | 7 | [tap-to-wake] 8 | device-path=/dev/input/by-path/SET-ME-UP 9 | verbose=false 10 | timeout=500 11 | 12 | [events] 13 | on-lock-cmd= 14 | on-unlock-cmd= 15 | on-tap-cmd= 16 | 17 | [profile] 18 | image-path= 19 | scale=128 20 | rounding=64 21 | 22 | [clock] 23 | time-format=%H:%M 24 | date-format=%a %d %b 25 | -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System64fumo/syslock/18910f055ec57619e1685c8dd57514513836402c/preview.gif -------------------------------------------------------------------------------- /src/auth.cpp: -------------------------------------------------------------------------------- 1 | #include "auth.hpp" 2 | 3 | int pam_conversation(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { 4 | struct pam_response *response = (struct pam_response *)malloc(num_msg * sizeof(struct pam_response)); 5 | if (response == NULL) { 6 | return PAM_CONV_ERR; 7 | } 8 | 9 | for (int i = 0; i < num_msg; ++i) { 10 | if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF || msg[i]->msg_style == PAM_PROMPT_ECHO_ON) { 11 | const char *password = (const char *)appdata_ptr; 12 | response[i].resp = strdup(password); 13 | response[i].resp_retcode = 0; 14 | } else { 15 | free(response); 16 | return PAM_CONV_ERR; 17 | } 18 | } 19 | 20 | *resp = response; 21 | return PAM_SUCCESS; 22 | } 23 | 24 | bool authenticate(char *user, const char *password) { 25 | struct pam_conv conv = { pam_conversation, (void *)password }; 26 | pam_handle_t *pamh = NULL; 27 | 28 | int retval = pam_start("login", user, &conv, &pamh); 29 | retval = pam_authenticate(pamh, 0); 30 | 31 | pam_end(pamh, retval); 32 | return (retval == PAM_SUCCESS); 33 | } 34 | 35 | static void lock_surface_configure(void *data, struct ext_session_lock_surface_v1 *lock_surface, uint32_t serial, uint32_t width, uint32_t height) { 36 | ext_session_lock_surface_v1_ack_configure(lock_surface, serial); 37 | } 38 | 39 | static ext_session_lock_surface_v1_listener lock_surface_listener = { 40 | .configure = lock_surface_configure, 41 | }; 42 | 43 | static void session_lock_done(void *data, struct ext_session_lock_v1 *lock) {} 44 | 45 | static void session_lock_finished(void *data, struct ext_session_lock_v1 *lock) {} 46 | 47 | static ext_session_lock_v1_listener session_lock_listener = { 48 | .locked = session_lock_done, 49 | .finished = session_lock_finished, 50 | }; 51 | 52 | static void registry_handler(void *data, struct wl_registry *registry, 53 | uint32_t id, const char *interface, uint32_t version) { 54 | 55 | if (strcmp(interface, ext_session_lock_manager_v1_interface.name) == 0) { 56 | session_lock_manager = (ext_session_lock_manager_v1*) 57 | wl_registry_bind(registry, id, 58 | &ext_session_lock_manager_v1_interface, 1u); 59 | 60 | session_lock = ext_session_lock_manager_v1_lock(session_lock_manager); 61 | ext_session_lock_v1_add_listener(session_lock, &session_lock_listener, nullptr); 62 | wayland_surface = gdk_wayland_surface_get_wl_surface(gdk_surface); 63 | } 64 | else if (strcmp(interface, wl_output_interface.name) == 0) { 65 | output = (struct wl_output *) wl_registry_bind(registry, id, &wl_output_interface, 1); 66 | 67 | wl_display_roundtrip(wayland_display); 68 | auto lock_surface = ext_session_lock_v1_get_lock_surface(session_lock, wayland_surface, output); 69 | ext_session_lock_surface_v1_add_listener(lock_surface, &lock_surface_listener, nullptr); 70 | wl_display_roundtrip(wayland_display); 71 | wl_display_flush(wayland_display); 72 | } 73 | } 74 | 75 | static wl_registry_listener registry_listener = { 76 | ®istry_handler 77 | }; 78 | 79 | 80 | void lock_session(Gtk::Window *window) { 81 | gdk_surface = window->get_surface()->gobj(); 82 | wayland_display = wl_display_connect(NULL); 83 | auto gdk_display = gdk_display_get_default(); 84 | auto display = gdk_wayland_display_get_wl_display(gdk_display); 85 | auto registry = wl_display_get_registry(display); 86 | wl_registry_add_listener(registry, ®istry_listener, nullptr); 87 | } 88 | 89 | void unlock_session() { 90 | ext_session_lock_v1_unlock_and_destroy(session_lock); 91 | session_lock = nullptr; 92 | wl_display_roundtrip(wayland_display); 93 | // TODO: When hiding the display this freaks out and crashes with: 94 | // Error 71 (Protocol error) dispatching to Wayland display. 95 | } 96 | -------------------------------------------------------------------------------- /src/auth.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "ext-session-lock-v1.h" 7 | 8 | inline GdkSurface* gdk_surface; 9 | inline wl_display* wayland_display; 10 | inline wl_surface* wayland_surface; 11 | inline wl_output* output; 12 | inline ext_session_lock_v1* session_lock; 13 | inline ext_session_lock_manager_v1* session_lock_manager; 14 | 15 | int pam_conversation(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr); 16 | bool authenticate(char *user, const char *password); 17 | void lock_session(Gtk::Window *window); 18 | void unlock_session(); 19 | -------------------------------------------------------------------------------- /src/config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Build time configuration Description 4 | #define CONFIG_RUNTIME // Allow the use of runtime arguments 5 | #define CONFIG_FILE // Allow the use of a config file 6 | #define FEATURE_TAP_TO_WAKE // Include tap to wake feature 7 | -------------------------------------------------------------------------------- /src/config_parser.cpp: -------------------------------------------------------------------------------- 1 | #include "config_parser.hpp" 2 | #include 3 | #include 4 | 5 | config_parser::config_parser(const std::string& filename) { 6 | std::ifstream file(filename); 7 | std::string line; 8 | std::string current_section; 9 | 10 | available = file.is_open(); 11 | 12 | if (available) { 13 | while (std::getline(file, line)) { 14 | line = trim(line); 15 | 16 | if (line.empty() || line[0] == ';' || line[0] == '#') { 17 | continue; 18 | } 19 | else if (line[0] == '[' && line[line.size() - 1] == ']') { 20 | current_section = line.substr(1, line.size() - 2); 21 | } 22 | else { 23 | size_t delimiter_pos = line.find('='); 24 | if (delimiter_pos != std::string::npos) { 25 | std::string key = trim(line.substr(0, delimiter_pos)); 26 | std::string value = trim(line.substr(delimiter_pos + 1)); 27 | data[current_section][key] = value; 28 | } 29 | } 30 | } 31 | file.close(); 32 | } 33 | else { 34 | std::fprintf(stderr, "Unable to open file: %s\n", filename.c_str()); 35 | } 36 | } 37 | 38 | std::string config_parser::trim(const std::string& str) { 39 | const size_t first = str.find_first_not_of(' '); 40 | if (std::string::npos == first) { 41 | return str; 42 | } 43 | const size_t last = str.find_last_not_of(' '); 44 | return str.substr(first, (last - first + 1)); 45 | } 46 | -------------------------------------------------------------------------------- /src/config_parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | // INI parser 6 | class config_parser { 7 | public: 8 | config_parser(const std::string&); 9 | std::map> data; 10 | bool available; 11 | 12 | private: 13 | std::string trim(const std::string&); 14 | }; 15 | -------------------------------------------------------------------------------- /src/css.cpp: -------------------------------------------------------------------------------- 1 | #include "css.hpp" 2 | #include 3 | #include 4 | 5 | css_loader::css_loader(const std::string &path, Gtk::Window *window) { 6 | if (!std::filesystem::exists(path)) 7 | return; 8 | 9 | auto css = Gtk::CssProvider::create(); 10 | css->load_from_path(path); 11 | auto style_context = window->get_style_context(); 12 | style_context->add_provider_for_display(window->property_display(), css, GTK_STYLE_PROVIDER_PRIORITY_USER); 13 | } 14 | -------------------------------------------------------------------------------- /src/css.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class css_loader : public Glib::RefPtr { 4 | public: 5 | css_loader(const std::string &path, Gtk::Window *window); 6 | }; 7 | -------------------------------------------------------------------------------- /src/graphics.cpp: -------------------------------------------------------------------------------- 1 | #include "window.hpp" 2 | 3 | #include 4 | 5 | Glib::RefPtr syslock::create_rounded_pixbuf(const Glib::RefPtr &src_pixbuf, const int &size, const int &rounding_radius) { 6 | // Limit to 50% rounding otherwise funky stuff happens 7 | int rounding = std::clamp(rounding_radius, 0, size / 2); 8 | 9 | int width = src_pixbuf->get_width(); 10 | int height = src_pixbuf->get_height(); 11 | 12 | auto surface = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, size, size); 13 | auto cr = Cairo::Context::create(surface); 14 | 15 | cr->begin_new_path(); 16 | cr->arc(rounding, rounding, rounding, M_PI, 3.0 * M_PI / 2.0); 17 | cr->arc(width - rounding, rounding, rounding, 3.0 * M_PI / 2.0, 0.0); 18 | cr->arc(width - rounding, height - rounding, rounding, 0.0, M_PI / 2.0); 19 | cr->arc(rounding, height - rounding, rounding, M_PI / 2.0, M_PI); 20 | cr->close_path(); 21 | cr->clip(); 22 | 23 | Gdk::Cairo::set_source_pixbuf(cr, src_pixbuf, 0, 0); 24 | cr->paint(); 25 | 26 | return Gdk::Pixbuf::create(surface, 0, 0, size, size); 27 | } 28 | -------------------------------------------------------------------------------- /src/keypad.cpp: -------------------------------------------------------------------------------- 1 | #include "keypad.hpp" 2 | 3 | #include 4 | 5 | keypad::keypad(Gtk::Entry &entry, const std::function &enter_func) : Gtk::FlowBox() { 6 | set_min_children_per_line(3); 7 | set_max_children_per_line(3); 8 | set_halign(Gtk::Align::CENTER); 9 | set_selection_mode(Gtk::SelectionMode::NONE); 10 | 11 | for (int i=1; i<10; i++) { 12 | Gtk::FlowBoxChild child; 13 | Gtk::Button *button = Gtk::manage(new Gtk::Button(std::to_string(i))); 14 | append(child); 15 | child.set_size_request(100, 100); 16 | child.set_child(*button); 17 | 18 | button->set_can_focus(false); 19 | button->signal_clicked().connect([&entry, i]() { 20 | entry.set_text(entry.get_text() + std::to_string(i)); 21 | entry.set_position(-1); 22 | }); 23 | } 24 | 25 | Gtk::FlowBoxChild child_enter; 26 | Gtk::Button *button_enter = Gtk::manage(new Gtk::Button()); 27 | button_enter->set_image_from_icon_name("am-dialog-ok-symbolic"); 28 | append(child_enter); 29 | child_enter.set_size_request(100, 100); 30 | child_enter.set_child(*button_enter); 31 | 32 | button_enter->set_can_focus(false); 33 | button_enter->signal_clicked().connect([&entry, enter_func]() { 34 | enter_func(); 35 | }); 36 | 37 | Gtk::FlowBoxChild child_zero; 38 | Gtk::Button *button_zero = Gtk::manage(new Gtk::Button("0")); 39 | append(child_zero); 40 | child_zero.set_size_request(100, 100); 41 | child_zero.set_child(*button_zero); 42 | 43 | button_zero->set_can_focus(false); 44 | button_zero->signal_clicked().connect([&entry]() { 45 | entry.set_text(entry.get_text() + "0"); 46 | entry.set_position(-1); 47 | }); 48 | 49 | Gtk::FlowBoxChild child_delete; 50 | Gtk::Button *button_delete = Gtk::manage(new Gtk::Button()); 51 | button_delete->set_image_from_icon_name("edit-clear-symbolic"); 52 | append(child_delete); 53 | child_delete.set_size_request(100, 100); 54 | child_delete.set_child(*button_delete); 55 | 56 | button_delete->set_can_focus(false); 57 | button_delete->signal_clicked().connect([&entry]() { 58 | if (entry.get_text().length() > 0) 59 | entry.set_text(entry.get_text().substr(1)); 60 | entry.set_position(-1); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /src/keypad.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class keypad : public Gtk::FlowBox { 5 | public: 6 | keypad(Gtk::Entry &entry, const std::function &enter_func); 7 | }; 8 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "main.hpp" 2 | #include "window.hpp" 3 | #include "config.hpp" 4 | #include "config_parser.hpp" 5 | #include "git_info.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | void load_libsyslock() { 12 | void* handle = dlopen("libsyslock.so", RTLD_LAZY); 13 | if (!handle) { 14 | std::fprintf(stderr, "Cannot open library: %s\n", dlerror()); 15 | exit(1); 16 | } 17 | 18 | syslock_create_ptr = (syslock_create_func)dlsym(handle, "syslock_create"); 19 | syslock_lock_ptr = (syslock_lock_func)dlsym(handle, "syslock_lock"); 20 | 21 | if (!syslock_create_ptr || !syslock_lock_ptr) { 22 | std::fprintf(stderr, "Cannot load symbols: %s\n", dlerror()); 23 | dlclose(handle); 24 | exit(1); 25 | } 26 | } 27 | 28 | void handle_signal(int signum) { 29 | if (signum == 10) { 30 | syslock_lock_ptr(win); 31 | } 32 | } 33 | 34 | int main(int argc, char* argv[]) { 35 | #ifdef CONFIG_FILE 36 | std::string config_path; 37 | std::map> config; 38 | std::map> config_usr; 39 | 40 | bool cfg_sys = std::filesystem::exists("/usr/share/sys64/lock/config.conf"); 41 | bool cfg_sys_local = std::filesystem::exists("/usr/local/share/sys64/lock/config.conf"); 42 | bool cfg_usr = std::filesystem::exists(std::string(getenv("HOME")) + "/.config/sys64/lock/config.conf"); 43 | 44 | // Load default config 45 | if (cfg_sys) 46 | config_path = "/usr/share/sys64/lock/config.conf"; 47 | else if (cfg_sys_local) 48 | config_path = "/usr/local/share/sys64/lock/config.conf"; 49 | else 50 | std::fprintf(stderr, "No default config found, Things will get funky!\n"); 51 | 52 | config = config_parser(config_path).data; 53 | 54 | // Load user config 55 | if (cfg_usr) 56 | config_path = std::string(getenv("HOME")) + "/.config/sys64/lock/config.conf"; 57 | else 58 | std::fprintf(stderr, "No user config found\n"); 59 | 60 | config_usr = config_parser(config_path).data; 61 | 62 | // Merge configs 63 | for (const auto& [key, nested_map] : config_usr) 64 | for (const auto& [inner_key, inner_value] : nested_map) 65 | config[key][inner_key] = inner_value; 66 | 67 | // Sanity check 68 | if (!(cfg_sys || cfg_sys_local || cfg_usr)) { 69 | std::fprintf(stderr, "No config available, Something ain't right here."); 70 | return 1; 71 | } 72 | #endif 73 | 74 | // Read launch arguments 75 | #ifdef CONFIG_RUNTIME 76 | while (true) { 77 | switch(getopt(argc, argv, "skl:dm:dedvh")) { 78 | case 's': 79 | config["main"]["start-unlocked"] = "true"; 80 | continue; 81 | 82 | case 'k': 83 | config["main"]["keypad"] = "true"; 84 | continue; 85 | 86 | case 'l': 87 | config["main"]["password-length"] = optarg; 88 | continue; 89 | 90 | case 'm': 91 | config["main"]["main-monitor"] = optarg; 92 | continue; 93 | 94 | case 'e': 95 | config["main"]["experimental"] = "true"; 96 | continue; 97 | 98 | case 'd': 99 | config["main"]["debug"] = "true"; 100 | continue; 101 | 102 | case 'v': 103 | std::printf("Commit: %s\n", GIT_COMMIT_MESSAGE); 104 | std::printf("Date: %s\n", GIT_COMMIT_DATE); 105 | return 0; 106 | 107 | case 'h': 108 | default : 109 | std::printf("usage:\n"); 110 | std::printf(" syslock [argument...]:\n\n"); 111 | std::printf("arguments:\n"); 112 | std::printf(" -s Start unlocked\n"); 113 | std::printf(" -k Enable the keypad\n"); 114 | std::printf(" -l Set password length\n"); 115 | std::printf(" -m Set primary monitor\n"); 116 | std::printf(" -e Enable experimental session lock\n"); 117 | std::printf(" -d Enable debug mode\n"); 118 | std::printf(" -v Prints version info\n"); 119 | std::printf(" -h Show this help message\n"); 120 | return 0; 121 | 122 | case -1: 123 | break; 124 | } 125 | 126 | break; 127 | } 128 | #endif 129 | 130 | Glib::RefPtr app = Gtk::Application::create("funky.sys64.syslock"); 131 | app->hold(); 132 | 133 | load_libsyslock(); 134 | win = syslock_create_ptr(config); 135 | 136 | signal(SIGUSR1, handle_signal); 137 | 138 | return app->run(); 139 | } 140 | -------------------------------------------------------------------------------- /src/main.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "window.hpp" 3 | 4 | syslock *win; 5 | 6 | typedef syslock* (*syslock_create_func)(const std::map>&); 7 | typedef void (*syslock_lock_func)(syslock*); 8 | 9 | syslock_create_func syslock_create_ptr; 10 | syslock_lock_func syslock_lock_ptr; 11 | -------------------------------------------------------------------------------- /src/tap_to_wake.cpp: -------------------------------------------------------------------------------- 1 | #include "tap_to_wake.hpp" 2 | 3 | #include 4 | #include 5 | 6 | tap_to_wake::tap_to_wake(const std::map>& cfg) { 7 | config_main = cfg; 8 | device_path = config_main["tap-to-wake"]["device-path"]; 9 | timeout = std::stoi(config_main["tap-to-wake"]["timeout"]); 10 | tap_cmd = config_main["events"]["on-tap-cmd"]; 11 | } 12 | 13 | tap_to_wake::~tap_to_wake() { 14 | stop_listener(); 15 | } 16 | 17 | void tap_to_wake::start_listener() { 18 | // Set up the device ya dingus 19 | if (device_path == "/dev/input/by-path/SET-ME-UP") 20 | return; 21 | 22 | // Prevent starting another thread if already running 23 | if (running) 24 | return; 25 | 26 | running = true; 27 | 28 | // Setup 29 | fd = open(device_path.c_str(), O_RDONLY|O_NONBLOCK); 30 | if (fd < 0) 31 | return; 32 | 33 | rc = libevdev_new_from_fd(fd, &dev); 34 | if (rc < 0) { 35 | std::fprintf(stderr, "Failed to set up tap to wake\n"); 36 | return; 37 | } 38 | 39 | pipe(pipefd); 40 | fcntl(pipefd[0], F_SETFL, O_NONBLOCK); 41 | 42 | thread_tap_listener = std::jthread([this](std::stop_token stoken) { 43 | while (!stoken.stop_requested()) { 44 | fd_set fds; 45 | FD_ZERO(&fds); 46 | FD_SET(fd, &fds); 47 | FD_SET(pipefd[0], &fds); 48 | int max_fd = std::max(fd, pipefd[0]); 49 | int select_result = select(max_fd + 1, &fds, nullptr, nullptr, nullptr); 50 | 51 | if (!FD_ISSET(fd, &fds)) 52 | continue; 53 | 54 | struct input_event ev; 55 | rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); 56 | 57 | if (rc != 0) 58 | continue; 59 | 60 | if (ev.type == 3 && ev.code == 57) { 61 | if (ev.value != -1) { 62 | fingers_held++; 63 | start_timestamp = ev.time.tv_sec * 1000000; 64 | start_timestamp += ev.time.tv_usec; 65 | } 66 | else { 67 | fingers_held--; 68 | long stop_timestamp = ev.time.tv_sec * 1000000; 69 | stop_timestamp += ev.time.tv_usec; 70 | if (stop_timestamp - start_timestamp < timeout * 1000 && fingers_held == 0) { 71 | if (tap_cmd != "") { 72 | std::thread([this]() { 73 | system(tap_cmd.c_str()); 74 | }).detach(); 75 | } 76 | } 77 | } 78 | } 79 | else if (select_result < 0) 80 | break; 81 | } 82 | 83 | close(pipefd[0]); 84 | close(pipefd[1]); 85 | }); 86 | 87 | thread_tap_listener.detach(); 88 | } 89 | 90 | void tap_to_wake::stop_listener() { 91 | thread_tap_listener.request_stop(); 92 | write(pipefd[1], "x", 1); 93 | libevdev_free(dev); 94 | close(fd); 95 | running = false; 96 | } 97 | -------------------------------------------------------------------------------- /src/tap_to_wake.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "config.hpp" 3 | #ifdef FEATURE_TAP_TO_WAKE 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class tap_to_wake { 11 | public: 12 | tap_to_wake(const std::map>&); 13 | ~tap_to_wake(); 14 | 15 | bool running; 16 | void start_listener(); 17 | void stop_listener(); 18 | 19 | private: 20 | std::map> config_main; 21 | int pipefd[2]; 22 | int fd; 23 | int rc; 24 | struct libevdev* dev = nullptr; 25 | std::jthread thread_tap_listener; 26 | long start_timestamp = 0; 27 | int fingers_held = 0; 28 | 29 | // Configs 30 | std::string device_path = "/dev/input/by-path/SET-ME-UP"; 31 | int timeout = 500; 32 | std::string tap_cmd = ""; 33 | }; 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/window.cpp: -------------------------------------------------------------------------------- 1 | #include "window.hpp" 2 | #include "css.hpp" 3 | #include "auth.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | syslock::syslock(const std::map>& cfg) { 13 | config_main = cfg; 14 | 15 | // Initialize 16 | profile_picture_path = config_main["profile"]["image-path"]; 17 | profile_scale = std::stoi(config_main["profile"]["scale"]); 18 | profile_rounding = std::stoi(config_main["profile"]["rounding"]); 19 | time_format = config_main["clock"]["time-format"]; 20 | date_format = config_main["clock"]["date-format"]; 21 | lock_cmd = config_main["events"]["on-lock-cmd"]; 22 | unlock_cmd = config_main["events"]["on-unlock-cmd"]; 23 | 24 | // Set up drag gestures 25 | gesture_drag = Gtk::GestureDrag::create(); 26 | gesture_drag->signal_drag_begin().connect(sigc::mem_fun(*this, &syslock::on_drag_start)); 27 | gesture_drag->signal_drag_update().connect(sigc::mem_fun(*this, &syslock::on_drag_update)); 28 | gesture_drag->signal_drag_end().connect(sigc::mem_fun(*this, &syslock::on_drag_stop)); 29 | dispatcher_auth.connect(sigc::mem_fun(*this, &syslock::auth_end)); 30 | 31 | // Tap to wake 32 | #ifdef FEATURE_TAP_TO_WAKE 33 | listener = new tap_to_wake(config_main); 34 | #endif 35 | 36 | // Load custom css 37 | std::string style_path; 38 | const std::string& home_dir = getenv("HOME"); 39 | if (std::filesystem::exists(home_dir + "/.config/sys64/lock/style.css")) 40 | style_path = home_dir + "/.config/sys64/lock/style.css"; 41 | else if (std::filesystem::exists("/usr/share/sys64/lock/style.css")) 42 | style_path = "/usr/share/sys64/lock/style.css"; 43 | else 44 | style_path = "/usr/local/share/sys64/lock/style.css"; 45 | 46 | if (std::filesystem::exists(style_path)) { 47 | auto css = Gtk::CssProvider::create(); 48 | css->load_from_path(style_path); 49 | auto display = Gdk::Display::get_default(); 50 | Gtk::StyleContext::add_provider_for_display( 51 | display, 52 | css, 53 | GTK_STYLE_PROVIDER_PRIORITY_USER 54 | ); 55 | } 56 | 57 | // Set classes properly (No clue why this has to be done this way, Don't question it) 58 | // signal_map().connect([this] () { 59 | // get_style_context()->remove_class("locked"); 60 | // Glib::signal_timeout().connect([this]() { 61 | // get_style_context()->add_class("locked"); 62 | // return false; 63 | // }, 100); 64 | // // TODO: This is unstable, Sometimes it works sometimes it doesn't 65 | // // Figure out why it's like this somehow. 66 | // // Also for some reason this causes the window to show twice? 67 | // // if (config_main["main"]["experimental"] == "true") 68 | // // lock_session(this); 69 | // }); 70 | 71 | // Monitor connect/disconnect detection 72 | auto display = Gdk::Display::get_default(); 73 | auto monitors = display->get_monitors(); 74 | monitors->signal_items_changed().connect([this](guint position, guint removed, guint added) { 75 | Glib::signal_timeout().connect([this, position, removed, added]() { 76 | on_monitors_changed(position, removed, added); 77 | return false; 78 | }, 100); 79 | }, true); 80 | 81 | handle_monitors_initial(); 82 | lock(); 83 | } 84 | 85 | void syslock::auth_start() { 86 | label_error.hide(); 87 | std::string password = entry_password.get_buffer()->get_text().raw(); 88 | 89 | // Remove focus 90 | Gtk::Button focus_dummy; 91 | box_layout.append(focus_dummy); 92 | focus_dummy.grab_focus(); 93 | entry_password.set_sensitive(false); 94 | box_layout.remove(focus_dummy); 95 | 96 | std::thread thread_auth([&, password]() { 97 | char *user = getenv("USER"); 98 | auth = authenticate(user, password.c_str()); 99 | dispatcher_auth.emit(); 100 | }); 101 | thread_auth.detach(); 102 | } 103 | 104 | void syslock::auth_end() { 105 | if (auth) { 106 | if (config_main["main"]["experimental"] == "true") 107 | unlock_session(); 108 | 109 | #ifdef FEATURE_TAP_TO_WAKE 110 | if (listener->running) 111 | listener->stop_listener(); 112 | #endif 113 | 114 | // Add a delay for fancy css animations 115 | for (std::vector::iterator it = windows.begin(); it != windows.end(); ++it) { 116 | Gtk::Window* window = *it; 117 | window->get_style_context()->remove_class("locked"); 118 | window->get_style_context()->add_class("unlocked"); 119 | } 120 | 121 | Glib::signal_timeout().connect([this]() { 122 | for (std::vector::iterator it = windows.begin(); it != windows.end(); ++it) { 123 | Gtk::Window* window = *it; 124 | window->get_style_context()->add_class("locked"); 125 | window->get_style_context()->remove_class("unlocked"); 126 | window->hide(); 127 | locked = false; 128 | } 129 | entry_password.set_text(""); 130 | connection.disconnect(); 131 | 132 | if (unlock_cmd != "") { 133 | std::thread([this]() { 134 | system(unlock_cmd.c_str()); 135 | }).detach(); 136 | } 137 | return false; 138 | }, 250); 139 | } 140 | else { 141 | // TODO: Display how many times the user can retry the password 142 | entry_password.set_text(""); 143 | label_error.show(); 144 | } 145 | entry_password.set_sensitive(true); 146 | } 147 | 148 | void syslock::on_entry_changed() { 149 | // Trigger a password check automatically 150 | if ((int)entry_password.get_text().length() == std::stoi(config_main["main"]["password-length"])) { 151 | auth_start(); 152 | } 153 | } 154 | 155 | void syslock::on_monitors_changed(guint position, guint removed, guint added) { 156 | // Get current monitors 157 | auto display = Gdk::Display::get_default(); 158 | auto monitor_list = display->get_monitors(); 159 | guint n_monitors = monitor_list->get_n_items(); 160 | 161 | // Create a set of current monitor connectors 162 | std::set current_monitors; 163 | for (guint i = 0; i < n_monitors; ++i) { 164 | GObject* raw = static_cast(g_list_model_get_item(monitor_list->gobj(), i)); 165 | auto monitor = GDK_MONITOR(raw); 166 | const char* connector = gdk_monitor_get_connector(monitor); 167 | current_monitors.insert(connector); 168 | g_object_unref(raw); 169 | } 170 | 171 | // Remove windows for disconnected monitors 172 | std::vector to_remove; 173 | for (auto& pair : monitor_windows) { 174 | if (current_monitors.find(pair.first) == current_monitors.end()) { 175 | auto it = std::find(windows.begin(), windows.end(), pair.second); 176 | if (it != windows.end()) 177 | windows.erase(it); 178 | 179 | pair.second->hide(); 180 | delete pair.second; 181 | 182 | to_remove.push_back(pair.first); 183 | } 184 | } 185 | 186 | // Clean up the map 187 | for (const auto& key : to_remove) { 188 | monitor_windows.erase(key); 189 | } 190 | 191 | // Handle new monitors 192 | for (guint i = 0; i < n_monitors; ++i) { 193 | GObject* raw = static_cast(g_list_model_get_item(monitor_list->gobj(), i)); 194 | auto monitor = GDK_MONITOR(raw); 195 | const char* connector = gdk_monitor_get_connector(monitor); 196 | std::string conn_str(connector); 197 | 198 | // If this monitor doesn't have a window yet, create one 199 | if (monitor_windows.find(conn_str) == monitor_windows.end()) { 200 | Gtk::Window* window; 201 | std::string main_monitor = config_main["main"]["main-monitor"]; 202 | 203 | if (main_monitor == conn_str || (main_monitor == "" && i == 0)) { 204 | window = create_main_window(monitor); 205 | primary_window = window; 206 | } 207 | else { 208 | window = create_secondary_window(monitor); 209 | } 210 | 211 | monitor_windows[conn_str] = window; 212 | 213 | if (locked) { 214 | window->show(); 215 | window->get_style_context()->add_class("locked"); 216 | 217 | if (main_monitor == conn_str || (main_monitor == "" && i == 0)) { 218 | GdkRectangle geometry; 219 | gdk_monitor_get_geometry(monitor, &geometry); 220 | window_height = geometry.height; 221 | } 222 | } 223 | else if (config_main["main"]["start-unlocked"] != "true") 224 | window->show(); 225 | } 226 | 227 | g_object_unref(raw); 228 | } 229 | 230 | if (primary_window == nullptr && !windows.empty()) { 231 | primary_window = windows[0]; 232 | } 233 | 234 | if (locked && primary_window != nullptr) { 235 | entry_password.grab_focus(); 236 | } 237 | } 238 | 239 | void syslock::handle_monitors_initial() { 240 | auto display = Gdk::Display::get_default(); 241 | auto monitor_list = display->get_monitors(); 242 | guint n_monitors = monitor_list->get_n_items(); 243 | 244 | std::string main_monitor = config_main["main"]["main-monitor"]; 245 | for (guint i = 0; i < n_monitors; ++i) { 246 | GObject* raw = static_cast(g_list_model_get_item(monitor_list->gobj(), i)); 247 | auto monitor = GDK_MONITOR(raw); 248 | const char* connector = gdk_monitor_get_connector(monitor); 249 | std::string conn_str(connector); 250 | Gtk::Window* window; 251 | 252 | // TODO: Fix improper monitor names (Fallback to the first available monitor) 253 | if (main_monitor == conn_str || (main_monitor == "" && i == 0)) { 254 | window = create_main_window(monitor); 255 | primary_window = window; 256 | } 257 | else { 258 | window = create_secondary_window(monitor); 259 | } 260 | 261 | monitor_windows[conn_str] = window; 262 | 263 | if (config_main["main"]["start-unlocked"] != "true") { 264 | window->show(); 265 | if (main_monitor == conn_str || (main_monitor == "" && i == 0)) { 266 | window->get_style_context()->add_class("locked"); 267 | GdkRectangle geometry; 268 | gdk_monitor_get_geometry(monitor, &geometry); 269 | window_height = geometry.height; 270 | } 271 | } 272 | 273 | g_object_unref(raw); 274 | } 275 | } 276 | 277 | Gtk::Window* syslock::create_main_window(GdkMonitor* monitor) { 278 | Gtk::Window* window = Gtk::make_managed(); 279 | setup_window(window->gobj(), monitor, "syslock"); 280 | window->set_default_size(640, 480); 281 | window->set_hide_on_close(true); 282 | 283 | window->set_child(overlay); 284 | overlay.set_child(box_lock_screen); 285 | box_lock_screen.get_style_context()->add_class("lock_screen"); 286 | box_lock_screen.property_orientation().set_value(Gtk::Orientation::VERTICAL); 287 | 288 | // TODO: Add a config option to select what appears on the lockscreen 289 | box_lock_screen.append(label_time); 290 | label_time.get_style_context()->add_class("time"); 291 | 292 | box_lock_screen.append(label_date); 293 | label_date.get_style_context()->add_class("date"); 294 | 295 | Glib::signal_timeout().connect(sigc::mem_fun(*this, &syslock::update_time_date), 1000); 296 | update_time_date(); 297 | 298 | overlay.add_overlay(box_layout); 299 | box_layout.append(scrolled_window); 300 | box_layout.get_style_context()->add_class("login_screen"); 301 | box_layout.property_orientation().set_value(Gtk::Orientation::VERTICAL); 302 | box_layout.set_opacity(0); 303 | 304 | scrolled_window.set_kinetic_scrolling(false); 305 | scrolled_window.set_child(box_login_screen); 306 | scrolled_window.set_policy(Gtk::PolicyType::EXTERNAL, Gtk::PolicyType::EXTERNAL); 307 | scrolled_window.set_valign(Gtk::Align::END); 308 | 309 | box_login_screen.property_orientation().set_value(Gtk::Orientation::HORIZONTAL); 310 | box_login_screen.set_valign(Gtk::Align::CENTER); 311 | box_login_screen.set_vexpand(true); 312 | box_login_screen.append(box_widgets); 313 | 314 | box_widgets.set_orientation(Gtk::Orientation::VERTICAL); 315 | box_widgets.set_valign(Gtk::Align::CENTER); 316 | box_widgets.set_hexpand(true); 317 | 318 | // And add a way to enable/disable specific features (PFP, Username, Ect) 319 | const std::string& home_dir = getenv("HOME"); 320 | if (profile_picture_path == "") 321 | profile_picture_path = home_dir + "/.face"; 322 | 323 | if (std::filesystem::exists(profile_picture_path) && profile_scale > 0) { 324 | box_widgets.append(image_profile); 325 | image_profile.get_style_context()->add_class("image_profile"); 326 | auto pixbuf = Gdk::Pixbuf::create_from_file(profile_picture_path); 327 | pixbuf = pixbuf->scale_simple(profile_scale, profile_scale, Gdk::InterpType::BILINEAR); 328 | 329 | // Round the image 330 | if (profile_rounding != 0) 331 | pixbuf = create_rounded_pixbuf(pixbuf, profile_scale, profile_rounding); 332 | 333 | image_profile.set_size_request(profile_scale, profile_scale); 334 | image_profile.set(pixbuf); 335 | image_profile.set_halign(Gtk::Align::CENTER); 336 | } 337 | 338 | box_widgets.append(label_username); 339 | label_username.get_style_context()->add_class("label_username"); 340 | const uid_t& uid = geteuid(); 341 | struct passwd *pw = getpwuid(uid); 342 | label_username.set_text((Glib::ustring)pw->pw_gecos); 343 | 344 | box_widgets.append(entry_password); 345 | entry_password.get_style_context()->add_class("entry_password"); 346 | entry_password.set_size_request(250, 30); 347 | entry_password.set_halign(Gtk::Align::CENTER); 348 | entry_password.set_visibility(false); 349 | entry_password.set_input_purpose(Gtk::InputPurpose::PASSWORD); 350 | entry_password.signal_activate().connect(sigc::mem_fun(*this, &syslock::auth_start)); 351 | entry_password.signal_changed().connect(sigc::mem_fun(*this, &syslock::on_entry_changed)); 352 | entry_password.grab_focus(); 353 | entry_password.signal_changed().connect([&]() { 354 | if (entry_password.get_text() == "") 355 | return; 356 | 357 | scrolled_window.set_valign(Gtk::Align::FILL); 358 | box_layout.set_opacity(1); 359 | scrolled_window.set_size_request(-1, primary_window->get_height()); 360 | connection.disconnect(); 361 | connection = Glib::signal_timeout().connect([&]() { 362 | lock(); 363 | return false; 364 | }, 10 * 1000); 365 | }); 366 | 367 | // TODO: add remaining tries left 368 | box_widgets.append(label_error); 369 | label_error.get_style_context()->add_class("label_error"); 370 | label_error.set_text("Incorrect password"); 371 | label_error.hide(); 372 | 373 | // Keypad 374 | if (config_main["main"]["keypad"] == "true") { 375 | keypad_main = Gtk::make_managed(entry_password, sigc::mem_fun(*this, &syslock::auth_start)); 376 | box_login_screen.append(*keypad_main); 377 | keypad_main->set_hexpand(true); 378 | entry_password.set_input_purpose(Gtk::InputPurpose::PIN); 379 | } 380 | 381 | 382 | 383 | window->signal_show().connect([&, window]() { 384 | Glib::signal_timeout().connect([&, window]() { 385 | if (window->get_height() > window->get_width()) 386 | box_login_screen.set_orientation(Gtk::Orientation::VERTICAL); 387 | else 388 | box_login_screen.set_orientation(Gtk::Orientation::HORIZONTAL); 389 | return false; 390 | }, 500); 391 | }); 392 | 393 | overlay.add_controller(gesture_drag); 394 | windows.push_back(window); 395 | return window; 396 | } 397 | 398 | Gtk::Window* syslock::create_secondary_window(GdkMonitor* monitor) { 399 | Gtk::Window* window = Gtk::make_managed(); 400 | setup_window(window->gobj(), monitor, "syslock-empty-window"); 401 | window->set_default_size(640, 480); 402 | window->set_hide_on_close(true); 403 | 404 | windows.push_back(window); 405 | return window; 406 | } 407 | 408 | void syslock::setup_window(GtkWindow* window, GdkMonitor* monitor, const char* name) { 409 | gtk_widget_set_name(GTK_WIDGET(window), name); 410 | 411 | if (config_main["main"]["debug"] == "true") 412 | return; 413 | 414 | gtk_layer_init_for_window(window); 415 | gtk_layer_set_namespace(window, name); 416 | gtk_layer_set_monitor(window, monitor); 417 | gtk_layer_set_layer(window, GTK_LAYER_SHELL_LAYER_TOP); 418 | gtk_layer_set_keyboard_mode(window, GTK_LAYER_SHELL_KEYBOARD_MODE_EXCLUSIVE); 419 | 420 | gtk_layer_set_anchor(window, GTK_LAYER_SHELL_EDGE_LEFT, true); 421 | gtk_layer_set_anchor(window, GTK_LAYER_SHELL_EDGE_RIGHT, true); 422 | gtk_layer_set_anchor(window, GTK_LAYER_SHELL_EDGE_TOP, true); 423 | gtk_layer_set_anchor(window, GTK_LAYER_SHELL_EDGE_BOTTOM, true); 424 | } 425 | 426 | void syslock::on_drag_start(const double &x, const double &y) { 427 | connection.disconnect(); 428 | 429 | // Block gesture inputs from the keypad 430 | if (config_main["main"]["keypad"] == "true") { 431 | double keypad_x, keypad_y; 432 | keypad_main->translate_coordinates(box_lock_screen, 0, 0, keypad_x, keypad_y); 433 | 434 | if (x >= keypad_x && x <= keypad_x + keypad_main->get_width() && 435 | y >= keypad_y && y <= keypad_y + keypad_main->get_height()) { 436 | gesture_drag->reset(); 437 | } 438 | } 439 | 440 | if (!gesture_drag->get_current_event()->get_pointer_emulated()) { 441 | scrolled_window.set_valign(Gtk::Align::FILL); 442 | box_layout.set_opacity(1); 443 | scrolled_window.set_size_request(-1, primary_window->get_height()); 444 | gesture_drag->reset(); 445 | return; 446 | } 447 | 448 | start_height = scrolled_window.get_height(); 449 | scrolled_window.set_valign(Gtk::Align::END); 450 | if (start_height > 300) { 451 | scrolled_window.set_size_request(-1, primary_window->get_height()); 452 | } 453 | } 454 | 455 | void syslock::on_drag_update(const double &x, const double &y) { 456 | if (start_height < 100) { 457 | if (scrolled_window.get_height() >= window_height) 458 | return; 459 | scrolled_window.set_size_request(-1, std::min(window_height, std::max(0.0, - y))); 460 | } 461 | else { 462 | if (-y > 0) 463 | return; 464 | scrolled_window.set_size_request(-1, std::min(window_height, std::max(0.0, start_height - y))); 465 | } 466 | 467 | box_layout.set_opacity(scrolled_window.get_height() / window_height); 468 | } 469 | 470 | void syslock::on_drag_stop(const double &x, const double &y) { 471 | connection = Glib::signal_timeout().connect([&]() {lock();return false;}, 10 * 1000); 472 | if (!gesture_drag->get_current_event()->get_pointer_emulated()) 473 | return; 474 | 475 | if (scrolled_window.get_height() > 300) { 476 | scrolled_window.set_valign(Gtk::Align::FILL); 477 | box_layout.set_opacity(1); 478 | #ifdef FEATURE_TAP_TO_WAKE 479 | if (listener->running) 480 | listener->stop_listener(); 481 | #endif 482 | } 483 | else { 484 | scrolled_window.set_valign(Gtk::Align::END); 485 | box_layout.set_opacity(0); 486 | #ifdef FEATURE_TAP_TO_WAKE 487 | if (!listener->running) 488 | listener->start_listener(); 489 | #endif 490 | } 491 | scrolled_window.set_size_request(-1, -1); 492 | } 493 | 494 | bool syslock::update_time_date() { 495 | const std::time_t& now = std::time(nullptr); 496 | std::tm* local_time = std::localtime(&now); 497 | 498 | char time_buffer[32]; 499 | std::strftime(time_buffer, sizeof(time_buffer), time_format.c_str(), local_time); 500 | label_time.set_text(time_buffer); 501 | 502 | char date_buffer[32]; 503 | std::strftime(date_buffer, sizeof(date_buffer), date_format.c_str(), local_time); 504 | label_date.set_text(date_buffer); 505 | return true; 506 | } 507 | 508 | void syslock::lock() { 509 | scrolled_window.set_valign(Gtk::Align::END); 510 | box_layout.set_opacity(0); 511 | scrolled_window.set_size_request(-1, -1); 512 | entry_password.set_text(""); 513 | 514 | if (locked) 515 | return; 516 | 517 | if (lock_cmd != "") { 518 | std::thread([this]() { 519 | system(lock_cmd.c_str()); 520 | }).detach(); 521 | } 522 | 523 | #ifdef FEATURE_TAP_TO_WAKE 524 | if (!listener->running) 525 | listener->start_listener(); 526 | #endif 527 | 528 | locked = true; 529 | } 530 | 531 | extern "C" { 532 | syslock *syslock_create(const std::map>& cfg) { 533 | return new syslock(cfg); 534 | } 535 | 536 | void syslock_lock(syslock *window) { 537 | window->lock(); 538 | for (std::vector::iterator it = window->windows.begin(); it != window->windows.end(); ++it) { 539 | Gtk::Window* window = *it; 540 | window->show(); 541 | } 542 | window->entry_password.grab_focus(); 543 | } 544 | } 545 | -------------------------------------------------------------------------------- /src/window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include "config.hpp" 17 | #include "config_parser.hpp" 18 | #include "keypad.hpp" 19 | #include "tap_to_wake.hpp" 20 | 21 | class syslock { 22 | 23 | public: 24 | syslock(const std::map>&); 25 | void lock(); 26 | 27 | std::map> config_main; 28 | std::vector windows; 29 | Gtk::Window* primary_window; 30 | Gtk::Entry entry_password; 31 | 32 | private: 33 | bool locked; 34 | double window_height; 35 | int start_height; 36 | bool auth; 37 | sigc::connection connection; 38 | std::map monitor_windows; 39 | 40 | #ifdef CONFIG_FILE 41 | config_parser *config; 42 | #endif 43 | 44 | std::string lock_cmd = ""; 45 | std::string unlock_cmd = ""; 46 | 47 | Gtk::Box box_lock_screen; 48 | Gtk::Box box_login_screen; 49 | Gtk::Box box_layout; 50 | Gtk::Box box_widgets; 51 | Gtk::ScrolledWindow scrolled_window; 52 | Gtk::Overlay overlay; 53 | 54 | Gtk::Label label_time; 55 | Gtk::Label label_date; 56 | std::string time_format = "%H:%M"; 57 | std::string date_format = "%a %d %b"; 58 | 59 | Gtk::Image image_profile; 60 | std::string profile_picture_path = ""; 61 | int profile_scale = 128; 62 | int profile_rounding = 64; 63 | 64 | Gtk::Label label_username; 65 | Gtk::Label label_error; 66 | Glib::RefPtr gesture_drag; 67 | Glib::Dispatcher dispatcher_auth; 68 | keypad *keypad_main; 69 | std::thread thread_auth; 70 | 71 | #ifdef FEATURE_TAP_TO_WAKE 72 | tap_to_wake *listener; 73 | #endif 74 | 75 | Glib::RefPtr create_rounded_pixbuf(const Glib::RefPtr &src_pixbuf, const int &size, const int &rounding_radius); 76 | 77 | Gtk::Window* create_main_window(GdkMonitor* monitor); 78 | Gtk::Window* create_secondary_window(GdkMonitor* monitor); 79 | 80 | void auth_start(); 81 | void auth_end(); 82 | void on_entry_changed(); 83 | void handle_monitors_initial(); 84 | void on_monitors_changed(guint position, guint removed, guint added); 85 | void setup_window(GtkWindow *window, GdkMonitor *monitor, const char* name); 86 | 87 | void on_drag_start(const double &x, const double &y); 88 | void on_drag_update(const double &x, const double &y); 89 | void on_drag_stop(const double &x, const double &y); 90 | 91 | bool update_time_date(); 92 | }; 93 | 94 | extern "C" { 95 | syslock *syslock_create(const std::map>&); 96 | void syslock_lock(syslock* window); 97 | } 98 | 99 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | #syslock, 2 | #syslock-empty-window { 3 | background-color: rgba(0, 0, 0, 0.5); 4 | background-image: url("/path/to/background.jpg"); 5 | } 6 | 7 | /* Lock screen */ 8 | #syslock .lock_screen { 9 | } 10 | 11 | #syslock .time { 12 | margin-top: 200px; 13 | font-size: 96px; 14 | } 15 | #syslock .date { 16 | font-size: 18px; 17 | } 18 | 19 | /* Login screen */ 20 | #syslock .login_screen { 21 | background: rgba(0, 0, 0, 0.5); 22 | } 23 | 24 | #syslock .image_profile { 25 | border: 3px solid white; 26 | border-radius: 50%; 27 | margin: 10px; 28 | } 29 | 30 | #syslock .label_username { 31 | font-size: 22px; 32 | margin: 10px; 33 | } 34 | 35 | #syslock .entry_password { 36 | margin: 10px; 37 | } 38 | 39 | #syslock .label_error { 40 | color: rgb(255,150,125); 41 | } 42 | --------------------------------------------------------------------------------