Commit unfinished full rewrite (Zig 0.11.0)

What needs to be dealt with:
- Matrix animation
- Authentication part
- Testing on actual TTY (not just virtual console)

Signed-off-by: AnErrupTion <anerruption@disroot.org>
pull/597/head
AnErrupTion 5 months ago
parent d1cf1ebd80
commit 92e1f083a1
No known key found for this signature in database
GPG Key ID: 3E85EB44F610AD7F

@ -1,6 +1,11 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const data_directory = b.option([]const u8, "data_directory", "Specify a default data directory (default is /etc/ly)");
const build_options = b.addOptions();
build_options.addOption([]const u8, "data_directory", data_directory orelse "/etc/ly");
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
@ -24,30 +29,26 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
});
exe.addOptions("build_options", build_options);
const ini = b.dependency("ini", .{});
exe.addModule("ini", ini.module("ini"));
const clap = b.dependency("clap", .{ .target = target, .optimize = optimize });
exe.addModule("clap", clap.module("clap"));
exe.linkSystemLibrary("pam");
exe.linkSystemLibrary("xcb");
exe.linkLibC();
exe.addIncludePath("src");
exe.addIncludePath("dep/configator/src");
exe.addIncludePath("dep/dragonfail/src");
exe.addIncludePath("dep/termbox_next/src");
exe.addCSourceFile("src/draw.c", &c_args);
exe.addCSourceFile("src/inputs.c", &c_args);
exe.addCSourceFile("src/login.c", &c_args);
exe.addCSourceFile("src/utils.c", &c_args);
exe.addCSourceFile("dep/configator/src/configator.c", &c_args);
exe.addCSourceFile("dep/dragonfail/src/dragonfail.c", &c_args);
exe.addCSourceFile("dep/termbox_next/src/input.c", &c_args);
exe.addCSourceFile("dep/termbox_next/src/memstream.c", &c_args);
exe.addCSourceFile("dep/termbox_next/src/ringbuffer.c", &c_args);
exe.addCSourceFile("dep/termbox_next/src/term.c", &c_args);
exe.addCSourceFile("dep/termbox_next/src/termbox.c", &c_args);
exe.addCSourceFile("dep/termbox_next/src/utf8.c", &c_args);
exe.addIncludePath(.{ .path = "dep/termbox_next/src" });
exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/input.c" }, .flags = &c_args });
exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/memstream.c" }, .flags = &c_args });
exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/ringbuffer.c" }, .flags = &c_args });
exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/term.c" }, .flags = &c_args });
exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/termbox.c" }, .flags = &c_args });
exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/utf8.c" }, .flags = &c_args });
b.installArtifact(exe);
@ -55,9 +56,7 @@ pub fn build(b: *std.Build) void {
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
if (b.args) |args| run_cmd.addArgs(args);
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);

@ -1,10 +1,14 @@
.{
.name = "ly",
.version = "0.7.0",
.version = "1.0.0",
.dependencies = .{
.ini = .{
.url = "https://github.com/AnErrupTion/zig-ini/archive/a40f3cae04939de0d795812a73633e0563cf3132.tar.gz",
.hash = "1220b39fc35714d235fd9633422f5ba3c4aba44f1ecaf29cf91515963f0b08a3da89",
.url = "https://github.com/AnErrupTion/zig-ini/archive/7ab77196a4dc63d1ede50e0a1af1a8325c152f2f.tar.gz",
.hash = "12204b1d133060dd0c4304d5e0fbdb33a70038118e7c112b14b5f8d176ea15cd5939",
},
.clap = .{
.url = "https://github.com/Hejsil/zig-clap/archive/f49b94700e0761b7514abdca0e4f0e7f3f938a93.tar.gz",
.hash = "1220f48518ce22882e102255ed3bcdb7aeeb4891f50b2cdd3bd74b5b2e24d3149ba2",
},
}
}
}

@ -1,2 +0,0 @@
bin
obj

@ -1,13 +0,0 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
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.

@ -1,95 +0,0 @@
# Configator
Configator is a lightweight library for ini config file parsing.
This was created to make it easy following the "DRY" coding rule
without using macros, and with flexibility in mind.
It integrates very well with the [Argoat](https://github.com/nullgemm/argoat.git)
arguments parser library, by using the same function pointers format.
This way, you can easily load settings from an ini file while overloading them
with command-line arguments if needed: the handling functions will be the same.
Configator does not use any macro or dynamic memory allocation,
and was built in less than 350 lines of C99 code.
## Testing
Run `make` to compile an example executable and perform basic testing
## Using
### TL;DR
Please see `example.c` for the condensed version
(or better, read the actual documentation below).
It is a bit too long to be copied here twice...
### Details
Include `argoat.h` and compile `argoat.c` with your code.
Write the functions that will handle your parameters.
They will be called during the parsing process, in the order given by the user
```
void handle_config_u8(void* data, char** value, const int pars_count)
{
if (pars_count > 0)
{
*((uint8_t*) data) = atoi(*value);
}
}
```
In your `main`, declare the variables to configure.
They will be passed to the corresponding functions as `void* data`
```
uint8_t answer = 0;
```
Declare the arrays of parameters by section, starting with the general section.
If you don't want to handle parameters in some section, just declare it `NULL`.
```
struct configator_param* map_no_section = NULL;
```
Declare real sections parameters afterwards
```
struct configator_param map_test_section[] =
{
{"ping", &answer, handle_config_u8},
{"pong", &answer, handle_config_u8},
};
```
Then group them in the map
```
struct configator_param* map[] =
{
map_no_section,
map_test_section
};
```
And declare the sections array. Configator will execute the pointed function
with `NULL` arguments at the beginning of each detected section.
You can also declare sections with `NULL` parameters, in which case nothing
will be executed.
```
struct configator_param sections[] =
{
{"network_test", &answer, handle_config_u8},
};
```
Don't forget to put the right numbers in the lenght variables
```
uint16_t map_len[] = {0, 2};
uint16_t sections_len = 1;
```
Then initialize and use configator
```
struct configator config;
config.map = map;
config.map_len = map_len;
config.sections = sections;
config.sections_len = sections_len;
configator(&config, "config.ini");
printf("answer = %d\n", answer);
```

@ -1,37 +0,0 @@
sectionless = 0000
[sections_test]
[regular]
[left-padded]
[right-padded]
[middle-padded]
[smaller]
[ right]
[left ]
[ middle ]
[]
[ ]
[three wrong words]
[]]
[[]
[[]]
[params_tests]
# comment
# padded comment
test = 1111
left-padded = 2222
right-padded = 3333
minified=4444
multi = 4444 3333 2222 1111
spaces = " middle "
wrong declaration = 5555
wrong line
[test_section]
answer = 42
[network_test]
ping = 255
[question]

@ -1,309 +0,0 @@
#include "configator.h"
#include <stddef.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
// returns the index of the searched element, or len if it can't be found
static uint16_t search(struct configator_param* config, uint16_t len, char* key)
{
// strcmp indicator
int8_t disc;
// initial tested index
uint16_t i = len / 2;
// initial bounds (inclusive)
uint16_t k = 0;
uint16_t l = len - 1;
// skip directly to the final check
if (len > 1)
{
// as long as a match is possible
do
{
disc = strcmp(config[i].key, key);
if (disc == 0)
{
// found by chance
return i;
}
else if (disc > 0)
{
l = i;
i = (i + k) / 2; // floor
}
else
{
k = i;
i = (i + l) / 2 + (i + l) % 2; // ceil
}
if (len == 2)
{
break;
}
}
while ((k+1) != l);
}
if (len > 0)
{
// final check
disc = strcmp(config[i].key, key);
if (disc == 0)
{
// found by dichotomy
return i;
}
}
// not found
return len;
}
static void configator_save_section(struct configator* config, char* line)
{
char c;
uint16_t index;
uint16_t k = 0; // last non-space pos
uint16_t l = 0; // second last non-space pos
// leading spaces
do
{
++line;
c = line[0];
}
while ((c != '\0') && isspace(c));
if (c == '[')
{
++line;
c = line[0];
}
// trailing spaces
for (uint16_t i = 1; c != '\0'; ++i)
{
if ((c != ']') && !isspace(c))
{
// we use two variables to avoid
// counting the ending ']'
l = k + 1; // we *must* increment here
k = i;
}
c = line[i];
}
// terminator
line[l] = '\0';
if (l == 0)
{
return;
}
// saving
strncpy(config->section, line, l + 1);
// searching
index = search(
config->sections,
config->sections_len,
config->section);
#ifdef CONFIGATOR_DEBUG
printf("[%s]\n", line);
#endif
// calling the function
if (index != config->sections_len)
{
config->current_section = index + 1;
if (config->sections[index].handle != NULL)
{
config->sections[index].handle(
config->sections[index].data,
NULL,
0);
}
}
}
static void configator_save_param(struct configator* config, char* line)
{
char c;
uint16_t index;
uint16_t i = 0;
uint16_t k = 0;
// leading chars
do
{
++i;
c = line[i];
}
while ((c != '\0') && (c != '=') && !isspace(c));
// empty line
if (c == '\0')
{
config->param[0] = '\0';
config->value[0] = '\0';
return;
}
// end of the param
k = i;
// spaces before next char if any
while ((c != '\0') && isspace(c))
{
++i;
c = line[i];
}
// that next char must be '='
if (c != '=')
{
config->param[0] = '\0';
config->value[0] = '\0';
return;
}
else
{
++i;
c = line[i];
}
// spaces after '='
while ((c != '\0') && isspace(c))
{
++i;
c = line[i];
}
line[k] = '\0';
strncpy(config->param, line, k + 1);
strncpy(config->value, line + i, strlen(line + i) + 1);
// searching
if ((config->current_section == 0) && (config->map_len[0] == 0))
{
return;
}
index = search(
config->map[config->current_section],
config->map_len[config->current_section],
config->param);
#ifdef CONFIGATOR_DEBUG
printf("%s = \"%s\"\n", config->param, config->value);
#endif
// calling the function
if ((index != config->map_len[config->current_section])
&& (config->map[config->current_section][index].handle != NULL))
{
char* tmp = (char*) config->value;
config->map[config->current_section][index].handle(
config->map[config->current_section][index].data,
&(tmp),
1);
}
}
static void configator_read(FILE* fp, char* line)
{
int c = fgetc(fp);
uint16_t i = 0;
uint16_t k = 0;
if (c == EOF)
{
line[0] = '\0';
return;
}
while ((c != '\n') && (c != EOF))
{
if ((i < (CONFIGATOR_MAX_LINE + 1)) // maximum len
&& ((i > 0) || !isspace(c))) // skips leading spaces
{
// used to trim trailing spaces
// and to terminate overflowing string
if (!isspace(c))
{
k = i;
}
line[i] = c;
++i;
}
c = fgetc(fp);
}
if (i == (CONFIGATOR_MAX_LINE + 1))
{
line[k] = '\0';
}
else
{
line[k + 1] = '\0';
}
}
int configator(struct configator* config, const char* path)
{
FILE* fp = fopen(path, "r");
if (fp == NULL)
{
return -1;
}
config->section[0] = '\0';
config->param[0] = '\0';
config->value[0] = '\0';
config->current_section = 0;
// event loop
char line[CONFIGATOR_MAX_LINE + 1];
while (1)
{
configator_read(fp, line);
// end of file
if (feof(fp))
{
break;
}
// comment
else if (line[0] == '#')
{
continue;
}
// section
else if ((line[0] == '[') && (line[strlen(line) - 1] == ']'))
{
configator_save_section(config, line);
}
// param
else
{
configator_save_param(config, line);
}
}
fclose(fp);
return 0;
}

@ -1,35 +0,0 @@
#ifndef H_CONFIGATOR
#define H_CONFIGATOR
#include <stdint.h>
#define CONFIGATOR_MAX_LINE 80
#if 0
#define CONFIGATOR_DEBUG
#endif
struct configator_param
{
char* key;
void* data;
void (*handle)(void* data, char** value, const int pars_count);
};
struct configator
{
char section[CONFIGATOR_MAX_LINE];
char param[CONFIGATOR_MAX_LINE];
char value[CONFIGATOR_MAX_LINE];
uint16_t current_section;
struct configator_param** map;
struct configator_param* sections;
uint16_t* map_len;
uint16_t sections_len;
};
int configator(struct configator* config, const char* path);
#endif

@ -1,71 +0,0 @@
#include "configator.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
void handle_config_u8(void* data, char** value, const int pars_count)
{
if (pars_count > 0)
{
*((uint8_t*) data) = atoi(*value);
}
}
void handle_question(void* data, char** value, const int pars_count)
{
*((uint8_t*) data) = 23;
}
int main(int argc, char** argv)
{
uint8_t answer = 0;
uint8_t question = 0;
// parameters, grouped in sections
struct configator_param* map_no_section = NULL;
struct configator_param* map_question_section = NULL;
struct configator_param map_test_section[] =
{
{"aaabbb", &answer, handle_config_u8},
{"aabbaa", &answer, handle_config_u8},
{"answer", &answer, handle_config_u8},
{"cccccc", &answer, handle_config_u8},
{"cccddd", &answer, handle_config_u8},
{"daaaaa", &answer, handle_config_u8},
{"ddaaaa", &answer, handle_config_u8},
{"eeeeee", &answer, handle_config_u8}
};
struct configator_param* map[] =
{
map_no_section,
map_question_section,
map_test_section
};
// sections (used to execute functions at sections start)
struct configator_param sections[] =
{
{"question", &question, handle_question},
{"test_section", NULL, NULL},
};
// number of parameters, by section
uint16_t map_len[] = {0, 0, 8};
// number of sections
uint16_t sections_len = 2;
// configator object
struct configator config;
config.map = map;
config.map_len = map_len;
config.sections = sections;
config.sections_len = sections_len;
// execute configuration
configator(&config, "config.ini");
printf("question = %d\n", question);
printf("answer = %d\n", answer);
return 0;
}

@ -1,22 +0,0 @@
#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_SIZE, // do not remove
};
//#define DRAGONFAIL_SKIP
#define DRAGONFAIL_BASIC_LOG
#define DRAGONFAIL_THROW_BASIC_LOG
#define DRAGONFAIL_THROW_DEBUG_LOG
//#define DRAGONFAIL_ABORT
#endif

@ -1,46 +0,0 @@
#include <stdio.h>
#include "dragonfail.h"
int div(int num, int den)
{
if (den == 0)
{
dgn_throw(DGN_DOMAIN);
return 0;
}
return num / den;
}
void log_init(char** log)
{
log[DGN_OK] = "out-of-bounds log message"; // special case
log[DGN_NULL] = "null pointer";
log[DGN_ALLOC] = "failed memory allocation";
log[DGN_BOUNDS] = "out-of-bounds index";
log[DGN_DOMAIN] = "invalid domain";
}
int main()
{
log_init(dgn_init());
int i;
int q;
for (i = -2; i < 3; ++i)
{
q = div(42, i);
if (dgn_catch())
{
printf("skipping division by zero\n");
dgn_reset();
continue;
}
printf("42/%d = %d\n", i, q);
}
return 0;
}

@ -1,13 +0,0 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
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.

@ -1,77 +0,0 @@
# Dragonfail
Dragonfail is a simple library providing basic error handling functionnalities.
It was designed to be as lightweight as possible, and can be completely disabled
with only one `#define` (more on that later).
Dragonfail was designed to be fast and uses inline functions exclusively. These
calls modify a global context which is not directly accessible by the programmer.
All the error codes must be written in an enum in **your** `dragonfail_error.h` file.
Because of this rather unusual architecture, the file must be found by the compiler
when it is processing `dragonfail.c` (in addition to your own source code of course).
## Testing
Run `make` to compile an example, and `make run` to execute it.
## Defines
This header can also contain some `#define` to modify dragonfail's behaviour:
- `DRAGONFAIL_SKIP` completely disables the whole library, making it completely
disappear from the binary (unless your compiler is a massive douche).
- `DRAGONFAIL_BASIC_LOG` enables the `dgn_basic_log()` function calls
- `DRAGONFAIL_THROW_BASIC_LOG` makes `dgn_throw()` call `dgn_basic_log()` automatically
- `DRAGONFAIL_THROW_DEBUG_LOG` also prints the file and line in which
`dgn_throw()` is called (you don't even need to compile with symbols
because this is achieved the smart way using simple C99 macros)
- `DRAGONFAIL_ABORT` makes `dgn_throw()` call `abort()`
Again, these `#define` must be placed in **your** `dragonfail_error.h` file.
## Using
### TL;DR
see the `example` folder :)
### Documentation
```
char** dgn_init();
```
This intializes the context to `DGN_OK` (no error) and returns the array of strings
you can fill with log messages corresponding to the errors you added in the enum.
```
void dgn_reset();
```
This resets the context to `DGN_OK`.
```
void dgn_basic_log();
```
This prints the message corresponding to the current error to stderr.
```
void dgn_throw(enum dgn_error new_code);
```
This sets the error to the given code.
```
char dgn_catch();
```
This returns true if the context currently holds an error
## Why is the architecture so strange?
The dragonfail context is global (extern) but really *implemented* in `dragonfail.c`.
Its type depends on the size of the enum so it is *declared* in `dragonfail_private.h`:
this way we can include the user's `dragonfail_error.h` and get `DGN_SIZE`.
The inline functions need to access this context and **we want it private**, so we can't
*implement* them directly in the header as a lot of people seem to appreciate. Instead,
we will *declare* them here, and put the *implementations* in `dragonfail.c`: this way
we can access the global context without including its declaration, because it is
implemented here as well.
When you include `dragonfail.h`, you get access to the inline functions declarations
and thanks to this design any compiler will do the rest of the job automatically. Yes,
this whole thing is useless and over-engineered. And yes, I had fun doing it...
## Greetings
Jinjer for the cool music \m/
Haiku developers for indirectly giving me the idea

