diff --git a/.gitea b/.gitea new file mode 100644 index 00000000..e1be9f47 --- /dev/null +++ b/.gitea @@ -0,0 +1,15 @@ +[submodule "sub/argoat"] + path = sub/argoat + url = https://git.cylgom.net/cylgom/argoat.git +[submodule "sub/configator"] + path = sub/configator + url = https://git.cylgom.net/cylgom/configator.git +[submodule "sub/ctypes"] + path = sub/ctypes + url = https://git.cylgom.net/cylgom/ctypes.git +[submodule "sub/dragonfail"] + path = sub/dragonfail + url = https://git.cylgom.net/cylgom/dragonfail.git +[submodule "sub/termbox_next"] + path = sub/termbox_next + url = https://git.cylgom.net/cylgom/termbox_next.git diff --git a/.github b/.github new file mode 100644 index 00000000..9eddfbbd --- /dev/null +++ b/.github @@ -0,0 +1,15 @@ +[submodule "sub/argoat"] + path = sub/argoat + url = https://github.com/cylgom/argoat.git +[submodule "sub/configator"] + path = sub/configator + url = https://github.com/cylgom/configator.git +[submodule "sub/ctypes"] + path = sub/ctypes + url = https://github.com/cylgom/ctypes.git +[submodule "sub/dragonfail"] + path = sub/dragonfail + url = https://github.com/cylgom/dragonfail.git +[submodule "sub/termbox_next"] + path = sub/termbox_next + url = https://github.com/cylgom/termbox_next.git diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..836a4d8a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +bin +obj +.gitmodules +valgrind.log diff --git a/license.md b/license.md new file mode 100644 index 00000000..5c93f456 --- /dev/null +++ b/license.md @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/makefile b/makefile new file mode 100644 index 00000000..d9db2d54 --- /dev/null +++ b/makefile @@ -0,0 +1,105 @@ +NAME = ly +CC = gcc +FLAGS = -std=c99 -pedantic -g +FLAGS+= -Wall -Wno-unused-parameter -Wextra -Werror=vla -Werror +#FLAGS+= -DDEBUG +FLAGS+= -DGIT_VERSION_STRING=\"$(shell git describe --long --tags | sed 's/\([^-]*-g\)/r\1/;s/-/./g')\" +LINK = -lpam -lxcb +VALGRIND = --show-leak-kinds=all --track-origins=yes --leak-check=full --suppressions=../res/valgrind.supp +CMD = ./$(NAME) + +OS:= $(shell uname -s) +ifeq ($(OS), Linux) + FLAGS+= -D_DEFAULT_SOURCE +endif + +BIND = bin +OBJD = obj +SRCD = src +SUBD = sub +RESD = res +TESTD = tests + +INCL = -I$(SRCD) +INCL+= -I$(SUBD)/ctypes +INCL+= -I$(SUBD)/argoat/src +INCL+= -I$(SUBD)/configator/src +INCL+= -I$(SUBD)/dragonfail/src +INCL+= -I$(SUBD)/termbox_next/src + +SRCS = $(SRCD)/main.c +SRCS += $(SRCD)/config.c +SRCS += $(SRCD)/draw.c +SRCS += $(SRCD)/inputs.c +SRCS += $(SRCD)/login.c +SRCS += $(SRCD)/utils.c +SRCS += $(SUBD)/argoat/src/argoat.c +SRCS += $(SUBD)/configator/src/configator.c +SRCS += $(SUBD)/dragonfail/src/dragonfail.c + +SRCS_OBJS:= $(patsubst %.c,$(OBJD)/%.o,$(SRCS)) +SRCS_OBJS+= $(SUBD)/termbox_next/bin/termbox.a + +.PHONY: final +final: $(BIND)/$(NAME) + +$(OBJD)/%.o: %.c + @echo "building object $@" + @mkdir -p $(@D) + @$(CC) $(INCL) $(FLAGS) -c -o $@ $< + +$(SUBD)/termbox_next/bin/termbox.a: + @echo "building static object $@" + @(cd $(SUBD)/termbox_next && $(MAKE)) + +$(BIND)/$(NAME): $(SRCS_OBJS) + @echo "compiling executable $@" + @mkdir -p $(@D) + @$(CC) -o $@ $^ $(LINK) + +run: + @cd $(BIND) && $(CMD) + +leak: leakgrind +leakgrind: $(BIND)/$(NAME) + @rm -f valgrind.log + @cd $(BIND) && valgrind $(VALGRIND) 2> ../valgrind.log $(CMD) + @less valgrind.log + +install: $(BIND)/$(NAME) + @echo "installing" + @install -dZ ${DESTDIR}/etc/ly + @install -DZ $(BIND)/$(NAME) -t ${DESTDIR}/usr/bin + @install -DZ $(RESD)/xsetup.sh -t ${DESTDIR}/etc/ly + @install -DZ $(RESD)/wsetup.sh -t ${DESTDIR}/etc/ly + @install -DZ $(RESD)/config.ini -t ${DESTDIR}/etc/ly + @install -dZ ${DESTDIR}/etc/ly/lang + @install -DZ $(RESD)/lang/* -t ${DESTDIR}/etc/ly/lang + @install -DZ $(RESD)/ly.service -t ${DESTDIR}/usr/lib/systemd/system + +uninstall: + @echo "uninstalling" + @rm -rf ${DESTDIR}/etc/ly + @rm -f ${DESTDIR}/usr/bin/ly + @rm -f ${DESTDIR}/usr/lib/systemd/system/ly.service + +clean: + @echo "cleaning" + @rm -rf $(BIND) $(OBJD) valgrind.log + @(cd $(SUBD)/termbox_next && $(MAKE) clean) + +github: + @echo "sourcing submodules from https://github.com" + @cp .github .gitmodules + @git submodule sync + @git submodule update --init --remote + @cd $(SUBD)/argoat && make github + @git submodule update --init --recursive --remote + +gitea: + @echo "sourcing submodules from https://git.cylgom.net" + @cp .gitea .gitmodules + @git submodule sync + @git submodule update --init --remote + @cd $(SUBD)/argoat && make gitea + @git submodule update --init --recursive --remote diff --git a/readme.md b/readme.md new file mode 100644 index 00000000..91eeb722 --- /dev/null +++ b/readme.md @@ -0,0 +1,104 @@ +# Ly - a TUI display manager +[![CodeFactor](https://www.codefactor.io/repository/github/cylgom/ly/badge/master)](https://www.codefactor.io/repository/github/cylgom/ly/overview/master) +![Ly screenshot](https://user-images.githubusercontent.com/5473047/42466218-8cb53d3c-83ae-11e8-8e53-bae3669f959c.png "Ly screenshot") + +Ly is a lightweight TUI (ncurses-like) display manager for Linux and BSD. + +## Dependencies + - a C99 compiler (tested with tcc and gcc) + - a C standard library + - make + - pam + - xcb + - xorg + - xorg-xauth + - mcookie + - tput + - shutdown + +## Support +The following desktop environments were tested with success + - budgie + - cinnamon + - deepin + - enlightenment + - gnome + - i3 + - kde + - lxde + - lxqt + - mate + - sway + - xfce + - pantheon + +Ly should work with any X desktop environment, and provides +basic wayland support (sway works very well, for example). + +## systemd? +Unlike what you may have heard, Ly does not require `systemd`, +and was even specifically designed not to depend on `logind`. +You should be able to make it work easily with a better init, +changing the source code won't be necessary :) + +## Cloning and Compiling +Clone the repository +``` +git clone https://github.com/cylgom/ly.git +``` + +Fetch submodules +``` +make github +``` + +Compile +``` +make +``` + +Test in the configured tty (tty2 by default) +or a terminal emulator (but desktop environments won't start) +``` +sudo make run +``` + +Install Ly and the provided systemd service file +``` +sudo make install +``` + +Enable the service +``` +sudo systemctl enable ly.service +``` + +If you need to switch between ttys after Ly's start you also have to +disable getty on Ly's tty to prevent "login" from spawning on top of it +``` +sudo systemctl disable getty@tty2.service +``` + +## Configuration +You can find all the configuration in `/etc/ly/config.ini`. +The file is commented, and includes the default values. + +## Controls +Use the up and down arrow keys to change the current field, and the +left and right arrow keys to change the target desktop environment +while on the desktop field (above the login field). + +## Tips +The numlock and capslock state is printed in the top-right corner. +Use the F1 and F2 keys to respectively shutdown and reboot. +Take a look at your .xsession if X doesn't start, as it can interfere +(this file is launched with X to configure the display properly). + +## PSX DOOM fire animation +To enable the famous PSX DOOM fire described by [Fabien Sanglard](http://fabiensanglard.net/doom_fire_psx/index.html), +just uncomment `animate = true` in `/etc/ly/config.ini`. You may also +disable the main box borders with `hide_borders = true`. + +## Additional Information +The name "Ly" is a tribute to the fairy from the game Rayman. +Ly was tested by oxodao, who is some seriously awesome dude. diff --git a/res/config.ini b/res/config.ini new file mode 100644 index 00000000..d33004eb --- /dev/null +++ b/res/config.ini @@ -0,0 +1,99 @@ +# animation enabled +#animate = false +#animate = true + +# the active animation (only animation '0' available for now) +#animation = 0 + +# the char used to mask the password +#asterisk = * +#asterisk = o + +# background color id +#bg = 0 + +# blank main box +#blank_box = true + +# erase password input on failure +#blank_password = true + +# console path +#console_dev = /dev/console + +# input active by default on startup +#default_input = 2 + +# foreground color id +#fg = 9 + +# remove main box borders +#hide_borders = false +#hide_borders = true + +# number of visible chars on an input +#input_len = 34 + +# active language +#lang = en +#lang = fr + +# load the saved desktop and login +#load = true + +# main box margins +#margin_box_h = 2 +#margin_box_v = 1 + +# total input sizes +#max_desktop_len = 100 +#max_login_len = 255 +#max_password_len = 255 + +# cookie generator +#mcookie_cmd = /usr/bin/mcookie + +# event timeout in milliseconds +#min_refresh_delta = 5 + +# default path +#path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/env + +# command executed when pressing F2 +#restart_cmd = /sbin/shutdown -r now + +# save the current desktop and login as defaults +#save = true + +# file in which to save and load the default desktop and login +#save_file = /etc/ly/save + +# service name (pam needs this set to login) +#service_name = login + +# command executed when pressing F1 +#shutdown_cmd = /sbin/shutdown -a now + +# terminal reset command (tput is faster) +#term_reset_cmd = /usr/bin/tput reset + +# tty in use +#tty = 2 + +# wayland setup command +#wayland_cmd = /etc/ly/wsetup.sh + +# wayland desktop environments +#waylandsessions = /usr/share/wayland-sessions + +# xorg server command +#x_cmd = /usr/bin/X + +# xorg setup command +#x_cmd_setup = /etc/ly/xsetup.sh + +# xorg xauthority edition tool +#xauth_cmd = /usr/bin/xauth + +# xorg desktop environments +#xsessions = /usr/share/xsessions diff --git a/res/lang/en.ini b/res/lang/en.ini new file mode 100644 index 00000000..134c637a --- /dev/null +++ b/res/lang/en.ini @@ -0,0 +1,45 @@ +capslock = capslock +err_alloc = failed memory allocation +err_bounds = out-of-bounds index +err_chdir = failed to open home folder +err_console_dev = failed to access console +err_dgn_oob = log message +err_domain = invalid domain +err_hostname = failed to get hostname +err_mlock = failed to lock password memory +err_null = null pointer +err_pam = pam transaction failed +err_pam_abort = pam transaction aborted +err_pam_acct_expired = account expired +err_pam_auth = authentication error +err_pam_authinfo_unavail = failed to get user info +err_pam_authok_reqd = token expired +err_pam_buf = memory buffer error +err_pam_cred_err = failed to set credentials +err_pam_cred_expired = credentials expired +err_pam_cred_insufficient = insufficient credentials +err_pam_cred_unavail = failed to get credentials +err_pam_maxtries = reached maximum tries limit +err_pam_perm_denied = permission denied +err_pam_session = session error +err_pam_sys = system error +err_pam_user_unknown = unknown user +err_path = failed to set path +err_perm_dir = failed to change current directory +err_perm_group = failed to downgrade group permissions +err_perm_user = failed to downgrade user permissions +err_pwnam = failed to get user info +err_user_gid = failed to set user GID +err_user_init = failed to initialize user +err_user_uid = failed to set user UID +err_xsessions_dir = failed to find sessions folder +err_xsessions_open = failed to open sessions folder +f1 = F1 shutdown +f2 = F2 reboot +login = login: +logout = logged out +numlock = numlock +password = password: +shell = shell +wayland = wayland +xinitrc = xinitrc diff --git a/res/lang/fr.ini b/res/lang/fr.ini new file mode 100644 index 00000000..f7a5cb18 --- /dev/null +++ b/res/lang/fr.ini @@ -0,0 +1,45 @@ +capslock = verr.maj +err_alloc = échec d'allocation mémoire +err_bounds = indice hors-limite +err_chdir = échec de l'ouverture du répertoire home +err_console_dev = échec d'accès à la console +err_dgn_oob = message +err_domain = domaine invalide +err_hostname = échec de captation du nom d'hôte +err_mlock = échec du verrouillage mémoire +err_null = pointeur null +err_pam = échec de la transaction pam +err_pam_abort = transaction pam avortée +err_pam_acct_expired = compte expiré +err_pam_auth = erreur d'authentification +err_pam_authok_reqd = tiquet expiré +err_pam_authinfo_unavail = échec de l'obtention des infos utilisateur +err_pam_buf = erreur de mémoire tampon +err_pam_cred_err = échec de la modification des identifiants +err_pam_cred_expired = identifiants expirés +err_pam_cred_insufficient = identifiants insuffisants +err_pam_cred_unavail = échec de l'obtention des identifiants +err_pam_maxtries = limite d'essais atteinte +err_pam_perm_denied = permission refusée +err_pam_session = erreur de session +err_pam_sys = erreur système +err_pam_user_unknown = utilisateur inconnu +err_path = échec de la modification du path +err_perm_dir = échec de changement de répertoire +err_perm_group = échec du déclassement des permissions de groupe +err_perm_user = échec du déclassement des permissions utilisateur +err_pwnam = échec de captation des infos utilisateur +err_user_gid = échec de modification du GID +err_user_init = échec d'initialisation de l'utilisateur +err_user_uid = échec de modification du UID +err_xsessions_dir = échec de la recherche du dossier de sessions +err_xsessions_open = échec de l'ouverture du dossier de sessions +f1 = F1 éteindre +f2 = F2 redémarrer +login = identifiant : +logout = déconnection +numlock = verr.num +password = mot de passe : +shell = shell +wayland = wayland +xinitrc = xinitrc diff --git a/res/ly.service b/res/ly.service new file mode 100644 index 00000000..135b987a --- /dev/null +++ b/res/ly.service @@ -0,0 +1,15 @@ +[Unit] +Description=TUI display manager +After=systemd-user-sessions.service plymouth-quit-wait.service +After=getty@tty2.service + +[Service] +Type=idle +ExecStart=/usr/bin/ly +StandardInput=tty +TTYPath=/dev/tty2 +TTYReset=yes +TTYVHangup=yes + +[Install] +Alias=display-manager.service diff --git a/res/valgrind.supp b/res/valgrind.supp new file mode 100644 index 00000000..274f2f0b --- /dev/null +++ b/res/valgrind.supp @@ -0,0 +1,31 @@ +{ + pam + Memcheck:Leak + ... + obj:/usr/lib/libpam.so.0.84.2 + ... +} + +{ + termbox + Memcheck:Leak + ... + fun:tb_init + ... +} + +{ + libc/dynamic + Memcheck:Leak + ... + fun:_dl_catch_exception + ... +} + +{ + libc/groups + Memcheck:Leak + ... + fun:initgroups + ... +} diff --git a/res/wsetup.sh b/res/wsetup.sh new file mode 100755 index 00000000..42be624a --- /dev/null +++ b/res/wsetup.sh @@ -0,0 +1,54 @@ +#!/bin/sh +# wayland-session - run as user +# Copyright (C) 2015-2016 Pier Luigi Fiorini + +# This file is extracted from kde-workspace (kdm/kfrontend/genkdmconf.c) +# Copyright (C) 2001-2005 Oswald Buddenhagen + +# Note that the respective logout scripts are not sourced. +case $SHELL in + */bash) + [ -z "$BASH" ] && exec $SHELL $0 "$@" + set +o posix + [ -f /etc/profile ] && . /etc/profile + if [ -f $HOME/.bash_profile ]; then + . $HOME/.bash_profile + elif [ -f $HOME/.bash_login ]; then + . $HOME/.bash_login + elif [ -f $HOME/.profile ]; then + . $HOME/.profile + fi + ;; +*/zsh) + [ -z "$ZSH_NAME" ] && exec $SHELL $0 "$@" + [ -d /etc/zsh ] && zdir=/etc/zsh || zdir=/etc + zhome=${ZDOTDIR:-$HOME} + # zshenv is always sourced automatically. + [ -f $zdir/zprofile ] && . $zdir/zprofile + [ -f $zhome/.zprofile ] && . $zhome/.zprofile + [ -f $zdir/zlogin ] && . $zdir/zlogin + [ -f $zhome/.zlogin ] && . $zhome/.zlogin + emulate -R sh + ;; + */csh|*/tcsh) + # [t]cshrc is always sourced automatically. + # Note that sourcing csh.login after .cshrc is non-standard. + wlsess_tmp=`mktemp /tmp/wlsess-env-XXXXXX` + $SHELL -c "if (-f /etc/csh.login) source /etc/csh.login; if (-f ~/.login) source ~/.login; /bin/sh -c 'export -p' >! $wlsess_tmp" + . $wlsess_tmp + rm -f $wlsess_tmp + ;; + */fish) + [ -f /etc/profile ] && . /etc/profile + xsess_tmp=`mktemp /tmp/xsess-env-XXXXXX` + $SHELL --login -c "/bin/sh -c 'export -p' > $xsess_tmp" + . $xsess_tmp + rm -f $xsess_tmp + ;; + *) # Plain sh, ksh, and anything we do not know. + [ -f /etc/profile ] && . /etc/profile + [ -f $HOME/.profile ] && . $HOME/.profile + ;; +esac + +exec $@ diff --git a/res/xsetup.sh b/res/xsetup.sh new file mode 100755 index 00000000..23973750 --- /dev/null +++ b/res/xsetup.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +~/.xsession +exec $@ diff --git a/src/config.c b/src/config.c new file mode 100644 index 00000000..5e0caa97 --- /dev/null +++ b/src/config.c @@ -0,0 +1,361 @@ +#include "configator.h" + +#include "config.h" + +#include +#include +#include +#include +#include + +#ifndef DEBUG + #define INI_LANG "/etc/ly/lang/%s.ini" + #define INI_CONFIG "/etc/ly/config.ini" +#else + #define INI_LANG "../res/lang/%s.ini" + #define INI_CONFIG "../res/config.ini" +#endif + +static void lang_handle(void* data, char** pars, const int pars_count) +{ + if (*((char**)data) != NULL) + { + free (*((char**)data)); + } + + *((char**)data) = strdup(*pars); +} + +static void config_handle_u8(void* data, char** pars, const int pars_count) +{ + if (strcmp(*pars, "") == 0) + { + *((u8*)data) = 0; + } + else + { + *((u8*)data) = atoi(*pars); + } +} + +static void config_handle_u16(void* data, char** pars, const int pars_count) +{ + if (strcmp(*pars, "") == 0) + { + *((u16*)data) = 0; + } + else + { + *((u16*)data) = atoi(*pars); + } +} + +void config_handle_str(void* data, char** pars, const int pars_count) +{ + if (*((char**)data) != NULL) + { + free(*((char**)data)); + } + + *((char**)data) = strdup(*pars); +} + +static void config_handle_char(void* data, char** pars, const int pars_count) +{ + *((char*)data) = **pars; +} + +static void config_handle_bool(void* data, char** pars, const int pars_count) +{ + *((bool*)data) = (strcmp("true", *pars) == 0); +} + +void lang_load() +{ + // must be alphabetically sorted + struct configator_param map_no_section[] = + { + {"capslock", &lang.capslock, lang_handle}, + {"err_alloc", &lang.err_alloc, lang_handle}, + {"err_bounds", &lang.err_bounds, lang_handle}, + {"err_chdir", &lang.err_chdir, lang_handle}, + {"err_console_dev", &lang.err_console_dev, lang_handle}, + {"err_dgn_oob", &lang.err_dgn_oob, lang_handle}, + {"err_domain", &lang.err_domain, lang_handle}, + {"err_hostname", &lang.err_hostname, lang_handle}, + {"err_mlock", &lang.err_mlock, lang_handle}, + {"err_null", &lang.err_null, lang_handle}, + {"err_pam", &lang.err_pam, lang_handle}, + {"err_pam_abort", &lang.err_pam_abort, lang_handle}, + {"err_pam_acct_expired", &lang.err_pam_acct_expired, lang_handle}, + {"err_pam_auth", &lang.err_pam_auth, lang_handle}, + {"err_pam_authinfo_unavail", &lang.err_pam_authinfo_unavail, lang_handle}, + {"err_pam_authok_reqd", &lang.err_pam_authok_reqd, lang_handle}, + {"err_pam_buf", &lang.err_pam_buf, lang_handle}, + {"err_pam_cred_err", &lang.err_pam_cred_err, lang_handle}, + {"err_pam_cred_expired", &lang.err_pam_cred_expired, lang_handle}, + {"err_pam_cred_insufficient", &lang.err_pam_cred_insufficient, lang_handle}, + {"err_pam_cred_unavail", &lang.err_pam_cred_unavail, lang_handle}, + {"err_pam_maxtries", &lang.err_pam_maxtries, lang_handle}, + {"err_pam_perm_denied", &lang.err_pam_perm_denied, lang_handle}, + {"err_pam_session", &lang.err_pam_session, lang_handle}, + {"err_pam_sys", &lang.err_pam_sys, lang_handle}, + {"err_pam_user_unknown", &lang.err_pam_user_unknown, lang_handle}, + {"err_path", &lang.err_path, lang_handle}, + {"err_perm_dir", &lang.err_perm_dir, lang_handle}, + {"err_perm_group", &lang.err_perm_group, lang_handle}, + {"err_perm_user", &lang.err_perm_user, lang_handle}, + {"err_pwnam", &lang.err_pwnam, lang_handle}, + {"err_user_gid", &lang.err_user_gid, lang_handle}, + {"err_user_init", &lang.err_user_init, lang_handle}, + {"err_user_uid", &lang.err_user_uid, lang_handle}, + {"err_xsessions_dir", &lang.err_xsessions_dir, lang_handle}, + {"err_xsessions_open", &lang.err_xsessions_open, lang_handle}, + {"f1", &lang.f1, lang_handle}, + {"f2", &lang.f2, lang_handle}, + {"login", &lang.login, lang_handle}, + {"logout", &lang.logout, lang_handle}, + {"numlock", &lang.numlock, lang_handle}, + {"password", &lang.password, lang_handle}, + {"shell", &lang.shell, lang_handle}, + {"wayland", &lang.wayland, lang_handle}, + {"xinitrc", &lang.xinitrc, lang_handle}, + }; + + uint16_t map_len[] = {45}; + struct configator_param* map[] = + { + map_no_section, + }; + + uint16_t sections_len = 0; + struct configator_param* sections = NULL; + + struct configator lang; + lang.map = map; + lang.map_len = map_len; + lang.sections = sections; + lang.sections_len = sections_len; + + char file[256]; + snprintf(file, 256, INI_LANG, config.lang); + + if (access(file, F_OK) != -1) + { + configator(&lang, file); + } +} + +void config_load() +{ + // must be alphabetically sorted + struct configator_param map_no_section[] = + { + {"animate", &config.animate, config_handle_bool}, + {"animation", &config.animation, config_handle_u8}, + {"asterisk", &config.asterisk, config_handle_char}, + {"bg", &config.bg, config_handle_u8}, + {"blank_box", &config.blank_box, config_handle_bool}, + {"blank_password", &config.blank_box, config_handle_bool}, + {"console_dev", &config.console_dev, config_handle_str}, + {"default_input", &config.default_input, config_handle_u8}, + {"fg", &config.fg, config_handle_u8}, + {"hide_borders", &config.hide_borders, config_handle_bool}, + {"input_len", &config.input_len, config_handle_u8}, + {"lang", &config.lang, config_handle_str}, + {"load", &config.load, config_handle_bool}, + {"margin_box_h", &config.margin_box_h, config_handle_u8}, + {"margin_box_v", &config.margin_box_v, config_handle_u8}, + {"max_desktop_len", &config.max_desktop_len, config_handle_u8}, + {"max_login_len", &config.max_login_len, config_handle_u8}, + {"max_password_len", &config.max_password_len, config_handle_u8}, + {"mcookie_cmd", &config.mcookie_cmd, config_handle_str}, + {"min_refresh_delta", &config.min_refresh_delta, config_handle_u16}, + {"path", &config.path, config_handle_str}, + {"restart_cmd", &config.restart_cmd, config_handle_str}, + {"save", &config.save, config_handle_bool}, + {"save_file", &config.save_file, config_handle_str}, + {"service_name", &config.service_name, config_handle_str}, + {"shutdown_cmd", &config.shutdown_cmd, config_handle_str}, + {"term_reset_cmd", &config.term_reset_cmd, config_handle_str}, + {"tty", &config.tty, config_handle_u8}, + {"wayland_cmd", &config.wayland_cmd, config_handle_str}, + {"waylandsessions", &config.waylandsessions, config_handle_str}, + {"x_cmd", &config.x_cmd, config_handle_str}, + {"x_cmd_setup", &config.x_cmd_setup, config_handle_str}, + {"xauth_cmd", &config.xauth_cmd, config_handle_str}, + {"xsessions", &config.xsessions, config_handle_str}, + }; + + uint16_t map_len[] = {34}; + struct configator_param* map[] = + { + map_no_section, + }; + + uint16_t sections_len = 0; + struct configator_param* sections = NULL; + + struct configator config; + config.map = map; + config.map_len = map_len; + config.sections = sections; + config.sections_len = sections_len; + + configator(&config, INI_CONFIG); +} + +void lang_defaults() +{ + lang.capslock = strdup("capslock"); + lang.err_alloc = strdup("failed memory allocation"); + lang.err_bounds = strdup("out-of-bounds index"); + lang.err_chdir = strdup("failed to open home folder"); + lang.err_console_dev = strdup("failed to access console"); + lang.err_dgn_oob = strdup("log message"); + lang.err_domain = strdup("invalid domain"); + lang.err_hostname = strdup("failed to get hostname"); + lang.err_mlock = strdup("failed to lock password memory"); + lang.err_null = strdup("null pointer"); + lang.err_pam = strdup("pam transaction failed"); + lang.err_pam_abort = strdup("pam transaction aborted"); + lang.err_pam_acct_expired = strdup("account expired"); + lang.err_pam_auth = strdup("authentication error"); + lang.err_pam_authinfo_unavail = strdup("failed to get user info"); + lang.err_pam_authok_reqd = strdup("token expired"); + lang.err_pam_buf = strdup("memory buffer error"); + lang.err_pam_cred_err = strdup("failed to set credentials"); + lang.err_pam_cred_expired = strdup("credentials expired"); + lang.err_pam_cred_insufficient = strdup("insufficient credentials"); + lang.err_pam_cred_unavail = strdup("failed to get credentials"); + lang.err_pam_maxtries = strdup("reached maximum tries limit"); + lang.err_pam_perm_denied = strdup("permission denied"); + lang.err_pam_session = strdup("session error"); + lang.err_pam_sys = strdup("system error"); + lang.err_pam_user_unknown = strdup("unknown user"); + lang.err_path = strdup("failed to set path"); + lang.err_perm_dir = strdup("failed to change current directory"); + lang.err_perm_group = strdup("failed to downgrade group permissions"); + lang.err_perm_user = strdup("failed to downgrade user permissions"); + lang.err_pwnam = strdup("failed to get user info"); + lang.err_user_gid = strdup("failed to set user GID"); + lang.err_user_init = strdup("failed to initialize user"); + lang.err_user_uid = strdup("failed to set user UID"); + lang.err_xsessions_dir = strdup("failed to find sessions folder"); + lang.err_xsessions_open = strdup("failed to open sessions folder"); + lang.f1 = strdup("F1 shutdown"); + lang.f2 = strdup("F2 reboot"); + lang.login = strdup("login:"); + lang.logout = strdup("logged out"); + lang.numlock = strdup("numlock"); + lang.password = strdup("password:"); + lang.shell = strdup("shell"); + lang.wayland = strdup("wayland"); + lang.xinitrc = strdup("xinitrc"); +} + +void config_defaults() +{ + config.animate = false; + config.animation = 0; + config.asterisk = '*'; + config.bg = 0; + config.blank_box = true; + config.blank_password = false; + config.console_dev = strdup("/dev/console"); + config.default_input = 2; + config.fg = 9; + config.hide_borders = false; + config.input_len = 34; + config.lang = strdup("en"); + config.load = true; + config.margin_box_h = 2; + config.margin_box_v = 1; + config.max_desktop_len = 100; + config.max_login_len = 255; + config.max_password_len = 255; + config.mcookie_cmd = strdup("/usr/bin/mcookie"); + config.min_refresh_delta = 5; + config.path = strdup("/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/env"); + config.restart_cmd = strdup("/sbin/shutdown -r now"); + config.save = true; + config.save_file = strdup("/etc/ly/save"); + config.service_name = strdup("login"); + config.shutdown_cmd = strdup("/sbin/shutdown -a now"); + config.term_reset_cmd = strdup("/usr/bin/tput reset"); + config.tty = 2; + config.wayland_cmd = strdup("/etc/ly/wsetup.sh"); + config.waylandsessions = strdup("/usr/share/wayland-sessions"); + config.x_cmd = strdup("/usr/bin/X"); + config.x_cmd_setup = strdup("/etc/ly/xsetup.sh"); + config.xauth_cmd = strdup("/usr/bin/xauth"); + config.xsessions = strdup("/usr/share/xsessions"); +} + +void lang_free() +{ + free(lang.capslock); + free(lang.err_alloc); + free(lang.err_bounds); + free(lang.err_chdir); + free(lang.err_console_dev); + free(lang.err_dgn_oob); + free(lang.err_domain); + free(lang.err_hostname); + free(lang.err_mlock); + free(lang.err_null); + free(lang.err_pam); + free(lang.err_pam_abort); + free(lang.err_pam_acct_expired); + free(lang.err_pam_auth); + free(lang.err_pam_authinfo_unavail); + free(lang.err_pam_authok_reqd); + free(lang.err_pam_buf); + free(lang.err_pam_cred_err); + free(lang.err_pam_cred_expired); + free(lang.err_pam_cred_insufficient); + free(lang.err_pam_cred_unavail); + free(lang.err_pam_maxtries); + free(lang.err_pam_perm_denied); + free(lang.err_pam_session); + free(lang.err_pam_sys); + free(lang.err_pam_user_unknown); + free(lang.err_path); + free(lang.err_perm_dir); + free(lang.err_perm_group); + free(lang.err_perm_user); + free(lang.err_pwnam); + free(lang.err_user_gid); + free(lang.err_user_init); + free(lang.err_user_uid); + free(lang.err_xsessions_dir); + free(lang.err_xsessions_open); + free(lang.f1); + free(lang.f2); + free(lang.login); + free(lang.logout); + free(lang.numlock); + free(lang.password); + free(lang.shell); + free(lang.wayland); + free(lang.xinitrc); +} + +void config_free() +{ + free(config.console_dev); + free(config.lang); + free(config.mcookie_cmd); + free(config.path); + free(config.restart_cmd); + free(config.save_file); + free(config.service_name); + free(config.shutdown_cmd); + free(config.term_reset_cmd); + free(config.wayland_cmd); + free(config.waylandsessions); + free(config.x_cmd); + free(config.x_cmd_setup); + free(config.xauth_cmd); + free(config.xsessions); +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 00000000..93a0fb18 --- /dev/null +++ b/src/config.h @@ -0,0 +1,104 @@ +#ifndef H_LY_CONFIG +#define H_LY_CONFIG + +#include "ctypes.h" + +struct lang +{ + char* capslock; + char* err_alloc; + char* err_bounds; + char* err_chdir; + char* err_console_dev; + char* err_dgn_oob; + char* err_domain; + char* err_hostname; + char* err_mlock; + char* err_null; + char* err_pam; + char* err_pam_abort; + char* err_pam_acct_expired; + char* err_pam_auth; + char* err_pam_authinfo_unavail; + char* err_pam_authok_reqd; + char* err_pam_buf; + char* err_pam_cred_err; + char* err_pam_cred_expired; + char* err_pam_cred_insufficient; + char* err_pam_cred_unavail; + char* err_pam_maxtries; + char* err_pam_perm_denied; + char* err_pam_session; + char* err_pam_sys; + char* err_pam_user_unknown; + char* err_path; + char* err_perm_dir; + char* err_perm_group; + char* err_perm_user; + char* err_pwnam; + char* err_user_gid; + char* err_user_init; + char* err_user_uid; + char* err_xsessions_dir; + char* err_xsessions_open; + char* f1; + char* f2; + char* login; + char* logout; + char* numlock; + char* password; + char* shell; + char* wayland; + char* xinitrc; +}; + +struct config +{ + bool animate; + u8 animation; + char asterisk; + u8 bg; + bool blank_box; + bool blank_password; + char* console_dev; + u8 default_input; + u8 fg; + bool hide_borders; + u8 input_len; + char* lang; + bool load; + u8 margin_box_h; + u8 margin_box_v; + u8 max_desktop_len; + u8 max_login_len; + u8 max_password_len; + char* mcookie_cmd; + u16 min_refresh_delta; + char* path; + char* restart_cmd; + bool save; + char* save_file; + char* service_name; + char* shutdown_cmd; + char* term_reset_cmd; + u8 tty; + char* wayland_cmd; + char* waylandsessions; + char* x_cmd; + char* x_cmd_setup; + char* xauth_cmd; + char* xsessions; +}; + +extern struct lang lang; +extern struct config config; + +void config_handle_str(void* data, char** pars, const int pars_count); +void lang_load(); +void config_load(); +void lang_defaults(); +void config_defaults(); +void lang_free(); +void config_free(); + +#endif diff --git a/src/dragonfail_error.h b/src/dragonfail_error.h new file mode 100644 index 00000000..f5ead2fe --- /dev/null +++ b/src/dragonfail_error.h @@ -0,0 +1,27 @@ +#ifndef H_DRAGONFAIL_ERROR +#define H_DRAGONFAIL_ERROR + +enum dgn_error +{ + DGN_OK, // do not remove + + DGN_NULL, + DGN_ALLOC, + DGN_BOUNDS, + DGN_DOMAIN, + DGN_MLOCK, + DGN_XSESSIONS_DIR, + DGN_XSESSIONS_OPEN, + DGN_PATH, + DGN_CHDIR, + DGN_PWNAM, + DGN_USER_INIT, + DGN_USER_GID, + DGN_USER_UID, + DGN_PAM, + DGN_HOSTNAME, + + DGN_SIZE, // do not remove +}; + +#endif diff --git a/src/draw.c b/src/draw.c new file mode 100644 index 00000000..dad9584a --- /dev/null +++ b/src/draw.c @@ -0,0 +1,648 @@ +#include "dragonfail.h" +#include "termbox.h" +#include "ctypes.h" + +#include "inputs.h" +#include "utils.h" +#include "config.h" +#include "draw.h" + +#include +#include +#include +#include +#include +#include +#include + +#if defined(__DragonFly__) || defined(__FreeBSD__) + #include +#else // linux + #include +#endif + +#define DOOM_STEPS 13 + +void draw_init(struct term_buf* buf) +{ + buf->width = tb_width(); + buf->height = tb_height(); + hostname(&buf->info_line); + buf->info_line = NULL; + + u16 len_login = strlen(lang.login); + u16 len_password = strlen(lang.password); + + if (len_login > len_password) + { + buf->labels_max_len = len_login; + } + else + { + buf->labels_max_len = len_password; + } + + buf->box_height = 7 + (2 * config.margin_box_v); + buf->box_width = + (2 * config.margin_box_h) + + (config.input_len + 1) + + buf->labels_max_len; + +#if defined(__linux__) + buf->box_chars.left_up = 0x250c; + buf->box_chars.left_down = 0x2514; + buf->box_chars.right_up = 0x2510; + buf->box_chars.right_down = 0x2518; + buf->box_chars.top = 0x2500; + buf->box_chars.bot = 0x2500; + buf->box_chars.left = 0x2502; + buf->box_chars.right = 0x2502; +#else + buf->box_chars.left_up = '+'; + buf->box_chars.left_down = '+'; + buf->box_chars.right_up = '+'; + buf->box_chars.right_down= '+'; + buf->box_chars.top = '-'; + buf->box_chars.bot = '-'; + buf->box_chars.left = '|'; + buf->box_chars.right = '|'; +#endif +} + +void draw_free(struct term_buf* buf) +{ + if (config.animate) + { + free(buf->tmp_buf); + } +} + +void draw_box(struct term_buf* buf) +{ + u16 box_x = (buf->width - buf->box_width) / 2; + u16 box_y = (buf->height - buf->box_height) / 2; + u16 box_x2 = (buf->width + buf->box_width) / 2; + u16 box_y2 = (buf->height + buf->box_height) / 2; + buf->box_x = box_x; + buf->box_y = box_y; + + if (!config.hide_borders) + { + // corners + tb_change_cell( + box_x - 1, + box_y - 1, + buf->box_chars.left_up, + config.fg, + config.bg); + tb_change_cell( + box_x2, + box_y - 1, + buf->box_chars.right_up, + config.fg, + config.bg); + tb_change_cell( + box_x - 1, + box_y2, + buf->box_chars.left_down, + config.fg, + config.bg); + tb_change_cell( + box_x2, + box_y2, + buf->box_chars.right_down, + config.fg, + config.bg); + + // top and bottom + struct tb_cell c1 = {buf->box_chars.top, config.fg, config.bg}; + struct tb_cell c2 = {buf->box_chars.bot, config.fg, config.bg}; + + for (u8 i = 0; i < buf->box_width; ++i) + { + tb_put_cell( + box_x + i, + box_y - 1, + &c1); + tb_put_cell( + box_x + i, + box_y2, + &c2); + } + + // left and right + c1.ch = buf->box_chars.left; + c2.ch = buf->box_chars.right; + + for (u8 i = 0; i < buf->box_height; ++i) + { + tb_put_cell( + box_x - 1, + box_y + i, + &c1); + + tb_put_cell( + box_x2, + box_y + i, + &c2); + } + } + + if (config.blank_box) + { + struct tb_cell blank = {' ', config.fg, config.bg}; + + for (u8 i = 0; i < buf->box_height; ++i) + { + for (u8 k = 0; k < buf->box_width; ++k) + { + tb_put_cell( + box_x + k, + box_y + i, + &blank); + } + } + } +} + +struct tb_cell* strn_cell(char* s, u16 len) // throws +{ + struct tb_cell* cells = malloc((sizeof (struct tb_cell)) * len); + char* s2 = s; + u32 c; + + if (cells != NULL) + { + for (u16 i = 0; i < len; ++i) + { + if ((s2 - s) >= len) + { + break; + } + + s2 += utf8_char_to_unicode(&c, s2); + + cells[i].ch = c; + cells[i].bg = config.bg; + cells[i].fg = config.fg; + } + } + else + { + dgn_throw(DGN_ALLOC); + } + + return cells; +} + +struct tb_cell* str_cell(char* s) // throws +{ + return strn_cell(s, strlen(s)); +} + +void draw_labels(struct term_buf* buf) // throws +{ + // login text + struct tb_cell* login = str_cell(lang.login); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit( + buf->box_x + config.margin_box_h, + buf->box_y + config.margin_box_v + 4, + strlen(lang.login), + 1, + login); + free(login); + } + + // password text + struct tb_cell* password = str_cell(lang.password); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit( + buf->box_x + config.margin_box_h, + buf->box_y + config.margin_box_v + 6, + strlen(lang.password), + 1, + password); + free(password); + } + + if (buf->info_line != NULL) + { + u16 len = strlen(buf->info_line); + struct tb_cell* info_cell = str_cell(buf->info_line); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit( + buf->box_x + ((buf->box_width - len) / 2), + buf->box_y + config.margin_box_v, + len, + 1, + info_cell); + free(info_cell); + } + } +} + +void draw_f_commands() +{ + struct tb_cell* f1 = str_cell(lang.f1); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit(0, 0, strlen(lang.f1), 1, f1); + free(f1); + } + + struct tb_cell* f2 = str_cell(lang.f2); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit(strlen(lang.f1) + 1, 0, strlen(lang.f2), 1, f2); + free(f2); + } +} + +void draw_lock_state(struct term_buf* buf) +{ + // get values + int fd = open(config.console_dev, O_RDONLY); + + if (fd < 0) + { + buf->info_line = lang.err_console_dev; + return; + } + + bool numlock_on; + bool capslock_on; + +#if defined(__DragonFly__) || defined(__FreeBSD__) + int led; + ioctl(fd, KDGETLED, &led); + numlock_on = led & LED_NUM; + capslock_on = led & LED_CAP; +#else // linux + char led; + ioctl(fd, KDGKBLED, &led); + numlock_on = led & K_NUMLOCK; + capslock_on = led & K_CAPSLOCK; +#endif + + close(fd); + + // print text + u16 pos_x = buf->width - strlen(lang.numlock); + + if (numlock_on) + { + struct tb_cell* numlock = str_cell(lang.numlock); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit(pos_x, 0, strlen(lang.numlock), 1, numlock); + free(numlock); + } + } + + pos_x -= strlen(lang.capslock) + 1; + + if (capslock_on) + { + struct tb_cell* capslock = str_cell(lang.capslock); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit(pos_x, 0, strlen(lang.capslock), 1, capslock); + free(capslock); + } + } +} + +void draw_desktop(struct desktop* target) +{ + u16 len = strlen(target->list[target->cur]); + + if (len > (target->visible_len - 3)) + { + len = target->visible_len - 3; + } + + tb_change_cell( + target->x, + target->y, + '<', + config.fg, + config.bg); + + tb_change_cell( + target->x + target->visible_len - 1, + target->y, + '>', + config.fg, + config.bg); + + for (u16 i = 0; i < len; ++ i) + { + tb_change_cell( + target->x + i + 2, + target->y, + target->list[target->cur][i], + config.fg, + config.bg); + } +} + +void draw_input(struct text* input) +{ + u16 len = strlen(input->text); + u16 visible_len = input->visible_len; + + if (len > visible_len) + { + len = visible_len; + } + + struct tb_cell* cells = strn_cell(input->visible_start, len); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit(input->x, input->y, len, 1, cells); + free(cells); + + struct tb_cell c1 = {' ', config.fg, config.bg}; + + for (u16 i = input->end - input->visible_start; i < visible_len; ++i) + { + tb_put_cell( + input->x + i, + input->y, + &c1); + } + } +} + +void draw_input_mask(struct text* input) +{ + u16 len = strlen(input->text); + u16 visible_len = input->visible_len; + + if (len > visible_len) + { + len = visible_len; + } + + struct tb_cell c1 = {config.asterisk, config.fg, config.bg}; + struct tb_cell c2 = {' ', config.fg, config.bg}; + + for (u16 i = 0; i < visible_len; ++i) + { + if (input->visible_start + i < input->end) + { + tb_put_cell( + input->x + i, + input->y, + &c1); + } + else + { + tb_put_cell( + input->x + i, + input->y, + &c2); + } + } +} + +void position_input( + struct term_buf* buf, + struct desktop* desktop, + struct text* login, + struct text* password) +{ + u16 x = buf->box_x + config.margin_box_h + buf->labels_max_len + 1; + i32 len = buf->box_x + buf->box_width - config.margin_box_h - x; + + if (len < 0) + { + return; + } + + desktop->x = x; + desktop->y = buf->box_y + config.margin_box_v + 2; + desktop->visible_len = len; + + login->x = x; + login->y = buf->box_y + config.margin_box_v + 4; + login->visible_len = len; + + password->x = x; + password->y = buf->box_y + config.margin_box_v + 6; + password->visible_len = len; +} + +static void doom_init(struct term_buf* buf) +{ + buf->init_width = buf->width; + buf->init_height = buf->height; + + u16 tmp_len = buf->width * buf->height; + buf->tmp_buf = malloc(tmp_len); + tmp_len -= buf->width; + + if (buf->tmp_buf == NULL) + { + dgn_throw(DGN_ALLOC); + } + + memset(buf->tmp_buf, 0, tmp_len); + memset(buf->tmp_buf + tmp_len, DOOM_STEPS - 1, buf->width); +} + +void animate_init(struct term_buf* buf) +{ + if (config.animate) + { + switch(config.animation) + { + default: + { + doom_init(buf); + break; + } + } + } +} + +static void doom(struct term_buf* term_buf) +{ + static struct tb_cell fire[DOOM_STEPS] = + { + {' ', 9, 0}, // default + {0x2591, 2, 0}, // red + {0x2592, 2, 0}, // red + {0x2593, 2, 0}, // red + {0x2588, 2, 0}, // red + {0x2591, 4, 2}, // yellow + {0x2592, 4, 2}, // yellow + {0x2593, 4, 2}, // yellow + {0x2588, 4, 2}, // yellow + {0x2591, 8, 4}, // white + {0x2592, 8, 4}, // white + {0x2593, 8, 4}, // white + {0x2588, 8, 4}, // white + }; + + u16 src; + u16 random; + u16 dst; + + u16 w = term_buf->init_width; + u8* tmp = term_buf->tmp_buf; + + if ((term_buf->width != term_buf->init_width) || (term_buf->height != term_buf->init_height)) + { + return; + } + + struct tb_cell* buf = tb_cell_buffer(); + + for (u16 x = 0; x < w; ++x) + { + for (u16 y = 1; y < term_buf->init_height; ++y) + { + src = y * w + x; + random = ((rand() % 7) & 3); + dst = src - random + 1; + + if (w > dst) + { + dst = 0; + } + else + { + dst -= w; + } + + tmp[dst] = tmp[src] - (random & 1); + + if (tmp[dst] > 12) + { + tmp[dst] = 0; + } + + buf[dst] = fire[tmp[dst]]; + buf[src] = fire[tmp[src]]; + } + } +} + +void animate(struct term_buf* buf) +{ + buf->width = tb_width(); + buf->height = tb_height(); + + if (config.animate) + { + switch(config.animation) + { + default: + { + doom(buf); + break; + } + } + } +} + +bool cascade(struct term_buf* term_buf, u8* fails) +{ + u16 width = term_buf->width; + u16 height = term_buf->height; + + struct tb_cell* buf = tb_cell_buffer(); + bool changes = false; + char c_under; + char c; + + for (int i = height - 2; i >= 0; --i) + { + for (int k = 0; k < width; ++k) + { + c = buf[i * width + k].ch; + + if (isspace(c)) + { + continue; + } + + c_under = buf[(i + 1) * width + k].ch; + + if (!isspace(c_under)) + { + continue; + } + + if (!changes) + { + changes = true; + } + + if ((rand() % 10) > 7) + { + continue; + } + + buf[(i + 1) * width + k] = buf[i * width + k]; + buf[i * width + k].ch = ' '; + } + } + + // stop force-updating + if (!changes) + { + sleep(7); + *fails = 0; + + return false; + } + + // force-update + return true; +} diff --git a/src/draw.h b/src/draw.h new file mode 100644 index 00000000..059c60b2 --- /dev/null +++ b/src/draw.h @@ -0,0 +1,63 @@ +#ifndef H_LY_DRAW +#define H_LY_DRAW + +#include "termbox.h" +#include "ctypes.h" + +#include "inputs.h" + +struct box +{ + u32 left_up; + u32 left_down; + u32 right_up; + u32 right_down; + u32 top; + u32 bot; + u32 left; + u32 right; +}; + +struct term_buf +{ + u16 width; + u16 height; + u16 init_width; + u16 init_height; + + struct box box_chars; + char* info_line; + u16 labels_max_len; + u16 box_x; + u16 box_y; + u16 box_width; + u16 box_height; + + u8* tmp_buf; +}; + +void draw_init(struct term_buf* buf); +void draw_free(struct term_buf* buf); +void draw_box(struct term_buf* buf); + +struct tb_cell* strn_cell(char* s, u16 len); +struct tb_cell* str_cell(char* s); + +void draw_labels(struct term_buf* buf); +void draw_f_commands(); +void draw_lock_state(struct term_buf* buf); +void draw_desktop(struct desktop* target); +void draw_input(struct text* input); +void draw_input_mask(struct text* input); + +void position_input( + struct term_buf* buf, + struct desktop* desktop, + struct text* login, + struct text* password); + +void animate_init(struct term_buf* buf); +void animate(struct term_buf* buf); +bool cascade(struct term_buf* buf, u8* fails); + +#endif diff --git a/src/inputs.c b/src/inputs.c new file mode 100644 index 00000000..08c1011f --- /dev/null +++ b/src/inputs.c @@ -0,0 +1,258 @@ +#include "dragonfail.h" +#include "termbox.h" +#include "ctypes.h" + +#include "inputs.h" +#include "config.h" + +#include +#include +#include + +void handle_desktop(void* input_struct, struct tb_event* event) +{ + struct desktop* target = (struct desktop*) input_struct; + + if ((event != NULL) && (event->type == TB_EVENT_KEY)) + { + if (event->key == TB_KEY_ARROW_LEFT) + { + input_desktop_right(target); + } + else if (event->key == TB_KEY_ARROW_RIGHT) + { + input_desktop_left(target); + } + } + + tb_set_cursor(target->x + 2, target->y); +} + +void handle_text(void* input_struct, struct tb_event* event) +{ + struct text* target = (struct text*) input_struct; + + if ((event != NULL) && (event->type == TB_EVENT_KEY)) + { + if (event->key == TB_KEY_ARROW_LEFT) + { + input_text_left(target); + } + else if (event->key == TB_KEY_ARROW_RIGHT) + { + input_text_right(target); + } + else if (event->key == TB_KEY_DELETE) + { + input_text_delete(target); + } + else if ((event->key == TB_KEY_BACKSPACE) + || (event->key == TB_KEY_BACKSPACE2)) + { + input_text_backspace(target); + } + else if (((event->ch > 31) && (event->ch < 127)) + || (event->key == TB_KEY_SPACE)) + { + char buf[7] = {0}; + + if (event->key == TB_KEY_SPACE) + { + buf[0] = ' '; + } + else + { + utf8_unicode_to_char(buf, event->ch); + } + + input_text_write(target, buf[0]); + } + } + + tb_set_cursor( + target->x + (target->cur - target->visible_start), + target->y); +} + +void input_desktop(struct desktop* target) +{ + target->list = NULL; + target->cmd = NULL; + target->display_server = NULL; + target->cur = 0; + target->len = 0; + + input_desktop_add(target, strdup(lang.shell), strdup(""), DS_SHELL); + input_desktop_add(target, strdup(lang.xinitrc), strdup("~/.xinitrc"), DS_XINITRC); +#if 0 + input_desktop_add(target, strdup(lang.wayland), strdup(""), DS_WAYLAND); +#endif +} + +void input_text(struct text* target, u64 len) +{ + target->text = malloc(len + 1); + + if (target->text == NULL) + { + dgn_throw(DGN_ALLOC); + return; + } + else + { + int ok = mlock(target->text, len + 1); + + if (ok < 0) + { + dgn_throw(DGN_MLOCK); + return; + } + + memset(target->text, 0, len + 1); + } + + target->cur = target->text; + target->end = target->text; + target->visible_start = target->text; + target->len = len; +} + +void input_desktop_free(struct desktop* target) +{ + if (target != NULL) + { + for (u16 i = 0; i < target->len; ++i) + { + if (target->list[i] != NULL) + { + free(target->list[i]); + } + + if (target->cmd[i] != NULL) + { + free(target->cmd[i]); + } + } + + free(target->list); + free(target->cmd); + free(target->display_server); + } +} + +void input_text_free(struct text* target) +{ + memset(target->text, 0, target->len); + munlock(target->text, target->len + 1); + free(target->text); +} + +void input_desktop_right(struct desktop* target) +{ + ++(target->cur); + + if (target->cur >= target->len) + { + target->cur = 0; + } +} + +void input_desktop_left(struct desktop* target) +{ + --(target->cur); + + if (target->cur >= target->len) + { + target->cur = target->len - 1; + } +} + +void input_desktop_add( + struct desktop* target, + char* name, + char* cmd, + enum display_server display_server) +{ + ++(target->len); + target->list = realloc(target->list, target->len * (sizeof (char*))); + target->cmd = realloc(target->cmd, target->len * (sizeof (char*))); + target->display_server = realloc( + target->display_server, + target->len * (sizeof (enum display_server))); + target->cur = target->len - 1; + + if ((target->list == NULL) + || (target->cmd == NULL) + || (target->display_server == NULL)) + { + dgn_throw(DGN_ALLOC); + return; + } + + target->list[target->cur] = name; + target->cmd[target->cur] = cmd; + target->display_server[target->cur] = display_server; +} + +void input_text_right(struct text* target) +{ + if (target->cur < target->end) + { + ++(target->cur); + + if ((target->cur - target->visible_start) > target->visible_len) + { + ++(target->visible_start); + } + } +} + +void input_text_left(struct text* target) +{ + if (target->cur > target->text) + { + --(target->cur); + + if ((target->cur - target->visible_start) < 0) + { + --(target->visible_start); + } + } +} + +void input_text_write(struct text* target, char ascii) +{ + if (ascii <= 0) + { + return; // unices do not support usernames and passwords other than ascii + } + + if ((target->end - target->text + 1) < target->len) + { + // moves the text to the right to add space for the new ascii char + memcpy(target->cur + 1, target->cur, target->end - target->cur); + ++(target->end); + // adds the new char and moves the cursor to the right + *(target->cur) = ascii; + input_text_right(target); + } +} + +void input_text_delete(struct text* target) +{ + if (target->cur < target->end) + { + // moves the text on the right to overwrite the currently pointed char + memcpy(target->cur, target->cur + 1, target->end - target->cur + 1); + --(target->end); + } +} + +void input_text_backspace(struct text* target) +{ + if (target->cur > target->text) + { + input_text_left(target); + input_text_delete(target); + } +} diff --git a/src/inputs.h b/src/inputs.h new file mode 100644 index 00000000..c0afcd34 --- /dev/null +++ b/src/inputs.h @@ -0,0 +1,54 @@ +#ifndef H_LY_INPUTS +#define H_LY_INPUTS + +#include "termbox.h" +#include "ctypes.h" + +enum display_server {DS_WAYLAND, DS_SHELL, DS_XINITRC, DS_XORG}; + +struct text +{ + char* text; + char* end; + i64 len; + char* cur; + char* visible_start; + u16 visible_len; + + u16 x; + u16 y; +}; + +struct desktop +{ + char** list; + char** cmd; + enum display_server* display_server; + + u16 cur; + u16 len; + u16 visible_len; + u16 x; + u16 y; +}; + +void handle_desktop(void* input_struct, struct tb_event* event); +void handle_text(void* input_struct, struct tb_event* event); +void input_desktop(struct desktop* target); +void input_text(struct text* target, u64 len); +void input_desktop_free(struct desktop* target); +void input_text_free(struct text* target); +void input_desktop_right(struct desktop* target); +void input_desktop_left(struct desktop* target); +void input_desktop_add( + struct desktop* target, + char* name, + char* cmd, + enum display_server display_server); +void input_text_right(struct text* target); +void input_text_left(struct text* target); +void input_text_write(struct text* target, char ascii); +void input_text_delete(struct text* target); +void input_text_backspace(struct text* target); + +#endif diff --git a/src/login.c b/src/login.c new file mode 100644 index 00000000..2c0330c9 --- /dev/null +++ b/src/login.c @@ -0,0 +1,647 @@ +#include "dragonfail.h" +#include "termbox.h" + +#include "inputs.h" +#include "draw.h" +#include "utils.h" +#include "config.h" +#include "login.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int get_free_display() +{ + char xlock[1024]; + u8 i; + + for (i = 0; i < 200; ++i) + { + snprintf(xlock, 1024, "/tmp/.X%d-lock", i); + + if (access(xlock, F_OK) == -1) + { + break; + } + } + + return i; +} + +void reset_terminal(struct passwd* pwd) +{ + pid_t pid = fork(); + + if (pid == 0) + { + execl(pwd->pw_shell, pwd->pw_shell, "-c", config.term_reset_cmd, NULL); + exit(EXIT_SUCCESS); + } + + int status; + waitpid(pid, &status, 0); +} + +int login_conv( + int num_msg, + const struct pam_message** msg, + struct pam_response** resp, + void* appdata_ptr) +{ + *resp = calloc(num_msg, sizeof (struct pam_response)); + + if (*resp == NULL) + { + return PAM_BUF_ERR; + } + + char* username; + char* password; + int ok = PAM_SUCCESS; + int i; + + for (i = 0; i < num_msg; ++i) + { + switch (msg[i]->msg_style) + { + case PAM_PROMPT_ECHO_ON: + { + username = ((char**) appdata_ptr)[0]; + (*resp)[i].resp = strdup(username); + break; + } + case PAM_PROMPT_ECHO_OFF: + { + password = ((char**) appdata_ptr)[1]; + (*resp)[i].resp = strdup(password); + break; + } + case PAM_ERROR_MSG: + { + ok = PAM_CONV_ERR; + break; + } + } + + if (ok != PAM_SUCCESS) + { + break; + } + } + + if (ok != PAM_SUCCESS) + { + for (i = 0; i < num_msg; ++i) + { + if ((*resp)[i].resp == NULL) + { + continue; + } + + free((*resp)[i].resp); + (*resp)[i].resp = NULL; + } + + free(*resp); + *resp = NULL; + } + + return ok; +} + +void pam_diagnose(int error, struct term_buf* buf) +{ + switch (error) + { + case PAM_ACCT_EXPIRED: + { + buf->info_line = lang.err_pam_acct_expired; + break; + } + case PAM_AUTH_ERR: + { + buf->info_line = lang.err_pam_auth; + break; + } + case PAM_AUTHINFO_UNAVAIL: + { + buf->info_line = lang.err_pam_authinfo_unavail; + break; + } + case PAM_BUF_ERR: + { + buf->info_line = lang.err_pam_buf; + break; + } + case PAM_CRED_ERR: + { + buf->info_line = lang.err_pam_cred_err; + break; + } + case PAM_CRED_EXPIRED: + { + buf->info_line = lang.err_pam_cred_expired; + break; + } + case PAM_CRED_INSUFFICIENT: + { + buf->info_line = lang.err_pam_cred_insufficient; + break; + } + case PAM_CRED_UNAVAIL: + { + buf->info_line = lang.err_pam_cred_unavail; + break; + } + case PAM_MAXTRIES: + { + buf->info_line = lang.err_pam_maxtries; + break; + } + case PAM_NEW_AUTHTOK_REQD: + { + buf->info_line = lang.err_pam_authok_reqd; + break; + } + case PAM_PERM_DENIED: + { + buf->info_line = lang.err_pam_perm_denied; + break; + } + case PAM_SESSION_ERR: + { + buf->info_line = lang.err_pam_session; + break; + } + case PAM_SYSTEM_ERR: + { + buf->info_line = lang.err_pam_sys; + break; + } + case PAM_USER_UNKNOWN: + { + buf->info_line = lang.err_pam_user_unknown; + break; + } + case PAM_ABORT: + default: + { + buf->info_line = lang.err_pam_abort; + break; + } + } + + dgn_throw(DGN_PAM); +} + +void env_init(struct passwd* pwd, const char* display_name) +{ + extern char** environ; + // term + char* term = getenv("TERM"); + // clean env + environ[0] = NULL; + + if (term != NULL) + { + setenv("TERM", term, 1); + } + else + { + setenv("TERM", "linux", 1); + } + + setenv("HOME", pwd->pw_dir, 1); + setenv("PWD", pwd->pw_dir, 1); + setenv("SHELL", pwd->pw_shell, 1); + setenv("USER", pwd->pw_name, 1); + setenv("LOGNAME", pwd->pw_name, 1); + setenv("DISPLAY", display_name, 1); + + // path + int ok = setenv("PATH", config.path, 1); + + if (ok != 0) + { + dgn_throw(DGN_PATH); + } +} + +void env_xdg(const char* tty_id, const enum display_server display_server) +{ + char user[15]; + snprintf(user, 15, "/run/user/%d", getuid()); + setenv("XDG_RUNTIME_DIR", user, 0); + setenv("XDG_SESSION_CLASS", "user", 0); + setenv("XDG_SEAT", "seat0", 0); + setenv("XDG_VTNR", tty_id, 0); + + switch (display_server) + { + case DS_WAYLAND: + { + setenv("XDG_SESSION_TYPE", "wayland", 0); + break; + } + case DS_SHELL: + { + setenv("XDG_SESSION_TYPE", "tty", 0); + break; + } + case DS_XINITRC: + case DS_XORG: + { + setenv("XDG_SESSION_TYPE", "x11", 0); + break; + } + } +} + +void xauth(const char* display_name, const char* shell, const char* home) +{ + char xauthority[256]; + snprintf(xauthority, 256, "%s/%s", home, ".lyxauth"); + setenv("XAUTHORITY", xauthority, 1); + + FILE* fp = fopen(xauthority, "ab+"); + + if (fp != NULL) + { + fclose(fp); + } + + pid_t pid = fork(); + + if (pid == 0) + { + char cmd[1024]; + snprintf( + cmd, + 1024, + "%s add %s . `%s`", + config.xauth_cmd, + display_name, + config.mcookie_cmd); + execl(shell, shell, "-c", cmd, NULL); + exit(EXIT_SUCCESS); + } + + int status; + waitpid(pid, &status, 0); +} + +void xorg( + struct passwd* pwd, + const char* display_name, + const char* vt, + const char* desktop_cmd) +{ + reset_terminal(pwd); + + // generate xauthority file + xauth(display_name, pwd->pw_shell, pwd->pw_dir); + + // start xorg + pid_t pid = fork(); + + if (pid == 0) + { + char x_cmd[1024]; + snprintf( + x_cmd, + 1024, + "%s %s %s", + config.x_cmd, + display_name, + vt); + execl(pwd->pw_shell, pwd->pw_shell, "-c", x_cmd, NULL); + exit(EXIT_SUCCESS); + } + + int ok; + xcb_connection_t* xcb; + + do + { + xcb = xcb_connect(NULL, NULL); + ok = xcb_connection_has_error(xcb); + kill(pid, 0); + } + while((ok != 0) && (errno != ESRCH)); + + if (ok != 0) + { + return; + } + + pid_t xorg_pid = fork(); + + if (xorg_pid == 0) + { + char de_cmd[1024]; + snprintf( + de_cmd, + 1024, + "%s %s", + config.x_cmd_setup, + desktop_cmd); + execl(pwd->pw_shell, pwd->pw_shell, "-c", de_cmd, NULL); + exit(EXIT_SUCCESS); + } + + int status; + waitpid(xorg_pid, &status, 0); + reset_terminal(pwd); + xcb_disconnect(xcb); + kill(pid, 0); + + if (errno != ESRCH) + { + kill(pid, SIGTERM); + waitpid(pid, &status, 0); + } +} + +void wayland( + struct passwd* pwd, + const char* desktop_cmd) +{ + reset_terminal(pwd); + pid_t pid = fork(); + + if (pid == 0) + { + char cmd[1024]; + snprintf(cmd, 1024, "%s %s", config.wayland_cmd, desktop_cmd); + execl(pwd->pw_shell, pwd->pw_shell, "-c", cmd, NULL); + exit(EXIT_SUCCESS); + } + + int status; + waitpid(pid, &status, 0); + reset_terminal(pwd); +} + +void shell(struct passwd* pwd) +{ + reset_terminal(pwd); + pid_t pid = fork(); + + if (pid == 0) + { + const char* pos = strrchr(pwd->pw_shell, '/'); + char args[1024]; + args[0] = '-'; + + if (pos != NULL) + { + pos = pos + 1; + } + else + { + pos = pwd->pw_shell; + } + + strncpy(args + 1, pos, 1024); + execl(pwd->pw_shell, args, NULL); + exit(EXIT_SUCCESS); + } + + int status; + waitpid(pid, &status, 0); + reset_terminal(pwd); +} + +void auth( + struct desktop* desktop, + struct text* login, + struct text* password, + struct term_buf* buf) +{ + int ok; + + // open pam session + const char* creds[2] = {login->text, password->text}; + struct pam_conv conv = {login_conv, creds}; + struct pam_handle* handle; + + ok = pam_start(config.service_name, NULL, &conv, &handle); + + if (ok != PAM_SUCCESS) + { + pam_diagnose(ok, buf); + pam_end(handle, ok); + return; + } + + ok = pam_authenticate(handle, 0); + + if (ok != PAM_SUCCESS) + { + pam_diagnose(ok, buf); + pam_end(handle, ok); + return; + } + + ok = pam_acct_mgmt(handle, 0); + + if (ok != PAM_SUCCESS) + { + pam_diagnose(ok, buf); + pam_end(handle, ok); + return; + } + + ok = pam_setcred(handle, PAM_ESTABLISH_CRED); + + if (ok != PAM_SUCCESS) + { + pam_diagnose(ok, buf); + pam_end(handle, ok); + return; + } + + ok = pam_open_session(handle, 0); + + if (ok != PAM_SUCCESS) + { + pam_diagnose(ok, buf); + pam_end(handle, ok); + return; + } + + // clear the credentials + input_text_free(password); + input_text(password, config.max_password_len); + + // get passwd structure + struct passwd* pwd = getpwnam(login->text); + endpwent(); + + if (pwd == NULL) + { + dgn_throw(DGN_PWNAM); + pam_end(handle, ok); + return; + } + + // set user shell + if (pwd->pw_shell[0] == '\0') + { + setusershell(); + + char* shell = getusershell(); + + if (shell != NULL) + { + strcpy(pwd->pw_shell, shell); + } + + endusershell(); + } + + // restore regular terminal mode + tb_clear(); + tb_present(); + tb_shutdown(); + + // start desktop environment + pid_t pid = fork(); + + if (pid == 0) + { + // set user info + ok = initgroups(pwd->pw_name, pwd->pw_gid); + + if (ok != 0) + { + dgn_throw(DGN_USER_INIT); + exit(EXIT_FAILURE); + } + + ok = setgid(pwd->pw_gid); + + if (ok != 0) + { + dgn_throw(DGN_USER_GID); + exit(EXIT_FAILURE); + } + + ok = setuid(pwd->pw_uid); + + if (ok != 0) + { + dgn_throw(DGN_USER_UID); + exit(EXIT_FAILURE); + } + + // get a display + int display_id = get_free_display(); + char display_name[3]; + char tty_id [3]; + char vt[5]; + + snprintf(display_name, 3, ":%d", display_id); + snprintf(tty_id, 3, "%d", config.tty); + snprintf(vt, 5, "vt%d", config.tty); + + // set env + env_init(pwd, display_name); + + if (dgn_catch()) + { + exit(EXIT_FAILURE); + } + + // add pam variables + char** env = pam_getenvlist(handle); + + for (u16 i = 0; env && env[i]; ++i) + { + putenv(env[i]); + } + + // add xdg variables + env_xdg(tty_id, desktop->display_server[desktop->cur]); + + // execute + int ok = chdir(pwd->pw_dir); + + if (ok != 0) + { + dgn_throw(DGN_CHDIR); + exit(EXIT_FAILURE); + } + + switch (desktop->display_server[desktop->cur]) + { + case DS_WAYLAND: + { + wayland(pwd, desktop->cmd[desktop->cur]); + break; + } + case DS_SHELL: + { + shell(pwd); + break; + } + case DS_XINITRC: + case DS_XORG: + { + xorg(pwd, display_name, vt, desktop->cmd[desktop->cur]); + break; + } + } + + exit(EXIT_SUCCESS); + } + + // wait for the session to stop + int status; + waitpid(pid, &status, 0); + + // reinit termbox + tb_init(); + tb_select_output_mode(TB_OUTPUT_NORMAL); + + // reload the desktop environment list on logout + input_desktop_free(desktop); + input_desktop(desktop); + desktop_load(desktop); + + // close pam session + ok = pam_close_session(handle, 0); + + if (ok != PAM_SUCCESS) + { + pam_diagnose(ok, buf); + pam_end(handle, ok); + return; + } + + ok = pam_setcred(handle, PAM_DELETE_CRED); + + if (ok != PAM_SUCCESS) + { + pam_diagnose(ok, buf); + pam_end(handle, ok); + return; + } + + ok = pam_end(handle, 0); + + if (ok != PAM_SUCCESS) + { + pam_diagnose(ok, buf); + } +} diff --git a/src/login.h b/src/login.h new file mode 100644 index 00000000..3fee58fa --- /dev/null +++ b/src/login.h @@ -0,0 +1,13 @@ +#ifndef H_LY_LOGIN +#define H_LY_LOGIN + +#include "draw.h" +#include "inputs.h" + +void auth( + struct desktop* desktop, + struct text* login, + struct text* password, + struct term_buf* buf); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 00000000..96a555e7 --- /dev/null +++ b/src/main.c @@ -0,0 +1,296 @@ +#include "argoat.h" +#include "configator.h" +#include "dragonfail.h" +#include "termbox.h" +#include "ctypes.h" + +#include "draw.h" +#include "inputs.h" +#include "login.h" +#include "utils.h" +#include "config.h" + +#include +#include +#include +#include +#include + +#define ARG_COUNT 5 +// things you can define: +// GIT_VERSION_STRING +// RUNIT + +// global +struct lang lang; +struct config config; + +// args handles +void arg_help(void* data, char** pars, const int pars_count) +{ + printf("RTFM\n"); +} + +void arg_version(void* data, char** pars, const int pars_count) +{ +#ifdef GIT_VERSION_STRING + printf("Ly version %s\n", GIT_VERSION_STRING); +#else + printf("Ly version unknown\n"); +#endif +} + +// low-level error messages +void log_init(char** log) +{ + log[DGN_OK] = lang.err_dgn_oob; + log[DGN_NULL] = lang.err_null; + log[DGN_ALLOC] = lang.err_alloc; + log[DGN_BOUNDS] = lang.err_bounds; + log[DGN_DOMAIN] = lang.err_domain; + log[DGN_MLOCK] = lang.err_mlock; + log[DGN_XSESSIONS_DIR] = lang.err_xsessions_dir; + log[DGN_XSESSIONS_OPEN] = lang.err_xsessions_open; + log[DGN_PATH] = lang.err_path; + log[DGN_CHDIR] = lang.err_chdir; + log[DGN_PWNAM] = lang.err_pwnam; + log[DGN_USER_INIT] = lang.err_user_init; + log[DGN_USER_GID] = lang.err_user_gid; + log[DGN_USER_UID] = lang.err_user_uid; + log[DGN_PAM] = lang.err_pam; + log[DGN_HOSTNAME] = lang.err_hostname; +} + +// ly! +int main(int argc, char** argv) +{ + // init error lib + log_init(dgn_init()); + + // load config + config_defaults(); + lang_defaults(); + + config_load(); + + if (strcmp(config.lang, "en") != 0) + { + lang_load(); + } + + // parse args + const struct argoat_sprig sprigs[ARG_COUNT] = + { + {NULL, 0, NULL, NULL}, + {"help", 0, NULL, arg_help}, + {"h", 0, NULL, arg_help}, + {"version", 0, NULL, arg_version}, + {"v", 0, NULL, arg_version}, + }; + + struct argoat args = {sprigs, ARG_COUNT, NULL, 0, 0}; + argoat_graze(&args, argc, argv); + + // init inputs + struct desktop desktop; + struct text login; + struct text password; + input_desktop(&desktop); + input_text(&login, config.max_login_len); + input_text(&password, config.max_password_len); + + if (dgn_catch()) + { + config_free(); + lang_free(); + return 1; + } + + void* input_structs[3] = + { + (void*) &desktop, + (void*) &login, + (void*) &password, + }; + + void (*input_handles[3]) (void*, struct tb_event*) = + { + handle_desktop, + handle_text, + handle_text, + }; + + desktop_load(&desktop); + load(&desktop, &login); + + // start termbox + tb_init(); + tb_select_output_mode(TB_OUTPUT_NORMAL); + tb_clear(); + + // init visible elements + struct tb_event event; + struct term_buf buf; + u8 active_input = config.default_input; + + (*input_handles[active_input])(input_structs[active_input], NULL); + + // init drawing stuff + draw_init(&buf); + + if (config.animate) + { + animate_init(&buf); + + if (dgn_catch()) + { + config.animate = false; + dgn_reset(); + } + } + + // init state info + int error; + bool run = true; + bool update = true; + bool reboot = false; + bool shutdown = false; + u8 auth_fails = 0; + + switch_tty(&buf); + + // main loop + while (run) + { + if (update) + { + if (auth_fails < 10) + { + tb_clear(); + animate(&buf); + draw_box(&buf); + draw_labels(&buf); + draw_f_commands(); + draw_lock_state(&buf); + position_input(&buf, &desktop, &login, &password); + draw_desktop(&desktop); + draw_input(&login); + draw_input_mask(&password); + update = config.animate; + } + else + { + usleep(10000); + update = cascade(&buf, &auth_fails); + } + + tb_present(); + } + + error = tb_peek_event(&event, config.min_refresh_delta); + + if (error < 0) + { + continue; + } + + if (event.type == TB_EVENT_KEY) + { + if (event.key == TB_KEY_F1) + { + shutdown = true; + break; + } + else if (event.key == TB_KEY_F2) + { + reboot = true; + break; + } + else if (event.key == TB_KEY_CTRL_C) + { + break; + } + else if ((event.key == TB_KEY_ARROW_UP) && (active_input > 0)) + { + --active_input; + update = true; + } + else if (((event.key == TB_KEY_ARROW_DOWN) + || (event.key == TB_KEY_ENTER)) + && (active_input < 2)) + { + ++active_input; + update = true; + } + else if (event.key == TB_KEY_TAB) + { + ++active_input; + + if (active_input > 2) + { + active_input = 0; + } + + update = true; + } + else if (event.key == TB_KEY_ENTER) + { + save(&desktop, &login); + auth(&desktop, &login, &password, &buf); + update = true; + + if (dgn_catch()) + { + ++auth_fails; + + if (dgn_output_code() != DGN_PAM) + { + buf.info_line = dgn_output_log(); + } + + dgn_reset(); + } + else + { + buf.info_line = lang.logout; + } + + load(&desktop, &login); + } + else + { + (*input_handles[active_input])( + input_structs[active_input], + &event); + update = true; + } + } + } + + // stop termbox + tb_shutdown(); + + // free inputs + input_desktop_free(&desktop); + input_text_free(&login); + input_text_free(&password); + free_hostname(); + + // unload config + draw_free(&buf); + lang_free(); + + if (shutdown) + { + execl("/bin/sh", "sh", "-c", config.shutdown_cmd, NULL); + } + + if (reboot) + { + execl("/bin/sh", "sh", "-c", config.restart_cmd, NULL); + } + + config_free(); + + return 0; +} diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 00000000..1c28c82f --- /dev/null +++ b/src/utils.c @@ -0,0 +1,245 @@ +#include "configator.h" +#include "dragonfail.h" + +#include "inputs.h" +#include "config.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include + +#if defined(__DragonFly__) || defined(__FreeBSD__) + #include +#else // linux + #include +#endif + +void desktop_crawl( + struct desktop* target, + char* sessions, + enum display_server server) +{ + DIR* dir; + struct dirent* dir_info; + int ok; + + ok = access(sessions, F_OK); + + if (ok == -1) + { + dgn_throw(DGN_XSESSIONS_DIR); + return; + } + + dir = opendir(sessions); + + if (dir == NULL) + { + dgn_throw(DGN_XSESSIONS_OPEN); + return; + } + + char* name = NULL; + char* exec = NULL; + + struct configator_param map_desktop[] = + { + {"Exec", &exec, config_handle_str}, + {"Name", &name, config_handle_str}, + }; + + struct configator_param* map[] = + { + NULL, + map_desktop, + }; + + struct configator_param sections[] = + { + {"Desktop Entry", NULL, NULL}, + }; + + uint16_t map_len[] = {0, 2}; + uint16_t sections_len = 1; + + struct configator desktop_config; + desktop_config.map = map; + desktop_config.map_len = map_len; + desktop_config.sections = sections; + desktop_config.sections_len = sections_len; + +#if defined(NAME_MAX) + char path[NAME_MAX]; +#elif defined(_POSIX_PATH_MAX) + char path[_POSIX_PATH_MAX]; +#else + char path[1024]; +#endif + + dir_info = readdir(dir); + + while (dir_info != NULL) + { + if ((dir_info->d_name)[0] == '.') + { + dir_info = readdir(dir); + continue; + } + + snprintf(path, (sizeof (path)) - 1, "%s/", sessions); + strncat(path, dir_info->d_name, (sizeof (path)) - 1); + configator(&desktop_config, path); + + if ((name != NULL) && (exec != NULL)) + { + input_desktop_add(target, name, exec, server); + } + + name = NULL; + exec = NULL; + dir_info = readdir(dir); + } + + closedir(dir); +} + +void desktop_load(struct desktop* target) +{ + desktop_crawl(target, config.waylandsessions, DS_WAYLAND); + desktop_crawl(target, config.xsessions, DS_XORG); +} + +static char* hostname_backup = NULL; + +void hostname(char** out) +{ + if (hostname_backup != NULL) + { + *out = hostname_backup; + return; + } + + int maxlen = sysconf(_SC_HOST_NAME_MAX); + + if (maxlen < 0) + { + maxlen = _POSIX_HOST_NAME_MAX; + } + + hostname_backup = malloc(maxlen + 1); + + if (hostname_backup == NULL) + { + dgn_throw(DGN_ALLOC); + return; + } + + if (gethostname(hostname_backup, maxlen) < 0) + { + dgn_throw(DGN_HOSTNAME); + return; + } + + hostname_backup[maxlen] = '\0'; + *out = hostname_backup; +} + +void free_hostname() +{ + free(hostname_backup); +} + +void switch_tty(struct term_buf* buf) +{ + FILE* console = fopen(config.console_dev, "w"); + + if (console == NULL) + { + buf->info_line = lang.err_console_dev; + return; + } + + int fd = fileno(console); + + ioctl(fd, VT_ACTIVATE, config.tty); + ioctl(fd, VT_WAITACTIVE, config.tty); + + fclose(console); +} + +void save(struct desktop* desktop, struct text* login) +{ + if (config.save) + { + FILE* fp = fopen(config.save_file, "wb+"); + + if (fp != NULL) + { + fprintf(fp, "%s\n%d", login->text, desktop->cur); + fclose(fp); + } + } +} + +void load(struct desktop* desktop, struct text* login) +{ + if (!config.load) + { + return; + } + + FILE* fp = fopen(config.save_file, "rb"); + + if (fp == NULL) + { + return; + } + + char* line = malloc(config.max_login_len + 1); + + if (line == NULL) + { + fclose(fp); + return; + } + + if (fgets(line, config.max_login_len + 1, fp)) + { + int len = strlen(line); + strncpy(login->text, line, login->len); + + if (len == 0) + { + login->end = login->text; + } + else + { + login->end = login->text + len - 1; + login->text[len - 1] = '\0'; + } + } + else + { + fclose(fp); + free(line); + return; + } + + if (fgets(line, config.max_login_len + 1, fp)) + { + int saved_cur = abs(atoi(line)); + + if (saved_cur < desktop->len) + { + desktop->cur = saved_cur; + } + } + + fclose(fp); + free(line); +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 00000000..09135803 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,15 @@ +#ifndef H_LY_UTILS +#define H_LY_UTILS + +#include "draw.h" +#include "inputs.h" +#include "config.h" + +void desktop_load(struct desktop* target); +void hostname(char** out); +void free_hostname(); +void switch_tty(struct term_buf* buf); +void save(struct desktop* desktop, struct text* login); +void load(struct desktop* desktop, struct text* login); + +#endif