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
This commit is contained in:
Bernardo Magri
2026-04-12 14:51:15 +01:00
parent a9ee79a5ce
commit bbdf34ced8
535 changed files with 119 additions and 127 deletions

View File

@@ -0,0 +1,25 @@
general.import = [ "~/.config/nomarchy/current/theme/alacritty.toml" ]
[env]
TERM = "xterm-256color"
[terminal]
osc52 = "CopyPaste"
[font]
normal = { family = "JetBrainsMono Nerd Font", style = "Regular" }
bold = { family = "JetBrainsMono Nerd Font", style = "Bold" }
italic = { family = "JetBrainsMono Nerd Font", style = "Italic" }
size = 9
[window]
padding.x = 14
padding.y = 14
decorations = "None"
[keyboard]
bindings = [
{ key = "Insert", mods = "Shift", action = "Paste" },
{ key = "Insert", mods = "Control", action = "Copy" },
{ key = "Return", mods = "Shift", chars = "\u001B\r" }
]

View File

@@ -0,0 +1,26 @@
{ config, pkgs, lib, ... }:
{
programs.alacritty = {
enable = lib.mkDefault true;
settings = lib.mkDefault {
env = {
TERM = "xterm-256color";
};
terminal = {
osc52 = "CopyPaste";
};
window = {
padding = { x = 14; y = 14; };
decorations = "None";
};
keyboard = {
bindings = [
{ key = "Insert"; mods = "Shift"; action = "Paste"; }
{ key = "Insert"; mods = "Control"; action = "Copy"; }
{ key = "Return"; mods = "Shift"; chars = "\\u001B\\r"; }
];
};
};
};
}

30
features/apps/browser.nix Normal file
View File

@@ -0,0 +1,30 @@
{ config, pkgs, lib, ... }:
let
nomarchyLib = import ../lib { inherit lib; };
activeThemeName = config.nomarchy.system.theme;
currentPalette = nomarchyLib.getPalette activeThemeName;
# Hex color for browser theme (base00 is background)
themeColor = "#${currentPalette.base00}";
# Detect light mode from theme name or palette
isLightTheme = nomarchyLib.isThemeLightMode {
themeName = activeThemeName;
assetsPath = ../../assets/themes;
};
browserPolicy = {
BrowserThemeColor = themeColor;
BrowserColorScheme = if isLightTheme then "light" else "dark";
};
in
{
# Chromium policies
programs.chromium.extraOpts = lib.mkDefault browserPolicy;
# Brave browser policies via managed policy file
environment.etc."brave/policies/managed/nomarchy.json".text = lib.mkDefault (
builtins.toJSON browserPolicy
);
}

View File

@@ -0,0 +1,246 @@
#? Config file for btop
#* Name of a btop++/bpytop/bashtop formatted ".theme" file, "Default" and "TTY" for builtin themes.
#* Themes should be placed in "../share/btop/themes" relative to binary or "$HOME/.config/btop/themes"
color_theme = "current"
#* If the theme set background should be shown, set to False if you want terminal background transparency.
theme_background = True
#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false.
truecolor = True
#* Set to true to force tty mode regardless if a real tty has been detected or not.
#* Will force 16-color mode and TTY theme, set all graph symbols to "tty" and swap out other non tty friendly symbols.
force_tty = False
#* Define presets for the layout of the boxes. Preset 0 is always all boxes shown with default settings. Max 9 presets.
#* Format: "box_name:P:G,box_name:P:G" P=(0 or 1) for alternate positions, G=graph symbol to use for box.
#* Use whitespace " " as separator between different presets.
#* Example: "cpu:0:default,mem:0:tty,proc:1:default cpu:0:braille,proc:0:tty"
presets = "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty"
#* Set to True to enable "h,j,k,l,g,G" keys for directional control in lists.
#* Conflicting keys for h:"help" and k:"kill" is accessible while holding shift.
vim_keys = True
#* Rounded corners on boxes, is ignored if TTY mode is ON.
rounded_corners = True
#* Default symbols to use for graph creation, "braille", "block" or "tty".
#* "braille" offers the highest resolution but might not be included in all fonts.
#* "block" has half the resolution of braille but uses more common characters.
#* "tty" uses only 3 different symbols but will work with most fonts and should work in a real TTY.
#* Note that "tty" only has half the horizontal resolution of the other two, so will show a shorter historical view.
graph_symbol = "braille"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_cpu = "default"
# Graph symbol to use for graphs in gpu box, "default", "braille", "block" or "tty".
graph_symbol_gpu = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_mem = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_net = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_proc = "default"
#* Manually set which boxes to show. Available values are "cpu mem net proc" and "gpu0" through "gpu5", separate values with whitespace.
shown_boxes = "cpu mem net proc"
#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs.
update_ms = 2000
#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu direct",
#* "cpu lazy" sorts top process over time (easier to follow), "cpu direct" updates top process directly.
proc_sorting = "cpu lazy"
#* Reverse sorting order, True or False.
proc_reversed = False
#* Show processes as a tree.
proc_tree = False
#* Use the cpu graph colors in the process list.
proc_colors = True
#* Use a darkening gradient in the process list.
proc_gradient = True
#* If process cpu usage should be of the core it's running on or usage of the total available cpu power.
proc_per_core = False
#* Show process memory as bytes instead of percent.
proc_mem_bytes = True
#* Show cpu graph for each process.
proc_cpu_graphs = True
#* Use /proc/[pid]/smaps for memory information in the process info box (very slow but more accurate)
proc_info_smaps = False
#* Show proc box on left side of screen instead of right.
proc_left = False
#* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop).
proc_filter_kernel = False
#* In tree-view, always accumulate child process resources in the parent process.
proc_aggregate = False
#* Sets the CPU stat shown in upper half of the CPU graph, "total" is always available.
#* Select from a list of detected attributes from the options menu.
cpu_graph_upper = "Auto"
#* Sets the CPU stat shown in lower half of the CPU graph, "total" is always available.
#* Select from a list of detected attributes from the options menu.
cpu_graph_lower = "Auto"
#* If gpu info should be shown in the cpu box. Available values = "Auto", "On" and "Off".
show_gpu_info = "Auto"
#* Toggles if the lower CPU graph should be inverted.
cpu_invert_lower = True
#* Set to True to completely disable the lower CPU graph.
cpu_single_graph = False
#* Show cpu box at bottom of screen instead of top.
cpu_bottom = False
#* Shows the system uptime in the CPU box.
show_uptime = True
#* Show cpu temperature.
check_temp = True
#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors.
cpu_sensor = "Auto"
#* Show temperatures for cpu cores also if check_temp is True and sensors has been found.
show_coretemp = True
#* Set a custom mapping between core and coretemp, can be needed on certain cpus to get correct temperature for correct core.
#* Use lm-sensors or similar to see which cores are reporting temperatures on your machine.
#* Format "x:y" x=core with wrong temp, y=core with correct temp, use space as separator between multiple entries.
#* Example: "4:0 5:1 6:3"
cpu_core_map = ""
#* Which temperature scale to use, available values: "celsius", "fahrenheit", "kelvin" and "rankine".
temp_scale = "celsius"
#* Use base 10 for bits/bytes sizes, KB = 1000 instead of KiB = 1024.
base_10_sizes = False
#* Show CPU frequency.
show_cpu_freq = True
#* Draw a clock at top of screen, formatting according to strftime, empty string to disable.
#* Special formatting: /host = hostname | /user = username | /uptime = system uptime
clock_format = "%X"
#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort.
background_update = True
#* Custom cpu model name, empty string to disable.
custom_cpu_name = ""
#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with whitespace " ".
#* Begin line with "exclude=" to change to exclude filter, otherwise defaults to "most include" filter. Example: disks_filter="exclude=/boot /home/user".
disks_filter = ""
#* Show graphs instead of meters for memory values.
mem_graphs = True
#* Show mem box below net box instead of above.
mem_below_net = False
#* Count ZFS ARC in cached and available memory.
zfs_arc_cached = True
#* If swap memory should be shown in memory box.
show_swap = True
#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk.
swap_disk = True
#* If mem box should be split to also show disks info.
show_disks = True
#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar.
only_physical = True
#* Read disks list from /etc/fstab. This also disables only_physical.
use_fstab = True
#* Setting this to True will hide all datasets, and only show ZFS pools. (IO stats will be calculated per-pool)
zfs_hide_datasets = False
#* Set to true to show available disk space for privileged users.
disk_free_priv = False
#* Toggles if io activity % (disk busy time) should be shown in regular disk usage view.
show_io_stat = True
#* Toggles io mode for disks, showing big graphs for disk read/write speeds.
io_mode = False
#* Set to True to show combined read/write io graphs in io mode.
io_graph_combined = False
#* Set the top speed for the io graphs in MiB/s (100 by default), use format "mountpoint:speed" separate disks with whitespace " ".
#* Example: "/mnt/media:100 /:20 /boot:1".
io_graph_speeds = ""
#* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False.
net_download = 100
net_upload = 100
#* Use network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest.
net_auto = True
#* Sync the auto scaling for download and upload to whichever currently has the highest scale.
net_sync = True
#* Starts with the Network Interface specified here.
net_iface = ""
#* Show battery stats in top right if battery is present.
show_battery = True
#* Which battery to use if multiple are present. "Auto" for auto detection.
selected_battery = "Auto"
#* Set loglevel for "~/.config/btop/btop.log" levels are: "ERROR" "WARNING" "INFO" "DEBUG".
#* The level set includes all lower levels, i.e. "DEBUG" will show all logging info.
log_level = "WARNING"
#* Measure PCIe throughput on NVIDIA cards, may impact performance on certain cards.
nvml_measure_pcie_speeds = True
#* Horizontally mirror the GPU graph.
gpu_mirror_graph = True
#* Custom gpu0 model name, empty string to disable.
custom_gpu_name0 = ""
#* Custom gpu1 model name, empty string to disable.
custom_gpu_name1 = ""
#* Custom gpu2 model name, empty string to disable.
custom_gpu_name2 = ""
#* Custom gpu3 model name, empty string to disable.
custom_gpu_name3 = ""
#* Custom gpu4 model name, empty string to disable.
custom_gpu_name4 = ""
#* Custom gpu5 model name, empty string to disable.
custom_gpu_name5 = ""

View File

@@ -0,0 +1,15 @@
{
"extensions": {
"theme": {
"id": "",
"use_system": false,
"use_custom": false
}
},
"browser": {
"theme": {
"color_scheme": 2,
"user_color": 2
}
}
}

View File

@@ -0,0 +1 @@
async = false

View File

@@ -0,0 +1,3 @@
show_actions = false
only_search_title = true
history = false

View File

@@ -0,0 +1 @@
command = 'wl-copy && hyprctl dispatch sendshortcut "SHIFT, Insert,"'

View File

@@ -0,0 +1,37 @@
# Dynamic theme colors
config-file = ?"~/.config/nomarchy/current/theme/ghostty.conf"
# Font
font-family = "JetBrainsMono Nerd Font"
font-style = Regular
font-size = 9
# Window
window-theme = ghostty
window-padding-x = 14
window-padding-y = 14
confirm-close-surface=false
resize-overlay = never
gtk-toolbar-style = flat
# Cursor styling
cursor-style = "block"
cursor-style-blink = false
# Cursor styling + SSH session terminfo
# (all shell integration options must be passed together)
shell-integration-features = no-cursor,ssh-env
# Keyboard bindings
keybind = shift+insert=paste_from_clipboard
keybind = control+insert=copy_to_clipboard
keybind = super+control+shift+alt+arrow_down=resize_split:down,100
keybind = super+control+shift+alt+arrow_up=resize_split:up,100
keybind = super+control+shift+alt+arrow_left=resize_split:left,100
keybind = super+control+shift+alt+arrow_right=resize_split:right,100
# Slowdown mouse scrolling
mouse-scroll-multiplier = 0.95
# Fix general slowness on hyprland (https://github.com/ghostty-org/ghostty/discussions/3224)
async-backend = epoll

View File

@@ -0,0 +1,30 @@
include ~/.config/nomarchy/current/theme/kitty.conf
# Font
font_family JetBrainsMono Nerd Font
bold_italic_font auto
font_size 9.0
# Window
window_padding_width 14
hide_window_decorations yes
confirm_os_window_close 0
# Keybindings
map ctrl+insert copy_to_clipboard
map shift+insert paste_from_clipboard
# Allow remote access
allow_remote_control yes
# Aesthetics
cursor_shape block
cursor_blink_interval 0
shell_integration no-cursor
enable_audio_bell no
# Minimal Tab bar styling
tab_bar_edge bottom
tab_bar_style powerline
tab_powerline_style slanted
tab_title_template {title}{' :{}:'.format(num_windows) if num_windows > 1 else ''}

View File

25
features/apps/makima.nix Normal file
View File

@@ -0,0 +1,25 @@
{ config, pkgs, lib, ... }:
let
cfg = config.nomarchy.system.features.makima;
in
{
config = lib.mkIf cfg {
# If the user has makima-bin available in their overlays (as they originally used a custom package),
# this will install it. Otherwise, it will fail evaluation if not available in nixpkgs.
environment.systemPackages = [ pkgs.makima-bin ];
environment.etc."makima/AT Translated Set 2 keyboard.toml".source = ../../assets/makima + "/AT Translated Set 2 keyboard.toml";
systemd.services.makima = {
description = "Makima key remapping service";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
User = config.services.displayManager.autoLogin.user;
Environment = "MAKIMA_CONFIG=/etc/makima";
ExecStart = "${pkgs.makima-bin}/bin/makima";
Restart = "on-failure";
};
};
};
}

View File

@@ -0,0 +1,7 @@
# Run nomarchy-restart-makima after any changes
[remap]
KEY_LEFTMETA-KEY_LEFTSHIFT-KEY_F23 = ["KEY_LEFTMETA", "KEY_LEFTALT", "KEY_SPACE"]
[settings]
GRAB_DEVICE = "true"

View File

