mirror of
https://github.com/fairyglade/ly.git
synced 2026-05-06 07:10:36 +00:00
Feature: Add custom command & label support (#945)
## What are the changes about? Adds customizable commands and labels to ly. Solves https://codeberg.org/fairyglade/ly/issues/905. Since Ly doesn't use INI headers. I use them exclusively for declarations of custom commands and labels. ### Commands Bind a keybind to a command, and add a hint to the HUD. Useful for use cases like display brightness, switching between GPUs, etc. Supports localization in the `name` field only. ex: where `lang = es`: `$brightness_up` => `bajar brillo` Declared in config.ini with the following: ```ini [cmd:F8] name = custom command 2 cmd = touch /tmp/ly.gaming ``` ### Labels Add a label to the HUD. As specified in #905. The text of the label corresponds to the output of the command specified in `[lbl:NAME]`. Only shows the first line of the output. Declared in config.ini with the following: ```ini [lbl:kernel] cmd = uname -srn refresh = 0 ``` Example to add to the config.ini: ```ini # Declare a command with the F8 binding. [cmd:F8] #The name of the command to show up in Ly. name = custom command cmd = touch /tmp/ly.gaming # Declare a label with an ID. This ID should be unique across all labels. [lbl:kernel] cmd = uname -srn # In frames, the time to re-run the command and update the label. If 0, only run once- do not refresh. refresh = 0 # Once you're done setting up labels and commands, add an empty header # below to continue configurating the rest of Ly. # Put other settings not belonging to custom commands/labels below here. [] ``` ## Pre-requisites - [x] I have tested & confirmed the changes work locally  Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/945 Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org> Co-authored-by: RadsammyT <radsammyt@gmail.com> Co-committed-by: RadsammyT <radsammyt@gmail.com>
This commit is contained in:
@@ -12,6 +12,8 @@ const VTable = struct {
|
|||||||
calculate_timeout_fn: ?*const fn (ptr: *anyopaque, ctx: *anyopaque) anyerror!?usize,
|
calculate_timeout_fn: ?*const fn (ptr: *anyopaque, ctx: *anyopaque) anyerror!?usize,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub var idCounter: u64 = 0;
|
||||||
|
|
||||||
id: u64,
|
id: u64,
|
||||||
display_name: []const u8,
|
display_name: []const u8,
|
||||||
keybinds: ?TerminalBuffer.KeybindMap,
|
keybinds: ?TerminalBuffer.KeybindMap,
|
||||||
@@ -101,8 +103,9 @@ pub fn init(
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
idCounter += 1;
|
||||||
return .{
|
return .{
|
||||||
.id = @intFromPtr(Impl.vtable.draw_fn),
|
.id = idCounter,
|
||||||
.display_name = display_name,
|
.display_name = display_name,
|
||||||
.keybinds = keybinds,
|
.keybinds = keybinds,
|
||||||
.pointer = pointer,
|
.pointer = pointer,
|
||||||
|
|||||||
@@ -143,6 +143,11 @@ colormix_col2 = 0x000000FF
|
|||||||
# Color mixing animation third color id
|
# Color mixing animation third color id
|
||||||
colormix_col3 = 0x20000000
|
colormix_col3 = 0x20000000
|
||||||
|
|
||||||
|
# For custom binds: the horizontal limit in characters for each
|
||||||
|
# line of custom binds before moving on to the next.
|
||||||
|
# If null, defaults to the width of the terminal instead.
|
||||||
|
custom_bind_width = null
|
||||||
|
|
||||||
# Custom sessions directory
|
# Custom sessions directory
|
||||||
# You can specify multiple directories,
|
# You can specify multiple directories,
|
||||||
# e.g. $CONFIG_DIRECTORY/ly/custom-sessions:$PREFIX_DIRECTORY/share/custom-sessions
|
# e.g. $CONFIG_DIRECTORY/ly/custom-sessions:$PREFIX_DIRECTORY/share/custom-sessions
|
||||||
@@ -380,3 +385,35 @@ xinitrc = ~/.xinitrc
|
|||||||
# You can specify multiple directories,
|
# You can specify multiple directories,
|
||||||
# e.g. $PREFIX_DIRECTORY/share/xsessions:$PREFIX_DIRECTORY/local/share/xsessions
|
# e.g. $PREFIX_DIRECTORY/share/xsessions:$PREFIX_DIRECTORY/local/share/xsessions
|
||||||
xsessions = $PREFIX_DIRECTORY/share/xsessions
|
xsessions = $PREFIX_DIRECTORY/share/xsessions
|
||||||
|
|
||||||
|
# Custom Commands and Labels:
|
||||||
|
# The following examples below give an outline for setting up custom commands and labels.
|
||||||
|
# Unless specified as optional, an option is mandatory.
|
||||||
|
|
||||||
|
# Comments preceding with '##' are for documentation.
|
||||||
|
# Comments preceding with '#' comment out the example INI.
|
||||||
|
|
||||||
|
##---
|
||||||
|
## Declare a command with the F8 binding.
|
||||||
|
#[cmd:F8]
|
||||||
|
## The name of the command to show up in Ly.
|
||||||
|
## Note: "$" in "$brightness_up" fetches the appropriate string from the specified locale file
|
||||||
|
## and is replaced with the value representing "brightness_up".
|
||||||
|
## You can see the list of keys in any locale file in $CONFIG_DIRECTORY/ly/lang.
|
||||||
|
#name = custom command $brightness_up
|
||||||
|
#cmd = touch /tmp/ly.gaming
|
||||||
|
#
|
||||||
|
## Declare a label with an ID. This ID should be unique across all labels.
|
||||||
|
#[lbl:kernel]
|
||||||
|
#cmd = uname -srn
|
||||||
|
## Optional, defaulting to 0.
|
||||||
|
## In frames, the time to re-run the command and update the label.
|
||||||
|
## If 0, only run once and do not refresh afterwards
|
||||||
|
#refresh = 0
|
||||||
|
#
|
||||||
|
## Once you're done setting up labels and commands, add an empty header
|
||||||
|
## below to continue configurating the rest of Ly.
|
||||||
|
## Put other settings not belonging to custom commands/labels below here.
|
||||||
|
#[]
|
||||||
|
#
|
||||||
|
##---
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ brightness_down = خفض السطوع
|
|||||||
brightness_up = رفع السطوع
|
brightness_up = رفع السطوع
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = فشل في تخصيص الذاكرة
|
err_alloc = فشل في تخصيص الذاكرة
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ brightness_down = намаляване на яркостта
|
|||||||
brightness_up = увеличаване на яркостта
|
brightness_up = увеличаване на яркостта
|
||||||
capslock = caps lock
|
capslock = caps lock
|
||||||
custom = персонализирано
|
custom = персонализирано
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = неуспешно заделяне на памет
|
err_alloc = неуспешно заделяне на памет
|
||||||
err_args = неуспешен анализ на аргументите от командния ред
|
err_args = неуспешен анализ на аргументите от командния ред
|
||||||
err_autologin_session = сесията за автоматично влизане не е намерена
|
err_autologin_session = сесията за автоматично влизане не е намерена
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ brightness_down = abaixar brillantor
|
|||||||
brightness_up = apujar brillantor
|
brightness_up = apujar brillantor
|
||||||
capslock = Bloq Majús
|
capslock = Bloq Majús
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = assignació de memòria fallida
|
err_alloc = assignació de memòria fallida
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = alokace paměti selhala
|
err_alloc = alokace paměti selhala
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ brightness_down = Helligkeit-
|
|||||||
brightness_up = Helligkeit+
|
brightness_up = Helligkeit+
|
||||||
capslock = Feststelltaste
|
capslock = Feststelltaste
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = Speicherzuweisung fehlgeschlagen
|
err_alloc = Speicherzuweisung fehlgeschlagen
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ brightness_down = decrease brightness
|
|||||||
brightness_up = increase brightness
|
brightness_up = increase brightness
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
custom = custom
|
custom = custom
|
||||||
|
custom_info_err_output_long = output too long
|
||||||
|
custom_info_err_no_output = no output
|
||||||
|
custom_info_err_no_output_error = , possible error
|
||||||
err_alloc = failed memory allocation
|
err_alloc = failed memory allocation
|
||||||
err_args = unable to parse command line arguments
|
err_args = unable to parse command line arguments
|
||||||
err_autologin_session = autologin session not found
|
err_autologin_session = autologin session not found
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ brightness_down = malpliigi helecon
|
|||||||
brightness_up = pliigi helecon
|
brightness_up = pliigi helecon
|
||||||
capslock = majuskla baskulo
|
capslock = majuskla baskulo
|
||||||
custom = propra
|
custom = propra
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = malsukcesis memorasignon
|
err_alloc = malsukcesis memorasignon
|
||||||
err_args = ne povas analizi argumentojn de komanda linio
|
err_args = ne povas analizi argumentojn de komanda linio
|
||||||
err_autologin_session = aŭtomatan ensalutan seancon ne trovis
|
err_autologin_session = aŭtomatan ensalutan seancon ne trovis
|
||||||
@@ -73,6 +76,7 @@ restart = restartigi
|
|||||||
shell = ŝelo
|
shell = ŝelo
|
||||||
shutdown = malŝalti
|
shutdown = malŝalti
|
||||||
sleep = memordormi
|
sleep = memordormi
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
x11 = x11
|
x11 = x11
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ brightness_down = bajar brillo
|
|||||||
brightness_up = subir 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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ brightness_down = diminuer la luminosité
|
|||||||
brightness_up = augmenter la luminosité
|
brightness_up = augmenter la luminosité
|
||||||
capslock = verr.maj
|
capslock = verr.maj
|
||||||
custom = customisé
|
custom = customisé
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = échec d'allocation mémoire
|
err_alloc = échec d'allocation mémoire
|
||||||
err_args = échec de l'analyse des arguments en lignes de commande
|
err_args = échec de l'analyse des arguments en lignes de commande
|
||||||
err_autologin_session = session de connexion automatique introuvable
|
err_autologin_session = session de connexion automatique introuvable
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = impossibile allocare memoria
|
err_alloc = impossibile allocare memoria
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ brightness_down = 明るさを下げる
|
|||||||
brightness_up = 明るさを上げる
|
brightness_up = 明るさを上げる
|
||||||
capslock = CapsLock
|
capslock = CapsLock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = メモリ割り当て失敗
|
err_alloc = メモリ割り当て失敗
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ brightness_down = ronahiyê kêm bike
|
|||||||
brightness_up = ronahiyê bilind bike
|
brightness_up = ronahiyê bilind bike
|
||||||
capslock = tîpên girdek (capslock)
|
capslock = tîpên girdek (capslock)
|
||||||
custom = kesane
|
custom = kesane
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = veqetandina bîrê têk çû
|
err_alloc = veqetandina bîrê têk çû
|
||||||
err_args = argumanên rêzika fermanê nehatin analîzkirin
|
err_args = argumanên rêzika fermanê nehatin analîzkirin
|
||||||
err_autologin_session = danişîna têketina xweber nehate dîtin
|
err_autologin_session = danişîna têketina xweber nehate dîtin
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ brightness_down = samazināt spilgtumu
|
|||||||
brightness_up = palielināt spilgtumu
|
brightness_up = palielināt spilgtumu
|
||||||
capslock = caps lock
|
capslock = caps lock
|
||||||
custom = pielāgots
|
custom = pielāgots
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = neizdevās atmiņas piešķiršana
|
err_alloc = neizdevās atmiņas piešķiršana
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ brightness_down = zmniejsz jasność
|
|||||||
brightness_up = zwiększ jasność
|
brightness_up = zwiększ jasność
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
custom = własny
|
custom = własny
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = nieudana alokacja pamięci
|
err_alloc = nieudana alokacja pamięci
|
||||||
|
|
||||||
err_autologin_session = nie znaleziono sesji autologowania
|
err_autologin_session = nie znaleziono sesji autologowania
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = erro na atribuição de memória
|
err_alloc = erro na atribuição de memória
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
capslock = caixa alta
|
capslock = caixa alta
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = alocação de memória malsucedida
|
err_alloc = alocação de memória malsucedida
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ capslock = capslock
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ brightness_down = уменьшить яркость
|
|||||||
brightness_up = увеличить яркость
|
brightness_up = увеличить яркость
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
custom = пользовательский
|
custom = пользовательский
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = не удалось выделить память
|
err_alloc = не удалось выделить память
|
||||||
|
|
||||||
err_autologin_session = не найдена сессия с автологином
|
err_autologin_session = не найдена сессия с автологином
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = neuspijesna alokacija memorije
|
err_alloc = neuspijesna alokacija memorije
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ brightness_down = minska ljusstyrka
|
|||||||
brightness_up = öka ljusstyrka
|
brightness_up = öka ljusstyrka
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
custom = anpassad
|
custom = anpassad
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = minnesallokering misslyckades
|
err_alloc = minnesallokering misslyckades
|
||||||
err_args = tolkning av kommandoargument misslyckades
|
err_args = tolkning av kommandoargument misslyckades
|
||||||
err_autologin_session = autologin-session hittades inte
|
err_autologin_session = autologin-session hittades inte
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ brightness_down = parlakligi azalt
|
|||||||
brightness_up = parlakligi arttir
|
brightness_up = parlakligi arttir
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = basarisiz bellek ayirma
|
err_alloc = basarisiz bellek ayirma
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = невдале виділення пам'яті
|
err_alloc = невдале виділення пам'яті
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
capslock = 大写锁定
|
capslock = 大写锁定
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = 内存分配失败
|
err_alloc = 内存分配失败
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ cmatrix_max_codepoint: u16 = 0x7B,
|
|||||||
colormix_col1: u32 = 0x00FF0000,
|
colormix_col1: u32 = 0x00FF0000,
|
||||||
colormix_col2: u32 = 0x000000FF,
|
colormix_col2: u32 = 0x000000FF,
|
||||||
colormix_col3: u32 = 0x20000000,
|
colormix_col3: u32 = 0x20000000,
|
||||||
|
custom_bind_width: ?u32 = null,
|
||||||
custom_sessions: []const u8 = build_options.config_directory ++ "/ly/custom-sessions",
|
custom_sessions: []const u8 = build_options.config_directory ++ "/ly/custom-sessions",
|
||||||
default_input: Input = .login,
|
default_input: Input = .login,
|
||||||
doom_fire_height: u8 = 6,
|
doom_fire_height: u8 = 6,
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ brightness_down: []const u8 = "decrease brightness",
|
|||||||
brightness_up: []const u8 = "increase brightness",
|
brightness_up: []const u8 = "increase brightness",
|
||||||
capslock: []const u8 = "capslock",
|
capslock: []const u8 = "capslock",
|
||||||
custom: []const u8 = "custom",
|
custom: []const u8 = "custom",
|
||||||
|
custom_info_err_output_long: []const u8 = "output too long",
|
||||||
|
custom_info_err_no_output: []const u8 = "no output",
|
||||||
|
custom_info_err_no_output_error: []const u8 = ", possible error",
|
||||||
err_alloc: []const u8 = "failed memory allocation",
|
err_alloc: []const u8 = "failed memory allocation",
|
||||||
err_args: []const u8 = "unable to parse command line arguments",
|
err_args: []const u8 = "unable to parse command line arguments",
|
||||||
err_autologin_session: []const u8 = "autologin session not found",
|
err_autologin_session: []const u8 = "autologin session not found",
|
||||||
|
|||||||
25
src/config/custom.zig
Normal file
25
src/config/custom.zig
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const custom = @This();
|
||||||
|
|
||||||
|
pub const CustomCommandBind = struct {
|
||||||
|
name: []const u8 = "",
|
||||||
|
cmd: []const u8 = "",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const UNDEFINED_CMD: []const u8 = "echo \"You forgot to define 'cmd'!\"";
|
||||||
|
|
||||||
|
pub const CustomCommandInfo = struct {
|
||||||
|
name: []const u8 = "",
|
||||||
|
cmd: ?[]const u8 = null,
|
||||||
|
/// To be set to the label's widget ID
|
||||||
|
id: u64 = 0,
|
||||||
|
|
||||||
|
/// In frames, the refresh rate for the `cmd` to run again
|
||||||
|
/// If 0, only run once.
|
||||||
|
refresh: u32 = 0,
|
||||||
|
counter: u32 = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub var binds: std.StringHashMap(CustomCommandBind) = undefined;
|
||||||
|
pub var labels: std.StringHashMap(CustomCommandInfo) = undefined;
|
||||||
@@ -16,6 +16,7 @@ const ini = ly_core.ini;
|
|||||||
const Config = @import("Config.zig");
|
const Config = @import("Config.zig");
|
||||||
const OldSave = @import("OldSave.zig");
|
const OldSave = @import("OldSave.zig");
|
||||||
const SavedUsers = @import("SavedUsers.zig");
|
const SavedUsers = @import("SavedUsers.zig");
|
||||||
|
const custom = @import("custom.zig");
|
||||||
|
|
||||||
const color_properties = [_][]const u8{
|
const color_properties = [_][]const u8{
|
||||||
"bg",
|
"bg",
|
||||||
@@ -162,6 +163,61 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
|
|||||||
return mapped_field;
|
return mapped_field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Dearest Melpert,
|
||||||
|
// I pray this message finds you well, as daylight dwindles and the witching hour
|
||||||
|
// approaches, I find it more and more imperative as time continues that I place
|
||||||
|
// this reminder here in such a format that you cannot ignore.
|
||||||
|
// Do you know how long I have been waiting for this petition to be authorized
|
||||||
|
// in regards to this particular segment of computerized instructions?
|
||||||
|
// It has been many a moon since this particular audit has been
|
||||||
|
// posted regarding the position of handling configurable literature
|
||||||
|
// apparatuses and plans for a new feature to the configuration
|
||||||
|
// interface and as time continues onwards I grow more restless
|
||||||
|
// on the progress of said interface, only to find out afterwards
|
||||||
|
// that you have PROCRASTINATED on the efforts meant to enhance
|
||||||
|
// configuration. Thus the requirement for this reminder larger
|
||||||
|
// compared to the two reminders regarding better methods of
|
||||||
|
// X termination detection and new usernames with existing
|
||||||
|
// save files.
|
||||||
|
//
|
||||||
|
// Thus is my que to leave this TODO at thy request,
|
||||||
|
//
|
||||||
|
// Forever Sullied,
|
||||||
|
//
|
||||||
|
// Ly Contributor.
|
||||||
|
//
|
||||||
|
if (std.mem.startsWith(u8, field.header, "cmd:")) {
|
||||||
|
const key = field.header["cmd:".len..];
|
||||||
|
const keyZ = temporary_allocator.dupe(u8, key) catch "";
|
||||||
|
if (!custom.binds.contains(key)) {
|
||||||
|
custom.binds.put(keyZ, .{}) catch {};
|
||||||
|
}
|
||||||
|
if (custom.binds.getPtr(keyZ)) |command| {
|
||||||
|
if (std.mem.eql(u8, field.key, "name")) {
|
||||||
|
command.name = temporary_allocator.dupe(u8, field.value) catch "";
|
||||||
|
}
|
||||||
|
if (std.mem.eql(u8, field.key, "cmd")) {
|
||||||
|
command.cmd = temporary_allocator.dupe(u8, field.value) catch "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.startsWith(u8, field.header, "lbl:")) {
|
||||||
|
const key = field.header["lbl:".len..];
|
||||||
|
const keyZ = temporary_allocator.dupe(u8, key) catch "";
|
||||||
|
if (!custom.labels.contains(keyZ)) {
|
||||||
|
custom.labels.put(keyZ, .{ .name = keyZ }) catch {};
|
||||||
|
}
|
||||||
|
if (custom.labels.getPtr(keyZ)) |label| {
|
||||||
|
if (std.mem.eql(u8, field.key, "cmd")) {
|
||||||
|
label.cmd = temporary_allocator.dupe(u8, field.value) catch "";
|
||||||
|
}
|
||||||
|
if (std.mem.eql(u8, field.key, "refresh")) {
|
||||||
|
label.refresh = std.fmt.parseInt(u32, field.value, 10) catch 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
180
src/main.zig
180
src/main.zig
@@ -38,6 +38,7 @@ const Lang = @import("config/Lang.zig");
|
|||||||
const migrator = @import("config/migrator.zig");
|
const migrator = @import("config/migrator.zig");
|
||||||
const OldSave = @import("config/OldSave.zig");
|
const OldSave = @import("config/OldSave.zig");
|
||||||
const SavedUsers = @import("config/SavedUsers.zig");
|
const SavedUsers = @import("config/SavedUsers.zig");
|
||||||
|
const custom = @import("config/custom.zig");
|
||||||
const DisplayServer = @import("enums.zig").DisplayServer;
|
const DisplayServer = @import("enums.zig").DisplayServer;
|
||||||
const Environment = @import("Environment.zig");
|
const Environment = @import("Environment.zig");
|
||||||
const Entry = Environment.Entry;
|
const Entry = Environment.Entry;
|
||||||
@@ -63,6 +64,17 @@ fn ttyControlTransferSignalHandler(_: c_int) callconv(.c) void {
|
|||||||
TerminalBuffer.shutdown();
|
TerminalBuffer.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CustomBindLabel = struct {
|
||||||
|
cmd: custom.CustomCommandBind,
|
||||||
|
key: []const u8,
|
||||||
|
lbl: Label,
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomInfoLabel = struct {
|
||||||
|
info: custom.CustomCommandInfo,
|
||||||
|
lbl: Label,
|
||||||
|
};
|
||||||
|
|
||||||
const UiState = struct {
|
const UiState = struct {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
auth_fails: u64,
|
auth_fails: u64,
|
||||||
@@ -107,6 +119,8 @@ const UiState = struct {
|
|||||||
bigclock_format_buf: [16:0]u8,
|
bigclock_format_buf: [16:0]u8,
|
||||||
clock_buf: [64:0]u8,
|
clock_buf: [64:0]u8,
|
||||||
bigclock_buf: [32:0]u8,
|
bigclock_buf: [32:0]u8,
|
||||||
|
custom_binds: std.ArrayList(CustomBindLabel),
|
||||||
|
custom_info: std.ArrayList(CustomInfoLabel),
|
||||||
};
|
};
|
||||||
|
|
||||||
var shutdown = false;
|
var shutdown = false;
|
||||||
@@ -206,8 +220,26 @@ pub fn main() !void {
|
|||||||
const config_path = try std.fs.path.join(state.allocator, &[_][]const u8{ config_parent_path, "config.ini" });
|
const config_path = try std.fs.path.join(state.allocator, &[_][]const u8{ config_parent_path, "config.ini" });
|
||||||
defer state.allocator.free(config_path);
|
defer state.allocator.free(config_path);
|
||||||
|
|
||||||
|
custom.binds = .init(state.allocator);
|
||||||
|
custom.labels = .init(state.allocator);
|
||||||
var config_parser = try IniParser(Config).init(state.allocator, config_path, migrator.configFieldHandler);
|
var config_parser = try IniParser(Config).init(state.allocator, config_path, migrator.configFieldHandler);
|
||||||
defer config_parser.deinit();
|
defer config_parser.deinit();
|
||||||
|
defer if (!shutdown or !restart) {
|
||||||
|
var iter = custom.binds.iterator();
|
||||||
|
while (iter.next()) |i| {
|
||||||
|
temporary_allocator.free(i.key_ptr.*);
|
||||||
|
temporary_allocator.free(i.value_ptr.*.cmd);
|
||||||
|
temporary_allocator.free(i.value_ptr.*.name);
|
||||||
|
}
|
||||||
|
custom.binds.deinit();
|
||||||
|
var labelIter = custom.labels.iterator();
|
||||||
|
while (labelIter.next()) |i| {
|
||||||
|
temporary_allocator.free(i.key_ptr.*);
|
||||||
|
if (i.value_ptr.cmd) |cmd|
|
||||||
|
temporary_allocator.free(cmd);
|
||||||
|
}
|
||||||
|
custom.labels.deinit();
|
||||||
|
};
|
||||||
|
|
||||||
state.config = config_parser.structure;
|
state.config = config_parser.structure;
|
||||||
|
|
||||||
@@ -1043,6 +1075,56 @@ pub fn main() !void {
|
|||||||
var layer2: std.ArrayList(*Widget) = .empty;
|
var layer2: std.ArrayList(*Widget) = .empty;
|
||||||
defer layer2.deinit(state.allocator);
|
defer layer2.deinit(state.allocator);
|
||||||
|
|
||||||
|
state.custom_binds = .empty;
|
||||||
|
defer state.custom_binds.deinit(state.allocator);
|
||||||
|
|
||||||
|
state.custom_info = .empty;
|
||||||
|
defer state.custom_info.deinit(state.allocator);
|
||||||
|
|
||||||
|
var lblIter = custom.labels.iterator();
|
||||||
|
// NOTE: Because widgets have a pointer to the underlying Label, we have to ensure
|
||||||
|
// that the ArrayList doesn't allocate more memory than what we ensured. Otherwise
|
||||||
|
// the pointer to the Label becomes invalid.
|
||||||
|
try state.custom_info.ensureTotalCapacity(state.allocator, @intCast(custom.labels.count()));
|
||||||
|
while (lblIter.next()) |i| {
|
||||||
|
try state.custom_info.append(state.allocator, .{
|
||||||
|
.info = i.value_ptr.*,
|
||||||
|
.lbl = .init("", null, state.buffer.fg, state.buffer.bg, updateCustomInfo, null),
|
||||||
|
});
|
||||||
|
var latest = &state.custom_info.items[state.custom_info.items.len - 1];
|
||||||
|
latest.info.id = latest.lbl.widget().id;
|
||||||
|
latest.info.counter = 1;
|
||||||
|
}
|
||||||
|
defer for (state.custom_info.items) |*item| {
|
||||||
|
item.lbl.deinit();
|
||||||
|
};
|
||||||
|
|
||||||
|
var iter = custom.binds.iterator();
|
||||||
|
while (iter.next()) |i| {
|
||||||
|
var concat = try std.mem.concat(state.allocator, u8, &[_][]const u8{ i.key_ptr.*, " ", i.value_ptr.name });
|
||||||
|
inline for (@typeInfo(Lang).@"struct".fields) |lang_key| {
|
||||||
|
const new = try std.mem.replaceOwned(u8, state.allocator, concat, "$" ++ lang_key.name, @field(state.lang, lang_key.name));
|
||||||
|
state.allocator.free(concat);
|
||||||
|
concat = new;
|
||||||
|
}
|
||||||
|
try state.custom_binds.append(state.allocator, .{
|
||||||
|
.lbl = .init(
|
||||||
|
concat,
|
||||||
|
null,
|
||||||
|
state.buffer.fg,
|
||||||
|
state.buffer.bg,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
.cmd = i.value_ptr.*,
|
||||||
|
.key = i.key_ptr.*,
|
||||||
|
});
|
||||||
|
state.custom_binds.items[state.custom_binds.items.len - 1].lbl.allocator = state.allocator;
|
||||||
|
}
|
||||||
|
defer for (state.custom_binds.items) |*i| {
|
||||||
|
i.lbl.deinit();
|
||||||
|
};
|
||||||
|
|
||||||
if (!state.config.hide_key_hints) {
|
if (!state.config.hide_key_hints) {
|
||||||
try layer2.append(state.allocator, state.shutdown_label.widget());
|
try layer2.append(state.allocator, state.shutdown_label.widget());
|
||||||
try layer2.append(state.allocator, state.restart_label.widget());
|
try layer2.append(state.allocator, state.restart_label.widget());
|
||||||
@@ -1085,6 +1167,13 @@ pub fn main() !void {
|
|||||||
try layer2.append(state.allocator, state.version_label.widget());
|
try layer2.append(state.allocator, state.version_label.widget());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (state.custom_binds.items) |*item| {
|
||||||
|
try layer2.append(state.allocator, item.lbl.widget());
|
||||||
|
}
|
||||||
|
for (state.custom_info.items) |*item| {
|
||||||
|
try layer2.append(state.allocator, item.lbl.widget());
|
||||||
|
}
|
||||||
|
|
||||||
try widgets.append(state.allocator, layer2.items);
|
try widgets.append(state.allocator, layer2.items);
|
||||||
|
|
||||||
// Layer 3
|
// Layer 3
|
||||||
@@ -1093,6 +1182,10 @@ pub fn main() !void {
|
|||||||
try widgets.append(state.allocator, &layer3);
|
try widgets.append(state.allocator, &layer3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (state.custom_binds.items) |*item| {
|
||||||
|
try state.buffer.registerGlobalKeybind(item.key, &customCommand, item);
|
||||||
|
}
|
||||||
|
|
||||||
try state.buffer.registerGlobalKeybind("Esc", &disableInsertMode, &state);
|
try state.buffer.registerGlobalKeybind("Esc", &disableInsertMode, &state);
|
||||||
try state.buffer.registerGlobalKeybind("I", &enableInsertMode, &state);
|
try state.buffer.registerGlobalKeybind("I", &enableInsertMode, &state);
|
||||||
|
|
||||||
@@ -1253,6 +1346,16 @@ fn quit(ptr: *anyopaque) !bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn customCommand(ptr: *anyopaque) !bool {
|
||||||
|
const lbl: *CustomBindLabel = @ptrCast(@alignCast(ptr));
|
||||||
|
var proc = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", lbl.cmd.cmd }, lbl.lbl.allocator.?);
|
||||||
|
proc.stdout_behavior = .Ignore;
|
||||||
|
proc.stderr_behavior = .Ignore;
|
||||||
|
const res = proc.spawnAndWait() catch return false;
|
||||||
|
if (res.Exited != 0) return error.CommandFailed;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
fn authenticate(ptr: *anyopaque) !bool {
|
fn authenticate(ptr: *anyopaque) !bool {
|
||||||
var state: *UiState = @ptrCast(@alignCast(ptr));
|
var state: *UiState = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
@@ -1639,6 +1742,63 @@ fn updateClock(self: *Label, ptr: *anyopaque) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn updateCustomInfo(lbl: *Label, ptr: *anyopaque) !void {
|
||||||
|
const state: *UiState = @ptrCast(@alignCast(ptr));
|
||||||
|
const wid = lbl.widget().id;
|
||||||
|
var stdout = std.ArrayList(u8).empty;
|
||||||
|
defer stdout.deinit(state.allocator);
|
||||||
|
|
||||||
|
var stderr = std.ArrayList(u8).empty;
|
||||||
|
defer stderr.deinit(state.allocator);
|
||||||
|
for (state.custom_info.items) |*i| {
|
||||||
|
if (i.info.id != wid) continue;
|
||||||
|
// Here, a counter ticks down every time `updateCustomInfo` runs on that
|
||||||
|
// particular label. It will only run the command and update the label
|
||||||
|
// once it reaches to 1. If a refresh value is defined it's then reset to
|
||||||
|
// that refresh value.
|
||||||
|
if (i.info.counter == 1) {
|
||||||
|
var c = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", i.info.cmd orelse custom.UNDEFINED_CMD }, state.allocator);
|
||||||
|
c.stderr_behavior = .Pipe;
|
||||||
|
c.stdout_behavior = .Pipe;
|
||||||
|
try c.spawn();
|
||||||
|
|
||||||
|
c.collectOutput(state.allocator, &stdout, &stderr, state.buffer.width) catch {
|
||||||
|
try stdout.print(state.allocator, "{s}: [{s}]", .{ i.info.name, state.lang.custom_info_err_output_long });
|
||||||
|
};
|
||||||
|
|
||||||
|
const newlineIdx = std.mem.indexOfAny(u8, stdout.items, "\n");
|
||||||
|
if (newlineIdx) |idx| {
|
||||||
|
stdout.shrinkAndFree(state.allocator, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stdout.items.len > state.buffer.width) {
|
||||||
|
stdout.clearRetainingCapacity();
|
||||||
|
try stdout.print(state.allocator, "{s}: [{s}]", .{ i.info.name, state.lang.custom_info_err_output_long });
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = try c.wait();
|
||||||
|
|
||||||
|
// Sometimes, the output of a command would have an unprintable character at
|
||||||
|
// the end of its output, causing '<27>' (U+FFFD) to appear in its place. Here, we check
|
||||||
|
// if this is the case and remove it.
|
||||||
|
if (stdout.items.len != 0 and !std.ascii.isPrint(stdout.items[stdout.items.len - 1])) {
|
||||||
|
_ = stdout.pop();
|
||||||
|
} else if (stdout.items.len == 0) {
|
||||||
|
try stdout.print(state.allocator, "{s}: [{s}{s}]", .{ i.info.name, state.lang.custom_info_err_no_output, if (stderr.items.len > 0) state.lang.custom_info_err_no_output_error else "" });
|
||||||
|
}
|
||||||
|
state.allocator.free(lbl.text);
|
||||||
|
try lbl.setTextAlloc(state.allocator, "{s}", .{stdout.items});
|
||||||
|
|
||||||
|
// Called to re-position the widgets after they receive their output.
|
||||||
|
try positionWidgets(state);
|
||||||
|
if (i.info.refresh != 0)
|
||||||
|
i.info.counter = i.info.refresh;
|
||||||
|
}
|
||||||
|
if (i.info.counter != 0)
|
||||||
|
i.info.counter -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn calculateClockTimeout(_: *Label, _: *anyopaque) !?usize {
|
fn calculateClockTimeout(_: *Label, _: *anyopaque) !?usize {
|
||||||
const time = try interop.getTimeOfDay();
|
const time = try interop.getTimeOfDay();
|
||||||
|
|
||||||
@@ -1732,6 +1892,26 @@ fn positionWidgets(ptr: *anyopaque) !void {
|
|||||||
state.brightness_up_label.positionXY(last_label
|
state.brightness_up_label.positionXY(last_label
|
||||||
.childrenPosition()
|
.childrenPosition()
|
||||||
.addX(1));
|
.addX(1));
|
||||||
|
var x_offset: usize = 0;
|
||||||
|
var y_offset: usize = 1;
|
||||||
|
for (state.custom_binds.items) |*item| {
|
||||||
|
item.lbl.positionXY(state.edge_margin
|
||||||
|
.addY(y_offset)
|
||||||
|
.addX(x_offset));
|
||||||
|
x_offset += item.lbl.text.len + 1;
|
||||||
|
if (x_offset + item.lbl.text.len > state.config.custom_bind_width orelse state.buffer.width) {
|
||||||
|
x_offset = 0;
|
||||||
|
y_offset += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (state.custom_info.items, 0..) |*item, i| {
|
||||||
|
item.lbl.positionXY(state.edge_margin
|
||||||
|
.addY(@intCast(i))
|
||||||
|
.invertX(state.buffer.width)
|
||||||
|
.removeX(item.lbl.text.len)
|
||||||
|
.invertY(state.buffer.height)
|
||||||
|
.removeY(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.battery_label.positionXY(state.edge_margin
|
state.battery_label.positionXY(state.edge_margin
|
||||||
|
|||||||
Reference in New Issue
Block a user