@ -1,106 +0,0 @@
#include "dragonfail.h"
#include "dragonfail_private.h"
#include "dragonfail_error.h"
#ifdef DRAGONFAIL_BASIC_LOG
#include <stdio.h>
#endif
#ifdef DRAGONFAIL_ABORT
#include <stdlib.h>
#endif
// extern
struct dgn dgn;
inline char** dgn_init()
{
#ifndef DRAGONFAIL_SKIP
dgn.error = DGN_OK;
return dgn.log;
#else
return NULL;
#endif
}
inline void dgn_reset()
{
#ifndef DRAGONFAIL_SKIP
dgn.error = DGN_OK;
#endif
}
inline void dgn_basic_log()
{
#ifdef DRAGONFAIL_BASIC_LOG
#ifndef DRAGONFAIL_SKIP
if (dgn.error < DGN_SIZE)
{
fprintf(stderr, "%s\n", dgn.log[dgn.error]);
}
else
{
fprintf(stderr, "%s\n", dgn.log[0]);
}
#endif
#endif
}
inline char* dgn_output_log()
{
if (dgn.error < DGN_SIZE)
{
return dgn.log[dgn.error];
}
else
{
return dgn.log[0];
}
}
enum dgn_error dgn_output_code()
{
return dgn.error;
}
#ifdef DRAGONFAIL_THROW_DEBUG_LOG
inline void dgn_throw_extra(
enum dgn_error new_code,
const char* file,
unsigned int line)
#else
inline void dgn_throw(
enum dgn_error new_code)
#endif
{
#ifndef DRAGONFAIL_SKIP
dgn.error = new_code;
#ifdef DRAGONFAIL_THROW_BASIC_LOG
#ifdef DRAGONFAIL_BASIC_LOG
#ifdef DRAGONFAIL_THROW_DEBUG_LOG
fprintf(
stderr,
"error in %s line %u: ",
file,
line);
#endif
dgn_basic_log();
#endif
#endif
#ifdef DRAGONFAIL_ABORT
abort();
#endif
#endif
}
inline char dgn_catch()
{
#ifndef DRAGONFAIL_SKIP
return (dgn.error != DGN_OK);
#else
return 0;
#endif
}

@ -1,22 +0,0 @@
#ifndef H_DRAGONFAIL
#define H_DRAGONFAIL
#include "dragonfail_error.h"
#ifdef DRAGONFAIL_THROW_DEBUG_LOG
#define dgn_throw(new_code) dgn_throw_extra(new_code, DGN_FILE, DGN_LINE)
#define DGN_FILE __FILE__
#define DGN_LINE __LINE__
void dgn_throw_extra(enum dgn_error new_code, const char* file, unsigned int line);
#else
void dgn_throw(enum dgn_error new_code);
#endif
char** dgn_init();
void dgn_reset();
void dgn_basic_log();
char* dgn_output_log();
enum dgn_error dgn_output_code();
char dgn_catch();
#endif

@ -1,14 +0,0 @@
#ifndef H_DRAGONFAIL_PRIVATE
#define H_DRAGONFAIL_PRIVATE
#include "dragonfail_error.h"
struct dgn
{
enum dgn_error error;
char* log[DGN_SIZE];
};
extern struct dgn dgn;
#endif

@ -625,17 +625,6 @@ static void get_term_size(int* w, int* h)
}
}
static void update_term_size(void)
{
struct winsize sz;
memset(&sz, 0, sizeof(sz));
ioctl(out_fileno, TIOCGWINSZ, &sz);
termw = sz.ws_col;
termh = sz.ws_row;
}
static void send_attr(uint32_t fg, uint32_t bg)
{
#define LAST_ATTR_INIT 0xFFFFFFFF
@ -789,6 +778,17 @@ static void update_size(void)
send_clear();
}
static void update_term_size(void)
{
struct winsize sz;
memset(&sz, 0, sizeof(sz));
ioctl(out_fileno, TIOCGWINSZ, &sz);
termw = sz.ws_col;
termh = sz.ws_row;
}
static int wait_fill_event(struct tb_event* event, struct timeval* timeout)
{
#define ENOUGH_DATA_FOR_INPUT_PARSING 128

@ -1,18 +1,16 @@
[ly]
# Animation enabled/disabled
animate = false
# The active animation
# 0 -> PSX DOOM fire (default)
# 1 -> CMatrix
animation = 0
# none -> Nothing (default)
# doom -> PSX DOOM fire
# matrix -> CMatrix
animation = doom
# Format string for clock in top right corner (see strftime specification)
clock =
clock = %c
# Enable/disable big clock
bigclock = false
bigclock = true
# The character used to mask the password
asterisk = *
@ -66,6 +64,7 @@ max_password_len = 255
# Input box active by default on startup
# Available inputs: session, login, password
default_input = login
# Load the saved desktop and username
@ -78,13 +77,19 @@ save = true
save_file = /etc/ly/save
# Remove F1/F2 command hints
hide_f1_commands = false
# Remove power management command hints
hide_key_hints = false
# Specifies the key used for shutdown (F1-F12)
shutdown_key = F1
# Specifies the key used for restart (F1-F12)
restart_key = F2
# Command executed when pressing F1
# Command executed when pressing shutdown_key
shutdown_cmd = /sbin/shutdown -a now
# Command executed when pressing F2
# Command executed when pressing restart_key
restart_cmd = /sbin/shutdown -r now
@ -112,6 +117,9 @@ service_name = ly
# Terminal reset command (tput is faster)
term_reset_cmd = /usr/bin/tput reset
# Terminal restore cursor command
term_restore_cursor_cmd = /usr/bin/tput cnorm
# Cookie generator
mcookie_cmd = /usr/bin/mcookie
@ -119,9 +127,6 @@ mcookie_cmd = /usr/bin/mcookie
# Wayland setup command
wayland_cmd = /etc/ly/wsetup.sh
# Add wayland specifier to session names
wayland_specifier = false
# Wayland desktop environments
waylandsessions = /usr/share/wayland-sessions

@ -35,12 +35,12 @@ err_user_init = error al inicialitzar usuari
err_user_uid = error al establir el UID de l'usuari
err_xsessions_dir = error al cercar la carpeta de sessions
err_xsessions_open = error al obrir la carpeta de sessions
f1 = F1 aturar
f2 = F2 reiniciar
login = iniciar sessió
logout = tancar sessió
numlock = Bloq Num
password = Clau
restart = reiniciar
shell = shell
shutdown = aturar
wayland = wayland
xinitrc = xinitrc

@ -35,12 +35,12 @@ err_user_init = inicializace uživatele selhala
err_user_uid = nastavení UID uživateli selhalo
err_xsessions_dir = nepodařilo se najít složku relací
err_xsessions_open = nepodařilo se otevřít složku relací
f1 = F1 vypnout
f2 = F2 restartovat
login = uživatel
logout = odhlášen
numlock = numlock
password = heslo
restart = restartovat
shell = příkazový řádek
shutdown = vypnout
wayland = wayland
xinitrc = xinitrc

@ -35,12 +35,12 @@ err_user_init = Initialisierung des Nutzers fehlgeschlagen
err_user_uid = Setzen der Benutzer Id fehlgeschlagen
err_xsessions_dir = Fehler beim finden des Sitzungsordners
err_xsessions_open = Fehler beim öffnen des Sitzungsordners
f1 = F1 Herunterfahren
f2 = F2 Neustarten
login = Anmelden
logout = Abgemeldet
numlock = Numtaste
password = Passwort
restart = Neustarten
shell = shell
shutdown = Herunterfahren
wayland = wayland
xinitrc = xinitrc

@ -35,12 +35,12 @@ 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
restart = reboot
shell = shell
shutdown = shutdown
wayland = wayland
xinitrc = xinitrc

@ -35,12 +35,12 @@ err_user_init = error al inicializar usuario
err_user_uid = error al establecer el UID del usuario
err_xsessions_dir = error al buscar la carpeta de sesiones
err_xsessions_open = error al abrir la carpeta de sesiones
f1 = F1 apagar
f2 = F2 reiniciar
login = iniciar sesión
logout = cerrar sesión
numlock = Bloq Num
password = contraseña
restart = reiniciar
shell = shell
shutdown = apagar
wayland = wayland
xinitrc = xinitrc

@ -35,12 +35,12 @@ 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
restart = redémarrer
shell = shell
shutdown = éteindre
wayland = wayland
xinitrc = xinitrc

@ -35,12 +35,12 @@ err_user_init = impossibile inizializzare utente
err_user_uid = impossible impostare UID utente
err_xsessions_dir = impossibile localizzare cartella sessioni
err_xsessions_open = impossibile aprire cartella sessioni
f1 = F1 arresto
f2 = F2 riavvio
login = username
logout = scollegato
numlock = numlock
password = password
restart = riavvio
shell = shell
shutdown = arresto
wayland = wayland
xinitrc = xinitrc

@ -35,12 +35,12 @@ err_user_init = nie udało się zainicjalizować użytkownika
err_user_uid = nie udało się ustawić UID użytkownika
err_xsessions_dir = nie udało się znaleźć folderu sesji
err_xsessions_open = nie udało się otworzyć folderu sesji
f1 = F1 wyłącz
f2 = F2 uruchom ponownie
login = login
logout = wylogowano
numlock = numlock
password = hasło
restart = uruchom ponownie
shell = powłoka
shutdown = wyłącz
wayland = wayland
xinitrc = xinitrc

@ -35,12 +35,12 @@ err_user_init = erro ao iniciar o utilizador
err_user_uid = erro ao definir o UID do utilizador
err_xsessions_dir = erro ao localizar a pasta das sessões
err_xsessions_open = erro ao abrir a pasta das sessões
f1 = F1 encerrar
f2 = F2 reiniciar
login = iniciar sessão
logout = terminar sessão
numlock = numlock
password = palavra-passe
restart = reiniciar
shell = shell
shutdown = encerrar
wayland = wayland
xinitrc = xinitrc

@ -35,12 +35,12 @@ err_user_init = não foi possível iniciar o usuário
err_user_uid = não foi possível definir o UID do usuário
err_xsessions_dir = não foi possível encontrar a pasta das sessões
err_xsessions_open = não foi possível abrir a pasta das sessões
f1 = F1 desligar
f2 = F2 reiniciar
login = conectar
logout = desconectado
numlock = numlock
password = senha
restart = reiniciar
shell = shell
shutdown = desligar
wayland = wayland
xinitrc = xinitrc

@ -35,12 +35,12 @@ err_perm_user = nu s-a putut face downgrade permisiunilor de utilizator
f1 = F1 opreşte sistemul
f2 = F2 resetează
login = utilizator
logout = opreşte sesiunea
numlock = numlock
password = parolă
restart = resetează
shell = shell
shutdown = opreşte sistemul
wayland = wayland
xinitrc = xinitrc

@ -35,12 +35,12 @@ err_user_init = не удалось инициализировать польз
err_user_uid = не удалось установить UID пользователя
err_xsessions_dir = не удалось найти сессионную папку
err_xsessions_open = не удалось открыть сессионную папку
f1 = F1 выключить
f2 = F2 перезагрузить
login = логин
logout = logged out
numlock = numlock
password = пароль
restart = перезагрузить
shell = shell
shutdown = выключить
wayland = wayland
xinitrc = xinitrc

@ -35,12 +35,12 @@ err_user_init = neuspijensa inicijalizacija korisnika
err_user_uid = neuspijesno postavljanje UID-a korisnika
err_xsessions_dir = neuspijesno pronalazenje foldera sesija
err_xsessions_open = neuspijesno otvaranje foldera sesija
f1 = F1 ugasi
f2 = F2 ponovo pokreni
login = korisnik
logout = izlogovan
numlock = numlock
password = lozinka
restart = ponovo pokreni
shell = shell
shutdown = ugasi
wayland = wayland
xinitrc = xinitrc

@ -35,12 +35,12 @@ err_user_init = misslyckades att initialisera användaren
err_user_uid = misslyckades att ställa in användar-UID
err_xsessions_dir = misslyckades att hitta sessionskatalog
err_xsessions_open = misslyckades att öppna sessionskatalog
f1 = F1 stäng av
f2 = F2 starta om
login = inloggning
logout = utloggad
numlock = numlock
password = lösenord
restart = starta om
shell = skal
shutdown = stäng av
wayland = wayland
xinitrc = xinitrc

@ -35,12 +35,12 @@ err_user_init = kullanici oturumu baslatilamadi
err_user_uid = kullanici icin UID ayarlanamadi
err_xsessions_dir = oturumlar klasoru bulunamadi
err_xsessions_open = oturumlar klasoru acilamadi
f1 = F1 makineyi kapat
f2 = F2 yeniden baslat
login = kullanici
logout = oturumdan cikis yapildi
numlock = numlock
password = sifre
restart = yeniden baslat
shell = shell
shutdown = makineyi kapat
wayland = wayland
xinitrc = xinitrc

@ -35,12 +35,12 @@ err_user_init = не вдалося ініціалізувати користу
err_user_uid = не вдалося змінити UID користувача
err_xsessions_dir = не вдалося знайти каталог сесій
err_xsessions_open = не вдалося відкрити каталог сесій
f1 = F1 вимкнути
f2 = F2 перезавантажити
login = логін
logout = вийти
numlock = numlock
password = пароль
restart = перезавантажити
shell = оболонка
shutdown = вимкнути
wayland = wayland
xinitrc = xinitrc

@ -0,0 +1,84 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const utils = @import("../tui/utils.zig");
const interop = @import("../interop.zig");
const termbox = interop.termbox;
const Doom = @This();
pub const STEPS = 13;
pub const FIRE = [_]termbox.tb_cell{
utils.initCell(' ', 9, 0),
utils.initCell(0x2591, 2, 0), // Red
utils.initCell(0x2592, 2, 0), // Red
utils.initCell(0x2593, 2, 0), // Red
utils.initCell(0x2588, 2, 0), // Red
utils.initCell(0x2591, 4, 2), // Yellow
utils.initCell(0x2592, 4, 2), // Yellow
utils.initCell(0x2593, 4, 2), // Yellow
utils.initCell(0x2588, 4, 2), // Yellow
utils.initCell(0x2591, 8, 4), // White
utils.initCell(0x2592, 8, 4), // White
utils.initCell(0x2593, 8, 4), // White
utils.initCell(0x2588, 8, 4), // White
};
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
buffer: []u8,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !Doom {
const buffer = try allocator.alloc(u8, terminal_buffer.width * terminal_buffer.height);
initBuffer(buffer, terminal_buffer.width);
return .{
.allocator = allocator,
.terminal_buffer = terminal_buffer,
.buffer = buffer,
};
}
pub fn deinit(self: Doom) void {
self.allocator.free(self.buffer);
}
pub fn realloc(self: *Doom) !void {
const buffer = try self.allocator.realloc(self.buffer, self.terminal_buffer.width * self.terminal_buffer.height);
initBuffer(buffer, self.terminal_buffer.width);
self.buffer = buffer;
}
pub fn draw(self: Doom) void {
for (0..self.terminal_buffer.width) |x| {
for (1..self.terminal_buffer.height) |y| {
const source = y * self.terminal_buffer.width + x;
const random = (self.terminal_buffer.random.int(u16) % 7) & 3;
var dest = source - random + 1;
if (self.terminal_buffer.width > dest) dest = 0 else dest -= self.terminal_buffer.width;
const buffer_source = self.buffer[source];
const buffer_dest_offset = random & 1;
if (buffer_source < buffer_dest_offset) continue;
var buffer_dest = buffer_source - buffer_dest_offset;
if (buffer_dest > 12) buffer_dest = 0;
self.buffer[dest] = @intCast(buffer_dest);
self.terminal_buffer.buffer[dest] = FIRE[buffer_dest];
self.terminal_buffer.buffer[source] = FIRE[buffer_source];
}
}
}
fn initBuffer(buffer: []u8, width: u64) void {
const length = buffer.len - width;
const slice_start = buffer[0..length];
const slice_end = buffer[length..];
@memset(slice_start, 0);
@memset(slice_end, STEPS - 1);
}

@ -0,0 +1,189 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Random = std.rand.Random;
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const interop = @import("../interop.zig");
const termbox = interop.termbox;
pub const FRAME_DELAY: u64 = 8;
// Allowed codepoints
pub const MIN_CODEPOINT: isize = 33;
pub const MAX_CODEPOINT: isize = 123 - MIN_CODEPOINT;
// Characters change mid-scroll
pub const MID_SCROLL_CHANGE = true;
const Matrix = @This();
pub const Dot = struct {
value: isize,
is_head: bool,
};
pub const Line = struct {
space: isize,
length: isize,
update: isize,
};
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
dots: []Dot,
lines: []Line,
frame: u64,
count: u64,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !Matrix {
const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1));
const lines = try allocator.alloc(Line, terminal_buffer.width);
initBuffers(dots, lines, terminal_buffer.width, terminal_buffer.height, terminal_buffer.random);
return .{
.allocator = allocator,
.terminal_buffer = terminal_buffer,
.dots = dots,
.lines = lines,
.frame = 3,
.count = 0,
};
}
pub fn deinit(self: Matrix) void {
self.allocator.free(self.dots);
self.allocator.free(self.lines);
}
pub fn realloc(self: *Matrix) !void {
const dots = try self.allocator.realloc(self.dots, self.terminal_buffer.width * (self.terminal_buffer.height + 1));
const lines = try self.allocator.realloc(self.lines, self.terminal_buffer.width);
initBuffers(dots, lines, self.terminal_buffer.width, self.terminal_buffer.height, self.terminal_buffer.random);
self.dots = dots;
self.lines = lines;
}
// TODO: Fix!!
pub fn draw(self: *Matrix) void {
var first_column = false;
self.count += 1;
if (self.count > FRAME_DELAY) {
self.frame += 1;
if (self.frame > 4) self.frame = 1;
self.count = 0;
var x: u64 = 0;
while (x < self.terminal_buffer.width) : (x += 2) {
var line = self.lines[x];
if (self.frame <= line.update) continue;
var tail: u64 = 0;
if (self.dots[x].value == -1 and self.dots[self.terminal_buffer.width + x].value == ' ') {
if (line.space <= 0) {
const random = self.terminal_buffer.random.int(i16);
const h: isize = @intCast(self.terminal_buffer.height);
line.length = @mod(random, h - 3) + 3;
line.space = @mod(random, h) + 1;
self.dots[x].value = @mod(random, MAX_CODEPOINT) + MIN_CODEPOINT;
} else {
line.space -= 1;
}
self.lines[x] = line;
first_column = true;
var y: u64 = 0;
var seg_length: u64 = 0;
while (y <= self.terminal_buffer.height) : (y += 1) {
// TODO: Are all these y/height checks required?
var dot = self.dots[y * self.terminal_buffer.width + x];
// Skip over spaces
while (dot.value == ' ' or dot.value == -1) {
y += 1;
if (y > self.terminal_buffer.height) break;
dot = self.dots[y * self.terminal_buffer.width + x];
}
if (y > self.terminal_buffer.height) break;
// Find the head of this column
tail = y;
seg_length = 0;
while (y <= self.terminal_buffer.height and dot.value != ' ' and dot.value != -1) {
dot.is_head = false;
if (MID_SCROLL_CHANGE) {
const random = self.terminal_buffer.random.int(i16);
if (@mod(random, 8) == 0) dot.value = @mod(random, MAX_CODEPOINT) + MIN_CODEPOINT;
}
self.dots[y * self.terminal_buffer.width + x] = dot;
y += 1;
seg_length += 1;
dot = self.dots[y * self.terminal_buffer.width + x];
}
// The head is down offscreen
if (y > self.terminal_buffer.height) {
self.dots[tail * self.terminal_buffer.width + x].value = ' ';
continue; // TODO: Shouldn't this be break?
}
const random = self.terminal_buffer.random.int(i16);
self.dots[y * self.terminal_buffer.width + x].value = @mod(random, MAX_CODEPOINT) + MIN_CODEPOINT;
self.dots[y * self.terminal_buffer.width + x].is_head = true;
if (seg_length > line.length or !first_column) {
self.dots[tail * self.terminal_buffer.width + x].value = ' ';
self.dots[x].value = -1;
}
first_column = false;
}
}
}
}
var x: u64 = 0;
while (x < self.terminal_buffer.width) : (x += 2) {
var y: u64 = 1;
while (y <= self.terminal_buffer.height) : (y += 1) {
const dot = self.dots[y * self.terminal_buffer.width + x];
var fg: u32 = @intCast(termbox.TB_GREEN);
if (dot.value == -1 or dot.value == ' ') {
termbox.tb_change_cell(@intCast(x), @intCast(y - 1), ' ', fg, termbox.TB_DEFAULT);
continue;
}
if (dot.is_head) fg = @intCast(termbox.TB_WHITE | termbox.TB_BOLD);
termbox.tb_change_cell(@intCast(x), @intCast(y - 1), @intCast(dot.value), fg, termbox.TB_DEFAULT);
}
}
}
fn initBuffers(dots: []Dot, lines: []Line, width: u64, height: u64, random: Random) void {
var y: u64 = 0;
while (y <= height) : (y += 1) {
var x: u64 = 0;
while (x < width) : (x += 2) {
dots[y * width + x].value = -1;
}
}
var x: u64 = 0;
while (x < width) : (x += 2) {
var line = lines[x];
const h: isize = @intCast(height);
line.space = @mod(random.int(i16), h) + 1;
line.length = @mod(random.int(i16), h - 3) + 3;
line.update = @mod(random.int(i16), 3) + 1;
lines[x] = line;
dots[width + x].value = ' ';
}
}