@@ -0,0 +1,5 @@
{
"$schema": "https://opencode.ai/config.json",
"theme": "system",
"autoupdate": false
}

View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Launch the fastfetch TUI that gives information about the current system.
exec nomarchy-launch-or-focus-tui "bash -c 'fastfetch; read -n 1 -s'"

View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Launch the Nomarchy audio controls TUI (provided by wiremix).
nomarchy-launch-or-focus-tui wiremix

View File

@@ -0,0 +1,7 @@
#!/bin/bash
# Launch the Nomarchy bluetooth controls TUI (provided by bluetui).
# Also attempts to unblock bluetooth service if rfkill had blocked it.
rfkill unblock bluetooth
exec nomarchy-launch-or-focus-tui bluetui

View File

@@ -0,0 +1,17 @@
#!/bin/bash
# Launch the default browser as determined by xdg-settings.
# Automatically converts --private into the correct flag for the given browser.
default_browser=$(xdg-settings get default-web-browser)
browser_exec=$(sed -n 's/^Exec=\([^ ]*\).*/\1/p' {~/.local,~/.nix-profile,/usr}/share/applications/$default_browser 2>/dev/null | head -1)
if $browser_exec --help | grep -q MOZ_LOG; then
private_flag="--private-window"
elif [[ $browser_exec =~ edge ]]; then
private_flag="--inprivate"
else
private_flag="--incognito"
fi
exec setsid uwsm-app -- "$browser_exec" "${@/--private/$private_flag}"

View File

@@ -0,0 +1,15 @@
#!/bin/bash
# Launch the default editor as determined by $EDITOR (set via ~/.config/uwsm/default) (or nvim if missing).
# Starts suitable editors in a terminal window and otherwise as a regular application.
nomarchy-cmd-present "$EDITOR" || EDITOR=nvim
case "$EDITOR" in
nvim | vim | nano | micro | hx | helix | fresh)
exec nomarchy-launch-tui "$EDITOR" "$@"
;;
*)
exec setsid uwsm-app -- "$EDITOR" "$@"
;;
esac

View File

@@ -0,0 +1,7 @@
#!/bin/bash
# Launch a floating terminal with the Nomarchy logo presentation, then execute the command passed in, and finally end with the nomarchy-show-done presentation.
# Used by actions such as Update System.
cmd="$*"
exec setsid uwsm-app -- xdg-terminal-exec --app-id=org.nomarchy.terminal --title=Nomarchy -e bash -c "nomarchy-show-logo; $cmd; if (( \$? != 130 )); then nomarchy-show-done; fi"

View File

@@ -0,0 +1,19 @@
#!/bin/bash
# Launch or focus on a given command identified by the passed in window-pattern.
# Use by some default bindings, like the one for Spotify, to ensure there is only one instance of the application open.
if (($# == 0)); then
echo "Usage: nomarchy-launch-or-focus [window-pattern] [launch-command]"
exit 1
fi
WINDOW_PATTERN="$1"
LAUNCH_COMMAND="${2:-"uwsm-app -- $WINDOW_PATTERN"}"
WINDOW_ADDRESS=$(hyprctl clients -j | jq -r --arg p "$WINDOW_PATTERN" '.[]|select((.class|test("\\b" + $p + "\\b";"i")) or (.title|test("\\b" + $p + "\\b";"i")))|.address' | head -n1)
if [[ -n $WINDOW_ADDRESS ]]; then
hyprctl dispatch focuswindow "address:$WINDOW_ADDRESS"
else
eval exec setsid $LAUNCH_COMMAND
fi

View File

@@ -0,0 +1,9 @@
#!/bin/bash
# Launch or focus on a given TUI identified by the passed in as the command.
# Use by commands like nomarchy-launch-wifi to ensure there is only one wifi configuration screen open.
APP_ID="org.nomarchy.$(basename "$1")"
LAUNCH_COMMAND="nomarchy-launch-tui $@"
exec nomarchy-launch-or-focus "$APP_ID" "$LAUNCH_COMMAND"

View File

@@ -0,0 +1,15 @@
#!/bin/bash
# Launch or focus on a given web app identified by the window-pattern.
# Use by some default bindings, like the one for WhatsApp, to ensure there is only one instance of the application open.
if (($# == 0)); then
echo "Usage: nomarchy-launch-or-focus-webapp [window-pattern] [url-and-flags...]"
exit 1
fi
WINDOW_PATTERN="$1"
shift
LAUNCH_COMMAND="nomarchy-launch-webapp $@"
exec nomarchy-launch-or-focus "$WINDOW_PATTERN" "$LAUNCH_COMMAND"

View File

@@ -0,0 +1,56 @@
#!/bin/bash
# Launch the Nomarchy screensaver in the default terminal on the system with the correct font configuration.
# Exit early if we don't have the tte show
if ! command -v tte &>/dev/null; then
exit 1
fi
# Exit early if screensave is already running
pgrep -f org.nomarchy.screensaver && exit 0
# Allow screensaver to be turned off but also force started
# Skip if screensaver is disabled in configuration
if [[ $NNOMARCHY_TOGGLE_SCREENSAVER == "false" ]] && [[ $1 != "force" ]]; then
exit 0
fi
# Silently quit Walker on overlay
walker -q
focused=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true).name')
terminal=$(xdg-terminal-exec --print-id)
for m in $(hyprctl monitors -j | jq -r '.[] | .name'); do
hyprctl dispatch focusmonitor $m
case $terminal in
*Alacritty*)
hyprctl dispatch exec -- \
alacritty --class=org.nomarchy.screensaver \
--config-file ~/.local/share/nomarchy/default/alacritty/screensaver.toml \
-e nomarchy-cmd-screensaver
;;
*ghostty*)
hyprctl dispatch exec -- \
ghostty --class=org.nomarchy.screensaver \
--config-file=~/.local/share/nomarchy/default/ghostty/screensaver \
--font-size=18 \
-e nomarchy-cmd-screensaver
;;
*kitty*)
hyprctl dispatch exec -- \
kitty --class=org.nomarchy.screensaver \
--override font_size=18 \
--override window_padding_width=0 \
-e nomarchy-cmd-screensaver
;;
*)
notify-send -u low "✋ Screensaver only runs in Alacritty, Ghostty, or Kitty"
;;
esac
done
hyprctl dispatch focusmonitor $focused

View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Launch the TUI command passed in as an argument in the default terminal with an org.nomarchy.COMMAND app id for styling.
exec setsid uwsm-app -- xdg-terminal-exec --app-id=org.nomarchy.$(basename $1) -e "$1" "${@:2}"

View File

@@ -0,0 +1,15 @@
#!/bin/bash
# Launch the Walker application launcher while ensuring that it's data provider (called elephant) is running first.
# Ensure elephant is running before launching walker
if ! pgrep -x elephant > /dev/null; then
setsid uwsm-app -- elephant &
fi
# Ensure walker service is running
if ! pgrep -f "walker --gapplication-service" > /dev/null; then
setsid uwsm-app -- walker --gapplication-service &
fi
exec walker --width 644 --maxheight 300 --minheight 300 "$@"

View File

@@ -0,0 +1,12 @@
#!/bin/bash
# Launch the passed in URL as a web app in the default browser (or chromium if the default doesn't support --app).
browser=$(xdg-settings get default-web-browser)
case $browser in
google-chrome* | brave-browser* | microsoft-edge* | opera* | vivaldi* | helium*) ;;
*) browser="chromium.desktop" ;;
esac
exec setsid uwsm-app -- $(sed -n 's/^Exec=\([^ ]*\).*/\1/p' {~/.local,~/.nix-profile,/usr}/share/applications/$browser 2>/dev/null | head -1) --app="$1" "${@:2}"

View File

@@ -0,0 +1,7 @@
#!/bin/bash
# Launch the Nomarchy wifi controls (provided by the Impala TUI).
# Attempts to unblock the wifi service first in case it should be been blocked.
rfkill unblock wifi
nomarchy-launch-or-focus-tui impala

View File

@@ -0,0 +1,7 @@
#!/bin/bash
# Restart an application by killing it and relaunching via uwsm.
# Usage: nomarchy-restart-app <application-name> [application-args...]
pkill -x $1
setsid uwsm-app -- "$@" >/dev/null 2>&1 &

View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Reload btop configuration (used by the Nomarchy theme switching).
pkill -SIGUSR2 btop

View File

@@ -0,0 +1,7 @@
#!/bin/bash
# Reload opencode configuration (used by the Nomarchy theme switching).
if pgrep -x opencode >/dev/null; then
killall -SIGUSR2 opencode
fi

View File

@@ -0,0 +1,13 @@
#!/bin/bash
if [[ -f ~/.config/alacritty/alacritty.toml ]]; then
touch ~/.config/alacritty/alacritty.toml
fi
if pgrep -x kitty >/dev/null; then
killall -SIGUSR1 kitty >/dev/null
fi
if pgrep -x ghostty >/dev/null; then
killall -SIGUSR2 ghostty
fi

View File

@@ -0,0 +1,7 @@
#!/bin/bash
# Restart tmux if running with the latest configuration
if pgrep -x tmux; then
tmux source-file ~/.config/tmux/tmux.conf
fi

View File

