103 Commits

Author SHA1 Message Date
Jaap Aarts
4bf4de816e Reimplement most of the asynchronous handling 2024-08-24 12:47:09 +02:00
Jaap Aarts
c42cf125d3 Fix processing wrt fingerprint login
This also involves a major split of the authentication code, which allows
the fingerprint and password logins to use the same code.
2024-08-20 17:49:49 +02:00
Jaap Aarts
2db48c50c9 Fix fast blinking cursor when no timeout is set 2024-08-20 17:35:45 +02:00
Jaap Aarts
cc15ab35d0 Add fprintd to the pam file 2024-08-20 17:33:14 +02:00
Jaap Aarts
6bf9b928f2 Implement finger-print authentication
Many thanks to Kerigan for providing the original C code.
This commit mainly posts his code to zig.
Additional features provided by me are:
	- automatically checking every 100ms
	- request new login info for new usernames

Co-authored-by: Kerigan <kerigancreighton@gmail.com>
2024-08-20 17:33:12 +02:00
S41G0N
87ceba4de8 Add installation instructions for Gentoo (#685)
* Adding installation section for Gentoo Linux (ly was recently added into Gentoo's official GURU project)

* replace '$ sudo' with '#'
2024-08-12 00:18:53 +02:00
AnErrupTion
b80c276dad Redirect X11 output to file via shell
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-11 18:27:58 +02:00
llc0930
b84158e1c0 Add option to center env name (#683) 2024-08-09 18:44:49 +02:00
DoctorKnowsBetter
c87d5b4e7a Fix OpenRC service (#682)
Co-authored-by: Your Name <you@example.com>
2024-08-07 17:50:55 +02:00
AnErrupTion
028cb9496a Fix session logging for X11 (somewhat)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-07 17:06:14 +02:00
Kian A.
00c94f8ffd Add Farsi Bigclock (#673)
* Change config type for bigclock

* Seprates big clock's hard codes from its logic

* Adds Farsi (fa) to big clock langs

* Minor changes

* Makes requested changes
2024-08-07 16:47:27 +02:00
AnErrupTion
2bd0d0d4f3 Don't mention log file yet in issue template
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-07 13:41:28 +02:00
AnErrupTion
767bdaf166 Add session logging support
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-07 12:03:34 +02:00
AnErrupTion
c033f5bd03 Use hexadecimal numbers for colors in config
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-06 19:38:02 +02:00
AnErrupTion
096b1a7d44 Refactor brightness handling code
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-06 18:40:24 +02:00
AnErrupTion
f0869f0e13 Add numlock set error + handle 2 more errors
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-06 15:32:31 +02:00
AnErrupTion
1ca53f661e Fix drawn position of sleep key hint
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-06 14:44:48 +02:00
AnErrupTion
8562cf4e29 Update feature request template
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-06 11:11:38 +02:00
AnErrupTion
b5b3317dd8 Add login & logout script support
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-06 11:06:20 +02:00
AnErrupTion
2901b408dc Arrange config alphabetically
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-06 10:07:23 +02:00
AnErrupTion
4e40e32f59 Slightly refactor resolution check
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-05 18:33:22 +02:00
AnErrupTion
5e85618730 Conditionally import login_cap.h with pwd.h
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-05 15:16:58 +02:00
AnErrupTion
2c428f5537 Reduce dependence on tb_cell and tb_cell_buffer()
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-05 11:08:51 +02:00
AnErrupTion
071b7a2182 Make all colors u16
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-05 10:41:34 +02:00
AnErrupTion
1075c923ef Make shell login use setup script
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-05 01:03:11 +02:00
AnErrupTion
391f86f602 Delete old setup scripts
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-05 00:59:29 +02:00
AnErrupTion
6fbbb4eff0 Consolidate xsetup.sh & wsetup.sh into one file
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-05 00:58:29 +02:00
AnErrupTion
c7f70ac78f Handle termbox2 outside of authentication
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-04 20:42:00 +02:00
AnErrupTion
ef86ea19ac Update termbox2
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-04 19:40:26 +02:00
AnErrupTion
37061269a4 Remove config.save_file
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-04 17:08:50 +02:00
AnErrupTion
7b9f03176d FreeBSD fixes
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-04 11:04:23 +02:00
AnErrupTion
b73c78d2fb Remove tput dependency & use termbox2 instead
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-03 21:48:38 +02:00
AnErrupTion
0bbe9c78dd Reduce heap allocations a bit
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-03 15:17:02 +02:00
AnErrupTion
b18f29a81a Load logind PAM modules before required ones
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-03 13:39:50 +02:00
AnErrupTion
cab3a7d214 Delete old save file if it's been migrated
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-03 10:34:13 +02:00
AnErrupTion
8995c590eb Fix CLI note & OpenRC service not using config directory option
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-03 08:36:26 +02:00
AnErrupTion
57d5d7497b Fix PAM module order
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-03 07:17:06 +02:00
AnErrupTion
ce3b310e58 Make authentication fail count configurable
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-02 22:31:35 +02:00
AnErrupTion
5d3cd62434 Swap /usr/bin and /usr/sbin in PATH
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-01 17:02:08 +02:00
AnErrupTion
d40ec873a7 Retrieve gettimeofday() from sys/time.h
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-01 14:23:52 +02:00
AnErrupTion
61f3fadfbf Make code more portable + remove mcookie usage
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-01 13:15:54 +02:00
AnErrupTion
1314c57796 Fix config.brightnessctl missing + bugs
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-01 00:54:00 +02:00
AnErrupTion
872b15c0d4 Fix authentication
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-01 00:39:00 +02:00
AnErrupTion
9b4d381f1e Update zigini (fixes an escaping bug)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-01 00:18:46 +02:00
AnErrupTion
bacbacd5fb Merge branch 'master' of https://github.com/fairyglade/ly 2024-07-31 22:35:55 +02:00
AnErrupTion
ee0c00574a Patch resource files + add prefix directory
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 22:35:36 +02:00
ello
6df91cac12 Add an animation timeout (#659)
* added animation timeout

* Updated animation timeout to u12

* updated config comment to reflect the new range for animation timeout
2024-07-31 21:49:19 +02:00
AnErrupTion
598fa6a505 I need to stop doing this
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 14:32:39 +02:00
AnErrupTion
548a411ae2 Remove maximum length config options + don't localize config parse error
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 14:31:28 +02:00
AnErrupTion
48f28e40c4 Fix an oopsie
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 14:02:43 +02:00
AnErrupTion
46f9ddd5fc Add translatable string for config parse error
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 14:01:44 +02:00
AnErrupTion
a393525212 Support multiple info lines in UI
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 13:58:49 +02:00
AnErrupTion
961018e753 Add generic cyclable label & base session component off it
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 13:21:54 +02:00
Joaquín Guerra
8b12ade372 Update spanish translation (#668) 2024-07-31 13:14:29 +02:00
AnErrupTion
a64d7efc69 Make asterisk optional (hides password if so)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 10:34:27 +02:00
AnErrupTion
b592a11fb0 Improve the config migrator
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 09:44:25 +02:00
AnErrupTion
3fedb59fdb Make setting numlock work on BSD + less homebrew interop
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-30 18:38:21 +02:00
AnErrupTion
b1bf89a4cf Only shutdown or restart after deinitializing everything
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-30 11:56:21 +02:00
AnErrupTion
2dec2e0b7f Print data directory deletion in build.zig
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-30 10:54:30 +02:00
AnErrupTion
5f2f21620a Update zigini (fixes incorrect comment parsing)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-30 09:43:56 +02:00
AnErrupTion
48185bdfe0 More verbose output in build.zig
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-29 14:44:56 +02:00
AnErrupTion
f646dddd02 Fix clock & bigclock not updating without input
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-29 14:18:23 +02:00
AnErrupTion
c1f1c8f5c1 Use usize instead of u64 in most places for better 32-bit compatibility
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-29 13:46:14 +02:00
AnErrupTion
ee488ba36e Revert "Redirect stderr to systemd journal in service (#621)"
This reverts commit 3d8d8d67df.

Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-29 12:25:44 +02:00
AnErrupTion
56c210372d Better handle info line messages internally
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-28 23:05:12 +02:00
AnErrupTion
04a0ad3b33 Move Gentoo/OpenRC installation tip to the OpenRC section
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-28 18:17:14 +02:00
AnErrupTion
2dd83b41e8 Incorporate some FreeBSD authentication patches
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-28 13:15:49 +02:00
Moabeat
19d4b195f3 Display error messages differently in info line (#661)
* Add list of error messages to InfoLine.zig

* Change info and error cases according to review

* Add changes from review for width calculation
2024-07-28 13:02:42 +02:00
AnErrupTion
93554d9ba3 Add missing supervise symlink on runit (fixes #610)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-28 11:34:33 +02:00
AnErrupTion
075bf67cef Fix missing brightness localized strings
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-28 00:09:59 +02:00
AnErrupTion
33791a2844 Remove changelog.md
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-28 00:06:08 +02:00
AnErrupTion
8333f7ea77 Update screenshot
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-28 00:05:37 +02:00
AnErrupTion
2bc12549a1 Switch to utmpx
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 23:39:09 +02:00
AnErrupTion
1df890b238 Set PAM_TTY (fixes #248)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 22:31:56 +02:00
AnErrupTion
92c6a38835 Fix ~/.profile not being loaded with Fish
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 22:21:50 +02:00
AnErrupTion
a7e8b55c6e Add COSMIC in the list of tested DEs
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 21:13:55 +02:00
AnErrupTion
b7e1c81ad1 Re-arrange keyring PAM files + integrate with pam_systemd & elogind
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 20:36:45 +02:00
mietinen
dc310ca80e Unlock GNOME Keyring & KWallet on login (#496) 2024-07-27 20:35:24 +02:00
AnErrupTion
da2ea08078 Clarify compile-time dependencies
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 18:38:07 +02:00
AnErrupTion
8c69472065 Allow building without X11 support
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 18:35:58 +02:00
AnErrupTion
0ee28927cf Update French translation
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 18:15:34 +02:00
AnErrupTion
b7934e42d1 Add dinit support
Co-authored-by: Simon Pflaumer <s.pflaumer@murena.io>
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 15:59:02 +02:00
AnErrupTion
e775827c8b Fix possible overflow with TTY ID
Co-authored-by: Kevin Morris <kevr@0cost.org>
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 15:20:42 +02:00
AnErrupTion
9cd58123c4 Fix silly mistake
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 15:02:01 +02:00
AnErrupTion
0cead672da Add s6 support
Co-authored-by: userbook <userbook@devuan>
Co-authored-by: TerminalJunki <158248817+TerminalJunki@users.noreply.github.com>
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 15:00:07 +02:00
AnErrupTion
ce90f91bbf Add issue template for feature requests
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 14:16:40 +02:00
AnErrupTion
23c7175528 Make pre-requisites required
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 14:11:42 +02:00
AnErrupTion
5ea780f806 Continue merge
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 14:09:19 +02:00
AnErrupTion
2eaa473144 Add issue template
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 14:06:28 +02:00
Varun Vasan V
a939b82179 Add configurable default vi mode (#660)
- `vi_default_mode` added
- supports `normal` and `insert`
2024-07-27 11:31:55 +02:00
AnErrupTion
2b0301c1d0 Make runit run and finish scripts executable
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-26 21:58:18 +02:00
AnErrupTion
b84e6c9eed Use default PRNG and retrieve better seed
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-26 21:39:27 +02:00
AnErrupTion
49b8697546 Use octal prefix for file modes in build.zig
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-26 18:54:27 +02:00
0xNiffin
1d7a001a0b Add customizable foreground color to CMatrix (#652)
* Final fixes, adding fg_ini as field

* Capital B changed to lowercase
2024-07-22 16:44:49 +02:00
0xNiffin
3dc1482603 fixed 'std' has no member 'ChildProcess' error when building with zig build command. (#651) 2024-07-17 13:27:54 +02:00
jinzhongjia
e4abf79ad5 Support Zig 0.13.0 and setting default TTY at build time (#632)
* feat: support zig `0.13.0`

* 12 compatible

* update clap

* feat: add default tty to build option

* little fix

* update `zigini`
2024-07-12 22:40:01 +02:00
simonfogliato
5f8fbe381c Fix documentation issue about DOOM animation (#647) 2024-07-12 22:37:18 +02:00
liesen
c6d7d177b7 Fix incorrect shebang in xsetup.sh (#640) 2024-07-12 22:32:46 +02:00
tubi16
dc8d143fac Add brightness control support with brightnessctl (#626)
* Added key binds to control brightness

I added keybinds to control brightness with brightnessctl. 

F5 to decrease brightness.
f6 to increase brightness.

* Update src/main.zig

Co-authored-by: ShiningLea <anerruption@disroot.org>

* added proper keybinds and configs for brightness control

* Update src/main.zig

Co-authored-by: ShiningLea <anerruption@disroot.org>

* code improvement and changes

* updated en.ini

---------

Co-authored-by: ShiningLea <anerruption@disroot.org>
2024-07-04 13:17:56 +02:00
AnErrupTion
cbe7b37564 Fix dest_directory embedded in binary
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-03 09:50:56 +02:00
AnErrupTion
4eb4e15e43 Start v1.1.0 development cycle
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-03 09:44:23 +02:00
jinzhongjia
08f6ce5184 Add minimum_zig_version to build.zig.zon (#630) 2024-07-03 09:22:18 +02:00
アシュ
291e0d836b Various bug fixes and small features (#606)
* Fix stray cursor, integer overflows and other bugs

* check for getenvlist error, shorten code

* fix cascade, set info_line before auth, make code clearer and a bug fix

* Add option to turn on numlock at startup

* Fix setting numlock

* Update build.zig

* Custom info text

* Shift+Tab for previous input

* update changelog and res/config

* Some fixes

* update build.zig

* update build.zig again

* Fix xauth command for some shells and fix building in ReleaseSafe

* Use git describe to get dev version str

* revert change to getLockState (it broke the doom animation)

* add new ly error messages. Only try to make path for pam/exe during install when dest_directory is defined + print warning on error.

* add warning message for workaround
2024-07-02 10:52:38 +02:00
65 changed files with 6064 additions and 5232 deletions

54
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
name: Bug report
description: File a bug report.
title: "[Bug] "
labels: ["bug"]
body:
- type: checkboxes
id: prerequisites
attributes:
label: Pre-requisites
description: By submitting this issue, you agree to have done the following.
options:
- label: I have looked for any other duplicate issues
required: true
- type: input
id: version
attributes:
label: Ly version
description: The output of `ly --version`
placeholder: 1.1.0-dev.12+2b0301c
validations:
required: true
- type: textarea
id: observed
attributes:
label: Observed behavior
description: What happened?
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: What did you expect to happen instead?
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: What **exactly** can someone else do in order to observe the problem you observed?
placeholder: |
1. Authenticate with ...
2. Go to ...
3. Create file ...
4. Log out and log back in
5. Observe error
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant logs
description: Please copy and paste any relevant logs, error messages or any other output. This will be automatically formatted into code, so no need for backticks. Screenshots are accepted if they make life easier for you.
render: shell

22
.github/ISSUE_TEMPLATE/feature.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Feature request
description: Request a new feature or enhancement.
title: "[Feature] "
labels: ["feature"]
body:
- type: checkboxes
id: prerequisites
attributes:
label: Pre-requisites
description: By submitting this issue, you agree to have done the following.
options:
- label: I have looked for any other duplicate issues
required: true
- label: I have confirmed the requested feature doesn't exist in the latest version in development
required: true
- type: textarea
id: wanted
attributes:
label: Wanted behavior
description: What do you want to be added? Describe the behavior clearly.
validations:
required: true

BIN
.github/screenshot.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.idea/ .idea/
zig-cache/ zig-cache/
zig-out/ zig-out/
valgrind.log valgrind.log
.zig-cache

521
build.zig
View File

@@ -1,35 +1,57 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const ly_version = std.SemanticVersion{ .major = 1, .minor = 0, .patch = 0, .build = "dev" }; const PatchMap = std.StringHashMap([]const u8);
pub fn build(b: *std.Build) void { const min_zig_string = "0.12.0";
const data_directory = b.option([]const u8, "data_directory", "Specify a default data directory (default is /etc/ly)"); const current_zig = builtin.zig_version;
// Implementing zig version detection through compile time
comptime {
const min_zig = std.SemanticVersion.parse(min_zig_string) catch unreachable;
if (current_zig.order(min_zig) == .lt) {
@compileError(std.fmt.comptimePrint("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig }));
}
}
const ly_version = std.SemanticVersion{ .major = 1, .minor = 1, .patch = 0 };
var dest_directory: []const u8 = undefined;
var config_directory: []const u8 = undefined;
var prefix_directory: []const u8 = undefined;
var executable_name: []const u8 = undefined;
var default_tty_str: []const u8 = undefined;
const ProgressNode = if (current_zig.minor == 12) *std.Progress.Node else std.Progress.Node;
pub fn build(b: *std.Build) !void {
dest_directory = b.option([]const u8, "dest_directory", "Specify a destination directory for installation") orelse "";
config_directory = b.option([]const u8, "config_directory", "Specify a default config directory (default is /etc). This path gets embedded into the binary") orelse "/etc";
prefix_directory = b.option([]const u8, "prefix_directory", "Specify a default prefix directory (default is /usr)") orelse "/usr";
executable_name = b.option([]const u8, "name", "Specify installed executable file name (default is ly)") orelse "ly";
const bin_directory = try b.allocator.dupe(u8, config_directory);
config_directory = try std.fs.path.join(b.allocator, &[_][]const u8{ dest_directory, config_directory });
const build_options = b.addOptions(); const build_options = b.addOptions();
build_options.addOption([]const u8, "data_directory", data_directory orelse "/etc/ly"); const version_str = try getVersionStr(b, "ly", ly_version);
const version_str = b.fmt("{d}.{d}.{d}-{s}", .{ ly_version.major, ly_version.minor, ly_version.patch, ly_version.build.? }); const enable_x11_support = b.option(bool, "enable_x11_support", "Enable X11 support (default is on)") orelse true;
const default_tty = b.option(u8, "default_tty", "Set the TTY (default is 2)") orelse 2;
default_tty_str = try std.fmt.allocPrint(b.allocator, "{d}", .{default_tty});
build_options.addOption([]const u8, "config_directory", bin_directory);
build_options.addOption([]const u8, "prefix_directory", prefix_directory);
build_options.addOption([]const u8, "version", version_str); build_options.addOption([]const u8, "version", version_str);
build_options.addOption(u8, "tty", default_tty);
build_options.addOption(bool, "enable_x11_support", enable_x11_support);
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const c_args = [_][]const u8{
"-std=c99",
"-pedantic",
"-g",
"-Wall",
"-Wextra",
"-Werror=vla",
"-Wno-unused-parameter",
"-D_DEFAULT_SOURCE",
"-D_POSIX_C_SOURCE=200809L",
"-D_XOPEN_SOURCE",
};
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "ly", .name = "ly",
.root_source_file = .{ .path = "src/main.zig" }, .root_source_file = b.path("src/main.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
@@ -42,18 +64,19 @@ pub fn build(b: *std.Build) void {
const clap = b.dependency("clap", .{ .target = target, .optimize = optimize }); const clap = b.dependency("clap", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("clap", clap.module("clap")); exe.root_module.addImport("clap", clap.module("clap"));
exe.addIncludePath(b.path("include"));
exe.linkSystemLibrary("pam"); exe.linkSystemLibrary("pam");
exe.linkSystemLibrary("xcb"); if (enable_x11_support) exe.linkSystemLibrary("xcb");
exe.linkLibC(); exe.linkLibC();
exe.addIncludePath(.{ .path = "dep/termbox_next/src" }); const translate_c = b.addTranslateC(.{
.root_source_file = b.path("include/termbox2.h"),
exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/input.c" }, .flags = &c_args }); .target = target,
exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/memstream.c" }, .flags = &c_args }); .optimize = optimize,
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 }); translate_c.defineCMacroRaw("TB_IMPL");
exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/termbox.c" }, .flags = &c_args }); const termbox2 = translate_c.addModule("termbox2");
exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/utf8.c" }, .flags = &c_args }); exe.root_module.addImport("termbox2", termbox2);
b.installArtifact(exe); b.installArtifact(exe);
@@ -67,155 +90,387 @@ pub fn build(b: *std.Build) void {
run_step.dependOn(&run_cmd.step); run_step.dependOn(&run_cmd.step);
const installexe_step = b.step("installexe", "Install Ly"); const installexe_step = b.step("installexe", "Install Ly");
installexe_step.makeFn = installexe; installexe_step.makeFn = ExeInstaller(true).make;
installexe_step.dependOn(b.getInstallStep()); installexe_step.dependOn(b.getInstallStep());
const installnoconf_step = b.step("installnoconf", "Install Ly without its configuration file"); const installnoconf_step = b.step("installnoconf", "Install Ly without its configuration file");
installnoconf_step.makeFn = installnoconf; installnoconf_step.makeFn = ExeInstaller(false).make;
installnoconf_step.dependOn(b.getInstallStep()); installnoconf_step.dependOn(b.getInstallStep());
const installsystemd_step = b.step("installsystemd", "Install the Ly systemd service"); const installsystemd_step = b.step("installsystemd", "Install the Ly systemd service");
installsystemd_step.makeFn = installsystemd; installsystemd_step.makeFn = ServiceInstaller(.Systemd).make;
installsystemd_step.dependOn(installexe_step); installsystemd_step.dependOn(installexe_step);
const installopenrc_step = b.step("installopenrc", "Install the Ly openrc service"); const installopenrc_step = b.step("installopenrc", "Install the Ly openrc service");
installopenrc_step.makeFn = installopenrc; installopenrc_step.makeFn = ServiceInstaller(.Openrc).make;
installopenrc_step.dependOn(installexe_step); installopenrc_step.dependOn(installexe_step);
const installrunit_step = b.step("installrunit", "Install the Ly runit service"); const installrunit_step = b.step("installrunit", "Install the Ly runit service");
installrunit_step.makeFn = installrunit; installrunit_step.makeFn = ServiceInstaller(.Runit).make;
installrunit_step.dependOn(installexe_step); installrunit_step.dependOn(installexe_step);
const installs6_step = b.step("installs6", "Install the Ly s6 service");
installs6_step.makeFn = ServiceInstaller(.S6).make;
installs6_step.dependOn(installexe_step);
const installdinit_step = b.step("installdinit", "Install the Ly dinit service");
installdinit_step.makeFn = ServiceInstaller(.Dinit).make;
installdinit_step.dependOn(installexe_step);
const uninstallall_step = b.step("uninstallall", "Uninstall Ly and all services"); const uninstallall_step = b.step("uninstallall", "Uninstall Ly and all services");
uninstallall_step.makeFn = uninstallall; uninstallall_step.makeFn = uninstallall;
} }
fn installexe(self: *std.Build.Step, progress: *std.Progress.Node) !void { pub fn ExeInstaller(install_conf: bool) type {
_ = progress; return struct {
_ = self; pub fn make(step: *std.Build.Step, _: ProgressNode) !void {
try install_ly(step.owner.allocator, install_conf);
try install_ly(true); }
}
fn installnoconf(self: *std.Build.Step, progress: *std.Progress.Node) !void {
_ = progress;
_ = self;
try install_ly(false);
}
fn installsystemd(self: *std.Build.Step, progress: *std.Progress.Node) !void {
_ = progress;
_ = self;
var service_dir = std.fs.openDirAbsolute("/usr/lib/systemd/system", .{}) catch unreachable;
defer service_dir.close();
try std.fs.cwd().copyFile("res/ly.service", service_dir, "ly.service", .{ .override_mode = 644 });
}
fn installopenrc(self: *std.Build.Step, progress: *std.Progress.Node) !void {
_ = progress;
_ = self;
var service_dir = std.fs.openDirAbsolute("/etc/init.d", .{}) catch unreachable;
defer service_dir.close();
try std.fs.cwd().copyFile("res/ly-openrc", service_dir, "ly", .{ .override_mode = 755 });
}
fn installrunit(self: *std.Build.Step, progress: *std.Progress.Node) !void {
_ = progress;
_ = self;
var service_dir = std.fs.openDirAbsolute("/etc/sv", .{}) catch unreachable;
defer service_dir.close();
std.fs.makeDirAbsolute("/etc/sv/ly") catch {
std.debug.print("warn: /etc/sv/ly already exists as a directory.\n", .{});
};
var ly_service_dir = std.fs.openDirAbsolute("/etc/sv/ly", .{}) catch unreachable;
defer ly_service_dir.close();
try std.fs.cwd().copyFile("res/ly-runit-service/conf", ly_service_dir, "conf", .{});
try std.fs.cwd().copyFile("res/ly-runit-service/finish", ly_service_dir, "finish", .{});
try std.fs.cwd().copyFile("res/ly-runit-service/run", ly_service_dir, "run", .{});
}
fn uninstallall(self: *std.Build.Step, progress: *std.Progress.Node) !void {
_ = progress;
_ = self;
try std.fs.deleteTreeAbsolute("/etc/ly");
try std.fs.deleteFileAbsolute("/usr/bin/ly");
try std.fs.deleteFileAbsolute("/etc/pam.d/ly");
std.fs.deleteFileAbsolute("/usr/lib/systemd/system/ly.service") catch {
std.debug.print("warn: systemd service not found.\n", .{});
};
std.fs.deleteFileAbsolute("/etc/init.d/ly") catch {
std.debug.print("warn: openrc service not found.\n", .{});
};
std.fs.deleteTreeAbsolute("/etc/sv/ly") catch {
std.debug.print("warn: runit service not found.\n", .{});
}; };
} }
fn install_ly(install_config: bool) !void { const InitSystem = enum {
std.fs.makeDirAbsolute("/etc/ly") catch { Systemd,
std.debug.print("warn: /etc/ly already exists as a directory.\n", .{}); Openrc,
Runit,
S6,
Dinit,
};
pub fn ServiceInstaller(comptime init_system: InitSystem) type {
return struct {
pub fn make(step: *std.Build.Step, _: ProgressNode) !void {
const allocator = step.owner.allocator;
var patch_map = PatchMap.init(allocator);
defer patch_map.deinit();
try patch_map.put("$DEFAULT_TTY", default_tty_str);
try patch_map.put("$CONFIG_DIRECTORY", config_directory);
try patch_map.put("$PREFIX_DIRECTORY", prefix_directory);
try patch_map.put("$EXECUTABLE_NAME", executable_name);
switch (init_system) {
.Systemd => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/lib/systemd/system" });
std.fs.cwd().makePath(service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
defer service_dir.close();
const patched_service = try patchFile(allocator, "res/ly.service", patch_map);
try installText(patched_service, service_dir, service_path, "ly.service", .{ .mode = 0o644 });
},
.Openrc => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/init.d" });
std.fs.cwd().makePath(service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
defer service_dir.close();
const patched_service = try patchFile(allocator, "res/ly-openrc", patch_map);
try installText(patched_service, service_dir, service_path, executable_name, .{ .mode = 0o755 });
},
.Runit => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/sv/ly" });
std.fs.cwd().makePath(service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
defer service_dir.close();
const supervise_path = try std.fs.path.join(allocator, &[_][]const u8{ service_path, "supervise" });
const patched_conf = try patchFile(allocator, "res/ly-runit-service/conf", patch_map);
try installText(patched_conf, service_dir, service_path, "conf", .{});
try installFile("res/ly-runit-service/finish", service_dir, service_path, "finish", .{ .override_mode = 0o755 });
const patched_run = try patchFile(allocator, "res/ly-runit-service/run", patch_map);
try installText(patched_run, service_dir, service_path, "run", .{ .mode = 0o755 });
try std.fs.cwd().symLink("/run/runit/supervise.ly", supervise_path, .{});
std.debug.print("info: installed symlink /run/runit/supervise.ly\n", .{});
},
.S6 => {
const admin_service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/adminsv/default/contents.d" });
std.fs.cwd().makePath(admin_service_path) catch {};
var admin_service_dir = std.fs.cwd().openDir(admin_service_path, .{}) catch unreachable;
defer admin_service_dir.close();
const file = try admin_service_dir.createFile("ly-srv", .{});
file.close();
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/sv/ly-srv" });
std.fs.cwd().makePath(service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
defer service_dir.close();
const patched_run = try patchFile(allocator, "res/ly-s6/run", patch_map);
try installText(patched_run, service_dir, service_path, "run", .{ .mode = 0o755 });
try installFile("res/ly-s6/type", service_dir, service_path, "type", .{});
},
.Dinit => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/dinit.d" });
std.fs.cwd().makePath(service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
defer service_dir.close();
const patched_service = try patchFile(allocator, "res/ly-dinit", patch_map);
try installText(patched_service, service_dir, service_path, "ly", .{});
},
}
}
};
}
fn install_ly(allocator: std.mem.Allocator, install_config: bool) !void {
const ly_config_directory = try std.fs.path.join(allocator, &[_][]const u8{ config_directory, "/ly" });
std.fs.cwd().makePath(ly_config_directory) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_config_directory});
}; };
std.fs.makeDirAbsolute("/etc/ly/lang") catch { const ly_lang_path = try std.fs.path.join(allocator, &[_][]const u8{ config_directory, "/ly/lang" });
std.debug.print("warn: /etc/ly/lang already exists as a directory.\n", .{}); std.fs.cwd().makePath(ly_lang_path) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{config_directory});
}; };
var current_dir = std.fs.cwd();
{ {
var executable_dir = std.fs.openDirAbsolute("/usr/bin", .{}) catch unreachable; const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" });
if (!std.mem.eql(u8, dest_directory, "")) {
std.fs.cwd().makePath(exe_path) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{exe_path});
};
}
var executable_dir = std.fs.cwd().openDir(exe_path, .{}) catch unreachable;
defer executable_dir.close(); defer executable_dir.close();
try current_dir.copyFile("zig-out/bin/ly", executable_dir, "ly", .{}); try installFile("zig-out/bin/ly", executable_dir, exe_path, executable_name, .{});
} }
{ {
var config_dir = std.fs.openDirAbsolute("/etc/ly", .{}) catch unreachable; var config_dir = std.fs.cwd().openDir(ly_config_directory, .{}) catch unreachable;
defer config_dir.close(); defer config_dir.close();
if (install_config) { if (install_config) {
try current_dir.copyFile("res/config.ini", config_dir, "config.ini", .{}); var patch_map = PatchMap.init(allocator);
defer patch_map.deinit();
try patch_map.put("$DEFAULT_TTY", default_tty_str);
try patch_map.put("$CONFIG_DIRECTORY", config_directory);
try patch_map.put("$PREFIX_DIRECTORY", prefix_directory);
const patched_config = try patchFile(allocator, "res/config.ini", patch_map);
try installText(patched_config, config_dir, ly_config_directory, "config.ini", .{});
}
{
var patch_map = PatchMap.init(allocator);
defer patch_map.deinit();
try patch_map.put("$CONFIG_DIRECTORY", config_directory);
const patched_setup = try patchFile(allocator, "res/setup.sh", patch_map);
try installText(patched_setup, config_dir, ly_config_directory, "setup.sh", .{ .mode = 0o755 });
} }
try current_dir.copyFile("res/xsetup.sh", config_dir, "xsetup.sh", .{});
try current_dir.copyFile("res/wsetup.sh", config_dir, "wsetup.sh", .{});
} }
{ {
var lang_dir = std.fs.openDirAbsolute("/etc/ly/lang", .{}) catch unreachable; var lang_dir = std.fs.cwd().openDir(ly_lang_path, .{}) catch unreachable;
defer lang_dir.close(); defer lang_dir.close();
try current_dir.copyFile("res/lang/cat.ini", lang_dir, "cat.ini", .{}); try installFile("res/lang/cat.ini", lang_dir, ly_lang_path, "cat.ini", .{});
try current_dir.copyFile("res/lang/cs.ini", lang_dir, "cs.ini", .{}); try installFile("res/lang/cs.ini", lang_dir, ly_lang_path, "cs.ini", .{});
try current_dir.copyFile("res/lang/de.ini", lang_dir, "de.ini", .{}); try installFile("res/lang/de.ini", lang_dir, ly_lang_path, "de.ini", .{});
try current_dir.copyFile("res/lang/en.ini", lang_dir, "en.ini", .{}); try installFile("res/lang/en.ini", lang_dir, ly_lang_path, "en.ini", .{});
try current_dir.copyFile("res/lang/es.ini", lang_dir, "es.ini", .{}); try installFile("res/lang/es.ini", lang_dir, ly_lang_path, "es.ini", .{});
try current_dir.copyFile("res/lang/fr.ini", lang_dir, "fr.ini", .{}); try installFile("res/lang/fr.ini", lang_dir, ly_lang_path, "fr.ini", .{});
try current_dir.copyFile("res/lang/it.ini", lang_dir, "it.ini", .{}); try installFile("res/lang/it.ini", lang_dir, ly_lang_path, "it.ini", .{});
try current_dir.copyFile("res/lang/pl.ini", lang_dir, "pl.ini", .{}); try installFile("res/lang/pl.ini", lang_dir, ly_lang_path, "pl.ini", .{});
try current_dir.copyFile("res/lang/pt.ini", lang_dir, "pt.ini", .{}); try installFile("res/lang/pt.ini", lang_dir, ly_lang_path, "pt.ini", .{});
try current_dir.copyFile("res/lang/pt_BR.ini", lang_dir, "pt_BR.ini", .{}); try installFile("res/lang/pt_BR.ini", lang_dir, ly_lang_path, "pt_BR.ini", .{});
try current_dir.copyFile("res/lang/ro.ini", lang_dir, "ro.ini", .{}); try installFile("res/lang/ro.ini", lang_dir, ly_lang_path, "ro.ini", .{});
try current_dir.copyFile("res/lang/ru.ini", lang_dir, "ru.ini", .{}); try installFile("res/lang/ru.ini", lang_dir, ly_lang_path, "ru.ini", .{});
try current_dir.copyFile("res/lang/sr.ini", lang_dir, "sr.ini", .{}); try installFile("res/lang/sr.ini", lang_dir, ly_lang_path, "sr.ini", .{});
try current_dir.copyFile("res/lang/sv.ini", lang_dir, "sv.ini", .{}); try installFile("res/lang/sv.ini", lang_dir, ly_lang_path, "sv.ini", .{});
try current_dir.copyFile("res/lang/tr.ini", lang_dir, "tr.ini", .{}); try installFile("res/lang/tr.ini", lang_dir, ly_lang_path, "tr.ini", .{});
try current_dir.copyFile("res/lang/uk.ini", lang_dir, "uk.ini", .{}); try installFile("res/lang/uk.ini", lang_dir, ly_lang_path, "uk.ini", .{});
} }
{ {
var pam_dir = std.fs.openDirAbsolute("/etc/pam.d", .{}) catch unreachable; const pam_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/pam.d" });
if (!std.mem.eql(u8, dest_directory, "")) {
std.fs.cwd().makePath(pam_path) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{pam_path});
};
}
var pam_dir = std.fs.cwd().openDir(pam_path, .{}) catch unreachable;
defer pam_dir.close(); defer pam_dir.close();
try current_dir.copyFile("res/pam.d/ly", pam_dir, "ly", .{ .override_mode = 644 }); try installFile("res/pam.d/ly", pam_dir, pam_path, "ly", .{ .override_mode = 0o644 });
} }
} }
pub fn uninstallall(step: *std.Build.Step, _: ProgressNode) !void {
const allocator = step.owner.allocator;
try deleteTree(allocator, config_directory, "/ly", "ly config directory not found");
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin/", executable_name });
var success = true;
std.fs.cwd().deleteFile(exe_path) catch {
std.debug.print("warn: ly executable not found\n", .{});
success = false;
};
if (success) std.debug.print("info: deleted {s}\n", .{exe_path});
try deleteFile(allocator, config_directory, "/pam.d/ly", "ly pam file not found");
try deleteFile(allocator, prefix_directory, "/lib/systemd/system/ly.service", "systemd service not found");
try deleteFile(allocator, config_directory, "/init.d/ly", "openrc service not found");
try deleteTree(allocator, config_directory, "/sv/ly", "runit service not found");
try deleteTree(allocator, config_directory, "/s6/sv/ly-srv", "s6 service not found");
try deleteFile(allocator, config_directory, "/s6/adminsv/default/contents.d/ly-srv", "s6 admin service not found");
try deleteFile(allocator, config_directory, "/dinit.d/ly", "dinit service not found");
}
fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion) ![]const u8 {
const version_str = b.fmt("{d}.{d}.{d}", .{ version.major, version.minor, version.patch });
var status: u8 = undefined;
const git_describe_raw = b.runAllowFail(&[_][]const u8{
"git",
"-C",
b.build_root.path orelse ".",
"describe",
"--match",
"*.*.*",
"--tags",
}, &status, .Ignore) catch {
return version_str;
};
var git_describe = std.mem.trim(u8, git_describe_raw, " \n\r");
git_describe = std.mem.trimLeft(u8, git_describe, "v");
switch (std.mem.count(u8, git_describe, "-")) {
0 => {
if (!std.mem.eql(u8, version_str, git_describe)) {
std.debug.print("{s} version '{s}' does not match git tag: '{s}'\n", .{ name, version_str, git_describe });
std.process.exit(1);
}
return version_str;
},
2 => {
// Untagged development build (e.g. 0.10.0-dev.2025+ecf0050a9).
var it = std.mem.splitScalar(u8, git_describe, '-');
const tagged_ancestor = std.mem.trimLeft(u8, it.first(), "v");
const commit_height = it.next().?;
const commit_id = it.next().?;
const ancestor_ver = try std.SemanticVersion.parse(tagged_ancestor);
if (version.order(ancestor_ver) != .gt) {
std.debug.print("{s} version '{}' must be greater than tagged ancestor '{}'\n", .{ name, version, ancestor_ver });
std.process.exit(1);
}
// Check that the commit hash is prefixed with a 'g' (a Git convention).
if (commit_id.len < 1 or commit_id[0] != 'g') {
std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe});
return version_str;
}
// The version is reformatted in accordance with the https://semver.org specification.
return b.fmt("{s}-dev.{s}+{s}", .{ version_str, commit_height, commit_id[1..] });
},
else => {
std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe});
return version_str;
},
}
}
fn installFile(
source_file: []const u8,
destination_directory: std.fs.Dir,
destination_directory_path: []const u8,
destination_file: []const u8,
options: std.fs.Dir.CopyFileOptions,
) !void {
try std.fs.cwd().copyFile(source_file, destination_directory, destination_file, options);
std.debug.print("info: installed {s}/{s}\n", .{ destination_directory_path, destination_file });
}
fn patchFile(allocator: std.mem.Allocator, source_file: []const u8, patch_map: PatchMap) ![]const u8 {
var file = try std.fs.cwd().openFile(source_file, .{});
defer file.close();
const reader = file.reader();
var text = try reader.readAllAlloc(allocator, std.math.maxInt(u16));
var iterator = patch_map.iterator();
while (iterator.next()) |kv| {
const new_text = try std.mem.replaceOwned(u8, allocator, text, kv.key_ptr.*, kv.value_ptr.*);
allocator.free(text);
text = new_text;
}
return text;
}
fn installText(
text: []const u8,
destination_directory: std.fs.Dir,
destination_directory_path: []const u8,
destination_file: []const u8,
options: std.fs.File.CreateFlags,
) !void {
var file = try destination_directory.createFile(destination_file, options);
defer file.close();
const writer = file.writer();
try writer.writeAll(text);
std.debug.print("info: installed {s}/{s}\n", .{ destination_directory_path, destination_file });
}
fn deleteFile(
allocator: std.mem.Allocator,
prefix: []const u8,
file: []const u8,
warning: []const u8,
) !void {
const path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix, file });
std.fs.cwd().deleteFile(path) catch |err| {
if (err == error.FileNotFound) {
std.debug.print("warn: {s}\n", .{warning});
return;
}
return err;
};
std.debug.print("info: deleted {s}\n", .{path});
}
fn deleteTree(
allocator: std.mem.Allocator,
prefix: []const u8,
directory: []const u8,
warning: []const u8,
) !void {
const path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix, directory });
var dir = std.fs.cwd().openDir(path, .{}) catch |err| {
if (err == error.FileNotFound) {
std.debug.print("warn: {s}\n", .{warning});
return;
}
return err;
};
dir.close();
try std.fs.cwd().deleteTree(path);
std.debug.print("info: deleted {s}\n", .{path});
}

View File

@@ -1,14 +1,15 @@
.{ .{
.name = "ly", .name = "ly",
.version = "1.0.0", .version = "1.0.0",
.minimum_zig_version = "0.12.0",
.dependencies = .{ .dependencies = .{
.clap = .{ .clap = .{
.url = "https://github.com/Hejsil/zig-clap/archive/8c98e6404b22aafc0184e999d8f068b81cc22fa1.tar.gz", .url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.9.1.tar.gz",
.hash = "122014e73fd712190e109950837b97f6143f02d7e2b6986e1db70b6f4aadb5ba6a0d", .hash = "122062d301a203d003547b414237229b09a7980095061697349f8bef41be9c30266b",
}, },
.zigini = .{ .zigini = .{
.url = "https://github.com/Kawaii-Ash/zigini/archive/ce1f322482099db058f5d9fdd05fbfa255d79723.tar.gz", .url = "https://github.com/Kawaii-Ash/zigini/archive/0bba97a12582928e097f4074cc746c43351ba4c8.tar.gz",
.hash = "1220e7a99793a0430e0a7c0b938cb3c98321035bc297e21cd0e2413cf740b4923b9f", .hash = "12209b971367b4066d40ecad4728e6fdffc4cc4f19356d424c2de57f5b69ac7a619a",
}, },
}, },
.paths = .{""}, .paths = .{""},

View File

@@ -1,47 +0,0 @@
# Zig Rewrite (Version 1.0.0)
## Config Options
res/config.ini contains all of the available config options and their default values.
### Additions
+ `border_fg` has been introduced to change the color of the borders.
+ `term_restore_cursor_cmd` should restore the cursor to it's usual state.
+ `vi_mode` to enable vi keybindings.
+ `sleep_key` and `sleep_cmd`.
Note: `sleep_cmd` is unset by default, meaning it's hidden and has no effect.
### Changes
+ xinitrc can be set to null to hide it.
+ `blank_password` has been renamed to `clear_password`.
+ `save_file` has been deprecated and will be removed in a future version.
### Removals
+ `wayland_specifier` has been removed.
## Save File
The save file is now in .ini format and stored in the same directory as the config.
Older save files will be migrated to the new format.
Example:
```ini
user = ash
session_index = 0
```
## Misc
+ Display server name added next to selected session.
+ getty@tty2 has been added as a conflict in res/ly.service, so if it is running, ly should still be able to start.
+ `XDG_CURRENT_DESKTOP` is now set by ly.
+ LANG is no longer set by ly.
+ X Server PID is fetched from /tmp/X{d}.lock to be able to kill the process since it detaches.
+ Non .desktop files are now ignored in sessions directory.
+ PAM auth is now done in a child process. (Fixes some issues with logging out and back in).
+ When ly receives SIGTERM, the terminal is now cleared and existing child processes are cleaned up.

View File

@@ -1,7 +0,0 @@
bin
obj
src/demo/*.o
src/demo/keyboard
src/demo/output
src/demo/paint
src/demo/truecolor

View File

@@ -1,19 +0,0 @@
Copyright (C) 2010-2013 nsf <no.smile.face@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,57 +0,0 @@
# Termbox
[Termbox](https://github.com/nsf/termbox)
was a promising Text User Interface (TUI) library.
Unfortunately, its original author
[changed his mind](https://github.com/nsf/termbox/issues/37#issuecomment-261075481)
about consoles and despite the
[community's efforts](https://github.com/nsf/termbox/pull/104#issuecomment-300308156)
to keep the library's development going, preferred to let it die. Before it happened,
[some people](https://wiki.musl-libc.org/alternatives.html)
already noticed the robustness of the initial architecture
[became compromised](https://github.com/nsf/termbox/commit/66c3f91b14e24510319bce6b5cc2fecf8cf5abff#commitcomment-3790714)
in a nonsensical refactoring frenzy. Now, the author refuses to merge features
like true-color support, invoking some
[spurious correlations](https://github.com/nsf/termbox/pull/104#issuecomment-300292223)
we will discuss no further.
## The new Termbox-next
This fork was made to restore the codebase to its original quality (before
[66c3f91](https://github.com/nsf/termbox/commit/66c3f91b14e24510319bce6b5cc2fecf8cf5abff))
while providing all the functionnalities of the current implementation.
This was achieved by branching at
[a2e217f](https://github.com/nsf/termbox/commit/a2e217f0fb97e6bbd589136ea3945f9d5a123d81)
and cherry-picking all the commits up to
[d63b83a](https://github.com/nsf/termbox/commit/d63b83af04e0fd6da836bb8f37e5cec72a1dc95a)
if they weren't harmful.
## Changes
A lot of things changed during the process:
- *waf*, the original build system, was completely removed from the
project and replaced by make.
- anything related to python was removed as well
## Getting started
Termbox's interface only consists of 12 functions:
```
tb_init() // initialization
tb_shutdown() // shutdown
tb_width() // width of the terminal screen
tb_height() // height of the terminal screen
tb_clear() // clear buffer
tb_present() // sync internal buffer with terminal
tb_put_cell()
tb_change_cell()
tb_blit() // drawing functions
tb_select_input_mode() // change input mode
tb_peek_event() // peek a keyboard event
tb_poll_event() // wait for a keyboard event
```
See src/termbox.h header file for full detail.
## TL;DR
`make` to build a static version of the lib under bin/termbox.a
`cd src/demo && make` to build the example programs in src/demo/

View File

@@ -1,827 +0,0 @@
#include <assert.h>
#include <stdint.h>
#include <stdarg.h>
#include <stdio.h>
#include "termbox.h"
struct key
{
unsigned char x;
unsigned char y;
uint32_t ch;
};
#define STOP {0,0,0}
struct key K_ESC[] = {{1, 1, 'E'}, {2, 1, 'S'}, {3, 1, 'C'}, STOP};
struct key K_F1[] = {{6, 1, 'F'}, {7, 1, '1'}, STOP};
struct key K_F2[] = {{9, 1, 'F'}, {10, 1, '2'}, STOP};
struct key K_F3[] = {{12, 1, 'F'}, {13, 1, '3'}, STOP};
struct key K_F4[] = {{15, 1, 'F'}, {16, 1, '4'}, STOP};
struct key K_F5[] = {{19, 1, 'F'}, {20, 1, '5'}, STOP};
struct key K_F6[] = {{22, 1, 'F'}, {23, 1, '6'}, STOP};
struct key K_F7[] = {{25, 1, 'F'}, {26, 1, '7'}, STOP};
struct key K_F8[] = {{28, 1, 'F'}, {29, 1, '8'}, STOP};
struct key K_F9[] = {{33, 1, 'F'}, {34, 1, '9'}, STOP};
struct key K_F10[] = {{36, 1, 'F'}, {37, 1, '1'}, {38, 1, '0'}, STOP};
struct key K_F11[] = {{40, 1, 'F'}, {41, 1, '1'}, {42, 1, '1'}, STOP};
struct key K_F12[] = {{44, 1, 'F'}, {45, 1, '1'}, {46, 1, '2'}, STOP};
struct key K_PRN[] = {{50, 1, 'P'}, {51, 1, 'R'}, {52, 1, 'N'}, STOP};
struct key K_SCR[] = {{54, 1, 'S'}, {55, 1, 'C'}, {56, 1, 'R'}, STOP};
struct key K_BRK[] = {{58, 1, 'B'}, {59, 1, 'R'}, {60, 1, 'K'}, STOP};
struct key K_LED1[] = {{66, 1, '-'}, STOP};
struct key K_LED2[] = {{70, 1, '-'}, STOP};
struct key K_LED3[] = {{74, 1, '-'}, STOP};
struct key K_TILDE[] = {{1, 4, '`'}, STOP};
struct key K_TILDE_SHIFT[] = {{1, 4, '~'}, STOP};
struct key K_1[] = {{4, 4, '1'}, STOP};
struct key K_1_SHIFT[] = {{4, 4, '!'}, STOP};
struct key K_2[] = {{7, 4, '2'}, STOP};
struct key K_2_SHIFT[] = {{7, 4, '@'}, STOP};
struct key K_3[] = {{10, 4, '3'}, STOP};
struct key K_3_SHIFT[] = {{10, 4, '#'}, STOP};
struct key K_4[] = {{13, 4, '4'}, STOP};
struct key K_4_SHIFT[] = {{13, 4, '$'}, STOP};
struct key K_5[] = {{16, 4, '5'}, STOP};
struct key K_5_SHIFT[] = {{16, 4, '%'}, STOP};
struct key K_6[] = {{19, 4, '6'}, STOP};
struct key K_6_SHIFT[] = {{19, 4, '^'}, STOP};
struct key K_7[] = {{22, 4, '7'}, STOP};
struct key K_7_SHIFT[] = {{22, 4, '&'}, STOP};
struct key K_8[] = {{25, 4, '8'}, STOP};
struct key K_8_SHIFT[] = {{25, 4, '*'}, STOP};
struct key K_9[] = {{28, 4, '9'}, STOP};
struct key K_9_SHIFT[] = {{28, 4, '('}, STOP};
struct key K_0[] = {{31, 4, '0'}, STOP};
struct key K_0_SHIFT[] = {{31, 4, ')'}, STOP};
struct key K_MINUS[] = {{34, 4, '-'}, STOP};
struct key K_MINUS_SHIFT[] = {{34, 4, '_'}, STOP};
struct key K_EQUALS[] = {{37, 4, '='}, STOP};
struct key K_EQUALS_SHIFT[] = {{37, 4, '+'}, STOP};
struct key K_BACKSLASH[] = {{40, 4, '\\'}, STOP};
struct key K_BACKSLASH_SHIFT[] = {{40, 4, '|'}, STOP};
struct key K_BACKSPACE[] = {{44, 4, 0x2190}, {45, 4, 0x2500}, {46, 4, 0x2500}, STOP};
struct key K_INS[] = {{50, 4, 'I'}, {51, 4, 'N'}, {52, 4, 'S'}, STOP};
struct key K_HOM[] = {{54, 4, 'H'}, {55, 4, 'O'}, {56, 4, 'M'}, STOP};
struct key K_PGU[] = {{58, 4, 'P'}, {59, 4, 'G'}, {60, 4, 'U'}, STOP};
struct key K_K_NUMLOCK[] = {{65, 4, 'N'}, STOP};
struct key K_K_SLASH[] = {{68, 4, '/'}, STOP};
struct key K_K_STAR[] = {{71, 4, '*'}, STOP};
struct key K_K_MINUS[] = {{74, 4, '-'}, STOP};
struct key K_TAB[] = {{1, 6, 'T'}, {2, 6, 'A'}, {3, 6, 'B'}, STOP};
struct key K_q[] = {{6, 6, 'q'}, STOP};
struct key K_Q[] = {{6, 6, 'Q'}, STOP};
struct key K_w[] = {{9, 6, 'w'}, STOP};
struct key K_W[] = {{9, 6, 'W'}, STOP};
struct key K_e[] = {{12, 6, 'e'}, STOP};
struct key K_E[] = {{12, 6, 'E'}, STOP};
struct key K_r[] = {{15, 6, 'r'}, STOP};
struct key K_R[] = {{15, 6, 'R'}, STOP};
struct key K_t[] = {{18, 6, 't'}, STOP};
struct key K_T[] = {{18, 6, 'T'}, STOP};
struct key K_y[] = {{21, 6, 'y'}, STOP};
struct key K_Y[] = {{21, 6, 'Y'}, STOP};
struct key K_u[] = {{24, 6, 'u'}, STOP};
struct key K_U[] = {{24, 6, 'U'}, STOP};
struct key K_i[] = {{27, 6, 'i'}, STOP};
struct key K_I[] = {{27, 6, 'I'}, STOP};
struct key K_o[] = {{30, 6, 'o'}, STOP};
struct key K_O[] = {{30, 6, 'O'}, STOP};
struct key K_p[] = {{33, 6, 'p'}, STOP};
struct key K_P[] = {{33, 6, 'P'}, STOP};
struct key K_LSQB[] = {{36, 6, '['}, STOP};
struct key K_LCUB[] = {{36, 6, '{'}, STOP};
struct key K_RSQB[] = {{39, 6, ']'}, STOP};
struct key K_RCUB[] = {{39, 6, '}'}, STOP};
struct key K_ENTER[] =
{
{43, 6, 0x2591}, {44, 6, 0x2591}, {45, 6, 0x2591}, {46, 6, 0x2591},
{43, 7, 0x2591}, {44, 7, 0x2591}, {45, 7, 0x21B5}, {46, 7, 0x2591},
{41, 8, 0x2591}, {42, 8, 0x2591}, {43, 8, 0x2591}, {44, 8, 0x2591},
{45, 8, 0x2591}, {46, 8, 0x2591}, STOP
};
struct key K_DEL[] = {{50, 6, 'D'}, {51, 6, 'E'}, {52, 6, 'L'}, STOP};
struct key K_END[] = {{54, 6, 'E'}, {55, 6, 'N'}, {56, 6, 'D'}, STOP};
struct key K_PGD[] = {{58, 6, 'P'}, {59, 6, 'G'}, {60, 6, 'D'}, STOP};
struct key K_K_7[] = {{65, 6, '7'}, STOP};
struct key K_K_8[] = {{68, 6, '8'}, STOP};
struct key K_K_9[] = {{71, 6, '9'}, STOP};
struct key K_K_PLUS[] = {{74, 6, ' '}, {74, 7, '+'}, {74, 8, ' '}, STOP};
struct key K_CAPS[] = {{1, 8, 'C'}, {2, 8, 'A'}, {3, 8, 'P'}, {4, 8, 'S'}, STOP};
struct key K_a[] = {{7, 8, 'a'}, STOP};
struct key K_A[] = {{7, 8, 'A'}, STOP};
struct key K_s[] = {{10, 8, 's'}, STOP};
struct key K_S[] = {{10, 8, 'S'}, STOP};
struct key K_d[] = {{13, 8, 'd'}, STOP};
struct key K_D[] = {{13, 8, 'D'}, STOP};
struct key K_f[] = {{16, 8, 'f'}, STOP};
struct key K_F[] = {{16, 8, 'F'}, STOP};
struct key K_g[] = {{19, 8, 'g'}, STOP};
struct key K_G[] = {{19, 8, 'G'}, STOP};
struct key K_h[] = {{22, 8, 'h'}, STOP};
struct key K_H[] = {{22, 8, 'H'}, STOP};
struct key K_j[] = {{25, 8, 'j'}, STOP};
struct key K_J[] = {{25, 8, 'J'}, STOP};
struct key K_k[] = {{28, 8, 'k'}, STOP};
struct key K_K[] = {{28, 8, 'K'}, STOP};
struct key K_l[] = {{31, 8, 'l'}, STOP};
struct key K_L[] = {{31, 8, 'L'}, STOP};
struct key K_SEMICOLON[] = {{34, 8, ';'}, STOP};
struct key K_PARENTHESIS[] = {{34, 8, ':'}, STOP};
struct key K_QUOTE[] = {{37, 8, '\''}, STOP};
struct key K_DOUBLEQUOTE[] = {{37, 8, '"'}, STOP};
struct key K_K_4[] = {{65, 8, '4'}, STOP};
struct key K_K_5[] = {{68, 8, '5'}, STOP};
struct key K_K_6[] = {{71, 8, '6'}, STOP};
struct key K_LSHIFT[] = {{1, 10, 'S'}, {2, 10, 'H'}, {3, 10, 'I'}, {4, 10, 'F'}, {5, 10, 'T'}, STOP};
struct key K_z[] = {{9, 10, 'z'}, STOP};
struct key K_Z[] = {{9, 10, 'Z'}, STOP};
struct key K_x[] = {{12, 10, 'x'}, STOP};
struct key K_X[] = {{12, 10, 'X'}, STOP};
struct key K_c[] = {{15, 10, 'c'}, STOP};
struct key K_C[] = {{15, 10, 'C'}, STOP};
struct key K_v[] = {{18, 10, 'v'}, STOP};
struct key K_V[] = {{18, 10, 'V'}, STOP};
struct key K_b[] = {{21, 10, 'b'}, STOP};
struct key K_B[] = {{21, 10, 'B'}, STOP};
struct key K_n[] = {{24, 10, 'n'}, STOP};
struct key K_N[] = {{24, 10, 'N'}, STOP};
struct key K_m[] = {{27, 10, 'm'}, STOP};
struct key K_M[] = {{27, 10, 'M'}, STOP};
struct key K_COMMA[] = {{30, 10, ','}, STOP};
struct key K_LANB[] = {{30, 10, '<'}, STOP};
struct key K_PERIOD[] = {{33, 10, '.'}, STOP};
struct key K_RANB[] = {{33, 10, '>'}, STOP};
struct key K_SLASH[] = {{36, 10, '/'}, STOP};
struct key K_QUESTION[] = {{36, 10, '?'}, STOP};
struct key K_RSHIFT[] = {{42, 10, 'S'}, {43, 10, 'H'}, {44, 10, 'I'}, {45, 10, 'F'}, {46, 10, 'T'}, STOP};
struct key K_ARROW_UP[] = {{54, 10, '('}, {55, 10, 0x2191}, {56, 10, ')'}, STOP};
struct key K_K_1[] = {{65, 10, '1'}, STOP};
struct key K_K_2[] = {{68, 10, '2'}, STOP};
struct key K_K_3[] = {{71, 10, '3'}, STOP};
struct key K_K_ENTER[] = {{74, 10, 0x2591}, {74, 11, 0x2591}, {74, 12, 0x2591}, STOP};
struct key K_LCTRL[] = {{1, 12, 'C'}, {2, 12, 'T'}, {3, 12, 'R'}, {4, 12, 'L'}, STOP};
struct key K_LWIN[] = {{6, 12, 'W'}, {7, 12, 'I'}, {8, 12, 'N'}, STOP};
struct key K_LALT[] = {{10, 12, 'A'}, {11, 12, 'L'}, {12, 12, 'T'}, STOP};
struct key K_SPACE[] =
{
{14, 12, ' '}, {15, 12, ' '}, {16, 12, ' '}, {17, 12, ' '}, {18, 12, ' '},
{19, 12, 'S'}, {20, 12, 'P'}, {21, 12, 'A'}, {22, 12, 'C'}, {23, 12, 'E'},
{24, 12, ' '}, {25, 12, ' '}, {26, 12, ' '}, {27, 12, ' '}, {28, 12, ' '},
STOP
};
struct key K_RALT[] = {{30, 12, 'A'}, {31, 12, 'L'}, {32, 12, 'T'}, STOP};
struct key K_RWIN[] = {{34, 12, 'W'}, {35, 12, 'I'}, {36, 12, 'N'}, STOP};
struct key K_RPROP[] = {{38, 12, 'P'}, {39, 12, 'R'}, {40, 12, 'O'}, {41, 12, 'P'}, STOP};
struct key K_RCTRL[] = {{43, 12, 'C'}, {44, 12, 'T'}, {45, 12, 'R'}, {46, 12, 'L'}, STOP};
struct key K_ARROW_LEFT[] = {{50, 12, '('}, {51, 12, 0x2190}, {52, 12, ')'}, STOP};
struct key K_ARROW_DOWN[] = {{54, 12, '('}, {55, 12, 0x2193}, {56, 12, ')'}, STOP};
struct key K_ARROW_RIGHT[] = {{58, 12, '('}, {59, 12, 0x2192}, {60, 12, ')'}, STOP};
struct key K_K_0[] = {{65, 12, ' '}, {66, 12, '0'}, {67, 12, ' '}, {68, 12, ' '}, STOP};
struct key K_K_PERIOD[] = {{71, 12, '.'}, STOP};
struct combo
{
struct key* keys[6];
};
struct combo combos[] =
{
{{K_TILDE, K_2, K_LCTRL, K_RCTRL, 0}},
{{K_A, K_LCTRL, K_RCTRL, 0}},
{{K_B, K_LCTRL, K_RCTRL, 0}},
{{K_C, K_LCTRL, K_RCTRL, 0}},
{{K_D, K_LCTRL, K_RCTRL, 0}},
{{K_E, K_LCTRL, K_RCTRL, 0}},
{{K_F, K_LCTRL, K_RCTRL, 0}},
{{K_G, K_LCTRL, K_RCTRL, 0}},
{{K_H, K_BACKSPACE, K_LCTRL, K_RCTRL, 0}},
{{K_I, K_TAB, K_LCTRL, K_RCTRL, 0}},
{{K_J, K_LCTRL, K_RCTRL, 0}},
{{K_K, K_LCTRL, K_RCTRL, 0}},
{{K_L, K_LCTRL, K_RCTRL, 0}},
{{K_M, K_ENTER, K_K_ENTER, K_LCTRL, K_RCTRL, 0}},
{{K_N, K_LCTRL, K_RCTRL, 0}},
{{K_O, K_LCTRL, K_RCTRL, 0}},
{{K_P, K_LCTRL, K_RCTRL, 0}},
{{K_Q, K_LCTRL, K_RCTRL, 0}},
{{K_R, K_LCTRL, K_RCTRL, 0}},
{{K_S, K_LCTRL, K_RCTRL, 0}},
{{K_T, K_LCTRL, K_RCTRL, 0}},
{{K_U, K_LCTRL, K_RCTRL, 0}},
{{K_V, K_LCTRL, K_RCTRL, 0}},
{{K_W, K_LCTRL, K_RCTRL, 0}},
{{K_X, K_LCTRL, K_RCTRL, 0}},
{{K_Y, K_LCTRL, K_RCTRL, 0}},
{{K_Z, K_LCTRL, K_RCTRL, 0}},
{{K_LSQB, K_ESC, K_3, K_LCTRL, K_RCTRL, 0}},
{{K_4, K_BACKSLASH, K_LCTRL, K_RCTRL, 0}},
{{K_RSQB, K_5, K_LCTRL, K_RCTRL, 0}},
{{K_6, K_LCTRL, K_RCTRL, 0}},
{{K_7, K_SLASH, K_MINUS_SHIFT, K_LCTRL, K_RCTRL, 0}},
{{K_SPACE, 0}},
{{K_1_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
{{K_DOUBLEQUOTE, K_LSHIFT, K_RSHIFT, 0}},
{{K_3_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
{{K_4_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
{{K_5_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
{{K_7_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
{{K_QUOTE, 0}},
{{K_9_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
{{K_0_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
{{K_8_SHIFT, K_K_STAR, K_LSHIFT, K_RSHIFT, 0}},
{{K_EQUALS_SHIFT, K_K_PLUS, K_LSHIFT, K_RSHIFT, 0}},
{{K_COMMA, 0}},
{{K_MINUS, K_K_MINUS, 0}},
{{K_PERIOD, K_K_PERIOD, 0}},
{{K_SLASH, K_K_SLASH, 0}},
{{K_0, K_K_0, 0}},
{{K_1, K_K_1, 0}},
{{K_2, K_K_2, 0}},
{{K_3, K_K_3, 0}},
{{K_4, K_K_4, 0}},
{{K_5, K_K_5, 0}},
{{K_6, K_K_6, 0}},
{{K_7, K_K_7, 0}},
{{K_8, K_K_8, 0}},
{{K_9, K_K_9, 0}},
{{K_PARENTHESIS, K_LSHIFT, K_RSHIFT, 0}},
{{K_SEMICOLON, 0}},
{{K_LANB, K_LSHIFT, K_RSHIFT, 0}},
{{K_EQUALS, 0}},
{{K_RANB, K_LSHIFT, K_RSHIFT, 0}},
{{K_QUESTION, K_LSHIFT, K_RSHIFT, 0}},
{{K_2_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
{{K_A, K_LSHIFT, K_RSHIFT, 0}},
{{K_B, K_LSHIFT, K_RSHIFT, 0}},
{{K_C, K_LSHIFT, K_RSHIFT, 0}},
{{K_D, K_LSHIFT, K_RSHIFT, 0}},
{{K_E, K_LSHIFT, K_RSHIFT, 0}},
{{K_F, K_LSHIFT, K_RSHIFT, 0}},
{{K_G, K_LSHIFT, K_RSHIFT, 0}},
{{K_H, K_LSHIFT, K_RSHIFT, 0}},
{{K_I, K_LSHIFT, K_RSHIFT, 0}},
{{K_J, K_LSHIFT, K_RSHIFT, 0}},
{{K_K, K_LSHIFT, K_RSHIFT, 0}},
{{K_L, K_LSHIFT, K_RSHIFT, 0}},
{{K_M, K_LSHIFT, K_RSHIFT, 0}},
{{K_N, K_LSHIFT, K_RSHIFT, 0}},
{{K_O, K_LSHIFT, K_RSHIFT, 0}},
{{K_P, K_LSHIFT, K_RSHIFT, 0}},
{{K_Q, K_LSHIFT, K_RSHIFT, 0}},
{{K_R, K_LSHIFT, K_RSHIFT, 0}},
{{K_S, K_LSHIFT, K_RSHIFT, 0}},
{{K_T, K_LSHIFT, K_RSHIFT, 0}},
{{K_U, K_LSHIFT, K_RSHIFT, 0}},
{{K_V, K_LSHIFT, K_RSHIFT, 0}},
{{K_W, K_LSHIFT, K_RSHIFT, 0}},
{{K_X, K_LSHIFT, K_RSHIFT, 0}},
{{K_Y, K_LSHIFT, K_RSHIFT, 0}},
{{K_Z, K_LSHIFT, K_RSHIFT, 0}},
{{K_LSQB, 0}},
{{K_BACKSLASH, 0}},
{{K_RSQB, 0}},
{{K_6_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
{{K_MINUS_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
{{K_TILDE, 0}},
{{K_a, 0}},
{{K_b, 0}},
{{K_c, 0}},
{{K_d, 0}},
{{K_e, 0}},
{{K_f, 0}},
{{K_g, 0}},
{{K_h, 0}},
{{K_i, 0}},
{{K_j, 0}},
{{K_k, 0}},
{{K_l, 0}},
{{K_m, 0}},
{{K_n, 0}},
{{K_o, 0}},
{{K_p, 0}},
{{K_q, 0}},
{{K_r, 0}},
{{K_s, 0}},
{{K_t, 0}},
{{K_u, 0}},
{{K_v, 0}},
{{K_w, 0}},
{{K_x, 0}},
{{K_y, 0}},
{{K_z, 0}},
{{K_LCUB, K_LSHIFT, K_RSHIFT, 0}},
{{K_BACKSLASH_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
{{K_RCUB, K_LSHIFT, K_RSHIFT, 0}},
{{K_TILDE_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
{{K_8, K_BACKSPACE, K_LCTRL, K_RCTRL, 0}}
};
struct combo func_combos[] =
{
{{K_F1, 0}},
{{K_F2, 0}},
{{K_F3, 0}},
{{K_F4, 0}},
{{K_F5, 0}},
{{K_F6, 0}},
{{K_F7, 0}},
{{K_F8, 0}},
{{K_F9, 0}},
{{K_F10, 0}},
{{K_F11, 0}},
{{K_F12, 0}},
{{K_INS, 0}},
{{K_DEL, 0}},
{{K_HOM, 0}},
{{K_END, 0}},
{{K_PGU, 0}},
{{K_PGD, 0}},
{{K_ARROW_UP, 0}},
{{K_ARROW_DOWN, 0}},
{{K_ARROW_LEFT, 0}},
{{K_ARROW_RIGHT, 0}}
};
void print_tb(const char* str, int x, int y, uint32_t fg, uint32_t bg)
{
while (*str)
{
uint32_t uni;
str += utf8_char_to_unicode(&uni, str);
tb_change_cell(x, y, uni, fg, bg);
x++;
}
}
void printf_tb(int x, int y, uint32_t fg, uint32_t bg, const char* fmt, ...)
{
char buf[4096];
va_list vl;
va_start(vl, fmt);
vsnprintf(buf, sizeof(buf), fmt, vl);
va_end(vl);
print_tb(buf, x, y, fg, bg);
}
void draw_key(struct key* k, uint32_t fg, uint32_t bg)
{
while (k->x)
{
tb_change_cell(k->x + 2, k->y + 4, k->ch, fg, bg);
k++;
}
}
void draw_keyboard()
{
int i;
tb_change_cell(0, 0, 0x250C, TB_WHITE, TB_DEFAULT);
tb_change_cell(79, 0, 0x2510, TB_WHITE, TB_DEFAULT);
tb_change_cell(0, 23, 0x2514, TB_WHITE, TB_DEFAULT);
tb_change_cell(79, 23, 0x2518, TB_WHITE, TB_DEFAULT);
for (i = 1; i < 79; ++i)
{
tb_change_cell(i, 0, 0x2500, TB_WHITE, TB_DEFAULT);
tb_change_cell(i, 23, 0x2500, TB_WHITE, TB_DEFAULT);
tb_change_cell(i, 17, 0x2500, TB_WHITE, TB_DEFAULT);
tb_change_cell(i, 4, 0x2500, TB_WHITE, TB_DEFAULT);
}
for (i = 1; i < 23; ++i)
{
tb_change_cell(0, i, 0x2502, TB_WHITE, TB_DEFAULT);
tb_change_cell(79, i, 0x2502, TB_WHITE, TB_DEFAULT);
}
tb_change_cell(0, 17, 0x251C, TB_WHITE, TB_DEFAULT);
tb_change_cell(79, 17, 0x2524, TB_WHITE, TB_DEFAULT);
tb_change_cell(0, 4, 0x251C, TB_WHITE, TB_DEFAULT);
tb_change_cell(79, 4, 0x2524, TB_WHITE, TB_DEFAULT);
for (i = 5; i < 17; ++i)
{
tb_change_cell(1, i, 0x2588, TB_YELLOW, TB_YELLOW);
tb_change_cell(78, i, 0x2588, TB_YELLOW, TB_YELLOW);
}
draw_key(K_ESC, TB_WHITE, TB_BLUE);
draw_key(K_F1, TB_WHITE, TB_BLUE);
draw_key(K_F2, TB_WHITE, TB_BLUE);
draw_key(K_F3, TB_WHITE, TB_BLUE);
draw_key(K_F4, TB_WHITE, TB_BLUE);
draw_key(K_F5, TB_WHITE, TB_BLUE);
draw_key(K_F6, TB_WHITE, TB_BLUE);
draw_key(K_F7, TB_WHITE, TB_BLUE);
draw_key(K_F8, TB_WHITE, TB_BLUE);
draw_key(K_F9, TB_WHITE, TB_BLUE);
draw_key(K_F10, TB_WHITE, TB_BLUE);
draw_key(K_F11, TB_WHITE, TB_BLUE);
draw_key(K_F12, TB_WHITE, TB_BLUE);
draw_key(K_PRN, TB_WHITE, TB_BLUE);
draw_key(K_SCR, TB_WHITE, TB_BLUE);
draw_key(K_BRK, TB_WHITE, TB_BLUE);
draw_key(K_LED1, TB_WHITE, TB_BLUE);
draw_key(K_LED2, TB_WHITE, TB_BLUE);
draw_key(K_LED3, TB_WHITE, TB_BLUE);
draw_key(K_TILDE, TB_WHITE, TB_BLUE);
draw_key(K_1, TB_WHITE, TB_BLUE);
draw_key(K_2, TB_WHITE, TB_BLUE);
draw_key(K_3, TB_WHITE, TB_BLUE);
draw_key(K_4, TB_WHITE, TB_BLUE);
draw_key(K_5, TB_WHITE, TB_BLUE);
draw_key(K_6, TB_WHITE, TB_BLUE);
draw_key(K_7, TB_WHITE, TB_BLUE);
draw_key(K_8, TB_WHITE, TB_BLUE);
draw_key(K_9, TB_WHITE, TB_BLUE);
draw_key(K_0, TB_WHITE, TB_BLUE);
draw_key(K_MINUS, TB_WHITE, TB_BLUE);
draw_key(K_EQUALS, TB_WHITE, TB_BLUE);
draw_key(K_BACKSLASH, TB_WHITE, TB_BLUE);
draw_key(K_BACKSPACE, TB_WHITE, TB_BLUE);
draw_key(K_INS, TB_WHITE, TB_BLUE);
draw_key(K_HOM, TB_WHITE, TB_BLUE);
draw_key(K_PGU, TB_WHITE, TB_BLUE);
draw_key(K_K_NUMLOCK, TB_WHITE, TB_BLUE);
draw_key(K_K_SLASH, TB_WHITE, TB_BLUE);
draw_key(K_K_STAR, TB_WHITE, TB_BLUE);
draw_key(K_K_MINUS, TB_WHITE, TB_BLUE);
draw_key(K_TAB, TB_WHITE, TB_BLUE);
draw_key(K_q, TB_WHITE, TB_BLUE);
draw_key(K_w, TB_WHITE, TB_BLUE);
draw_key(K_e, TB_WHITE, TB_BLUE);
draw_key(K_r, TB_WHITE, TB_BLUE);
draw_key(K_t, TB_WHITE, TB_BLUE);
draw_key(K_y, TB_WHITE, TB_BLUE);
draw_key(K_u, TB_WHITE, TB_BLUE);
draw_key(K_i, TB_WHITE, TB_BLUE);
draw_key(K_o, TB_WHITE, TB_BLUE);
draw_key(K_p, TB_WHITE, TB_BLUE);
draw_key(K_LSQB, TB_WHITE, TB_BLUE);
draw_key(K_RSQB, TB_WHITE, TB_BLUE);
draw_key(K_ENTER, TB_WHITE, TB_BLUE);
draw_key(K_DEL, TB_WHITE, TB_BLUE);
draw_key(K_END, TB_WHITE, TB_BLUE);
draw_key(K_PGD, TB_WHITE, TB_BLUE);
draw_key(K_K_7, TB_WHITE, TB_BLUE);
draw_key(K_K_8, TB_WHITE, TB_BLUE);
draw_key(K_K_9, TB_WHITE, TB_BLUE);
draw_key(K_K_PLUS, TB_WHITE, TB_BLUE);
draw_key(K_CAPS, TB_WHITE, TB_BLUE);
draw_key(K_a, TB_WHITE, TB_BLUE);
draw_key(K_s, TB_WHITE, TB_BLUE);
draw_key(K_d, TB_WHITE, TB_BLUE);
draw_key(K_f, TB_WHITE, TB_BLUE);
draw_key(K_g, TB_WHITE, TB_BLUE);
draw_key(K_h, TB_WHITE, TB_BLUE);
draw_key(K_j, TB_WHITE, TB_BLUE);
draw_key(K_k, TB_WHITE, TB_BLUE);
draw_key(K_l, TB_WHITE, TB_BLUE);
draw_key(K_SEMICOLON, TB_WHITE, TB_BLUE);
draw_key(K_QUOTE, TB_WHITE, TB_BLUE);
draw_key(K_K_4, TB_WHITE, TB_BLUE);
draw_key(K_K_5, TB_WHITE, TB_BLUE);
draw_key(K_K_6, TB_WHITE, TB_BLUE);
draw_key(K_LSHIFT, TB_WHITE, TB_BLUE);
draw_key(K_z, TB_WHITE, TB_BLUE);
draw_key(K_x, TB_WHITE, TB_BLUE);
draw_key(K_c, TB_WHITE, TB_BLUE);
draw_key(K_v, TB_WHITE, TB_BLUE);
draw_key(K_b, TB_WHITE, TB_BLUE);
draw_key(K_n, TB_WHITE, TB_BLUE);
draw_key(K_m, TB_WHITE, TB_BLUE);
draw_key(K_COMMA, TB_WHITE, TB_BLUE);
draw_key(K_PERIOD, TB_WHITE, TB_BLUE);
draw_key(K_SLASH, TB_WHITE, TB_BLUE);
draw_key(K_RSHIFT, TB_WHITE, TB_BLUE);
draw_key(K_ARROW_UP, TB_WHITE, TB_BLUE);
draw_key(K_K_1, TB_WHITE, TB_BLUE);
draw_key(K_K_2, TB_WHITE, TB_BLUE);
draw_key(K_K_3, TB_WHITE, TB_BLUE);
draw_key(K_K_ENTER, TB_WHITE, TB_BLUE);
draw_key(K_LCTRL, TB_WHITE, TB_BLUE);
draw_key(K_LWIN, TB_WHITE, TB_BLUE);
draw_key(K_LALT, TB_WHITE, TB_BLUE);
draw_key(K_SPACE, TB_WHITE, TB_BLUE);
draw_key(K_RCTRL, TB_WHITE, TB_BLUE);
draw_key(K_RPROP, TB_WHITE, TB_BLUE);
draw_key(K_RWIN, TB_WHITE, TB_BLUE);
draw_key(K_RALT, TB_WHITE, TB_BLUE);
draw_key(K_ARROW_LEFT, TB_WHITE, TB_BLUE);
draw_key(K_ARROW_DOWN, TB_WHITE, TB_BLUE);
draw_key(K_ARROW_RIGHT, TB_WHITE, TB_BLUE);
draw_key(K_K_0, TB_WHITE, TB_BLUE);
draw_key(K_K_PERIOD, TB_WHITE, TB_BLUE);
printf_tb(33, 1, TB_MAGENTA | TB_BOLD, TB_DEFAULT, "Keyboard demo!");
printf_tb(21, 2, TB_MAGENTA, TB_DEFAULT,
"(press CTRL+X and then CTRL+Q to exit)");
printf_tb(15, 3, TB_MAGENTA, TB_DEFAULT,
"(press CTRL+X and then CTRL+C to change input mode)");
int inputmode = tb_select_input_mode(0);
char inputmode_str[64];
if (inputmode & TB_INPUT_ESC)
{
sprintf(inputmode_str, "TB_INPUT_ESC");
}
if (inputmode & TB_INPUT_ALT)
{
sprintf(inputmode_str, "TB_INPUT_ALT");
}
if (inputmode & TB_INPUT_MOUSE)
{
sprintf(inputmode_str + 12, " | TB_INPUT_MOUSE");
}
printf_tb(3, 18, TB_WHITE, TB_DEFAULT, "Input mode: %s", inputmode_str);
}
const char* funckeymap(int k)
{
static const char* fcmap[] =
{
"CTRL+2, CTRL+~",
"CTRL+A",
"CTRL+B",
"CTRL+C",
"CTRL+D",
"CTRL+E",
"CTRL+F",
"CTRL+G",
"CTRL+H, BACKSPACE",
"CTRL+I, TAB",
"CTRL+J",
"CTRL+K",
"CTRL+L",
"CTRL+M, ENTER",
"CTRL+N",
"CTRL+O",
"CTRL+P",
"CTRL+Q",
"CTRL+R",
"CTRL+S",
"CTRL+T",
"CTRL+U",
"CTRL+V",
"CTRL+W",
"CTRL+X",
"CTRL+Y",
"CTRL+Z",
"CTRL+3, ESC, CTRL+[",
"CTRL+4, CTRL+\\",
"CTRL+5, CTRL+]",
"CTRL+6",
"CTRL+7, CTRL+/, CTRL+_",
"SPACE"
};
static const char* fkmap[] =
{
"F1",
"F2",
"F3",
"F4",
"F5",
"F6",
"F7",
"F8",
"F9",
"F10",
"F11",
"F12",
"INSERT",
"DELETE",
"HOME",
"END",
"PGUP",
"PGDN",
"ARROW UP",
"ARROW DOWN",
"ARROW LEFT",
"ARROW RIGHT"
};
if (k == TB_KEY_CTRL_8)
{
return "CTRL+8, BACKSPACE 2"; // 0x7F
}
else if (k >= TB_KEY_ARROW_RIGHT && k <= 0xFFFF)
{
return fkmap[0xFFFF - k];
}
else if (k <= TB_KEY_SPACE)
{
return fcmap[k];
}
return "UNKNOWN";
}
void pretty_print_press(struct tb_event* ev)
{
char buf[7];
buf[utf8_unicode_to_char(buf, ev->ch)] = '\0';
printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Key: ");
printf_tb(8, 19, TB_YELLOW, TB_DEFAULT, "decimal: %d", ev->key);
printf_tb(8, 20, TB_GREEN, TB_DEFAULT, "hex: 0x%X", ev->key);
printf_tb(8, 21, TB_CYAN, TB_DEFAULT, "octal: 0%o", ev->key);
printf_tb(8, 22, TB_RED, TB_DEFAULT, "string: %s", funckeymap(ev->key));
printf_tb(54, 19, TB_WHITE, TB_DEFAULT, "Char: ");
printf_tb(60, 19, TB_YELLOW, TB_DEFAULT, "decimal: %d", ev->ch);
printf_tb(60, 20, TB_GREEN, TB_DEFAULT, "hex: 0x%X", ev->ch);
printf_tb(60, 21, TB_CYAN, TB_DEFAULT, "octal: 0%o", ev->ch);
printf_tb(60, 22, TB_RED, TB_DEFAULT, "string: %s", buf);
printf_tb(54, 18, TB_WHITE, TB_DEFAULT, "Modifier: %s",
(ev->mod) ? "TB_MOD_ALT" : "none");
}
void pretty_print_resize(struct tb_event* ev)
{
printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Resize event: %d x %d", ev->w, ev->h);
}
int counter = 0;
void pretty_print_mouse(struct tb_event* ev)
{
printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Mouse event: %d x %d", ev->x, ev->y);
char* btn = "";
switch (ev->key)
{
case TB_KEY_MOUSE_LEFT:
btn = "MouseLeft: %d";
break;
case TB_KEY_MOUSE_MIDDLE:
btn = "MouseMiddle: %d";
break;
case TB_KEY_MOUSE_RIGHT:
btn = "MouseRight: %d";
break;
case TB_KEY_MOUSE_WHEEL_UP:
btn = "MouseWheelUp: %d";
break;
case TB_KEY_MOUSE_WHEEL_DOWN:
btn = "MouseWheelDown: %d";
break;
case TB_KEY_MOUSE_RELEASE:
btn = "MouseRelease: %d";
}
counter++;
printf_tb(43, 19, TB_WHITE, TB_DEFAULT, "Key: ");
printf_tb(48, 19, TB_YELLOW, TB_DEFAULT, btn, counter);
}
void dispatch_press(struct tb_event* ev)
{
if (ev->mod & TB_MOD_ALT)
{
draw_key(K_LALT, TB_WHITE, TB_RED);
draw_key(K_RALT, TB_WHITE, TB_RED);
}
struct combo* k = 0;
if (ev->key >= TB_KEY_ARROW_RIGHT)
{
k = &func_combos[0xFFFF - ev->key];
}
else if (ev->ch < 128)
{
if (ev->ch == 0 && ev->key < 128)
{
k = &combos[ev->key];
}
else
{
k = &combos[ev->ch];
}
}
if (!k)
{
return;
}
struct key** keys = k->keys;
while (*keys)
{
draw_key(*keys, TB_WHITE, TB_RED);
keys++;
}
}
int main(int argc, char** argv)
{
(void) argc;
(void) argv;
int ret;
ret = tb_init();
if (ret)
{
fprintf(stderr, "tb_init() failed with error code %d\n", ret);
return 1;
}
tb_select_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE);
struct tb_event ev;
tb_clear();
draw_keyboard();
tb_present();
int inputmode = 0;
int ctrlxpressed = 0;
while (tb_poll_event(&ev))
{
switch (ev.type)
{
case TB_EVENT_KEY:
if (ev.key == TB_KEY_CTRL_Q && ctrlxpressed)
{
tb_shutdown();
return 0;
}
if (ev.key == TB_KEY_CTRL_C && ctrlxpressed)
{
static int chmap[] =
{
TB_INPUT_ESC | TB_INPUT_MOUSE, // 101
TB_INPUT_ALT | TB_INPUT_MOUSE, // 110
TB_INPUT_ESC, // 001
TB_INPUT_ALT, // 010
};
inputmode++;
if (inputmode >= 4)
{
inputmode = 0;
}
tb_select_input_mode(chmap[inputmode]);
}
if (ev.key == TB_KEY_CTRL_X)
{
ctrlxpressed = 1;
}
else
{
ctrlxpressed = 0;
}
tb_clear();
draw_keyboard();
dispatch_press(&ev);
pretty_print_press(&ev);
tb_present();
break;
case TB_EVENT_RESIZE:
tb_clear();
draw_keyboard();
pretty_print_resize(&ev);
tb_present();
break;
case TB_EVENT_MOUSE:
tb_clear();
draw_keyboard();
pretty_print_mouse(&ev);
tb_present();
break;
default:
break;
}
}
tb_shutdown();
return 0;
}

View File

@@ -1,30 +0,0 @@
CC=gcc
FLAGS=-std=c99 -pedantic -Wall -Werror -g -static
INCL=-I../
BIND=../../bin
%.o:%.c
@echo "building source object $@"
@$(CC) $(INCL) $(FLAGS) -c -o $@ $<
all:keyboard output paint truecolor
keyboard:keyboard.o
@echo "compiling $@"
@$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a
output:output.o
@echo "compiling $@"
@$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a
paint:paint.o
@echo "compiling $@"
@$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a
truecolor:truecolor.o
@echo "compiling $@"
@$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a
clean:
@echo "cleaning workspace"
@rm -rf *.o keyboard output paint truecolor

View File

@@ -1,156 +0,0 @@
#include <stdio.h>
#include <string.h>
#include "../termbox.h"
static const char chars[] = "nnnnnnnnnbbbbbbbbbuuuuuuuuuBBBBBBBBB";
static const uint32_t all_attrs[] =
{
0,
TB_BOLD,
TB_UNDERLINE,
TB_BOLD | TB_UNDERLINE,
};
static int next_char(int current)
{
current++;
if (!chars[current])
{
current = 0;
}
return current;
}
static void draw_line(int x, int y, uint32_t bg)
{
int a, c;
int current_char = 0;
for (a = 0; a < 4; a++)
{
for (c = TB_DEFAULT; c <= TB_WHITE; c++)
{
uint32_t fg = all_attrs[a] | c;
tb_change_cell(x, y, chars[current_char], fg, bg);
current_char = next_char(current_char);
x++;
}
}
}
static void print_combinations_table(int sx, int sy, const uint32_t* attrs,
int attrs_n)
{
int i, c;
for (i = 0; i < attrs_n; i++)
{
for (c = TB_DEFAULT; c <= TB_WHITE; c++)
{
uint32_t bg = attrs[i] | c;
draw_line(sx, sy, bg);
sy++;
}
}
}
static void draw_all()
{
tb_clear();
tb_select_output_mode(TB_OUTPUT_NORMAL);
static const uint32_t col1[] = {0, TB_BOLD};
static const uint32_t col2[] = {TB_REVERSE};
print_combinations_table(1, 1, col1, 2);
print_combinations_table(2 + strlen(chars), 1, col2, 1);
tb_present();
tb_select_output_mode(TB_OUTPUT_GRAYSCALE);
int c, x, y;
for (x = 0, y = 23; x < 24; ++x)
{
tb_change_cell(x, y, '@', x, 0);
tb_change_cell(x + 25, y, ' ', 0, x);
}
tb_present();
tb_select_output_mode(TB_OUTPUT_216);
y++;
for (c = 0, x = 0; c < 216; ++c, ++x)
{
if (!(x % 24))
{
x = 0;
++y;
}
tb_change_cell(x, y, '@', c, 0);
tb_change_cell(x + 25, y, ' ', 0, c);
}
tb_present();
tb_select_output_mode(TB_OUTPUT_256);
y++;
for (c = 0, x = 0; c < 256; ++c, ++x)
{
if (!(x % 24))
{
x = 0;
++y;
}
tb_change_cell(x, y, '+', c | ((y & 1) ? TB_UNDERLINE : 0), 0);
tb_change_cell(x + 25, y, ' ', 0, c);
}
tb_present();
}
int main(int argc, char** argv)
{
(void)argc;
(void)argv;
int ret = tb_init();
if (ret)
{
fprintf(stderr, "tb_init() failed with error code %d\n", ret);
return 1;
}
draw_all();
struct tb_event ev;
while (tb_poll_event(&ev))
{
switch (ev.type)
{
case TB_EVENT_KEY:
switch (ev.key)
{
case TB_KEY_ESC:
goto done;
break;
}
break;
case TB_EVENT_RESIZE:
draw_all();
break;
}
}
done:
tb_shutdown();
return 0;
}

View File

@@ -1,183 +0,0 @@
#include "../termbox.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
static int curCol = 0;
static int curRune = 0;
static struct tb_cell* backbuf;
static int bbw = 0, bbh = 0;
static const uint32_t runes[] =
{
0x20, // ' '
0x2591, // '░'
0x2592, // '▒'
0x2593, // '▓'
0x2588, // '█'
};
#define len(a) (sizeof(a)/sizeof(a[0]))
static const uint32_t colors[] =
{
TB_BLACK,
TB_RED,
TB_GREEN,
TB_YELLOW,
TB_BLUE,
TB_MAGENTA,
TB_CYAN,
TB_WHITE,
};
void updateAndDrawButtons(int* current, int x, int y, int mx, int my, int n,
void (*attrFunc)(int, uint32_t*, uint32_t*, uint32_t*))
{
int lx = x;
int ly = y;
for (int i = 0; i < n; i++)
{
if (lx <= mx && mx <= lx + 3 && ly <= my && my <= ly + 1)
{
*current = i;
}
uint32_t r;
uint32_t fg, bg;
(*attrFunc)(i, &r, &fg, &bg);
tb_change_cell(lx + 0, ly + 0, r, fg, bg);
tb_change_cell(lx + 1, ly + 0, r, fg, bg);
tb_change_cell(lx + 2, ly + 0, r, fg, bg);
tb_change_cell(lx + 3, ly + 0, r, fg, bg);
tb_change_cell(lx + 0, ly + 1, r, fg, bg);
tb_change_cell(lx + 1, ly + 1, r, fg, bg);
tb_change_cell(lx + 2, ly + 1, r, fg, bg);
tb_change_cell(lx + 3, ly + 1, r, fg, bg);
lx += 4;
}
lx = x;
ly = y;
for (int i = 0; i < n; i++)
{
if (*current == i)
{
uint32_t fg = TB_RED | TB_BOLD;
uint32_t bg = TB_DEFAULT;
tb_change_cell(lx + 0, ly + 2, '^', fg, bg);
tb_change_cell(lx + 1, ly + 2, '^', fg, bg);
tb_change_cell(lx + 2, ly + 2, '^', fg, bg);
tb_change_cell(lx + 3, ly + 2, '^', fg, bg);
}
lx += 4;
}
}
void runeAttrFunc(int i, uint32_t* r, uint32_t* fg, uint32_t* bg)
{
*r = runes[i];
*fg = TB_DEFAULT;
*bg = TB_DEFAULT;
}
void colorAttrFunc(int i, uint32_t* r, uint32_t* fg, uint32_t* bg)
{
*r = ' ';
*fg = TB_DEFAULT;
*bg = colors[i];
}
void updateAndRedrawAll(int mx, int my)
{
tb_clear();
if (mx != -1 && my != -1)
{
backbuf[bbw * my + mx].ch = runes[curRune];
backbuf[bbw * my + mx].fg = colors[curCol];
}
memcpy(tb_cell_buffer(), backbuf, sizeof(struct tb_cell)*bbw * bbh);
int h = tb_height();
updateAndDrawButtons(&curRune, 0, 0, mx, my, len(runes), runeAttrFunc);
updateAndDrawButtons(&curCol, 0, h - 3, mx, my, len(colors), colorAttrFunc);
tb_present();
}
void reallocBackBuffer(int w, int h)
{
bbw = w;
bbh = h;
if (backbuf)
{
free(backbuf);
}
backbuf = calloc(sizeof(struct tb_cell), w * h);
}
int main(int argv, char** argc)
{
(void)argc;
(void)argv;
int code = tb_init();
if (code < 0)
{
fprintf(stderr, "termbox init failed, code: %d\n", code);
return -1;
}
tb_select_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE);
int w = tb_width();
int h = tb_height();
reallocBackBuffer(w, h);
updateAndRedrawAll(-1, -1);
for (;;)
{
struct tb_event ev;
int mx = -1;
int my = -1;
int t = tb_poll_event(&ev);
if (t == -1)
{
tb_shutdown();
fprintf(stderr, "termbox poll event error\n");
return -1;
}
switch (t)
{
case TB_EVENT_KEY:
if (ev.key == TB_KEY_ESC)
{
tb_shutdown();
return 0;
}
break;
case TB_EVENT_MOUSE:
if (ev.key == TB_KEY_MOUSE_LEFT)
{
mx = ev.x;
my = ev.y;
}
break;
case TB_EVENT_RESIZE:
reallocBackBuffer(ev.w, ev.h);
break;
}
updateAndRedrawAll(mx, my);
}
}

View File

@@ -1,69 +0,0 @@
#include "termbox.h"
int main()
{
tb_init();
tb_select_output_mode(TB_OUTPUT_TRUECOLOR);
int w = tb_width();
int h = tb_height();
uint32_t bg = 0x000000, fg = 0x000000;
tb_clear();
int z = 0;
for (int y = 1; y < h; y++)
{
for (int x = 1; x < w; x++)
{
uint32_t ch;
utf8_char_to_unicode(&ch, "x");
fg = 0;
if (z % 2 == 0)
{
fg |= TB_BOLD;
}
if (z % 3 == 0)
{
fg |= TB_UNDERLINE;
}
if (z % 5 == 0)
{
fg |= TB_REVERSE;
}
tb_change_cell(x, y, ch, fg, bg);
bg += 0x000101;
z++;
}
bg += 0x080000;
if (bg > 0xFFFFFF)
{
bg = 0;
}
}
tb_present();
while (1)
{
struct tb_event ev;
int t = tb_poll_event(&ev);
if (t == -1)
{
break;
}
if (t == TB_EVENT_KEY)
{
break;
}
}
tb_shutdown();
return 0;
}

View File

@@ -1,319 +0,0 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include "term.h"
#define BUFFER_SIZE_MAX 16
// if s1 starts with s2 returns 1, else 0
static int starts_with(const char* s1, const char* s2)
{
// nice huh?
while (*s2)
{
if (*s1++ != *s2++)
{
return 0;
}
}
return 1;
}
static int parse_mouse_event(struct tb_event* event, const char* buf, int len)
{
if ((len >= 6) && starts_with(buf, "\033[M"))
{
// X10 mouse encoding, the simplest one
// \033 [ M Cb Cx Cy
int b = buf[3] - 32;
switch (b & 3)
{
case 0:
if ((b & 64) != 0)
{
event->key = TB_KEY_MOUSE_WHEEL_UP;
}
else
{
event->key = TB_KEY_MOUSE_LEFT;
}
break;
case 1:
if ((b & 64) != 0)
{
event->key = TB_KEY_MOUSE_WHEEL_DOWN;
}
else
{
event->key = TB_KEY_MOUSE_MIDDLE;
}
break;
case 2:
event->key = TB_KEY_MOUSE_RIGHT;
break;
case 3:
event->key = TB_KEY_MOUSE_RELEASE;
break;
default:
return -6;
}
event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default
if ((b & 32) != 0)
{
event->mod |= TB_MOD_MOTION;
}
// the coord is 1,1 for upper left
event->x = (uint8_t)buf[4] - 1 - 32;
event->y = (uint8_t)buf[5] - 1 - 32;
return 6;
}
else if (starts_with(buf, "\033[<") || starts_with(buf, "\033["))
{
// xterm 1006 extended mode or urxvt 1015 extended mode
// xterm: \033 [ < Cb ; Cx ; Cy (M or m)
// urxvt: \033 [ Cb ; Cx ; Cy M
int i, mi = -1, starti = -1;
int isM, isU, s1 = -1, s2 = -1;
int n1 = 0, n2 = 0, n3 = 0;
for (i = 0; i < len; i++)
{
// We search the first (s1) and the last (s2) ';'
if (buf[i] == ';')
{
if (s1 == -1)
{
s1 = i;
}
s2 = i;
}
// We search for the first 'm' or 'M'
if ((buf[i] == 'm' || buf[i] == 'M') && mi == -1)
{
mi = i;
break;
}
}
if (mi == -1)
{
return 0;
}
// whether it's a capital M or not
isM = (buf[mi] == 'M');
if (buf[2] == '<')
{
isU = 0;
starti = 3;
}
else
{
isU = 1;
starti = 2;
}
if (s1 == -1 || s2 == -1 || s1 == s2)
{
return 0;
}
n1 = strtoul(&buf[starti], NULL, 10);
n2 = strtoul(&buf[s1 + 1], NULL, 10);
n3 = strtoul(&buf[s2 + 1], NULL, 10);
if (isU)
{
n1 -= 32;
}
switch (n1 & 3)
{
case 0:
if ((n1 & 64) != 0)
{
event->key = TB_KEY_MOUSE_WHEEL_UP;
}
else
{
event->key = TB_KEY_MOUSE_LEFT;
}
break;
case 1:
if ((n1 & 64) != 0)
{
event->key = TB_KEY_MOUSE_WHEEL_DOWN;
}
else
{
event->key = TB_KEY_MOUSE_MIDDLE;
}
break;
case 2:
event->key = TB_KEY_MOUSE_RIGHT;
break;
case 3:
event->key = TB_KEY_MOUSE_RELEASE;
break;
default:
return mi + 1;
}
if (!isM)
{
// on xterm mouse release is signaled by lowercase m
event->key = TB_KEY_MOUSE_RELEASE;
}
event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default
if ((n1 & 32) != 0)
{
event->mod |= TB_MOD_MOTION;
}
event->x = (uint8_t)n2 - 1;
event->y = (uint8_t)n3 - 1;
return mi + 1;
}
return 0;
}
// convert escape sequence to event, and return consumed bytes on success (failure == 0)
static int parse_escape_seq(struct tb_event* event, const char* buf, int len)
{
int mouse_parsed = parse_mouse_event(event, buf, len);
if (mouse_parsed != 0)
{
return mouse_parsed;
}
// it's pretty simple here, find 'starts_with' match and return success, else return failure
int i;
for (i = 0; keys[i]; i++)
{
if (starts_with(buf, keys[i]))
{
event->ch = 0;
event->key = 0xFFFF - i;
return strlen(keys[i]);
}
}
return 0;
}
bool extract_event(struct tb_event* event, struct ringbuffer* inbuf,
int inputmode)
{
char buf[BUFFER_SIZE_MAX + 1];
int nbytes = ringbuffer_data_size(inbuf);
if (nbytes > BUFFER_SIZE_MAX)
{
nbytes = BUFFER_SIZE_MAX;
}
if (nbytes == 0)
{
return false;
}
ringbuffer_read(inbuf, buf, nbytes);
buf[nbytes] = '\0';
if (buf[0] == '\033')
{
int n = parse_escape_seq(event, buf, nbytes);
if (n != 0)
{
bool success = true;
if (n < 0)
{
success = false;
n = -n;
}
ringbuffer_pop(inbuf, 0, n);
return success;
}
else
{
// it's not escape sequence, then it's ALT or ESC, check inputmode
if (inputmode & TB_INPUT_ESC)
{
// if we're in escape mode, fill ESC event, pop buffer, return success
event->ch = 0;
event->key = TB_KEY_ESC;
event->mod = 0;
ringbuffer_pop(inbuf, 0, 1);
return true;
}
else if (inputmode & TB_INPUT_ALT)
{
// if we're in alt mode, set ALT modifier to event and redo parsing
event->mod = TB_MOD_ALT;
ringbuffer_pop(inbuf, 0, 1);
return extract_event(event, inbuf, inputmode);
}
assert(!"never got here");
}
}
// if we're here, this is not an escape sequence and not an alt sequence
// so, it's a FUNCTIONAL KEY or a UNICODE character
// first of all check if it's a functional key*/
if ((unsigned char)buf[0] <= TB_KEY_SPACE ||
(unsigned char)buf[0] == TB_KEY_BACKSPACE2)
{
// fill event, pop buffer, return success
event->ch = 0;
event->key = (uint16_t)buf[0];
ringbuffer_pop(inbuf, 0, 1);
return true;
}
// feh... we got utf8 here
// check if there is all bytes
if (nbytes >= utf8_char_length(buf[0]))
{
// everything ok, fill event, pop buffer, return success
utf8_char_to_unicode(&event->ch, buf);
event->key = 0;
ringbuffer_pop(inbuf, 0, utf8_char_length(buf[0]));
return true;
}
return false;
}

View File

@@ -1,36 +0,0 @@
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include "memstream.h"
void memstream_init(struct memstream* s, int fd, void* buffer, size_t len)
{
s->file = fd;
s->data = buffer;
s->pos = 0;
s->capa = len;
}
void memstream_flush(struct memstream* s)
{
write(s->file, s->data, s->pos);
s->pos = 0;
}
void memstream_write(struct memstream* s, void* source, size_t len)
{
unsigned char* data = source;
if (s->pos + len > s->capa)
{
memstream_flush(s);
}
memcpy(s->data + s->pos, data, len);
s->pos += len;
}
void memstream_puts(struct memstream* s, const char* str)
{
memstream_write(s, (void*) str, strlen(str));
}

View File

@@ -1,20 +0,0 @@
#ifndef H_MEMSTREAM
#define H_MEMSTREAM
#include <stddef.h>
#include <stdio.h>
struct memstream
{
size_t pos;
size_t capa;
int file;
unsigned char* data;
};
void memstream_init(struct memstream* s, int fd, void* buffer, size_t len);
void memstream_flush(struct memstream* s);
void memstream_write(struct memstream* s, void* source, size_t len);
void memstream_puts(struct memstream* s, const char* str);
#endif

View File

@@ -1,195 +0,0 @@
#include "ringbuffer.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h> // for ptrdiff_t
int init_ringbuffer(struct ringbuffer* r, size_t size)
{
r->buf = (char*)malloc(size);
if (!r->buf)
{
return ERINGBUFFER_ALLOC_FAIL;
}
r->size = size;
clear_ringbuffer(r);
return 0;
}
void free_ringbuffer(struct ringbuffer* r)
{
free(r->buf);
}
void clear_ringbuffer(struct ringbuffer* r)
{
r->begin = 0;
r->end = 0;
}
size_t ringbuffer_free_space(struct ringbuffer* r)
{
if (r->begin == 0 && r->end == 0)
{
return r->size;
}
if (r->begin < r->end)
{
return r->size - (r->end - r->begin) - 1;
}
else
{
return r->begin - r->end - 1;
}
}
size_t ringbuffer_data_size(struct ringbuffer* r)
{
if (r->begin == 0 && r->end == 0)
{
return 0;
}
if (r->begin <= r->end)
{
return r->end - r->begin + 1;
}
else
{
return r->size - (r->begin - r->end) + 1;
}
}
void ringbuffer_push(struct ringbuffer* r, const void* data, size_t size)
{
if (ringbuffer_free_space(r) < size)
{
return;
}
if (r->begin == 0 && r->end == 0)
{
memcpy(r->buf, data, size);
r->begin = r->buf;
r->end = r->buf + size - 1;
return;
}
r->end++;
if (r->begin < r->end)
{
if ((size_t)(r->buf + (ptrdiff_t)r->size - r->begin) >= size)
{
// we can fit without cut
memcpy(r->end, data, size);
r->end += size - 1;
}
else
{
// make a cut
size_t s = r->buf + r->size - r->end;
memcpy(r->end, data, s);
size -= s;
memcpy(r->buf, (char*)data + s, size);
r->end = r->buf + size - 1;
}
}
else
{
memcpy(r->end, data, size);
r->end += size - 1;
}
}
void ringbuffer_pop(struct ringbuffer* r, void* data, size_t size)
{
if (ringbuffer_data_size(r) < size)
{
return;
}
int need_clear = 0;
if (ringbuffer_data_size(r) == size)
{
need_clear = 1;
}
if (r->begin < r->end)
{
if (data)
{
memcpy(data, r->begin, size);
}
r->begin += size;
}
else
{
if ((size_t)(r->buf + (ptrdiff_t)r->size - r->begin) >= size)
{
if (data)
{
memcpy(data, r->begin, size);
}
r->begin += size;
}
else
{
size_t s = r->buf + r->size - r->begin;
if (data)
{
memcpy(data, r->begin, s);
}
size -= s;
if (data)
{
memcpy((char*)data + s, r->buf, size);
}
r->begin = r->buf + size;
}
}
if (need_clear)
{
clear_ringbuffer(r);
}
}
void ringbuffer_read(struct ringbuffer* r, void* data, size_t size)
{
if (ringbuffer_data_size(r) < size)
{
return;
}
if (r->begin < r->end)
{
memcpy(data, r->begin, size);
}
else
{
if ((size_t)(r->buf + (ptrdiff_t)r->size - r->begin) >= size)
{
memcpy(data, r->begin, size);
}
else
{
size_t s = r->buf + r->size - r->begin;
memcpy(data, r->begin, s);
size -= s;
memcpy((char*)data + s, r->buf, size);
}
}
}

View File

@@ -1,26 +0,0 @@
#ifndef H_RINGBUFFER
#define H_RINGBUFFER
#include <stddef.h>
#define ERINGBUFFER_ALLOC_FAIL -1
struct ringbuffer
{
char* buf;
size_t size;
char* begin;
char* end;
};
int init_ringbuffer(struct ringbuffer* r, size_t size);
void free_ringbuffer(struct ringbuffer* r);
void clear_ringbuffer(struct ringbuffer* r);
size_t ringbuffer_free_space(struct ringbuffer* r);
size_t ringbuffer_data_size(struct ringbuffer* r);
void ringbuffer_push(struct ringbuffer* r, const void* data, size_t size);
void ringbuffer_pop(struct ringbuffer* r, void* data, size_t size);
void ringbuffer_read(struct ringbuffer* r, void* data, size_t size);
#endif

View File

@@ -1,412 +0,0 @@
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "term.h"
#define ENTER_MOUSE_SEQ "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h"
#define EXIT_MOUSE_SEQ "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l"
#define EUNSUPPORTED_TERM -1
// rxvt-256color
static const char* rxvt_256color_keys[] =
{
"\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~",
"\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~",
"\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~",
"\033[A", "\033[B", "\033[D", "\033[C", NULL
};
static const char* rxvt_256color_funcs[] =
{
"\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l",
"\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m",
"\033=", "\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ,
};
// Eterm
static const char* eterm_keys[] =
{
"\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~",
"\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~",
"\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~",
"\033[A", "\033[B", "\033[D", "\033[C", NULL
};
static const char* eterm_funcs[] =
{
"\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l",
"\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m",
"", "", "", "",
};
// screen
static const char* screen_keys[] =
{
"\033OP", "\033OQ", "\033OR", "\033OS", "\033[15~", "\033[17~",
"\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~",
"\033[2~", "\033[3~", "\033[1~", "\033[4~", "\033[5~", "\033[6~",
"\033OA", "\033OB", "\033OD", "\033OC", NULL
};
static const char* screen_funcs[] =
{
"\033[?1049h", "\033[?1049l", "\033[34h\033[?25h", "\033[?25l",
"\033[H\033[J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m",
"\033[?1h\033=", "\033[?1l\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ,
};
// rxvt-unicode
static const char* rxvt_unicode_keys[] =
{
"\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~",
"\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~",
"\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~",
"\033[A", "\033[B", "\033[D", "\033[C", NULL
};
static const char* rxvt_unicode_funcs[] =
{
"\033[?1049h", "\033[r\033[?1049l", "\033[?25h", "\033[?25l",
"\033[H\033[2J", "\033[m\033(B", "\033[4m", "\033[1m", "\033[5m",
"\033[7m", "\033=", "\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ,
};
// linux
static const char* linux_keys[] =
{
"\033[[A", "\033[[B", "\033[[C", "\033[[D", "\033[[E", "\033[17~",
"\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~",
"\033[2~", "\033[3~", "\033[1~", "\033[4~", "\033[5~", "\033[6~",
"\033[A", "\033[B", "\033[D", "\033[C", NULL
};
static const char* linux_funcs[] =
{
"", "", "\033[?25h\033[?0c", "\033[?25l\033[?1c", "\033[H\033[J",
"\033[0;10m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "", "", "", "",
};
// xterm
static const char* xterm_keys[] =
{
"\033OP", "\033OQ", "\033OR", "\033OS", "\033[15~", "\033[17~", "\033[18~",
"\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~",
"\033[3~", "\033OH", "\033OF", "\033[5~", "\033[6~", "\033OA", "\033OB",
"\033OD", "\033OC", NULL
};
static const char* xterm_funcs[] =
{
"\033[?1049h", "\033[?1049l", "\033[?12l\033[?25h", "\033[?25l",
"\033[H\033[2J", "\033(B\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m",
"\033[?1h\033=", "\033[?1l\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ,
};
struct term
{
const char* name;
const char** keys;
const char** funcs;
};
static struct term terms[] =
{
{"rxvt-256color", rxvt_256color_keys, rxvt_256color_funcs},
{"Eterm", eterm_keys, eterm_funcs},
{"screen", screen_keys, screen_funcs},
{"rxvt-unicode", rxvt_unicode_keys, rxvt_unicode_funcs},
{"linux", linux_keys, linux_funcs},
{"xterm", xterm_keys, xterm_funcs},
{0, 0, 0},
};
static int init_from_terminfo = 0;
const char** keys;
const char** funcs;
static int try_compatible(const char* term, const char* name,
const char** tkeys, const char** tfuncs)
{
if (strstr(term, name))
{
keys = tkeys;
funcs = tfuncs;
return 0;
}
return EUNSUPPORTED_TERM;
}
static int init_term_builtin(void)
{
int i;
const char* term = getenv("TERM");
if (term)
{
for (i = 0; terms[i].name; i++)
{
if (!strcmp(terms[i].name, term))
{
keys = terms[i].keys;
funcs = terms[i].funcs;
return 0;
}
}
// let's do some heuristic, maybe it's a compatible terminal
if (try_compatible(term, "xterm", xterm_keys, xterm_funcs) == 0)
{
return 0;
}
if (try_compatible(term, "rxvt", rxvt_unicode_keys, rxvt_unicode_funcs) == 0)
{
return 0;
}
if (try_compatible(term, "linux", linux_keys, linux_funcs) == 0)
{
return 0;
}
if (try_compatible(term, "Eterm", eterm_keys, eterm_funcs) == 0)
{
return 0;
}
if (try_compatible(term, "screen", screen_keys, screen_funcs) == 0)
{
return 0;
}
// let's assume that 'cygwin' is xterm compatible
if (try_compatible(term, "cygwin", xterm_keys, xterm_funcs) == 0)
{
return 0;
}
}
return EUNSUPPORTED_TERM;
}
// terminfo
static char* read_file(const char* file)
{
FILE* f = fopen(file, "rb");
if (!f)
{
return 0;
}
struct stat st;
if (fstat(fileno(f), &st) != 0)
{
fclose(f);
return 0;
}
char* data = malloc(st.st_size);
if (!data)
{
fclose(f);
return 0;
}
if (fread(data, 1, st.st_size, f) != (size_t)st.st_size)
{
fclose(f);
free(data);
return 0;
}
fclose(f);
return data;
}
static char* terminfo_try_path(const char* path, const char* term)
{
char tmp[4096];
snprintf(tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term);
tmp[sizeof(tmp) - 1] = '\0';
char* data = read_file(tmp);
if (data)
{
return data;
}
// fallback to darwin specific dirs structure
snprintf(tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term);
tmp[sizeof(tmp) - 1] = '\0';
return read_file(tmp);
}
static char* load_terminfo(void)
{
char tmp[4096];
const char* term = getenv("TERM");
if (!term)
{
return 0;
}
// if TERMINFO is set, no other directory should be searched
const char* terminfo = getenv("TERMINFO");
if (terminfo)
{
return terminfo_try_path(terminfo, term);
}
// next, consider ~/.terminfo
const char* home = getenv("HOME");
if (home)
{
snprintf(tmp, sizeof(tmp), "%s/.terminfo", home);
tmp[sizeof(tmp) - 1] = '\0';
char* data = terminfo_try_path(tmp, term);
if (data)
{
return data;
}
}
// next, TERMINFO_DIRS
const char* dirs = getenv("TERMINFO_DIRS");
if (dirs)
{
snprintf(tmp, sizeof(tmp), "%s", dirs);
tmp[sizeof(tmp) - 1] = '\0';
char* dir = strtok(tmp, ":");
while (dir)
{
const char* cdir = dir;
if (strcmp(cdir, "") == 0)
{
cdir = "/usr/share/terminfo";
}
char* data = terminfo_try_path(cdir, term);
if (data)
{
return data;
}
dir = strtok(0, ":");
}
}
// fallback to /usr/share/terminfo
return terminfo_try_path("/usr/share/terminfo", term);
}
#define TI_MAGIC 0432
#define TI_ALT_MAGIC 542
#define TI_HEADER_LENGTH 12
#define TB_KEYS_NUM 22
static const char* terminfo_copy_string(char* data, int str, int table)
{
const int16_t off = *(int16_t*)(data + str);
const char* src = data + table + off;
int len = strlen(src);
char* dst = malloc(len + 1);
strcpy(dst, src);
return dst;
}
const int16_t ti_funcs[] =
{
28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88,
};
const int16_t ti_keys[] =
{
// apparently not a typo; 67 is F10 for whatever reason
66, 68, 69, 70, 71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82,
81, 87, 61, 79, 83,
};
int init_term(void)
{
int i;
char* data = load_terminfo();
if (!data)
{
init_from_terminfo = 0;
return init_term_builtin();
}
int16_t* header = (int16_t*)data;
const int number_sec_len = header[0] == TI_ALT_MAGIC ? 4 : 2;
if ((header[1] + header[2]) % 2)
{
// old quirk to align everything on word boundaries
header[2] += 1;
}
const int str_offset = TI_HEADER_LENGTH +
header[1] + header[2] + number_sec_len * header[3];
const int table_offset = str_offset + 2 * header[4];
keys = malloc(sizeof(const char*) * (TB_KEYS_NUM + 1));
for (i = 0; i < TB_KEYS_NUM; i++)
{
keys[i] = terminfo_copy_string(data,
str_offset + 2 * ti_keys[i], table_offset);
}
keys[i] = NULL;
funcs = malloc(sizeof(const char*) * T_FUNCS_NUM);
// the last two entries are reserved for mouse. because the table offset is
// not there, the two entries have to fill in manually
for (i = 0; i < T_FUNCS_NUM - 2; i++)
{
funcs[i] = terminfo_copy_string(data,
str_offset + 2 * ti_funcs[i], table_offset);
}
funcs[T_FUNCS_NUM - 2] = ENTER_MOUSE_SEQ;
funcs[T_FUNCS_NUM - 1] = EXIT_MOUSE_SEQ;
init_from_terminfo = 1;
free(data);
return 0;
}
void shutdown_term(void)
{
if (init_from_terminfo)
{
int i;
for (i = 0; i < TB_KEYS_NUM; i++)
{
free((void*)keys[i]);
}
// the last two entries are reserved for mouse. because the table offset
// is not there, the two entries have to fill in manually and do not
// need to be freed.
for (i = 0; i < T_FUNCS_NUM - 2; i++)
{
free((void*)funcs[i]);
}
free(keys);
free(funcs);
}
}

View File

@@ -1,38 +0,0 @@
#ifndef H_TERM
#define H_TERM
#include "termbox.h"
#include "ringbuffer.h"
#include <stdbool.h>
#define EUNSUPPORTED_TERM -1
enum
{
T_ENTER_CA,
T_EXIT_CA,
T_SHOW_CURSOR,
T_HIDE_CURSOR,
T_CLEAR_SCREEN,
T_SGR0,
T_UNDERLINE,
T_BOLD,
T_BLINK,
T_REVERSE,
T_ENTER_KEYPAD,
T_EXIT_KEYPAD,
T_ENTER_MOUSE,
T_EXIT_MOUSE,
T_FUNCS_NUM,
};
extern const char** keys;
extern const char** funcs;
// true on success, false on failure
bool extract_event(struct tb_event* event, struct ringbuffer* inbuf,
int inputmode);
int init_term(void);
void shutdown_term(void);
#endif

View File

@@ -1,885 +0,0 @@
#include "term.h"
#include "termbox.h"
#include "memstream.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdbool.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <termios.h>
#include <unistd.h>
#include <wchar.h>
struct cellbuf
{
int width;
int height;
struct tb_cell* cells;
};
#define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)]
#define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1)
#define LAST_COORD_INIT -1
static struct termios orig_tios;
static struct cellbuf back_buffer;
static struct cellbuf front_buffer;
static unsigned char write_buffer_data[32 * 1024];
static struct memstream write_buffer;
static int termw = -1;
static int termh = -1;
static int inputmode = TB_INPUT_ESC;
static int outputmode = TB_OUTPUT_NORMAL;
static struct ringbuffer inbuf;
static int out;
static FILE* in;
static int out_fileno;
static int in_fileno;
static int winch_fds[2];
static int lastx = LAST_COORD_INIT;
static int lasty = LAST_COORD_INIT;
static int cursor_x = -1;
static int cursor_y = -1;
static uint32_t background = TB_DEFAULT;
static uint32_t foreground = TB_DEFAULT;
static void write_cursor(int x, int y);
static void write_sgr(uint32_t fg, uint32_t bg);
static void cellbuf_init(struct cellbuf* buf, int width, int height);
static void cellbuf_resize(struct cellbuf* buf, int width, int height);
static void cellbuf_clear(struct cellbuf* buf);
static void cellbuf_free(struct cellbuf* buf);
static void update_size(void);
static void update_term_size(void);
static void send_attr(uint32_t fg, uint32_t bg);
static void send_char(int x, int y, uint32_t c);
static void send_clear(void);
static void sigwinch_handler(int xxx);
static int wait_fill_event(struct tb_event* event, struct timeval* timeout);
// may happen in a different thread
static volatile int buffer_size_change_request;
int tb_init_file(const char* name)
{
out = open(name, O_WRONLY);
in = fopen(name, "r");
if (out == -1 || !in)
{
if (out != -1)
{
close(out);
}
if (in)
{
fclose(in);
}
return TB_EFAILED_TO_OPEN_TTY;
}
out_fileno = out;
in_fileno = fileno(in);
if (init_term() < 0)
{
close(out);
fclose(in);
return TB_EUNSUPPORTED_TERMINAL;
}
if (pipe(winch_fds) < 0)
{
close(out);
fclose(in);
return TB_EPIPE_TRAP_ERROR;
}
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigwinch_handler;
sa.sa_flags = 0;
sigaction(SIGWINCH, &sa, 0);
tcgetattr(out_fileno, &orig_tios);
struct termios tios;
memcpy(&tios, &orig_tios, sizeof(tios));
tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
tios.c_oflag &= ~OPOST;
tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
tios.c_cflag &= ~(CSIZE | PARENB);
tios.c_cflag |= CS8;
tios.c_cc[VMIN] = 0;
tios.c_cc[VTIME] = 0;
tcsetattr(out_fileno, TCSAFLUSH, &tios);
memstream_init(&write_buffer, out_fileno, write_buffer_data,
sizeof(write_buffer_data));
memstream_puts(&write_buffer, funcs[T_ENTER_CA]);
memstream_puts(&write_buffer, funcs[T_ENTER_KEYPAD]);
memstream_puts(&write_buffer, funcs[T_HIDE_CURSOR]);
send_clear();
update_term_size();
cellbuf_init(&back_buffer, termw, termh);
cellbuf_init(&front_buffer, termw, termh);
cellbuf_clear(&back_buffer);
cellbuf_clear(&front_buffer);
init_ringbuffer(&inbuf, 4096);
return 0;
}
int tb_init(void)
{
return tb_init_file("/dev/tty");
}
void tb_shutdown(void)
{
if (termw == -1)
{
fputs("tb_shutdown() should not be called twice.", stderr);
abort();
}
memstream_puts(&write_buffer, funcs[T_SHOW_CURSOR]);
memstream_puts(&write_buffer, funcs[T_SGR0]);
memstream_puts(&write_buffer, funcs[T_CLEAR_SCREEN]);
memstream_puts(&write_buffer, funcs[T_EXIT_CA]);
memstream_puts(&write_buffer, funcs[T_EXIT_KEYPAD]);
memstream_puts(&write_buffer, funcs[T_EXIT_MOUSE]);
memstream_flush(&write_buffer);
tcsetattr(out_fileno, TCSAFLUSH, &orig_tios);
shutdown_term();
close(out);
fclose(in);
close(winch_fds[0]);
close(winch_fds[1]);
cellbuf_free(&back_buffer);
cellbuf_free(&front_buffer);
free_ringbuffer(&inbuf);
termw = termh = -1;
}
void tb_present(void)
{
int x, y, w, i;
struct tb_cell* back, *front;
// invalidate cursor position
lastx = LAST_COORD_INIT;
lasty = LAST_COORD_INIT;
if (buffer_size_change_request)
{
update_size();
buffer_size_change_request = 0;
}
for (y = 0; y < front_buffer.height; ++y)
{
for (x = 0; x < front_buffer.width;)
{
back = &CELL(&back_buffer, x, y);
front = &CELL(&front_buffer, x, y);
w = wcwidth(back->ch);
if (w < 1)
{
w = 1;
}
if (memcmp(back, front, sizeof(struct tb_cell)) == 0)
{
x += w;
continue;
}
memcpy(front, back, sizeof(struct tb_cell));
send_attr(back->fg, back->bg);
if (w > 1 && x >= front_buffer.width - (w - 1))
{
// Not enough room for wide ch, so send spaces
for (i = x; i < front_buffer.width; ++i)
{
send_char(i, y, ' ');
}
}
else
{
send_char(x, y, back->ch);
for (i = 1; i < w; ++i)
{
front = &CELL(&front_buffer, x + i, y);
front->ch = 0;
front->fg = back->fg;
front->bg = back->bg;
}
}
x += w;
}
}
if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
{
write_cursor(cursor_x, cursor_y);
}
memstream_flush(&write_buffer);
}
void tb_set_cursor(int cx, int cy)
{
if (IS_CURSOR_HIDDEN(cursor_x, cursor_y) && !IS_CURSOR_HIDDEN(cx, cy))
{
memstream_puts(&write_buffer, funcs[T_SHOW_CURSOR]);
}
if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y) && IS_CURSOR_HIDDEN(cx, cy))
{
memstream_puts(&write_buffer, funcs[T_HIDE_CURSOR]);
}
cursor_x = cx;
cursor_y = cy;
if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
{
write_cursor(cursor_x, cursor_y);
}
}
void tb_put_cell(int x, int y, const struct tb_cell* cell)
{
if ((unsigned)x >= (unsigned)back_buffer.width)
{
return;
}
if ((unsigned)y >= (unsigned)back_buffer.height)
{
return;
}
CELL(&back_buffer, x, y) = *cell;
}
void tb_change_cell(int x, int y, uint32_t ch, uint32_t fg, uint32_t bg)
{
struct tb_cell c = {ch, fg, bg};
tb_put_cell(x, y, &c);
}
void tb_blit(int x, int y, int w, int h, const struct tb_cell* cells)
{
if (x + w < 0 || x >= back_buffer.width)
{
return;
}
if (y + h < 0 || y >= back_buffer.height)
{
return;
}
int xo = 0, yo = 0, ww = w, hh = h;
if (x < 0)
{
xo = -x;
ww -= xo;
x = 0;
}
if (y < 0)
{
yo = -y;
hh -= yo;
y = 0;
}
if (ww > back_buffer.width - x)
{
ww = back_buffer.width - x;
}
if (hh > back_buffer.height - y)
{
hh = back_buffer.height - y;
}
int sy;
struct tb_cell* dst = &CELL(&back_buffer, x, y);
const struct tb_cell* src = cells + yo * w + xo;
size_t size = sizeof(struct tb_cell) * ww;
for (sy = 0; sy < hh; ++sy)
{
memcpy(dst, src, size);
dst += back_buffer.width;
src += w;
}
}
struct tb_cell* tb_cell_buffer(void)
{
return back_buffer.cells;
}
int tb_poll_event(struct tb_event* event)
{
return wait_fill_event(event, 0);
}
int tb_peek_event(struct tb_event* event, int timeout)
{
struct timeval tv;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
return wait_fill_event(event, &tv);
}
int tb_width(void)
{
return termw;
}
int tb_height(void)
{
return termh;
}
void tb_clear(void)
{
if (buffer_size_change_request)
{
update_size();
buffer_size_change_request = 0;
}
cellbuf_clear(&back_buffer);
}
int tb_select_input_mode(int mode)
{
if (mode)
{
if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0)
{
mode |= TB_INPUT_ESC;
}
// technically termbox can handle that, but let's be nice
// and show here what mode is actually used
if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT))
{
mode &= ~TB_INPUT_ALT;
}
inputmode = mode;
if (mode & TB_INPUT_MOUSE)
{
memstream_puts(&write_buffer, funcs[T_ENTER_MOUSE]);
memstream_flush(&write_buffer);
}
else
{
memstream_puts(&write_buffer, funcs[T_EXIT_MOUSE]);
memstream_flush(&write_buffer);
}
}
return inputmode;
}
int tb_select_output_mode(int mode)
{
if (mode)
{
outputmode = mode;
}
return outputmode;
}
void tb_set_clear_attributes(uint32_t fg, uint32_t bg)
{
foreground = fg;
background = bg;
}
static unsigned convertnum(uint32_t num, char* buf)
{
unsigned i, l = 0;
int ch;
do
{
buf[l++] = '0' + (num % 10);
num /= 10;
}
while (num);
for (i = 0; i < l / 2; i++)
{
ch = buf[i];
buf[i] = buf[l - 1 - i];
buf[l - 1 - i] = ch;
}
return l;
}
#define WRITE_LITERAL(X) memstream_write(&write_buffer, (X), sizeof(X) -1)
#define WRITE_INT(X) memstream_write(&write_buffer, buf, convertnum((X), buf))
static void write_cursor(int x, int y)
{
char buf[32];
WRITE_LITERAL("\033[");
WRITE_INT(y + 1);
WRITE_LITERAL(";");
WRITE_INT(x + 1);
WRITE_LITERAL("H");
}
static void write_sgr(uint32_t fg, uint32_t bg)
{
char buf[32];
if (outputmode != TB_OUTPUT_TRUECOLOR && fg == TB_DEFAULT && bg == TB_DEFAULT)
{
return;
}
switch (outputmode)
{
case TB_OUTPUT_TRUECOLOR:
WRITE_LITERAL("\033[38;2;");
WRITE_INT(fg >> 16 & 0xFF);
WRITE_LITERAL(";");
WRITE_INT(fg >> 8 & 0xFF);
WRITE_LITERAL(";");
WRITE_INT(fg & 0xFF);
WRITE_LITERAL(";48;2;");
WRITE_INT(bg >> 16 & 0xFF);
WRITE_LITERAL(";");
WRITE_INT(bg >> 8 & 0xFF);
WRITE_LITERAL(";");
WRITE_INT(bg & 0xFF);
WRITE_LITERAL("m");
break;
case TB_OUTPUT_256:
case TB_OUTPUT_216:
case TB_OUTPUT_GRAYSCALE:
WRITE_LITERAL("\033[");
if (fg != TB_DEFAULT)
{
WRITE_LITERAL("38;5;");
WRITE_INT(fg);
if (bg != TB_DEFAULT)
{
WRITE_LITERAL(";");
}
}
if (bg != TB_DEFAULT)
{
WRITE_LITERAL("48;5;");
WRITE_INT(bg);
}
WRITE_LITERAL("m");
break;
case TB_OUTPUT_NORMAL:
default:
WRITE_LITERAL("\033[");
if (fg != TB_DEFAULT)
{
WRITE_LITERAL("3");
WRITE_INT(fg - 1);
if (bg != TB_DEFAULT)
{
WRITE_LITERAL(";");
}
}
if (bg != TB_DEFAULT)
{
WRITE_LITERAL("4");
WRITE_INT(bg - 1);
}
WRITE_LITERAL("m");
break;
}
}
static void cellbuf_init(struct cellbuf* buf, int width, int height)
{
buf->cells = (struct tb_cell*)malloc(sizeof(struct tb_cell) * width * height);
assert(buf->cells);
buf->width = width;
buf->height = height;
}
static void cellbuf_resize(struct cellbuf* buf, int width, int height)
{
if (buf->width == width && buf->height == height)
{
return;
}
int oldw = buf->width;
int oldh = buf->height;
struct tb_cell* oldcells = buf->cells;
cellbuf_init(buf, width, height);
cellbuf_clear(buf);
int minw = (width < oldw) ? width : oldw;
int minh = (height < oldh) ? height : oldh;
int i;
for (i = 0; i < minh; ++i)
{
struct tb_cell* csrc = oldcells + (i * oldw);
struct tb_cell* cdst = buf->cells + (i * width);
memcpy(cdst, csrc, sizeof(struct tb_cell) * minw);
}
free(oldcells);
}
static void cellbuf_clear(struct cellbuf* buf)
{
int i;
int ncells = buf->width * buf->height;
for (i = 0; i < ncells; ++i)
{
buf->cells[i].ch = ' ';
buf->cells[i].fg = foreground;
buf->cells[i].bg = background;
}
}
static void cellbuf_free(struct cellbuf* buf)
{
free(buf->cells);
}
static void get_term_size(int* w, int* h)
{
struct winsize sz;
memset(&sz, 0, sizeof(sz));
ioctl(out_fileno, TIOCGWINSZ, &sz);
if (w)
{
*w = sz.ws_col;
}
if (h)
{
*h = sz.ws_row;
}
}
static void send_attr(uint32_t fg, uint32_t bg)
{
#define LAST_ATTR_INIT 0xFFFFFFFF
static uint32_t lastfg = LAST_ATTR_INIT, lastbg = LAST_ATTR_INIT;
if (fg != lastfg || bg != lastbg)
{
memstream_puts(&write_buffer, funcs[T_SGR0]);
uint32_t fgcol;
uint32_t bgcol;
switch (outputmode)
{
case TB_OUTPUT_TRUECOLOR:
fgcol = fg;
bgcol = bg;
break;
case TB_OUTPUT_256:
fgcol = fg & 0xFF;
bgcol = bg & 0xFF;
break;
case TB_OUTPUT_216:
fgcol = fg & 0xFF;
if (fgcol > 215)
{
fgcol = 7;
}
bgcol = bg & 0xFF;
if (bgcol > 215)
{
bgcol = 0;
}
fgcol += 0x10;
bgcol += 0x10;
break;
case TB_OUTPUT_GRAYSCALE:
fgcol = fg & 0xFF;
if (fgcol > 23)
{
fgcol = 23;
}
bgcol = bg & 0xFF;
if (bgcol > 23)
{
bgcol = 0;
}
fgcol += 0xe8;
bgcol += 0xe8;
break;
case TB_OUTPUT_NORMAL:
default:
fgcol = fg & 0x0F;
bgcol = bg & 0x0F;
}
if (fg & TB_BOLD)
{
memstream_puts(&write_buffer, funcs[T_BOLD]);
}
if (bg & TB_BOLD)
{
memstream_puts(&write_buffer, funcs[T_BLINK]);
}
if (fg & TB_UNDERLINE)
{
memstream_puts(&write_buffer, funcs[T_UNDERLINE]);
}
if ((fg & TB_REVERSE) || (bg & TB_REVERSE))
{
memstream_puts(&write_buffer, funcs[T_REVERSE]);
}
write_sgr(fgcol, bgcol);
lastfg = fg;
lastbg = bg;
}
}
static void send_char(int x, int y, uint32_t c)
{
char buf[7];
int bw = utf8_unicode_to_char(buf, c);
buf[bw] = '\0';
if (x - 1 != lastx || y != lasty)
{
write_cursor(x, y);
}
lastx = x;
lasty = y;
if (!c)
{
buf[0] = ' '; // replace 0 with whitespace
}
memstream_puts(&write_buffer, buf);
}
static void send_clear(void)
{
send_attr(foreground, background);
memstream_puts(&write_buffer, funcs[T_CLEAR_SCREEN]);
if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
{
write_cursor(cursor_x, cursor_y);
}
memstream_flush(&write_buffer);
// we need to invalidate cursor position too and these two vars are
// used only for simple cursor positioning optimization, cursor
// actually may be in the correct place, but we simply discard
// optimization once and it gives us simple solution for the case when
// cursor moved
lastx = LAST_COORD_INIT;
lasty = LAST_COORD_INIT;
}
static void sigwinch_handler(int xxx)
{
(void) xxx;
const int zzz = 1;
write(winch_fds[1], &zzz, sizeof(int));
}
static void update_size(void)
{
update_term_size();
cellbuf_resize(&back_buffer, termw, termh);
cellbuf_resize(&front_buffer, termw, termh);
cellbuf_clear(&front_buffer);
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
int result;
char buf[ENOUGH_DATA_FOR_INPUT_PARSING];
fd_set events;
memset(event, 0, sizeof(struct tb_event));
// try to extract event from input buffer, return on success
event->type = TB_EVENT_KEY;
if (extract_event(event, &inbuf, inputmode))
{
return event->type;
}
// it looks like input buffer is incomplete, let's try the short path
size_t r = fread(buf, 1, ENOUGH_DATA_FOR_INPUT_PARSING, in);
if (r < ENOUGH_DATA_FOR_INPUT_PARSING && feof(in))
{
clearerr(in);
}
if (r > 0)
{
if (ringbuffer_free_space(&inbuf) < r)
{
return -1;
}
ringbuffer_push(&inbuf, buf, r);
if (extract_event(event, &inbuf, inputmode))
{
return event->type;
}
}
// no stuff in FILE's internal buffer, block in select
while (1)
{
FD_ZERO(&events);
FD_SET(in_fileno, &events);
FD_SET(winch_fds[0], &events);
int maxfd = (winch_fds[0] > in_fileno) ? winch_fds[0] : in_fileno;
result = select(maxfd + 1, &events, 0, 0, timeout);
if (!result)
{
return 0;
}
if (FD_ISSET(in_fileno, &events))
{
event->type = TB_EVENT_KEY;
size_t r = fread(buf, 1, ENOUGH_DATA_FOR_INPUT_PARSING, in);
if (r < ENOUGH_DATA_FOR_INPUT_PARSING && feof(in))
{
clearerr(in);
}
if (r == 0)
{
continue;
}
// if there is no free space in input buffer, return error
if (ringbuffer_free_space(&inbuf) < r)
{
return -1;
}
// fill buffer
ringbuffer_push(&inbuf, buf, r);
if (extract_event(event, &inbuf, inputmode))
{
return event->type;
}
}
if (FD_ISSET(winch_fds[0], &events))
{
event->type = TB_EVENT_RESIZE;
int zzz = 0;
read(winch_fds[0], &zzz, sizeof(int));
buffer_size_change_request = 1;
get_term_size(&event->w, &event->h);
return TB_EVENT_RESIZE;
}
}
}

View File

@@ -1,307 +0,0 @@
#ifndef H_TERMBOX
#define H_TERMBOX
#include <stdint.h>
// shared objects
#if __GNUC__ >= 4
#define SO_IMPORT __attribute__((visibility("default")))
#else
#define SO_IMPORT
#endif
// c++
#ifdef __cplusplus
extern "C" {
#endif
// Key constants. See also struct tb_event's key field.
// These are a safe subset of terminfo keys, which exist on all popular
// terminals. Termbox uses only them to stay truly portable.
#define TB_KEY_F1 (0xFFFF-0)
#define TB_KEY_F2 (0xFFFF-1)
#define TB_KEY_F3 (0xFFFF-2)
#define TB_KEY_F4 (0xFFFF-3)
#define TB_KEY_F5 (0xFFFF-4)
#define TB_KEY_F6 (0xFFFF-5)
#define TB_KEY_F7 (0xFFFF-6)
#define TB_KEY_F8 (0xFFFF-7)
#define TB_KEY_F9 (0xFFFF-8)
#define TB_KEY_F10 (0xFFFF-9)
#define TB_KEY_F11 (0xFFFF-10)
#define TB_KEY_F12 (0xFFFF-11)
#define TB_KEY_INSERT (0xFFFF-12)
#define TB_KEY_DELETE (0xFFFF-13)
#define TB_KEY_HOME (0xFFFF-14)
#define TB_KEY_END (0xFFFF-15)
#define TB_KEY_PGUP (0xFFFF-16)
#define TB_KEY_PGDN (0xFFFF-17)
#define TB_KEY_ARROW_UP (0xFFFF-18)
#define TB_KEY_ARROW_DOWN (0xFFFF-19)
#define TB_KEY_ARROW_LEFT (0xFFFF-20)
#define TB_KEY_ARROW_RIGHT (0xFFFF-21)
#define TB_KEY_MOUSE_LEFT (0xFFFF-22)
#define TB_KEY_MOUSE_RIGHT (0xFFFF-23)
#define TB_KEY_MOUSE_MIDDLE (0xFFFF-24)
#define TB_KEY_MOUSE_RELEASE (0xFFFF-25)
#define TB_KEY_MOUSE_WHEEL_UP (0xFFFF-26)
#define TB_KEY_MOUSE_WHEEL_DOWN (0xFFFF-27)
// These are all ASCII code points below SPACE character and a BACKSPACE key.
#define TB_KEY_CTRL_TILDE 0x00
#define TB_KEY_CTRL_2 0x00 // clash with 'CTRL_TILDE'
#define TB_KEY_CTRL_A 0x01
#define TB_KEY_CTRL_B 0x02
#define TB_KEY_CTRL_C 0x03
#define TB_KEY_CTRL_D 0x04
#define TB_KEY_CTRL_E 0x05
#define TB_KEY_CTRL_F 0x06
#define TB_KEY_CTRL_G 0x07
#define TB_KEY_BACKSPACE 0x08
#define TB_KEY_CTRL_H 0x08 // clash with 'CTRL_BACKSPACE'
#define TB_KEY_TAB 0x09
#define TB_KEY_CTRL_I 0x09 // clash with 'TAB'
#define TB_KEY_CTRL_J 0x0A
#define TB_KEY_CTRL_K 0x0B
#define TB_KEY_CTRL_L 0x0C
#define TB_KEY_ENTER 0x0D
#define TB_KEY_CTRL_M 0x0D // clash with 'ENTER'
#define TB_KEY_CTRL_N 0x0E
#define TB_KEY_CTRL_O 0x0F
#define TB_KEY_CTRL_P 0x10
#define TB_KEY_CTRL_Q 0x11
#define TB_KEY_CTRL_R 0x12
#define TB_KEY_CTRL_S 0x13
#define TB_KEY_CTRL_T 0x14
#define TB_KEY_CTRL_U 0x15
#define TB_KEY_CTRL_V 0x16
#define TB_KEY_CTRL_W 0x17
#define TB_KEY_CTRL_X 0x18
#define TB_KEY_CTRL_Y 0x19
#define TB_KEY_CTRL_Z 0x1A
#define TB_KEY_ESC 0x1B
#define TB_KEY_CTRL_LSQ_BRACKET 0x1B // clash with 'ESC'
#define TB_KEY_CTRL_3 0x1B // clash with 'ESC'
#define TB_KEY_CTRL_4 0x1C
#define TB_KEY_CTRL_BACKSLASH 0x1C // clash with 'CTRL_4'
#define TB_KEY_CTRL_5 0x1D
#define TB_KEY_CTRL_RSQ_BRACKET 0x1D // clash with 'CTRL_5'
#define TB_KEY_CTRL_6 0x1E
#define TB_KEY_CTRL_7 0x1F
#define TB_KEY_CTRL_SLASH 0x1F // clash with 'CTRL_7'
#define TB_KEY_CTRL_UNDERSCORE 0x1F // clash with 'CTRL_7'
#define TB_KEY_SPACE 0x20
#define TB_KEY_BACKSPACE2 0x7F
#define TB_KEY_CTRL_8 0x7F // clash with 'BACKSPACE2'
// These are non-existing ones.
// #define TB_KEY_CTRL_1 clash with '1'
// #define TB_KEY_CTRL_9 clash with '9'
// #define TB_KEY_CTRL_0 clash with '0'
// Alt modifier constant, see tb_event.mod field and tb_select_input_mode function.
// Mouse-motion modifier
#define TB_MOD_ALT 0x01
#define TB_MOD_MOTION 0x02
// Colors (see struct tb_cell's fg and bg fields).
#define TB_DEFAULT 0x00
#define TB_BLACK 0x01
#define TB_RED 0x02
#define TB_GREEN 0x03
#define TB_YELLOW 0x04
#define TB_BLUE 0x05
#define TB_MAGENTA 0x06
#define TB_CYAN 0x07
#define TB_WHITE 0x08
// Attributes, it is possible to use multiple attributes by combining them
// using bitwise OR ('|'). Although, colors cannot be combined. But you can
// combine attributes and a single color. See also struct tb_cell's fg and bg
// fields.
#define TB_BOLD 0x01000000
#define TB_UNDERLINE 0x02000000
#define TB_REVERSE 0x04000000
// A cell, single conceptual entity on the terminal screen. The terminal screen
// is basically a 2d array of cells. It has the following fields:
// - 'ch' is a unicode character
// - 'fg' foreground color and attributes
// - 'bg' background color and attributes
struct tb_cell
{
uint32_t ch;
uint32_t fg;
uint32_t bg;
};
#define TB_EVENT_KEY 1
#define TB_EVENT_RESIZE 2
#define TB_EVENT_MOUSE 3
// An event, single interaction from the user. The 'mod' and 'ch' fields are
// valid if 'type' is TB_EVENT_KEY. The 'w' and 'h' fields are valid if 'type'
// is TB_EVENT_RESIZE. The 'x' and 'y' fields are valid if 'type' is
// TB_EVENT_MOUSE. The 'key' field is valid if 'type' is either TB_EVENT_KEY
// or TB_EVENT_MOUSE. The fields 'key' and 'ch' are mutually exclusive; only
// one of them can be non-zero at a time.
struct tb_event
{
uint8_t type;
uint8_t mod; // modifiers to either 'key' or 'ch' below
uint16_t key; // one of the TB_KEY_* constants
uint32_t ch; // unicode character
int32_t w;
int32_t h;
int32_t x;
int32_t y;
};
// Error codes returned by tb_init(). All of them are self-explanatory, except
// the pipe trap error. Termbox uses unix pipes in order to deliver a message
// from a signal handler (SIGWINCH) to the main event reading loop. Honestly in
// most cases you should just check the returned code as < 0.
#define TB_EUNSUPPORTED_TERMINAL -1
#define TB_EFAILED_TO_OPEN_TTY -2
#define TB_EPIPE_TRAP_ERROR -3
// Initializes the termbox library. This function should be called before any
// other functions. Function tb_init is same as tb_init_file("/dev/tty"). After successful initialization, the library must be
// finalized using the tb_shutdown() function.
SO_IMPORT int tb_init(void);
SO_IMPORT int tb_init_file(const char* name);
SO_IMPORT void tb_shutdown(void);
// Returns the size of the internal back buffer (which is the same as
// terminal's window size in characters). The internal buffer can be resized
// after tb_clear() or tb_present() function calls. Both dimensions have an
// unspecified negative value when called before tb_init() or after
// tb_shutdown().
SO_IMPORT int tb_width(void);
SO_IMPORT int tb_height(void);
// Clears the internal back buffer using TB_DEFAULT color or the
// color/attributes set by tb_set_clear_attributes() function.
SO_IMPORT void tb_clear(void);
SO_IMPORT void tb_set_clear_attributes(uint32_t fg, uint32_t bg);
// Synchronizes the internal back buffer with the terminal.
SO_IMPORT void tb_present(void);
#define TB_HIDE_CURSOR -1
// Sets the position of the cursor. Upper-left character is (0, 0). If you pass
// TB_HIDE_CURSOR as both coordinates, then the cursor will be hidden. Cursor
// is hidden by default.
SO_IMPORT void tb_set_cursor(int cx, int cy);
// Changes cell's parameters in the internal back buffer at the specified
// position.
SO_IMPORT void tb_put_cell(int x, int y, const struct tb_cell* cell);
SO_IMPORT void tb_change_cell(int x, int y, uint32_t ch, uint32_t fg,
uint32_t bg);
// Copies the buffer from 'cells' at the specified position, assuming the
// buffer is a two-dimensional array of size ('w' x 'h'), represented as a
// one-dimensional buffer containing lines of cells starting from the top.
// (DEPRECATED: use tb_cell_buffer() instead and copy memory on your own)
SO_IMPORT void tb_blit(int x, int y, int w, int h, const struct tb_cell* cells);
// Returns a pointer to internal cell back buffer. You can get its dimensions
// using tb_width() and tb_height() functions. The pointer stays valid as long
// as no tb_clear() and tb_present() calls are made. The buffer is
// one-dimensional buffer containing lines of cells starting from the top.
SO_IMPORT struct tb_cell* tb_cell_buffer(void);
#define TB_INPUT_CURRENT 0 // 000
#define TB_INPUT_ESC 1 // 001
#define TB_INPUT_ALT 2 // 010
#define TB_INPUT_MOUSE 4 // 100
// Sets the termbox input mode. Termbox has two input modes:
// 1. Esc input mode.
// When ESC sequence is in the buffer and it doesn't match any known
// ESC sequence => ESC means TB_KEY_ESC.
// 2. Alt input mode.
// When ESC sequence is in the buffer and it doesn't match any known
// sequence => ESC enables TB_MOD_ALT modifier for the next keyboard event.
//
// You can also apply TB_INPUT_MOUSE via bitwise OR operation to either of the
// modes (e.g. TB_INPUT_ESC | TB_INPUT_MOUSE). If none of the main two modes
// were set, but the mouse mode was, TB_INPUT_ESC mode is used. If for some
// reason you've decided to use (TB_INPUT_ESC | TB_INPUT_ALT) combination, it
// will behave as if only TB_INPUT_ESC was selected.
//
// If 'mode' is TB_INPUT_CURRENT, it returns the current input mode.
//
// Default termbox input mode is TB_INPUT_ESC.
SO_IMPORT int tb_select_input_mode(int mode);
#define TB_OUTPUT_CURRENT 0
#define TB_OUTPUT_NORMAL 1
#define TB_OUTPUT_256 2
#define TB_OUTPUT_216 3
#define TB_OUTPUT_GRAYSCALE 4
#define TB_OUTPUT_TRUECOLOR 5
// Sets the termbox output mode. Termbox has three output options:
// 1. TB_OUTPUT_NORMAL => [1..8]
// This mode provides 8 different colors:
// black, red, green, yellow, blue, magenta, cyan, white
// Shortcut: TB_BLACK, TB_RED, ...
// Attributes: TB_BOLD, TB_UNDERLINE, TB_REVERSE
//
// Example usage:
// tb_change_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED);
//
// 2. TB_OUTPUT_256 => [0..256]
// In this mode you can leverage the 256 terminal mode:
// 0x00 - 0x07: the 8 colors as in TB_OUTPUT_NORMAL
// 0x08 - 0x0f: TB_* | TB_BOLD
// 0x10 - 0xe7: 216 different colors
// 0xe8 - 0xff: 24 different shades of grey
//
// Example usage:
// tb_change_cell(x, y, '@', 184, 240);
// tb_change_cell(x, y, '@', 0xb8, 0xf0);
//
// 3. TB_OUTPUT_216 => [0..216]
// This mode supports the 3rd range of the 256 mode only.
// But you don't need to provide an offset.
//
// 4. TB_OUTPUT_GRAYSCALE => [0..23]
// This mode supports the 4th range of the 256 mode only.
// But you dont need to provide an offset.
//
// 5. TB_OUTPUT_TRUECOLOR => [0x000000..0xFFFFFF]
// This mode supports 24-bit true color. Format is 0xRRGGBB.
//
// Execute build/src/demo/output to see its impact on your terminal.
//
// If 'mode' is TB_OUTPUT_CURRENT, it returns the current output mode.
//
// Default termbox output mode is TB_OUTPUT_NORMAL.
SO_IMPORT int tb_select_output_mode(int mode);
// Wait for an event up to 'timeout' milliseconds and fill the 'event'
// structure with it, when the event is available. Returns the type of the
// event (one of TB_EVENT_* constants) or -1 if there was an error or 0 in case
// there were no event during 'timeout' period.
SO_IMPORT int tb_peek_event(struct tb_event* event, int timeout);
// Wait for an event forever and fill the 'event' structure with it, when the
// event is available. Returns the type of the event (one of TB_EVENT_
// constants) or -1 if there was an error.
SO_IMPORT int tb_poll_event(struct tb_event* event);
// Utility utf8 functions.
#define TB_EOF -1
SO_IMPORT int utf8_char_length(char c);
SO_IMPORT int utf8_char_to_unicode(uint32_t* out, const char* c);
SO_IMPORT int utf8_unicode_to_char(char* out, uint32_t c);
// c++
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -1,106 +0,0 @@
#include "termbox.h"
static const unsigned char utf8_length[256] =
{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1
};
static const unsigned char utf8_mask[6] =
{
0x7F,
0x1F,
0x0F,
0x07,
0x03,
0x01
};
int utf8_char_length(char c)
{
return utf8_length[(unsigned char)c];
}
int utf8_char_to_unicode(uint32_t* out, const char* c)
{
if (*c == 0)
{
return TB_EOF;
}
int i;
unsigned char len = utf8_char_length(*c);
unsigned char mask = utf8_mask[len - 1];
uint32_t result = c[0] & mask;
for (i = 1; i < len; ++i)
{
result <<= 6;
result |= c[i] & 0x3f;
}
*out = result;
return (int)len;
}
int utf8_unicode_to_char(char* out, uint32_t c)
{
int len = 0;
int first;
int i;
if (c < 0x80)
{
first = 0;
len = 1;
}
else if (c < 0x800)
{
first = 0xc0;
len = 2;
}
else if (c < 0x10000)
{
first = 0xe0;
len = 3;
}
else if (c < 0x200000)
{
first = 0xf0;
len = 4;
}
else if (c < 0x4000000)
{
first = 0xf8;
len = 5;
}
else
{
first = 0xfc;
len = 6;
}
for (i = len - 1; i > 0; --i)
{
out[i] = (c & 0x3f) | 0x80;
c >>= 6;
}
out[0] = c | first;
return len;
}

View File

@@ -1,27 +0,0 @@
--style=break
--indent=force-tab=4
--indent-classes
--indent-switches
--indent-namespaces
--indent-after-parens
--indent-continuation=1
--indent-preproc-block
--indent-preproc-define
--indent-preproc-cond
--indent-col1-comments
--min-conditional-indent=0
--max-continuation-indent=40
--break-blocks
--pad-oper
--pad-comma
--pad-header
--unpad-paren
--align-pointer=type
--align-reference=type
--break-one-line-headers
--add-braces
--attach-return-type
--attach-return-type-decl
--remove-comment-prefix
--max-code-length=80
--mode=c

View File

@@ -1,108 +0,0 @@
#!/usr/bin/env python
import sys, os, subprocess
def escaped(s):
return s.replace("\033", "\\033")
def tput(term, name):
try:
return subprocess.check_output(['tput', '-T%s' % term, name]).decode()
except subprocess.CalledProcessError as e:
return e.output.decode()
def w(s):
if s == None:
return
sys.stdout.write(s)
terminals = {
'xterm' : 'xterm',
'rxvt-256color' : 'rxvt_256color',
'rxvt-unicode' : 'rxvt_unicode',
'linux' : 'linux',
'Eterm' : 'eterm',
'screen' : 'screen'
}
keys = [
"F1", "kf1",
"F2", "kf2",
"F3", "kf3",
"F4", "kf4",
"F5", "kf5",
"F6", "kf6",
"F7", "kf7",
"F8", "kf8",
"F9", "kf9",
"F10", "kf10",
"F11", "kf11",
"F12", "kf12",
"INSERT", "kich1",
"DELETE", "kdch1",
"HOME", "khome",
"END", "kend",
"PGUP", "kpp",
"PGDN", "knp",
"KEY_UP", "kcuu1",
"KEY_DOWN", "kcud1",
"KEY_LEFT", "kcub1",
"KEY_RIGHT", "kcuf1"
]
funcs = [
"T_ENTER_CA", "smcup",
"T_EXIT_CA", "rmcup",
"T_SHOW_CURSOR", "cnorm",
"T_HIDE_CURSOR", "civis",
"T_CLEAR_SCREEN", "clear",
"T_SGR0", "sgr0",
"T_UNDERLINE", "smul",
"T_BOLD", "bold",
"T_BLINK", "blink",
"T_REVERSE", "rev",
"T_ENTER_KEYPAD", "smkx",
"T_EXIT_KEYPAD", "rmkx"
]
def iter_pairs(iterable):
iterable = iter(iterable)
while True:
yield (next(iterable), next(iterable))
def do_term(term, nick):
w("// %s\n" % term)
w("static const char *%s_keys[] = {\n\t" % nick)
for k, v in iter_pairs(keys):
w('"')
w(escaped(tput(term, v)))
w('",')
w(" 0\n};\n")
w("static const char *%s_funcs[] = {\n\t" % nick)
for k,v in iter_pairs(funcs):
w('"')
if v == "sgr":
w("\\033[3%d;4%dm")
elif v == "cup":
w("\\033[%d;%dH")
else:
w(escaped(tput(term, v)))
w('", ')
w("\n};\n\n")
def do_terms(d):
w("static struct term {\n")
w("\tconst char *name;\n")
w("\tconst char **keys;\n")
w("\tconst char **funcs;\n")
w("} terms[] = {\n")
for k, v in d.items():
w('\t{"%s", %s_keys, %s_funcs},\n' % (k, v, v))
w("\t{0, 0, 0},\n")
w("};\n")
for k,v in terminals.items():
do_term(k, v)
do_terms(terminals)

3475
include/termbox2.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,18 @@
# Ly - a TUI display manager # Ly - a TUI display manager
![Ly screenshot](https://user-images.githubusercontent.com/5473047/88958888-65efbf80-d2a1-11ea-8ae5-3f263bce9cce.png "Ly screenshot") ![Ly screenshot](.github/screenshot.png "Ly screenshot")
Ly is a lightweight TUI (ncurses-like) display manager for Linux and BSD. Ly is a lightweight TUI (ncurses-like) display manager for Linux and BSD.
## Dependencies ## Dependencies
- Compile-time: - Compile-time:
- zig 0.12.0 - zig >=0.12.0
- a C standard library - libc
- pam - pam
- xcb - xcb (optional, required by default; needed for X11 support)
- Runtime (with default config): - Runtime (with default config):
- xorg - xorg
- xorg-xauth - xorg-xauth
- mcookie
- tput
- shutdown - shutdown
### Debian ### Debian
@@ -36,6 +34,7 @@ The following desktop environments were tested with success:
- bspwm - bspwm
- budgie - budgie
- cinnamon - cinnamon
- cosmic
- deepin - deepin
- dwl - dwl
- dwm - dwm
@@ -104,7 +103,7 @@ disable getty on Ly's tty to prevent "login" from spawning on top of it
``` ```
### OpenRC ### OpenRC
**NOTE**: On Gentoo, Ly will disable the `display-manager-init` service in order to run. **NOTE 1**: On Gentoo, Ly will disable the `display-manager-init` service in order to run.
Clone, compile and test. Clone, compile and test.
@@ -126,13 +125,15 @@ then you have to disable getty, so it doesn't respawn on top of ly
# rc-update del agetty.tty2 # rc-update del agetty.tty2
``` ```
**NOTE 2**: To avoid a console spawning on top on Ly, comment out the appropriate line from /etc/inittab (default is 2).
### runit ### runit
``` ```
# zig build installrunit # zig build installrunit
# ln -s /etc/sv/ly /var/service/ # ln -s /etc/sv/ly /var/service/
``` ```
By default, ly will run on tty2. To change the tty it must be set in `/etc/ly/config.ini` By default, ly will run on tty2. To change the tty it must be set in `/etc/ly/config.ini`
You should as well disable your existing display manager service if needed, e.g.: You should as well disable your existing display manager service if needed, e.g.:
@@ -148,6 +149,31 @@ you should disable the agetty-tty2 service like this:
# rm /var/service/agetty-tty2 # rm /var/service/agetty-tty2
``` ```
### s6
```
# zig build installs6
```
Then, edit `/etc/s6/config/ttyX.conf` and set `SPAWN="no"`, where X is the TTY ID (e.g. `2`).
Finally, enable the service:
```
# s6-service add default ly-srv
# s6-db-reload
# s6-rc -u change ly-srv
```
### dinit
```
# zig build installdinit
# dinitctl enable ly
```
In addition to the steps above, you will also have to keep a TTY free within `/etc/dinit.d/config/console.conf`.
To do that, change `ACTIVE_CONSOLES` so that the tty that ly should use in `/etc/ly/config.ini` is free.
### Updating ### Updating
You can also install Ly without copying the system service and the configuration file. That's You can also install Ly without copying the system service and the configuration file. That's
called *updating*. To update, simply run: called *updating*. To update, simply run:
@@ -168,6 +194,29 @@ You can install ly from the [`[extra]` repos](https://archlinux.org/packages/ext
$ sudo pacman -S ly $ sudo pacman -S ly
``` ```
## Gentoo Installation
You can install ly from the GURU repository:
Note: If the package is masked, you may need to unmask it using ~amd64 keyword:
```bash
# echo 'x11-misc/ly ~amd64' >> /etc/portage/package.accept_keywords
```
1. Enable the GURU repository:
```bash
# eselect repository enable guru
```
2. Sync the GURU repository:
```bash
# emaint sync -r guru
```
3. Install ly from source:
```bash
# emerge --ask x11-misc/ly
```
## Configuration ## Configuration
You can find all the configuration in `/etc/ly/config.ini`. You can find all the configuration in `/etc/ly/config.ini`.
The file is commented, and includes the default values. The file is commented, and includes the default values.
@@ -196,12 +245,10 @@ Take a look at your .xsession if X doesn't start, as it can interfere
## PSX DOOM fire animation ## PSX DOOM fire animation
To enable the famous PSX DOOM fire described by [Fabien Sanglard](http://fabiensanglard.net/doom_fire_psx/index.html), To enable the famous PSX DOOM fire described by [Fabien Sanglard](http://fabiensanglard.net/doom_fire_psx/index.html),
just uncomment `animate = true` in `/etc/ly/config.ini`. You may also just set `animation = doom` in `/etc/ly/config.ini`. You may also
disable the main box borders with `hide_borders = true`. disable the main box borders with `hide_borders = true`.
## Additional Information ## Additional Information
The name "Ly" is a tribute to the fairy from the game Rayman. The name "Ly" is a tribute to the fairy from the game Rayman.
Ly was tested by oxodao, who is some seriously awesome dude. Ly was tested by oxodao, who is some seriously awesome dude.
## Gentoo (OpenRC) installation tip
To avoid a console spawning on top on Ly, comment out the appropriate line from /etc/inittab (default is 2).

View File

@@ -1,25 +1,4 @@
# The active animation # The color settings in Ly take a digit 0-8 corresponding to:
# none -> Nothing (default)
# doom -> PSX DOOM fire
# matrix -> CMatrix
animation = none
# Format string for clock in top right corner (see strftime specification). Example: %c
clock = null
# Enable/disable big clock
bigclock = false
# The character used to mask the password
asterisk = *
# Erase password input on failure
clear_password = false
# Enable vi keybindings
vi_mode = false
# The `fg` and `bg` color settings take a digit 0-8 corresponding to:
#define TB_DEFAULT 0x00 #define TB_DEFAULT 0x00
#define TB_BLACK 0x01 #define TB_BLACK 0x01
#define TB_RED 0x02 #define TB_RED 0x02
@@ -29,125 +8,212 @@ vi_mode = false
#define TB_MAGENTA 0x06 #define TB_MAGENTA 0x06
#define TB_CYAN 0x07 #define TB_CYAN 0x07
#define TB_WHITE 0x08 #define TB_WHITE 0x08
# The default color varies, but usually it makes the background black and the foreground white.
# You can also combine these colors with the following style attributes using bitwise OR:
#define TB_BOLD 0x0100
#define TB_UNDERLINE 0x0200
#define TB_REVERSE 0x0400
#define TB_ITALIC 0x0800
#define TB_BLINK 0x1000
#define TB_HI_BLACK 0x2000
#define TB_BRIGHT 0x4000
#define TB_DIM 0x8000
# For example, to set the foreground color to red and bold, you would do 0x02 | 0x0100 = 0x0102.
# Note that you must pre-calculate the value because Ly doesn't parse bitwise OR operations in its config.
# #
# Setting both to zero makes `bg` black and `fg` white. To set the actual color palette you are encouraged to use another tool # Moreover, to set the VT color palette, you are encouraged to use another tool such as
# such as [mkinitcpio-colors](https://github.com/evanpurkhiser/mkinitcpio-colors). Note that the color palette defined with # mkinitcpio-colors (https://github.com/evanpurkhiser/mkinitcpio-colors). Note that the color palette defined with
# `mkinitcpio-colors` takes 16 colors (0-15), only values 0-8 are valid for `ly` config and these values do not correspond # mkinitcpio-colors takes 16 colors (0-15), only values 0-8 are valid with Ly and these values do not correspond
# exactly. For instance, in defining palettes with `mkinitcpio-colors` the order is black, dark red, dark green, brown, dark # exactly. For instance, in defining palettes with mkinitcpio-colors, the order is black, dark red, dark green, brown, dark
# blue, dark purple, dark cyan, light gray, dark gray, bright red, bright green, yellow, bright blue, bright purple, bright # blue, dark purple, dark cyan, light gray, dark gray, bright red, bright green, yellow, bright blue, bright purple, bright
# cyan, and white, indexed in that order 0 through 15. For example, the color defined for white (indexed at 15 in the mkinitcpio # cyan, and white, indexed in that order 0 through 15. For example, the color defined for white (indexed at 15 in the mkinitcpio
# config) will be used by `ly` for `fg = 8`. # config) will be used by Ly for fg = 0x0008.
# The active animation
# none -> Nothing
# doom -> PSX DOOM fire
# matrix -> CMatrix
animation = none
# Stop the animation after some time
# 0 -> Run forever
# 1..2e12 -> Stop the animation after this many seconds
animation_timeout_sec = 0
# The character used to mask the password
# If null, the password will be hidden
# Note: you can use a # by escaping it like so: \#
asterisk = *
# The number of failed authentications before a special animation is played... ;)
auth_fails = 10
# Background color id # Background color id
bg = 0 bg = 0x0000
# Foreground color id # Change the state and language of the big clock
fg = 8 # none -> Disabled (default)
# en -> English
# Border color # fa -> Farsi
border_fg = 8 bigclock = none
# Blank main box background # Blank main box background
# Setting to false will make it transparent # Setting to false will make it transparent
blank_box = true blank_box = true
# Remove main box borders # Border foreground color id
hide_borders = false border_fg = 0x0008
# Main box margins # Title to show at the top of the main box
margin_box_h = 2 # If set to null, none will be shown
margin_box_v = 1 box_title = null
# Input boxes length # Brightness increase command
input_len = 34 brightness_down_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q s 10%-
# Max input sizes # Brightness decrease key
max_desktop_len = 100 brightness_down_key = F5
max_login_len = 255
max_password_len = 255
# Input box active by default on startup # Brightness increase command
# Available inputs: session, login, password brightness_up_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q s +10%
default_input = login
# Load the saved desktop and username # Brightness increase key
load = true brightness_up_key = F6
# Save the current desktop and login as defaults # Erase password input on failure
save = true clear_password = false
# Deprecated - Will be removed in a future version # Format string for clock in top right corner (see strftime specification). Example: %c
# New save files are now loaded from the same directory as the config # If null, the clock won't be shown
# Currently used to migrate old save files to the new version clock = null
# File in which to save and load the default desktop and login
save_file = /etc/ly/save
# Remove power management command hints # CMatrix animation foreground color id
hide_key_hints = false cmatrix_fg = 0x0003
# Specifies the key used for shutdown (F1-F12)
shutdown_key = F1
# Specifies the key used for restart (F1-F12)
restart_key = F2
# Specifies the key used for sleep (F1-F12)
sleep_key = F3
# Command executed when pressing shutdown_key
shutdown_cmd = /sbin/shutdown -a now
# Command executed when pressing restart_key
restart_cmd = /sbin/shutdown -r now
# Command executed when pressing sleep key (can be null)
sleep_cmd = null
# Active language
# Available languages are found in /etc/ly/lang/
lang = en
# TTY in use
tty = 2
# Console path # Console path
console_dev = /dev/console console_dev = /dev/console
# Default path. If null, ly doesn't set a path. # Input box active by default on startup
path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin # Available inputs: info_line, session, login, password
default_input = login
# Error background color id
error_bg = 0x0000
# Error foreground color id
# Default is red and bold: TB_RED | TB_BOLD
error_fg = 0x0102
# Foreground color id
fg = 0x0008
# Remove main box borders
hide_borders = false
# Remove power management command hints
hide_key_hints = false
# Initial text to show on the info line
# If set to null, the info line defaults to the hostname
initial_info_text = null
# Input boxes length
input_len = 34
# Active language
# Available languages are found in $CONFIG_DIRECTORY/ly/lang/
lang = en
# Load the saved desktop and username
load = true
# Command executed when logging in
# If null, no command will be executed
# Important: the code itself must end with `exec "$@"` in order to launch the session!
# You can also set environment variables in there, they'll persist until logout
login_cmd = null
# Command executed when logging out
# If null, no command will be executed
# Important: the session will already be terminated when this command is executed, so
# no need to add `exec "$@"` at the end
logout_cmd = null
# Main box horizontal margin
margin_box_h = 2
# Main box vertical margin
margin_box_v = 1
# Event timeout in milliseconds # Event timeout in milliseconds
min_refresh_delta = 5 min_refresh_delta = 5
# Set numlock on/off at startup
numlock = false
# Default path
# If null, ly doesn't set a path
path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
# Command executed when pressing restart_key
restart_cmd = /sbin/shutdown -r now
# Specifies the key used for restart (F1-F12)
restart_key = F2
# Save the current desktop and login as defaults
save = true
# Service name (set to ly to use the provided pam config file) # Service name (set to ly to use the provided pam config file)
service_name = ly service_name = ly
# Terminal reset command (tput is faster) # Session log file path
term_reset_cmd = /usr/bin/tput reset # This will contain stdout and stderr of X11 and Wayland sessions
# By default it's saved in the user's home directory
# Note: this file won't be used in a shell session (due to the need of stdout and stderr)
session_log = ly-session.log
# Terminal restore cursor command # Setup command
term_restore_cursor_cmd = /usr/bin/tput cnorm setup_cmd = $CONFIG_DIRECTORY/ly/setup.sh
# Cookie generator # Command executed when pressing shutdown_key
mcookie_cmd = /usr/bin/mcookie shutdown_cmd = /sbin/shutdown -a now
# Wayland setup command # Specifies the key used for shutdown (F1-F12)
wayland_cmd = /etc/ly/wsetup.sh shutdown_key = F1
# Command executed when pressing sleep key (can be null)
sleep_cmd = null
# Specifies the key used for sleep (F1-F12)
sleep_key = F3
# Center the session name.
text_in_center = false
# TTY in use
tty = $DEFAULT_TTY
# Default vi mode
# normal -> normal mode
# insert -> insert mode
vi_default_mode = normal
# Enable vi keybindings
vi_mode = false
# Wayland desktop environments # Wayland desktop environments
waylandsessions = /usr/share/wayland-sessions waylandsessions = $PREFIX_DIRECTORY/share/wayland-sessions
# xinitrc (hidden if null)
xinitrc = ~/.xinitrc
# Xorg server command # Xorg server command
x_cmd = /usr/bin/X x_cmd = $PREFIX_DIRECTORY/bin/X
# Xorg setup command
x_cmd_setup = /etc/ly/xsetup.sh
# Xorg xauthority edition tool # Xorg xauthority edition tool
xauth_cmd = /usr/bin/xauth xauth_cmd = $PREFIX_DIRECTORY/bin/xauth
# xinitrc
# If null, the xinitrc session will be hidden
xinitrc = ~/.xinitrc
# Xorg desktop environments # Xorg desktop environments
xsessions = /usr/share/xsessions xsessions = $PREFIX_DIRECTORY/share/xsessions

View File

@@ -1,13 +1,19 @@
authenticating = authenticating...
brightness_down = decrease brightness
brightness_up = increase brightness
capslock = capslock capslock = capslock
err_alloc = failed memory allocation err_alloc = failed memory allocation
err_bounds = out-of-bounds index err_bounds = out-of-bounds index
err_brightness_change = failed to change brightness
err_chdir = failed to open home folder err_chdir = failed to open home folder
err_console_dev = failed to access console err_console_dev = failed to access console
err_dgn_oob = log message err_dgn_oob = log message
err_domain = invalid domain err_domain = invalid domain
err_envlist = failed to get envlist
err_hostname = failed to get hostname err_hostname = failed to get hostname
err_mlock = failed to lock password memory err_mlock = failed to lock password memory
err_null = null pointer err_null = null pointer
err_numlock = failed to set numlock
err_pam = pam transaction failed err_pam = pam transaction failed
err_pam_abort = pam transaction aborted err_pam_abort = pam transaction aborted
err_pam_acct_expired = account expired err_pam_acct_expired = account expired
@@ -33,12 +39,15 @@ err_unknown = an unknown error occurred
err_user_gid = failed to set user GID err_user_gid = failed to set user GID
err_user_init = failed to initialize user err_user_init = failed to initialize user
err_user_uid = failed to set user UID err_user_uid = failed to set user UID
err_xauth = xauth command failed
err_xcb_conn = xcb connection failed
err_xsessions_dir = failed to find sessions folder err_xsessions_dir = failed to find sessions folder
err_xsessions_open = failed to open sessions folder err_xsessions_open = failed to open sessions folder
insert = insert insert = insert
login = login login = login
logout = logged out logout = logged out
normal = normal normal = normal
no_x11_support = x11 support disabled at compile-time
numlock = numlock numlock = numlock
password = password password = password
restart = reboot restart = reboot

View File

@@ -1,3 +1,6 @@
authenticating = autenticando...
brightness_down = bajar brillo
brightness_up = subir brillo
capslock = Bloq Mayús capslock = Bloq Mayús
err_alloc = asignación de memoria fallida err_alloc = asignación de memoria fallida
err_bounds = índice fuera de límites err_bounds = índice fuera de límites
@@ -34,12 +37,17 @@ err_user_init = error al inicializar usuario
err_user_uid = error al establecer el UID del usuario err_user_uid = error al establecer el UID del usuario
err_xsessions_dir = error al buscar la carpeta de sesiones err_xsessions_dir = error al buscar la carpeta de sesiones
err_xsessions_open = error al abrir la carpeta de sesiones err_xsessions_open = error al abrir la carpeta de sesiones
login = iniciar sesión insert = insertar
login = usuario
logout = cerrar sesión logout = cerrar sesión
no_x11_support = soporte para x11 deshabilitado en tiempo de compilación
normal = normal
numlock = Bloq Num numlock = Bloq Num
other = otro
password = contraseña password = contraseña
restart = reiniciar restart = reiniciar
shell = shell shell = shell
shutdown = apagar shutdown = apagar
sleep = suspender
wayland = wayland wayland = wayland
xinitrc = xinitrc xinitrc = xinitrc

View File

@@ -1,19 +1,25 @@
capslock = verr.maj authenticating = authentification...
brightness_down = diminuer la luminosité
brightness_up = augmenter la luminosité
capslock = verr.maj
err_alloc = échec d'allocation mémoire err_alloc = échec d'allocation mémoire
err_bounds = indice hors-limite err_bounds = indice hors-limite
err_brightness_change = échec du changement de luminosité
err_chdir = échec de l'ouverture du répertoire home err_chdir = échec de l'ouverture du répertoire home
err_console_dev = échec d'accès à la console err_console_dev = échec d'accès à la console
err_dgn_oob = message err_dgn_oob = message
err_domain = domaine invalide err_domain = domaine invalide
err_envlist = échec de lecture de la liste d'environnement
err_hostname = échec de lecture du nom d'hôte err_hostname = échec de lecture du nom d'hôte
err_mlock = échec du verrouillage mémoire err_mlock = échec du verrouillage mémoire
err_null = pointeur null err_null = pointeur null
err_numlock = échec de modification du verr.num
err_pam = échec de la transaction pam err_pam = échec de la transaction pam
err_pam_abort = transaction pam avortée err_pam_abort = transaction pam avortée
err_pam_acct_expired = compte expiré err_pam_acct_expired = compte expiré
err_pam_auth = erreur d'authentification err_pam_auth = erreur d'authentification
err_pam_authok_reqd = tiquet expiré
err_pam_authinfo_unavail = échec de l'obtention des infos utilisateur err_pam_authinfo_unavail = échec de l'obtention des infos utilisateur
err_pam_authok_reqd = tiquet expiré
err_pam_buf = erreur de mémoire tampon err_pam_buf = erreur de mémoire tampon
err_pam_cred_err = échec de la modification des identifiants err_pam_cred_err = échec de la modification des identifiants
err_pam_cred_expired = identifiants expirés err_pam_cred_expired = identifiants expirés
@@ -29,17 +35,25 @@ err_perm_dir = échec de changement de répertoire
err_perm_group = échec du déclassement des permissions de groupe err_perm_group = échec du déclassement des permissions de groupe
err_perm_user = échec du déclassement des permissions utilisateur err_perm_user = échec du déclassement des permissions utilisateur
err_pwnam = échec de lecture des infos utilisateur err_pwnam = échec de lecture des infos utilisateur
err_unknown = une erreur inconnue est survenue
err_user_gid = échec de modification du GID err_user_gid = échec de modification du GID
err_user_init = échec d'initialisation de l'utilisateur err_user_init = échec d'initialisation de l'utilisateur
err_user_uid = échec de modification du UID err_user_uid = échec de modification du UID
err_xauth = échec de la commande xauth
err_xcb_conn = échec de la connexion xcb
err_xsessions_dir = échec de la recherche du dossier de sessions err_xsessions_dir = échec de la recherche du dossier de sessions
err_xsessions_open = échec de l'ouverture du dossier de sessions err_xsessions_open = échec de l'ouverture du dossier de sessions
insert = insertion
login = identifiant login = identifiant
logout = déconnection logout = déconnecté
normal = normal
no_x11_support = support pour x11 désactivé lors de la compilation
numlock = verr.num numlock = verr.num
password = mot de passe password = mot de passe
restart = redémarrer restart = redémarrer
shell = shell shell = shell
shutdown = éteindre shutdown = éteindre
sleep = veille
wayland = wayland wayland = wayland
xinitrc = xinitrc xinitrc = xinitrc
x11 = x11

8
res/ly-dinit Normal file
View File

@@ -0,0 +1,8 @@
type = process
restart = true
smooth-recovery = true
command = $PREFIX_DIRECTORY/bin/$EXE_NAME
depends-on = loginready
termsignal = HUP
# ly needs access to the console while loginready already occupies it
options = shares-console

View File

@@ -20,16 +20,16 @@ then
fi fi
## Get the tty from the conf file ## Get the tty from the conf file
CONFTTY=$(cat /etc/ly/config.ini | sed -n 's/^tty.*=[^1-9]*// p') CONFTTY=$(cat $CONFIG_DIRECTORY/ly/config.ini | sed -n 's/^tty.*=[^1-9]*// p')
## The execution vars ## The execution vars
# If CONFTTY is empty then default to 2 # If CONFTTY is empty then default to $DEFAULT_TTY
TTY="tty${CONFTTY:-2}" TTY="tty${CONFTTY:-$DEFAULT_TTY}"
TERM=linux TERM=linux
BAUD=38400 BAUD=38400
# If we don't have getty then we should have agetty # If we don't have getty then we should have agetty
command=${commandB:-$commandUL} command=${commandB:-$commandUL}
command_args_foreground="-nl /usr/bin/ly $TTY $BAUD $TERM" command_args_foreground="-nl $PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME $TTY $BAUD $TERM"
depend() { depend() {
after agetty after agetty

View File

@@ -8,5 +8,5 @@ fi
BAUD_RATE=38400 BAUD_RATE=38400
TERM_NAME=linux TERM_NAME=linux
auxtty=$(/bin/cat /etc/ly/config.ini 2>/dev/null 1| /bin/sed -n 's/\(^[[:space:]]*tty[[:space:]]*=[[:space:]]*\)\([[:digit:]][[:digit:]]*\)\(.*\)/\2/p') auxtty=$(/bin/cat $CONFIG_DIRECTORY/ly/config.ini 2>/dev/null 1| /bin/sed -n 's/\(^[[:space:]]*tty[[:space:]]*=[[:space:]]*\)\([[:digit:]][[:digit:]]*\)\(.*\)/\2/p')
TTY=tty${auxtty:-2} TTY=tty${auxtty:-$DEFAULT_TTY}

View File

@@ -10,4 +10,4 @@ elif [ -x /sbin/agetty -o -x /bin/agetty ]; then
GETTY=agetty GETTY=agetty
fi fi
exec setsid ${GETTY} ${GETTY_ARGS} -nl /usr/bin/ly "${TTY}" "${BAUD_RATE}" "${TERM_NAME}" exec setsid ${GETTY} ${GETTY_ARGS} -nl $PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME "${TTY}" "${BAUD_RATE}" "${TERM_NAME}"

2
res/ly-s6/run Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/execlineb -P
exec agetty -L -8 -n -l $PREFIX_DIRECTORY/bin/$EXE_NAME tty$DEFAULT_TTY 115200

1
res/ly-s6/type Normal file
View File

@@ -0,0 +1 @@
longrun

View File

@@ -1,15 +1,14 @@
[Unit] [Unit]
Description=TUI display manager Description=TUI display manager
After=systemd-user-sessions.service plymouth-quit-wait.service After=systemd-user-sessions.service plymouth-quit-wait.service
After=getty@tty2.service After=getty@tty$DEFAULT_TTY.service
Conflicts=getty@tty2.service Conflicts=getty@tty$DEFAULT_TTY.service
[Service] [Service]
Type=idle Type=idle
ExecStart=/usr/bin/ly ExecStart=$PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME
StandardError=journal
StandardInput=tty StandardInput=tty
TTYPath=/dev/tty2 TTYPath=/dev/tty$DEFAULT_TTY
TTYReset=yes TTYReset=yes
TTYVHangup=yes TTYVHangup=yes

View File

@@ -1,6 +1,18 @@
#%PAM-1.0 #%PAM-1.0
auth sufficient pam_unix.so try_first_pass likeauth nullok # empty-password will not pass this, but will not fail causing the next line to get executed
-auth sufficient pam_fprintd.so # We do not want to get errors when pam_fprintd.so is not present
auth include login auth include login
-auth optional pam_gnome_keyring.so
-auth optional pam_kwallet5.so
account include login account include login
password include login password include login
-password optional pam_gnome_keyring.so use_authtok
-session optional pam_systemd.so class=greeter
-session optional pam_elogind.so
session include login session include login
-session optional pam_gnome_keyring.so auto_start
-session optional pam_kwallet5.so auto_start

107
res/setup.sh Executable file
View File

@@ -0,0 +1,107 @@
#!/bin/sh
# Shell environment setup after login
# Copyright (C) 2015-2016 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
# This file is extracted from kde-workspace (kdm/kfrontend/genkdmconf.c)
# Copyright (C) 2001-2005 Oswald Buddenhagen <ossi@kde.org>
# Copyright (C) 2024 The Fairy Glade
# This work is free. You can redistribute it and/or modify it under the
# terms of the Do What The Fuck You Want To Public License, Version 2,
# as published by Sam Hocevar. See the LICENSE file for more details.
# Note that the respective logout scripts are not sourced.
case $SHELL in
*/bash)
[ -z "$BASH" ] && exec $SHELL "$0" "$@"
set +o posix
[ -f "$CONFIG_DIRECTORY"/profile ] && . "$CONFIG_DIRECTORY"/profile
if [ -f "$HOME"/.bash_profile ]; then
. "$HOME"/.bash_profile
elif [ -f "$HOME"/.bash_login ]; then
. "$HOME"/.bash_login
elif [ -f "$HOME"/.profile ]; then
. "$HOME"/.profile
fi
;;
*/zsh)
[ -z "$ZSH_NAME" ] && exec $SHELL "$0" "$@"
[ -d "$CONFIG_DIRECTORY"/zsh ] && zdir="$CONFIG_DIRECTORY"/zsh || zdir="$CONFIG_DIRECTORY"
zhome=${ZDOTDIR:-"$HOME"}
# zshenv is always sourced automatically.
[ -f "$zdir"/zprofile ] && . "$zdir"/zprofile
[ -f "$zhome"/.zprofile ] && . "$zhome"/.zprofile
[ -f "$zdir"/zlogin ] && . "$zdir"/zlogin
[ -f "$zhome"/.zlogin ] && . "$zhome"/.zlogin
emulate -R sh
;;
*/csh|*/tcsh)
# [t]cshrc is always sourced automatically.
# Note that sourcing csh.login after .cshrc is non-standard.
sess_tmp=$(mktemp /tmp/sess-env-XXXXXX)
$SHELL -c "if (-f $CONFIG_DIRECTORY/csh.login) source $CONFIG_DIRECTORY/csh.login; if (-f ~/.login) source ~/.login; /bin/sh -c 'export -p' >! $sess_tmp"
. "$sess_tmp"
rm -f "$sess_tmp"
;;
*/fish)
[ -f "$CONFIG_DIRECTORY"/profile ] && . "$CONFIG_DIRECTORY"/profile
[ -f "$HOME"/.profile ] && . "$HOME"/.profile
sess_tmp=$(mktemp /tmp/sess-env-XXXXXX)
$SHELL --login -c "/bin/sh -c 'export -p' > $sess_tmp"
. "$sess_tmp"
rm -f "$sess_tmp"
;;
*) # Plain sh, ksh, and anything we do not know.
[ -f "$CONFIG_DIRECTORY"/profile ] && . "$CONFIG_DIRECTORY"/profile
[ -f "$HOME"/.profile ] && . "$HOME"/.profile
;;
esac
if [ "$XDG_SESSION_TYPE" = "x11" ]; then
[ -f "$CONFIG_DIRECTORY"/xprofile ] && . "$CONFIG_DIRECTORY"/xprofile
[ -f "$HOME"/.xprofile ] && . "$HOME"/.xprofile
# run all system xinitrc shell scripts.
if [ -d "$CONFIG_DIRECTORY"/X11/xinit/xinitrc.d ]; then
for i in "$CONFIG_DIRECTORY"/X11/xinit/xinitrc.d/* ; do
if [ -x "$i" ]; then
. "$i"
fi
done
fi
# Load Xsession scripts
# OPTIONFILE, USERXSESSION, USERXSESSIONRC and ALTUSERXSESSION are required
# by the scripts to work
xsessionddir="$CONFIG_DIRECTORY"/X11/Xsession.d
export OPTIONFILE="$CONFIG_DIRECTORY"/X11/Xsession.options
export USERXSESSION="$HOME"/.xsession
export USERXSESSIONRC="$HOME"/.xsessionrc
export ALTUSERXSESSION="$HOME"/.Xsession
if [ -d "$xsessionddir" ]; then
for i in $(ls "$xsessionddir"); do
script="$xsessionddir/$i"
echo "Loading X session script $script"
if [ -r "$script" ] && [ -f "$script" ] && expr "$i" : '^[[:alnum:]_-]\+$' > /dev/null; then
. "$script"
fi
done
fi
if [ -d "$CONFIG_DIRECTORY"/X11/Xresources ]; then
for i in "$CONFIG_DIRECTORY"/X11/Xresources/*; do
[ -f "$i" ] && xrdb -merge "$i"
done
elif [ -f "$CONFIG_DIRECTORY"/X11/Xresources ]; then
xrdb -merge "$CONFIG_DIRECTORY"/X11/Xresources
fi
[ -f "$HOME"/.Xresources ] && xrdb -merge "$HOME"/.Xresources
[ -f "$XDG_CONFIG_HOME"/X11/Xresources ] && xrdb -merge "$XDG_CONFIG_HOME"/X11/Xresources
if [ -f "$USERXSESSION" ]; then
. "$USERXSESSION"
fi
fi
exec "$@"

View File

@@ -1,54 +0,0 @@
#!/bin/sh
# wayland-session - run as user
# Copyright (C) 2015-2016 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
# This file is extracted from kde-workspace (kdm/kfrontend/genkdmconf.c)
# Copyright (C) 2001-2005 Oswald Buddenhagen <ossi@kde.org>
# Note that the respective logout scripts are not sourced.
case $SHELL in
*/bash)
[ -z "$BASH" ] && exec $SHELL $0 "$@"
set +o posix
[ -f /etc/profile ] && . /etc/profile
if [ -f $HOME/.bash_profile ]; then
. $HOME/.bash_profile
elif [ -f $HOME/.bash_login ]; then
. $HOME/.bash_login
elif [ -f $HOME/.profile ]; then
. $HOME/.profile
fi
;;
*/zsh)
[ -z "$ZSH_NAME" ] && exec $SHELL $0 "$@"
[ -d /etc/zsh ] && zdir=/etc/zsh || zdir=/etc
zhome=${ZDOTDIR:-$HOME}
# zshenv is always sourced automatically.
[ -f $zdir/zprofile ] && . $zdir/zprofile
[ -f $zhome/.zprofile ] && . $zhome/.zprofile
[ -f $zdir/zlogin ] && . $zdir/zlogin
[ -f $zhome/.zlogin ] && . $zhome/.zlogin
emulate -R sh
;;
*/csh|*/tcsh)
# [t]cshrc is always sourced automatically.
# Note that sourcing csh.login after .cshrc is non-standard.
wlsess_tmp=`mktemp /tmp/wlsess-env-XXXXXX`
$SHELL -c "if (-f /etc/csh.login) source /etc/csh.login; if (-f ~/.login) source ~/.login; /bin/sh -c 'export -p' >! $wlsess_tmp"
. $wlsess_tmp
rm -f $wlsess_tmp
;;
*/fish)
[ -f /etc/profile ] && . /etc/profile
xsess_tmp=`mktemp /tmp/xsess-env-XXXXXX`
$SHELL --login -c "/bin/sh -c 'export -p' > $xsess_tmp"
. $xsess_tmp
rm -f $xsess_tmp
;;
*) # Plain sh, ksh, and anything we do not know.
[ -f /etc/profile ] && . /etc/profile
[ -f $HOME/.profile ] && . $HOME/.profile
;;
esac
exec "$@"

View File

@@ -1,103 +0,0 @@
#! /bin/sh
# Xsession - run as user
# Copyright (C) 2016 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
# This file is extracted from kde-workspace (kdm/kfrontend/genkdmconf.c)
# Copyright (C) 2001-2005 Oswald Buddenhagen <ossi@kde.org>
# Note that the respective logout scripts are not sourced.
case $SHELL in
*/bash)
[ -z "$BASH" ] && exec $SHELL $0 "$@"
set +o posix
[ -f /etc/profile ] && . /etc/profile
if [ -f $HOME/.bash_profile ]; then
. $HOME/.bash_profile
elif [ -f $HOME/.bash_login ]; then
. $HOME/.bash_login
elif [ -f $HOME/.profile ]; then
. $HOME/.profile
fi
;;
*/zsh)
[ -z "$ZSH_NAME" ] && exec $SHELL $0 "$@"
[ -d /etc/zsh ] && zdir=/etc/zsh || zdir=/etc
zhome=${ZDOTDIR:-$HOME}
# zshenv is always sourced automatically.
[ -f $zdir/zprofile ] && . $zdir/zprofile
[ -f $zhome/.zprofile ] && . $zhome/.zprofile
[ -f $zdir/zlogin ] && . $zdir/zlogin
[ -f $zhome/.zlogin ] && . $zhome/.zlogin
emulate -R sh
;;
*/csh|*/tcsh)
# [t]cshrc is always sourced automatically.
# Note that sourcing csh.login after .cshrc is non-standard.
xsess_tmp=`mktemp /tmp/xsess-env-XXXXXX`
$SHELL -c "if (-f /etc/csh.login) source /etc/csh.login; if (-f ~/.login) source ~/.login; /bin/sh -c 'export -p' >! $xsess_tmp"
. $xsess_tmp
rm -f $xsess_tmp
;;
*/fish)
[ -f /etc/profile ] && . /etc/profile
xsess_tmp=`mktemp /tmp/xsess-env-XXXXXX`
$SHELL --login -c "/bin/sh -c 'export -p' > $xsess_tmp"
. $xsess_tmp
rm -f $xsess_tmp
;;
*) # Plain sh, ksh, and anything we do not know.
[ -f /etc/profile ] && . /etc/profile
[ -f $HOME/.profile ] && . $HOME/.profile
;;
esac
[ -f /etc/xprofile ] && . /etc/xprofile
[ -f $HOME/.xprofile ] && . $HOME/.xprofile
# run all system xinitrc shell scripts.
if [ -d /etc/X11/xinit/xinitrc.d ]; then
for i in /etc/X11/xinit/xinitrc.d/* ; do
if [ -x "$i" ]; then
. "$i"
fi
done
fi
# Load Xsession scripts
# OPTIONFILE, USERXSESSION, USERXSESSIONRC and ALTUSERXSESSION are required
# by the scripts to work
xsessionddir="/etc/X11/Xsession.d"
OPTIONFILE=/etc/X11/Xsession.options
USERXSESSION=$HOME/.xsession
USERXSESSIONRC=$HOME/.xsessionrc
ALTUSERXSESSION=$HOME/.Xsession
if [ -d "$xsessionddir" ]; then
for i in `ls $xsessionddir`; do
script="$xsessionddir/$i"
echo "Loading X session script $script"
if [ -r "$script" -a -f "$script" ] && expr "$i" : '^[[:alnum:]_-]\+$' > /dev/null; then
. "$script"
fi
done
fi
if [ -d /etc/X11/Xresources ]; then
for i in /etc/X11/Xresources/*; do
[ -f $i ] && xrdb -merge $i
done
elif [ -f /etc/X11/Xresources ]; then
xrdb -merge /etc/X11/Xresources
fi
[ -f $HOME/.Xresources ] && xrdb -merge $HOME/.Xresources
[ -f $XDG_CONFIG_HOME/X11/Xresources ] && xrdb -merge $XDG_CONFIG_HOME/X11/Xresources
if [ -f "$USERXSESSION" ]; then
. "$USERXSESSION"
fi
if [ -z "$*" ]; then
exec xmessage -center -buttons OK:0 -default OK "Sorry, $DESKTOP_SESSION is no valid session."
else
exec $@
fi

View File

@@ -9,7 +9,7 @@ const termbox = interop.termbox;
const Doom = @This(); const Doom = @This();
pub const STEPS = 13; pub const STEPS = 13;
pub const FIRE = [_]termbox.tb_cell{ pub const FIRE = [_]utils.Cell{
utils.initCell(' ', 9, 0), utils.initCell(' ', 9, 0),
utils.initCell(0x2591, 2, 0), // Red utils.initCell(0x2591, 2, 0), // Red
utils.initCell(0x2592, 2, 0), // Red utils.initCell(0x2592, 2, 0), // Red
@@ -56,7 +56,7 @@ pub fn draw(self: Doom) void {
const source = y * self.terminal_buffer.width + x; const source = y * self.terminal_buffer.width + x;
const random = (self.terminal_buffer.random.int(u16) % 7) & 3; const random = (self.terminal_buffer.random.int(u16) % 7) & 3;
var dest = source - random + 1; var dest = (source - @min(source, random)) + 1;
if (self.terminal_buffer.width > dest) dest = 0 else dest -= self.terminal_buffer.width; if (self.terminal_buffer.width > dest) dest = 0 else dest -= self.terminal_buffer.width;
const buffer_source = self.buffer[source]; const buffer_source = self.buffer[source];
@@ -68,13 +68,13 @@ pub fn draw(self: Doom) void {
if (buffer_dest > 12) buffer_dest = 0; if (buffer_dest > 12) buffer_dest = 0;
self.buffer[dest] = @intCast(buffer_dest); self.buffer[dest] = @intCast(buffer_dest);
self.terminal_buffer.buffer[dest] = FIRE[buffer_dest]; self.terminal_buffer.buffer[dest] = toTermboxCell(FIRE[buffer_dest]);
self.terminal_buffer.buffer[source] = FIRE[buffer_source]; self.terminal_buffer.buffer[source] = toTermboxCell(FIRE[buffer_source]);
} }
} }
} }
fn initBuffer(buffer: []u8, width: u64) void { fn initBuffer(buffer: []u8, width: usize) void {
const length = buffer.len - width; const length = buffer.len - width;
const slice_start = buffer[0..length]; const slice_start = buffer[0..length];
const slice_end = buffer[length..]; const slice_end = buffer[length..];
@@ -82,3 +82,11 @@ fn initBuffer(buffer: []u8, width: u64) void {
@memset(slice_start, 0); @memset(slice_start, 0);
@memset(slice_end, STEPS - 1); @memset(slice_end, STEPS - 1);
} }
fn toTermboxCell(cell: utils.Cell) termbox.tb_cell {
return .{
.ch = cell.ch,
.fg = cell.fg,
.bg = cell.bg,
};
}

View File

@@ -34,8 +34,9 @@ dots: []Dot,
lines: []Line, lines: []Line,
frame: u64, frame: u64,
count: u64, count: u64,
fg_ini: u16,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !Matrix { pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_ini: u16) !Matrix {
const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1)); const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1));
const lines = try allocator.alloc(Line, terminal_buffer.width); const lines = try allocator.alloc(Line, terminal_buffer.width);
@@ -48,6 +49,7 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !Matrix {
.lines = lines, .lines = lines,
.frame = 3, .frame = 3,
.count = 0, .count = 0,
.fg_ini = fg_ini,
}; };
} }
@@ -75,9 +77,9 @@ pub fn draw(self: *Matrix) void {
if (self.frame > 4) self.frame = 1; if (self.frame > 4) self.frame = 1;
self.count = 0; self.count = 0;
var x: u64 = 0; var x: usize = 0;
while (x < self.terminal_buffer.width) : (x += 2) { while (x < self.terminal_buffer.width) : (x += 2) {
var tail: u64 = 0; var tail: usize = 0;
var line = &self.lines[x]; var line = &self.lines[x];
if (self.frame <= line.update) continue; if (self.frame <= line.update) continue;
@@ -93,7 +95,7 @@ pub fn draw(self: *Matrix) void {
} }
} }
var y: u64 = 0; var y: usize = 0;
var first_col = true; var first_col = true;
var seg_len: u64 = 0; var seg_len: u64 = 0;
height_it: while (y <= buf_height) : (y += 1) { height_it: while (y <= buf_height) : (y += 1) {
@@ -140,34 +142,35 @@ pub fn draw(self: *Matrix) void {
} }
} }
var x: u64 = 0; var x: usize = 0;
while (x < buf_width) : (x += 2) { while (x < buf_width) : (x += 2) {
var y: u64 = 1; var y: usize = 1;
while (y <= self.terminal_buffer.height) : (y += 1) { while (y <= self.terminal_buffer.height) : (y += 1) {
const dot = self.dots[buf_width * y + x]; const dot = self.dots[buf_width * y + x];
var fg: u32 = @intCast(termbox.TB_GREEN);
var fg = self.fg_ini;
if (dot.value == -1 or dot.value == ' ') { if (dot.value == -1 or dot.value == ' ') {
termbox.tb_change_cell(@intCast(x), @intCast(y - 1), ' ', fg, termbox.TB_DEFAULT); _ = termbox.tb_set_cell(@intCast(x), @intCast(y - 1), ' ', fg, termbox.TB_DEFAULT);
continue; continue;
} }
if (dot.is_head) fg = @intCast(termbox.TB_WHITE | termbox.TB_BOLD); 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); _ = termbox.tb_set_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 { fn initBuffers(dots: []Dot, lines: []Line, width: usize, height: usize, random: Random) void {
var y: u64 = 0; var y: usize = 0;
while (y <= height) : (y += 1) { while (y <= height) : (y += 1) {
var x: u64 = 0; var x: usize = 0;
while (x < width) : (x += 2) { while (x < width) : (x += 2) {
dots[y * width + x].value = -1; dots[y * width + x].value = -1;
} }
} }
var x: u64 = 0; var x: usize = 0;
while (x < width) : (x += 2) { while (x < width) : (x += 2) {
var line = lines[x]; var line = lines[x];
const h: isize = @intCast(height); const h: isize = @intCast(height);

View File

@@ -1,15 +1,78 @@
const std = @import("std"); const std = @import("std");
const build_options = @import("build_options");
const builtin = @import("builtin");
const enums = @import("enums.zig"); const enums = @import("enums.zig");
const interop = @import("interop.zig"); const interop = @import("interop.zig");
const TerminalBuffer = @import("tui/TerminalBuffer.zig"); const TerminalBuffer = @import("tui/TerminalBuffer.zig");
const Desktop = @import("tui/components/Desktop.zig"); const Session = @import("tui/components/Session.zig");
const Text = @import("tui/components/Text.zig"); const Text = @import("tui/components/Text.zig");
const Config = @import("config/Config.zig"); const Config = @import("config/Config.zig");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Md5 = std.crypto.hash.Md5;
const utmp = interop.utmp; const utmp = interop.utmp;
const Utmp = utmp.utmp; const Utmp = utmp.utmpx;
const SharedError = @import("SharedError.zig"); const SharedError = @import("SharedError.zig");
// When setting the currentLogin you must deallocate the previous currentLogin first
pub var currentLogin: ?[:0]const u8 = null;
pub var asyncPamHandle: ?*interop.pam.pam_handle = null;
pub var current_environment: ?Session.Environment = null;
fn environment_equals(e0: Session.Environment, e1: Session.Environment) bool {
if (!std.mem.eql(u8, e0.cmd, e1.cmd)) {
return false;
}
if (!std.mem.eql(u8, e0.name, e1.name)) {
return false;
}
if (!std.mem.eql(u8, e0.specifier, e1.specifier)) {
return false;
}
if (!(e0.xdg_desktop_names == null and e1.xdg_desktop_names == null) or
(e0.xdg_desktop_names != null and e1.xdg_desktop_names != null and !std.mem.eql(u8, e0.xdg_desktop_names.?, e1.xdg_desktop_names.?)))
{
return false;
}
if (!(e0.xdg_session_desktop == null and e1.xdg_session_desktop == null) or
(e0.xdg_session_desktop != null and e1.xdg_session_desktop != null and !std.mem.eql(u8, e0.xdg_session_desktop.?, e1.xdg_session_desktop.?)))
{
return false;
}
if (e0.display_server != e1.display_server) {
return false;
}
return true;
}
pub fn automaticLogin(config: Config, login: [:0]const u8, environment: Session.Environment, wakesem: *std.Thread.Semaphore) !void {
while (asyncPamHandle == null and currentLogin != null and std.mem.eql(u8, currentLogin.?, login)) {
if (authenticate(config, login, "", environment)) |handle| {
if (currentLogin != null and !std.mem.eql(u8, currentLogin.?, login) and environment_equals(current_environment.?, environment)) {
return;
}
asyncPamHandle = handle;
wakesem.post();
return;
} else |_| {}
}
}
pub fn startAutomaticLogin(allocator: std.mem.Allocator, config: Config, login: Text, environment: Session.Environment, wakesem: *std.Thread.Semaphore) !void {
if (currentLogin) |clogin| {
allocator.free(clogin);
currentLogin = null;
}
const login_text = try allocator.dupeZ(u8, login.text.items);
currentLogin = login_text;
var handle = try std.Thread.spawn(.{}, automaticLogin, .{
config,
login_text,
environment,
wakesem,
});
handle.detach();
}
var xorg_pid: std.posix.pid_t = 0; var xorg_pid: std.posix.pid_t = 0;
pub fn xorgSignalHandler(i: c_int) callconv(.C) void { pub fn xorgSignalHandler(i: c_int) callconv(.C) void {
if (xorg_pid > 0) _ = std.c.kill(xorg_pid, i); if (xorg_pid > 0) _ = std.c.kill(xorg_pid, i);
@@ -20,14 +83,20 @@ pub fn sessionSignalHandler(i: c_int) callconv(.C) void {
if (child_pid > 0) _ = std.c.kill(child_pid, i); if (child_pid > 0) _ = std.c.kill(child_pid, i);
} }
pub fn authenticate(config: Config, desktop: Desktop, login: [:0]const u8, password: [:0]const u8) !void { pub fn authenticate(
config: Config,
login: [:0]const u8,
password: [:0]const u8,
environment: Session.Environment,
) !*interop.pam.pam_handle {
var pam_tty_buffer: [6]u8 = undefined;
const pam_tty_str = try std.fmt.bufPrintZ(&pam_tty_buffer, "tty{d}", .{config.tty});
var tty_buffer: [2]u8 = undefined; var tty_buffer: [2]u8 = undefined;
const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{config.tty}); const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{config.tty});
const current_environment = desktop.environments.items[desktop.current];
// Set the XDG environment variables // Set the XDG environment variables
setXdgSessionEnv(current_environment.display_server); setXdgSessionEnv(environment.display_server);
try setXdgEnv(tty_str, current_environment.xdg_session_desktop, current_environment.xdg_desktop_names orelse ""); try setXdgEnv(tty_str, environment.xdg_session_desktop orelse "", environment.xdg_desktop_names orelse "");
// Open the PAM session // Open the PAM session
var credentials = [_:null]?[*:0]const u8{ login, password }; var credentials = [_:null]?[*:0]const u8{ login, password };
@@ -38,10 +107,15 @@ pub fn authenticate(config: Config, desktop: Desktop, login: [:0]const u8, passw
}; };
var handle: ?*interop.pam.pam_handle = undefined; var handle: ?*interop.pam.pam_handle = undefined;
var status = interop.pam.pam_start(config.service_name.ptr, null, &conv, &handle); // Do the PAM routine
var status = interop.pam.pam_start(config.service_name, null, &conv, &handle);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
errdefer _ = interop.pam.pam_end(handle, status);
// Set PAM_TTY as the current TTY. This is required in case it isn't being set by another PAM module
status = interop.pam.pam_set_item(handle, interop.pam.PAM_TTY, pam_tty_str.ptr);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
// Do the PAM routine
status = interop.pam.pam_authenticate(handle, 0); status = interop.pam.pam_authenticate(handle, 0);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
@@ -49,24 +123,35 @@ pub fn authenticate(config: Config, desktop: Desktop, login: [:0]const u8, passw
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED); status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); errdefer _ = interop.pam.pam_end(handle, status);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
return handle.?;
}
pub fn finaliseAuth(config: Config, environment: Session.Environment, handle: ?*interop.pam.pam_handle, login: [:0]const u8) !void {
var status: c_int = undefined;
defer status = interop.pam.pam_end(handle, status);
defer status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED);
// Open the PAM session
status = interop.pam.pam_open_session(handle, 0); status = interop.pam.pam_open_session(handle, 0);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
defer status = interop.pam.pam_close_session(handle, status);
var pwd: *interop.passwd = undefined; var pwd: *interop.pwd.passwd = undefined;
{ {
defer interop.endpwent(); defer interop.pwd.endpwent();
// Get password structure from username // Get password structure from username
pwd = interop.getpwnam(login.ptr) orelse return error.GetPasswordNameFailed; pwd = interop.pwd.getpwnam(login) orelse return error.GetPasswordNameFailed;
} }
// Set user shell if it hasn't already been set // Set user shell if it hasn't already been set
if (pwd.pw_shell[0] == 0) { if (pwd.pw_shell == null) {
interop.setusershell(); interop.unistd.setusershell();
pwd.pw_shell = interop.getusershell(); pwd.pw_shell = interop.unistd.getusershell();
interop.endusershell(); interop.unistd.endusershell();
} }
var shared_err = try SharedError.init(); var shared_err = try SharedError.init();
@@ -74,105 +159,102 @@ pub fn authenticate(config: Config, desktop: Desktop, login: [:0]const u8, passw
child_pid = try std.posix.fork(); child_pid = try std.posix.fork();
if (child_pid == 0) { if (child_pid == 0) {
startSession(config, pwd, handle, current_environment) catch |e| { startSession(config, pwd, handle, environment) catch |e| {
shared_err.writeError(e); shared_err.writeError(e);
std.process.exit(1); std.process.exit(1);
}; };
std.process.exit(0); std.process.exit(0);
} }
var entry: Utmp = std.mem.zeroes(Utmp); var entry = std.mem.zeroes(Utmp);
addUtmpEntry(&entry, pwd.pw_name, child_pid) catch {};
// If we receive SIGTERM, forward it to child_pid {
const act = std.posix.Sigaction{ // If an error occurs here, we can send SIGTERM to the session
.handler = .{ .handler = &sessionSignalHandler }, errdefer cleanup: {
.mask = std.posix.empty_sigset, _ = std.posix.kill(child_pid, std.posix.SIG.TERM) catch break :cleanup;
.flags = 0, _ = std.posix.waitpid(child_pid, 0);
}; }
try std.posix.sigaction(std.posix.SIG.TERM, &act, null);
// If we receive SIGTERM, forward it to child_pid
const act = std.posix.Sigaction{
.handler = .{ .handler = &sessionSignalHandler },
.mask = std.posix.empty_sigset,
.flags = 0,
};
try std.posix.sigaction(std.posix.SIG.TERM, &act, null);
try addUtmpEntry(&entry, pwd.pw_name.?, child_pid);
}
// Wait for the session to stop // Wait for the session to stop
_ = std.posix.waitpid(child_pid, 0); _ = std.posix.waitpid(child_pid, 0);
removeUtmpEntry(&entry); removeUtmpEntry(&entry);
try resetTerminal(pwd.pw_shell, config.term_reset_cmd);
// Close the PAM session
status = interop.pam.pam_close_session(handle, 0);
if (status != 0) return pamDiagnose(status);
status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED);
if (status != 0) return pamDiagnose(status);
status = interop.pam.pam_end(handle, status);
if (status != 0) return pamDiagnose(status);
if (shared_err.readError()) |err| return err; if (shared_err.readError()) |err| return err;
} }
fn startSession( fn startSession(
config: Config, config: Config,
pwd: *interop.passwd, pwd: *interop.pwd.passwd,
handle: ?*interop.pam.pam_handle, handle: ?*interop.pam.pam_handle,
current_environment: Desktop.Environment, environment: Session.Environment,
) !void { ) !void {
var status: c_int = 0; if (builtin.os.tag == .freebsd) {
status = interop.initgroups(pwd.pw_name, pwd.pw_gid); // FreeBSD has initgroups() in unistd
if (status != 0) return error.GroupInitializationFailed; const status = interop.unistd.initgroups(pwd.pw_name, pwd.pw_gid);
if (status != 0) return error.GroupInitializationFailed;
std.posix.setgid(pwd.pw_gid) catch return error.SetUserGidFailed; // FreeBSD sets the GID and UID with setusercontext()
std.posix.setuid(pwd.pw_uid) catch return error.SetUserUidFailed; const result = interop.pwd.setusercontext(null, pwd, pwd.pw_uid, interop.pwd.LOGIN_SETALL);
if (result != 0) return error.SetUserUidFailed;
} else {
const status = interop.grp.initgroups(pwd.pw_name, pwd.pw_gid);
if (status != 0) return error.GroupInitializationFailed;
std.posix.setgid(pwd.pw_gid) catch return error.SetUserGidFailed;
std.posix.setuid(pwd.pw_uid) catch return error.SetUserUidFailed;
}
// Set up the environment // Set up the environment
try initEnv(pwd, config.path); try initEnv(pwd, config.path);
// Set the PAM variables // Set the PAM variables
const pam_env_vars = interop.pam.pam_getenvlist(handle); const pam_env_vars: ?[*:null]?[*:0]u8 = interop.pam.pam_getenvlist(handle);
if (pam_env_vars == null) return error.GetEnvListFailed;
var index: usize = 0; const env_list = std.mem.span(pam_env_vars.?);
while (true) : (index += 1) { for (env_list) |env_var| _ = interop.stdlib.putenv(env_var);
const pam_env_var = pam_env_vars[index];
if (pam_env_var == null) break;
_ = interop.putenv(pam_env_var); // Change to the user's home directory
} std.posix.chdirZ(pwd.pw_dir.?) catch return error.ChangeDirectoryFailed;
// Execute what the user requested // Execute what the user requested
std.posix.chdirZ(pwd.pw_dir) catch return error.ChangeDirectoryFailed; switch (environment.display_server) {
.wayland => try executeWaylandCmd(pwd.pw_shell.?, config, environment.cmd),
try resetTerminal(pwd.pw_shell, config.term_reset_cmd); .shell => try executeShellCmd(pwd.pw_shell.?, config),
.xinitrc, .x11 => if (build_options.enable_x11_support) {
switch (current_environment.display_server) {
.wayland => try executeWaylandCmd(pwd.pw_shell, config.wayland_cmd, current_environment.cmd),
.shell => try executeShellCmd(pwd.pw_shell),
.xinitrc, .x11 => {
var vt_buf: [5]u8 = undefined; var vt_buf: [5]u8 = undefined;
const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{config.tty}); const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{config.tty});
try executeX11Cmd(pwd.pw_shell, pwd.pw_dir, config, current_environment.cmd, vt); try executeX11Cmd(pwd.pw_shell.?, pwd.pw_dir.?, config, environment.cmd, vt);
}, },
} }
} }
fn initEnv(pwd: *interop.passwd, path_env: ?[:0]const u8) !void { fn initEnv(pwd: *interop.pwd.passwd, path_env: ?[:0]const u8) !void {
const term_env = std.posix.getenv("TERM"); _ = interop.stdlib.setenv("HOME", pwd.pw_dir, 1);
_ = interop.stdlib.setenv("PWD", pwd.pw_dir, 1);
if (term_env) |term| _ = interop.setenv("TERM", term, 1); _ = interop.stdlib.setenv("SHELL", pwd.pw_shell, 1);
_ = interop.setenv("HOME", pwd.pw_dir, 1); _ = interop.stdlib.setenv("USER", pwd.pw_name, 1);
_ = interop.setenv("PWD", pwd.pw_dir, 1); _ = interop.stdlib.setenv("LOGNAME", pwd.pw_name, 1);
_ = interop.setenv("SHELL", pwd.pw_shell, 1);
_ = interop.setenv("USER", pwd.pw_name, 1);
_ = interop.setenv("LOGNAME", pwd.pw_name, 1);
if (path_env) |path| { if (path_env) |path| {
const status = interop.setenv("PATH", path, 1); const status = interop.stdlib.setenv("PATH", path, 1);
if (status != 0) return error.SetPathFailed; if (status != 0) return error.SetPathFailed;
} }
} }
fn setXdgSessionEnv(display_server: enums.DisplayServer) void { fn setXdgSessionEnv(display_server: enums.DisplayServer) void {
_ = interop.setenv("XDG_SESSION_TYPE", switch (display_server) { _ = interop.stdlib.setenv("XDG_SESSION_TYPE", switch (display_server) {
.wayland => "wayland", .wayland => "wayland",
.shell => "tty", .shell => "tty",
.xinitrc, .x11 => "x11", .xinitrc, .x11 => "x11",
@@ -180,17 +262,24 @@ fn setXdgSessionEnv(display_server: enums.DisplayServer) void {
} }
fn setXdgEnv(tty_str: [:0]u8, desktop_name: [:0]const u8, xdg_desktop_names: [:0]const u8) !void { fn setXdgEnv(tty_str: [:0]u8, desktop_name: [:0]const u8, xdg_desktop_names: [:0]const u8) !void {
const uid = interop.getuid(); // The "/run/user/%d" directory is not available on FreeBSD. It is much
var uid_buffer: [10 + @sizeOf(u32) + 1]u8 = undefined; // better to stick to the defaults and let applications using
const uid_str = try std.fmt.bufPrintZ(&uid_buffer, "/run/user/{d}", .{uid}); // XDG_RUNTIME_DIR to fall back to directories inside user's home
// directory.
if (builtin.os.tag != .freebsd) {
const uid = interop.unistd.getuid();
var uid_buffer: [10 + @sizeOf(u32) + 1]u8 = undefined;
const uid_str = try std.fmt.bufPrintZ(&uid_buffer, "/run/user/{d}", .{uid});
_ = interop.setenv("XDG_CURRENT_DESKTOP", xdg_desktop_names.ptr, 0); _ = interop.stdlib.setenv("XDG_RUNTIME_DIR", uid_str, 0);
_ = interop.setenv("XDG_RUNTIME_DIR", uid_str.ptr, 0); }
_ = interop.setenv("XDG_SESSION_CLASS", "user", 0);
_ = interop.setenv("XDG_SESSION_ID", "1", 0); _ = interop.stdlib.setenv("XDG_CURRENT_DESKTOP", xdg_desktop_names, 0);
_ = interop.setenv("XDG_SESSION_DESKTOP", desktop_name.ptr, 0); _ = interop.stdlib.setenv("XDG_SESSION_CLASS", "user", 0);
_ = interop.setenv("XDG_SEAT", "seat0", 0); _ = interop.stdlib.setenv("XDG_SESSION_ID", "1", 0);
_ = interop.setenv("XDG_VTNR", tty_str.ptr, 0); _ = interop.stdlib.setenv("XDG_SESSION_DESKTOP", desktop_name, 0);
_ = interop.stdlib.setenv("XDG_SEAT", "seat0", 0);
_ = interop.stdlib.setenv("XDG_VTNR", tty_str, 0);
} }
fn loginConv( fn loginConv(
@@ -207,7 +296,7 @@ fn loginConv(
// Initialise allocated memory to 0 // Initialise allocated memory to 0
// This ensures memory can be freed by pam on success // This ensures memory can be freed by pam on success
for (response) |*r| r.* = std.mem.zeroes(interop.pam.pam_response); @memset(response, std.mem.zeroes(interop.pam.pam_response));
var username: ?[:0]u8 = null; var username: ?[:0]u8 = null;
var password: ?[:0]u8 = null; var password: ?[:0]u8 = null;
@@ -221,7 +310,7 @@ fn loginConv(
status = interop.pam.PAM_BUF_ERR; status = interop.pam.PAM_BUF_ERR;
break :set_credentials; break :set_credentials;
}; };
response[i].resp = username.?.ptr; response[i].resp = username.?;
}, },
interop.pam.PAM_PROMPT_ECHO_OFF => { interop.pam.PAM_PROMPT_ECHO_OFF => {
const data: [*][*:0]u8 = @ptrCast(@alignCast(appdata_ptr)); const data: [*][*:0]u8 = @ptrCast(@alignCast(appdata_ptr));
@@ -229,7 +318,7 @@ fn loginConv(
status = interop.pam.PAM_BUF_ERR; status = interop.pam.PAM_BUF_ERR;
break :set_credentials; break :set_credentials;
}; };
response[i].resp = password.?.ptr; response[i].resp = password.?;
}, },
interop.pam.PAM_ERROR_MSG => { interop.pam.PAM_ERROR_MSG => {
status = interop.pam.PAM_CONV_ERR; status = interop.pam.PAM_CONV_ERR;
@@ -251,17 +340,6 @@ fn loginConv(
return status; return status;
} }
fn resetTerminal(shell: [*:0]const u8, term_reset_cmd: [:0]const u8) !void {
const pid = try std.posix.fork();
if (pid == 0) {
const args = [_:null]?[*:0]const u8{ shell, "-c", term_reset_cmd };
std.posix.execveZ(shell, &args, std.c.environ) catch {};
std.process.exit(1);
}
_ = std.posix.waitpid(pid, 0);
}
fn getFreeDisplay() !u8 { fn getFreeDisplay() !u8 {
var buf: [15]u8 = undefined; var buf: [15]u8 = undefined;
var i: u8 = 0; var i: u8 = 0;
@@ -335,34 +413,57 @@ fn createXauthFile(pwd: [:0]const u8) ![:0]const u8 {
return xauthority; return xauthority;
} }
fn xauth(display_name: [:0]u8, shell: [*:0]const u8, pw_dir: [*:0]const u8, xauth_cmd: []const u8, mcookie_cmd: []const u8) !void { fn mcookie() [Md5.digest_length * 2]u8 {
var buf: [4096]u8 = undefined;
std.crypto.random.bytes(&buf);
var out: [Md5.digest_length]u8 = undefined;
Md5.hash(&buf, &out, .{});
return std.fmt.bytesToHex(&out, .lower);
}
fn xauth(display_name: [:0]u8, shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config) !void {
var pwd_buf: [100]u8 = undefined; var pwd_buf: [100]u8 = undefined;
const pwd = try std.fmt.bufPrintZ(&pwd_buf, "{s}", .{pw_dir}); const pwd = try std.fmt.bufPrintZ(&pwd_buf, "{s}", .{pw_dir});
const xauthority = try createXauthFile(pwd); const xauthority = try createXauthFile(pwd);
_ = interop.setenv("XAUTHORITY", xauthority, 1); _ = interop.stdlib.setenv("XAUTHORITY", xauthority, 1);
_ = interop.setenv("DISPLAY", display_name, 1); _ = interop.stdlib.setenv("DISPLAY", display_name, 1);
const magic_cookie = mcookie();
const pid = try std.posix.fork(); const pid = try std.posix.fork();
if (pid == 0) { if (pid == 0) {
const log_file = try redirectStandardStreams(config.session_log, true);
defer log_file.close();
var cmd_buffer: [1024]u8 = undefined; var cmd_buffer: [1024]u8 = undefined;
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . $({s})", .{ xauth_cmd, display_name, mcookie_cmd }) catch std.process.exit(1); const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . {s}", .{ config.xauth_cmd, display_name, magic_cookie }) catch std.process.exit(1);
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str }; const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
std.posix.execveZ(shell, &args, std.c.environ) catch {}; std.posix.execveZ(shell, &args, std.c.environ) catch {};
std.process.exit(1); std.process.exit(1);
} }
_ = std.posix.waitpid(pid, 0); const status = std.posix.waitpid(pid, 0);
if (status.status != 0) return error.XauthFailed;
} }
fn executeShellCmd(shell: [*:0]const u8) !void { fn executeShellCmd(shell: [*:0]const u8, config: Config) !void {
const args = [_:null]?[*:0]const u8{shell}; // We don't want to redirect stdout and stderr in a shell session
var cmd_buffer: [1024]u8 = undefined;
const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ config.setup_cmd, config.login_cmd orelse "", shell });
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
return std.posix.execveZ(shell, &args, std.c.environ); return std.posix.execveZ(shell, &args, std.c.environ);
} }
fn executeWaylandCmd(shell: [*:0]const u8, wayland_cmd: []const u8, desktop_cmd: []const u8) !void { fn executeWaylandCmd(shell: [*:0]const u8, config: Config, desktop_cmd: []const u8) !void {
const log_file = try redirectStandardStreams(config.session_log, true);
defer log_file.close();
var cmd_buffer: [1024]u8 = undefined; var cmd_buffer: [1024]u8 = undefined;
const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s}", .{ wayland_cmd, desktop_cmd }); const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ config.setup_cmd, config.login_cmd orelse "", desktop_cmd });
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str }; const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
return std.posix.execveZ(shell, &args, std.c.environ); return std.posix.execveZ(shell, &args, std.c.environ);
} }
@@ -371,12 +472,12 @@ fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config, de
const display_num = try getFreeDisplay(); const display_num = try getFreeDisplay();
var buf: [5]u8 = undefined; var buf: [5]u8 = undefined;
const display_name = try std.fmt.bufPrintZ(&buf, ":{d}", .{display_num}); const display_name = try std.fmt.bufPrintZ(&buf, ":{d}", .{display_num});
try xauth(display_name, shell, pw_dir, config.xauth_cmd, config.mcookie_cmd); try xauth(display_name, shell, pw_dir, config);
const pid = try std.posix.fork(); const pid = try std.posix.fork();
if (pid == 0) { if (pid == 0) {
var cmd_buffer: [1024]u8 = undefined; var cmd_buffer: [1024]u8 = undefined;
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ config.x_cmd, display_name, vt }) catch std.process.exit(1); const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s} >{s} 2>&1", .{ config.x_cmd, display_name, vt, config.session_log }) catch std.process.exit(1);
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str }; const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
std.posix.execveZ(shell, &args, std.c.environ) catch {}; std.posix.execveZ(shell, &args, std.c.environ) catch {};
std.process.exit(1); std.process.exit(1);
@@ -388,7 +489,7 @@ fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config, de
xcb = interop.xcb.xcb_connect(null, null); xcb = interop.xcb.xcb_connect(null, null);
ok = interop.xcb.xcb_connection_has_error(xcb); ok = interop.xcb.xcb_connection_has_error(xcb);
std.posix.kill(pid, 0) catch |e| { std.posix.kill(pid, 0) catch |e| {
if (e == error.ProcessNotFound and ok != 0) return; if (e == error.ProcessNotFound and ok != 0) return error.XcbConnectionFailed;
}; };
} }
@@ -399,7 +500,7 @@ fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config, de
xorg_pid = try std.posix.fork(); xorg_pid = try std.posix.fork();
if (xorg_pid == 0) { if (xorg_pid == 0) {
var cmd_buffer: [1024]u8 = undefined; var cmd_buffer: [1024]u8 = undefined;
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s}", .{ config.x_cmd_setup, desktop_cmd }) catch std.process.exit(1); const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s} >{s} 2>&1", .{ config.setup_cmd, config.login_cmd orelse "", desktop_cmd, config.session_log }) catch std.process.exit(1);
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str }; const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
std.posix.execveZ(shell, &args, std.c.environ) catch {}; std.posix.execveZ(shell, &args, std.c.environ) catch {};
std.process.exit(1); std.process.exit(1);
@@ -423,30 +524,39 @@ fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config, de
_ = std.c.waitpid(x_pid, &status, 0); _ = std.c.waitpid(x_pid, &status, 0);
} }
fn redirectStandardStreams(session_log: []const u8, create: bool) !std.fs.File {
const log_file = if (create) (try std.fs.cwd().createFile(session_log, .{ .mode = 0o666 })) else (try std.fs.cwd().openFile(session_log, .{ .mode = .read_write }));
try std.posix.dup2(std.posix.STDOUT_FILENO, std.posix.STDERR_FILENO);
try std.posix.dup2(log_file.handle, std.posix.STDOUT_FILENO);
return log_file;
}
fn addUtmpEntry(entry: *Utmp, username: [*:0]const u8, pid: c_int) !void { fn addUtmpEntry(entry: *Utmp, username: [*:0]const u8, pid: c_int) !void {
entry.ut_type = utmp.USER_PROCESS; entry.ut_type = utmp.USER_PROCESS;
entry.ut_pid = pid; entry.ut_pid = pid;
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
const ttyname = try std.os.getFdPath(0, &buf); const ttyname = try std.os.getFdPath(std.posix.STDIN_FILENO, &buf);
var ttyname_buf: [32]u8 = undefined; var ttyname_buf: [@sizeOf(@TypeOf(entry.ut_line))]u8 = undefined;
_ = try std.fmt.bufPrintZ(&ttyname_buf, "{s}", .{ttyname["/dev/".len..]}); _ = try std.fmt.bufPrintZ(&ttyname_buf, "{s}", .{ttyname["/dev/".len..]});
entry.ut_line = ttyname_buf; entry.ut_line = ttyname_buf;
entry.ut_id = ttyname_buf["tty".len..7].*; entry.ut_id = ttyname_buf["tty".len..7].*;
var username_buf: [32]u8 = undefined; var username_buf: [@sizeOf(@TypeOf(entry.ut_user))]u8 = undefined;
_ = try std.fmt.bufPrintZ(&username_buf, "{s}", .{username}); _ = try std.fmt.bufPrintZ(&username_buf, "{s}", .{username});
entry.ut_user = username_buf; entry.ut_user = username_buf;
var host: [256]u8 = undefined; var host: [@sizeOf(@TypeOf(entry.ut_host))]u8 = undefined;
host[0] = 0; host[0] = 0;
entry.ut_host = host; entry.ut_host = host;
var tv: std.c.timeval = undefined; var tv: interop.system_time.timeval = undefined;
_ = std.c.gettimeofday(&tv, null); _ = interop.system_time.gettimeofday(&tv, null);
entry.ut_tv = .{ entry.ut_tv = .{
.tv_sec = @intCast(tv.tv_sec), .tv_sec = @intCast(tv.tv_sec),
@@ -454,18 +564,18 @@ fn addUtmpEntry(entry: *Utmp, username: [*:0]const u8, pid: c_int) !void {
}; };
entry.ut_addr_v6[0] = 0; entry.ut_addr_v6[0] = 0;
utmp.setutent(); utmp.setutxent();
_ = utmp.pututline(entry); _ = utmp.pututxline(entry);
utmp.endutent(); utmp.endutxent();
} }
fn removeUtmpEntry(entry: *Utmp) void { fn removeUtmpEntry(entry: *Utmp) void {
entry.ut_type = utmp.DEAD_PROCESS; entry.ut_type = utmp.DEAD_PROCESS;
entry.ut_line[0] = 0; entry.ut_line[0] = 0;
entry.ut_user[0] = 0; entry.ut_user[0] = 0;
utmp.setutent(); utmp.setutxent();
_ = utmp.pututline(entry); _ = utmp.pututxline(entry);
utmp.endutent(); utmp.endutxent();
} }
fn pamDiagnose(status: c_int) anyerror { fn pamDiagnose(status: c_int) anyerror {

View File

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

23
src/bigclock/Lang.zig Normal file
View File

@@ -0,0 +1,23 @@
const builtin = @import("builtin");
pub const WIDTH = 5;
pub const HEIGHT = 5;
pub const SIZE = WIDTH * HEIGHT;
pub const X: u32 = if (builtin.os.tag == .linux or builtin.os.tag.isBSD()) 0x2593 else '#';
pub const O: u32 = 0;
pub const LocaleChars = struct {
ZERO: [SIZE]u21,
ONE: [SIZE]u21,
TWO: [SIZE]u21,
THREE: [SIZE]u21,
FOUR: [SIZE]u21,
FIVE: [SIZE]u21,
SIX: [SIZE]u21,
SEVEN: [SIZE]u21,
EIGHT: [SIZE]u21,
NINE: [SIZE]u21,
S: [SIZE]u21,
E: [SIZE]u21,
};

94
src/bigclock/en.zig Normal file
View File

@@ -0,0 +1,94 @@
const Lang = @import("Lang.zig");
const LocaleChars = Lang.LocaleChars;
const X = Lang.X;
const O = Lang.O;
// zig fmt: off
pub const locale_chars = LocaleChars{
.ZERO = [_]u21{
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,
},
.ONE = [_]u21{
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,
},
.TWO = [_]u21{
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,
},
.THREE = [_]u21{
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,
},
.FOUR = [_]u21{
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,
},
.FIVE = [_]u21{
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,
},
.SIX = [_]u21{
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,
},
.SEVEN = [_]u21{
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,
},
.EIGHT = [_]u21{
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,
},
.NINE = [_]u21{
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,
},
.S = [_]u21{
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,
},
.E = [_]u21{
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

94
src/bigclock/fa.zig Normal file
View File

@@ -0,0 +1,94 @@
const Lang = @import("Lang.zig");
const LocaleChars = Lang.LocaleChars;
const X = Lang.X;
const O = Lang.O;
// zig fmt: off
pub const locale_chars = LocaleChars{
.ZERO = [_]u21{
O,O,O,O,O,
O,O,X,O,O,
O,X,O,X,O,
O,O,X,O,O,
O,O,O,O,O,
},
.ONE = [_]u21{
O,O,X,O,O,
O,X,X,O,O,
O,O,X,O,O,
O,O,X,O,O,
O,O,X,O,O,
},
.TWO = [_]u21{
O,X,O,X,O,
O,X,X,X,O,
O,X,O,O,O,
O,X,O,O,O,
O,X,O,O,O,
},
.THREE = [_]u21{
X,O,X,O,X,
X,X,X,X,X,
X,O,O,O,O,
X,O,O,O,O,
X,O,O,O,O,
},
.FOUR = [_]u21{
O,X,O,X,X,
O,X,X,O,O,
O,X,X,X,X,
O,X,O,O,O,
O,X,O,O,O,
},
.FIVE = [_]u21{
O,O,X,X,O,
O,X,O,O,X,
X,O,O,O,X,
X,O,X,O,X,
O,X,O,X,O,
},
.SIX = [_]u21{
O,X,X,O,O,
O,X,O,O,X,
O,O,X,O,O,
O,X,O,O,O,
X,O,O,O,O,
},
.SEVEN = [_]u21{
X,O,O,O,X,
X,O,O,O,X,
O,X,O,X,O,
O,X,O,X,O,
O,O,X,O,O,
},
.EIGHT = [_]u21{
O,O,O,X,O,
O,O,X,O,X,
O,O,X,O,X,
O,X,O,O,X,
O,X,O,O,X,
},
.NINE = [_]u21{
O,X,X,X,O,
O,X,O,X,O,
O,X,X,X,O,
O,O,O,X,O,
O,O,O,X,O,
},
.S = [_]u21{
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,
},
.E = [_]u21{
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

View File

@@ -3,48 +3,59 @@ const enums = @import("../enums.zig");
const Animation = enums.Animation; const Animation = enums.Animation;
const Input = enums.Input; const Input = enums.Input;
const ViMode = enums.ViMode;
const Bigclock = enums.Bigclock;
animation: Animation = .none, animation: Animation = .none,
asterisk: u8 = '*', animation_timeout_sec: u12 = 0,
bg: u8 = 0, asterisk: ?u8 = '*',
bigclock: bool = false, auth_fails: u64 = 10,
bg: u16 = 0,
bigclock: Bigclock = .none,
blank_box: bool = true, blank_box: bool = true,
border_fg: u8 = 8, border_fg: u16 = 8,
box_title: ?[]const u8 = null,
brightness_down_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q s 10%-",
brightness_down_key: []const u8 = "F5",
brightness_up_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q s +10%",
brightness_up_key: []const u8 = "F6",
clear_password: bool = false, clear_password: bool = false,
clock: ?[:0]const u8 = null, clock: ?[:0]const u8 = null,
console_dev: [:0]const u8 = "/dev/console", cmatrix_fg: u16 = 3,
console_dev: []const u8 = "/dev/console",
default_input: Input = .login, default_input: Input = .login,
fg: u8 = 8, error_bg: u16 = 0,
error_fg: u16 = 258,
fg: u16 = 8,
hide_borders: bool = false, hide_borders: bool = false,
hide_key_hints: bool = false, hide_key_hints: bool = false,
initial_info_text: ?[]const u8 = null,
input_len: u8 = 34, input_len: u8 = 34,
lang: []const u8 = "en", lang: []const u8 = "en",
load: bool = true, load: bool = true,
login_cmd: ?[]const u8 = null,
logout_cmd: ?[]const u8 = null,
margin_box_h: u8 = 2, margin_box_h: u8 = 2,
margin_box_v: u8 = 1, margin_box_v: u8 = 1,
max_desktop_len: u8 = 100,
max_login_len: u8 = 255,
max_password_len: u8 = 255,
mcookie_cmd: []const u8 = "/usr/bin/mcookie",
min_refresh_delta: u16 = 5, min_refresh_delta: u16 = 5,
path: ?[:0]const u8 = "/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin", numlock: bool = false,
path: ?[:0]const u8 = "/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin",
restart_cmd: []const u8 = "/sbin/shutdown -r now", restart_cmd: []const u8 = "/sbin/shutdown -r now",
restart_key: []const u8 = "F2", restart_key: []const u8 = "F2",
save: bool = true, save: bool = true,
save_file: []const u8 = "/etc/ly/save",
service_name: [:0]const u8 = "ly", service_name: [:0]const u8 = "ly",
session_log: []const u8 = "ly-session.log",
setup_cmd: []const u8 = build_options.config_directory ++ "/ly/setup.sh",
shutdown_cmd: []const u8 = "/sbin/shutdown -a now", shutdown_cmd: []const u8 = "/sbin/shutdown -a now",
shutdown_key: []const u8 = "F1", shutdown_key: []const u8 = "F1",
sleep_cmd: ?[]const u8 = null, sleep_cmd: ?[]const u8 = null,
sleep_key: []const u8 = "F3", sleep_key: []const u8 = "F3",
term_reset_cmd: [:0]const u8 = "/usr/bin/tput reset", text_in_center: bool = false,
term_restore_cursor_cmd: []const u8 = "/usr/bin/tput cnorm", tty: u8 = build_options.tty,
tty: u8 = 2, vi_default_mode: ViMode = .normal,
vi_mode: bool = false, vi_mode: bool = false,
wayland_cmd: []const u8 = build_options.data_directory ++ "/wsetup.sh", waylandsessions: []const u8 = build_options.prefix_directory ++ "/share/wayland-sessions",
waylandsessions: []const u8 = "/usr/share/wayland-sessions", x_cmd: []const u8 = build_options.prefix_directory ++ "/bin/X",
x_cmd: []const u8 = "/usr/bin/X", xauth_cmd: []const u8 = build_options.prefix_directory ++ "/bin/xauth",
xinitrc: ?[]const u8 = "~/.xinitrc", xinitrc: ?[]const u8 = "~/.xinitrc",
x_cmd_setup: []const u8 = build_options.data_directory ++ "/xsetup.sh", xsessions: []const u8 = build_options.prefix_directory ++ "/share/xsessions",
xauth_cmd: []const u8 = "/usr/bin/xauth",
xsessions: []const u8 = "/usr/share/xsessions",

View File

@@ -1,13 +1,20 @@
authenticating: []const u8 = "authenticating...",
brightness_down: []const u8 = "decrease brightness",
brightness_up: []const u8 = "increase brightness",
capslock: []const u8 = "capslock", capslock: []const u8 = "capslock",
err_alloc: []const u8 = "failed memory allocation", err_alloc: []const u8 = "failed memory allocation",
err_bounds: []const u8 = "out-of-bounds index", err_bounds: []const u8 = "out-of-bounds index",
err_brightness_change: []const u8 = "failed to change brightness",
err_chdir: []const u8 = "failed to open home folder", err_chdir: []const u8 = "failed to open home folder",
err_config: []const u8 = "unable to parse config file",
err_console_dev: []const u8 = "failed to access console", err_console_dev: []const u8 = "failed to access console",
err_dgn_oob: []const u8 = "log message", err_dgn_oob: []const u8 = "log message",
err_domain: []const u8 = "invalid domain", err_domain: []const u8 = "invalid domain",
err_envlist: []const u8 = "failed to get envlist",
err_hostname: []const u8 = "failed to get hostname", err_hostname: []const u8 = "failed to get hostname",
err_mlock: []const u8 = "failed to lock password memory", err_mlock: []const u8 = "failed to lock password memory",
err_null: []const u8 = "null pointer", err_null: []const u8 = "null pointer",
err_numlock: []const u8 = "failed to set numlock",
err_pam: []const u8 = "pam transaction failed", err_pam: []const u8 = "pam transaction failed",
err_pam_abort: []const u8 = "pam transaction aborted", err_pam_abort: []const u8 = "pam transaction aborted",
err_pam_acct_expired: []const u8 = "account expired", err_pam_acct_expired: []const u8 = "account expired",
@@ -33,12 +40,15 @@ err_unknown: []const u8 = "an unknown error occurred",
err_user_gid: []const u8 = "failed to set user GID", err_user_gid: []const u8 = "failed to set user GID",
err_user_init: []const u8 = "failed to initialize user", err_user_init: []const u8 = "failed to initialize user",
err_user_uid: []const u8 = "failed to set user UID", err_user_uid: []const u8 = "failed to set user UID",
err_xauth: []const u8 = "xauth command failed",
err_xcb_conn: []const u8 = "xcb connection failed",
err_xsessions_dir: []const u8 = "failed to find sessions folder", err_xsessions_dir: []const u8 = "failed to find sessions folder",
err_xsessions_open: []const u8 = "failed to open sessions folder", err_xsessions_open: []const u8 = "failed to open sessions folder",
insert: []const u8 = "insert", insert: []const u8 = "insert",
login: []const u8 = "login:", login: []const u8 = "login:",
logout: []const u8 = "logged out", logout: []const u8 = "logged out",
normal: []const u8 = "normal", normal: []const u8 = "normal",
no_x11_support: []const u8 = "x11 support disabled at compile-time",
numlock: []const u8 = "numlock", numlock: []const u8 = "numlock",
other: []const u8 = "other", other: []const u8 = "other",
password: []const u8 = "password:", password: []const u8 = "password:",

View File

@@ -1,2 +1,2 @@
user: ?[]const u8 = null, user: ?[]const u8 = null,
session_index: ?u64 = null, session_index: ?usize = null,

View File

@@ -1,30 +1,144 @@
// The migrator ensures compatibility with <=0.6.0 configuration files
const std = @import("std"); const std = @import("std");
const ini = @import("zigini"); const ini = @import("zigini");
const Save = @import("Save.zig"); const Save = @import("Save.zig");
const enums = @import("../enums.zig");
pub fn tryMigrateSaveFile(user_buf: *[32]u8, path: []const u8) Save { var temporary_allocator = std.heap.page_allocator;
pub var maybe_animate: ?bool = null;
pub var maybe_save_file: ?[]const u8 = null;
pub var mapped_config_fields = false;
pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniField {
if (std.mem.eql(u8, field.key, "animate")) {
// The option doesn't exist anymore, but we save its value for "animation"
maybe_animate = std.mem.eql(u8, field.value, "true");
mapped_config_fields = true;
return null;
}
if (std.mem.eql(u8, field.key, "animation")) {
// The option now uses a string (which then gets converted into an enum) instead of an integer
// It also combines the previous "animate" and "animation" options
const animation = std.fmt.parseInt(u8, field.value, 10) catch return field;
var mapped_field = field;
mapped_field.value = switch (animation) {
0 => "doom",
1 => "matrix",
else => "none",
};
mapped_config_fields = true;
return mapped_field;
}
if (std.mem.eql(u8, field.key, "blank_password")) {
// The option has simply been renamed
var mapped_field = field;
mapped_field.key = "clear_password";
mapped_config_fields = true;
return mapped_field;
}
if (std.mem.eql(u8, field.key, "default_input")) {
// The option now uses a string (which then gets converted into an enum) instead of an integer
const default_input = std.fmt.parseInt(u8, field.value, 10) catch return field;
var mapped_field = field;
mapped_field.value = switch (default_input) {
0 => "session",
1 => "login",
2 => "password",
else => "login",
};
mapped_config_fields = true;
return mapped_field;
}
if (std.mem.eql(u8, field.key, "save_file")) {
// The option doesn't exist anymore, but we save its value for migration later on
maybe_save_file = temporary_allocator.dupe(u8, field.value) catch return null;
mapped_config_fields = true;
return null;
}
if (std.mem.eql(u8, field.key, "wayland_specifier") or
std.mem.eql(u8, field.key, "max_desktop_len") or
std.mem.eql(u8, field.key, "max_login_len") or
std.mem.eql(u8, field.key, "max_password_len") or
std.mem.eql(u8, field.key, "mcookie_cmd") or
std.mem.eql(u8, field.key, "term_reset_cmd") or
std.mem.eql(u8, field.key, "term_restore_cursor_cmd") or
std.mem.eql(u8, field.key, "x_cmd_setup") or
std.mem.eql(u8, field.key, "wayland_cmd"))
{
// The options don't exist anymore
mapped_config_fields = true;
return null;
}
if (std.mem.eql(u8, field.key, "bigclock")) {
// The option now uses a string (which then gets converted into an enum) instead of an boolean
// It also includes the ability to change active bigclock's language
var mapped_field = field;
if (std.mem.eql(u8, field.value, "true")){
mapped_field.value = "en";
mapped_config_fields = true;
}else if (std.mem.eql(u8, field.value, "false")){
mapped_field.value = "none";
mapped_config_fields = true;
}
return mapped_field;
}
return field;
}
// This is the stuff we only handle after reading the config.
// For example, the "animate" field could come after "animation"
pub fn lateConfigFieldHandler(animation: *enums.Animation) void {
if (maybe_animate) |animate| {
if (!animate) animation.* = .none;
}
}
pub fn tryMigrateSaveFile(user_buf: *[32]u8) Save {
var save = Save{}; var save = Save{};
var file = std.fs.openFileAbsolute(path, .{}) catch return save; if (maybe_save_file) |path| {
defer file.close(); defer temporary_allocator.free(path);
const reader = file.reader(); var file = std.fs.openFileAbsolute(path, .{}) catch return save;
defer file.close();
var user_fbs = std.io.fixedBufferStream(user_buf); const reader = file.reader();
reader.streamUntilDelimiter(user_fbs.writer(), '\n', 32) catch return save;
const user = user_fbs.getWritten();
if (user.len > 0) save.user = user;
var session_buf: [20]u8 = undefined; var user_fbs = std.io.fixedBufferStream(user_buf);
var session_fbs = std.io.fixedBufferStream(&session_buf); reader.streamUntilDelimiter(user_fbs.writer(), '\n', user_buf.len) catch return save;
reader.streamUntilDelimiter(session_fbs.writer(), '\n', 20) catch {}; const user = user_fbs.getWritten();
if (user.len > 0) save.user = user;
const session_index_str = session_fbs.getWritten(); var session_buf: [20]u8 = undefined;
var session_index: ?u64 = null; var session_fbs = std.io.fixedBufferStream(&session_buf);
if (session_index_str.len > 0) { reader.streamUntilDelimiter(session_fbs.writer(), '\n', session_buf.len) catch return save;
session_index = std.fmt.parseUnsigned(u64, session_index_str, 10) catch return save;
const session_index_str = session_fbs.getWritten();
var session_index: ?usize = null;
if (session_index_str.len > 0) {
session_index = std.fmt.parseUnsigned(usize, session_index_str, 10) catch return save;
}
save.session_index = session_index;
} }
save.session_index = session_index;
return save; return save;
} }

View File

@@ -12,7 +12,19 @@ pub const DisplayServer = enum {
}; };
pub const Input = enum { pub const Input = enum {
info_line,
session, session,
login, login,
password, password,
}; };
pub const ViMode = enum {
normal,
insert,
};
pub const Bigclock = enum {
none,
en,
fa,
};

View File

@@ -2,107 +2,110 @@ const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
pub const termbox = @cImport({ pub const termbox = @import("termbox2");
@cInclude("termbox.h");
});
pub const pam = @cImport({ pub const pam = @cImport({
@cInclude("security/pam_appl.h"); @cInclude("security/pam_appl.h");
}); });
pub const utmp = @cImport({ pub const utmp = @cImport({
@cInclude("utmp.h"); @cInclude("utmpx.h");
}); });
// Exists for X11 support only
pub const xcb = @cImport({ pub const xcb = @cImport({
@cInclude("xcb/xcb.h"); @cInclude("xcb/xcb.h");
}); });
pub const c_size = u64; pub const unistd = @cImport({
pub const c_uid = u32; @cInclude("unistd.h");
pub const c_gid = 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 passwd = extern struct {
pw_name: [*:0]u8,
pw_passwd: [*:0]u8,
pw_uid: c_uid, pub const time = @cImport({
pw_gid: c_gid, @cInclude("time.h");
pw_gecos: [*:0]u8, });
pw_dir: [*:0]u8,
pw_shell: [*:0]u8,
};
pub const VT_ACTIVATE: c_int = 0x5606; pub const system_time = @cImport({
pub const VT_WAITACTIVE: c_int = 0x5607; @cInclude("sys/time.h");
});
pub const KDGETLED: c_int = 0x4B31; pub const stdlib = @cImport({
pub const KDGKBLED: c_int = 0x4B64; @cInclude("stdlib.h");
});
pub const LED_NUM: c_int = 0x02; pub const pwd = @cImport({
pub const LED_CAP: c_int = 0x04; @cInclude("pwd.h");
// We include a FreeBSD-specific header here since login_cap.h references
// the passwd struct directly, so we can't import it separately'
if (builtin.os.tag == .freebsd) @cInclude("login_cap.h");
});
pub const K_NUMLOCK: c_int = 0x02; pub const grp = @cImport({
pub const K_CAPSLOCK: c_int = 0x04; @cInclude("grp.h");
});
pub extern "c" fn localtime(timer: *const c_time) *tm; // BSD-specific headers
pub extern "c" fn strftime(str: [*:0]u8, maxsize: c_size, format: [*:0]const u8, timeptr: *const tm) c_size; pub const kbio = @cImport({
pub extern "c" fn setenv(name: [*:0]const u8, value: [*:0]const u8, overwrite: c_int) c_int; @cInclude("sys/kbio.h");
pub extern "c" fn putenv(name: [*:0]u8) c_int; });
pub extern "c" fn getuid() c_uid;
pub extern "c" fn getpwnam(name: [*:0]const u8) ?*passwd; // Linux-specific headers
pub extern "c" fn endpwent() void; pub const kd = @cImport({
pub extern "c" fn setusershell() void; @cInclude("sys/kd.h");
pub extern "c" fn getusershell() [*:0]u8; });
pub extern "c" fn endusershell() void;
pub extern "c" fn initgroups(user: [*:0]const u8, group: c_gid) c_int; pub const vt = @cImport({
@cInclude("sys/vt.h");
});
// Used for getting & setting the lock state
const LedState = if (builtin.os.tag.isBSD()) c_int else c_char;
const get_led_state = if (builtin.os.tag.isBSD()) kbio.KDGETLED else kd.KDGKBLED;
const set_led_state = if (builtin.os.tag.isBSD()) kbio.KDSETLED else kd.KDSKBLED;
const numlock_led = if (builtin.os.tag.isBSD()) kbio.LED_NUM else kd.K_NUMLOCK;
const capslock_led = if (builtin.os.tag.isBSD()) kbio.LED_CAP else kd.K_CAPSLOCK;
pub fn timeAsString(buf: [:0]u8, format: [:0]const u8) ![]u8 { pub fn timeAsString(buf: [:0]u8, format: [:0]const u8) ![]u8 {
const timer = std.time.timestamp(); const timer = std.time.timestamp();
const tm_info = localtime(&timer); const tm_info = time.localtime(&timer);
const len = strftime(buf, buf.len, format, tm_info); const len = time.strftime(buf, buf.len, format, tm_info);
if (len < 0) return error.CannotGetFormattedTime; if (len < 0) return error.CannotGetFormattedTime;
return buf[0..len]; return buf[0..len];
} }
pub fn getLockState(console_dev: [:0]const u8) !struct { pub fn switchTty(console_dev: []const u8, tty: u8) !void {
const fd = try std.posix.open(console_dev, .{ .ACCMODE = .WRONLY }, 0);
defer std.posix.close(fd);
_ = std.c.ioctl(fd, vt.VT_ACTIVATE, tty);
_ = std.c.ioctl(fd, vt.VT_WAITACTIVE, tty);
}
pub fn getLockState(console_dev: []const u8) !struct {
numlock: bool, numlock: bool,
capslock: bool, capslock: bool,
} { } {
const fd = std.c.open(console_dev, .{ .ACCMODE = .RDONLY }); const fd = try std.posix.open(console_dev, .{ .ACCMODE = .RDONLY }, 0);
if (fd < 0) return error.CannotOpenConsoleDev; defer std.posix.close(fd);
defer _ = std.c.close(fd);
var numlock = false; var led: LedState = undefined;
var capslock = false; _ = std.c.ioctl(fd, get_led_state, &led);
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 .{ return .{
.numlock = numlock, .numlock = (led & numlock_led) != 0,
.capslock = capslock, .capslock = (led & capslock_led) != 0,
}; };
} }
pub fn setNumlock(val: bool) !void {
var led: LedState = undefined;
_ = std.c.ioctl(0, get_led_state, &led);
const numlock = (led & numlock_led) != 0;
if (numlock != val) {
const status = std.c.ioctl(std.posix.STDIN_FILENO, set_led_state, led ^ numlock_led);
if (status != 0) return error.FailedToSetNumlock;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,19 +4,19 @@ const interop = @import("../interop.zig");
const utils = @import("utils.zig"); const utils = @import("utils.zig");
const Config = @import("../config/Config.zig"); const Config = @import("../config/Config.zig");
const Random = std.rand.Random; const Random = std.Random;
const termbox = interop.termbox; const termbox = interop.termbox;
const TerminalBuffer = @This(); const TerminalBuffer = @This();
random: Random, random: Random,
width: u64, width: usize,
height: u64, height: usize,
buffer: [*]termbox.tb_cell, buffer: [*]termbox.tb_cell,
fg: u8, fg: u16,
bg: u8, bg: u16,
border_fg: u8, border_fg: u16,
box_chars: struct { box_chars: struct {
left_up: u32, left_up: u32,
left_down: u32, left_down: u32,
@@ -27,19 +27,17 @@ box_chars: struct {
left: u32, left: u32,
right: u32, right: u32,
}, },
labels_max_length: u64, labels_max_length: usize,
box_x: u64, box_x: usize,
box_y: u64, box_y: usize,
box_width: u64, box_width: usize,
box_height: u64, box_height: usize,
margin_box_v: u8, margin_box_v: u8,
margin_box_h: u8, margin_box_h: u8,
pub fn init(config: Config, labels_max_length: u64) TerminalBuffer { pub fn init(config: Config, labels_max_length: usize, random: Random) TerminalBuffer {
var prng = std.rand.Isaac64.init(@intCast(std.time.timestamp()));
return .{ return .{
.random = prng.random(), .random = random,
.width = @intCast(termbox.tb_width()), .width = @intCast(termbox.tb_width()),
.height = @intCast(termbox.tb_height()), .height = @intCast(termbox.tb_height()),
.buffer = termbox.tb_cell_buffer(), .buffer = termbox.tb_cell_buffer(),
@@ -76,58 +74,62 @@ pub fn init(config: Config, labels_max_length: u64) TerminalBuffer {
} }
pub fn cascade(self: TerminalBuffer) bool { pub fn cascade(self: TerminalBuffer) bool {
var changes = false; var changed = false;
var y = self.height - 2; var y = self.height - 2;
while (y > 0) : (y -= 1) { while (y > 0) : (y -= 1) {
for (0..self.width) |x| { for (0..self.width) |x| {
const c: u8 = @truncate(self.buffer[(y - 1) * self.width + x].ch); const cell = self.buffer[(y - 1) * self.width + x];
if (std.ascii.isWhitespace(c)) continue; const cell_under = self.buffer[y * self.width + x];
const c_under: u8 = @truncate(self.buffer[y * self.width + x].ch); const char: u8 = @truncate(cell.ch);
if (!std.ascii.isWhitespace(c_under)) continue; if (std.ascii.isWhitespace(char)) continue;
changes = true; const char_under: u8 = @truncate(cell_under.ch);
if (!std.ascii.isWhitespace(char_under)) continue;
changed = true;
if ((self.random.int(u16) % 10) > 7) continue; if ((self.random.int(u16) % 10) > 7) continue;
self.buffer[y * self.width + x] = self.buffer[(y - 1) * self.width + x]; _ = termbox.tb_set_cell(@intCast(x), @intCast(y), cell.ch, cell.fg, cell.bg);
self.buffer[(y - 1) * self.width + x].ch = ' '; _ = termbox.tb_set_cell(@intCast(x), @intCast(y - 1), ' ', cell_under.fg, cell_under.bg);
} }
} }
return changes; return changed;
} }
pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool) void { pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool) void {
const x1 = (self.width - self.box_width) / 2; if (self.width < 2 or self.height < 2) return;
const y1 = (self.height - self.box_height) / 2; const x1 = (self.width - @min(self.width - 2, self.box_width)) / 2;
const x2 = (self.width + self.box_width) / 2; const y1 = (self.height - @min(self.height - 2, self.box_height)) / 2;
const y2 = (self.height + self.box_height) / 2; const x2 = (self.width + @min(self.width, self.box_width)) / 2;
const y2 = (self.height + @min(self.height, self.box_height)) / 2;
self.box_x = x1; self.box_x = x1;
self.box_y = y1; self.box_y = y1;
if (show_borders) { if (show_borders) {
termbox.tb_change_cell(@intCast(x1 - 1), @intCast(y1 - 1), self.box_chars.left_up, self.border_fg, self.bg); _ = termbox.tb_set_cell(@intCast(x1 - 1), @intCast(y1 - 1), self.box_chars.left_up, self.border_fg, self.bg);
termbox.tb_change_cell(@intCast(x2), @intCast(y1 - 1), self.box_chars.right_up, self.border_fg, self.bg); _ = termbox.tb_set_cell(@intCast(x2), @intCast(y1 - 1), self.box_chars.right_up, self.border_fg, self.bg);
termbox.tb_change_cell(@intCast(x1 - 1), @intCast(y2), self.box_chars.left_down, self.border_fg, self.bg); _ = termbox.tb_set_cell(@intCast(x1 - 1), @intCast(y2), self.box_chars.left_down, self.border_fg, self.bg);
termbox.tb_change_cell(@intCast(x2), @intCast(y2), self.box_chars.right_down, self.border_fg, self.bg); _ = termbox.tb_set_cell(@intCast(x2), @intCast(y2), self.box_chars.right_down, self.border_fg, self.bg);
var c1 = utils.initCell(self.box_chars.top, self.border_fg, self.bg); var c1 = utils.initCell(self.box_chars.top, self.border_fg, self.bg);
var c2 = utils.initCell(self.box_chars.bottom, self.border_fg, self.bg); var c2 = utils.initCell(self.box_chars.bottom, self.border_fg, self.bg);
for (0..self.box_width) |i| { for (0..self.box_width) |i| {
termbox.tb_put_cell(@intCast(x1 + i), @intCast(y1 - 1), &c1); utils.putCell(x1 + i, y1 - 1, c1);
termbox.tb_put_cell(@intCast(x1 + i), @intCast(y2), &c2); utils.putCell(x1 + i, y2, c2);
} }
c1.ch = self.box_chars.left; c1.ch = self.box_chars.left;
c2.ch = self.box_chars.right; c2.ch = self.box_chars.right;
for (0..self.box_height) |i| { for (0..self.box_height) |i| {
termbox.tb_put_cell(@intCast(x1 - 1), @intCast(y1 + i), &c1); utils.putCell(x1 - 1, y1 + i, c1);
termbox.tb_put_cell(@intCast(x2), @intCast(y1 + i), &c2); utils.putCell(x2, y1 + i, c2);
} }
} }
@@ -136,42 +138,62 @@ pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool)
for (0..self.box_height) |y| { for (0..self.box_height) |y| {
for (0..self.box_width) |x| { for (0..self.box_width) |x| {
termbox.tb_put_cell(@intCast(x1 + x), @intCast(y1 + y), &blank); utils.putCell(x1 + x, y1 + y, blank);
} }
} }
} }
} }
pub fn calculateComponentCoordinates(self: TerminalBuffer) struct { pub fn calculateComponentCoordinates(self: TerminalBuffer) struct {
x: u64, start_x: usize,
y: u64, x: usize,
visible_length: u64, y: usize,
full_visible_length: usize,
visible_length: usize,
} { } {
const x = self.box_x + self.margin_box_h + self.labels_max_length + 1; const start_x = self.box_x + self.margin_box_h;
const x = start_x + self.labels_max_length + 1;
const y = self.box_y + self.margin_box_v; const y = self.box_y + self.margin_box_v;
const full_visible_length = self.box_x + self.box_width - self.margin_box_h - start_x;
const visible_length = self.box_x + self.box_width - self.margin_box_h - x; const visible_length = self.box_x + self.box_width - self.margin_box_h - x;
return .{ return .{
.start_x = start_x,
.x = x, .x = x,
.y = y, .y = y,
.full_visible_length = full_visible_length,
.visible_length = visible_length, .visible_length = visible_length,
}; };
} }
pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: u64, y: u64) void { pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: usize, y: usize) void {
drawColorLabel(text, x, y, self.fg, self.bg);
}
pub fn drawColorLabel(text: []const u8, x: usize, y: usize, fg: u16, bg: u16) void {
const yc: c_int = @intCast(y); const yc: c_int = @intCast(y);
const utf8view = std.unicode.Utf8View.init(text) catch return; const utf8view = std.unicode.Utf8View.init(text) catch return;
var utf8 = utf8view.iterator(); var utf8 = utf8view.iterator();
var i = x; var i = x;
while (utf8.nextCodepoint()) |codepoint| : (i += 1) { while (utf8.nextCodepoint()) |codepoint| : (i += 1) {
termbox.tb_change_cell(@intCast(i), yc, codepoint, self.fg, self.bg); _ = termbox.tb_set_cell(@intCast(i), yc, codepoint, fg, bg);
} }
} }
pub fn drawCharMultiple(self: TerminalBuffer, char: u8, x: u64, y: u64, length: u64) void { pub fn drawConfinedLabel(self: TerminalBuffer, text: []const u8, x: usize, y: usize, max_length: usize) void {
const yc: c_int = @intCast(y); const yc: c_int = @intCast(y);
const cell = utils.initCell(char, self.fg, self.bg); const utf8view = std.unicode.Utf8View.init(text) catch return;
var utf8 = utf8view.iterator();
for (0..length) |xx| termbox.tb_put_cell(@intCast(x + xx), yc, &cell); var i: usize = 0;
while (utf8.nextCodepoint()) |codepoint| : (i += 1) {
if (i >= max_length) break;
_ = termbox.tb_set_cell(@intCast(i + x), yc, codepoint, self.fg, self.bg);
}
}
pub fn drawCharMultiple(self: TerminalBuffer, char: u8, x: usize, y: usize, length: usize) void {
const cell = utils.initCell(char, self.fg, self.bg);
for (0..length) |xx| utils.putCell(x + xx, y, cell);
} }

View File

@@ -1,223 +0,0 @@
const std = @import("std");
const enums = @import("../../enums.zig");
const interop = @import("../../interop.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Ini = @import("zigini").Ini;
const Lang = @import("../../config/Lang.zig");
const Allocator = std.mem.Allocator;
const EnvironmentList = std.ArrayList(Environment);
const DisplayServer = enums.DisplayServer;
const termbox = interop.termbox;
const Desktop = @This();
pub const Environment = struct {
entry_ini: ?Ini(Entry) = null,
name: [:0]const u8 = "",
xdg_session_desktop: [:0]const u8 = "",
xdg_desktop_names: ?[:0]const u8 = "",
cmd: []const u8 = "",
specifier: []const u8 = "",
display_server: DisplayServer = .wayland,
};
const DesktopEntry = struct {
Exec: []const u8 = "",
Name: [:0]const u8 = "",
DesktopNames: ?[]const u8 = null,
};
pub const Entry = struct { @"Desktop Entry": DesktopEntry = DesktopEntry{} };
allocator: Allocator,
buffer: *TerminalBuffer,
environments: EnvironmentList,
current: u64,
visible_length: u64,
x: u64,
y: u64,
lang: Lang,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64, lang: Lang) !Desktop {
return .{
.allocator = allocator,
.buffer = buffer,
.environments = try EnvironmentList.initCapacity(allocator, max_length),
.current = 0,
.visible_length = 0,
.x = 0,
.y = 0,
.lang = lang,
};
}
pub fn deinit(self: Desktop) void {
for (self.environments.items) |*environment| {
if (environment.entry_ini) |*entry_ini| entry_ini.deinit();
if (environment.xdg_desktop_names) |desktop_name| self.allocator.free(desktop_name);
self.allocator.free(environment.xdg_session_desktop);
}
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, entry: DesktopEntry, xdg_session_desktop: []const u8, display_server: DisplayServer) !void {
var xdg_desktop_names: ?[:0]const u8 = null;
if (entry.DesktopNames) |desktop_names| {
const desktop_names_z = try self.allocator.dupeZ(u8, desktop_names);
for (desktop_names_z) |*c| {
if (c.* == ';') c.* = ':';
}
xdg_desktop_names = desktop_names_z;
}
errdefer {
if (xdg_desktop_names) |desktop_names| self.allocator.free(desktop_names);
}
const session_desktop = try self.allocator.dupeZ(u8, xdg_session_desktop);
errdefer self.allocator.free(session_desktop);
try self.environments.append(.{
.entry_ini = null,
.name = entry.Name,
.xdg_session_desktop = session_desktop,
.xdg_desktop_names = xdg_desktop_names,
.cmd = entry.Exec,
.specifier = switch (display_server) {
.wayland => self.lang.wayland,
.x11 => self.lang.x11,
else => self.lang.other,
},
.display_server = display_server,
});
self.current = self.environments.items.len - 1;
}
pub fn addEnvironmentWithIni(self: *Desktop, entry_ini: Ini(Entry), xdg_session_desktop: []const u8, display_server: DisplayServer) !void {
const entry = entry_ini.data.@"Desktop Entry";
var xdg_desktop_names: ?[:0]const u8 = null;
if (entry.DesktopNames) |desktop_names| {
const desktop_names_z = try self.allocator.dupeZ(u8, desktop_names);
for (desktop_names_z) |*c| {
if (c.* == ';') c.* = ':';
}
xdg_desktop_names = desktop_names_z;
}
errdefer {
if (xdg_desktop_names) |desktop_names| self.allocator.free(desktop_names);
}
const session_desktop = try self.allocator.dupeZ(u8, xdg_session_desktop);
errdefer self.allocator.free(session_desktop);
try self.environments.append(.{
.entry_ini = entry_ini,
.name = entry.Name,
.xdg_session_desktop = session_desktop,
.xdg_desktop_names = xdg_desktop_names,
.cmd = entry.Exec,
.specifier = switch (display_server) {
.wayland => self.lang.wayland,
.x11 => self.lang.x11,
else => self.lang.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 iterable_directory = std.fs.openDirAbsolute(path, .{ .iterate = true }) catch return;
defer iterable_directory.close();
var iterator = iterable_directory.iterate();
while (try iterator.next()) |item| {
if (!std.mem.eql(u8, std.fs.path.extension(item.name), ".desktop")) continue;
const entry_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ path, item.name });
defer self.allocator.free(entry_path);
var entry_ini = Ini(Entry).init(self.allocator);
_ = try entry_ini.readFileToStruct(entry_path);
errdefer entry_ini.deinit();
var xdg_session_desktop: []const u8 = undefined;
const maybe_desktop_names = entry_ini.data.@"Desktop Entry".DesktopNames;
if (maybe_desktop_names) |desktop_names| {
xdg_session_desktop = std.mem.sliceTo(desktop_names, ';');
} else {
// if DesktopNames is empty, we'll take the name of the session file
xdg_session_desktop = std.fs.path.stem(item.name);
}
try self.addEnvironmentWithIni(entry_ini, xdg_session_desktop, display_server);
}
}
pub fn handle(self: *Desktop, maybe_event: ?*termbox.tb_event, insert_mode: bool) 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 => {
if (!insert_mode) {
switch (event.ch) {
'h' => self.goLeft(),
'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;
}

View File

@@ -1,11 +1,61 @@
const std = @import("std");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const generic = @import("generic.zig");
const utils = @import("../utils.zig"); const utils = @import("../utils.zig");
const Allocator = std.mem.Allocator;
const MessageLabel = generic.CyclableLabel(Message);
const InfoLine = @This(); const InfoLine = @This();
text: []const u8 = "", const Message = struct {
width: u8 = 0, width: u8,
text: []const u8,
bg: u16,
fg: u16,
};
pub fn setText(self: *InfoLine, text: []const u8) !void { label: MessageLabel,
self.width = if (text.len > 0) try utils.strWidth(text) else 0;
self.text = text; pub fn init(allocator: Allocator, buffer: *TerminalBuffer) InfoLine {
return .{
.label = MessageLabel.init(allocator, buffer, drawItem),
};
}
pub fn deinit(self: InfoLine) void {
self.label.deinit();
}
pub fn addMessage(self: *InfoLine, text: []const u8, bg: u16, fg: u16) !void {
if (text.len == 0) return;
try self.label.addItem(.{
.width = try utils.strWidth(text),
.text = text,
.bg = bg,
.fg = fg,
});
}
pub fn clearRendered(allocator: Allocator, buffer: TerminalBuffer) !void {
// Draw over the area
const y = buffer.box_y + buffer.margin_box_v;
const spaces = try allocator.alloc(u8, buffer.box_width);
defer allocator.free(spaces);
@memset(spaces, ' ');
buffer.drawLabel(spaces, buffer.box_x, y);
}
fn drawItem(label: *MessageLabel, message: Message, _: usize, _: usize) bool {
if (message.width == 0 or label.buffer.box_width <= message.width) return false;
const x = label.buffer.box_x + ((label.buffer.box_width - message.width) / 2);
label.first_char_x = x + message.width;
TerminalBuffer.drawColorLabel(message.text, x, label.y, message.fg, message.bg);
return true;
} }

View File

@@ -0,0 +1,142 @@
const std = @import("std");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const enums = @import("../../enums.zig");
const generic = @import("generic.zig");
const Ini = @import("zigini").Ini;
const Lang = @import("../../config/Lang.zig");
const Allocator = std.mem.Allocator;
const DisplayServer = enums.DisplayServer;
const EnvironmentLabel = generic.CyclableLabel(Environment);
const Session = @This();
pub const Environment = struct {
entry_ini: ?Ini(Entry) = null,
name: [:0]const u8 = "",
xdg_session_desktop: ?[:0]const u8 = null,
xdg_desktop_names: ?[:0]const u8 = null,
cmd: []const u8 = "",
specifier: []const u8 = "",
display_server: DisplayServer = .wayland,
};
const DesktopEntry = struct {
Exec: []const u8 = "",
Name: [:0]const u8 = "",
DesktopNames: ?[:0]u8 = null,
};
pub const Entry = struct { @"Desktop Entry": DesktopEntry = .{} };
label: EnvironmentLabel,
lang: Lang,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, lang: Lang) Session {
return .{
.label = EnvironmentLabel.init(allocator, buffer, drawItem),
.lang = lang,
};
}
pub fn deinit(self: Session) void {
for (self.label.list.items) |*environment| {
if (environment.entry_ini) |*entry_ini| entry_ini.deinit();
if (environment.xdg_session_desktop) |session_desktop| self.label.allocator.free(session_desktop);
}
self.label.deinit();
}
pub fn addEnvironment(self: *Session, entry: DesktopEntry, xdg_session_desktop: ?[:0]const u8, display_server: DisplayServer) !void {
var xdg_desktop_names: ?[:0]const u8 = null;
if (entry.DesktopNames) |desktop_names| {
for (desktop_names) |*c| {
if (c.* == ';') c.* = ':';
}
xdg_desktop_names = desktop_names;
}
try self.label.addItem(.{
.entry_ini = null,
.name = entry.Name,
.xdg_session_desktop = xdg_session_desktop,
.xdg_desktop_names = xdg_desktop_names,
.cmd = entry.Exec,
.specifier = switch (display_server) {
.wayland => self.lang.wayland,
.x11 => self.lang.x11,
else => self.lang.other,
},
.display_server = display_server,
});
}
pub fn addEnvironmentWithIni(self: *Session, entry_ini: Ini(Entry), xdg_session_desktop: ?[:0]const u8, display_server: DisplayServer) !void {
const entry = entry_ini.data.@"Desktop Entry";
var xdg_desktop_names: ?[:0]const u8 = null;
if (entry.DesktopNames) |desktop_names| {
for (desktop_names) |*c| {
if (c.* == ';') c.* = ':';
}
xdg_desktop_names = desktop_names;
}
try self.label.addItem(.{
.entry_ini = entry_ini,
.name = entry.Name,
.xdg_session_desktop = xdg_session_desktop,
.xdg_desktop_names = xdg_desktop_names,
.cmd = entry.Exec,
.specifier = switch (display_server) {
.wayland => self.lang.wayland,
.x11 => self.lang.x11,
else => self.lang.other,
},
.display_server = display_server,
});
}
pub fn crawl(self: *Session, path: []const u8, display_server: DisplayServer) !void {
var iterable_directory = std.fs.openDirAbsolute(path, .{ .iterate = true }) catch return;
defer iterable_directory.close();
var iterator = iterable_directory.iterate();
while (try iterator.next()) |item| {
if (!std.mem.eql(u8, std.fs.path.extension(item.name), ".desktop")) continue;
const entry_path = try std.fmt.allocPrint(self.label.allocator, "{s}/{s}", .{ path, item.name });
defer self.label.allocator.free(entry_path);
var entry_ini = Ini(Entry).init(self.label.allocator);
_ = try entry_ini.readFileToStruct(entry_path, "#", null);
errdefer entry_ini.deinit();
var xdg_session_desktop: []const u8 = undefined;
const maybe_desktop_names = entry_ini.data.@"Desktop Entry".DesktopNames;
if (maybe_desktop_names) |desktop_names| {
xdg_session_desktop = std.mem.sliceTo(desktop_names, ';');
} else {
// if DesktopNames is empty, we'll take the name of the session file
xdg_session_desktop = std.fs.path.stem(item.name);
}
const session_desktop = try self.label.allocator.dupeZ(u8, xdg_session_desktop);
errdefer self.label.allocator.free(session_desktop);
try self.addEnvironmentWithIni(entry_ini, session_desktop, display_server);
}
}
fn drawItem(label: *EnvironmentLabel, environment: Environment, x: usize, y: usize) bool {
const length = @min(environment.name.len, label.visible_length - 3);
if (length == 0) return false;
const nx = if (label.text_in_center) (label.x + (label.visible_length - environment.name.len) / 2) else (label.x + 2);
label.first_char_x = nx + environment.name.len;
label.buffer.drawLabel(environment.specifier, x, y);
label.buffer.drawLabel(environment.name, nx, label.y);
return true;
}

View File

@@ -13,15 +13,17 @@ const Text = @This();
allocator: Allocator, allocator: Allocator,
buffer: *TerminalBuffer, buffer: *TerminalBuffer,
text: DynamicString, text: DynamicString,
end: u64, end: usize,
cursor: u64, cursor: usize,
visible_start: u64, visible_start: usize,
visible_length: u64, visible_length: usize,
x: u64, x: usize,
y: u64, y: usize,
masked: bool,
maybe_mask: ?u8,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64) !Text { pub fn init(allocator: Allocator, buffer: *TerminalBuffer, masked: bool, maybe_mask: ?u8) Text {
const text = try DynamicString.initCapacity(allocator, max_length); const text = DynamicString.init(allocator);
return .{ return .{
.allocator = allocator, .allocator = allocator,
@@ -33,6 +35,8 @@ pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64) !Tex
.visible_length = 0, .visible_length = 0,
.x = 0, .x = 0,
.y = 0, .y = 0,
.masked = masked,
.maybe_mask = maybe_mask,
}; };
} }
@@ -40,7 +44,7 @@ pub fn deinit(self: Text) void {
self.text.deinit(); self.text.deinit();
} }
pub fn position(self: *Text, x: u64, y: u64, visible_length: u64) void { pub fn position(self: *Text, x: usize, y: usize, visible_length: usize) void {
self.x = x; self.x = x;
self.y = y; self.y = y;
self.visible_length = visible_length; self.visible_length = visible_length;
@@ -78,24 +82,39 @@ pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event, insert_mode: bool) !
} }
} }
termbox.tb_set_cursor(@intCast(self.x + (self.cursor - self.visible_start)), @intCast(self.y)); if (self.masked and self.maybe_mask == null) {
_ = termbox.tb_set_cursor(@intCast(self.x), @intCast(self.y));
return;
}
_ = termbox.tb_set_cursor(@intCast(self.x + (self.cursor - self.visible_start)), @intCast(self.y));
} }
pub fn draw(self: Text) void { pub fn draw(self: Text) void {
if (self.masked) {
if (self.maybe_mask) |mask| {
const length = @min(self.text.items.len, self.visible_length - 1);
if (length == 0) return;
self.buffer.drawCharMultiple(mask, self.x, self.y, length);
}
return;
}
const length = @min(self.text.items.len, self.visible_length); const length = @min(self.text.items.len, self.visible_length);
if (length == 0) return; 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..]; const visible_slice = vs: {
if (self.text.items.len > self.visible_length and self.cursor < self.text.items.len) {
break :vs self.text.items[self.visible_start..(self.visible_length + self.visible_start)];
} else {
break :vs self.text.items[self.visible_start..];
}
};
self.buffer.drawLabel(visible_slice, self.x, self.y); 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 { pub fn clear(self: *Text) void {
self.text.clearRetainingCapacity(); self.text.clearRetainingCapacity();
self.end = 0; self.end = 0;

View File

@@ -0,0 +1,115 @@
const std = @import("std");
const enums = @import("../../enums.zig");
const interop = @import("../../interop.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
pub fn CyclableLabel(comptime ItemType: type) type {
return struct {
const Allocator = std.mem.Allocator;
const ItemList = std.ArrayList(ItemType);
const DrawItemFn = *const fn (*Self, ItemType, usize, usize) bool;
const termbox = interop.termbox;
const Self = @This();
allocator: Allocator,
buffer: *TerminalBuffer,
list: ItemList,
current: usize,
visible_length: usize,
x: usize,
y: usize,
first_char_x: usize,
text_in_center: bool,
draw_item_fn: DrawItemFn,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, draw_item_fn: DrawItemFn) Self {
return .{
.allocator = allocator,
.buffer = buffer,
.list = ItemList.init(allocator),
.current = 0,
.visible_length = 0,
.x = 0,
.y = 0,
.first_char_x = 0,
.text_in_center = false,
.draw_item_fn = draw_item_fn,
};
}
pub fn deinit(self: Self) void {
self.list.deinit();
}
pub fn position(self: *Self, x: usize, y: usize, visible_length: usize, text_in_center: ?bool) void {
self.x = x;
self.y = y;
self.visible_length = visible_length;
self.first_char_x = x + 2;
if (text_in_center) |value| {
self.text_in_center = value;
}
}
pub fn addItem(self: *Self, item: ItemType) !void {
try self.list.append(item);
self.current = self.list.items.len - 1;
}
pub fn handle(self: *Self, maybe_event: ?*termbox.tb_event, insert_mode: bool) 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 => {
if (!insert_mode) {
switch (event.ch) {
'h' => self.goLeft(),
'l' => self.goRight(),
else => {},
}
}
},
}
}
_ = termbox.tb_set_cursor(@intCast(self.first_char_x), @intCast(self.y));
}
pub fn draw(self: *Self) void {
if (self.list.items.len == 0) return;
const current_item = self.list.items[self.current];
const x = self.buffer.box_x + self.buffer.margin_box_h;
const y = self.buffer.box_y + self.buffer.margin_box_v + 2;
const continue_drawing = @call(.auto, self.draw_item_fn, .{ self, current_item, x, y });
if (!continue_drawing) return;
_ = termbox.tb_set_cell(@intCast(self.x), @intCast(self.y), '<', self.buffer.fg, self.buffer.bg);
_ = termbox.tb_set_cell(@intCast(self.x + self.visible_length - 1), @intCast(self.y), '>', self.buffer.fg, self.buffer.bg);
}
fn goLeft(self: *Self) void {
if (self.current == 0) {
self.current = self.list.items.len - 1;
return;
}
self.current -= 1;
}
fn goRight(self: *Self) void {
if (self.current == self.list.items.len - 1) {
self.current = 0;
return;
}
self.current += 1;
}
};
}

View File

@@ -3,7 +3,13 @@ const interop = @import("../interop.zig");
const termbox = interop.termbox; const termbox = interop.termbox;
pub fn initCell(ch: u32, fg: u32, bg: u32) termbox.tb_cell { pub const Cell = struct {
ch: u32,
fg: u16,
bg: u16,
};
pub fn initCell(ch: u32, fg: u16, bg: u16) Cell {
return .{ return .{
.ch = ch, .ch = ch,
.fg = fg, .fg = fg,
@@ -11,6 +17,10 @@ pub fn initCell(ch: u32, fg: u32, bg: u32) termbox.tb_cell {
}; };
} }
pub fn putCell(x: usize, y: usize, cell: Cell) void {
_ = termbox.tb_set_cell(@intCast(x), @intCast(y), cell.ch, cell.fg, cell.bg);
}
// Every codepoint is assumed to have a width of 1. // Every codepoint is assumed to have a width of 1.
// Since ly should be running in a tty, this should be fine. // Since ly should be running in a tty, this should be fine.
pub fn strWidth(str: []const u8) !u8 { pub fn strWidth(str: []const u8) !u8 {