@ -0,0 +1,65 @@
const std = @import("std");
const enums = @import("enums.zig");
const interop = @import("interop.zig");
const TerminalBuffer = @import("tui/TerminalBuffer.zig");
const Desktop = @import("tui/components/Desktop.zig");
const Text = @import("tui/components/Text.zig");
const Allocator = std.mem.Allocator;
// TODO
pub fn authenticate(allocator: Allocator, tty: u8, buffer: TerminalBuffer, desktop: Desktop, login: Text, password: Text) !void {
_ = buffer;
const uid = interop.getuid();
var tty_buffer = std.mem.zeroes([@sizeOf(u8) + 1]u8);
var uid_buffer = std.mem.zeroes([10 + @sizeOf(u32) + 1]u8);
const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{tty});
const uid_str = try std.fmt.bufPrintZ(&uid_buffer, "/run/user/{d}", .{uid});
const current_environment = desktop.environments.items[desktop.current];
// Add XDG environment variables
setXdgSessionEnv(current_environment.display_server);
try setXdgEnv(allocator, tty_str, uid_str, current_environment.xdg_name);
// Open the PAM session
var credentials = [_][]const u8{ login.text.items, password.text.items };
const conv = interop.pam.pam_conv{
.conv = loginConv,
.appdata_ptr = @ptrCast(&credentials),
};
_ = conv;
}
fn setXdgSessionEnv(display_server: enums.DisplayServer) void {
_ = interop.setenv("XDG_SESSION_TYPE", switch (display_server) {
.wayland => "wayland",
.shell => "tty",
.xinitrc, .x11 => "x11",
}, 0);
}
fn setXdgEnv(allocator: Allocator, tty_str: [:0]u8, uid_str: [:0]u8, desktop_name: []const u8) !void {
const desktop_name_z = try allocator.dupeZ(u8, desktop_name);
defer allocator.free(desktop_name_z);
_ = interop.setenv("XDG_RUNTIME_DIR", uid_str, 0);
_ = interop.setenv("XDG_SESSION_CLASS", "user", 0);
_ = interop.setenv("XDG_SESSION_ID", "1", 0);
_ = interop.setenv("XDG_SESSION_DESKTOP", desktop_name_z, 0);
_ = interop.setenv("XDG_SEAT", "seat0", 0);
_ = interop.setenv("XDG_VTNR", tty_str, 0);
}
fn loginConv(
num_msg: c_int,
msg: [*][*]const interop.pam.pam_message,
resp: [*][*]const interop.pam.pam_response,
appdata_ptr: ?*anyopaque,
) c_int {
_ = num_msg;
_ = msg;
_ = resp;
_ = appdata_ptr;
}

@ -1,146 +0,0 @@
#include <stdint.h>
#define CLOCK_W 5
#define CLOCK_H 5
#if defined(__linux__) || defined(__FreeBSD__)
#define X 0x2593
#define _ 0x0000
#else
#define X '#'
#define _ 0
#endif
#if CLOCK_W == 5 && CLOCK_H == 5
uint32_t CLOCK_0[] = {
X,X,X,X,X,
X,X,_,X,X,
X,X,_,X,X,
X,X,_,X,X,
X,X,X,X,X
};
uint32_t CLOCK_1[] = {
_,_,_,X,X,
_,_,_,X,X,
_,_,_,X,X,
_,_,_,X,X,
_,_,_,X,X
};
uint32_t CLOCK_2[] = {
X,X,X,X,X,
_,_,_,X,X,
X,X,X,X,X,
X,X,_,_,_,
X,X,X,X,X
};
uint32_t CLOCK_3[] = {
X,X,X,X,X,
_,_,_,X,X,
X,X,X,X,X,
_,_,_,X,X,
X,X,X,X,X
};
uint32_t CLOCK_4[] = {
X,X,_,X,X,
X,X,_,X,X,
X,X,X,X,X,
_,_,_,X,X,
_,_,_,X,X
};
uint32_t CLOCK_5[] = {
X,X,X,X,X,
X,X,_,_,_,
X,X,X,X,X,
_,_,_,X,X,
X,X,X,X,X
};
uint32_t CLOCK_6[] = {
X,X,X,X,X,
X,X,_,_,_,
X,X,X,X,X,
X,X,_,X,X,
X,X,X,X,X,
};
uint32_t CLOCK_7[] = {
X,X,X,X,X,
_,_,_,X,X,
_,_,_,X,X,
_,_,_,X,X,
_,_,_,X,X
};
uint32_t CLOCK_8[] = {
X,X,X,X,X,
X,X,_,X,X,
X,X,X,X,X,
X,X,_,X,X,
X,X,X,X,X
};
uint32_t CLOCK_9[] = {
X,X,X,X,X,
X,X,_,X,X,
X,X,X,X,X,
_,_,_,X,X,
X,X,X,X,X
};
uint32_t CLOCK_S[] = {
_,_,_,_,_,
_,_,X,_,_,
_,_,_,_,_,
_,_,X,_,_,
_,_,_,_,_
};
uint32_t CLOCK_E[] = {
_,_,_,_,_,
_,_,_,_,_,
_,_,_,_,_,
_,_,_,_,_,
_,_,_,_,_
};
#endif
#undef X
#undef _
static inline uint32_t* CLOCK_N(char c)
{
switch(c)
{
case '0':
return CLOCK_0;
case '1':
return CLOCK_1;
case '2':
return CLOCK_2;
case '3':
return CLOCK_3;
case '4':
return CLOCK_4;
case '5':
return CLOCK_5;
case '6':
return CLOCK_6;
case '7':
return CLOCK_7;
case '8':
return CLOCK_8;
case '9':
return CLOCK_9;
case ':':
return CLOCK_S;
default:
return CLOCK_E;
}
}

@ -0,0 +1,140 @@
const std = @import("std");
const builtin = @import("builtin");
const interop = @import("interop.zig");
const utils = @import("tui/utils.zig");
const termbox = interop.termbox;
const X: u32 = if (builtin.os.tag == .linux or builtin.os.tag.isBSD()) 0x2593 else '#';
const O: u32 = 0;
pub const WIDTH: u64 = 5;
pub const HEIGHT: u64 = 5;
pub const SIZE = WIDTH * HEIGHT;
// zig fmt: off
const ZERO = [_]u32{
X,X,X,X,X,
X,X,O,X,X,
X,X,O,X,X,
X,X,O,X,X,
X,X,X,X,X,
};
const ONE = [_]u32{
O,O,O,X,X,
O,O,O,X,X,
O,O,O,X,X,
O,O,O,X,X,
O,O,O,X,X,
};
const TWO = [_]u32{
X,X,X,X,X,
O,O,O,X,X,
X,X,X,X,X,
X,X,O,O,O,
X,X,X,X,X,
};
const THREE = [_]u32{
X,X,X,X,X,
O,O,O,X,X,
X,X,X,X,X,
O,O,O,X,X,
X,X,X,X,X,
};
const FOUR = [_]u32{
X,X,O,X,X,
X,X,O,X,X,
X,X,X,X,X,
O,O,O,X,X,
O,O,O,X,X,
};
const FIVE = [_]u32{
X,X,X,X,X,
X,X,O,O,O,
X,X,X,X,X,
O,O,O,X,X,
X,X,X,X,X,
};
const SIX = [_]u32{
X,X,X,X,X,
X,X,O,O,O,
X,X,X,X,X,
X,X,O,X,X,
X,X,X,X,X,
};
const SEVEN = [_]u32{
X,X,X,X,X,
O,O,O,X,X,
O,O,O,X,X,
O,O,O,X,X,
O,O,O,X,X,
};
const EIGHT = [_]u32{
X,X,X,X,X,
X,X,O,X,X,
X,X,X,X,X,
X,X,O,X,X,
X,X,X,X,X,
};
const NINE = [_]u32{
X,X,X,X,X,
X,X,O,X,X,
X,X,X,X,X,
O,O,O,X,X,
X,X,X,X,X,
};
const S = [_]u32{
O,O,O,O,O,
O,O,X,O,O,
O,O,O,O,O,
O,O,X,O,O,
O,O,O,O,O,
};
const E = [_]u32{
O,O,O,O,O,
O,O,O,O,O,
O,O,O,O,O,
O,O,O,O,O,
O,O,O,O,O,
};
// zig fmt: on
pub fn clockCell(animate: bool, char: u8, fg: u8, bg: u8) [SIZE]termbox.tb_cell {
var cells = std.mem.zeroes([SIZE]termbox.tb_cell);
var tv = std.mem.zeroes(std.c.timeval);
_ = std.c.gettimeofday(&tv, null);
const clock_chars = toBigNumber(if (animate and char == ':' and @divTrunc(tv.tv_usec, 500000) != 0) ' ' else char);
for (0..cells.len) |i| cells[i] = utils.initCell(clock_chars[i], fg, bg);
return cells;
}
pub fn alphaBlit(buffer: [*c]termbox.tb_cell, x: u64, y: u64, tb_width: u64, tb_height: u64, cells: [SIZE]termbox.tb_cell) void {
if (x + WIDTH >= tb_width or y + HEIGHT >= tb_height) return;
for (0..HEIGHT) |yy| {
for (0..WIDTH) |xx| {
const cell = cells[yy * WIDTH + xx];
if (cell.ch != 0) buffer[(y + yy) * tb_width + (x + xx)] = cell;
}
}
}
fn toBigNumber(char: u8) []const u32 {
return switch (char) {
'0' => &ZERO,
'1' => &ONE,
'2' => &TWO,
'3' => &THREE,
'4' => &FOUR,
'5' => &FIVE,
'6' => &SIX,
'7' => &SEVEN,
'8' => &EIGHT,
'9' => &NINE,
':' => &S,
else => &E,
};
}

@ -1,102 +0,0 @@
#ifndef H_LY_CONFIG
#define H_LY_CONFIG
#include <stdbool.h>
#include <stdint.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;
uint8_t animation;
char asterisk;
uint8_t bg;
bool bigclock;
bool blank_box;
bool blank_password;
char* clock;
char* console_dev;
uint8_t default_input;
uint8_t fg;
bool hide_borders;
bool hide_f1_commands;
uint8_t input_len;
char* lang;
bool load;
uint8_t margin_box_h;
uint8_t margin_box_v;
uint8_t max_desktop_len;
uint8_t max_login_len;
uint8_t max_password_len;
char* mcookie_cmd;
uint16_t min_refresh_delta;
char* path;
char* restart_cmd;
bool save;
char* save_file;
char* service_name;
char* shutdown_cmd;
char* term_reset_cmd;
uint8_t tty;
char* wayland_cmd;
bool wayland_specifier;
char* waylandsessions;
char* x_cmd;
char* xinitrc;
char* x_cmd_setup;
char* xauth_cmd;
char* xsessions;
};
extern struct lang lang;
extern struct config config;
#endif