@@ -0,0 +1,59 @@
#!/bin/bash
set -e
if (( $# != 4 )); then
echo -e "\e[32mLet's create a TUI shortcut you can start with the app launcher.\n\e[0m"
APP_NAME=$(gum input --prompt "Name> " --placeholder "My TUI")
APP_EXEC=$(gum input --prompt "Launch Command> " --placeholder "lazydocker or bash -c 'dust; read -n 1 -s'")
WINDOW_STYLE=$(gum choose --header "Window style" float tile)
ICON_URL=$(gum input --prompt "Icon URL> " --placeholder "See https://dashboardicons.com (must use PNG or SVG!)")
else
APP_NAME="$1"
APP_EXEC="$2"
WINDOW_STYLE="$3"
ICON_URL="$4"
fi
if [[ -z $APP_NAME || -z $APP_EXEC || -z $ICON_URL ]]; then
echo "You must set app name, app command, and icon URL!"
exit 1
fi
ICON_DIR="$HOME/.local/share/applications/icons"
DESKTOP_FILE="$HOME/.local/share/applications/$APP_NAME.desktop"
if [[ ! $ICON_URL =~ ^https?:// ]] && [[ -f $ICON_URL ]]; then
ICON_PATH="$ICON_URL"
else
ICON_PATH="$ICON_DIR/$APP_NAME.png"
mkdir -p "$ICON_DIR"
if ! curl -sL -o "$ICON_PATH" "$ICON_URL"; then
echo "Error: Failed to download icon."
exit 1
fi
fi
if [[ $WINDOW_STYLE == "float" ]]; then
APP_CLASS="TUI.float"
else
APP_CLASS="TUI.tile"
fi
cat >"$DESKTOP_FILE" <<EOF
[Desktop Entry]
Version=1.0
Name=$APP_NAME
Comment=$APP_NAME
Exec=xdg-terminal-exec --app-id=$APP_CLASS -e $APP_EXEC
Terminal=false
Type=Application
Icon=$ICON_PATH
StartupNotify=true
EOF
chmod +x "$DESKTOP_FILE"
if (( $# != 4 )); then
echo -e "You can now find $APP_NAME using the app launcher (SUPER + SPACE)\n"
fi

View File

@@ -0,0 +1,43 @@
#!/bin/bash
set -e
ICON_DIR="$HOME/.local/share/applications/icons"
DESKTOP_DIR="$HOME/.local/share/applications/"
if (( $# == 0 )); then
# Find all TUIs
while IFS= read -r -d '' file; do
if grep -qE '^Exec=.*(\$TERMINAL|xdg-terminal-exec).*-e' "$file"; then
TUIS+=("$(basename "${file%.desktop}")")
fi
done < <(find "$DESKTOP_DIR" -name '*.desktop' -print0)
if ((${#TUIS[@]})); then
IFS=$'\n' SORTED_TUIS=($(sort <<<"${TUIS[*]}"))
unset IFS
APP_NAMES_STRING=$(gum choose --no-limit --header "Select TUI to remove..." --selected-prefix="✗ " "${SORTED_TUIS[@]}")
# Convert newline-separated string to array
APP_NAMES=()
while IFS= read -r line; do
[[ -n $line ]] && APP_NAMES+=("$line")
done <<< "$APP_NAMES_STRING"
else
echo "No TUIs to remove."
exit 1
fi
else
# Use array to preserve spaces in app names
APP_NAMES=("$@")
fi
if (( ${#APP_NAMES[@]} == 0 )); then
echo "You must provide TUI names."
exit 1
fi
for APP_NAME in "${APP_NAMES[@]}"; do
rm -f "$DESKTOP_DIR/$APP_NAME.desktop"
rm -f "$ICON_DIR/$APP_NAME.png"
echo "Removed $APP_NAME"
done

View File

@@ -0,0 +1,36 @@
#!/bin/bash
# Remove all TUIs installed via nomarchy-tui-install.
# Identifies TUIs by their Exec pattern (xdg-terminal-exec --app-id=TUI.).
set -e
APP_DIR="${1:-$HOME/.local/share/applications}"
ICON_DIR="$HOME/.local/share/applications/icons"
echo "Scanning for TUIs in $APP_DIR..."
tui_desktop_files=()
while IFS= read -r -d '' file; do
if grep -q "Exec=xdg-terminal-exec --app-id=TUI\." "$file" 2>/dev/null; then
tui_desktop_files+=("$file")
fi
done < <(find "$APP_DIR" -maxdepth 1 -name "*.desktop" -print0 2>/dev/null)
if (( ${#tui_desktop_files[@]} == 0 )); then
echo "No TUIs found."
exit 0
fi
for file in "${tui_desktop_files[@]}"; do
app_name=$(basename "$file" .desktop)
echo "Removing TUI: $app_name"
rm -f "$file"
rm -f "$ICON_DIR/$app_name.png"
done
if command -v update-desktop-database &>/dev/null; then
update-desktop-database "$APP_DIR" &>/dev/null || true
fi
echo "TUIs removed successfully."

View File

@@ -0,0 +1,6 @@
#!/bin/bash
set -e
# Used by Voxtype waybar module to open config on right click
exec nomarchy-launch-editor ~/.config/voxtype/config.toml

View File

@@ -0,0 +1,21 @@
#!/bin/bash
set -e
# Install voxtype and configure it for use.
if gum confirm "Install Voxtype + AI model (~150MB) to enable dictation?"; then
nomarchy-pkg-add wtype voxtype-bin
# Setup voxtype
mkdir -p ~/.config/voxtype
cp ~/.config/nomarchy/default/voxtype/config.toml ~/.config/voxtype/
voxtype setup --download --no-post-install
if nomarchy-hw-vulkan; then
voxtype setup gpu --enable || true
fi
voxtype setup systemd
nomarchy-restart-waybar
notify-send " Voxtype Dictation Ready" "Press Super + Ctrl + X to toggle dictation.\nEdit ~/.config/voxtype/config.toml for options." -t 10000
fi

View File

@@ -0,0 +1,5 @@
#!/bin/bash
set -e
nomarchy-launch-floating-terminal-with-presentation "voxtype setup model"
nomarchy-restart-waybar

View File

@@ -0,0 +1,20 @@
#!/bin/bash
set -e
# Remove voxtype and its configurations.
if nomarchy-cmd-present voxtype; then
echo "Uninstall Voxtype to remove dictation."
# Remove services
systemctl --user stop voxtype.service 2>/dev/null || true
rm -f ~/.config/systemd/user/voxtype*
systemctl --user daemon-reload
# Remove packages and configs
nomarchy-pkg-drop wtype voxtype-bin
rm -rf ~/.config/voxtype
rm -rf ~/.local/share/voxtype
else
echo "Voxtype was not installed."
fi

View File

@@ -0,0 +1,9 @@
#!/bin/bash
if nomarchy-cmd-present voxtype; then
voxtype status --follow --extended --format json | while read -r line; do
echo "$line" | jq -c '. + {alt: .class}'
done
else
echo '{"alt": "", "tooltip": ""}'
fi

View File

@@ -0,0 +1,11 @@
#!/bin/bash
url="$1"
web_url="https://app.hey.com"
# Handle mailto: URLs
if [[ $url =~ ^mailto: ]]; then
email=$(echo "$url" | sed 's/mailto://')
web_url="https://app.hey.com/messages/new?to=$email"
fi
exec nomarchy-launch-webapp "$web_url"

View File

@@ -0,0 +1,20 @@
#!/bin/bash
url="$1"
web_url="https://app.zoom.us/wc/home"
if [[ $url =~ ^zoom(mtg|us):// ]]; then
confno=$(echo "$url" | sed -n 's/.*[?&]confno=\([^&]*\).*/\1/p')
if [[ -n $confno ]]; then
pwd=$(echo "$url" | sed -n 's/.*[?&]pwd=\([^&]*\).*/\1/p')
if [[ -n $pwd ]]; then
web_url="https://app.zoom.us/wc/join/$confno?pwd=$pwd"
else
web_url="https://app.zoom.us/wc/join/$confno"
fi
fi
fi
exec nomarchy-launch-webapp "$web_url"

View File

@@ -0,0 +1,89 @@
#!/bin/bash
set -e
ICON_DIR="$HOME/.local/share/applications/icons"
if (( $# < 3 )); then
echo -e "\e[32mLet's create a new web app you can start with the app launcher.\n\e[0m"
APP_NAME=$(gum input --prompt "Name> " --placeholder "My favorite web app")
APP_URL=$(gum input --prompt "URL> " --placeholder "https://example.com")
if [[ ! $APP_URL =~ ^[a-zA-Z][a-zA-Z0-9+.-]*: ]]; then
APP_URL="https://$APP_URL"
fi
# Try to fetch favicon automatically first.
FAVICON_URL="https://www.google.com/s2/favicons?domain=${APP_URL}&sz=128"
mkdir -p "$ICON_DIR"
if curl -fsSL -o "$ICON_DIR/$APP_NAME.png" "$FAVICON_URL" && [[ -s $ICON_DIR/$APP_NAME.png ]]; then
ICON_REF="$APP_NAME.png"
else
ICON_REF=$(gum input --prompt "Icon URL> " --placeholder "Could not fetch favicon automatically. Enter PNG icon URL (see https://dashboardicons.com)")
fi
CUSTOM_EXEC=""
MIME_TYPES=""
INTERACTIVE_MODE=true
else
APP_NAME="$1"
APP_URL="$2"
if [[ ! $APP_URL =~ ^[a-zA-Z][a-zA-Z0-9+.-]*: ]]; then
APP_URL="https://$APP_URL"
fi
ICON_REF="$3"
CUSTOM_EXEC="$4" # Optional custom exec command
MIME_TYPES="$5" # Optional mime types
INTERACTIVE_MODE=false
fi
# Ensure valid execution
if [[ -z $APP_NAME || -z $APP_URL ]]; then
echo "You must set app name and app URL!"
exit 1
fi
# Resolve icon from URL or from a local icon name.
mkdir -p "$ICON_DIR"
if [[ -z $ICON_REF ]]; then
ICON_REF="https://www.google.com/s2/favicons?domain=${APP_URL}&sz=128"
fi
if [[ $ICON_REF =~ ^https?:// ]]; then
ICON_PATH="$ICON_DIR/$APP_NAME.png"
if ! curl -fsSL -o "$ICON_PATH" "$ICON_REF" || [[ ! -s $ICON_PATH ]]; then
echo "Error: Failed to download icon."
exit 1
fi
else
ICON_PATH="$ICON_DIR/$ICON_REF"
fi
# Use custom exec if provided, otherwise default behavior
EXEC_COMMAND="${CUSTOM_EXEC:-nomarchy-launch-webapp $APP_URL}"
# Create application .desktop file
DESKTOP_FILE="$HOME/.local/share/applications/$APP_NAME.desktop"
cat >"$DESKTOP_FILE" <<EOF
[Desktop Entry]
Version=1.0
Name=$APP_NAME
Comment=$APP_NAME
Exec=$EXEC_COMMAND
Terminal=false
Type=Application
Icon=$ICON_PATH
StartupNotify=true
EOF
# Add mime types if provided
if [[ -n $MIME_TYPES ]]; then
echo "MimeType=$MIME_TYPES" >>"$DESKTOP_FILE"
fi
chmod +x "$DESKTOP_FILE"
if [[ $INTERACTIVE_MODE == "true" ]]; then
echo -e "You can now find $APP_NAME using the app launcher (SUPER + SPACE)\n"
fi

View File

@@ -0,0 +1,43 @@
#!/bin/bash
set -e
ICON_DIR="$HOME/.local/share/applications/icons"
DESKTOP_DIR="$HOME/.local/share/applications/"
if (( $# == 0 )); then
# Find all web apps
while IFS= read -r -d '' file; do
if grep -q '^Exec=.*\(nomarchy-launch-webapp\|nomarchy-webapp-handler\).*' "$file"; then
WEB_APPS+=("$(basename "${file%.desktop}")")
fi
done < <(find "$DESKTOP_DIR" -name '*.desktop' -print0)
if ((${#WEB_APPS[@]})); then
IFS=$'\n' SORTED_WEB_APPS=($(sort <<<"${WEB_APPS[*]}"))
unset IFS
APP_NAMES_STRING=$(gum choose --no-limit --header "Select web app to remove..." --selected-prefix="✗ " "${SORTED_WEB_APPS[@]}")
# Convert newline-separated string to array
APP_NAMES=()
while IFS= read -r line; do
[[ -n $line ]] && APP_NAMES+=("$line")
done <<< "$APP_NAMES_STRING"
else
echo "No web apps to remove."
exit 1
fi
else
# Use array to preserve spaces in app names
APP_NAMES=("$@")
fi
if (( ${#APP_NAMES[@]} == 0 )); then
echo "You must select at least one web app to remove."
exit 1
fi
for APP_NAME in "${APP_NAMES[@]}"; do
rm -f "$DESKTOP_DIR/$APP_NAME.desktop"
rm -f "$ICON_DIR/$APP_NAME.png"
echo "Removed $APP_NAME"
done

View File

@@ -0,0 +1,36 @@
#!/bin/bash
# Remove all web apps installed via nomarchy-webapp-install.
# Identifies web apps by their Exec pattern (nomarchy-launch-webapp or nomarchy-webapp-handler).
set -e
APP_DIR="${1:-$HOME/.local/share/applications}"
ICON_DIR="$HOME/.local/share/applications/icons"
echo "Scanning for web apps in $APP_DIR..."
webapp_desktop_files=()
while IFS= read -r -d '' file; do
if grep -q "Exec=nomarchy-launch-webapp\|Exec=nomarchy-webapp-handler" "$file" 2>/dev/null; then
webapp_desktop_files+=("$file")
fi
done < <(find "$APP_DIR" -maxdepth 1 -name "*.desktop" -print0 2>/dev/null)
if (( ${#webapp_desktop_files[@]} == 0 )); then
echo "No web apps found."
exit 0
fi
for file in "${webapp_desktop_files[@]}"; do
app_name=$(basename "$file" .desktop)
echo "Removing web app: $app_name"
rm -f "$file"
rm -f "$ICON_DIR/$app_name.png"
done
if command -v update-desktop-database &>/dev/null; then
update-desktop-database "$APP_DIR" &>/dev/null || true
fi
echo "Web apps removed successfully."

View File

@@ -0,0 +1,448 @@
#!/bin/bash
COMPOSE_FILE="$HOME/.config/windows/docker-compose.yml"
check_prerequisites() {
local DISK_SIZE_GB=${1:-64}
local REQUIRED_SPACE=$((DISK_SIZE_GB + 10)) # Add 10GB for Windows ISO and overhead
# Check for KVM support
if [[ ! -e /dev/kvm ]]; then
gum style \
--border normal \
--padding "1 2" \
--margin "1" \
"❌ KVM virtualization not available!" \
"" \
"Please enable virtualization in BIOS or run:" \
" sudo modprobe kvm-intel # for Intel CPUs" \
" sudo modprobe kvm-amd # for AMD CPUs"
exit 1
fi
# Check disk space
AVAILABLE_SPACE=$(df "$HOME" | awk 'NR==2 {print int($4/1024/1024)}')
if (( AVAILABLE_SPACE < REQUIRED_SPACE )); then
echo "❌ Insufficient disk space!"
echo " Available: ${AVAILABLE_SPACE}GB"
echo " Required: ${REQUIRED_SPACE}GB (${DISK_SIZE_GB}GB disk + 10GB for Windows image)"
exit 1
fi
}
install_windows() {
# Set up trap to handle Ctrl+C
trap "echo ''; echo 'Installation cancelled by user'; exit 1" INT
check_prerequisites
nomarchy-pkg-add freerdp openbsd-netcat gum
mkdir -p "$HOME/.windows"
mkdir -p "$HOME/.config/windows"
mkdir -p "$HOME/.local/share/applications/icons"
# Install Windows VM icon and desktop file
if [[ -f $NOMARCHY_PATH/applications/icons/windows.png ]]; then
cp "$NOMARCHY_PATH/applications/icons/windows.png" "$HOME/.local/share/applications/icons/windows.png"
fi
cat << EOF | tee "$HOME/.local/share/applications/windows-vm.desktop" > /dev/null
[Desktop Entry]
Name=Windows
Comment=Start Windows VM via Docker and connect with RDP
Exec=uwsm app -- nomarchy-windows-vm launch
Icon=$HOME/.local/share/applications/icons/windows.png
Terminal=false
Type=Application
Categories=System;Virtualization;
EOF
# Get system resources
TOTAL_RAM=$(free -h | awk 'NR==2 {print $2}')
TOTAL_RAM_GB=$(awk 'NR==1 {printf "%d", $2/1024/1024}' /proc/meminfo)
TOTAL_CORES=$(nproc)
echo ""
echo "System Resources Detected:"
echo " Total RAM: $TOTAL_RAM"
echo " Total CPU Cores: $TOTAL_CORES"
echo ""
RAM_OPTIONS=""
for size in 2 4 8 16 32 64; do
if (( size <= TOTAL_RAM_GB )); then
RAM_OPTIONS="$RAM_OPTIONS ${size}G"
fi
done
SELECTED_RAM=$(echo $RAM_OPTIONS | tr ' ' '\n' | gum choose --selected="4G" --header="How much RAM would you like to allocate to Windows VM?")
# Check if user cancelled
if [[ -z $SELECTED_RAM ]]; then
echo "Installation cancelled by user"
exit 1
fi
SELECTED_CORES=$(gum input --placeholder="Number of CPU cores (1-$TOTAL_CORES)" --value="2" --header="How many CPU cores would you like to allocate to Windows VM?" --char-limit=2)
# Check if user cancelled (Ctrl+C in gum input returns empty string)
if [[ -z $SELECTED_CORES ]]; then
echo "Installation cancelled by user"
exit 1
fi
if ! [[ $SELECTED_CORES =~ ^[0-9]+$ ]] || (( SELECTED_CORES < 1 )) || (( SELECTED_CORES > TOTAL_CORES )); then
echo "Invalid input. Using default: 2 cores"
SELECTED_CORES=2
fi
AVAILABLE_SPACE=$(df "$HOME" | awk 'NR==2 {print int($4/1024/1024)}')
MAX_DISK_GB=$((AVAILABLE_SPACE - 10)) # Leave 10GB for Windows image
# Check if we have enough space for minimum
if (( MAX_DISK_GB < 32 )); then
echo "❌ Insufficient disk space for Windows VM!"
echo " Available: ${AVAILABLE_SPACE}GB"
echo " Minimum required: 42GB (32GB disk + 10GB for Windows image)"
exit 1
fi
DISK_OPTIONS=""
for size in 32 64 128 256 512; do
if (( size <= MAX_DISK_GB )); then
DISK_OPTIONS="$DISK_OPTIONS ${size}G"
fi
done
# Default to 64G if available, otherwise 32G
DEFAULT_DISK="64G"
if ! echo "$DISK_OPTIONS" | grep -q "64G"; then
DEFAULT_DISK="32G"
fi
SELECTED_DISK=$(echo $DISK_OPTIONS | tr ' ' '\n' | gum choose --selected="$DEFAULT_DISK" --header="How much disk space would you like to give Windows VM? (64GB+ recommended)")
# Check if user cancelled
if [[ -z $SELECTED_DISK ]]; then
echo "Installation cancelled by user"
exit 1
fi
# Extract just the number for prerequisite check
DISK_SIZE_NUM=$(echo "$SELECTED_DISK" | sed 's/G//')
# Re-check prerequisites with selected disk size
check_prerequisites "$DISK_SIZE_NUM"
# Prompt for username and password
USERNAME=$(gum input --placeholder="Username (Press enter to use default: docker)" --header="Enter Windows username:")
if [[ -z $USERNAME ]]; then
USERNAME="docker"
fi
PASSWORD=$(gum input --placeholder="Password (Press enter to use default: admin)" --password --header="Enter Windows password:")
if [[ -z $PASSWORD ]]; then
PASSWORD="admin"
PASSWORD_DISPLAY="(default)"
else
PASSWORD_DISPLAY="(user-defined)"
fi
# Display configuration summary
gum style \
--border normal \
--padding "1 2" \
--margin "1" \
--align left \
--bold \
"Windows VM Configuration" \
"" \
"RAM: $SELECTED_RAM" \
"CPU: $SELECTED_CORES cores" \
"Disk: $SELECTED_DISK" \
"Username: $USERNAME" \
"Password: $PASSWORD_DISPLAY"
# Ask for confirmation
echo ""
if ! gum confirm "Proceed with this configuration?"; then
echo "Installation cancelled by user"
exit 1
fi
mkdir -p $HOME/Windows
# Create docker-compose.yml in user config directory
cat << EOF | tee "$COMPOSE_FILE" > /dev/null
services:
windows:
image: dockurr/windows
container_name: nomarchy-windows
environment:
VERSION: "11"
RAM_SIZE: "$SELECTED_RAM"
CPU_CORES: "$SELECTED_CORES"
DISK_SIZE: "$SELECTED_DISK"
USERNAME: "$USERNAME"
PASSWORD: "$PASSWORD"
TZ: "$(timedatectl show -p Timezone --value 2>/dev/null || echo UTC)"
ARGUMENTS: "-rtc base=localtime,clock=host,driftfix=slew"
devices:
- /dev/kvm
- /dev/net/tun
cap_add:
- NET_ADMIN
ports:
- 127.0.0.1:8006:8006
- 127.0.0.1:3389:3389/tcp
- 127.0.0.1:3389:3389/udp
volumes:
- $HOME/.windows:/storage
- $HOME/Windows:/shared
restart: unless-stopped
stop_grace_period: 2m
EOF
echo ""
echo "Starting Windows VM installation..."
echo "This will download a Windows 11 image (may take 10-15 minutes)."
echo ""
echo "Monitor installation progress at: http://127.0.0.1:8006"
echo ""
# Start docker-compose with user's config
echo "Starting Windows VM with docker-compose..."
if ! docker-compose -f "$COMPOSE_FILE" up -d 2>&1; then
echo "❌ Failed to start Windows VM!"
echo " Common issues:"
echo " - Docker daemon not running: sudo systemctl start docker"
echo " - Port already in use: check if another VM is running"
echo " - Permission issues: make sure you're in the docker group"
exit 1
fi
echo ""
echo "Windows VM is starting up!"
echo ""
echo "Opening browser to monitor installation..."
# Open browser to monitor installation
sleep 3
xdg-open "http://127.0.0.1:8006"
echo ""
echo "Installation is running in the background."
echo "You can monitor progress at: http://127.0.0.1:8006"
echo ""
echo "Once finished, launch 'Windows' via Super + Space"
echo ""
echo "To stop the VM: nomarchy-windows-vm stop"
echo "To change resources: ~/.config/windows/docker-compose.yml"
echo ""
}
remove_windows() {
if ! gum confirm --default=false "Remove Windows VM and delete all associated data?"; then
echo "Removal cancelled by user"
exit 1
fi
echo "Removing Windows VM..."
docker-compose -f "$COMPOSE_FILE" down 2>/dev/null || true
docker rmi dockurr/windows 2>/dev/null || echo "Image already removed or not found"
rm "$HOME/.local/share/applications/windows-vm.desktop"
rm -rf "$HOME/.config/windows"
rm -rf "$HOME/.windows"
echo ""
echo "Windows VM removal completed!"
}
launch_windows() {
KEEP_ALIVE=false
if [[ $1 = "--keep-alive" ]] || [[ $1 = "-k" ]]; then
KEEP_ALIVE=true
fi
# Check if config exists
if [[ ! -f $COMPOSE_FILE ]]; then
echo "Windows VM not configured. Please run: nomarchy-windows-vm install"
exit 1
fi
# Extract credentials from compose file
WIN_USER=$(grep "USERNAME:" "$COMPOSE_FILE" | sed 's/.*USERNAME: "\(.*\)"/\1/')
WIN_PASS=$(grep "PASSWORD:" "$COMPOSE_FILE" | sed 's/.*PASSWORD: "\(.*\)"/\1/')
# Use defaults if not found
[[ -z $WIN_USER ]] && WIN_USER="docker"
[[ -z $WIN_PASS ]] && WIN_PASS="admin"
# Check if container is already running
CONTAINER_STATUS=$(docker inspect --format='{{.State.Status}}' nomarchy-windows 2>/dev/null)
if [[ $CONTAINER_STATUS != "running" ]]; then
echo "Starting Windows VM..."
# Send desktop notification
notify-send " Starting Windows VM" " This can take 15-30 seconds" -t 15000
if ! docker-compose -f "$COMPOSE_FILE" up -d 2>&1; then
echo "❌ Failed to start Windows VM!"
echo " Try checking: nomarchy-windows-vm status"
echo " View logs: docker logs nomarchy-windows"
notify-send -u critical "Windows VM" "Failed to start Windows VM"
exit 1
fi
echo "Waiting for Windows VM to start..."
WAIT_COUNT=0
until docker logs nomarchy-windows 2>&1 | grep -qi "windows started successfully"; do
sleep 2
WAIT_COUNT=$((WAIT_COUNT + 1))
if (( WAIT_COUNT > 60 )); then # 2 minutes timeout
echo ""
echo "❌ Timeout: Windows VM failed to start within 2 minutes"
echo " Check logs: docker logs nomarchy-windows"
exit 1
fi
done
fi
# Build the connection info
if [[ $KEEP_ALIVE = "true" ]]; then
LIFECYCLE="VM will keep running after RDP closes
To stop: nomarchy-windows-vm stop"
else
LIFECYCLE="VM will auto-stop when RDP closes"
fi
gum style \
--border normal \
--padding "1 2" \
--margin "1" \
--align center \
"Connecting to Windows VM" \
"" \
"$LIFECYCLE"
# Detect display scale from Hyprland
HYPR_SCALE=$(hyprctl monitors -j | jq -r '.[] | select (.focused == true) | .scale')
SCALE_PERCENT=$(echo "$HYPR_SCALE" | awk '{print int($1 * 100)}')
RDP_SCALE=""
if (( SCALE_PERCENT >= 170 )); then
RDP_SCALE="/scale:180"
elif (( SCALE_PERCENT >= 130 )); then
RDP_SCALE="/scale:140"
fi
# If scale is less than 130%, don't set any scale (use default 100)
# Connect with RDP in fullscreen (auto-detects resolution)
xfreerdp3 /u:"$WIN_USER" /p:"$WIN_PASS" /v:127.0.0.1:3389 -grab-keyboard /sound /microphone /clipboard /cert:ignore /title:"Windows VM - Nomarchy" /dynamic-resolution /gfx:AVC444 /floatbar:sticky:off,default:visible,show:fullscreen $RDP_SCALE
# After RDP closes, stop the container unless --keep-alive was specified
if [[ $KEEP_ALIVE = "false" ]]; then
echo ""
echo "RDP session closed. Stopping Windows VM..."
docker-compose -f "$COMPOSE_FILE" down
echo "Windows VM stopped."
else
echo ""
echo "RDP session closed. Windows VM is still running."
echo "To stop it: nomarchy-windows-vm stop"
fi
}
stop_windows() {
if [[ ! -f $COMPOSE_FILE ]]; then
echo "Windows VM not configured."
exit 1
fi
echo "Stopping Windows VM..."
docker-compose -f "$COMPOSE_FILE" down
echo "Windows VM stopped."
}
status_windows() {
if [[ ! -f $COMPOSE_FILE ]]; then
echo "Windows VM not configured."
echo "To set up: nomarchy-windows-vm install"
exit 1
fi
CONTAINER_STATUS=$(docker inspect --format='{{.State.Status}}' nomarchy-windows 2>/dev/null)
if [[ -z $CONTAINER_STATUS ]]; then
echo "Windows VM container not found."
echo "To start: nomarchy-windows-vm launch"
elif [[ $CONTAINER_STATUS = "running" ]]; then
gum style \
--border normal \
--padding "1 2" \
--margin "1" \
--align left \
"Windows VM Status: RUNNING" \
"" \
"Web interface: http://127.0.0.1:8006" \
"RDP available: port 3389" \
"" \
"To connect: nomarchy-windows-vm launch" \
"To stop: nomarchy-windows-vm stop"
else
echo "Windows VM is stopped (status: $CONTAINER_STATUS)"
echo "To start: nomarchy-windows-vm launch"
fi
}
show_usage() {
echo "Usage: nomarchy-windows-vm [command] [options]"
echo ""
echo "Commands:"
echo " install Install and configure Windows VM"
echo " remove Remove Windows VM and optionally its data"
echo " launch [options] Start Windows VM (if needed) and connect via RDP"
echo " Options:"
echo " --keep-alive, -k Keep VM running after RDP closes"
echo " stop Stop the running Windows VM"
echo " status Show current VM status"
echo " help Show this help message"
echo ""
echo "Examples:"
echo " nomarchy-windows-vm install # Set up Windows VM for first time"
echo " nomarchy-windows-vm launch # Connect to VM (auto-stop on exit)"
echo " nomarchy-windows-vm launch -k # Connect to VM (keep running)"
echo " nomarchy-windows-vm stop # Shut down the VM"
}
# Main command dispatcher
case "$1" in
install)
install_windows
;;
remove)
remove_windows
;;
launch|start)
launch_windows "$2"
;;
stop|down)
stop_windows
;;
status)
status_windows
;;
help|--help|-h|"")
show_usage
;;
*)
echo "Unknown command: $1" >&2
echo "" >&2
show_usage >&2
exit 1
;;
esac

41
features/apps/swayosd.nix Normal file
View File

@@ -0,0 +1,41 @@
{ config, pkgs, lib, ... }:
{
services.swayosd = {
enable = lib.mkDefault true;
stylePath = lib.mkDefault "${config.home.homeDirectory}/.config/swayosd/style.css";
};
xdg.configFile."swayosd/style.css".text = lib.mkDefault ''
@define-color background-color #${config.colorScheme.palette.base00};
@define-color border-color #${config.colorScheme.palette.base0E};
@define-color label #${config.colorScheme.palette.base05};
@define-color image #${config.colorScheme.palette.base05};
@define-color progress #${config.colorScheme.palette.base0B};
window {
border-radius: 0;
opacity: 0.97;
border: 2px solid @border-color;
background-color: @background-color;
}
label {
font-family: '${config.nomarchy.fonts.monospace}';
font-size: 11pt;
color: @label;
}
image {
color: @image;
}
progressbar {
border-radius: 0;
}
progress {
background-color: @progress;
}
'';
}

View File

@@ -0,0 +1,94 @@
# Prefix
set -g prefix C-Space
set -g prefix2 C-b
bind C-Space send-prefix
# Reload config
bind q source-file ~/.config/tmux/tmux.conf \; display "Configuration reloaded"
# Vi mode for copy
setw -g mode-keys vi
bind -T copy-mode-vi v send -X begin-selection
bind -T copy-mode-vi y send -X copy-selection-and-cancel
# Pane Controls
bind h split-window -v -c "#{pane_current_path}"
bind v split-window -h -c "#{pane_current_path}"
bind x kill-pane
bind -n C-M-Left select-pane -L
bind -n C-M-Right select-pane -R
bind -n C-M-Up select-pane -U
bind -n C-M-Down select-pane -D
bind -n C-M-S-Left resize-pane -L 5
bind -n C-M-S-Down resize-pane -D 5
bind -n C-M-S-Up resize-pane -U 5
bind -n C-M-S-Right resize-pane -R 5
# Window navigation
bind r command-prompt -I "#W" "rename-window -- '%%'"
bind c new-window -c "#{pane_current_path}"
bind k kill-window
bind -n M-1 select-window -t 1
bind -n M-2 select-window -t 2
bind -n M-3 select-window -t 3
bind -n M-4 select-window -t 4
bind -n M-5 select-window -t 5
bind -n M-6 select-window -t 6
bind -n M-7 select-window -t 7
bind -n M-8 select-window -t 8
bind -n M-9 select-window -t 9
bind -n M-Left select-window -t -1
bind -n M-Right select-window -t +1
bind -n M-S-Left swap-window -t -1 \; select-window -t -1
bind -n M-S-Right swap-window -t +1 \; select-window -t +1
# Session controls
bind R command-prompt -I "#S" "rename-session -- '%%'"
bind C new-session -c "#{pane_current_path}"
bind K kill-session
bind P switch-client -p
bind N switch-client -n
bind -n M-Up switch-client -p
bind -n M-Down switch-client -n
# General
set -g default-terminal "tmux-256color"
set -ag terminal-overrides ",*:RGB"
set -g mouse on
set -g base-index 1
setw -g pane-base-index 1
set -g renumber-windows on
set -g history-limit 50000
set -g escape-time 0
set -g focus-events on
set -g set-clipboard on
set -g allow-passthrough on
setw -g aggressive-resize on
set -g detach-on-destroy off
# Status bar
set -g status-position top
set -g status-interval 5
set -g status-left-length 30
set -g status-right-length 50
set -g window-status-separator ""
set -gw automatic-rename on
set -gw automatic-rename-format '#{b:pane_current_path}'
# Theme
set -g status-style "bg=default,fg=default"
set -g status-left "#[fg=black,bg=blue,bold] #S #[bg=default] "
set -g status-right "#[fg=blue]#{?pane_in_mode,COPY ,}#{?client_prefix,PREFIX ,}#{?window_zoomed_flag,ZOOM ,}#[fg=brightblack]#h "
set -g window-status-format "#[fg=brightblack] #I:#W "
set -g window-status-current-format "#[fg=blue,bold] #I:#W "
set -g pane-border-style "fg=brightblack"
set -g pane-active-border-style "fg=blue"
set -g message-style "bg=default,fg=blue"
set -g message-command-style "bg=default,fg=blue"
set -g mode-style "bg=blue,fg=black"
setw -g clock-mode-colour blue

53
features/apps/vscode.nix Normal file
View File

@@ -0,0 +1,53 @@
{ config, pkgs, lib, ... }:
let
themeConfig = builtins.fromJSON (builtins.readFile (../../assets/themes + "/${config.nomarchy.theme}/vscode.json"));
# Development extensions that match the system theme
devExtensions = with pkgs.vscode-extensions; [
# Language support
ms-python.python
rust-lang.rust-analyzer
golang.go
jnoortheen.nix-ide
# Git integration
eamodio.gitlens
# Editor enhancements
esbenp.prettier-vscode
dbaeumer.vscode-eslint
bradlc.vscode-tailwindcss
# Theme extensions (provide color themes matching nomarchy palettes)
catppuccin.catppuccin-vsc
enkia.tokyo-night
arcticicestudio.nord-visual-studio-code
sainnhe.everforest
mvllow.rose-pine
];
in
{
options.nomarchy.vscode = {
devExtensions = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to install development extensions for VSCode.";
};
};
config = {
programs.vscode = {
enable = lib.mkDefault true;
package = lib.mkDefault pkgs.vscode;
userSettings = lib.mkDefault {
"update.mode" = "none";
"workbench.colorTheme" = themeConfig.name;
"window.titleBarStyle" = "custom";
"editor.fontFamily" = "'${config.nomarchy.fonts.monospace}', 'Droid Sans Mono', monospace";
"terminal.integrated.fontFamily" = config.nomarchy.fonts.monospace;
};
extensions = lib.mkIf config.nomarchy.vscode.devExtensions (lib.mkDefault devExtensions);
};
};
}

56
features/apps/walker.nix Normal file
View File

@@ -0,0 +1,56 @@
{ config, pkgs, lib, ... }:
{
programs.walker = {
enable = lib.mkDefault true;
runAsService = lib.mkDefault true;
config = lib.mkDefault {
theme = "nomarchy";
ui = {
anchors = {
top = true;
};
};
selection_wrap = true;
hide_action_hints = true;
placeholders = {
"default" = { input = " Search..."; list = "No Results"; };
};
keybinds = {
quick_activate = [];
};
columns = {
symbols = 1;
};
providers = {
max_results = 256;
default = [
"desktopapplications"
"websearch"
];
prefixes = [
{ prefix = "/"; provider = "providerlist"; }
{ prefix = "."; provider = "files"; }
{ prefix = ":"; provider = "symbols"; }
{ prefix = "="; provider = "calc"; }
{ prefix = "@"; provider = "websearch"; }
{ prefix = "$"; provider = "clipboard"; }
];
};
};
themes."nomarchy" = lib.mkDefault {
style = ''
* {
color: #${config.colorScheme.palette.base05};
}
#window {
background-color: #${config.colorScheme.palette.base00};
}
.item.active {
background-color: #${config.colorScheme.palette.base03};
color: #${config.colorScheme.palette.base0B};
}
'';
};
};
}

87
features/default.nix Normal file
View File

@@ -0,0 +1,87 @@
{ config, pkgs, inputs, lib, ... }:
let
nomarchyLib = import ../lib { inherit lib; };
userPackagesFile = "${config.home.homeDirectory}/.config/home-manager/user-packages.json";
userPackages = if builtins.pathExists userPackagesFile then
let
pkgNames = builtins.fromJSON (builtins.readFile userPackagesFile);
# Filter to only packages that exist in pkgs to prevent build failures
validPkgs = builtins.filter (name: builtins.hasAttr name pkgs) pkgNames;
in builtins.map (name: pkgs.${name}) validPkgs
else [];
in
{
imports = [
inputs.nix-colors.homeManagerModules.default
inputs.walker.homeManagerModules.default
../themes/engine/stylix-compat.nix
../core/home/options.nix
../core/home/state.nix
../core/home/overrides.nix
../core/home/behavior.nix
../themes/engine/loader.nix
../themes/engine/files.nix
../core/home/fonts.nix
./apps/alacritty/default.nix
./desktop/nightlight.nix
./desktop/idle.nix
../themes/engine/stylix.nix
./desktop/hyprland/default.nix
./desktop/waybar/default.nix
./apps/walker.nix
../themes/engine/switcher.nix
./scripts/default.nix
../core/home/configs.nix
./apps/swayosd.nix
../core/home/security.nix
./scripts/battery-monitor.nix
../core/home/bash.nix
];
colorScheme = lib.mkDefault (nomarchyLib.getColorScheme config.nomarchy.theme);
# Enable neovim program module (required for stylix integration)
programs.neovim.enable = lib.mkDefault true;
home.packages = lib.mkDefault (with pkgs; [
# Core applications
firefox
xfce.thunar
# Media
imv # Image viewer
mpv # Video player
# Hyprland ecosystem
swww # Wallpaper daemon
mako # Notification daemon
hyprlock # Lock screen
# Screenshot and clipboard
wl-clipboard
grim
slurp
# Hardware control
brightnessctl
playerctl
pamixer
# Utilities
jq
xmlstarlet
mise
gum # TUI components for scripts
# Theming
yaru-theme
bibata-cursors
# Fonts
nerd-fonts.jetbrains-mono
nerd-fonts.roboto-mono
nerd-fonts.fira-code
nerd-fonts.ubuntu-mono
] ++ userPackages);
}

View File

@@ -0,0 +1,71 @@
# paths to stylesheets on the filesystem which should be applied to the application
#
# relative paths are resolved relative to the location of the config file
stylesheets: ["../nomarchy/current/theme/hyprland-preview-share-picker.css"]
# default page selected when the picker is opened
default_page: outputs
window:
# height of the application window
height: 500
# width of the application window
width: 1000
image:
# size to which the images should be internally resized to reduce the memory footprint
resize_size: 500
# target size of the longer side of the image widget
widget_size: 150
classes:
# css classname of the window
window: window
# css classname of the card containing an image and a label
image_card: card
# css classname of the card containing an image and a label when the image is still being loaded
image_card_loading: card-loading
# css classname of the image inside the card
image: image
# css classname of the label inside the card
image_label: image-label
# css classname of the notebook containing all pages
notebook: notebook
# css classname of a label of the notebook
tab_label: tab-label
# css classname of a notebook page (e.g. windows container)
notebook_page: page
# css classname of the region selection button
region_button: region-button
# css classname of the button containing the session restore checkbox and label
restore_button: restore-button
windows:
# minimum amount of image cards per row on the windows page
min_per_row: 3
# maximum amount of image cards per row on the windows page
max_per_row: 999
# number of clicks needed to select a window
clicks: 1
# spacing in pixels between the window cards
spacing: 12
outputs:
# number of clicks needed to select an output
clicks: 1
# spacing in pixels between the outputs in the layout
# note: the spacing is applied from both sides (the gap is `spacing * 2`)
spacing: 6
# show the label with the output name
show_label: false
# size the output cards respectively to their scaling
respect_output_scaling: true
region:
# command to run for region selection
# the output needs to be in the <output>@<x>,<y>,<w>,<h> (e.g. DP-3@2789,436,756,576) format
command: slurp -f '%o@%x,%y,%w,%h'
# hide the token restore checkbox and use the default value instead
hide_token_restore: true
# enable debug logs by default
debug: false

View File

@@ -0,0 +1,2 @@
# Extra autostart processes
# exec-once = uwsm-app -- my-service

View File

@@ -0,0 +1,34 @@
# Application bindings
bindd = SUPER, RETURN, Terminal, exec, uwsm-app -- xdg-terminal-exec --dir="$(nomarchy-cmd-terminal-cwd)"
bindd = SUPER ALT, RETURN, Tmux, exec, uwsm-app -- xdg-terminal-exec --dir="$(nomarchy-cmd-terminal-cwd)" bash -c "tmux attach || tmux new -s Work"
bindd = SUPER SHIFT, RETURN, Browser, exec, nomarchy-launch-browser
bindd = SUPER SHIFT, F, File manager, exec, uwsm-app -- nautilus --new-window
bindd = SUPER ALT SHIFT, F, File manager (cwd), exec, uwsm-app -- nautilus --new-window "$(nomarchy-cmd-terminal-cwd)"
bindd = SUPER SHIFT, B, Browser, exec, nomarchy-launch-browser
bindd = SUPER SHIFT ALT, B, Browser (private), exec, nomarchy-launch-browser --private
bindd = SUPER SHIFT, M, Music, exec, nomarchy-launch-or-focus spotify
bindd = SUPER SHIFT, N, Editor, exec, nomarchy-launch-editor
bindd = SUPER SHIFT, D, Docker, exec, nomarchy-launch-tui lazydocker
bindd = SUPER SHIFT, G, Signal, exec, nomarchy-launch-or-focus ^signal$ "uwsm-app -- signal-desktop"
bindd = SUPER SHIFT, O, Obsidian, exec, nomarchy-launch-or-focus ^obsidian$ "uwsm-app -- obsidian -disable-gpu --enable-wayland-ime"
bindd = SUPER SHIFT, W, Typora, exec, uwsm-app -- typora --enable-wayland-ime
bindd = SUPER SHIFT, SLASH, Passwords, exec, uwsm-app -- 1password
# If your web app url contains #, type it as ## to prevent hyprland treating it as a comment
bindd = SUPER SHIFT, A, ChatGPT, exec, nomarchy-launch-webapp "https://chatgpt.com"
bindd = SUPER SHIFT ALT, A, Grok, exec, nomarchy-launch-webapp "https://grok.com"
bindd = SUPER SHIFT, C, Calendar, exec, nomarchy-launch-webapp "https://app.hey.com/calendar/weeks/"
bindd = SUPER SHIFT, E, Email, exec, nomarchy-launch-webapp "https://app.hey.com"
bindd = SUPER SHIFT, Y, YouTube, exec, nomarchy-launch-webapp "https://youtube.com/"
bindd = SUPER SHIFT ALT, G, WhatsApp, exec, nomarchy-launch-or-focus-webapp WhatsApp "https://web.whatsapp.com/"
bindd = SUPER SHIFT CTRL, G, Google Messages, exec, nomarchy-launch-or-focus-webapp "Google Messages" "https://messages.google.com/web/conversations"
bindd = SUPER SHIFT, P, Google Photos, exec, nomarchy-launch-or-focus-webapp "Google Photos" "https://photos.google.com/"
bindd = SUPER SHIFT, X, X, exec, nomarchy-launch-webapp "https://x.com/"
bindd = SUPER SHIFT ALT, X, X Post, exec, nomarchy-launch-webapp "https://x.com/compose/post"
# Add extra bindings
# bind = SUPER SHIFT, R, exec, alacritty -e ssh your-server
# Overwrite existing bindings, like putting Nomarchy Menu on Super + Space
# unbind = SUPER, SPACE
# bindd = SUPER, SPACE, Nomarchy menu, exec, nomarchy-menu

View File

@@ -0,0 +1,43 @@
source = ~/.config/nomarchy/current/theme/hyprlock.conf
general {
ignore_empty_input = true
}
background {
monitor =
color = $color
path = ~/.config/nomarchy/current/background
blur_passes = 3
}
animations {
enabled = false
}
input-field {
monitor =
size = 650, 100
position = 0, 0
halign = center
valign = center
inner_color = $inner_color
outer_color = $outer_color
outline_thickness = 4
font_family = JetBrainsMono Nerd Font
font_color = $font_color
placeholder_text = Enter Password
check_color = $check_color
fail_text = <i>$FAIL ($ATTEMPTS)</i>
rounding = 0
shadow_passes = 0
fade_on_empty = false
}
auth {
fingerprint:enabled = false
}

View File

@@ -0,0 +1,14 @@
# Makes hyprsunset do nothing to the screen by default
# Without this, the default applies some tint to the monitor
profile {
time = 07:00
identity = true
}
# To enable auto switch to nightlight, set in your .config/hypr/autostart:
# exec-once = uwsm app -- hyprsunset
# and use the following:
# profile {
# time = 20:00
# temperature = 4000
# }

View File

@@ -0,0 +1,53 @@
# Control your input devices
# See https://wiki.hypr.land/Configuring/Variables/#input
input {
# Use multiple keyboard layouts and switch between them with Left Alt + Right Alt
# kb_layout = us,dk,eu
# Use a specific keyboard variant if needed (e.g. intl for international keyboards)
# kb_variant = intl
kb_options = compose:caps # ,grp:alts_toggle
# Change speed of keyboard repeat
repeat_rate = 40
repeat_delay = 600
# Start with numlock on by default
numlock_by_default = true
# Increase sensitivity for mouse/trackpad (default: 0)
# sensitivity = 0.35
# Turn off mouse acceleration (default: false)
# force_no_accel = true
touchpad {
# Use natural (inverse) scrolling
# natural_scroll = true
# Use two-finger clicks for right-click instead of lower-right corner
# clickfinger_behavior = true
# Control the speed of your scrolling
scroll_factor = 0.4
# Enable the touchpad while typing
# disable_while_typing = false
# Left-click-and-drag with three fingers
# drag_3fg = 1
}
}
# Scroll nicely in the terminal
#windowrulev2 = match:class (Alacritty|kitty), scroll_touchpad 1.5
#windowrulev2 = match:class com.mitchellh.ghostty, scroll_touchpad 0.2
# Enable touchpad gestures for changing workspaces
# See https://wiki.hyprland.org/Configuring/Gestures/
# gesture = 3, horizontal, workspace
# Enable touchpad gestures for moving focus (helpful on scrolling layout)
# gesture = 3, left, dispatcher, movefocus, l
# gesture = 3, right, dispatcher, movefocus, r

View File

@@ -0,0 +1,34 @@
# Change the default Nomarchy look'n'feel
# https://wiki.hyprland.org/Configuring/Variables/#general
general {
# No gaps between windows or borders
# gaps_in = 0
# gaps_out = 0
# border_size = 0
# Change to niri-like side-scrolling layout
# layout = scrolling
}
# https://wiki.hyprland.org/Configuring/Variables/#decoration
decoration {
# Use round window corners
# rounding = 8
# Dim unfocused windows (0.0 = no dim, 1.0 = fully dimmed)
# dim_inactive = true
# dim_strength = 0.15
}
# https://wiki.hyprland.org/Configuring/Variables/#animations
animations {
# Disable all animations
# enabled = no
}
# https://wiki.hypr.land/Configuring/Variables/#layout
layout {
# Avoid overly wide single-window layouts on wide screens
# single_window_aspect_ratio = 1 1
}

View File

@@ -0,0 +1,23 @@
# See https://wiki.hyprland.org/Configuring/Monitors/
# List current monitors and resolutions possible: hyprctl monitors
# Format: monitor = [port], resolution, position, scale
# Optimized for retina-class 2x displays, like 13" 2.8K, 27" 5K, 32" 6K.
env = GDK_SCALE,2
monitor=,preferred,auto,auto
# Good compromise for 27" or 32" 4K monitors (but fractional!)
# env = GDK_SCALE,1.75
# monitor=,preferred,auto,1.6
# Straight 1x setup for low-resolution displays like 1080p or 1440p
# Or for ultrawide monitors like 34" 3440x1440 or 49" 5120x1440
# env = GDK_SCALE,1
# monitor=,preferred,auto,1
# Portrait/rotated secondary monitor (transform: 1 = 90°, 3 = 270°)
# monitor = DP-2, preferred, auto, 1, transform, 1
# Example for Framework 13 w/ 6K XDR Apple display
# monitor = DP-5, 6016x3384@60, auto, 2
# monitor = eDP-1, 2880x1920@120, auto, 2

View File

@@ -0,0 +1,47 @@
# Learn how to configure Hyprland: https://wiki.hyprland.org/Configuring/
# ============================================================================
# BEHAVIOR CONFIGS (keybindings, input, rules - non-visual)
# ============================================================================
# These are the Nomarchy defaults - don't edit these directly!
# Environment variables
source = ~/.config/nomarchy/default/hypr/envs.conf
# Keybindings
source = ~/.config/nomarchy/default/hypr/bindings/media.conf
source = ~/.config/nomarchy/default/hypr/bindings/clipboard.conf
source = ~/.config/nomarchy/default/hypr/bindings/tiling-v2.conf
source = ~/.config/nomarchy/default/hypr/bindings/utilities.conf
# Input settings
source = ~/.config/nomarchy/default/hypr/input.conf
# Window rules and layout
source = ~/.config/nomarchy/default/hypr/windows.conf
# Autostart applications
source = ~/.config/nomarchy/default/hypr/autostart.conf
# Look and feel (animations, layout behavior)
source = ~/.config/nomarchy/default/hypr/looknfeel.conf
# ============================================================================
# VISUAL CONFIGS (colors, theming)
# ============================================================================
# Theme colors - loaded from the currently active theme
source = ~/.config/nomarchy/current/theme/hyprland.conf
# ============================================================================
# USER OVERRIDES
# ============================================================================
# Your personal overrides - edit these files to customize Nomarchy!
source = ~/.config/hypr/monitors.conf
source = ~/.config/hypr/input.conf
source = ~/.config/hypr/bindings.conf
source = ~/.config/hypr/looknfeel.conf
source = ~/.config/hypr/autostart.conf
# Add any other personal Hyprland configuration below
# windowrule = workspace 5, match:class qemu

View File

@@ -0,0 +1,4 @@
screencopy {
allow_token_by_default = true
custom_picker_binary = hyprland-preview-share-picker
}

View File

@@ -0,0 +1,44 @@
{ config, pkgs, lib, ... }:
let
nomarchyLib = import ../lib { inherit lib; };
assetsPath = ../../assets/themes;
# Use shared wallpaper resolver
activeWallpaper = nomarchyLib.resolveWallpaper {
wallpaperPath = config.nomarchy.wallpaper;
themeName = config.nomarchy.theme;
inherit assetsPath;
};
hyprlandState = config.nomarchy.hyprland;
in
{
home.sessionVariables = lib.mkDefault {
WLR_NO_HARDWARE_CURSORS = "1";
HYPRLAND_LOG_WLR = "1";
};
wayland.windowManager.hyprland = {
enable = lib.mkDefault true;
settings = lib.mkDefault {
"debug" = {
"disable_logs" = false;
"enable_stdout_logs" = true;
};
"cursor" = {
"no_hardware_cursors" = true;
};
"general" = {
"gaps_in" = hyprlandState.gaps_in;
"gaps_out" = hyprlandState.gaps_out;
"border_size" = hyprlandState.border_size;
"col.active_border" = "rgb(${config.colorScheme.palette.base0E})";
"col.inactive_border" = "rgb(${config.colorScheme.palette.base03})";
};
};
extraConfig = lib.mkDefault ''
source = ~/.config/hypr/nomarchy.conf
'';
};
}

View File

@@ -0,0 +1,12 @@
$activeBorderColor = rgb(dcd7ba)
general {
col.active_border = $activeBorderColor
}
group {
col.border_active = $activeBorderColor
}
# Kanagawa backdrop is too strong for detault opacity
windowrule = opacity 0.98 0.95, match:tag terminal

View File

@@ -0,0 +1,26 @@
$activeBorderColor = rgb(f2fcff)
$activeShadowColor = rgb(6fb8e3)
$inactiveBorderColor = rgba(30486099)
$inactiveShadowColor = rgba(30486077)
general {
col.active_border = $activeBorderColor
col.inactive_border = $inactiveBorderColor
gaps_in = 8
gaps_out = 16
}
group {
col.border_active = $activeBorderColor
col.border_inactive = $inactiveBorderColor
}
decoration {
shadow {
enabled = true
range = 16
render_power = 4
color = $activeShadowColor
color_inactive = $inactiveShadowColor
}
}

View File

@@ -0,0 +1,9 @@
$activeBorderColor = rgb(81a1c1)
general {
col.active_border = $activeBorderColor
}
group {
col.border_active = $activeBorderColor
}

View File

@@ -0,0 +1,9 @@
$activeBorderColor = rgb(faa968)
general {
col.active_border = $activeBorderColor
}
group {
col.border_active = $activeBorderColor
}

View File

@@ -0,0 +1,61 @@
# --- Everforest Color Palette ---
$bg0 = rgba(2d353bee)
$bg1 = rgba(343f44ee)
$bg2 = rgba(3d484dee)
$bg3 = rgba(475258ee)
$bg4 = rgba(4f585eee)
$bg5 = rgba(56635fee)
$fg = rgba(d3c6aaee)
$red = rgba(e67e80ee)
$orange = rgba(e69875ee)
$yellow = rgba(dbbc7fee)
$green = rgba(a7c080ee)
$aqua = rgba(83c092ee)
$blue = rgba(7fbbb3ee)
$purple = rgba(d699b6ee)
$grey0 = rgba(7a8478ee)
$grey1 = rgba(859289ee)
$grey2 = rgba(9da9a0ee)
general {
gaps_in = 6
gaps_out = 12
border_size = 3
col.active_border = $fg
col.inactive_border = $bg5
layout = dwindle
resize_on_border = true
}
decoration {
rounding = 10
blur {
enabled = true
size = 5
passes = 3
new_optimizations = true
ignore_opacity = true
}
shadow {
enabled = true
range = 20
render_power = 3
color = rgba(00000044)
}
}
animations {
enabled = true
bezier = overshot, 0.05, 0.9, 0.1, 1.05
bezier = smoothOut, 0.36, 0, 0.66, -0.56
bezier = smoothIn, 0.25, 1, 0.5, 1
animation = windows, 1, 3, overshot, slide
animation = windowsOut, 1, 3, smoothOut, slide
animation = windowsMove, 1, 3, default
animation = border, 1, 3, default
animation = fade, 1, 3, smoothIn
animation = workspaces, 1, 3, smoothIn, slide
}

36
features/desktop/idle.nix Normal file
View File

@@ -0,0 +1,36 @@
{ config, lib, ... }:
{
services.hypridle = {
enable = lib.mkDefault config.nomarchy.toggles.idle;
settings = lib.mkDefault {
general = {
lock_cmd = "nomarchy-lock-screen";
before_sleep_cmd = "loginctl lock-session";
after_sleep_cmd = "sleep 1 && hyprctl dispatch dpms on";
inhibit_sleep = 3;
};
listener = [
{
timeout = 150;
on-timeout = "pidof hyprlock || nomarchy-launch-screensaver";
}
{
timeout = 151;
on-timeout = "loginctl lock-session";
}
{
timeout = 330;
on-timeout = "brightnessctl -sd '*::kbd_backlight' set 0";
on-resume = "brightnessctl -rd '*::kbd_backlight'";
}
{
timeout = 330;
on-timeout = "hyprctl dispatch dpms off";
on-resume = "hyprctl dispatch dpms on && brightnessctl -r";
}
];
};
};
}

View File

@@ -0,0 +1,11 @@
{ config, lib, ... }:
let
temp = toString (if config.nomarchy.toggles.nightlight then config.nomarchy.nightlightTemperature else 6500);
in
{
services.hyprsunset = {
enable = lib.mkDefault true; # Always enabled, we control via IPC and state
extraArgs = lib.mkDefault [ "--temperature" temp ];
};
}

View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Toggles transparency for the currently focused window.
hyprctl dispatch setprop "address:$(hyprctl activewindow -j | jq -r '.address')" opaque toggle

View File

@@ -0,0 +1,24 @@
#!/bin/bash
# Get the active monitor (the one with the cursor)
MONITOR_INFO=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true)')
ACTIVE_MONITOR=$(echo "$MONITOR_INFO" | jq -r '.name')
CURRENT_SCALE=$(echo "$MONITOR_INFO" | jq -r '.scale')
WIDTH=$(echo "$MONITOR_INFO" | jq -r '.width')
HEIGHT=$(echo "$MONITOR_INFO" | jq -r '.height')
REFRESH_RATE=$(echo "$MONITOR_INFO" | jq -r '.refreshRate')
# Cycle through scales: 1 → 1.6 → 2 → 3 → 1
CURRENT_INT=$(awk -v s="$CURRENT_SCALE" 'BEGIN { printf "%.0f", s * 10 }')
case "$CURRENT_INT" in
10) NEW_SCALE=1.6 ;;
16) NEW_SCALE=2 ;;
20) NEW_SCALE=3 ;;
*) NEW_SCALE=1 ;;
esac
hyprctl keyword misc:disable_scale_notification true
hyprctl keyword monitor "$ACTIVE_MONITOR,${WIDTH}x${HEIGHT}@${REFRESH_RATE},auto,$NEW_SCALE"
hyprctl keyword misc:disable_scale_notification false
notify-send -u low "󰍹 Display scaling set to ${NEW_SCALE}x"

View File

@@ -0,0 +1,9 @@
#!/bin/bash
# Close all open windows
hyprctl clients -j | \
jq -r ".[].address" | \
xargs -I{} hyprctl dispatch closewindow address:{}
# Move to first workspace
hyprctl dispatch workspace 1

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# Toggles the window gaps globally between no gaps and the default 10/5/2, declaratively and instantly.
STATE_DIR="$HOME/.config/nomarchy"
STATE_FILE="$STATE_DIR/state.json"
mkdir -p "$STATE_DIR"
if [ ! -f "$STATE_FILE" ]; then
echo "{}" > "$STATE_FILE"
fi
gaps=$(jq -r '.hyprland.gaps_out // 10' "$STATE_FILE")
if [[ $gaps == "0" ]]; then
NEW_STATE='{"gaps_out": 10, "gaps_in": 5, "border_size": 2}'
hyprctl keyword general:gaps_out 10
hyprctl keyword general:gaps_in 5
hyprctl keyword general:border_size 2
else
NEW_STATE='{"gaps_out": 0, "gaps_in": 0, "border_size": 0}'
hyprctl keyword general:gaps_out 0
hyprctl keyword general:gaps_in 0
hyprctl keyword general:border_size 0
fi
TMP_JSON=$(mktemp)
jq --argjson state "$NEW_STATE" '.hyprland = $state' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
echo "Toggled gaps to $NEW_STATE declaratively."

View File

@@ -0,0 +1,46 @@
#!/bin/bash
# Toggle to pop-out a tile to stay fixed on a display basis.
# Usage:
# nomarchy-hyprland-window-pop [width height [x y]]
#
# Arguments:
# width Optional. Width of the floating window. Default: 1300
# height Optional. Height of the floating window. Default: 900
# x Optional. X position of the window. Must provide both X and Y to take effect.
# y Optional. Y position of the window. Must provide both X and Y to take effect.
#
# Behavior:
# - If the window is already pinned, it will be unpinned and removed from the pop layer.
# - If the window is not pinned, it will be floated, resized, moved/centered, pinned, brought to top, and popped.
width=${1:-1300}
height=${2:-900}
x=${3:-}
y=${4:-}
active=$(hyprctl activewindow -j)
pinned=$(echo "$active" | jq ".pinned")
addr=$(echo "$active" | jq -r ".address")
if [[ $pinned == "true" ]]; then
hyprctl -q --batch \
"dispatch pin address:$addr;" \
"dispatch togglefloating address:$addr;" \
"dispatch tagwindow -pop address:$addr;"
elif [[ -n $addr ]]; then
hyprctl dispatch togglefloating address:$addr
hyprctl dispatch resizeactive exact $width $height address:$addr
if [[ -n $x && -n $y ]]; then
hyprctl dispatch moveactive $x $y address:$addr
else
hyprctl dispatch centerwindow address:$addr
fi
hyprctl -q --batch \
"dispatch pin address:$addr;" \
"dispatch alterzorder top address:$addr;" \
"dispatch tagwindow +pop address:$addr;"
fi

View File

@@ -0,0 +1,13 @@
#!/bin/bash
# Check current single_window_aspect_ratio setting
CURRENT_VALUE=$(hyprctl getoption "layout:single_window_aspect_ratio" 2>/dev/null | head -1)
# Parse vec2 output: "vec2: [1, 1]" or "vec2: [0, 0]"
if [[ $CURRENT_VALUE == *"[1, 1]"* ]]; then
hyprctl keyword layout:single_window_aspect_ratio "0 0"
notify-send -u low " Disable single-window square aspect ratio"
else
hyprctl keyword layout:single_window_aspect_ratio "1 1"
notify-send -u low " Enable single-window square aspect"
fi

View File

@@ -0,0 +1,14 @@
#!/bin/bash
# Toggle the layout on the current active workspace between dwindle and scrolling
ACTIVE_WORKSPACE=$(hyprctl activeworkspace -j | jq -r '.id')
CURRENT_LAYOUT=$(hyprctl activeworkspace -j | jq -r '.tiledLayout')
case "$CURRENT_LAYOUT" in
dwindle) NEW_LAYOUT=scrolling ;;
*) NEW_LAYOUT=dwindle ;;
esac
hyprctl keyword workspace $ACTIVE_WORKSPACE, layout:$NEW_LAYOUT
notify-send -u low "󱂬 Workspace layout set to $NEW_LAYOUT"

View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Reload hyprland configuration (used by the Nomarchy theme switching).
hyprctl reload >/dev/null

View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Restart the hypridle service (used for idle detection and auto-lock).
nomarchy-restart-app hypridle

View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Restart the hyprsunset service (used for blue light filtering/night light).
nomarchy-restart-app hyprsunset

View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Reload mako configuration (used by the Nomarchy theme switching).
makoctl reload

View File

@@ -0,0 +1,3 @@
#!/bin/bash
nomarchy-restart-app swayosd-server

View File

@@ -0,0 +1,22 @@
#!/bin/bash
restart_services() {
if systemctl --user is-enabled elephant.service &>/dev/null; then
systemctl --user restart elephant.service
fi
if systemctl --user is-enabled app-walker@autostart.service &>/dev/null; then
systemctl --user restart app-walker@autostart.service
else
echo -e "\e[31mUnable to restart Walker -- RESTART MANUALLY\e[0m"
fi
}
if (( EUID == 0 )); then
SCRIPT_OWNER=$(stat -c '%U' "$0")
USER_UID=$(id -u "$SCRIPT_OWNER")
systemd-run --uid="$SCRIPT_OWNER" --setenv=XDG_RUNTIME_DIR="/run/user/$USER_UID" \
bash -c "$(declare -f restart_services); restart_services"
else
restart_services
fi

View File

@@ -0,0 +1,3 @@
#!/bin/bash
nomarchy-restart-app waybar

View File

@@ -0,0 +1,15 @@
#!/bin/bash
# Display brightness level using SwayOSD on the current monitor.
# Usage: nomarchy-swayosd-brightness <percent>
percent="$1"
progress="$(awk -v p="$percent" 'BEGIN{printf "%.2f", p/100}')"
[[ $progress == "0.00" ]] && progress="0.01"
swayosd-client \
--monitor "$(hyprctl monitors -j | jq -r '.[]|select(.focused==true).name')" \
--custom-icon display-brightness \
--custom-progress "$progress" \
--custom-progress-text "${percent}%"

View File

@@ -0,0 +1,15 @@
#!/bin/bash
# Display keyboard brightness level using SwayOSD on the current monitor.
# Usage: nomarchy-swayosd-kbd-brightness <percent>
percent="$1"
progress="$(awk -v p="$percent" 'BEGIN{printf "%.2f", p/100}')"
[[ $progress == "0.00" ]] && progress="0.01"
swayosd-client \
--monitor "$(hyprctl monitors -j | jq -r '.[]|select(.focused==true).name')" \
--custom-icon keyboard-brightness \
--custom-progress "$progress" \
--custom-progress-text "${percent}%"

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Toggles the screensaver availability.
# Hybrid: updates state.json and runs env-update for persistence.
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_SCREENSAVER == "false" ]]; then
NEW_VALUE="true"
notify-send -u low " Screensaver enabled"
else
NEW_VALUE="false"
notify-send -u low " Screensaver disabled"
fi
TMP_JSON=$(mktemp)
jq --argjson val "$NEW_VALUE" '.screensaver = $val' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
echo "Screensaver state set to $NEW_VALUE. Updating environment..."
env-update

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Toggles the waybar top bar.
# 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_WAYBAR == "false" ]]; then
NEW_VALUE="true"
uwsm-app -- waybar >/dev/null 2>&1 &
notify-send -u low " Top bar enabled"
else
NEW_VALUE="false"
pkill -x waybar
notify-send -u low " Top bar disabled"
fi
TMP_JSON=$(mktemp)
jq --argjson val "$NEW_VALUE" '.waybar = $val' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
echo "Waybar state set to $NEW_VALUE. Environment will be fully updated on next rebuild."

View File

@@ -0,0 +1,174 @@
{
"reload_style_on_change": true,
"layer": "top",
"position": "top",
"spacing": 0,
"height": 26,
"modules-left": ["custom/nomarchy", "hyprland/workspaces"],
"modules-center": ["clock", "custom/update", "custom/voxtype", "custom/screenrecording-indicator", "custom/idle-indicator", "custom/notification-silencing-indicator"],
"modules-right": [
"group/tray-expander",
"bluetooth",
"network",
"pulseaudio",
"cpu",
"battery"
],
"hyprland/workspaces": {
"on-click": "activate",
"format": "{icon}",
"format-icons": {
"default": "",
"1": "1",
"2": "2",
"3": "3",
"4": "4",
"5": "5",
"6": "6",
"7": "7",
"8": "8",
"9": "9",
"10": "0",
"active": "󱓻"
},
"persistent-workspaces": {
"1": [],
"2": [],
"3": [],
"4": [],
"5": []
}
},
"custom/nomarchy": {
"format": "<span font='nomarchy'>a</span>",
"on-click": "nomarchy-menu",
"on-click-right": "xdg-terminal-exec",
"tooltip-format": "Nomarchy Menu\n\nSuper + Alt + Space"
},
"custom/update": {
"format": "",
"exec": "nomarchy-update-available",
"on-click": "nomarchy-launch-floating-terminal-with-presentation nomarchy-update",
"tooltip-format": "Nomarchy update available",
"signal": 7,
"interval": 21600
},
"cpu": {
"interval": 5,
"format": "󰍛",
"on-click": "nomarchy-launch-or-focus-tui btop",
"on-click-right": "alacritty"
},
"clock": {
"format": "{:L%A %H:%M}",
"format-alt": "{:L%d %B W%V %Y}",
"tooltip": false,
"on-click-right": "nomarchy-launch-floating-terminal-with-presentation nomarchy-tz-select"
},
"network": {
"format-icons": ["󰤯", "󰤟", "󰤢", "󰤥", "󰤨"],
"format": "{icon}",
"format-wifi": "{icon}",
"format-ethernet": "󰀂",
"format-disconnected": "󰤮",
"tooltip-format-wifi": "{essid} ({frequency} GHz)\n⇣{bandwidthDownBytes} ⇡{bandwidthUpBytes}",
"tooltip-format-ethernet": "⇣{bandwidthDownBytes} ⇡{bandwidthUpBytes}",
"tooltip-format-disconnected": "Disconnected",
"interval": 3,
"spacing": 1,
"on-click": "nomarchy-launch-wifi"
},
"battery": {
"format": "{capacity}% {icon}",
"format-discharging": "{icon}",
"format-charging": "{icon}",
"format-plugged": "",
"format-icons": {
"charging": ["󰢜", "󰂆", "󰂇", "󰂈", "󰢝", "󰂉", "󰢞", "󰂊", "󰂋", "󰂅"],
"default": ["󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"]
},
"format-full": "󰂅",
"tooltip-format-discharging": "{power:>1.0f}W↓ {capacity}%",
"tooltip-format-charging": "{power:>1.0f}W↑ {capacity}%",
"interval": 5,
"on-click": "nomarchy-menu power",
"states": {
"warning": 20,
"critical": 10
}
},
"bluetooth": {
"format": "",
"format-off": "󰂲",
"format-disabled": "󰂲",
"format-connected": "󰂱",
"format-no-controller": "",
"tooltip-format": "Devices connected: {num_connections}",
"on-click": "nomarchy-launch-bluetooth"
},
"pulseaudio": {
"format": "{icon}",
"on-click": "nomarchy-launch-audio",
"on-click-right": "pamixer -t",
"tooltip-format": "Playing at {volume}%",
"scroll-step": 5,
"format-muted": "",
"format-icons": {
"headphone": "",
"headset": "",
"default": ["", "", ""]
}
},
"group/tray-expander": {
"orientation": "inherit",
"drawer": {
"transition-duration": 600,
"children-class": "tray-group-item"
},
"modules": ["custom/expand-icon", "tray"]
},
"custom/expand-icon": {
"format": "",
"tooltip": false,
"on-scroll-up": "",
"on-scroll-down": "",
"on-scroll-left": "",
"on-scroll-right": ""
},
"custom/screenrecording-indicator": {
"on-click": "nomarchy-cmd-screenrecord",
"exec": "$OMARCHY_PATH/default/waybar/indicators/screen-recording.sh",
"signal": 8,
"return-type": "json"
},
"custom/idle-indicator": {
"on-click": "nomarchy-toggle-idle",
"exec": "$OMARCHY_PATH/default/waybar/indicators/idle.sh",
"signal": 9,
"return-type": "json"
},
"custom/notification-silencing-indicator": {
"on-click": "nomarchy-toggle-notification-silencing",
"exec": "~/.config/nomarchy/default/waybar/indicators/notification-silencing.sh",
"signal": 10,
"return-type": "json"
},
"custom/voxtype": {
"exec": "nomarchy-voxtype-status",
"return-type": "json",
"format": "{icon}",
"format-icons": {
"idle": "",
"recording": "󰍬",
"transcribing": "󰔟"
},
"tooltip": true,
"on-click-right": "nomarchy-voxtype-config",
"on-click": "nomarchy-voxtype-model"
},
"tray": {
"icon-size": 12,
"spacing": 17
}
}

View File

@@ -0,0 +1,100 @@
@import "../nomarchy/current/theme/waybar.css";
* {
background-color: @background;
color: @foreground;
border: none;
border-radius: 0;
min-height: 0;
font-family: 'JetBrainsMono Nerd Font';
font-size: 12px;
}
.modules-left {
margin-left: 8px;
}
.modules-right {
margin-right: 8px;
}
#workspaces button {
all: initial;
padding: 0 6px;
margin: 0 1.5px;
min-width: 9px;
}
#workspaces button.empty {
opacity: 0.5;
}
#cpu,
#battery,
#pulseaudio,
#custom-nomarchy,
#custom-update {
min-width: 12px;
margin: 0 7.5px;
}
#tray {
margin-right: 16px;
}
#bluetooth {
margin-right: 17px;
}
#network {
margin-right: 13px;
}
#custom-expand-icon {
margin-right: 18px;
}
tooltip {
padding: 2px;
}
#custom-update {
font-size: 10px;
}
#clock {
margin-left: 8.75px;
}
.hidden {
opacity: 0;
}
#custom-screenrecording-indicator,
#custom-idle-indicator,
#custom-notification-silencing-indicator {
min-width: 12px;
margin-left: 5px;
margin-right: 0;
font-size: 10px;
padding-bottom: 1px;
}
#custom-screenrecording-indicator.active {
color: #a55555;
}
#custom-idle-indicator.active,
#custom-notification-silencing-indicator.active {
color: #a55555;
}
#custom-voxtype {
min-width: 12px;
margin: 0 0 0 7.5px;
}
#custom-voxtype.recording {
color: #a55555;
}

View File

@@ -0,0 +1,32 @@
{ config, pkgs, lib, ... }:
let
activeTheme = config.nomarchy.theme;
# Path to local theme files
themeDir = ./themes + "/${activeTheme}";
hasThemeConfig = builtins.pathExists (themeDir + "/config.jsonc");
hasThemeStyle = builtins.pathExists (themeDir + "/style.css");
# Default fallback files
defaultConfig = ./config/config.jsonc;
defaultStyle = ./config/style.css;
# Selected files
configFile = if hasThemeConfig then (themeDir + "/config.jsonc") else defaultConfig;
styleFile = if hasThemeStyle then (themeDir + "/style.css") else defaultStyle;
in
{
programs.waybar = {
enable = lib.mkDefault true;
systemd.enable = lib.mkDefault true;
settings = lib.mkDefault [ (builtins.fromJSON (builtins.readFile configFile)) ];
style = lib.mkDefault (builtins.readFile styleFile);
};
home.packages = lib.mkDefault (with pkgs; [
font-awesome
]);
}

View File

@@ -0,0 +1,2 @@
@define-color foreground #cdd6f4;
@define-color background #181824;

View File

@@ -0,0 +1,2 @@
@define-color foreground #d6e2ee;
@define-color background #213442;

View File

@@ -0,0 +1,3 @@
@define-color bg #00172e;
@define-color foreground #f6dcac;
@define-color background alpha(@bg, 0.8);

View File

@@ -0,0 +1,122 @@
{
"margin-top": 0,
"margin-left": 120,
"margin-bottom": 0,
"margin-right": 120,
"height": 61,
"layer": "top",
"position": "top",
"spacing": 15,
"modules-left": ["custom/launcher", "clock", "clock#date"],
"modules-center": ["hyprland/workspaces"],
"modules-right": ["idle_inhibitor", "pulseaudio", "custom/battery", "backlight", "tray", "custom/powermenu"],
"hyprland/workspaces": {
"disable-scroll": true,
"all-outputs": true,
"on-click": "activate",
"on-scroll-up": "hyprctl dispatch workspace r+1",
"on-scroll-down": "hyprctl dispatch workspace r-1",
"persistent_workspaces": {
"1": [],
"2": [],
"3": [],
"4": [],
"5": [],
"6": [],
"7": [],
"8": [],
"9": [],
"10": []
}
},
"tray": {
"icon-size": 20,
"spacing": 5
},
"custom/launcher": {
"interval": "once",
"format": "",
"on-click": "pkill rofi || rofi -show drun",
"tooltip-format": "Application Launcher"
},
"backlight": {
"max-length": "4",
"format": "{icon} {percent}%",
"tooltip-format": "{percent}%",
"format-icons": ["󱩎","󱩏","󱩐","󱩑", "󱩓", "󱩔", "󰛨"],
"on-click": "",
"on-scroll-up": "brightnessctl set 10%-",
"on-scroll-down": "brightnessctl set +10%"
},
"memory": {
"interval": 30,
"format": " {}%",
"format-alt":" {used:0.1f}G",
"max-length": 10
},
"idle_inhibitor": {
"format": "{icon}",
"format-icons": {
"activated": " 󰅶 ",
"deactivated": " 󰾪 "
}
},
"pulseaudio": {
"scroll-step": 5,
"format": "{icon} {volume}%",
"format-bluetooth": "{icon} {volume}%",
"format-bluetooth-muted": " {icon}",
"format-muted": " muted",
"format-icons": {
"headphone": "",
"hands-free": "",
"headset": "",
"phone": "",
"portable": "",
"car": "",
"default": ["", "", ""]
},
"on-click": "mute-vol",
"on-click-middle": "audio-menu",
"on-click-right": "pavucontrol",
"tooltip-format": "{icon} {desc} | {volume}%"
},
"network": {
"format-wifi": " {signalStrength}%",
"format-ethernet": " {signalStrength}%",
"format-disconnected": "󰤭",
"on-click": ""
},
"custom/battery": {
"interval": 30,
"format": "{}",
"exec": "waybar-battery-status",
"on-click": "power-menu"
},
"clock": {
"format": " {:%H:%M}",
"tooltip": false
},
"clock#date": {
"format": " {:%A, %B %d, %Y}",
"on-click": "kitty calcurse",
"tooltip-format": "Open calendar"
},
"custom/powermenu": {
"format": "",
"on-click": "wlogout",
"tooltip": false
}
}

View File

@@ -0,0 +1,122 @@
@define-color bg_dim #232a2e;
@define-color bg0 #2d353b;
@define-color bg1 #343f44;
@define-color bg2 #3d484d;
@define-color bg3 #475258;
@define-color bg4 #4f585e;
@define-color bg5 #56635f;
@define-color bg_visual #543a48;
@define-color bg_red #514045;
@define-color bg_green #425047;
@define-color bg_blue #3a515d;
@define-color bg_yellow #4d4c43;
@define-color fg #d3c6aa;
@define-color red #e67e80;
@define-color orange #e69875;
@define-color yellow #dbbc7f;
@define-color green #a7c080;
@define-color aqua #83c092;
@define-color blue #7fbbb3;
@define-color purple #d699b6;
@define-color grey0 #7a8478;
@define-color grey1 #859289;
@define-color grey2 #9da9a0;
/* margin: top right bottom left */
/* Spacing outside the element */
/* padding: top right bottom left */
/* Spacing inside the element */
* {
font-family: JetBrainsMono Nerd Font, FontAwesome;
font-size: 13px;
font-weight: bold;
}
window#waybar {
background-color: @fg;
color: @bg0;
transition-property: background-color;
transition-duration: 0.5s;
border-radius: 0px 0px 15px 15px;
transition-duration: .5s;
border-bottom-width: 5px;
border-bottom-color: #7d6a40;
border-bottom-style: solid;
}
#backlight,
#tray,
#custom-launcher,
#clock,
#clock-date,
#workspaces,
#pulseaudio,
#idle_inhibitor,
#custom-battery,
#custom-powermenu {
background-color: @bg0;
color: @fg;
padding-left: 10px;
padding-right: 10px;
margin-top: 7px;
margin-bottom: 12px;
border-radius: 10px;
border-bottom-width: 5px;
border-bottom-color: #161a1d;
border-bottom-style: solid;
}
#tray > .active {
color: @fg;
}
#tray > .needs-attention {
color: @green;
}
#workspaces {
padding: 0px;
}
#workspaces button.active {
background-color: @blue;
color: @bg0;
border-radius: 10px;
margin-bottom: -5px;
border-bottom-width: 5px;
border-bottom-color: #366660;
border-bottom-style: solid;
}
#custom-launcher {
background-color: @green;
color: @bg0;
border-bottom-color: #556a35;
margin-left: 15px;
padding-left: 20px;
padding-right: 21px;
font-size: 20px;
}
#custom-powermenu {
background-color: @red;
color: @bg0;
border-bottom-color: #951c1f;
margin-right: 15px;
padding-left: 20px;
padding-right: 23px;
}
#custom-battery.preservation {
color: @green;
}
#idle_inhibitor {
padding-left: 10px;
padding-right: 10px;
font-size: 20px;
}

