refactor: implement component-based architecture for enhanced maintainability
- Reorganize directory structure into core/, features/, and themes/ - Colocate application Nix logic, configs, scripts, and theme overrides - Implement 'Inversion of Control' for theming: apps now pull theme-specific layouts - Update flake.nix and shared library paths to match the new structure - Document the new Feature-Centric architecture in README.md
58
themes/engine/files.nix
Normal file
@@ -0,0 +1,58 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
themePath = ../palettes + "/${config.nomarchy.theme}";
|
||||
themeAppsPath = themePath + "/apps";
|
||||
nordAppsPath = ../palettes/nord/apps;
|
||||
|
||||
# Check if theme has apps/hyprland.conf
|
||||
hasHyprlandConf = builtins.pathExists (themeAppsPath + "/hyprland.conf");
|
||||
in
|
||||
{
|
||||
xdg.configFile."nomarchy/current/theme" = {
|
||||
source = themePath;
|
||||
recursive = true;
|
||||
};
|
||||
|
||||
# Ensure theme-specific hyprland config exists, fallback to nord if not
|
||||
# Now checking in apps/ subdirectory
|
||||
xdg.configFile."nomarchy/current/theme/apps/hyprland.conf" = lib.mkIf (!hasHyprlandConf) {
|
||||
source = nordAppsPath + "/hyprland.conf";
|
||||
};
|
||||
|
||||
# Legacy compatibility: symlink apps/hyprland.conf to root for scripts expecting old path
|
||||
xdg.configFile."nomarchy/current/theme/hyprland.conf" = {
|
||||
source =
|
||||
if hasHyprlandConf
|
||||
then themeAppsPath + "/hyprland.conf"
|
||||
else nordAppsPath + "/hyprland.conf";
|
||||
};
|
||||
|
||||
xdg.configFile."nomarchy/current/theme.name".text = config.nomarchy.theme;
|
||||
|
||||
# Expose branding assets
|
||||
xdg.configFile."nomarchy/branding/logo.png".source = ../../core/branding/logo.png;
|
||||
xdg.configFile."nomarchy/branding/logo.txt".source = ../../core/branding/logo.txt;
|
||||
xdg.configFile."nomarchy/branding/logo.svg".source = ../../core/branding/logo.svg;
|
||||
xdg.configFile."nomarchy/branding/icon.png".source = ../../core/branding/icon.png;
|
||||
xdg.configFile."nomarchy/branding/icon.txt".source = ../../core/branding/icon.txt;
|
||||
|
||||
# Expose all themes to the system via local share for script accessibility
|
||||
# We filter out images to prevent Nix Store bloat
|
||||
xdg.dataFile."nomarchy/themes".source = builtins.path {
|
||||
name = "nomarchy-themes-no-images";
|
||||
path = ../palettes;
|
||||
filter = path: type:
|
||||
let
|
||||
baseName = baseNameOf path;
|
||||
in
|
||||
! (type == "regular" && (
|
||||
lib.hasSuffix ".jpg" baseName ||
|
||||
lib.hasSuffix ".png" baseName ||
|
||||
lib.hasSuffix ".jpeg" baseName
|
||||
));
|
||||
};
|
||||
|
||||
# Nautilus python extensions
|
||||
xdg.dataFile."nautilus-python/extensions/localsend.py".source = ../../core/home/config/nautilus-python/extensions/localsend.py;
|
||||
}
|
||||
128
themes/engine/loader.nix
Normal file
@@ -0,0 +1,128 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# Theme Loader Module
|
||||
#
|
||||
# This module handles loading and deploying theme-specific application configs.
|
||||
# It reads the active theme from state and deploys configs from the theme's apps/
|
||||
# subdirectory to appropriate locations.
|
||||
#
|
||||
# Theme structure expected:
|
||||
# assets/themes/<theme-name>/
|
||||
# ├── colors.toml # Color palette (required)
|
||||
# ├── light.mode # Marker for light themes (optional)
|
||||
# ├── icons.theme # Icon theme name
|
||||
# ├── backgrounds/ # Wallpapers
|
||||
# ├── apps/ # App-specific themed configs
|
||||
# │ ├── alacritty.toml
|
||||
# │ ├── kitty.conf
|
||||
# │ ├── waybar.css
|
||||
# │ ├── hyprland-colors.conf
|
||||
# │ ├── mako.conf
|
||||
# │ ├── swayosd.css
|
||||
# │ ├── btop.theme
|
||||
# │ ├── vscode.json
|
||||
# │ └── neovim.lua
|
||||
# └── preview.png
|
||||
|
||||
let
|
||||
nomarchyLib = import ../lib { inherit lib; };
|
||||
assetsPath = ../palettes;
|
||||
activeTheme = config.nomarchy.theme;
|
||||
themePath = assetsPath + "/${activeTheme}";
|
||||
themeAppsPath = themePath + "/apps";
|
||||
|
||||
# Check if a theme has an apps directory
|
||||
themeHasApps = builtins.pathExists themeAppsPath;
|
||||
|
||||
# Get the color palette for template processing
|
||||
palette = nomarchyLib.getPalette activeTheme;
|
||||
|
||||
# Helper to check if a file exists in theme
|
||||
hasThemeFile = name: builtins.pathExists (themePath + "/${name}");
|
||||
hasThemeAppFile = name: themeHasApps && builtins.pathExists (themeAppsPath + "/${name}");
|
||||
|
||||
# All app configs are now in apps/ subdirectory
|
||||
# Legacy root-level files are no longer supported
|
||||
|
||||
in
|
||||
{
|
||||
options.nomarchy.themeLoader = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to enable automatic theme app config loading.";
|
||||
};
|
||||
|
||||
apps = {
|
||||
btop = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to load btop theme from active theme.";
|
||||
};
|
||||
waybar = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to load waybar CSS from active theme.";
|
||||
};
|
||||
mako = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to load mako config from active theme.";
|
||||
};
|
||||
kitty = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to load kitty config from active theme.";
|
||||
};
|
||||
alacritty = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to load alacritty config from active theme.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.nomarchy.themeLoader.enable {
|
||||
# Deploy btop theme if available in apps/
|
||||
xdg.configFile."btop/themes/nomarchy.theme" = lib.mkIf (config.nomarchy.themeLoader.apps.btop && hasThemeAppFile "btop.theme") {
|
||||
source = lib.mkDefault (themeAppsPath + "/btop.theme");
|
||||
};
|
||||
|
||||
# Note: Waybar CSS is generated inline in waybar.nix using colorScheme
|
||||
# This loader would be used if themes provide complete waybar.css overrides
|
||||
# For now, theme waybar.css files are only used for themes that need
|
||||
# significantly different styling (like retro-82)
|
||||
|
||||
# Create theme info file for scripts
|
||||
xdg.configFile."nomarchy/theme-loader/current".text = ''
|
||||
THEME_NAME="${activeTheme}"
|
||||
THEME_PATH="${toString themePath}"
|
||||
THEME_HAS_APPS="${if themeHasApps then "true" else "false"}"
|
||||
IS_LIGHT_MODE="${if config.nomarchy.isLightMode then "true" else "false"}"
|
||||
ICONS_THEME="${config.nomarchy.iconsTheme}"
|
||||
'';
|
||||
|
||||
# Expose palette as shell-sourceable file for scripts
|
||||
xdg.configFile."nomarchy/theme-loader/palette.sh".text = ''
|
||||
# Auto-generated palette for ${activeTheme}
|
||||
# Source this file in scripts to get theme colors
|
||||
|
||||
BASE00="${palette.base00}"
|
||||
BASE01="${palette.base01}"
|
||||
BASE02="${palette.base02}"
|
||||
BASE03="${palette.base03}"
|
||||
BASE04="${palette.base04}"
|
||||
BASE05="${palette.base05}"
|
||||
BASE06="${palette.base06}"
|
||||
BASE07="${palette.base07}"
|
||||
BASE08="${palette.base08}"
|
||||
BASE09="${palette.base09}"
|
||||
BASE0A="${palette.base0A}"
|
||||
BASE0B="${palette.base0B}"
|
||||
BASE0C="${palette.base0C}"
|
||||
BASE0D="${palette.base0D}"
|
||||
BASE0E="${palette.base0E}"
|
||||
BASE0F="${palette.base0F}"
|
||||
'';
|
||||
};
|
||||
}
|
||||
30
themes/engine/plymouth.nix
Normal file
@@ -0,0 +1,30 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
nomarchy-plymouth = pkgs.stdenv.mkDerivation {
|
||||
pname = "nomarchy-plymouth";
|
||||
version = "1.0";
|
||||
|
||||
src = ./plymouth;
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/share/plymouth/themes/nomarchy
|
||||
cp * $out/share/plymouth/themes/nomarchy/
|
||||
# Fix path in the plymouth file to point to the nix store
|
||||
sed -i "s|/[a-z]*/share/plymouth/themes/nomarchy|$out/share/plymouth/themes/nomarchy|g" $out/share/plymouth/themes/nomarchy/nomarchy.plymouth
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
boot.initrd.systemd.enable = lib.mkDefault true;
|
||||
boot.initrd.verbose = lib.mkDefault false;
|
||||
console.earlySetup = lib.mkDefault true;
|
||||
boot.consoleLogLevel = lib.mkDefault 0;
|
||||
boot.plymouth = {
|
||||
enable = lib.mkDefault true;
|
||||
themePackages = lib.mkDefault [ nomarchy-plymouth ];
|
||||
theme = lib.mkDefault "nomarchy";
|
||||
};
|
||||
|
||||
boot.kernelParams = lib.mkDefault [ "quiet" "splash" "loglevel=3" "rd.systemd.show_status=false" "rd.udev.log_level=3" "udev.log_priority=3" "boot.shell_on_fail" ];
|
||||
}
|
||||
BIN
themes/engine/plymouth/bullet.png
Normal file
|
After Width: | Height: | Size: 293 B |
BIN
themes/engine/plymouth/entry.png
Normal file
|
After Width: | Height: | Size: 694 B |
BIN
themes/engine/plymouth/lock.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
themes/engine/plymouth/logo.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
11
themes/engine/plymouth/nomarchy.plymouth
Normal file
@@ -0,0 +1,11 @@
|
||||
[Plymouth Theme]
|
||||
Name=Nomarchy
|
||||
Description=Omarchy splash screen.
|
||||
ModuleName=script
|
||||
|
||||
[script]
|
||||
ImageDir=/usr/share/plymouth/themes/nomarchy
|
||||
ScriptFile=/usr/share/plymouth/themes/nomarchy/nomarchy.script
|
||||
ConsoleLogBackgroundColor=0x1a1b26
|
||||
MonospaceFont=Cantarell 11
|
||||
Font=Cantarell 11
|
||||
271
themes/engine/plymouth/nomarchy.script
Normal file
@@ -0,0 +1,271 @@
|
||||
# Omarchy Plymouth Theme Script
|
||||
|
||||
Window.SetBackgroundTopColor(0.101, 0.105, 0.149);
|
||||
Window.SetBackgroundBottomColor(0.101, 0.105, 0.149);
|
||||
|
||||
logo.image = Image("logo.png");
|
||||
|
||||
# Calculate scale factor to make logo ~15% of screen height
|
||||
logo_scale_factor = (Window.GetHeight() * 0.15) / logo.image.GetHeight();
|
||||
logo_width = logo.image.GetWidth() * logo_scale_factor;
|
||||
logo_height = logo.image.GetHeight() * logo_scale_factor;
|
||||
logo.image = logo.image.Scale(logo_width, logo_height);
|
||||
|
||||
logo.sprite = Sprite(logo.image);
|
||||
logo.sprite.SetX (Window.GetWidth() / 2 - logo.image.GetWidth() / 2);
|
||||
logo.sprite.SetY (Window.GetHeight() / 2 - logo.image.GetHeight() / 2);
|
||||
logo.sprite.SetOpacity (1);
|
||||
|
||||
# Use these to adjust the progress bar timing
|
||||
global.fake_progress_limit = 0.7; # Target percentage for fake progress (0.0 to 1.0)
|
||||
global.fake_progress_duration = 15.0; # Duration in seconds to reach limit
|
||||
|
||||
# Progress bar animation variables
|
||||
global.fake_progress = 0.0;
|
||||
global.real_progress = 0.0;
|
||||
global.fake_progress_active = 0; # 0 / 1 boolean
|
||||
global.animation_frame = 0;
|
||||
global.fake_progress_start_time = 0; # Track when fake progress started
|
||||
global.password_shown = 0; # Track if password dialog has been shown
|
||||
global.max_progress = 0.0; # Track the maximum progress reached to prevent backwards movement
|
||||
|
||||
fun refresh_callback ()
|
||||
{
|
||||
global.animation_frame++;
|
||||
|
||||
# Animate fake progress to limit over time with easing
|
||||
if (global.fake_progress_active == 1)
|
||||
{
|
||||
# Calculate elapsed time since start
|
||||
elapsed_time = global.animation_frame / 50.0; # Convert frames to seconds (50 FPS)
|
||||
|
||||
# Calculate linear progress ratio (0 to 1) based on time
|
||||
time_ratio = elapsed_time / global.fake_progress_duration;
|
||||
if (time_ratio > 1.0)
|
||||
time_ratio = 1.0;
|
||||
|
||||
# Apply easing curve: ease-out quadratic
|
||||
# Formula: 1 - (1 - x)^2
|
||||
eased_ratio = 1 - ((1 - time_ratio) * (1 - time_ratio));
|
||||
|
||||
# Calculate fake progress based on eased ratio
|
||||
global.fake_progress = eased_ratio * global.fake_progress_limit;
|
||||
|
||||
# Update progress bar with fake progress
|
||||
update_progress_bar(global.fake_progress);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Plymouth.SetRefreshFunction (refresh_callback);
|
||||
|
||||
#----------------------------------------- Helper Functions --------------------------------
|
||||
|
||||
fun update_progress_bar(progress)
|
||||
{
|
||||
# Only update if progress is moving forward
|
||||
if (progress > global.max_progress)
|
||||
{
|
||||
global.max_progress = progress;
|
||||
width = Math.Int(progress_bar.original_image.GetWidth() * progress);
|
||||
if (width < 1) width = 1; # Ensure minimum width of 1 pixel
|
||||
|
||||
progress_bar.image = progress_bar.original_image.Scale(width, progress_bar.original_image.GetHeight());
|
||||
progress_bar.sprite.SetImage(progress_bar.image);
|
||||
}
|
||||
}
|
||||
|
||||
fun show_progress_bar()
|
||||
{
|
||||
progress_box.sprite.SetOpacity(1);
|
||||
progress_bar.sprite.SetOpacity(1);
|
||||
}
|
||||
|
||||
fun hide_progress_bar()
|
||||
{
|
||||
progress_box.sprite.SetOpacity(0);
|
||||
progress_bar.sprite.SetOpacity(0);
|
||||
}
|
||||
|
||||
fun show_password_dialog()
|
||||
{
|
||||
lock.sprite.SetOpacity(1);
|
||||
entry.sprite.SetOpacity(1);
|
||||
}
|
||||
|
||||
fun hide_password_dialog()
|
||||
{
|
||||
lock.sprite.SetOpacity(0);
|
||||
entry.sprite.SetOpacity(0);
|
||||
for (index = 0; bullet.sprites[index]; index++)
|
||||
bullet.sprites[index].SetOpacity(0);
|
||||
}
|
||||
|
||||
fun start_fake_progress()
|
||||
{
|
||||
# Don't reset if we already have progress
|
||||
if (global.max_progress == 0.0)
|
||||
{
|
||||
global.fake_progress = 0.0;
|
||||
global.real_progress = 0.0;
|
||||
update_progress_bar(0.0);
|
||||
}
|
||||
global.fake_progress_active = 1;
|
||||
global.animation_frame = 0;
|
||||
}
|
||||
|
||||
fun stop_fake_progress()
|
||||
{
|
||||
global.fake_progress_active = 0;
|
||||
}
|
||||
|
||||
#----------------------------------------- Dialogue --------------------------------
|
||||
|
||||
lock.image = Image("lock.png");
|
||||
entry.image = Image("entry.png");
|
||||
bullet.image = Image("bullet.png");
|
||||
|
||||
entry.sprite = Sprite(entry.image);
|
||||
entry.x = Window.GetWidth()/2 - entry.image.GetWidth() / 2;
|
||||
entry.y = logo.sprite.GetY() + logo.image.GetHeight() + 40;
|
||||
entry.sprite.SetPosition(entry.x, entry.y, 10001);
|
||||
entry.sprite.SetOpacity(0);
|
||||
|
||||
# Scale lock to be slightly shorter than entry field height
|
||||
# Original lock is 84x96, entry height determines scale
|
||||
lock_height = entry.image.GetHeight() * 0.8;
|
||||
lock_scale = lock_height / 96;
|
||||
lock_width = 84 * lock_scale;
|
||||
|
||||
scaled_lock = lock.image.Scale(lock_width, lock_height);
|
||||
lock.sprite = Sprite(scaled_lock);
|
||||
lock.x = entry.x - lock_width - 15;
|
||||
lock.y = entry.y + entry.image.GetHeight()/2 - lock_height/2;
|
||||
lock.sprite.SetPosition(lock.x, lock.y, 10001);
|
||||
lock.sprite.SetOpacity(0);
|
||||
|
||||
# Bullet array
|
||||
bullet.sprites = [];
|
||||
|
||||
fun display_normal_callback ()
|
||||
{
|
||||
hide_password_dialog();
|
||||
|
||||
# Get current mode
|
||||
mode = Plymouth.GetMode();
|
||||
|
||||
# Only show progress bar for boot and resume modes
|
||||
if (mode == "boot" || mode == "resume")
|
||||
{
|
||||
show_progress_bar();
|
||||
start_fake_progress();
|
||||
}
|
||||
}
|
||||
|
||||
fun display_password_callback (prompt, bullets)
|
||||
{
|
||||
global.password_shown = 1; # Mark that password dialog has been shown
|
||||
|
||||
# Reset progress when password dialog appears
|
||||
stop_fake_progress();
|
||||
hide_progress_bar();
|
||||
global.max_progress = 0.0;
|
||||
global.fake_progress = 0.0;
|
||||
global.real_progress = 0.0;
|
||||
show_password_dialog();
|
||||
|
||||
# Clear all bullets first
|
||||
for (index = 0; bullet.sprites[index]; index++)
|
||||
bullet.sprites[index].SetOpacity(0);
|
||||
|
||||
# Create and show bullets for current password (max 21)
|
||||
max_bullets = 21;
|
||||
bullets_to_show = bullets;
|
||||
if (bullets_to_show > max_bullets)
|
||||
bullets_to_show = max_bullets;
|
||||
|
||||
for (index = 0; index < bullets_to_show; index++)
|
||||
{
|
||||
if (!bullet.sprites[index])
|
||||
{
|
||||
# Scale bullet image to 7x7 pixels
|
||||
scaled_bullet = bullet.image.Scale(7, 7);
|
||||
bullet.sprites[index] = Sprite(scaled_bullet);
|
||||
bullet.x = entry.x + 20 + index * (7 + 5);
|
||||
bullet.y = entry.y + entry.image.GetHeight() / 2 - 3.5;
|
||||
bullet.sprites[index].SetPosition(bullet.x, bullet.y, 10002);
|
||||
}
|
||||
bullet.sprites[index].SetOpacity(1);
|
||||
}
|
||||
}
|
||||
|
||||
Plymouth.SetDisplayNormalFunction(display_normal_callback);
|
||||
Plymouth.SetDisplayPasswordFunction(display_password_callback);
|
||||
|
||||
#----------------------------------------- Progress Bar --------------------------------
|
||||
|
||||
progress_box.image = Image("progress_box.png");
|
||||
progress_box.sprite = Sprite(progress_box.image);
|
||||
|
||||
progress_box.x = Window.GetWidth() / 2 - progress_box.image.GetWidth() / 2;
|
||||
progress_box.y = entry.y + entry.image.GetHeight() / 2 - progress_box.image.GetHeight() / 2;
|
||||
progress_box.sprite.SetPosition(progress_box.x, progress_box.y, 0);
|
||||
progress_box.sprite.SetOpacity(0);
|
||||
|
||||
progress_bar.original_image = Image("progress_bar.png");
|
||||
progress_bar.sprite = Sprite();
|
||||
progress_bar.image = progress_bar.original_image.Scale(1, progress_bar.original_image.GetHeight());
|
||||
|
||||
progress_bar.x = Window.GetWidth() / 2 - progress_bar.original_image.GetWidth() / 2;
|
||||
progress_bar.y = progress_box.y + (progress_box.image.GetHeight() - progress_bar.original_image.GetHeight()) / 2;
|
||||
progress_bar.sprite.SetPosition(progress_bar.x, progress_bar.y, 1);
|
||||
progress_bar.sprite.SetOpacity(0);
|
||||
|
||||
fun progress_callback (duration, progress)
|
||||
{
|
||||
global.real_progress = progress;
|
||||
|
||||
# If real progress is above limit, stop fake progress and use real progress
|
||||
if (progress > global.fake_progress_limit)
|
||||
{
|
||||
stop_fake_progress();
|
||||
update_progress_bar(progress);
|
||||
}
|
||||
}
|
||||
|
||||
Plymouth.SetBootProgressFunction(progress_callback);
|
||||
|
||||
#----------------------------------------- Quit --------------------------------
|
||||
|
||||
fun quit_callback ()
|
||||
{
|
||||
logo.sprite.SetOpacity (1);
|
||||
}
|
||||
|
||||
Plymouth.SetQuitFunction(quit_callback);
|
||||
|
||||
#----------------------------------------- Message --------------------------------
|
||||
|
||||
message_sprite = Sprite();
|
||||
message_sprite.SetPosition(10, 10, 10000);
|
||||
|
||||
fun display_message_callback (text)
|
||||
{
|
||||
my_image = Image.Text(text, 1, 1, 1);
|
||||
message_sprite.SetImage(my_image);
|
||||
}
|
||||
|
||||
fun hide_message_callback (text)
|
||||
{
|
||||
message_sprite.SetOpacity(0);
|
||||
}
|
||||
|
||||
Plymouth.SetDisplayMessageFunction (display_message_callback);
|
||||
Plymouth.SetHideMessageFunction (hide_message_callback);
|
||||
|
||||
# Initialize progress bar immediately for normal boots
|
||||
if (Plymouth.GetMode() == "boot" || Plymouth.GetMode() == "resume")
|
||||
{
|
||||
show_progress_bar();
|
||||
start_fake_progress();
|
||||
}
|
||||
BIN
themes/engine/plymouth/progress_bar.png
Normal file
|
After Width: | Height: | Size: 314 B |
BIN
themes/engine/plymouth/progress_box.png
Normal file
|
After Width: | Height: | Size: 314 B |
6
themes/engine/scripts/nomarchy-font-current
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Returns the name of the current monospace font being used by extracting it from the Waybar stylesheet.
|
||||
# This can be changed using nomarchy-font-set.
|
||||
|
||||
grep -oP 'font-family:\s*["'\'']?\K[^;"'\'']+' ~/.config/waybar/style.css | head -n1
|
||||
5
themes/engine/scripts/nomarchy-font-list
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Returns a list of all the monospace fonts available on the system that can be set using nomarchy-font-set.
|
||||
|
||||
fc-list :spacing=100 -f "%{family[0]}\n" | grep -v -i -E 'emoji|signwriting|nomarchy' | sort -u
|
||||
38
themes/engine/scripts/nomarchy-font-set
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Set the system-wide monospace font that should be used by the terminal, hyprlock, waybar, swayosd, etc.
|
||||
# Declarative version for Nomarchy NixOS.
|
||||
|
||||
font_name="$1"
|
||||
|
||||
if [[ -z $font_name ]]; then
|
||||
echo "Usage: nomarchy-font-set <font-name>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STATE_DIR="$HOME/.config/nomarchy"
|
||||
STATE_FILE="$STATE_DIR/state.json"
|
||||
|
||||
mkdir -p "$STATE_DIR"
|
||||
[[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE"
|
||||
|
||||
if fc-list | grep -iq "$font_name"; then
|
||||
TMP_JSON=$(mktemp)
|
||||
jq --arg font "$font_name" '.font = $font' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
|
||||
echo "Font set to $font_name declaratively. Applying changes..."
|
||||
env-update
|
||||
|
||||
# Instant feedback for certain apps via IPC
|
||||
if pgrep -x kitty; then
|
||||
pkill -USR1 kitty
|
||||
fi
|
||||
if pgrep -x ghostty; then
|
||||
pkill -SIGUSR2 ghostty
|
||||
notify-send -u low " You must restart Ghostty to see font change"
|
||||
fi
|
||||
|
||||
nomarchy-hook font-set "$font_name"
|
||||
else
|
||||
echo "Font '$font_name' not found."
|
||||
exit 1
|
||||
fi
|
||||
7
themes/engine/scripts/nomarchy-show-done
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Display a "Done!" message with a spinner and wait for user to press any key.
|
||||
# Used by various install scripts to indicate completion.
|
||||
|
||||
echo
|
||||
gum spin --spinner "globe" --title "Done! Press any key to close..." -- bash -c 'read -n 1 -s'
|
||||
9
themes/engine/scripts/nomarchy-show-logo
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Display the Nomarchy logo in the terminal using green color.
|
||||
# Used by various presentation scripts to show branding.
|
||||
|
||||
clear
|
||||
echo -e "\033[32m"
|
||||
cat < ~/.config/nomarchy/branding/logo.txt
|
||||
echo -e "\033[0m"
|
||||
7
themes/engine/scripts/nomarchy-theme-bg-install
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
CURRENT_THEME_NAME=$(cat "$HOME/.config/nomarchy/current/theme.name")
|
||||
THEME_USER_BACKGROUNDS="$HOME/.config/nomarchy/backgrounds/$CURRENT_THEME_NAME"
|
||||
|
||||
mkdir -p "$THEME_USER_BACKGROUNDS"
|
||||
nautilus "$THEME_USER_BACKGROUNDS"
|
||||
57
themes/engine/scripts/nomarchy-theme-bg-next
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Cycles through the background images available for the current theme.
|
||||
# Declarative + Hybrid (instant swww) for Nomarchy NixOS.
|
||||
|
||||
STATE_DIR="$HOME/.config/nomarchy"
|
||||
STATE_FILE="$STATE_DIR/state.json"
|
||||
mkdir -p "$STATE_DIR"
|
||||
[[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE"
|
||||
|
||||
THEME_NAME=$(jq -r '.theme // "nord"' "$STATE_FILE")
|
||||
|
||||
# Resolve themes directory (Built-in from Nix store via Home Manager, or user extra)
|
||||
if [ -d "$HOME/.config/nomarchy/themes/$THEME_NAME" ]; then
|
||||
THEMES_DIR="$HOME/.config/nomarchy/themes"
|
||||
else
|
||||
THEMES_DIR="$HOME/.local/share/nomarchy/themes"
|
||||
fi
|
||||
|
||||
BG_DIR="$THEMES_DIR/$THEME_NAME/backgrounds"
|
||||
|
||||
if [ ! -d "$BG_DIR" ]; then
|
||||
notify-send "No background directory found for theme $THEME_NAME"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mapfile -t BACKGROUNDS < <(ls "$BG_DIR" | sort)
|
||||
TOTAL=${#BACKGROUNDS[@]}
|
||||
|
||||
if (( TOTAL == 0 )); then
|
||||
notify-send "No backgrounds found in $BG_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CURRENT_BG=$(jq -r '.wallpaper' "$STATE_FILE")
|
||||
INDEX=-1
|
||||
for i in "${!BACKGROUNDS[@]}"; do
|
||||
if [[ "$BG_DIR/${BACKGROUNDS[$i]}" == "$CURRENT_BG" ]]; then
|
||||
INDEX=$i
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
NEXT_INDEX=$(((INDEX + 1) % TOTAL))
|
||||
NEW_BG="$BG_DIR/${BACKGROUNDS[$NEXT_INDEX]}"
|
||||
|
||||
TMP_JSON=$(mktemp)
|
||||
jq --arg wp "$NEW_BG" '.wallpaper = $wp' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
|
||||
|
||||
# Instant feedback via swww
|
||||
if pgrep -x swww-daemon >/dev/null; then
|
||||
swww img "$NEW_BG" --transition-type outer --transition-pos 0.85,0.97 --transition-step 90
|
||||
else
|
||||
swww init && swww img "$NEW_BG"
|
||||
fi
|
||||
|
||||
echo "Background set to $NEW_BG declaratively."
|
||||
18
themes/engine/scripts/nomarchy-theme-bg-set
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Sets the specified image as the current background
|
||||
|
||||
if [[ -z $1 ]]; then
|
||||
echo "Usage: nomarchy-theme-bg-set <path-to-image>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BACKGROUND="$1"
|
||||
CURRENT_BACKGROUND_LINK="$HOME/.config/nomarchy/current/background"
|
||||
|
||||
# Create symlink to the new background
|
||||
ln -nsf "$BACKGROUND" "$CURRENT_BACKGROUND_LINK"
|
||||
|
||||
# Kill existing swaybg and start new one
|
||||
pkill -x swaybg
|
||||
setsid uwsm-app -- swaybg -i "$CURRENT_BACKGROUND_LINK" -m fill >/dev/null 2>&1 &
|
||||
9
themes/engine/scripts/nomarchy-theme-current
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
THEME_NAME_PATH="$HOME/.config/nomarchy/current/theme.name"
|
||||
|
||||
if [[ -f $THEME_NAME_PATH ]]; then
|
||||
cat $THEME_NAME_PATH | sed -E 's/(^|-)([a-z])/\1\u\2/g; s/-/ /g'
|
||||
else
|
||||
echo "Unknown"
|
||||
fi
|
||||
33
themes/engine/scripts/nomarchy-theme-install
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
|
||||
# nomarchy-theme-install: Install a new theme from a git repo for Nomarchy
|
||||
# Usage: nomarchy-theme-install <git-repo-url>
|
||||
|
||||
if [[ -z $1 ]]; then
|
||||
echo -e "\e[32mSee https://manuals.omamix.org/2/the-nomarchy-manual/90/extra-themes\n\e[0m"
|
||||
REPO_URL=$(gum input --placeholder="Git repo URL for theme" --header="")
|
||||
else
|
||||
REPO_URL="$1"
|
||||
fi
|
||||
|
||||
if [[ -z $REPO_URL ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
THEMES_DIR="$HOME/.config/nomarchy/themes"
|
||||
THEME_NAME=$(basename "$REPO_URL" .git | sed -E 's/^nomarchy-//; s/-theme$//')
|
||||
THEME_PATH="$THEMES_DIR/$THEME_NAME"
|
||||
|
||||
# Remove existing theme if present
|
||||
if [[ -d $THEME_PATH ]]; then
|
||||
rm -rf "$THEME_PATH"
|
||||
fi
|
||||
|
||||
# Clone the repo directly to ~/.config/nomarchy/themes
|
||||
if ! git clone "$REPO_URL" "$THEME_PATH"; then
|
||||
echo "Error: Failed to clone theme repo."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Apply the new theme with nomarchy-theme-set
|
||||
nomarchy-theme-set $THEME_NAME
|
||||
8
themes/engine/scripts/nomarchy-theme-list
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
{
|
||||
find ~/.config/nomarchy/themes/ -mindepth 1 -maxdepth 1 \( -type d -o -type l \) -printf '%f\n'
|
||||
find "$NOMARCHY_PATH/assets/themes/" -mindepth 1 -maxdepth 1 -type d -printf '%f\n'
|
||||
} | sort -u | while read -r name; do
|
||||
echo "$name" | sed -E 's/(^|-)([a-z])/\1\u\2/g; s/-/ /g'
|
||||
done
|
||||
9
themes/engine/scripts/nomarchy-theme-refresh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Refresh the current theme from its templates.
|
||||
|
||||
THEME_NAME_PATH="$HOME/.config/nomarchy/current/theme.name"
|
||||
|
||||
if [[ -f $THEME_NAME_PATH ]]; then
|
||||
nomarchy-theme-set "$(cat $THEME_NAME_PATH)"
|
||||
fi
|
||||
35
themes/engine/scripts/nomarchy-theme-remove
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
# nomarchy-theme-remove: Remove a theme from Nomarchy by name
|
||||
# Usage: nomarchy-theme-remove <theme-name>
|
||||
|
||||
if [[ -z $1 ]]; then
|
||||
mapfile -t extra_themes < <(find ~/.config/nomarchy/themes -mindepth 1 -maxdepth 1 -type d ! -xtype l -printf '%f\n')
|
||||
|
||||
if (( ${#extra_themes[@]} > 0 )); then
|
||||
THEME_NAME=$(printf '%s\n' "${extra_themes[@]}" | sort | gum choose --header="Remove extra theme")
|
||||
else
|
||||
echo "No extra themes installed."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
THEME_NAME="$1"
|
||||
fi
|
||||
|
||||
THEMES_DIR="$HOME/.config/nomarchy/themes"
|
||||
CURRENT_DIR="$HOME/.config/nomarchy/current"
|
||||
THEME_PATH="$THEMES_DIR/$THEME_NAME"
|
||||
|
||||
# Ensure a theme was set
|
||||
if [[ -z $THEME_NAME ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if theme exists before attempting removal
|
||||
if [[ ! -d $THEME_PATH ]]; then
|
||||
echo "Error: Theme '$THEME_NAME' not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Now remove the theme directory for THEME_NAME
|
||||
rm -rf "$THEME_PATH"
|
||||
55
themes/engine/scripts/nomarchy-theme-set
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Set the system theme declaratively.
|
||||
# Usage: nomarchy-theme-set <theme-name>
|
||||
|
||||
THEME_NAME="$1"
|
||||
|
||||
if [[ -z $THEME_NAME ]]; then
|
||||
echo "Usage: nomarchy-theme-set <theme-name>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STATE_DIR="$HOME/.config/nomarchy"
|
||||
STATE_FILE="$STATE_DIR/state.json"
|
||||
|
||||
# Resolve themes directory (Built-in from Nix store via Home Manager, or user extra)
|
||||
if [ -d "$HOME/.config/nomarchy/themes/$THEME_NAME" ]; then
|
||||
THEMES_DIR="$HOME/.config/nomarchy/themes"
|
||||
else
|
||||
THEMES_DIR="$HOME/.local/share/nomarchy/themes"
|
||||
fi
|
||||
|
||||
mkdir -p "$STATE_DIR"
|
||||
[[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE"
|
||||
|
||||
if [ ! -d "$THEMES_DIR/$THEME_NAME" ] && ! [[ "$THEME_NAME" == "nord" ]]; then
|
||||
echo "Theme '$THEME_NAME' not found in $THEMES_DIR"
|
||||
fi
|
||||
|
||||
TMP_JSON=$(mktemp)
|
||||
jq --arg theme "$THEME_NAME" '.theme = $theme' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
|
||||
|
||||
# Sync to system state if we have permissions (for system-level theming like browser policies)
|
||||
SYSTEM_STATE_FILE="/etc/nixos/state.json"
|
||||
if [ -w "$SYSTEM_STATE_FILE" ] || [ -w "/etc/nixos" ]; then
|
||||
sudo jq --arg theme "$THEME_NAME" '.theme = $theme' "$SYSTEM_STATE_FILE" > /tmp/system-state.json 2>/dev/null && sudo mv /tmp/system-state.json "$SYSTEM_STATE_FILE" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Try to find a background for this theme
|
||||
BG_DIR="$THEMES_DIR/$THEME_NAME/backgrounds"
|
||||
if [ -d "$BG_DIR" ]; then
|
||||
BG=$(ls "$BG_DIR" | head -n 1)
|
||||
if [ -n "$BG" ]; then
|
||||
TMP_JSON=$(mktemp)
|
||||
jq --arg wp "$BG_DIR/$BG" '.wallpaper = $wp' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Theme set to $THEME_NAME. Applying changes with env-update..."
|
||||
rm -rf "$HOME/.config/nomarchy/current/theme"
|
||||
env-update
|
||||
|
||||
nomarchy-theme-set-templates
|
||||
|
||||
nomarchy-hook theme-set "$THEME_NAME"
|
||||
4
themes/engine/scripts/nomarchy-theme-set-keyboard
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
nomarchy-theme-set-keyboard-asus-rog
|
||||
nomarchy-theme-set-keyboard-f16
|
||||
7
themes/engine/scripts/nomarchy-theme-set-keyboard-asus-rog
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
ASUSCTL_THEME=~/.config/nomarchy/current/theme/keyboard.rgb
|
||||
|
||||
if nomarchy-cmd-present asusctl; then
|
||||
asusctl aura effect static -c $(sed 's/^#//' $ASUSCTL_THEME)
|
||||
fi
|
||||
22
themes/engine/scripts/nomarchy-theme-set-keyboard-f16
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
FRAMEWORK16_THEME=~/.config/nomarchy/current/theme/keyboard.rgb
|
||||
|
||||
if nomarchy-cmd-present qmk_hid && [[ -f $FRAMEWORK16_THEME ]]; then
|
||||
hex=$(cat "$FRAMEWORK16_THEME")
|
||||
hex="${hex#\#}"
|
||||
|
||||
# Convert hex to QMK HSV (0-255 scale) using Python's colorsys
|
||||
read -r h s <<< $(python3 -c "
|
||||
import colorsys
|
||||
r, g, b = int('$hex'[:2],16)/255, int('$hex'[2:4],16)/255, int('$hex'[4:6],16)/255
|
||||
h, s, v = colorsys.rgb_to_hsv(r, g, b)
|
||||
print(int(h * 255), int(s * 255))
|
||||
")
|
||||
|
||||
qmk_hid via --rgb-effect 1 2>/dev/null
|
||||
qmk_hid via --rgb-hue "$h" 2>/dev/null
|
||||
qmk_hid via --rgb-saturation "$s" 2>/dev/null
|
||||
qmk_hid via --rgb-brightness 100 2>/dev/null
|
||||
qmk_hid via --save 2>/dev/null
|
||||
fi
|
||||
27
themes/engine/scripts/nomarchy-theme-set-obsidian
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Sync Nomarchy theme to all Obsidian vaults
|
||||
|
||||
CURRENT_THEME_DIR="$HOME/.config/nomarchy/current/theme"
|
||||
|
||||
[[ -f $CURRENT_THEME_DIR/obsidian.css ]] || exit 0
|
||||
|
||||
jq -r '.vaults | values[].path' ~/.config/obsidian/obsidian.json 2>/dev/null | while read -r vault_path; do
|
||||
[[ -d $vault_path/.obsidian ]] || continue
|
||||
|
||||
theme_dir="$vault_path/.obsidian/themes/Nomarchy"
|
||||
mkdir -p "$theme_dir"
|
||||
|
||||
[[ -f $theme_dir/manifest.json ]] || cat >"$theme_dir/manifest.json" <<'EOF'
|
||||
{
|
||||
"name": "Nomarchy",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.16.0",
|
||||
"description": "Automatically syncs with your current Nomarchy system theme colors and fonts",
|
||||
"author": "Nomarchy",
|
||||
"authorUrl": "https://nomarchy.org"
|
||||
}
|
||||
EOF
|
||||
|
||||
cp "$CURRENT_THEME_DIR/obsidian.css" "$theme_dir/theme.css"
|
||||
done
|
||||
46
themes/engine/scripts/nomarchy-theme-set-templates
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
|
||||
TEMPLATES_DIR="$NOMARCHY_PATH/assets/themed"
|
||||
USER_TEMPLATES_DIR="$HOME/.config/nomarchy/themed"
|
||||
NEXT_THEME_DIR="$HOME/.config/nomarchy/current/theme"
|
||||
COLORS_FILE="$NEXT_THEME_DIR/colors.toml"
|
||||
|
||||
# Convert hex color to decimal RGB (e.g., "#1e1e2e" -> "30,30,46")
|
||||
hex_to_rgb() {
|
||||
local hex="${1#\#}"
|
||||
printf "%d,%d,%d" "0x${hex:0:2}" "0x${hex:2:2}" "0x${hex:4:2}"
|
||||
}
|
||||
|
||||
# Only generate dynamic templates for themes with a colors.toml definition
|
||||
if [[ -f $COLORS_FILE ]]; then
|
||||
sed_script=$(mktemp)
|
||||
|
||||
while IFS='=' read -r key value; do
|
||||
key="${key//[\"\' ]/}" # strip quotes and spaces from key
|
||||
[[ $key && $key != \#* ]] || continue # skip empty lines and comments
|
||||
value="${value#*[\"\']}"
|
||||
value="${value%%[\"\']*}" # extract value between quotes (ignores inline comments)
|
||||
|
||||
printf 's|{{ %s }}|%s|g\n' "$key" "$value" # {{ key }} -> value
|
||||
printf 's|{{ %s_strip }}|%s|g\n' "$key" "${value#\#}" # {{ key_strip }} -> value without leading #
|
||||
if [[ $value =~ ^# ]]; then
|
||||
rgb=$(hex_to_rgb "$value")
|
||||
echo "s|{{ ${key}_rgb }}|${rgb}|g"
|
||||
fi
|
||||
done <"$COLORS_FILE" >"$sed_script"
|
||||
|
||||
shopt -s nullglob
|
||||
|
||||
# Process user templates first, then built-in templates (user overrides built-in)
|
||||
for tpl in "$USER_TEMPLATES_DIR"/*.tpl "$TEMPLATES_DIR"/*.tpl; do
|
||||
filename=$(basename "$tpl" .tpl)
|
||||
output_path="$NEXT_THEME_DIR/$filename"
|
||||
|
||||
# Don't overwrite configs already exists in the output directory (copied from theme specific folder)
|
||||
if [[ ! -f $output_path ]]; then
|
||||
sed -f "$sed_script" "$tpl" >"$output_path"
|
||||
fi
|
||||
done
|
||||
|
||||
rm "$sed_script"
|
||||
fi
|
||||
20
themes/engine/scripts/nomarchy-theme-set-vscode
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Nomarchy VS Code Theme Setter
|
||||
# This script only updates the global state.json.
|
||||
# Home Manager (modules/home/vscode.nix) handles the declarative settings injection.
|
||||
|
||||
STATE_DIR="$HOME/.config/nomarchy"
|
||||
STATE_FILE="$STATE_DIR/state.json"
|
||||
|
||||
mkdir -p "$STATE_DIR"
|
||||
[[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE"
|
||||
|
||||
# Theme is already set in state.json by nomarchy-theme-set.
|
||||
# This script is now mostly a placeholder to maintain the same workflow,
|
||||
# triggering an env-update if needed to apply the declarative changes.
|
||||
|
||||
if [[ $NOMARCHY_TOGGLE_SKIP_VSCODE_THEME != "true" ]]; then
|
||||
# We trigger env-update to apply the new VSCode theme declaratively.
|
||||
env-update
|
||||
fi
|
||||
8
themes/engine/scripts/nomarchy-theme-update
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
for dir in ~/.config/nomarchy/themes/*/; do
|
||||
if [[ -d $dir ]] && [[ ! -L ${dir%/} ]] && [[ -d $dir/.git ]]; then
|
||||
echo "Updating: $(basename "$dir")"
|
||||
git -C "$dir" pull
|
||||
fi
|
||||
done
|
||||
26
themes/engine/scripts/nomarchy-toggle-nightlight
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Toggles the nightlight (hyprsunset).
|
||||
# Hybrid: updates state.json and provides instant feedback.
|
||||
|
||||
STATE_DIR="$HOME/.config/nomarchy"
|
||||
STATE_FILE="$STATE_DIR/state.json"
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
# Initialize if doesn't exist
|
||||
[[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE"
|
||||
|
||||
if [[ $NOMARCHY_TOGGLE_NIGHTLIGHT == "false" ]]; then
|
||||
NEW_VALUE="true"
|
||||
hyprctl dispatch exec hyprsunset --temperature 4000
|
||||
notify-send -u low " Nightlight enabled"
|
||||
else
|
||||
NEW_VALUE="false"
|
||||
pkill hyprsunset
|
||||
notify-send -u low " Nightlight disabled"
|
||||
fi
|
||||
|
||||
TMP_JSON=$(mktemp)
|
||||
jq --argjson val "$NEW_VALUE" '.nightlight = $val' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
|
||||
|
||||
echo "Nightlight state set to $NEW_VALUE. Environment will be fully updated on next rebuild."
|
||||
43
themes/engine/sddm.nix
Normal file
@@ -0,0 +1,43 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
nomarchy-sddm-theme = pkgs.stdenv.mkDerivation {
|
||||
pname = "nomarchy-sddm-theme";
|
||||
version = "1.0";
|
||||
src = ./sddm/nomarchy;
|
||||
nativeBuildInputs = [ pkgs.libsForQt5.qt5.wrapQtAppsHook ];
|
||||
installPhase = ''
|
||||
mkdir -p $out/share/sddm/themes/nomarchy
|
||||
cp -r * $out/share/sddm/themes/nomarchy/
|
||||
'';
|
||||
propagatedBuildInputs = with pkgs.libsForQt5.qt5; [
|
||||
qtgraphicaleffects
|
||||
qtquickcontrols2
|
||||
qtsvg
|
||||
];
|
||||
};
|
||||
in
|
||||
{
|
||||
services.xserver.enable = lib.mkDefault true;
|
||||
services.displayManager.sddm = {
|
||||
enable = lib.mkDefault true;
|
||||
wayland.enable = lib.mkDefault true;
|
||||
theme = lib.mkDefault "nomarchy";
|
||||
};
|
||||
|
||||
services.displayManager.defaultSession = lib.mkDefault "hyprland-uwsm";
|
||||
|
||||
services.displayManager.autoLogin = {
|
||||
enable = lib.mkDefault true;
|
||||
user = lib.mkDefault "nomarchy";
|
||||
};
|
||||
|
||||
environment.systemPackages = lib.mkDefault [ nomarchy-sddm-theme ];
|
||||
|
||||
# Enable Hyprland system-level dependencies
|
||||
programs.hyprland = {
|
||||
enable = lib.mkDefault true;
|
||||
withUWSM = lib.mkDefault true;
|
||||
};
|
||||
programs.uwsm.enable = lib.mkDefault true;
|
||||
}
|
||||
99
themes/engine/sddm/nomarchy/Main.qml
Normal file
@@ -0,0 +1,99 @@
|
||||
import QtQuick 2.0
|
||||
import SddmComponents 2.0
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
width: 640
|
||||
height: 480
|
||||
color: "#000000"
|
||||
|
||||
property string currentUser: userModel.lastUser
|
||||
property int sessionIndex: {
|
||||
for (var i = 0; i < sessionModel.rowCount(); i++) {
|
||||
var name = (sessionModel.data(sessionModel.index(i, 0), Qt.DisplayRole) || "").toString()
|
||||
if (name.toLowerCase().indexOf("uwsm") !== -1)
|
||||
return i
|
||||
}
|
||||
return Math.max(0, sessionModel.lastIndex)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: sddm
|
||||
function onLoginFailed() {
|
||||
errorMessage.text = "Login failed"
|
||||
password.text = ""
|
||||
password.focus = true
|
||||
}
|
||||
function onLoginSucceeded() {
|
||||
errorMessage.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: root.height * 0.04
|
||||
width: parent.width
|
||||
|
||||
Image {
|
||||
source: "logo.svg"
|
||||
width: root.width * 0.35
|
||||
height: Math.round(width * sourceSize.height / sourceSize.width)
|
||||
fillMode: Image.PreserveAspectFit
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: root.width * 0.007
|
||||
|
||||
Text {
|
||||
text: "\uf023"
|
||||
color: "#ffffff"
|
||||
font.family: "JetBrainsMono Nerd Font"
|
||||
font.pixelSize: root.height * 0.025
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: root.width * 0.17
|
||||
height: root.height * 0.04
|
||||
color: "#000000"
|
||||
border.color: "#ffffff"
|
||||
border.width: 1
|
||||
clip: true
|
||||
|
||||
TextInput {
|
||||
id: password
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.height * 0.008
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
echoMode: TextInput.Password
|
||||
font.family: "JetBrainsMono Nerd Font"
|
||||
font.pixelSize: root.height * 0.02
|
||||
font.letterSpacing: root.height * 0.004
|
||||
passwordCharacter: "\u2022"
|
||||
color: "#ffffff"
|
||||
focus: true
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
sddm.login(root.currentUser, password.text, root.sessionIndex)
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: errorMessage
|
||||
text: ""
|
||||
color: "#f7768e"
|
||||
font.family: "JetBrainsMono Nerd Font"
|
||||
font.pixelSize: root.height * 0.018
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: password.forceActiveFocus()
|
||||
}
|
||||
64
themes/engine/sddm/nomarchy/logo.svg
Normal file
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="157.40488mm"
|
||||
height="162.48518mm"
|
||||
viewBox="0 0 157.40488 162.48518"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||
sodipodi:docname="icon.svg"
|
||||
inkscape:export-filename="logo.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="0.82803284"
|
||||
inkscape:cx="216.175"
|
||||
inkscape:cy="452.27675"
|
||||
inkscape:window-width="1914"
|
||||
inkscape:window-height="1012"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="layer1"><inkscape:page
|
||||
x="0"
|
||||
y="-1.1741086e-21"
|
||||
width="157.40488"
|
||||
height="162.48518"
|
||||
id="page2"
|
||||
margin="0"
|
||||
bleed="0" /></sodipodi:namedview><defs
|
||||
id="defs1" /><g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-28.332214,-25.599269)"><g
|
||||
id="g4"
|
||||
transform="translate(3.1953242,-22.686801)"
|
||||
style="fill:#000088;fill-opacity:1"><path
|
||||
style="fill:#000088;fill-opacity:1;stroke-width:0.264583"
|
||||
d="M 25.136891,85.823024 25.557592,210.77125 88.452409,174.38061 67.417351,160.49747 57.110174,166.38729 V 105.80633 Z"
|
||||
id="path1" /><path
|
||||
style="fill:#000088;fill-opacity:1;stroke-width:0.264583"
|
||||
d="M 67.728991,112.41131 182.54178,185.60757 153.16137,202.85259 67.830432,148.17947 Z"
|
||||
id="path2" /><path
|
||||
style="fill:#000088;fill-opacity:1;stroke-width:0.264583"
|
||||
d="M 139.74857,145.88014 140.00405,110.4959 54.800856,56.333749 25.675926,73.32329 Z"
|
||||
id="path3" /><path
|
||||
style="fill:#000088;fill-opacity:1;stroke-width:0.264583"
|
||||
d="M 182.2863,172.70573 V 48.286069 l -62.59305,36.406166 20.82177,13.668277 10.21927,-5.74834 0.12774,60.165978 z"
|
||||
id="path4" /></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
7
themes/engine/sddm/nomarchy/metadata.desktop
Normal file
@@ -0,0 +1,7 @@
|
||||
[SddmGreeterTheme]
|
||||
Name=Nomarchy
|
||||
Description=Minimal terminal-style login theme matching the Limine bootloader aesthetic
|
||||
Author=Nomarchy
|
||||
Type=sddm-theme
|
||||
Interface=Qt5
|
||||
Version=1.0
|
||||
1
themes/engine/sddm/nomarchy/theme.conf
Normal file
@@ -0,0 +1 @@
|
||||
[General]
|
||||
35
themes/engine/stylix-compat.nix
Normal file
@@ -0,0 +1,35 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
# Compatibility shims for stylix target modules
|
||||
# Stylix unconditionally imports all target modules, which expect certain
|
||||
# program options to exist. This module defines stub options for programs
|
||||
# we don't use to prevent evaluation errors.
|
||||
|
||||
{
|
||||
options = {
|
||||
# Neovim: stylix uses initLua but home-manager uses extraLuaConfig
|
||||
programs.neovim.initLua = lib.mkOption {
|
||||
type = lib.types.lines;
|
||||
default = "";
|
||||
description = "Lua code to run at init (compatibility shim for stylix)";
|
||||
};
|
||||
|
||||
# OpenCode: stylix expects programs.opencode options
|
||||
programs.opencode = {
|
||||
tui = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.anything;
|
||||
default = {};
|
||||
description = "OpenCode TUI settings (stylix compatibility shim)";
|
||||
};
|
||||
themes = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.anything;
|
||||
default = {};
|
||||
description = "OpenCode themes (stylix compatibility shim)";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.programs.neovim.initLua != "") {
|
||||
programs.neovim.extraLuaConfig = config.programs.neovim.initLua;
|
||||
};
|
||||
}
|
||||
101
themes/engine/stylix.nix
Normal file
@@ -0,0 +1,101 @@
|
||||
{ config, pkgs, inputs, lib, ... }:
|
||||
|
||||
# Stylix Integration Module
|
||||
#
|
||||
# This module handles base-level theming through Stylix:
|
||||
# - Color scheme injection from the active theme's palette
|
||||
# - Cursor configuration
|
||||
# - Font configuration
|
||||
# - GTK/GNOME theming
|
||||
#
|
||||
# App-specific theming is handled separately:
|
||||
# - theme-loader.nix: Deploys theme's apps/ configs (btop, neovim, etc.)
|
||||
# - waybar.nix: Generates waybar CSS from colorScheme
|
||||
# - hyprland.nix: Handles hyprland border colors
|
||||
#
|
||||
# Stylix targets disabled here (we have custom implementations):
|
||||
# - hyprland: Custom border/rule config
|
||||
# - waybar: Custom CSS with theme colors
|
||||
|
||||
let
|
||||
nomarchyLib = import ../lib { inherit lib; };
|
||||
assetsPath = ../palettes;
|
||||
|
||||
activeThemeName = config.nomarchy.theme;
|
||||
|
||||
# Use shared wallpaper resolver
|
||||
activeWallpaper = nomarchyLib.resolveWallpaper {
|
||||
wallpaperPath = config.nomarchy.wallpaper;
|
||||
themeName = activeThemeName;
|
||||
inherit assetsPath;
|
||||
};
|
||||
|
||||
# Get palette using shared library
|
||||
currentPalette = nomarchyLib.getPalette activeThemeName;
|
||||
in
|
||||
{
|
||||
imports = [ inputs.stylix.homeModules.stylix ];
|
||||
|
||||
stylix = {
|
||||
enable = lib.mkDefault true;
|
||||
enableReleaseChecks = lib.mkDefault false;
|
||||
autoEnable = lib.mkDefault false; # Disable auto-detection, explicitly enable targets
|
||||
image = lib.mkDefault activeWallpaper;
|
||||
base16Scheme = lib.mkDefault currentPalette;
|
||||
|
||||
# Use detected light mode state
|
||||
polarity = lib.mkDefault (if config.nomarchy.isLightMode then "light" else "dark");
|
||||
|
||||
cursor = lib.mkDefault {
|
||||
package = config.nomarchy.cursor.package;
|
||||
name = config.nomarchy.cursor.name;
|
||||
size = 24;
|
||||
};
|
||||
|
||||
fonts = lib.mkDefault {
|
||||
monospace = {
|
||||
package = pkgs.nerd-fonts.jetbrains-mono;
|
||||
name = config.nomarchy.fonts.monospace;
|
||||
};
|
||||
sansSerif = {
|
||||
package = pkgs.dejavu_fonts;
|
||||
name = "DejaVu Sans";
|
||||
};
|
||||
serif = {
|
||||
package = pkgs.dejavu_fonts;
|
||||
name = "DejaVu Serif";
|
||||
};
|
||||
emoji = {
|
||||
package = pkgs.noto-fonts-color-emoji;
|
||||
name = "Noto Color Emoji";
|
||||
};
|
||||
sizes = {
|
||||
applications = 11;
|
||||
terminal = 11;
|
||||
desktop = 11;
|
||||
popups = 11;
|
||||
};
|
||||
};
|
||||
|
||||
# Enable theming for specific targets
|
||||
targets = lib.mkDefault {
|
||||
hyprland.enable = false; # We keep our custom hyprland config for borders/rules
|
||||
waybar.enable = false; # We keep our custom waybar CSS
|
||||
neovim.enable = false; # We deploy theme lua files via theme-loader instead
|
||||
neovide.enable = false; # Neovide depends on neovim program module
|
||||
alacritty.enable = true;
|
||||
kitty.enable = true;
|
||||
gtk.enable = true;
|
||||
gnome.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
# GTK Icon Theme configuration
|
||||
gtk = {
|
||||
enable = lib.mkDefault true;
|
||||
iconTheme = lib.mkDefault {
|
||||
package = pkgs.yaru-theme;
|
||||
name = config.nomarchy.iconsTheme;
|
||||
};
|
||||
};
|
||||
}
|
||||
64
themes/engine/switcher.nix
Normal file
@@ -0,0 +1,64 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
nomarchyLib = import ../lib { inherit lib; };
|
||||
themeList = builtins.concatStringsSep "\\n" nomarchyLib.themeNames;
|
||||
|
||||
nomarchy-theme-selector = pkgs.writeShellScriptBin "nomarchy-theme-selector" ''
|
||||
SELECTED_THEME=$(echo -e "${themeList}" | walker --dmenu)
|
||||
|
||||
if [ -n "$SELECTED_THEME" ]; then
|
||||
nomarchy-theme-set "$SELECTED_THEME"
|
||||
fi
|
||||
'';
|
||||
|
||||
nomarchy-font-selector = pkgs.writeShellScriptBin "nomarchy-font-selector" ''
|
||||
STATE_DIR="${config.home.homeDirectory}/.config/nomarchy"
|
||||
STATE_FILE="$STATE_DIR/state.json"
|
||||
|
||||
mkdir -p "$STATE_DIR"
|
||||
[[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE"
|
||||
|
||||
# Simple list of common nerd fonts, could be expanded
|
||||
FONTS="JetBrainsMono Nerd Font\nRobotoMono Nerd Font\nFiraCode Nerd Font\nUbuntuMono Nerd Font"
|
||||
SELECTED_FONT=$(echo -e "$FONTS" | walker --dmenu)
|
||||
|
||||
if [ -n "$SELECTED_FONT" ]; then
|
||||
TMP_JSON=$(mktemp)
|
||||
jq --arg font "$SELECTED_FONT" '.font = $font' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
|
||||
env-update
|
||||
fi
|
||||
'';
|
||||
|
||||
nomarchy-wallpaper-selector = pkgs.writeShellScriptBin "nomarchy-wallpaper-selector" ''
|
||||
STATE_DIR="${config.home.homeDirectory}/.config/nomarchy"
|
||||
STATE_FILE="$STATE_DIR/state.json"
|
||||
THEMES_DIR="${config.xdg.dataHome}/nomarchy/themes"
|
||||
|
||||
mkdir -p "$STATE_DIR"
|
||||
[[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE"
|
||||
|
||||
# List all images in all themes backgrounds
|
||||
# We search in the system-wide flake source for distro wallpapers to avoid Nix Store bloat
|
||||
WALLPAPERS=$(find "${config.xdg.dataHome}/nomarchy/themes" -type f \( -name "*.jpg" -o -name "*.png" \) 2>/dev/null)
|
||||
DISTRO_THEMES="/etc/nixos/nomarchy/assets/themes"
|
||||
if [ -d "$DISTRO_THEMES" ]; then
|
||||
WALLPAPERS="$WALLPAPERS\n$(find "$DISTRO_THEMES" -type f \( -name "*.jpg" -o -name "*.png" \))"
|
||||
fi
|
||||
# Include user themes if they exist
|
||||
if [ -d "${config.home.homeDirectory}/.config/nomarchy/themes" ]; then
|
||||
WALLPAPERS="$WALLPAPERS\n$(find "${config.home.homeDirectory}/.config/nomarchy/themes" -type f \( -name "*.jpg" -o -name "*.png" \))"
|
||||
fi
|
||||
SELECTED_WP=$(echo -e "$WALLPAPERS" | walker --dmenu)
|
||||
|
||||
if [ -n "$SELECTED_WP" ]; then
|
||||
TMP_JSON=$(mktemp)
|
||||
jq --arg wp "$SELECTED_WP" '.wallpaper = $wp' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
|
||||
swww img "$SELECTED_WP" --transition-type outer --transition-pos 0.85,0.97 --transition-step 90 &
|
||||
env-update
|
||||
fi
|
||||
'';
|
||||
in
|
||||
{
|
||||
home.packages = [ nomarchy-theme-selector nomarchy-font-selector nomarchy-wallpaper-selector ];
|
||||
}
|
||||