@ -1,424 +0,0 @@
const std = @import("std");
const ini = @import("ini");
const main = @import("main.zig");
const interop = @import("interop.zig");
const INI_CONFIG_PATH: []const u8 = "/etc/ly/";
const INI_CONFIG_MAX_SIZE: usize = 16 * 1024;
pub const Inputs = enum {
session,
login,
password,
};
pub const LyConfig = struct {
ly: struct {
animate: bool,
animation: u8,
asterisk: u8,
bg: u8,
bigclock: bool,
blank_box: bool,
blank_password: bool,
clock: []const u8,
console_dev: []const u8,
default_input: Inputs,
fg: u8,
hide_borders: bool,
hide_f1_commands: bool,
input_len: u8,
lang: []const u8,
load: bool,
margin_box_h: u8,
margin_box_v: u8,
max_desktop_len: u8,
max_login_len: u8,
max_password_len: u8,
mcookie_cmd: []const u8,
min_refresh_delta: u16,
path: []const u8,
restart_cmd: []const u8,
save: bool,
save_file: []const u8,
service_name: []const u8,
shutdown_cmd: []const u8,
term_reset_cmd: []const u8,
tty: u8,
wayland_cmd: []const u8,
wayland_specifier: bool,
waylandsessions: []const u8,
x_cmd: []const u8,
xinitrc: []const u8,
x_cmd_setup: []const u8,
xauth_cmd: []const u8,
xsessions: []const u8,
},
};
pub const LyLang = struct {
ly: struct {
capslock: []const u8,
err_alloc: []const u8,
err_bounds: []const u8,
err_chdir: []const u8,
err_console_dev: []const u8,
err_dgn_oob: []const u8,
err_domain: []const u8,
err_hostname: []const u8,
err_mlock: []const u8,
err_null: []const u8,
err_pam: []const u8,
err_pam_abort: []const u8,
err_pam_acct_expired: []const u8,
err_pam_auth: []const u8,
err_pam_authinfo_unavail: []const u8,
err_pam_authok_reqd: []const u8,
err_pam_buf: []const u8,
err_pam_cred_err: []const u8,
err_pam_cred_expired: []const u8,
err_pam_cred_insufficient: []const u8,
err_pam_cred_unavail: []const u8,
err_pam_maxtries: []const u8,
err_pam_perm_denied: []const u8,
err_pam_session: []const u8,
err_pam_sys: []const u8,
err_pam_user_unknown: []const u8,
err_path: []const u8,
err_perm_dir: []const u8,
err_perm_group: []const u8,
err_perm_user: []const u8,
err_pwnam: []const u8,
err_user_gid: []const u8,
err_user_init: []const u8,
err_user_uid: []const u8,
err_xsessions_dir: []const u8,
err_xsessions_open: []const u8,
f1: []const u8,
f2: []const u8,
login: []const u8,
logout: []const u8,
numlock: []const u8,
password: []const u8,
shell: []const u8,
wayland: []const u8,
xinitrc: []const u8,
},
};
pub var ly_config: LyConfig = undefined;
pub var ly_lang: LyLang = undefined;
var config_buffer: []u8 = undefined;
var lang_buffer: []u8 = undefined;
var config_clock: [:0]u8 = undefined;
var config_console_dev: [:0]u8 = undefined;
var config_lang: [:0]u8 = undefined;
var config_mcookie_cmd: [:0]u8 = undefined;
var config_path: [:0]u8 = undefined;
var config_restart_cmd: [:0]u8 = undefined;
var config_save_file: [:0]u8 = undefined;
var config_service_name: [:0]u8 = undefined;
var config_shutdown_cmd: [:0]u8 = undefined;
var config_term_reset_cmd: [:0]u8 = undefined;
var config_wayland_cmd: [:0]u8 = undefined;
var config_waylandsessions: [:0]u8 = undefined;
var config_x_cmd: [:0]u8 = undefined;
var config_xinitrc: [:0]u8 = undefined;
var config_x_cmd_setup: [:0]u8 = undefined;
var config_xauth_cmd: [:0]u8 = undefined;
var config_xsessions: [:0]u8 = undefined;
var lang_capslock: [:0]u8 = undefined;
var lang_err_alloc: [:0]u8 = undefined;
var lang_err_bounds: [:0]u8 = undefined;
var lang_err_chdir: [:0]u8 = undefined;
var lang_err_console_dev: [:0]u8 = undefined;
var lang_err_dgn_oob: [:0]u8 = undefined;
var lang_err_domain: [:0]u8 = undefined;
var lang_err_hostname: [:0]u8 = undefined;
var lang_err_mlock: [:0]u8 = undefined;
var lang_err_null: [:0]u8 = undefined;
var lang_err_pam: [:0]u8 = undefined;
var lang_err_pam_abort: [:0]u8 = undefined;
var lang_err_pam_acct_expired: [:0]u8 = undefined;
var lang_err_pam_auth: [:0]u8 = undefined;
var lang_err_pam_authinfo_unavail: [:0]u8 = undefined;
var lang_err_pam_authok_reqd: [:0]u8 = undefined;
var lang_err_pam_buf: [:0]u8 = undefined;
var lang_err_pam_cred_err: [:0]u8 = undefined;
var lang_err_pam_cred_expired: [:0]u8 = undefined;
var lang_err_pam_cred_insufficient: [:0]u8 = undefined;
var lang_err_pam_cred_unavail: [:0]u8 = undefined;
var lang_err_pam_maxtries: [:0]u8 = undefined;
var lang_err_pam_perm_denied: [:0]u8 = undefined;
var lang_err_pam_session: [:0]u8 = undefined;
var lang_err_pam_sys: [:0]u8 = undefined;
var lang_err_pam_user_unknown: [:0]u8 = undefined;
var lang_err_path: [:0]u8 = undefined;
var lang_err_perm_dir: [:0]u8 = undefined;
var lang_err_perm_group: [:0]u8 = undefined;
var lang_err_perm_user: [:0]u8 = undefined;
var lang_err_pwnam: [:0]u8 = undefined;
var lang_err_user_gid: [:0]u8 = undefined;
var lang_err_user_init: [:0]u8 = undefined;
var lang_err_user_uid: [:0]u8 = undefined;
var lang_err_xsessions_dir: [:0]u8 = undefined;
var lang_err_xsessions_open: [:0]u8 = undefined;
var lang_f1: [:0]u8 = undefined;
var lang_f2: [:0]u8 = undefined;
var lang_login: [:0]u8 = undefined;
var lang_logout: [:0]u8 = undefined;
var lang_numlock: [:0]u8 = undefined;
var lang_password: [:0]u8 = undefined;
var lang_shell: [:0]u8 = undefined;
var lang_wayland: [:0]u8 = undefined;
var lang_xinitrc: [:0]u8 = undefined;
pub fn config_load(cfg_path: []const u8) !void {
var file = try std.fs.cwd().openFile(if (cfg_path.len == 0) INI_CONFIG_PATH ++ "config.ini" else cfg_path, .{});
defer file.close();
config_buffer = try main.allocator.alloc(u8, INI_CONFIG_MAX_SIZE);
var length = try file.readAll(config_buffer);
ly_config = try ini.readToStruct(LyConfig, config_buffer[0..length]);
config_clock = try interop.c_str(ly_config.ly.clock);
config_console_dev = try interop.c_str(ly_config.ly.console_dev);
config_lang = try interop.c_str(ly_config.ly.lang);
config_mcookie_cmd = try interop.c_str(ly_config.ly.mcookie_cmd);
config_path = try interop.c_str(ly_config.ly.path);
config_restart_cmd = try interop.c_str(ly_config.ly.restart_cmd);
config_save_file = try interop.c_str(ly_config.ly.save_file);
config_service_name = try interop.c_str(ly_config.ly.service_name);
config_shutdown_cmd = try interop.c_str(ly_config.ly.shutdown_cmd);
config_term_reset_cmd = try interop.c_str(ly_config.ly.term_reset_cmd);
config_wayland_cmd = try interop.c_str(ly_config.ly.wayland_cmd);
config_waylandsessions = try interop.c_str(ly_config.ly.waylandsessions);
config_x_cmd = try interop.c_str(ly_config.ly.x_cmd);
config_xinitrc = try interop.c_str(ly_config.ly.xinitrc);
config_x_cmd_setup = try interop.c_str(ly_config.ly.x_cmd_setup);
config_xauth_cmd = try interop.c_str(ly_config.ly.xauth_cmd);
config_xsessions = try interop.c_str(ly_config.ly.xsessions);
main.c_config.animate = ly_config.ly.animate;
main.c_config.animation = ly_config.ly.animation;
main.c_config.asterisk = ly_config.ly.asterisk;
main.c_config.bg = ly_config.ly.bg;
main.c_config.bigclock = ly_config.ly.bigclock;
main.c_config.blank_box = ly_config.ly.blank_box;
main.c_config.blank_password = ly_config.ly.blank_password;
main.c_config.clock = config_clock.ptr;
main.c_config.console_dev = config_console_dev.ptr;
main.c_config.default_input = @intFromEnum(ly_config.ly.default_input);
main.c_config.fg = ly_config.ly.fg;
main.c_config.hide_borders = ly_config.ly.hide_borders;
main.c_config.hide_f1_commands = ly_config.ly.hide_f1_commands;
main.c_config.input_len = ly_config.ly.input_len;
main.c_config.lang = config_lang.ptr;
main.c_config.load = ly_config.ly.load;
main.c_config.margin_box_h = ly_config.ly.margin_box_h;
main.c_config.margin_box_v = ly_config.ly.margin_box_v;
main.c_config.max_desktop_len = ly_config.ly.max_desktop_len;
main.c_config.max_login_len = ly_config.ly.max_login_len;
main.c_config.max_password_len = ly_config.ly.max_password_len;
main.c_config.mcookie_cmd = config_mcookie_cmd.ptr;
main.c_config.min_refresh_delta = ly_config.ly.min_refresh_delta;
main.c_config.path = config_path.ptr;
main.c_config.restart_cmd = config_restart_cmd.ptr;
main.c_config.save = ly_config.ly.save;
main.c_config.save_file = config_save_file.ptr;
main.c_config.service_name = config_service_name.ptr;
main.c_config.shutdown_cmd = config_shutdown_cmd.ptr;
main.c_config.term_reset_cmd = config_term_reset_cmd.ptr;
main.c_config.tty = ly_config.ly.tty;
main.c_config.wayland_cmd = config_wayland_cmd.ptr;
main.c_config.wayland_specifier = ly_config.ly.wayland_specifier;
main.c_config.waylandsessions = config_waylandsessions.ptr;
main.c_config.x_cmd = config_x_cmd.ptr;
main.c_config.xinitrc = config_xinitrc.ptr;
main.c_config.x_cmd_setup = config_x_cmd_setup.ptr;
main.c_config.xauth_cmd = config_xauth_cmd.ptr;
main.c_config.xsessions = config_xsessions.ptr;
}
pub fn lang_load() !void {
var path = try std.fmt.allocPrint(main.allocator, "{s}{s}.ini", .{ INI_CONFIG_PATH ++ "lang/", ly_config.ly.lang });
defer main.allocator.free(path);
var file = try std.fs.cwd().openFile(path, .{});
defer file.close();
lang_buffer = try main.allocator.alloc(u8, INI_CONFIG_MAX_SIZE);
var length = try file.readAll(lang_buffer);
ly_lang = try ini.readToStruct(LyLang, lang_buffer[0..length]);
lang_capslock = try interop.c_str(ly_lang.ly.capslock);
lang_err_alloc = try interop.c_str(ly_lang.ly.err_alloc);
lang_err_bounds = try interop.c_str(ly_lang.ly.err_bounds);
lang_err_chdir = try interop.c_str(ly_lang.ly.err_chdir);
lang_err_console_dev = try interop.c_str(ly_lang.ly.err_console_dev);
lang_err_dgn_oob = try interop.c_str(ly_lang.ly.err_dgn_oob);
lang_err_domain = try interop.c_str(ly_lang.ly.err_domain);
lang_err_hostname = try interop.c_str(ly_lang.ly.err_hostname);
lang_err_mlock = try interop.c_str(ly_lang.ly.err_mlock);
lang_err_null = try interop.c_str(ly_lang.ly.err_null);
lang_err_pam = try interop.c_str(ly_lang.ly.err_pam);
lang_err_pam_abort = try interop.c_str(ly_lang.ly.err_pam_abort);
lang_err_pam_acct_expired = try interop.c_str(ly_lang.ly.err_pam_acct_expired);
lang_err_pam_auth = try interop.c_str(ly_lang.ly.err_pam_auth);
lang_err_pam_authinfo_unavail = try interop.c_str(ly_lang.ly.err_pam_authinfo_unavail);
lang_err_pam_authok_reqd = try interop.c_str(ly_lang.ly.err_pam_authok_reqd);
lang_err_pam_buf = try interop.c_str(ly_lang.ly.err_pam_buf);
lang_err_pam_cred_err = try interop.c_str(ly_lang.ly.err_pam_cred_err);
lang_err_pam_cred_expired = try interop.c_str(ly_lang.ly.err_pam_cred_expired);
lang_err_pam_cred_insufficient = try interop.c_str(ly_lang.ly.err_pam_cred_insufficient);
lang_err_pam_cred_unavail = try interop.c_str(ly_lang.ly.err_pam_cred_unavail);
lang_err_pam_maxtries = try interop.c_str(ly_lang.ly.err_pam_maxtries);
lang_err_pam_perm_denied = try interop.c_str(ly_lang.ly.err_pam_perm_denied);
lang_err_pam_session = try interop.c_str(ly_lang.ly.err_pam_session);
lang_err_pam_sys = try interop.c_str(ly_lang.ly.err_pam_sys);
lang_err_pam_user_unknown = try interop.c_str(ly_lang.ly.err_pam_user_unknown);
lang_err_path = try interop.c_str(ly_lang.ly.err_path);
lang_err_perm_dir = try interop.c_str(ly_lang.ly.err_perm_dir);
lang_err_perm_group = try interop.c_str(ly_lang.ly.err_perm_group);
lang_err_perm_user = try interop.c_str(ly_lang.ly.err_perm_user);
lang_err_pwnam = try interop.c_str(ly_lang.ly.err_pwnam);
lang_err_user_gid = try interop.c_str(ly_lang.ly.err_user_gid);
lang_err_user_init = try interop.c_str(ly_lang.ly.err_user_init);
lang_err_user_uid = try interop.c_str(ly_lang.ly.err_user_uid);
lang_err_xsessions_dir = try interop.c_str(ly_lang.ly.err_xsessions_dir);
lang_err_xsessions_open = try interop.c_str(ly_lang.ly.err_xsessions_open);
lang_f1 = try interop.c_str(ly_lang.ly.f1);
lang_f2 = try interop.c_str(ly_lang.ly.f2);
lang_login = try interop.c_str(ly_lang.ly.login);
lang_logout = try interop.c_str(ly_lang.ly.logout);
lang_numlock = try interop.c_str(ly_lang.ly.numlock);
lang_password = try interop.c_str(ly_lang.ly.password);
lang_shell = try interop.c_str(ly_lang.ly.shell);
lang_wayland = try interop.c_str(ly_lang.ly.wayland);
lang_xinitrc = try interop.c_str(ly_lang.ly.xinitrc);
main.c_lang.capslock = lang_capslock.ptr;
main.c_lang.err_alloc = lang_err_alloc.ptr;
main.c_lang.err_bounds = lang_err_bounds.ptr;
main.c_lang.err_chdir = lang_err_chdir.ptr;
main.c_lang.err_console_dev = lang_err_console_dev.ptr;
main.c_lang.err_dgn_oob = lang_err_dgn_oob.ptr;
main.c_lang.err_domain = lang_err_domain.ptr;
main.c_lang.err_hostname = lang_err_hostname.ptr;
main.c_lang.err_mlock = lang_err_mlock.ptr;
main.c_lang.err_null = lang_err_null.ptr;
main.c_lang.err_pam = lang_err_pam.ptr;
main.c_lang.err_pam_abort = lang_err_pam_abort.ptr;
main.c_lang.err_pam_acct_expired = lang_err_pam_acct_expired.ptr;
main.c_lang.err_pam_auth = lang_err_pam_auth.ptr;
main.c_lang.err_pam_authinfo_unavail = lang_err_pam_authinfo_unavail.ptr;
main.c_lang.err_pam_authok_reqd = lang_err_pam_authok_reqd.ptr;
main.c_lang.err_pam_buf = lang_err_pam_buf.ptr;
main.c_lang.err_pam_cred_err = lang_err_pam_cred_err.ptr;
main.c_lang.err_pam_cred_expired = lang_err_pam_cred_expired.ptr;
main.c_lang.err_pam_cred_insufficient = lang_err_pam_cred_insufficient.ptr;
main.c_lang.err_pam_cred_unavail = lang_err_pam_cred_unavail.ptr;
main.c_lang.err_pam_maxtries = lang_err_pam_maxtries.ptr;
main.c_lang.err_pam_perm_denied = lang_err_pam_perm_denied.ptr;
main.c_lang.err_pam_session = lang_err_pam_session.ptr;
main.c_lang.err_pam_sys = lang_err_pam_sys.ptr;
main.c_lang.err_pam_user_unknown = lang_err_pam_user_unknown.ptr;
main.c_lang.err_path = lang_err_path.ptr;
main.c_lang.err_perm_dir = lang_err_perm_dir.ptr;
main.c_lang.err_perm_group = lang_err_perm_group.ptr;
main.c_lang.err_perm_user = lang_err_perm_user.ptr;
main.c_lang.err_pwnam = lang_err_pwnam.ptr;
main.c_lang.err_user_gid = lang_err_user_gid.ptr;
main.c_lang.err_user_init = lang_err_user_init.ptr;
main.c_lang.err_user_uid = lang_err_user_uid.ptr;
main.c_lang.err_xsessions_dir = lang_err_xsessions_dir.ptr;
main.c_lang.err_xsessions_open = lang_err_xsessions_open.ptr;
main.c_lang.f1 = lang_f1.ptr;
main.c_lang.f2 = lang_f2.ptr;
main.c_lang.login = lang_login.ptr;
main.c_lang.logout = lang_logout.ptr;
main.c_lang.numlock = lang_numlock.ptr;
main.c_lang.password = lang_password.ptr;
main.c_lang.shell = lang_shell.ptr;
main.c_lang.wayland = lang_wayland.ptr;
main.c_lang.xinitrc = lang_xinitrc.ptr;
}
pub fn config_free() void {
interop.allocator.free(config_clock);
interop.allocator.free(config_console_dev);
interop.allocator.free(config_lang);
interop.allocator.free(config_mcookie_cmd);
interop.allocator.free(config_path);
interop.allocator.free(config_restart_cmd);
interop.allocator.free(config_save_file);
interop.allocator.free(config_service_name);
interop.allocator.free(config_shutdown_cmd);
interop.allocator.free(config_term_reset_cmd);
interop.allocator.free(config_wayland_cmd);
interop.allocator.free(config_waylandsessions);
interop.allocator.free(config_x_cmd);
interop.allocator.free(config_xinitrc);
interop.allocator.free(config_x_cmd_setup);
interop.allocator.free(config_xauth_cmd);
interop.allocator.free(config_xsessions);
main.allocator.free(config_buffer);
}
pub fn lang_free() void {
interop.allocator.free(lang_capslock);
interop.allocator.free(lang_err_alloc);
interop.allocator.free(lang_err_bounds);
interop.allocator.free(lang_err_chdir);
interop.allocator.free(lang_err_console_dev);
interop.allocator.free(lang_err_dgn_oob);
interop.allocator.free(lang_err_domain);
interop.allocator.free(lang_err_hostname);
interop.allocator.free(lang_err_mlock);
interop.allocator.free(lang_err_null);
interop.allocator.free(lang_err_pam);
interop.allocator.free(lang_err_pam_abort);
interop.allocator.free(lang_err_pam_acct_expired);
interop.allocator.free(lang_err_pam_auth);
interop.allocator.free(lang_err_pam_authinfo_unavail);
interop.allocator.free(lang_err_pam_authok_reqd);
interop.allocator.free(lang_err_pam_buf);
interop.allocator.free(lang_err_pam_cred_err);
interop.allocator.free(lang_err_pam_cred_expired);
interop.allocator.free(lang_err_pam_cred_insufficient);
interop.allocator.free(lang_err_pam_cred_unavail);
interop.allocator.free(lang_err_pam_maxtries);
interop.allocator.free(lang_err_pam_perm_denied);
interop.allocator.free(lang_err_pam_session);
interop.allocator.free(lang_err_pam_sys);
interop.allocator.free(lang_err_pam_user_unknown);
interop.allocator.free(lang_err_path);
interop.allocator.free(lang_err_perm_dir);
interop.allocator.free(lang_err_perm_group);
interop.allocator.free(lang_err_perm_user);
interop.allocator.free(lang_err_pwnam);
interop.allocator.free(lang_err_user_gid);
interop.allocator.free(lang_err_user_init);
interop.allocator.free(lang_err_user_uid);
interop.allocator.free(lang_err_xsessions_dir);
interop.allocator.free(lang_err_xsessions_open);
interop.allocator.free(lang_f1);
interop.allocator.free(lang_f2);
interop.allocator.free(lang_login);
interop.allocator.free(lang_logout);
interop.allocator.free(lang_numlock);
interop.allocator.free(lang_password);
interop.allocator.free(lang_shell);
interop.allocator.free(lang_wayland);
interop.allocator.free(lang_xinitrc);
main.allocator.free(lang_buffer);
}

@ -0,0 +1,97 @@
const build_options = @import("build_options");
const enums = @import("../enums.zig");
const Animation = enums.Animation;
const Input = enums.Input;
const Config = @This();
ly: struct {
animation: Animation,
asterisk: u8,
bg: u8,
bigclock: bool,
blank_box: bool,
blank_password: bool,
clock: []const u8,
console_dev: []const u8,
default_input: Input,
fg: u8,
hide_borders: bool,
hide_key_hints: bool,
input_len: u8,
lang: []const u8,
load: bool,
margin_box_h: u8,
margin_box_v: u8,
max_desktop_len: u8,
max_login_len: u8,
max_password_len: u8,
mcookie_cmd: []const u8,
min_refresh_delta: u16,
path: []const u8,
restart_cmd: []const u8,
restart_key: []const u8,
save: bool,
save_file: []const u8,
service_name: []const u8,
shutdown_cmd: []const u8,
shutdown_key: []const u8,
term_reset_cmd: []const u8,
term_restore_cursor_cmd: []const u8,
tty: u8,
wayland_cmd: []const u8,
wayland_specifier: bool,
waylandsessions: []const u8,
x_cmd: []const u8,
xinitrc: []const u8,
x_cmd_setup: []const u8,
xauth_cmd: []const u8,
xsessions: []const u8,
},
pub fn init() Config {
return .{ .ly = .{
.animation = .none,
.asterisk = '*',
.bg = 0,
.bigclock = false,
.blank_box = true,
.blank_password = false,
.clock = "",
.console_dev = "/dev/console",
.default_input = .login,
.fg = 9,
.hide_borders = false,
.hide_key_hints = false,
.input_len = 34,
.lang = "en",
.load = true,
.margin_box_h = 2,
.margin_box_v = 1,
.max_desktop_len = 100,
.max_login_len = 255,
.max_password_len = 255,
.mcookie_cmd = "/usr/bin/mcookie",
.min_refresh_delta = 5,
.path = "/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin",
.restart_cmd = "/sbin/shutdown -r now",
.restart_key = "F2",
.save = true,
.save_file = "/etc/ly/save",
.service_name = "ly",
.shutdown_cmd = "/sbin/shutdown -a now",
.shutdown_key = "F1",
.term_reset_cmd = "/usr/bin/tput reset",
.term_restore_cursor_cmd = "/usr/bin/tput cnorm",
.tty = 2,
.wayland_cmd = build_options.data_directory ++ "/wsetup.sh",
.wayland_specifier = false,
.waylandsessions = "/usr/share/wayland-sessions",
.x_cmd = "/usr/bin/X",
.xinitrc = "~/.xinitrc",
.x_cmd_setup = build_options.data_directory ++ "/xsetup.sh",
.xauth_cmd = "/usr/bin/xauth",
.xsessions = "/usr/share/xsessions",
} };
}