View File

@@ -0,0 +1,34 @@
{ pkgs, ... }:
{
systemd.user.services.nomarchy-battery-monitor = {
Unit = {
Description = "Nomarchy Battery Monitor Check";
After = [ "graphical-session.target" ];
};
Service = {
Type = "oneshot";
# The script is packaged in nomarchy-scripts which is in the home profile
ExecStart = "nomarchy-battery-monitor";
Environment = [ "DISPLAY=:0" ];
LogLevelMax = "warning";
};
};
systemd.user.timers.nomarchy-battery-monitor = {
Unit = {
Description = "Nomarchy Battery Monitor Timer";
};
Timer = {
OnBootSec = "1min";
OnUnitActiveSec = "30sec";
AccuracySec = "10sec";
};
Install = {
WantedBy = [ "timers.target" ];
};
};
}

View File

@@ -0,0 +1,156 @@
{ config, pkgs, lib, ... }:
let
# Core dependencies needed by most scripts (minimal set)
coreDeps = with pkgs; [
coreutils
gnused
gnugrep
findutils
gawk
jq
];
# Category-specific dependencies
categoryDeps = with pkgs; {
appearance = [
swww
libnotify
];
apps = [
hyprland
libnotify
];
hardware = [
brightnessctl
playerctl
pamixer
pciutils
libnotify
];
system = [
gum
libnotify
curl
wget
];
utils = [
gum
hyprland
libnotify
wl-clipboard
grim
slurp
xmlstarlet
curl
wget
];
wm = [
hyprland
swayosd
libnotify
procps
];
};
# All dependencies combined (for scripts that might call others)
allDeps = coreDeps ++ (lib.unique (lib.flatten (builtins.attrValues categoryDeps)));
# Environment variables to inject
envVars = {
NOMARCHY_TOGGLE_SUSPEND = if config.nomarchy.toggles.suspend then "true" else "false";
NOMARCHY_TOGGLE_SCREENSAVER = if config.nomarchy.toggles.screensaver then "true" else "false";
NOMARCHY_TOGGLE_IDLE = if config.nomarchy.toggles.idle then "true" else "false";
NOMARCHY_TOGGLE_NIGHTLIGHT = if config.nomarchy.toggles.nightlight then "true" else "false";
NOMARCHY_TOGGLE_WAYBAR = if config.nomarchy.toggles.waybar then "true" else "false";
NOMARCHY_TOGGLE_SKIP_VSCODE_THEME = if config.nomarchy.toggles.skipVsCodeTheme then "true" else "false";
NOMARCHY_MONOSPACE_FONT = config.nomarchy.fonts.monospace;
};
# Build wrapper arguments for environment variables
envWrapperArgs = lib.concatStringsSep " " (
lib.mapAttrsToList (name: value: "--set ${name} \"${value}\"") envVars
);
nomarchy-scripts = pkgs.stdenv.mkDerivation {
pname = "nomarchy-scripts";
version = "1.0.0";
src = ../../bin;
nativeBuildInputs = [ pkgs.makeWrapper ];
installPhase = ''
mkdir -p $out/bin
# Copy scripts preserving directory structure for category detection
for category in appearance apps hardware system utils wm; do
if [ -d "$category" ]; then
for script in "$category"/*; do
if [ -f "$script" ]; then
cp "$script" "$out/bin/"
fi
done
fi
done
chmod +x $out/bin/*
patchShebangs $out/bin
'';
postFixup = ''
# Wrap scripts with category-specific dependencies
for file in $out/bin/*; do
if [ -f "$file" ]; then
scriptName=$(basename "$file")
# Determine category from original source location
category=""
for cat in appearance apps hardware system utils wm; do
if [ -f "$src/$cat/$scriptName" ]; then
category="$cat"
break
fi
done
# Select dependencies based on category
case "$category" in
appearance)
deps="${lib.makeBinPath (coreDeps ++ categoryDeps.appearance)}"
;;
apps)
deps="${lib.makeBinPath (coreDeps ++ categoryDeps.apps)}"
;;
hardware)
deps="${lib.makeBinPath (coreDeps ++ categoryDeps.hardware)}"
;;
system)
deps="${lib.makeBinPath (coreDeps ++ categoryDeps.system)}"
;;
utils)
deps="${lib.makeBinPath (coreDeps ++ categoryDeps.utils)}"
;;
wm)
deps="${lib.makeBinPath (coreDeps ++ categoryDeps.wm)}"
;;
*)
# Fallback to all deps for unknown categories
deps="${lib.makeBinPath allDeps}"
;;
esac
wrapProgram "$file" \
--prefix PATH : "$deps" \
${envWrapperArgs}
fi
done
'';
};
in
{
home.packages = [ nomarchy-scripts ];
}

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# Build the Nomarchy Installer ISO declaratively using the flake.
echo "Building Nomarchy Installer ISO..."
# The output will be a symlink named 'result' in the current directory
nix build .#nixosConfigurations.installerIso.config.system.build.isoImage
if [ $? -eq 0 ]; then
ISO_PATH=$(readlink -f result/iso/*.iso)
echo "Success! ISO built at: $ISO_PATH"
echo "You can now burn this to a USB drive using 'dd' or 'etcher'."
else
echo "Error: ISO build failed."
exit 1
fi

View File

@@ -0,0 +1,66 @@
#!/bin/bash
# Switch between audio outputs while preserving the mute status. By default mapped to Super + Mute.
focused_monitor="$(hyprctl monitors -j | jq -r '.[] | select(.focused == true).name')"
sinks=$(pactl -f json list sinks | jq '[.[] | select((.ports | length == 0) or ([.ports[]? | .availability != "not available"] | any))]')
sinks_count=$(echo "$sinks" | jq '. | length')
if (( sinks_count == 0 )); then
swayosd-client \
--monitor "$focused_monitor" \
--custom-message "No audio devices found"
exit 1
fi
current_sink_name=$(pactl get-default-sink)
current_sink_index=$(echo "$sinks" | jq -r --arg name "$current_sink_name" 'map(.name) | index($name)')
if [[ $current_sink_index != "null" ]]; then
next_sink_index=$(((current_sink_index + 1) % sinks_count))
else
next_sink_index=0
fi
next_sink=$(echo "$sinks" | jq -r ".[$next_sink_index]")
next_sink_name=$(echo "$next_sink" | jq -r '.name')
next_sink_description=$(echo "$next_sink" | jq -r '.description')
if [[ $next_sink_description == "(null)" ]] || [[ $next_sink_description == "null" ]] || [[ -z $next_sink_description ]]; then
# For Bluetooth devices, the friendly name is on the Device entry (device.id), not the Sink entry (object.id)
device_id=$(echo "$next_sink" | jq -r '.properties."device.id"')
if [[ $device_id != "null" ]] && [[ -n $device_id ]]; then
next_sink_description=$(wpctl status | grep -E "^\s*│?\s+${device_id}\." | sed -E 's/^.*[0-9]+\.\s+//' | sed -E 's/\s+\[.*$//')
fi
# Fall back to object.id lookup if device.id didn't yield a result
if [[ -z $next_sink_description ]]; then
sink_id=$(echo "$next_sink" | jq -r '.properties."object.id"')
next_sink_description=$(wpctl status | grep -E "\s+\*?\s+${sink_id}\." | sed -E 's/^.*[0-9]+\.\s+//' | sed -E 's/\s+\[.*$//')
fi
fi
next_sink_volume=$(echo "$next_sink" | jq -r \
'.volume | to_entries[0].value.value_percent | sub("%"; "")')
next_sink_is_muted=$(echo "$next_sink" | jq -r '.mute')
if [[ $next_sink_is_muted = "true" ]] || (( next_sink_volume == 0 )); then
icon_state="muted"
elif (( next_sink_volume <= 33 )); then
icon_state="low"
elif (( next_sink_volume <= 66 )); then
icon_state="medium"
else
icon_state="high"
fi
next_sink_volume_icon="sink-volume-${icon_state}-symbolic"
if [[ $next_sink_name != $current_sink_name ]]; then
pactl set-default-sink "$next_sink_name"
fi
swayosd-client \
--monitor "$focused_monitor" \
--custom-message "$next_sink_description" \
--custom-icon "$next_sink_volume_icon"

Some files were not shown because too many files have changed in this diff Show More