@ -0,0 +1,47 @@
const std = @import("std");
const ini = @import("ini");
const Config = @import("Config.zig");
const Lang = @import("Lang.zig");
const Allocator = std.mem.Allocator;
pub const CONFIG_MAX_SIZE: u64 = 8 * 1024;
const ConfigReader = @This();
allocator: Allocator,
config_allocated: bool = false,
lang_allocated: bool = false,
config: []u8 = undefined,
lang: []u8 = undefined,
pub fn init(config_allocator: Allocator) ConfigReader {
return .{
.allocator = config_allocator,
};
}
pub fn deinit(self: ConfigReader) void {
if (self.config_allocated) self.allocator.free(self.config);
if (self.lang_allocated) self.allocator.free(self.lang);
}
pub fn readConfig(self: *ConfigReader, path: []const u8) !Config {
var file = std.fs.cwd().openFile(path, .{}) catch return Config.init();
defer file.close();
self.config = try file.readToEndAlloc(self.allocator, CONFIG_MAX_SIZE);
self.config_allocated = true;
return try ini.readToStruct(Config, self.config);
}
pub fn readLang(self: *ConfigReader, path: []const u8) !Lang {
var file = std.fs.cwd().openFile(path, .{}) catch return Lang.init();
defer file.close();
self.lang = try file.readToEndAlloc(self.allocator, CONFIG_MAX_SIZE);
self.lang_allocated = true;
return try ini.readToStruct(Lang, self.lang);
}

@ -0,0 +1,99 @@
const Lang = @This();
ly: struct {
capslock: []const u8,
err_alloc: []const u8,
err_bounds: []const u8,
err_chdir: []const u8,
err_console_dev: []const u8,
err_dgn_oob: []const u8,
err_domain: []const u8,
err_hostname: []const u8,
err_mlock: []const u8,
err_null: []const u8,
err_pam: []const u8,
err_pam_abort: []const u8,
err_pam_acct_expired: []const u8,
err_pam_auth: []const u8,
err_pam_authinfo_unavail: []const u8,
err_pam_authok_reqd: []const u8,
err_pam_buf: []const u8,
err_pam_cred_err: []const u8,
err_pam_cred_expired: []const u8,
err_pam_cred_insufficient: []const u8,
err_pam_cred_unavail: []const u8,
err_pam_maxtries: []const u8,
err_pam_perm_denied: []const u8,
err_pam_session: []const u8,
err_pam_sys: []const u8,
err_pam_user_unknown: []const u8,
err_path: []const u8,
err_perm_dir: []const u8,
err_perm_group: []const u8,
err_perm_user: []const u8,
err_pwnam: []const u8,
err_user_gid: []const u8,
err_user_init: []const u8,
err_user_uid: []const u8,
err_xsessions_dir: []const u8,
err_xsessions_open: []const u8,
login: []const u8,
logout: []const u8,
numlock: []const u8,
password: []const u8,
restart: []const u8,
shell: []const u8,
shutdown: []const u8,
wayland: []const u8,
xinitrc: []const u8,
},
pub fn init() Lang {
return .{ .ly = .{
.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",
.login = "login:",
.logout = "logged out",
.numlock = "numlock",
.password = "password:",
.restart = "reboot",
.shell = "shell",
.shutdown = "shutdown",
.wayland = "wayland",
.xinitrc = "xinitrc",
} };
}

@ -1,27 +0,0 @@
#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

@ -1,997 +0,0 @@
#include "dragonfail.h"
#include "termbox.h"
#include "inputs.h"
#include "utils.h"
#include "config.h"
#include "draw.h"
#include "bigclock.h"
#include <ctype.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <time.h>
#if defined(__DragonFly__) || defined(__FreeBSD__)
#include <sys/kbio.h>
#else // linux
#include <linux/kd.h>
#endif
#define DOOM_STEPS 13
void draw_init(struct term_buf* buf)
{
buf->width = tb_width();
buf->height = tb_height();
hostname(&buf->info_line);
uint16_t len_login = strlen(lang.login);
uint16_t 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__) || defined(__FreeBSD__)
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
}
static void doom_free(struct term_buf* buf);
static void matrix_free(struct term_buf* buf);
void draw_free(struct term_buf* buf)
{
if (config.animate)
{
switch (config.animation)
{
case 0:
doom_free(buf);
break;
case 1:
matrix_free(buf);
break;
}
}
}
void draw_box(struct term_buf* buf)
{
uint16_t box_x = (buf->width - buf->box_width) / 2;
uint16_t box_y = (buf->height - buf->box_height) / 2;
uint16_t box_x2 = (buf->width + buf->box_width) / 2;
uint16_t 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 (uint16_t 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 (uint16_t 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 (uint16_t i = 0; i < buf->box_height; ++i)
{
for (uint16_t k = 0; k < buf->box_width; ++k)
{
tb_put_cell(
box_x + k,
box_y + i,
&blank);
}
}
}
}
char* time_str(char* fmt, int maxlen)
{
time_t timer;
char* buffer = malloc(maxlen);
struct tm* tm_info;
timer = time(NULL);
tm_info = localtime(&timer);
if (strftime(buffer, maxlen, fmt, tm_info) == 0)
{
buffer[0] = '\0';
}
return buffer;
}
extern inline uint32_t* CLOCK_N(char c);
struct tb_cell* clock_cell(char c)
{
struct tb_cell* cells = malloc(sizeof(struct tb_cell) * CLOCK_W * CLOCK_H);
struct timeval tv;
gettimeofday(&tv, NULL);
if (config.animate && c == ':' && tv.tv_usec / 500000)
{
c = ' ';
}
uint32_t* clockchars = CLOCK_N(c);
for (int i = 0; i < CLOCK_W * CLOCK_H; i++)
{
cells[i].ch = clockchars[i];
cells[i].fg = config.fg;
cells[i].bg = config.bg;
}
return cells;
}
void alpha_blit(struct tb_cell* buf, uint16_t x, uint16_t y, uint16_t w, uint16_t h, struct tb_cell* cells)
{
if (x + w >= tb_width() || y + h >= tb_height())
return;
for (int i = 0; i < h; i++)
{
for (int j = 0; j < w; j++)
{
struct tb_cell cell = cells[i * w + j];
if (cell.ch)
{
buf[(y + i) * tb_width() + (x + j)] = cell;
}
}
}
}
void draw_bigclock(struct term_buf* buf)
{
if (!config.bigclock)
{
return;
}
int xo = buf->width / 2 - (5 * (CLOCK_W + 1)) / 2;
int yo = (buf->height - buf->box_height) / 2 - CLOCK_H - 2;
char* clockstr = time_str("%H:%M", 6);
struct tb_cell* clockcell;
for (int i = 0; i < 5; i++)
{
clockcell = clock_cell(clockstr[i]);
alpha_blit(tb_cell_buffer(), xo + i * (CLOCK_W + 1), yo, CLOCK_W, CLOCK_H, clockcell);
free(clockcell);
}
free(clockstr);
}
void draw_clock(struct term_buf* buf)
{
if (config.clock == NULL || strlen(config.clock) == 0)
{
return;
}
char* clockstr = time_str(config.clock, 32);
int clockstrlen = strlen(clockstr);
struct tb_cell* cells = strn_cell(clockstr, clockstrlen);
tb_blit(buf->width - clockstrlen, 0, clockstrlen, 1, cells);
free(clockstr);
free(cells);
}
struct tb_cell* strn_cell(char* s, uint16_t len) // throws
{
struct tb_cell* cells = malloc((sizeof (struct tb_cell)) * len);
char* s2 = s;
uint32_t c;
if (cells != NULL)
{
for (uint16_t 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)
{
uint16_t 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
uint16_t 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)
{
uint16_t 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 (uint16_t 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)
{
uint16_t len = strlen(input->text);
uint16_t 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 (uint16_t 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)
{
uint16_t len = strlen(input->text);
uint16_t 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 (uint16_t 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)
{
uint16_t x = buf->box_x + config.margin_box_h + buf->labels_max_len + 1;
int32_t 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;
buf->astate.doom = malloc(sizeof(struct doom_state));
if (buf->astate.doom == NULL)
{
dgn_throw(DGN_ALLOC);
}
uint16_t tmp_len = buf->width * buf->height;
buf->astate.doom->buf = malloc(tmp_len);
tmp_len -= buf->width;
if (buf->astate.doom->buf == NULL)
{
dgn_throw(DGN_ALLOC);
}
memset(buf->astate.doom->buf, 0, tmp_len);
memset(buf->astate.doom->buf + tmp_len, DOOM_STEPS - 1, buf->width);
}
static void doom_free(struct term_buf* buf)
{
free(buf->astate.doom->buf);
free(buf->astate.doom);
}
// Adapted from cmatrix
static void matrix_init(struct term_buf* buf)
{
buf->init_width = buf->width;
buf->init_height = buf->height;
buf->astate.matrix = malloc(sizeof(struct matrix_state));
struct matrix_state* s = buf->astate.matrix;
if (s == NULL)
{
dgn_throw(DGN_ALLOC);
}
uint16_t len = buf->height + 1;
s->grid = malloc(sizeof(struct matrix_dot*) * len);
if (s->grid == NULL)
{
dgn_throw(DGN_ALLOC);
}
len = (buf->height + 1) * buf->width;
(s->grid)[0] = malloc(sizeof(struct matrix_dot) * len);
if ((s->grid)[0] == NULL)
{
dgn_throw(DGN_ALLOC);
}
for (int i = 1; i <= buf->height; ++i)
{
s->grid[i] = s->grid[i - 1] + buf->width;
if (s->grid[i] == NULL)
{
dgn_throw(DGN_ALLOC);
}
}
s->length = malloc(buf->width * sizeof(int));
if (s->length == NULL)
{
dgn_throw(DGN_ALLOC);
}
s->spaces = malloc(buf->width * sizeof(int));
if (s->spaces == NULL)
{
dgn_throw(DGN_ALLOC);
}
s->updates = malloc(buf->width * sizeof(int));
if (s->updates == NULL)
{
dgn_throw(DGN_ALLOC);
}
// Initialize grid
for (int i = 0; i <= buf->height; ++i)
{
for (int j = 0; j <= buf->width - 1; j += 2)
{
s->grid[i][j].val = -1;
}
}
for (int j = 0; j < buf->width; j += 2)
{
s->spaces[j] = (int) rand() % buf->height + 1;
s->length[j] = (int) rand() % (buf->height - 3) + 3;
s->grid[1][j].val = ' ';
s->updates[j] = (int) rand() % 3 + 1;
}
}
static void matrix_free(struct term_buf* buf)
{
free(buf->astate.matrix->grid[0]);
free(buf->astate.matrix->grid);
free(buf->astate.matrix->length);
free(buf->astate.matrix->spaces);
free(buf->astate.matrix->updates);
free(buf->astate.matrix);
}
void animate_init(struct term_buf* buf)
{
if (config.animate)
{
switch(config.animation)
{
case 0:
{
doom_init(buf);
break;
}
case 1:
{
matrix_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
};
uint16_t src;
uint16_t random;
uint16_t dst;
uint16_t w = term_buf->init_width;
uint8_t* tmp = term_buf->astate.doom->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 (uint16_t x = 0; x < w; ++x)
{
for (uint16_t 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]];
}
}
}
// Adapted from cmatrix
static void matrix(struct term_buf* buf)
{
static int frame = 3;
const int frame_delay = 8;
static int count = 0;
bool first_col;
struct matrix_state* s = buf->astate.matrix;
// Allowed codepoints
const int randmin = 33;
const int randnum = 123 - randmin;
// Chars change mid-scroll
const bool changes = true;
if ((buf->width != buf->init_width) || (buf->height != buf->init_height))
{
return;
}
count += 1;
if (count > frame_delay)
{
frame += 1;
if (frame > 4) frame = 1;
count = 0;
for (int j = 0; j < buf->width; j += 2)
{
int tail;
if (frame > s->updates[j])
{
if (s->grid[0][j].val == -1 && s->grid[1][j].val == ' ')
{
if (s->spaces[j] > 0)
{
s->spaces[j]--;
} else {
s->length[j] = (int) rand() % (buf->height - 3) + 3;
s->grid[0][j].val = (int) rand() % randnum + randmin;
s->spaces[j] = (int) rand() % buf->height + 1;
}
}
int i = 0, seg_len = 0;
first_col = 1;
while (i <= buf->height)
{
// Skip over spaces
while (i <= buf->height
&& (s->grid[i][j].val == ' ' || s->grid[i][j].val == -1))
{
i++;
}
if (i > buf->height) break;
// Find the head of this col
tail = i;
seg_len = 0;
while (i <= buf->height
&& (s->grid[i][j].val != ' ' && s->grid[i][j].val != -1))
{
s->grid[i][j].is_head = false;
if (changes)
{
if (rand() % 8 == 0)
s->grid[i][j].val = (int) rand() % randnum + randmin;
}
i++;
seg_len++;
}
// Head's down offscreen
if (i > buf->height)
{
s->grid[tail][j].val = ' ';
continue;
}
s->grid[i][j].val = (int) rand() % randnum + randmin;
s->grid[i][j].is_head = true;
if (seg_len > s->length[j] || !first_col) {
s->grid[tail][j].val = ' ';
s->grid[0][j].val = -1;
}
first_col = 0;
i++;
}
}
}
}
uint32_t blank;
utf8_char_to_unicode(&blank, " ");
for (int j = 0; j < buf->width; j += 2)
{
for (int i = 1; i <= buf->height; ++i)
{
uint32_t c;
int fg = TB_GREEN;
int bg = TB_DEFAULT;
if (s->grid[i][j].val == -1 || s->grid[i][j].val == ' ')
{
tb_change_cell(j, i - 1, blank, fg, bg);
continue;
}
char tmp[2];
tmp[0] = s->grid[i][j].val;
tmp[1] = '\0';
if(utf8_char_to_unicode(&c, tmp))
{
if (s->grid[i][j].is_head)
{
fg = TB_WHITE | TB_BOLD;
}
tb_change_cell(j, i - 1, c, fg, bg);
}
}
}
}
void animate(struct term_buf* buf)
{
buf->width = tb_width();
buf->height = tb_height();
if (config.animate)
{
switch(config.animation)
{
case 0:
{
doom(buf);
break;
}
case 1:
{
matrix(buf);
break;
}
}
}
}
bool cascade(struct term_buf* term_buf, uint8_t* fails)
{
uint16_t width = term_buf->width;
uint16_t 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;
}

@ -1,92 +0,0 @@
#ifndef H_LY_DRAW
#define H_LY_DRAW
#include "termbox.h"
#include "inputs.h"
#include <stdbool.h>
#include <stdint.h>
struct box
{
uint32_t left_up;
uint32_t left_down;
uint32_t right_up;
uint32_t right_down;
uint32_t top;
uint32_t bot;
uint32_t left;
uint32_t right;
};
struct matrix_dot
{
int val;
bool is_head;
};
struct matrix_state
{
struct matrix_dot** grid;
int* length;
int* spaces;
int* updates;
};
struct doom_state
{
uint8_t* buf;
};
union anim_state
{
struct doom_state* doom;
struct matrix_state* matrix;
};
struct term_buf
{
uint16_t width;
uint16_t height;
uint16_t init_width;
uint16_t init_height;
struct box box_chars;
char* info_line;
uint16_t labels_max_len;
uint16_t box_x;
uint16_t box_y;
uint16_t box_width;
uint16_t box_height;
union anim_state astate;
};
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, uint16_t 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, uint8_t* fails);
void draw_bigclock(struct term_buf *buf);
void draw_clock(struct term_buf *buf);
#endif

@ -0,0 +1,18 @@
pub const Animation = enum {
none,
doom,
matrix,
};
pub const DisplayServer = enum {
wayland,
shell,
xinitrc,
x11,
};
pub const Input = enum {
session,
login,
password,
};

@ -1,285 +0,0 @@
#include "dragonfail.h"
#include "termbox.h"
#include "inputs.h"
#include "config.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <ctype.h>
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 || (event->key == TB_KEY_CTRL_H))
{
input_desktop_right(target);
}
else if (event->key == TB_KEY_ARROW_RIGHT || (event->key == TB_KEY_CTRL_L))
{
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->list_simple = 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(config.xinitrc), DS_XINITRC);
#if 0
input_desktop_add(target, strdup(lang.wayland), strdup(""), DS_WAYLAND);
#endif
}
void input_text(struct text* target, uint64_t 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;
target->x = 0;
target->y = 0;
}
void input_desktop_free(struct desktop* target)
{
if (target != NULL)
{
for (uint16_t 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->list_simple = realloc(target->list_simple, 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;
int name_len = strlen(name);
char* name_simple = strdup(name);
if (strstr(name_simple, " ") != NULL)
{
name_simple = strtok(name_simple, " ");
}
for (int i = 0; i < name_len; i++)
{
name_simple[i] = tolower(name_simple[i]);
}
target->list_simple[target->cur] = name_simple;
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);
}
}
void input_text_clear(struct text* target)
{
memset(target->text, 0, target->len + 1);
target->cur = target->text;
target->end = target->text;
target->visible_start = target->text;
}

@ -1,57 +0,0 @@
#ifndef H_LY_INPUTS
#define H_LY_INPUTS
#include "termbox.h"
#include <stdint.h>
enum display_server {DS_WAYLAND, DS_SHELL, DS_XINITRC, DS_XORG};
struct text
{
char* text;
char* end;
int64_t len;
char* cur;
char* visible_start;
uint16_t visible_len;
uint16_t x;
uint16_t y;
};
struct desktop
{
char** list;
char** list_simple;
char** cmd;
enum display_server* display_server;
uint16_t cur;
uint16_t len;
uint16_t visible_len;
uint16_t x;
uint16_t 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, uint64_t 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);
void input_text_clear(struct text* target);
#endif

@ -1,13 +1,125 @@
const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
pub const allocator = std.heap.raw_c_allocator;
pub const termbox = @cImport({
@cInclude("termbox.h");
});
pub fn c_str(str: []const u8) ![:0]u8 {
const new_str = try allocator.allocSentinel(u8, str.len, 0);
pub const pam = @cImport({
@cInclude("security/pam_appl.h");
});
for (str, 0..) |c, i| {
new_str[i] = c;
pub const c_size = u64;
pub const c_uid = u32;
pub const c_time = c_long;
pub const tm = extern struct {
tm_sec: c_int,
tm_min: c_int,
tm_hour: c_int,
tm_mday: c_int,
tm_mon: c_int,
tm_year: c_int,
tm_wday: c_int,
tm_yday: c_int,
tm_isdst: c_int,
};
pub const _POSIX_HOST_NAME_MAX: c_int = 0xFF;
pub const _SC_HOST_NAME_MAX: c_int = 0xB4;
pub const VT_ACTIVATE: c_int = 0x5606;
pub const VT_WAITACTIVE: c_int = 0x5607;
pub const KDGETLED: c_int = 0x4B31;
pub const KDGKBLED: c_int = 0x4B64;
pub const LED_NUM: c_int = 0x02;
pub const LED_CAP: c_int = 0x04;
pub const K_NUMLOCK: c_int = 0x02;
pub const K_CAPSLOCK: c_int = 0x04;
pub const O_RDONLY: c_uint = 0x00;
pub const O_WRONLY: c_uint = 0x01;
pub const O_RDWR: c_uint = 0x02;
pub extern "c" fn fileno(stream: *std.c.FILE) c_int;
pub extern "c" fn sysconf(name: c_int) c_long;
pub extern "c" fn time(second: ?*c_time) c_time;
pub extern "c" fn localtime(timer: *const c_time) *tm;
pub extern "c" fn strftime(str: [*:0]u8, maxsize: c_size, format: [*:0]const u8, timeptr: *const tm) c_size;
pub extern "c" fn setenv(name: [*:0]const u8, value: [*:0]const u8, overwrite: c_int) c_int;
pub extern "c" fn getuid() c_uid;
pub fn getHostName(allocator: Allocator) !struct {
buffer: []u8,
slice: []const u8,
} {
const hostname_sysconf = sysconf(_SC_HOST_NAME_MAX);
const hostname_max_length: u64 = if (hostname_sysconf < 0) @intCast(_POSIX_HOST_NAME_MAX) else @intCast(hostname_sysconf);
const buffer = try allocator.alloc(u8, hostname_max_length);
const error_code = std.c.gethostname(buffer.ptr, hostname_max_length);
if (error_code < 0) return error.CannotGetHostName;
var hostname_length: u64 = 0;
for (buffer, 0..) |char, i| {
if (char == 0) {
hostname_length = i + 1;
break;
}
}
return .{
.buffer = buffer,
.slice = buffer[0..hostname_length],
};
}
pub fn timeAsString(allocator: Allocator, format: []const u8, max_length: u64) ![:0]u8 {
const timer = time(null);
const tm_info = localtime(&timer);
const buffer = try allocator.allocSentinel(u8, max_length, 0);
const format_z = try allocator.dupeZ(u8, format);
defer allocator.free(format_z);
if (strftime(buffer, max_length, format_z, tm_info) < 0) return error.CannotGetFormattedTime;
return buffer;
}
pub fn getLockState(allocator: Allocator, console_dev: []const u8) !struct {
numlock: bool,
capslock: bool,
} {
const console_dev_z = try allocator.dupeZ(u8, console_dev);
defer allocator.free(console_dev_z);
const fd = std.c.open(console_dev_z, O_RDONLY);
if (fd < 0) return error.CannotOpenConsoleDev;
var numlock = false;
var capslock = false;
if (builtin.os.tag.isBSD()) {
var led: c_int = undefined;
_ = std.c.ioctl(fd, KDGETLED, &led);
numlock = (led & LED_NUM) != 0;
capslock = (led & LED_CAP) != 0;
} else {
var led: c_char = undefined;
_ = std.c.ioctl(fd, KDGKBLED, &led);
numlock = (led & K_NUMLOCK) != 0;
capslock = (led & K_CAPSLOCK) != 0;
}
return new_str;
_ = std.c.close(fd);
return .{
.numlock = numlock,
.capslock = capslock,
};
}

@ -1,715 +0,0 @@
#include "dragonfail.h"
#include "termbox.h"
#include "inputs.h"
#include "draw.h"
#include "utils.h"
#include "config.h"
#include "login.h"
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <security/pam_appl.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <utmp.h>
#include <xcb/xcb.h>
int get_free_display()
{
char xlock[1024];
uint8_t 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)
{
extern char** environ;
char* term = getenv("TERM");
char* lang = getenv("LANG");
// clean env
environ[0] = NULL;
setenv("TERM", term ? 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("LANG", lang ? lang : "C", 1);
// Set PATH if specified in the configuration
if (strlen(config.path))
{
int ok = setenv("PATH", config.path, 1);
if (ok != 0)
{
dgn_throw(DGN_PATH);
}
}
}
void env_xdg_session(const enum display_server display_server)
{
switch (display_server)
{
case DS_WAYLAND:
{
setenv("XDG_SESSION_TYPE", "wayland", 1);
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 env_xdg(const char* tty_id, const char* desktop_name)
{
char user[20];
snprintf(user, 20, "/run/user/%d", getuid());
setenv("XDG_RUNTIME_DIR", user, 0);
setenv("XDG_SESSION_CLASS", "user", 0);
setenv("XDG_SESSION_ID", "1", 0);
setenv("XDG_SESSION_DESKTOP", desktop_name, 0);
setenv("XDG_SEAT", "seat0", 0);
setenv("XDG_VTNR", tty_id, 0);
}
void add_utmp_entry(
struct utmp *entry,
char *username,
pid_t display_pid
) {
entry->ut_type = USER_PROCESS;
entry->ut_pid = display_pid;
strcpy(entry->ut_line, ttyname(STDIN_FILENO) + strlen("/dev/"));
/* only correct for ptys named /dev/tty[pqr][0-9a-z] */
strcpy(entry->ut_id, ttyname(STDIN_FILENO) + strlen("/dev/tty"));
time((long int *) &entry->ut_time);
strncpy(entry->ut_user, username, UT_NAMESIZE);
memset(entry->ut_host, 0, UT_HOSTSIZE);
entry->ut_addr = 0;
setutent();
pututline(entry);
}
void remove_utmp_entry(struct utmp *entry) {
entry->ut_type = DEAD_PROCESS;
memset(entry->ut_line, 0, UT_LINESIZE);
entry->ut_time = 0;
memset(entry->ut_user, 0, UT_NAMESIZE);
setutent();
pututline(entry);
endutent();
}
void xauth(const char* display_name, const char* shell, char* pwd)
{
const char* xauth_file = "lyxauth";
char* xauth_dir = getenv("XDG_RUNTIME_DIR");
if ((xauth_dir == NULL) || (*xauth_dir == '\0'))
{
xauth_dir = getenv("XDG_CONFIG_HOME");
struct stat sb;
if ((xauth_dir == NULL) || (*xauth_dir == '\0'))
{
xauth_dir = strdup(pwd);
strcat(xauth_dir, "/.config");
stat(xauth_dir, &sb);
if (S_ISDIR(sb.st_mode))
{
strcat(xauth_dir, "/ly");
}
else
{
xauth_dir = pwd;
xauth_file = ".lyxauth";
}
}
else
{
strcat(xauth_dir, "/ly");
}
// If .config/ly/ or XDG_CONFIG_HOME/ly/ doesn't exist and can't create the directory, use pwd
// Passing pwd beforehand is safe since stat will always evaluate false
stat(xauth_dir, &sb);
if (!S_ISDIR(sb.st_mode) && mkdir(xauth_dir, 0777) == -1)
{
xauth_dir = pwd;
xauth_file = ".lyxauth";
}
}
// trim trailing slashes
int i = strlen(xauth_dir) - 1;
while (xauth_dir[i] == '/') i--;
xauth_dir[i + 1] = '\0';
char xauthority[256];
snprintf(xauthority, 256, "%s/%s", xauth_dir, xauth_file);
setenv("XAUTHORITY", xauthority, 1);
setenv("DISPLAY", display_name, 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* vt,
const char* desktop_cmd)
{
char display_name[4];
snprintf(display_name, 3, ":%d", get_free_display());
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);
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)
{
char cmd[1024];
snprintf(cmd, 1024, "%s %s", config.wayland_cmd, desktop_cmd);
execl(pwd->pw_shell, pwd->pw_shell, "-c", cmd, NULL);
}
void shell(struct passwd* pwd)
{
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, 1023);
execl(pwd->pw_shell, args, NULL);
}
// pam_do performs the pam action specified in pam_action
// on pam_action fail, call diagnose and end pam session
int pam_do(
int (pam_action)(struct pam_handle *, int),
struct pam_handle *handle,
int flags,
struct term_buf *buf)
{
int status = pam_action(handle, flags);
if (status != PAM_SUCCESS) {
pam_diagnose(status, buf);
pam_end(handle, status);
}
return status;
}
void auth(
struct desktop* desktop,
struct text* login,
struct text* password,
struct term_buf* buf)
{
int ok;
char tty_id [3];
snprintf(tty_id, 3, "%d", config.tty);
// Add XDG environment variables
env_xdg_session(desktop->display_server[desktop->cur]);
env_xdg(tty_id, desktop->list_simple[desktop->cur]);
// 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_do(pam_authenticate, handle, 0, buf);
if (ok != PAM_SUCCESS)
{
return;
}
ok = pam_do(pam_acct_mgmt, handle, 0, buf);
if (ok != PAM_SUCCESS)
{
return;
}
ok = pam_do(pam_setcred, handle, PAM_ESTABLISH_CRED, buf);
if (ok != PAM_SUCCESS)
{
return;
}
ok = pam_do(pam_open_session, handle, 0, buf);
if (ok != PAM_SUCCESS)
{
return;
}
// clear the credentials
input_text_clear(password);
// 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
char vt[5];
snprintf(vt, 5, "vt%d", config.tty);
// set env (this clears the environment)
env_init(pwd);
// Re-add XDG environment variables from lines 508,509
env_xdg_session(desktop->display_server[desktop->cur]);
env_xdg(tty_id, desktop->list_simple[desktop->cur]);
if (dgn_catch())
{
exit(EXIT_FAILURE);
}
// add pam variables
char** env = pam_getenvlist(handle);
for (uint16_t i = 0; env && env[i]; ++i)
{
putenv(env[i]);
}
// execute
int ok = chdir(pwd->pw_dir);
if (ok != 0)
{
dgn_throw(DGN_CHDIR);
exit(EXIT_FAILURE);
}
reset_terminal(pwd);
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, vt, desktop->cmd[desktop->cur]);
break;
}
}
exit(EXIT_SUCCESS);
}
// add utmp audit
struct utmp entry;
add_utmp_entry(&entry, pwd->pw_name, pid);
// wait for the session to stop
int status;
waitpid(pid, &status, 0);
remove_utmp_entry(&entry);
reset_terminal(pwd);
// 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_do(pam_close_session, handle, 0, buf);
if (ok != PAM_SUCCESS)
{
return;
}
ok = pam_do(pam_setcred, handle, PAM_DELETE_CRED, buf);
if (ok != PAM_SUCCESS)
{
return;
}
ok = pam_end(handle, 0);
if (ok != PAM_SUCCESS)
{
pam_diagnose(ok, buf);
}
}

@ -1,13 +0,0 @@
#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

@ -1,383 +1,507 @@
const std = @import("std");
const config = @import("config.zig");
const utils = @import("utils.zig");
const build_options = @import("build_options");
const builtin = @import("builtin");
const clap = @import("clap");
const auth = @import("auth.zig");
const bigclock = @import("bigclock.zig");
const interop = @import("interop.zig");
const Doom = @import("animations/Doom.zig");
const Matrix = @import("animations/Matrix.zig");
const TerminalBuffer = @import("tui/TerminalBuffer.zig");
const Desktop = @import("tui/components/Desktop.zig");
const Text = @import("tui/components/Text.zig");
const Config = @import("config/Config.zig");
const ConfigReader = @import("config/ConfigReader.zig");
const Lang = @import("config/Lang.zig");
const termbox = interop.termbox;
const LY_VERSION = "1.0.0";
pub const c = @cImport({
@cInclude("dragonfail.h");
@cInclude("termbox.h");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const stderr = std.io.getStdErr().writer();
// Load arguments
const params = comptime clap.parseParamsComptime(
\\-h, --help Shows all commands.
\\-v, --version Shows the version of Ly.
\\-c, --config <str> Overrides the default configuration path. Example: --config /usr/share/ly
);
var diag = clap.Diagnostic{};
var res = clap.parse(clap.Help, &params, clap.parsers.default, .{ .diagnostic = &diag }) catch |err| {
diag.report(stderr, err) catch {};
return err;
};
defer res.deinit();
var config: Config = undefined;
var lang: Lang = undefined;
var info_line: []const u8 = undefined;
if (res.args.help != 0) {
try clap.help(stderr, clap.Help, &params, .{});
_ = try stderr.write("Note: if you want to configure Ly, please check the config file, which is usually located at /etc/ly/config.ini.\n");
std.os.exit(0);
}
if (res.args.version != 0) {
_ = try stderr.write("Ly version " ++ LY_VERSION ++ "\n");
std.os.exit(0);
}
@cInclude("draw.h");
@cInclude("inputs.h");
@cInclude("login.h");
@cInclude("utils.h");
@cInclude("config.h");
});
// Load configuration file
var config_reader = ConfigReader.init(allocator);
defer config_reader.deinit();
// Compile-time settings
const LY_VERSION = "0.7.0";
const MAX_AUTH_FAILS = 10;
if (res.args.config) |s| {
const trailing_slash = if (s[s.len - 1] != '/') "/" else "";
// Main allocator for Ly
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const config_path = try std.fmt.allocPrint(allocator, "{s}{s}config.ini", .{ s, trailing_slash });
defer allocator.free(config_path);
pub const allocator = gpa.allocator();
config = try config_reader.readConfig(config_path);
// Ly general and language configuration
pub var c_config: c.struct_config = undefined;
pub var c_lang: c.struct_lang = undefined;
const lang_path = try std.fmt.allocPrint(allocator, "{s}{s}lang/{s}.ini", .{ s, trailing_slash, config.ly.lang });
defer allocator.free(lang_path);
comptime {
@export(c_config, .{ .name = "config" });
@export(c_lang, .{ .name = "lang" });
}
lang = try config_reader.readLang(lang_path);
} else {
config = try config_reader.readConfig(build_options.data_directory ++ "/config.ini");
// Main function
pub fn main() !void {
// Initialize structs
var config_ptr = try allocator.create(c.struct_config);
defer allocator.destroy(config_ptr);
var lang_ptr = try allocator.create(c.struct_lang);
defer allocator.destroy(lang_ptr);
c_config = config_ptr.*;
c_lang = lang_ptr.*;
// Initialize error library
log_init(c.dgn_init());
// Parse command line arguments
var config_path: []const u8 = "";
var process_args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, process_args);
if (process_args.len > 1) {
var first_arg = process_args[1];
if (std.mem.eql(u8, first_arg, "--help") or std.mem.eql(u8, first_arg, "-h")) {
std.debug.print("--help (-h) | Shows this help message.\n", .{});
std.debug.print("--version (-v) | Shows the version of Ly.\n", .{});
std.debug.print("--config (-c) <path> | Overrides the configuration file path.\n", .{});
std.debug.print("\n", .{});
std.debug.print("If you want to configure Ly, please check the config file, usually located at /etc/ly/config.ini.\n", .{});
std.os.exit(0);
} else if (std.mem.eql(u8, first_arg, "--version") or std.mem.eql(u8, first_arg, "-v")) {
std.debug.print("Ly version {s}.\n", .{LY_VERSION});
std.os.exit(0);
} else if (std.mem.eql(u8, first_arg, "--config") or std.mem.eql(u8, first_arg, "-c")) {
if (process_args.len != 3) {
std.debug.print("Invalid usage! Correct usage: 'ly --config <path>'.\n", .{});
std.os.exit(1);
}
const lang_path = try std.fmt.allocPrint(allocator, "{s}/lang/{s}.ini", .{ build_options.data_directory, config.ly.lang });
defer allocator.free(lang_path);
config_path = process_args[2];
} else {
std.debug.print("Invalid argument: '{s}'.\n", .{first_arg});
std.os.exit(1);
}
lang = try config_reader.readLang(lang_path);
}
// Load configuration and language
try config.config_load(config_path);
try config.lang_load();
// Initialize information line with host name
get_host_name: {
const host_name_struct = interop.getHostName(allocator) catch |err| {
if (err == error.CannotGetHostName) {
info_line = lang.ly.err_hostname;
} else {
info_line = lang.ly.err_alloc;
}
break :get_host_name;
};
defer allocator.free(host_name_struct.buffer);
if (c.dgn_catch() != 0) {
config.config_free();
config.lang_free();
std.os.exit(1);
info_line = host_name_struct.slice;
}
// Initialize inputs
var desktop = try allocator.create(c.struct_desktop);
defer allocator.destroy(desktop);
// Initialize termbox
_ = termbox.tb_init();
defer termbox.tb_shutdown();
var username = try allocator.create(c.struct_text);
defer allocator.destroy(username);
_ = termbox.tb_select_output_mode(termbox.TB_OUTPUT_NORMAL);
termbox.tb_clear();
var password = try allocator.create(c.struct_text);
defer allocator.destroy(password);
// Initialize terminal buffer
const labels_max_length = @max(lang.ly.login.len, lang.ly.password.len);
c.input_desktop(desktop);
c.input_text(username, config.ly_config.ly.max_login_len);
c.input_text(password, config.ly_config.ly.max_password_len);
var buffer = TerminalBuffer.init(config.ly.margin_box_v, config.ly.margin_box_h, config.ly.input_len, labels_max_length, config.ly.fg, config.ly.bg);
utils.desktop_load(desktop);
try utils.load(desktop, username);
// Initialize components
var desktop = try Desktop.init(allocator, &buffer, config.ly.max_desktop_len);
defer desktop.deinit();
// Start termbox
_ = c.tb_init();
_ = c.tb_select_output_mode(c.TB_OUTPUT_NORMAL);
c.tb_clear();
desktop.addEnvironment(lang.ly.shell, "", .shell) catch {
info_line = lang.ly.err_alloc;
};
desktop.addEnvironment(lang.ly.xinitrc, config.ly.xinitrc, .xinitrc) catch {
info_line = lang.ly.err_alloc;
};
// Initialize visible elements
var event = try allocator.create(c.struct_tb_event);
defer allocator.destroy(event);
try desktop.crawl(config.ly.waylandsessions, .wayland);
try desktop.crawl(config.ly.xsessions, .x11);
var buffer = try allocator.create(c.struct_term_buf);
defer allocator.destroy(buffer);
var login = try Text.init(allocator, &buffer, config.ly.max_login_len);
defer login.deinit();
// Place the cursor on the login field if there is no saved username
// If there is, place the curser on the password field
var active_input: config.Inputs = undefined;
if (config.ly_config.ly.default_input == .login and username.text != username.end) {
active_input = .password;
} else {
active_input = config.ly_config.ly.default_input;
var password = try Text.init(allocator, &buffer, config.ly.max_password_len);
defer password.deinit();
// Load last saved username and desktop selection, if any
if (config.ly.load) load_last_saved: {
var file = std.fs.openFileAbsolute(config.ly.save_file, .{}) catch break :load_last_saved;
defer file.close();
const reader = file.reader();
const username_length = try reader.readIntLittle(u64);
const username_buffer = try file.readToEndAlloc(allocator, username_length);
defer allocator.free(username_buffer);
_ = try reader.read(username_buffer);
const current_desktop = try reader.readIntLittle(u64);
const load_buffer = try file.readToEndAlloc(allocator, config.ly.max_login_len + 5);
defer allocator.free(load_buffer);
if (username_buffer.len > 0) {
try login.text.appendSlice(username_buffer);
login.end = username_buffer.len;
}
if (current_desktop < desktop.environments.items.len) desktop.current = current_desktop;
}
// Initialize drawing code
c.draw_init(buffer);
// draw_box() and position_input() are called because they need to be
// called before the switch case for the cursor to be positioned correctly
c.draw_box(buffer);
c.position_input(buffer, desktop, username, password);
switch (active_input) {
.session => {
c.handle_desktop(desktop, event);
},
.login => {
c.handle_text(username, event);
},
.password => {
c.handle_text(password, event);
},
var active_input = if (config.ly.default_input == .login and login.text.items.len != login.end) .password else config.ly.default_input;
// Place components on the screen
{
buffer.drawBoxCenter(!config.ly.hide_borders, config.ly.blank_box);
const coordinates = buffer.calculateComponentCoordinates();
desktop.position(coordinates.x, coordinates.y + 2, coordinates.visible_length);
login.position(coordinates.x, coordinates.y + 4, coordinates.visible_length);
password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length);
switch (active_input) {
.session => desktop.handle(null),
.login => login.handle(null) catch {
info_line = lang.ly.err_alloc;
},
.password => password.handle(null) catch {
info_line = lang.ly.err_alloc;
},
}
}
if (config.ly_config.ly.animate) {
c.animate_init(buffer);
// Initialize the animation, if any
var doom: Doom = undefined;
var matrix: Matrix = undefined;
if (c.dgn_catch() != 0) {
config.ly_config.ly.animate = false;
c.dgn_reset();
switch (config.ly.animation) {
.none => {},
.doom => doom = try Doom.init(allocator, &buffer),
.matrix => matrix = try Matrix.init(allocator, &buffer),
}
defer {
switch (config.ly.animation) {
.none => {},
.doom => doom.deinit(),
.matrix => matrix.deinit(),
}
}
// Initialize state information
var err: c_int = 0;
const animate = config.ly.animation != .none;
const has_clock = config.ly.clock.len > 0;
const shutdown_key = try std.fmt.parseInt(u8, config.ly.shutdown_key[1..], 10);
const restart_key = try std.fmt.parseInt(u8, config.ly.restart_key[1..], 10);
var event = std.mem.zeroes(termbox.tb_event);
var run = true;
var update = true;
var reboot = false;
var resolution_changed = false;
var shutdown = false;
var auth_fails: u8 = 0;
var restart = false;
var auth_fails: u64 = 0;
// Switch to selected TTY if possible
open_console_dev: {
const console_dev_z = allocator.dupeZ(u8, config.ly.console_dev) catch {
info_line = lang.ly.err_alloc;
break :open_console_dev;
};
defer allocator.free(console_dev_z);
const fd = std.c.open(console_dev_z, interop.O_WRONLY);
defer _ = std.c.close(fd);
if (fd < 0) {
info_line = lang.ly.err_console_dev;
break :open_console_dev;
}
c.switch_tty(buffer);
_ = std.c.ioctl(fd, interop.VT_ACTIVATE, config.ly.tty);
_ = std.c.ioctl(fd, interop.VT_WAITACTIVE, config.ly.tty);
}
// Main loop
while (run) {
// If there's no input or there's an animation, a resolution change needs to be checked
if (!update or config.ly.animation != .none) {
if (!update) std.time.sleep(100_000_000);
termbox.tb_present(); // Required to update tb_width(), tb_height() and tb_cell_buffer()
const width: u64 = @intCast(termbox.tb_width());
const height: u64 = @intCast(termbox.tb_height());
if (width != buffer.width) {
buffer.width = width;
resolution_changed = true;
}
if (height != buffer.height) {
buffer.height = height;
resolution_changed = true;
}
// If it did change, then update the cell buffer, reallocate the current animation's buffers, and force a draw update
if (resolution_changed) {
buffer.buffer = termbox.tb_cell_buffer();
switch (config.ly.animation) {
.none => {},
.doom => doom.realloc() catch {
info_line = lang.ly.err_alloc;
},
.matrix => matrix.realloc() catch {
info_line = lang.ly.err_alloc;
},
}
update = true;
}
}
if (update) {
if (auth_fails < MAX_AUTH_FAILS) {
// If the user entered a wrong password 10 times in a row, play a cascade animation, else update normally
if (auth_fails < 10) {
switch (active_input) {
.session => {
c.handle_desktop(desktop, event);
},
.login => {
c.handle_text(username, event);
.session => desktop.handle(null),
.login => login.handle(null) catch {
info_line = lang.ly.err_alloc;
},
.password => {
c.handle_text(password, event);
.password => password.handle(null) catch {
info_line = lang.ly.err_alloc;
},
}
c.tb_clear();
c.animate(buffer);
c.draw_bigclock(buffer);
c.draw_box(buffer);
c.draw_clock(buffer);
c.draw_labels(buffer);
if (!config.ly_config.ly.hide_f1_commands) {
c.draw_f_commands();
termbox.tb_clear();
switch (config.ly.animation) {
.none => {},
.doom => doom.draw(),
.matrix => matrix.draw(),
}
c.draw_lock_state(buffer);
c.position_input(buffer, desktop, username, password);
c.draw_desktop(desktop);
c.draw_input(username);
c.draw_input_mask(password);
update = config.ly_config.ly.animate;
} else {
std.time.sleep(10000000); // Sleep 0.01 seconds
update = c.cascade(buffer, &auth_fails);
}
c.tb_present();
}
if (config.ly.bigclock and buffer.box_height + (bigclock.HEIGHT + 2) * 2 < buffer.height) draw_big_clock: {
const format = "%H:%M";
const xo = buffer.width / 2 - (format.len * (bigclock.WIDTH + 1)) / 2;
const yo = (buffer.height - buffer.box_height) / 2 - bigclock.HEIGHT - 2;
var timeout: c_int = -1;
const clock_str = interop.timeAsString(allocator, format, format.len + 1) catch {
info_line = lang.ly.err_alloc;
break :draw_big_clock;
};
defer allocator.free(clock_str);
if (config.ly_config.ly.animate) {
timeout = config.ly_config.ly.min_refresh_delta;
} else {
// TODO: Use the Zig standard library directly
var time = try allocator.create(std.os.linux.timeval);
defer allocator.destroy(time);
for (0..format.len) |i| {
const clock_cell = bigclock.clockCell(animate, clock_str[i], buffer.fg, buffer.bg);
bigclock.alphaBlit(buffer.buffer, xo + i * (bigclock.WIDTH + 1), yo, buffer.width, buffer.height, clock_cell);
}
}
_ = std.os.linux.gettimeofday(time, undefined);
buffer.drawBoxCenter(!config.ly.hide_borders, config.ly.blank_box);
if (config.ly_config.ly.bigclock) {
timeout = @intCast((60 - @mod(time.tv_sec, 60)) * 1000 - @divTrunc(time.tv_usec, 1000) + 1);
} else if (config.ly_config.ly.clock.len > 0) {
timeout = @intCast(1000 - @divTrunc(time.tv_usec, 1000) + 1);
}
}
if (has_clock) draw_clock: {
const clock_buffer = interop.timeAsString(allocator, config.ly.clock, 32) catch {
info_line = lang.ly.err_alloc;
break :draw_clock;
};
defer allocator.free(clock_buffer);
if (timeout == -1) {
err = c.tb_poll_event(event);
} else {
err = c.tb_peek_event(event, timeout);
}
var clock_str_length: u64 = 0;
for (clock_buffer, 0..) |char, i| {
if (char == 0) {
clock_str_length = i;
break;
}
}
if (err < 0) {
continue;
}
if (clock_str_length == 0) return error.FormattedTimeEmpty;
if (event.type == c.TB_EVENT_KEY) {
switch (event.key) {
c.TB_KEY_F1 => {
shutdown = true;
run = false;
},
c.TB_KEY_F2 => {
reboot = true;
run = false;
},
c.TB_KEY_CTRL_C => {
run = false;
},
c.TB_KEY_CTRL_U => {
if (active_input != .session) {
switch (active_input) {
.login => {
c.input_text_clear(username);
},
.password => {
c.input_text_clear(password);
},
else => unreachable,
}
buffer.drawLabel(clock_buffer[0..clock_str_length], buffer.width - clock_str_length, 0);
}
update = true;
}
},
c.TB_KEY_CTRL_K, c.TB_KEY_ARROW_UP => {
if (active_input != .session) {
active_input = switch (active_input) {
.login => .session,
.password => .login,
else => unreachable,
};
update = true;
}
},
c.TB_KEY_CTRL_J, c.TB_KEY_ARROW_DOWN => {
if (active_input != .password) {
active_input = switch (active_input) {
.session => .login,
.login => .password,
else => unreachable,
};
update = true;
}
},
c.TB_KEY_TAB => {
active_input = switch (active_input) {
.session => .login,
.login => .password,
.password => .session,
};
const label_x = buffer.box_x + buffer.margin_box_h;
const label_y = buffer.box_y + buffer.margin_box_v;
update = true;
},
c.TB_KEY_ENTER => {
try utils.save(desktop, username);
c.auth(desktop, username, password, buffer);
buffer.drawLabel(lang.ly.login, label_x, label_y + 4);
buffer.drawLabel(lang.ly.password, label_x, label_y + 6);
if (c.dgn_catch() != 0) {
auth_fails += 1;
if (info_line.len > 0) {
const x = buffer.box_x + ((buffer.box_width - info_line.len) / 2);
buffer.drawLabel(info_line, x, label_y);
}
// Move focus back to password input
active_input = .password;
if (!config.ly.hide_key_hints) {
var length: u64 = 0;
if (c.dgn_output_code() != c.DGN_PAM) {
buffer.info_line = c.dgn_output_log();
}
buffer.drawLabel(config.ly.shutdown_key, length, 0);
length += config.ly.shutdown_key.len + 1;
if (config.ly_config.ly.blank_password) {
c.input_text_clear(password);
buffer.drawLabel(lang.ly.shutdown, length, 0);
length += lang.ly.shutdown.len + 1;
buffer.drawLabel(config.ly.restart_key, length, 0);
length += config.ly.restart_key.len + 1;
buffer.drawLabel(lang.ly.restart, length, 0);
length += lang.ly.restart.len + 1;
}
draw_lock_state: {
const lock_state = interop.getLockState(allocator, config.ly.console_dev) catch |err| {
if (err == error.CannotOpenConsoleDev) {
info_line = lang.ly.err_console_dev;
} else {
info_line = lang.ly.err_alloc;
}
break :draw_lock_state;
};
c.dgn_reset();
} else {
buffer.info_line = c_lang.logout;
}
var lock_state_x = buffer.width - lang.ly.numlock.len;
const lock_state_y: u64 = if (has_clock) 1 else 0;
try utils.load(desktop, username);
if (lock_state.numlock) buffer.drawLabel(lang.ly.numlock, lock_state_x, lock_state_y);
lock_state_x -= lang.ly.capslock.len + 1;
if (lock_state.capslock) buffer.drawLabel(lang.ly.capslock, lock_state_x, lock_state_y);
}
// Reset cursor to its normal state
_ = std.ChildProcess.exec(.{ .argv = &[_][]const u8{ "/usr/bin/tput", "cnorm" }, .allocator = allocator }) catch return;
if (resolution_changed) {
const coordinates = buffer.calculateComponentCoordinates();
desktop.position(coordinates.x, coordinates.y + 2, coordinates.visible_length);
login.position(coordinates.x, coordinates.y + 4, coordinates.visible_length);
password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length);
update = true;
},
else => {
update = true;
},
resolution_changed = false;
}
desktop.draw();
login.draw();
password.drawMasked(config.ly.asterisk);
update = animate;
} else {
std.time.sleep(10_000_000);
update = buffer.cascade();
if (!update) {
std.time.sleep(7_000_000_000);
auth_fails = 0;
}
}
termbox.tb_present();
}
}
// Stop termbox
c.tb_shutdown();
var timeout: i32 = -1;
// Free inputs
c.input_desktop_free(desktop);
c.input_text_free(username);
c.input_text_free(password);
c.free_hostname();
// Calculate the maximum timeout based on current animations, or the (big) clock. If there's none, we wait for the event indefinitely instead
if (animate) {
timeout = config.ly.min_refresh_delta;
} else if (config.ly.bigclock and config.ly.clock.len == 0) {
var tv = std.mem.zeroes(std.c.timeval);
_ = std.c.gettimeofday(&tv, null);
// Unload configuration
c.draw_free(buffer);
config.lang_free();
timeout = @intCast((60 - @rem(tv.tv_sec, 60)) * 1000 - @divTrunc(tv.tv_usec, 1000) + 1);
} else if (config.ly.clock.len > 0 or auth_fails >= 10) {
var tv = std.mem.zeroes(std.c.timeval);
_ = std.c.gettimeofday(&tv, null);
if (shutdown) {
var shutdown_cmd = try std.fmt.allocPrint(allocator, "{s}", .{config.ly_config.ly.shutdown_cmd});
// This will never be freed! But it's fine, we're shutting down the system anyway
defer allocator.free(shutdown_cmd);
timeout = @intCast(1000 - @divTrunc(tv.tv_usec, 1000) + 1);
}
config.config_free();
const event_error = if (timeout == -1) termbox.tb_poll_event(&event) else termbox.tb_peek_event(&event, timeout);
std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", shutdown_cmd }) catch return;
} else if (reboot) {
var restart_cmd = try std.fmt.allocPrint(allocator, "{s}", .{config.ly_config.ly.restart_cmd});
// This will never be freed! But it's fine, we're rebooting the system anyway
defer allocator.free(restart_cmd);
if (event_error < 0 or event.type != termbox.TB_EVENT_KEY) continue;
config.config_free();
switch (event.key) {
termbox.TB_KEY_F1, termbox.TB_KEY_F2, termbox.TB_KEY_F3, termbox.TB_KEY_F4, termbox.TB_KEY_F5, termbox.TB_KEY_F6, termbox.TB_KEY_F7, termbox.TB_KEY_F8, termbox.TB_KEY_F9, termbox.TB_KEY_F10, termbox.TB_KEY_F11, termbox.TB_KEY_F12 => {
if (0xFFFF - event.key + 1 == shutdown_key) {
shutdown = true;
run = false;
} else if (0xFFFF - event.key + 1 == restart_key) {
restart = true;
run = false;
}
},
termbox.TB_KEY_CTRL_C => run = false,
termbox.TB_KEY_CTRL_U => {
if (active_input == .login) {
login.clear();
update = true;
} else if (active_input == .password) {
password.clear();
update = true;
}
},
termbox.TB_KEY_CTRL_K, termbox.TB_KEY_ARROW_UP => {
active_input = switch (active_input) {
.session, .login => .session,
.password => .login,
};
update = true;
},
termbox.TB_KEY_CTRL_J, termbox.TB_KEY_ARROW_DOWN => {
active_input = switch (active_input) {
.session => .login,
.login, .password => .password,
};
update = true;
},
termbox.TB_KEY_TAB => {
active_input = switch (active_input) {
.session => .login,
.login => .password,
.password => .session,
};
update = true;
},
termbox.TB_KEY_ENTER => authenticate: {
if (config.ly.save) save_last_settings: {
var file = std.fs.createFileAbsolute(config.ly.save_file, .{}) catch break :save_last_settings;
defer file.close();
const writer = file.writer();
try writer.writeIntLittle(u64, login.end);
_ = try writer.write(login.text.items);
try writer.writeIntLittle(u64, desktop.current);
}
std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", restart_cmd }) catch return;
} else {
config.config_free();
var has_error = false;
auth.authenticate(allocator, config.ly.tty, buffer, desktop, login, password) catch {
has_error = true;
auth_fails += 1;
active_input = .password;
// TODO: Errors in info_line
if (config.ly.blank_password) password.clear();
};
update = true;
if (!has_error) info_line = lang.ly.logout;
std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", config.ly.term_restore_cursor_cmd }) catch break :authenticate;
},
else => {
switch (active_input) {
.session => desktop.handle(&event),
.login => login.handle(&event) catch {
info_line = lang.ly.err_alloc;
},
.password => password.handle(&event) catch {
info_line = lang.ly.err_alloc;
},
}
update = true;
},
}
}
}
// Low-level error messages
fn log_init(log: [*c][*c]u8) void {
log[c.DGN_OK] = c_lang.err_dgn_oob;
log[c.DGN_NULL] = c_lang.err_null;
log[c.DGN_ALLOC] = c_lang.err_alloc;
log[c.DGN_BOUNDS] = c_lang.err_bounds;
log[c.DGN_DOMAIN] = c_lang.err_domain;
log[c.DGN_MLOCK] = c_lang.err_mlock;
log[c.DGN_XSESSIONS_DIR] = c_lang.err_xsessions_dir;
log[c.DGN_XSESSIONS_OPEN] = c_lang.err_xsessions_open;
log[c.DGN_PATH] = c_lang.err_path;
log[c.DGN_CHDIR] = c_lang.err_chdir;
log[c.DGN_PWNAM] = c_lang.err_pwnam;
log[c.DGN_USER_INIT] = c_lang.err_user_init;
log[c.DGN_USER_GID] = c_lang.err_user_gid;
log[c.DGN_USER_UID] = c_lang.err_user_uid;
log[c.DGN_PAM] = c_lang.err_pam;
log[c.DGN_HOSTNAME] = c_lang.err_hostname;
if (shutdown) {
return std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", config.ly.shutdown_cmd });
} else if (restart) {
return std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", config.ly.restart_cmd });
}
}

@ -0,0 +1,169 @@
const std = @import("std");
const builtin = @import("builtin");
const interop = @import("../interop.zig");
const utils = @import("utils.zig");
const Allocator = std.mem.Allocator;
const Random = std.rand.Random;
const termbox = interop.termbox;
const TerminalBuffer = @This();
random: Random,
width: u64,
height: u64,
buffer: [*]termbox.tb_cell,
fg: u8,
bg: u8,
box_chars: struct {
left_up: u32,
left_down: u32,
right_up: u32,
right_down: u32,
top: u32,
bottom: u32,
left: u32,
right: u32,
},
labels_max_length: u64,
box_x: u64,
box_y: u64,
box_width: u64,
box_height: u64,
margin_box_v: u8,
margin_box_h: u8,
pub fn init(margin_box_v: u8, margin_box_h: u8, input_length: u8, labels_max_length: u64, fg: u8, bg: u8) TerminalBuffer {
var prng = std.rand.Isaac64.init(@intCast(std.time.timestamp()));
return .{
.random = prng.random(),
.width = @intCast(termbox.tb_width()),
.height = @intCast(termbox.tb_height()),
.buffer = termbox.tb_cell_buffer(),
.fg = fg,
.bg = bg,
.box_chars = if (builtin.os.tag == .linux or builtin.os.tag.isBSD()) .{
.left_up = 0x250C,
.left_down = 0x2514,
.right_up = 0x2510,
.right_down = 0x2518,
.top = 0x2500,
.bottom = 0x2500,
.left = 0x2502,
.right = 0x2502,
} else .{
.left_up = '+',
.left_down = '+',
.right_up = '+',
.right_down = '+',
.top = '-',
.bottom = '-',
.left = '|',
.right = '|',
},
.labels_max_length = labels_max_length,
.box_x = 0,
.box_y = 0,
.box_width = (2 * margin_box_h) + input_length + 1 + labels_max_length,
.box_height = 7 + (2 * margin_box_v),
.margin_box_v = margin_box_v,
.margin_box_h = margin_box_h,
};
}
pub fn cascade(self: TerminalBuffer) bool {
var changes = false;
var y = self.height - 2;
while (y > 0) : (y -= 1) {
for (0..self.width) |x| {
const c: u8 = @truncate(self.buffer[(y - 1) * self.width + x].ch);
if (std.ascii.isWhitespace(c)) continue;
const c_under: u8 = @truncate(self.buffer[y * self.width + x].ch);
if (!std.ascii.isWhitespace(c_under)) continue;
changes = true;
if ((self.random.int(u16) % 10) > 7) continue;
self.buffer[y * self.width + x] = self.buffer[(y - 1) * self.width + x];
self.buffer[(y - 1) * self.width + x].ch = ' ';
}
}
return changes;
}
pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool) void {
const x1 = (self.width - self.box_width) / 2;
const y1 = (self.height - self.box_height) / 2;
const x2 = (self.width + self.box_width) / 2;
const y2 = (self.height + self.box_height) / 2;
self.box_x = x1;
self.box_y = y1;
if (show_borders) {
termbox.tb_change_cell(@intCast(x1 - 1), @intCast(y1 - 1), self.box_chars.left_up, self.fg, self.bg);
termbox.tb_change_cell(@intCast(x2), @intCast(y1 - 1), self.box_chars.right_up, self.fg, self.bg);
termbox.tb_change_cell(@intCast(x1 - 1), @intCast(y2), self.box_chars.left_down, self.fg, self.bg);
termbox.tb_change_cell(@intCast(x2), @intCast(y2), self.box_chars.right_down, self.fg, self.bg);
var c1 = utils.initCell(self.box_chars.top, self.fg, self.bg);
var c2 = utils.initCell(self.box_chars.bottom, self.fg, self.bg);
for (0..self.box_width) |i| {
termbox.tb_put_cell(@intCast(x1 + i), @intCast(y1 - 1), &c1);
termbox.tb_put_cell(@intCast(x1 + i), @intCast(y2), &c2);
}
c1.ch = self.box_chars.left;
c2.ch = self.box_chars.right;
for (0..self.box_height) |i| {
termbox.tb_put_cell(@intCast(x1 - 1), @intCast(y1 + i), &c1);
termbox.tb_put_cell(@intCast(x2), @intCast(y1 + i), &c2);
}
}
if (blank_box) {
const blank = utils.initCell(' ', self.fg, self.bg);
for (0..self.box_height) |y| {
for (0..self.box_width) |x| {
termbox.tb_put_cell(@intCast(x1 + x), @intCast(y1 + y), &blank);
}
}
}
}
pub fn calculateComponentCoordinates(self: TerminalBuffer) struct {
x: u64,
y: u64,
visible_length: u64,
} {
const x = self.box_x + self.margin_box_h + self.labels_max_length + 1;
const y = self.box_y + self.margin_box_v;
const visible_length = self.box_x + self.box_width - self.margin_box_h - x;
return .{
.x = x,
.y = y,
.visible_length = visible_length,
};
}
pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: u64, y: u64) void {
const yc: c_int = @intCast(y);
for (0..text.len) |xx| termbox.tb_change_cell(@intCast(x + xx), yc, text[xx], self.fg, self.bg);
}
pub fn drawCharMultiple(self: TerminalBuffer, char: u8, x: u64, y: u64, length: u64) void {
const yc: c_int = @intCast(y);
const cell = utils.initCell(char, self.fg, self.bg);
for (0..length) |xx| termbox.tb_put_cell(@intCast(x + xx), yc, &cell);
}

@ -0,0 +1,170 @@
const std = @import("std");
const ini = @import("ini");
const enums = @import("../../enums.zig");
const interop = @import("../../interop.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Allocator = std.mem.Allocator;
const EnvironmentList = std.ArrayList(Environment);
const DisplayServer = enums.DisplayServer;
const termbox = interop.termbox;
pub const DESKTOP_ENTRY_MAX_SIZE = 8 * 1024;
const Desktop = @This();
pub const Environment = struct {
has_entry_buffer: bool,
entry_buffer: []u8,
name: []const u8,
xdg_name: []const u8,
cmd: []const u8,
specifier: []const u8,
display_server: DisplayServer,
};
pub const Entry = struct {
Desktop_Entry: struct {
Exec: []const u8,
Name: []const u8,
},
};
allocator: Allocator,
buffer: *TerminalBuffer,
environments: EnvironmentList,
current: u64,
visible_length: u64,
x: u64,
y: u64,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64) !Desktop {
return .{
.allocator = allocator,
.buffer = buffer,
.environments = try EnvironmentList.initCapacity(allocator, max_length),
.current = 0,
.visible_length = 0,
.x = 0,
.y = 0,
};
}
pub fn deinit(self: Desktop) void {
for (self.environments.items) |environment| {
if (environment.has_entry_buffer) self.allocator.free(environment.entry_buffer);
}
self.environments.deinit();
}
pub fn position(self: *Desktop, x: u64, y: u64, visible_length: u64) void {
self.x = x;
self.y = y;
self.visible_length = visible_length;
}
pub fn addEnvironment(self: *Desktop, name: []const u8, cmd: []const u8, display_server: DisplayServer) !void {
try self.environments.append(.{
.has_entry_buffer = false,
.entry_buffer = undefined,
.name = name,
.xdg_name = name, // TODO
.cmd = cmd,
.specifier = switch (display_server) {
.wayland => "wayland",
.x11 => "x11",
else => "other",
},
.display_server = display_server,
});
self.current = self.environments.items.len - 1;
}
pub fn addEnvironmentWithBuffer(self: *Desktop, entry_buffer: []u8, name: []const u8, cmd: []const u8, display_server: DisplayServer) !void {
try self.environments.append(.{
.has_entry_buffer = true,
.entry_buffer = entry_buffer,
.name = name,
.xdg_name = name, // TODO
.cmd = cmd,
.specifier = switch (display_server) {
.wayland => "wayland",
.x11 => "x11",
else => "other",
},
.display_server = display_server,
});
self.current = self.environments.items.len - 1;
}
pub fn crawl(self: *Desktop, path: []const u8, display_server: DisplayServer) !void {
var directory = try std.fs.openDirAbsolute(path, .{});
defer directory.close();
var iterable_directory = try std.fs.openIterableDirAbsolute(path, .{});
defer iterable_directory.close();
var iterator = iterable_directory.iterate();
while (try iterator.next()) |item| {
var file = try directory.openFile(item.name, .{});
defer file.close();
const buffer = try file.readToEndAlloc(self.allocator, DESKTOP_ENTRY_MAX_SIZE);
const entry = try ini.readToStruct(Entry, buffer);
try self.addEnvironmentWithBuffer(buffer, entry.Desktop_Entry.Name, entry.Desktop_Entry.Exec, display_server);
}
}
pub fn handle(self: *Desktop, maybe_event: ?*termbox.tb_event) void {
if (maybe_event) |event| blk: {
if (event.type != termbox.TB_EVENT_KEY) break :blk;
switch (event.key) {
termbox.TB_KEY_ARROW_LEFT, termbox.TB_KEY_CTRL_H => self.goLeft(),
termbox.TB_KEY_ARROW_RIGHT, termbox.TB_KEY_CTRL_L => self.goRight(),
else => {},
}
}
termbox.tb_set_cursor(@intCast(self.x + 2), @intCast(self.y));
}
pub fn draw(self: Desktop) void {
const environment = self.environments.items[self.current];
const length = @min(environment.name.len, self.visible_length - 3);
if (length == 0) return;
const x = self.buffer.box_x + self.buffer.margin_box_h;
const y = self.buffer.box_y + self.buffer.margin_box_v + 2;
self.buffer.drawLabel(environment.specifier, x, y);
termbox.tb_change_cell(@intCast(self.x), @intCast(self.y), '<', self.buffer.fg, self.buffer.bg);
termbox.tb_change_cell(@intCast(self.x + self.visible_length - 1), @intCast(self.y), '>', self.buffer.fg, self.buffer.bg);
self.buffer.drawLabel(environment.name, self.x + 2, self.y);
}
fn goLeft(self: *Desktop) void {
if (self.current == 0) {
self.current = self.environments.items.len - 1;
return;
}
self.current -= 1;
}
fn goRight(self: *Desktop) void {
if (self.current == self.environments.items.len - 1) {
self.current = 0;
return;
}
self.current += 1;
}

@ -0,0 +1,122 @@
const std = @import("std");
const interop = @import("../../interop.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const utils = @import("../utils.zig");
const Allocator = std.mem.Allocator;
const DynamicString = std.ArrayList(u8);
const termbox = interop.termbox;
const Text = @This();
allocator: Allocator,
buffer: *TerminalBuffer,
text: DynamicString,
end: u64,
cursor: u64,
visible_start: u64,
visible_length: u64,
x: u64,
y: u64,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64) !Text {
const text = try DynamicString.initCapacity(allocator, max_length);
return .{
.allocator = allocator,
.buffer = buffer,
.text = text,
.end = 0,
.cursor = 0,
.visible_start = 0,
.visible_length = 0,
.x = 0,
.y = 0,
};
}
pub fn deinit(self: Text) void {
self.text.deinit();
}
pub fn position(self: *Text, x: u64, y: u64, visible_length: u64) void {
self.x = x;
self.y = y;
self.visible_length = visible_length;
}
pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event) !void {
if (maybe_event) |event| blk: {
if (event.type != termbox.TB_EVENT_KEY) break :blk;
switch (event.key) {
termbox.TB_KEY_ARROW_LEFT => self.goLeft(),
termbox.TB_KEY_ARROW_RIGHT => self.goRight(),
termbox.TB_KEY_DELETE => self.delete(),
termbox.TB_KEY_BACKSPACE, termbox.TB_KEY_BACKSPACE2 => self.backspace(),
termbox.TB_KEY_SPACE => try self.write(' '),
else => if (event.ch > 31 and event.ch < 127) try self.write(@intCast(event.ch)),
}
}
termbox.tb_set_cursor(@intCast(self.x + (self.cursor - self.visible_start)), @intCast(self.y));
}
pub fn draw(self: Text) void {
const length = @min(self.text.items.len, self.visible_length);
if (length == 0) return;
const visible_slice = if (self.text.items.len > self.visible_length and self.cursor < self.text.items.len) self.text.items[self.visible_start..(self.visible_length + self.visible_start)] else self.text.items[self.visible_start..];
self.buffer.drawLabel(visible_slice, self.x, self.y);
}
pub fn drawMasked(self: Text, mask: u8) void {
const length = @min(self.text.items.len, self.visible_length - 1);
if (length == 0) return;
self.buffer.drawCharMultiple(mask, self.x, self.y, length);
}
pub fn clear(self: *Text) void {
self.text.clearRetainingCapacity();
self.end = 0;
self.cursor = 0;
self.visible_start = 0;
}
fn goLeft(self: *Text) void {
if (self.cursor == 0) return;
if (self.visible_start > 0) self.visible_start -= 1;
self.cursor -= 1;
}
fn goRight(self: *Text) void {
if (self.cursor >= self.end) return;
if (self.cursor - self.visible_start == self.visible_length - 1) self.visible_start += 1;
self.cursor += 1;
}
fn delete(self: *Text) void {
_ = self.text.orderedRemove(self.cursor);
self.end -= 1;
}
fn backspace(self: *Text) void {
if (self.cursor == 0) return;
self.goLeft();
self.delete();
}
fn write(self: *Text, char: u8) !void {
if (char == 0) return;
try self.text.insert(self.cursor, char);
self.end += 1;
self.goRight();
}

@ -0,0 +1,12 @@
const std = @import("std");
const interop = @import("../interop.zig");
const termbox = interop.termbox;
pub fn initCell(ch: u32, fg: u32, bg: u32) termbox.tb_cell {
var cell = std.mem.zeroes(termbox.tb_cell);
cell.ch = ch;
cell.fg = fg;
cell.bg = bg;
return cell;
}

@ -1,78 +0,0 @@
#include "configator.h"
#include "dragonfail.h"
#include "inputs.h"
#include "config.h"
#include "utils.h"
#include <dirent.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#if defined(__DragonFly__) || defined(__FreeBSD__)
#include <sys/consio.h>
#else // linux
#include <linux/vt.h>
#endif
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);
}

@ -1,13 +0,0 @@
#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);
#endif

@ -1,114 +0,0 @@
const std = @import("std");
const ini = @import("ini");
const main = @import("main.zig");
const config = @import("config.zig");
const interop = @import("interop.zig");
const DESKTOP_ENTRY_MAX_SIZE: usize = 8 * 1024;
const Entry = struct {
Desktop_Entry: struct {
Name: []const u8,
Comment: []const u8,
Exec: []const u8,
Type: []const u8,
DesktopNames: []const u8,
},
};
pub export fn desktop_load(target: *main.c.struct_desktop) void {
// We don't care about desktop environments presence
// because the fallback shell is always available
// so we just dismiss any "throw" for now
var err: c_int = 0;
desktop_crawl(target, config.ly_config.ly.waylandsessions, main.c.DS_WAYLAND) catch {};
if (main.c.dgn_catch() != 0) {
err += 1;
main.c.dgn_reset();
}
desktop_crawl(target, config.ly_config.ly.xsessions, main.c.DS_XORG) catch {};
if (main.c.dgn_catch() != 0) {
err += 1;
main.c.dgn_reset();
}
}
pub fn save(desktop: *main.c.struct_desktop, login: *main.c.struct_text) !void {
if (!config.ly_config.ly.save) {
return;
}
var file = std.fs.openFileAbsolute(config.ly_config.ly.save_file, .{ .mode = .write_only }) catch {
return;
};
defer file.close();
var buffer = try std.fmt.allocPrint(main.allocator, "{s}\n{d}", .{ login.*.text, desktop.*.cur });
defer main.allocator.free(buffer);
try file.writeAll(buffer);
}
pub fn load(desktop: *main.c.struct_desktop, login: *main.c.struct_text) !void {
if (!config.ly_config.ly.load) {
return;
}
var file = std.fs.openFileAbsolute(config.ly_config.ly.save_file, .{}) catch {
return;
};
defer file.close();
var buffer = try main.allocator.alloc(u8, config.ly_config.ly.max_login_len * 2 + 1);
defer main.allocator.free(buffer);
_ = try file.readAll(buffer);
var array = std.mem.splitSequence(u8, buffer, "\n");
login.*.text = try interop.c_str(array.first()); // TODO: Free?
desktop.*.cur = try std.fmt.parseUnsigned(u16, array.next().?, 0);
}
fn desktop_crawl(target: *main.c.struct_desktop, sessions: []const u8, server: main.c.enum_display_server) !void {
var iterable_dir = std.fs.openIterableDirAbsolute(sessions, .{}) catch {
main.c.dgn_throw(main.c.DGN_XSESSIONS_OPEN);
return;
};
defer iterable_dir.close();
var iterator = iterable_dir.iterate();
var dir = std.fs.openDirAbsolute(sessions, .{}) catch {
main.c.dgn_throw(main.c.DGN_XSESSIONS_OPEN);
return;
};
defer dir.close();
while (try iterator.next()) |item| {
if (!std.mem.endsWith(u8, item.name, ".desktop")) {
continue;
}
var file = try dir.openFile(item.name, .{});
defer file.close();
var buffer = try main.allocator.alloc(u8, DESKTOP_ENTRY_MAX_SIZE); // TODO: Free
var length = try file.readAll(buffer);
var entry = try ini.readToStruct(Entry, buffer[0..length]);
// TODO: If it's a wayland session, add " (Wayland)" to its name,
// as long as it doesn't already contain that string
const name = entry.Desktop_Entry.Name;
const exec = entry.Desktop_Entry.Exec;
if (name.len > 0 and exec.len > 0) {
main.c.input_desktop_add(target, (try interop.c_str(name)).ptr, (try interop.c_str(exec)).ptr, server);
}
}
}
Loading…
Cancel
Save