replace the old dotfiles with the sakura line rice

The bare $HOME repo is gone. This is now the rice: install.sh, rice-*
scripts in bin/, configs in home/ linked into $HOME, theme templates, st
and the particle wallpaper. Clone to ~/rice and run install.sh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BLLuuQfeDiqFHHSgd2VWrQ
This commit is contained in:
Sarthak 2026-09-14 16:12:09 +05:30
commit 4b203d06bd
104 changed files with 8077 additions and 0 deletions

68
home/.config/bspwm/bspwmrc Executable file
View file

@ -0,0 +1,68 @@
#!/usr/bin/env bash
# Sakura Line - bspwm.
# Colours, border width and gaps come from the theme (rice-theme); everything
# that runs for the session is started by rice-session. Machine-specific extras
# (autostarted apps, per-app desktops) go in ~/.config/rice/local.sh, which is
# not part of the repo.
export PATH="$HOME/.local/bin:$PATH"
export XDG_CURRENT_DESKTOP=bspwm
export QT_QPA_PLATFORMTHEME=qt6ct
export GTK_THEME=Adwaita:dark
[[ -f $HOME/.env ]] && . "$HOME/.env"
theme="${XDG_STATE_HOME:-$HOME/.local/state}/rice/theme"
# ---------------------------------------------------------------- monitors
primary=$(xrandr --query | awk '/ connected primary/ {print $1; exit}')
[[ -z $primary ]] && primary=$(bspc query -M --names | head -n1)
bspc monitor "$primary" -d 1 2 3 4 5 6
# Run the primary output at its highest mode and refresh rate.
mode=$(xrandr --query | awk -v m="$primary" '$1 == m {getline; print $1; exit}')
rate=$(xrandr --query | awk -v m="$primary" '$1 == m {getline; for (i = 2; i <= NF; i++) { r = $i; gsub(/[*+]/, "", r); if (r + 0 > best + 0) best = r }; print best; exit}')
[[ -n $mode && -n $rate ]] && xrandr --output "$primary" --mode "$mode" --rate "$rate"
# ------------------------------------------------------------------ layout
bspc config focus_follows_pointer true
bspc config borderless_monocle true
bspc config gapless_monocle false
bspc config split_ratio 0.52
bspc config automatic_scheme longest_side
bspc config initial_polarity second_child
bspc config pointer_modifier mod4
bspc config pointer_action1 move
bspc config pointer_action2 resize_side
bspc config pointer_action3 resize_corner
# colours, border width, gaps
[[ -f $theme/bspwm-colors.sh ]] && . "$theme/bspwm-colors.sh"
# ------------------------------------------------------------------- rules
# Restarting bspwm re-runs this file, so start from a clean rule list.
bspc rule -r '*'
for app in feh imv mpv vlc Yad Zenity Xarchiver ark Blueman-manager; do
bspc rule -a "$app" state=floating center=on
done
bspc rule -a Pavucontrol state=floating center=on rectangle=920x450+0+0
bspc rule -a qt5ct state=floating center=on rectangle=900x600+0+0
bspc rule -a qt6ct state=floating center=on rectangle=900x600+0+0
bspc rule -a Gnome-calculator state=floating center=on rectangle=360x500+0+0
bspc rule -a loupe state=floating center=on rectangle=1200x800+0+0
bspc rule -a '*:sun-awt-X11-XWindowPeer' manage=off
# rice surfaces
bspc rule -a rice-float state=floating center=on rectangle=900x560+0+0
bspc rule -a rice-about state=floating center=on rectangle=760x440+0+0
bspc rule -a rice-scratchpad state=floating sticky=on layer=above
bspc rule -a '*:rice-webcam' state=floating sticky=on layer=above border=off focus=off
bspc rule -a boomer state=fullscreen
# ------------------------------------------------------------------ session
xsetroot -cursor_name left_ptr
wmname LG3D
[[ -f $HOME/.config/rice/local.sh ]] && . "$HOME/.config/rice/local.sh"
rice-session start &

286
home/.config/btop/btop.conf Normal file
View file

@ -0,0 +1,286 @@
#? Config file for btop v.1.4.7
#* 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 = "rice"
#* If the theme set background should be shown, set to False if you want terminal background transparency.
theme_background = false
#* 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
#* Option to disable presets. Either the default preset, custom presets, or all presets.
#* "Off" All presets are enabled.
#* "Default" preset is disabled.#* "Custom" presets are disabled.#* "All" presets are disabled.
disable_presets = "Off"
#* 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 = false
#* Disable all mouse events.
disable_mouse = false
#* Rounded corners on boxes, is ignored if TTY mode is ON.
rounded_corners = true
#* Use terminal synchronized output sequences to reduce flickering on supported terminals.
terminal_sync = 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 = 1000
#* 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
#* Should the process list follow the selected process when detailed view is open.
proc_follow_detailed = true
#* In tree-view, always accumulate child process resources in the parent process.
proc_aggregate = false
#* Should cpu and memory usage display be preserved for dead processes when paused.
keep_dead_proc_usage = 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 = "total"
#* 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
#* Shows the CPU package current power consumption in watts. Requires running `make setcap` or `make setuid` or running with sudo.
show_cpu_watts = 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
#* How to calculate CPU frequency, available values: "first", "range", "lowest", "highest" and "average".
freq_mode = "first"
#* 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 = "%H:%M"
#* 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 " ".
#* Only disks matching the filter will be shown. Prepend exclude= to only show disks not matching the filter. Examples: disk_filter="/boot /home/user", 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 = ""
#* Swap the positions of the upload and download speed graphs. When true, upload will be on top.
swap_upload_download = false
#* 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 = ""
#* "True" shows bitrates in base 10 (Kbps, Mbps). "False" shows bitrates in binary sizes (Kibps, Mibps, etc.). "Auto" uses base_10_sizes.
base_10_bitrate = "Auto"
#* 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"
#* Show power stats of battery next to charge indicator.
show_battery_watts = true
#* Set loglevel for "~/.local/state/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"
#* Automatically save current settings to config file on exit.
save_config_on_exit = false
#* Measure PCIe throughput on NVIDIA cards, may impact performance on certain cards.
nvml_measure_pcie_speeds = true
#* Measure PCIe throughput on AMD cards, may impact performance on certain cards.
rsmi_measure_pcie_speeds = true
#* Horizontally mirror the GPU graph.
gpu_mirror_graph = true
#* Set which GPU vendors to show. Available values are "nvidia amd intel apple"
shown_gpus = "nvidia amd intel apple"
#* 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 = ""

102
home/.config/dunst/dunstrc Normal file
View file

@ -0,0 +1,102 @@
# Sakura Line - dunst behaviour. Colours and fonts come from the theme drop-in
# (dunstrc.d/50-theme.conf -> ~/.local/state/rice/theme/dunst.conf).
[global]
monitor = 0
follow = mouse
origin = top-right
# Same outer edges as tiled windows and popups. bspwm draws borders inside the
# gap, so that is one gap (10px) in from the right and bar (37px) + gap down.
offset = (10, 47)
width = (300, 400)
height = (0, 300)
notification_limit = 5
gap_size = 6
padding = 12
horizontal_padding = 14
text_icon_padding = 12
frame_width = 1
corner_radius = 12
progress_bar = true
progress_bar_height = 6
progress_bar_frame_width = 0
progress_bar_min_width = 150
progress_bar_max_width = 372
progress_bar_corner_radius = 3
indicate_hidden = yes
sort = urgency_descending
idle_threshold = 120
line_height = 2
markup = full
format = "<b>%s</b>\n%b"
alignment = left
vertical_alignment = center
show_age_threshold = 60
ellipsize = middle
word_wrap = yes
ignore_newline = no
stack_duplicates = true
hide_duplicate_count = false
show_indicators = no
enable_recursive_icon_lookup = true
icon_position = left
min_icon_size = 32
max_icon_size = 48
sticky_history = yes
history_length = 50
always_run_script = true
dmenu = /usr/bin/rofi -dmenu -theme ~/.config/rofi/menu.rasi -p "Actions"
browser = /usr/bin/xdg-open
title = Dunst
class = Dunst
default_pause_level = 0
mouse_left_click = do_action, close_current
mouse_middle_click = close_all
mouse_right_click = close_current
[urgency_low]
timeout = 4
[urgency_normal]
timeout = 6
[urgency_critical]
timeout = 0
# Do-not-disturb (rice-notify dnd) sets pause level 60. Your own confirmations
# and critical alerts sent from the command line still get through; chat apps
# that mark everything critical use their own app name, so they stay quiet.
[rice_feedback]
appname = "rice"
override_pause_level = 100
[critical_cli]
appname = "notify-send"
msg_urgency = critical
override_pause_level = 100
# Volume, brightness and recording progress: replaced in place, never kept.
[osd]
appname = "osd"
format = "<b>%s</b>"
icon_position = off
timeout = 2
history_ignore = yes
override_pause_level = 100
# Every other notification pokes the notification centre so its count and list stay live.
[notify_centre]
appname = "*"
script = ~/.local/bin/rice-notify-hook
[fullscreen_delay]
fullscreen = delay
[fullscreen_critical]
msg_urgency = critical
fullscreen = show
[flameshot_noise]
body = "grim's screenshot component is implemented based on wlroots*"
skip_display = true
history_ignore = yes

217
home/.config/eww/eww.scss Normal file
View file

@ -0,0 +1,217 @@
// Sakura Line - eww design system.
// The same tokens as polybar, rofi and dunst: the theme's alpha, 1px border,
// 12px radius, rows with an 8px radius and a 17% accent tint on hover.
// colors.scss is a link to ~/.local/state/rice/theme/eww-colors.scss.
@import "colors";
$tint: rgba($accent, 0.17);
* {
all: unset;
font-family: $font, "Symbols Nerd Font";
}
window, .closer { background-color: transparent; }
tooltip {
background-color: rgba($bg, 0.95);
border: $border-width solid $border;
border-radius: $radius-small;
padding: 6px 10px;
color: $fg;
font-size: 11px;
}
// ------------------------------------------------------------------ panel
.panel {
background-color: rgba($bg, $alpha);
border: $border-width solid $border;
border-radius: $radius;
padding: 4px 8px 10px 8px;
color: $fg;
font-size: 13px;
}
.head {
padding: 10px 10px 12px 10px;
margin-bottom: 6px;
border-bottom: $border-width solid $border;
}
.title { font-weight: bold; font-size: 14px; }
.head-action {
color: $dim;
font-size: 15px;
padding: 2px 7px;
margin-left: 2px;
border-radius: $radius-small;
}
.head-action:hover { color: $fg; background-color: $tint; }
.head-action.active { color: $accent; }
.section {
color: $dim;
font-size: 11px;
font-weight: 600;
padding: 10px 10px 4px 10px;
}
.sep {
min-height: 1px;
margin: 6px 4px;
background-color: $border;
}
.empty { color: $dim; padding: 8px 10px; }
// -------------------------------------------------------------------- rows
.row {
padding: 8px 10px;
border-radius: $radius-small;
}
.row:hover { background-color: $tint; }
.row.active .row-title, .row.active .glyph { color: $accent; }
.row.dimmed .row-title, .row.dimmed .glyph { color: $dim; }
.glyph { font-size: 15px; min-width: 24px; margin-right: 10px; }
.row-title { color: $fg; }
.row-sub { color: $dim; font-size: 11px; margin-top: 1px; }
.row-value { color: $dim; margin-left: 10px; }
.chev { color: $dim; margin-left: 8px; font-size: 15px; }
.accent { color: $accent; }
.urgent { color: $urgent; }
.good { color: $green; }
.warn { color: $yellow; }
.dim { color: $dim; }
// ---------------------------------------------------------------- controls
.slider-row:hover { background-color: transparent; }
.slider-row .glyph { padding: 2px 0; border-radius: $radius-small; }
.slider-row .glyph:hover { color: $accent; }
.slider-value { min-width: 48px; }
scale trough {
min-height: 6px;
min-width: 140px;
margin: 0 4px;
border-radius: 3px;
background-color: $selection;
}
scale highlight {
border-radius: 3px;
background-color: $accent;
}
scale slider {
min-width: 12px;
min-height: 12px;
margin: -3px;
border-radius: 6px;
background-color: $fg;
}
.switch {
min-width: 30px;
min-height: 16px;
padding: 2px;
border-radius: 10px;
background-color: $selection;
}
.switch.on { background-color: rgba($accent, 0.55); }
.knob {
min-width: 12px;
min-height: 12px;
border-radius: 6px;
background-color: $dim;
}
.switch.on .knob { background-color: $fg; }
.pill {
padding: 4px 10px;
margin: 0 3px;
border-radius: $radius-small;
color: $dim;
}
.pills { padding: 2px 6px 6px 6px; }
.pill:hover { color: $fg; background-color: $tint; }
.pill.active { color: $accent; background-color: $tint; }
.field {
padding: 8px 10px;
border-radius: $radius-small;
background-color: rgba($surface, 0.8);
color: $fg;
caret-color: $accent;
}
progressbar trough {
min-height: 4px;
border-radius: 2px;
background-color: $selection;
}
progressbar progress {
min-height: 4px;
border-radius: 2px;
background-color: $accent;
}
// ---------------------------------------------------------------- calendar
.cal-title { font-weight: bold; }
.cal-grid { margin: 4px 4px 2px 4px; }
.cal-dow, .cal-wk { color: $dim; font-size: 10px; font-weight: bold; min-height: 22px; }
.cal-wk { min-width: 26px; }
.cal-day {
min-width: 34px;
min-height: 30px;
border-radius: $radius-small;
color: $fg;
}
.cal-day:hover { background-color: $tint; }
.cal-day.other { color: $muted; }
.cal-day.weekend { color: $fg-alt; }
.cal-day.selected { background-color: $tint; color: $accent; }
.cal-day.today { background-color: $accent; color: $bg; font-weight: bold; }
.cal-dot { color: $magenta; font-size: 8px; min-height: 8px; }
.cal-day.today .cal-dot { color: $bg; }
.event-time { color: $accent; min-width: 52px; font-size: 12px; }
.event-bar { min-width: 3px; border-radius: 2px; margin-right: 10px; background-color: $magenta; }
.event-bar.reminder { background-color: $yellow; }
// ------------------------------------------------------------------- media
.art {
min-width: 72px;
min-height: 72px;
border-radius: $radius-small;
background-size: cover;
background-position: center;
background-color: $surface;
}
.media-title { font-weight: bold; }
.media-artist { color: $dim; font-size: 12px; }
.media-btn {
font-size: 18px;
padding: 4px 12px;
border-radius: $radius-small;
}
.media-btn:hover { background-color: $tint; color: $accent; }
.media-time { color: $dim; font-size: 10px; }
// ----------------------------------------------------------- notifications
// The event box takes the hover tint; GTK ignores padding on event boxes,
// so the box inside it carries the padding.
.notif-hit { border-radius: $radius-small; }
.notif-hit:hover { background-color: $tint; }
.notif { padding: 8px 10px; }
.notif.critical .notif-summary { color: $urgent; }
.notif-app { color: $dim; font-size: 10px; font-weight: bold; }
.notif-summary { font-weight: bold; }
.notif-body { color: $fg-alt; font-size: 12px; }
.notif-ago { color: $dim; font-size: 10px; }
.notif-close {
color: $dim;
padding: 0 6px;
border-radius: $radius-small;
font-size: 13px;
}
.notif-close:hover { color: $urgent; background-color: rgba($urgent, 0.15); }

91
home/.config/eww/eww.yuck Normal file
View file

@ -0,0 +1,91 @@
;; Sakura Line - eww popups.
;; rice-panel opens one window at a time; every panel is built from the
;; widgets below so they all look and behave like the rofi menus.
;; ------------------------------------------------------------------ state
(defvar open_panel "") ; set by rice-panel; list polls only run while their panel is open
(defvar cal_offset 0) ; months away from the current one
(defvar cal_day "") ; selected day as YYYY-MM-DD, "" = today
(defvar cal_input "") ; text typed into the calendar's add box
(deflisten notifs :initial "{}" "rice-notify watch")
(deflisten audio :initial "{}" "rice-audio watch")
(deflisten media :initial "{}" "rice-media watch")
(defpoll net :interval "4s" :initial "{}" :run-while {open_panel == "network" || open_panel == "hub"} "rice-net list")
(defpoll bt :interval "4s" :initial "{}" :run-while {open_panel == "bluetooth" || open_panel == "hub"} "rice-bt list")
(defpoll display :interval "10s" :initial "{}" :run-while {open_panel == "display" || open_panel == "hub"} "rice-display info")
(defpoll power :interval "3s" :initial "{}" :run-while {open_panel == "power" || open_panel == "hub"} "rice-power info")
;; rice-cal reads cal_offset / cal_day itself and pushes fresh data on every click.
(defpoll month :interval "60s" :initial "{}" :run-while {open_panel == "calendar"} "rice-cal month")
(defpoll agenda :interval "30s" :initial "{}" :run-while {open_panel == "calendar"} "rice-cal agenda")
(defpoll idle :interval "3s" :initial "{}" :run-while {open_panel == "hub"} "rice-idle status")
;; ---------------------------------------------------------------- windows
;; A transparent layer under the open panel: clicking anywhere else closes it.
(defwindow closer
:monitor 0
:geometry (geometry :x "0" :y "0" :width "100%" :height "100%")
:stacking "fg"
:wm-ignore true
(eventbox :class "closer" :onclick "rice-panel close" :onrightclick "rice-panel close"))
;; -------------------------------------------------------- shared widgets
;; Header: title on the left, optional action buttons on the right.
(defwidget head [title]
(box :class "head" :space-evenly false
(label :class "title" :text title :hexpand true :xalign 0)
(children)))
(defwidget head-action [glyph onclick ?tip ?active]
(button :class "head-action ${active == true ? 'active' : ''}" :onclick onclick :tooltip {tip ?: ""} glyph))
(defwidget section [text]
(label :class "section" :xalign 0 :text text))
(defwidget sep []
(box :class "sep"))
;; One list row: glyph, title and optional subtitle, value on the right.
(defwidget row [glyph title ?sub ?value ?onclick ?active ?chev ?dim]
(button :class "row ${active == true ? 'active' : ''} ${dim == true ? 'dimmed' : ''}" :onclick {onclick ?: ""}
(box :space-evenly false
(label :class "glyph" :text glyph)
(box :orientation "v" :hexpand true :space-evenly false :valign "center"
(label :class "row-title" :xalign 0 :limit-width 30 :text title)
(label :class "row-sub" :xalign 0 :limit-width 38 :visible {(sub ?: "") != ""} :text {sub ?: ""}))
(label :class "row-value" :visible {(value ?: "") != ""} :text {value ?: ""})
(label :class "chev" :visible {chev == true} :text ""))))
;; A row with a pill switch.
(defwidget switch-row [glyph title on onclick ?sub]
(button :class "row" :onclick onclick
(box :space-evenly false
(label :class "glyph ${on == true ? 'accent' : ''}" :text glyph)
(box :orientation "v" :hexpand true :space-evenly false :valign "center"
(label :class "row-title" :xalign 0 :text title)
(label :class "row-sub" :xalign 0 :visible {(sub ?: "") != ""} :text {sub ?: ""}))
(box :class "switch ${on == true ? 'on' : ''}" :valign "center" :space-evenly false
(box :class "knob" :hexpand true :halign {on == true ? "end" : "start"})))))
;; A slider row: clicking the glyph runs onglyph (mute), dragging runs onchange.
(defwidget slider-row [glyph value onchange ?onglyph ?muted]
(box :class "row slider-row" :space-evenly false
(button :class "glyph ${muted == true ? 'urgent' : ''}" :onclick {onglyph ?: ""} glyph)
(scale :class "slider" :hexpand true :min 0 :max 101 :value {value ?: 0} :onchange onchange)
(label :class "row-value slider-value" :xalign 1 :text {muted == true ? "muted" : "${value ?: 0}%"})))
;; An empty-state line inside a list.
(defwidget empty [text]
(label :class "empty" :xalign 0 :text text))
;; ----------------------------------------------------------------- panels
(include "panels/hub.yuck")
(include "panels/audio.yuck")
(include "panels/network.yuck")
(include "panels/bluetooth.yuck")
(include "panels/display.yuck")
(include "panels/power.yuck")
(include "panels/media.yuck")
(include "panels/calendar.yuck")
(include "panels/notifications.yuck")

View file

@ -0,0 +1,3 @@
# sxhkd config that rice-panel runs only while a panel is open.
Escape
rice-panel close

View file

@ -0,0 +1,45 @@
;; Audio: output and mic levels, device pickers, per-app mixer.
(defwindow audio
:monitor 0
:geometry (geometry :x "-10px" :y "47px" :width "380px" :anchor "top right")
:stacking "fg"
:wm-ignore true
:resizable false
(box :class "panel" :orientation "v" :space-evenly false
(head :title "Audio"
(head-action :glyph "󰒓" :tip "Advanced mixer" :onclick "rice-panel close; setsid -f pavucontrol"))
(slider-row :glyph {audio?.sink?.muted == true ? "󰝟" : "󰕾"}
:value {audio?.sink?.vol ?: 0}
:muted {audio?.sink?.muted ?: false}
:onglyph "rice-volume mute"
:onchange "rice-volume set {}")
(slider-row :glyph {audio?.source?.muted == true ? "󰍭" : "󰍬"}
:value {audio?.source?.vol ?: 0}
:muted {audio?.source?.muted ?: false}
:onglyph "rice-audio mic-mute"
:onchange "rice-audio mic {}")
;; eww appends a loop's items after their siblings, so every loop gets its own box.
(section :text "Output")
(box :orientation "v" :space-evenly false
(for s in {audio?.sinks ?: []}
(row :glyph "󰓃" :title {s.desc} :active {s.default} :onclick "rice-audio sink ${s.index}")))
(section :text "Input")
(box :orientation "v" :space-evenly false
(for s in {audio?.sources ?: []}
(row :glyph "󰍬" :title {s.desc} :active {s.default} :onclick "rice-audio source ${s.index}")))
(box :orientation "v" :space-evenly false :visible {arraylength(audio?.apps ?: []) > 0}
(section :text "Apps")
(box :orientation "v" :space-evenly false
(for a in {audio?.apps ?: []}
(box :class "row slider-row" :space-evenly false
(button :class "glyph ${a.muted ? 'urgent' : ''}" :tooltip {a.name}
:onclick "rice-audio app-mute ${a.id}"
{a.muted ? "󰝟" : "󰎆"})
(label :class "row-title" :xalign 0 :width 90 :limit-width 11 :text {a.name})
(scale :class "slider" :hexpand true :min 0 :max 101 :value {a.vol}
:onchange "rice-audio app-volume ${a.id} {}")
(label :class "row-value slider-value" :xalign 1 :text "${a.vol}%")))))))

View file

@ -0,0 +1,32 @@
;; Bluetooth: power, paired devices with battery, nearby devices to pair.
(defwindow bluetooth
:monitor 0
:geometry (geometry :x "-10px" :y "47px" :width "380px" :anchor "top right")
:stacking "fg"
:wm-ignore true
:resizable false
(box :class "panel" :orientation "v" :space-evenly false
(head :title "Bluetooth"
(head-action :glyph "󰑓" :tip "Look for new devices" :active {bt?.discovering == true}
:onclick "rice-bt scan"))
(switch-row :glyph "󰂯" :title "Bluetooth" :on {bt?.powered == true}
:onclick "rice-bt power toggle"
:sub {bt?.powered != true ? "off" : (bt?.connected > 0 ? "${bt.connected} connected" : "on")})
(box :orientation "v" :space-evenly false :visible {bt?.powered == true}
(section :text "My devices")
(box :orientation "v" :space-evenly false
(for d in {bt?.paired ?: []}
(row :glyph {d.glyph} :title {d.name} :sub {d.sub} :value {d.battery}
:active {d.connected} :onclick "rice-bt toggle ${d.mac}")))
(box :visible {arraylength(bt?.paired ?: []) == 0}
(empty :text "No paired devices"))
(section :text {bt?.discovering == true ? "Nearby · scanning" : "Nearby"})
(box :orientation "v" :space-evenly false
(for d in {bt?.nearby ?: []}
(row :glyph {d.glyph} :title {d.name} :sub {d.sub} :dim true
:onclick "rice-bt toggle ${d.mac}")))
(box :visible {arraylength(bt?.nearby ?: []) == 0}
(empty :text {bt?.discovering == true ? "Looking…" : "Press 󰑓 to look for devices"})))))

View file

@ -0,0 +1,47 @@
;; Calendar: month grid with ISO weeks, the selected day's events and reminders.
(defwindow calendar
:monitor 0
:geometry (geometry :x "0" :y "47px" :width "360px" :anchor "top center")
:stacking "fg"
:wm-ignore true
:resizable false
(box :class "panel" :orientation "v" :space-evenly false
(head :title {month?.title ?: "Calendar"}
(head-action :glyph "󰅁" :tip "Previous month" :onclick "rice-cal shift -1")
(head-action :glyph "󰃭" :tip "Today" :onclick "rice-cal today")
(head-action :glyph "󰅂" :tip "Next month" :onclick "rice-cal shift 1"))
(box :class "cal-grid" :orientation "v" :space-evenly false
(box :space-evenly false
(label :class "cal-wk" :text "Wk")
(for d in {month?.dow ?: []}
(label :class "cal-dow" :hexpand true :text d)))
(for w in {month?.weeks ?: []}
(box :space-evenly false
(label :class "cal-wk" :text {w.wk})
(for d in {w.days}
(button :hexpand true :onclick "rice-cal select ${d.date}"
:class "cal-day ${d.other ? 'other' : ''} ${d.weekend ? 'weekend' : ''} ${d.selected ? 'selected' : ''} ${d.today ? 'today' : ''}"
(box :orientation "v" :space-evenly false :valign "center"
(label :text {d.d})
(label :class "cal-dot" :text {d.events > 0 ? "●" : " "})))))))
(sep)
(box :space-evenly false
(label :class "section" :hexpand true :xalign 0 :text {agenda?.heading ?: "Today"})
(head-action :glyph "󰃅" :tip "Add event" :onclick "rice-cal add")
(head-action :glyph "󰀠" :tip "Add reminder" :onclick "rice-panel close; rice-reminder new"))
(box :orientation "v" :space-evenly false
(for ev in {agenda?.items ?: []}
(box :class "row" :space-evenly false
(box :class "event-bar ${ev.kind == 'reminder' ? 'reminder' : ''}")
(label :class "event-time" :xalign 0 :text {ev.time})
(box :orientation "v" :hexpand true :space-evenly false :valign "center"
(label :class "row-title" :xalign 0 :limit-width 28 :text {ev.title})
(label :class "row-sub" :xalign 0 :limit-width 36 :visible {ev.sub != ""} :text {ev.sub}))
(button :class "notif-close" :valign "center" :visible {ev.kind == "reminder"}
:tooltip "Cancel reminder" :onclick "rice-reminder cancel ${ev.id}" "󰅖"))))
(box :orientation "v" :space-evenly false :visible {arraylength(agenda?.items ?: []) == 0}
(empty :text {(agenda?.status ?: "") != "" ? agenda.status : "Nothing planned"}))))

View file

@ -0,0 +1,30 @@
;; Display: brightness over DDC, resolution, refresh rate, bar, style shortcuts.
(defwindow display
:monitor 0
:geometry (geometry :x "-10px" :y "47px" :width "380px" :anchor "top right")
:stacking "fg"
:wm-ignore true
:resizable false
(box :class "panel" :orientation "v" :space-evenly false
(head :title "Display"
(head-action :glyph "󰸉" :tip "Wallpaper" :onclick "rice-panel close; rice-menu wallpaper")
(head-action :glyph "󰏘" :tip "Theme" :onclick "rice-panel close; rice-menu theme"))
(box :orientation "v" :space-evenly false :visible {display?.ddc == true}
(slider-row :glyph "󰃠" :value {display?.brightness ?: 0}
:onchange "rice-display brightness {}"))
(box :orientation "v" :space-evenly false :visible {display?.ddc != true}
(row :glyph "󰃠" :title "Brightness" :sub {display?.ddcHint ?: ""} :dim true))
(section :text {display?.output ?: "Output"})
(box :orientation "v" :space-evenly false
(for m in {display?.modes ?: []}
(row :glyph "󰍹" :title {m.mode} :active {m.current} :onclick "rice-display mode ${m.id}")))
(section :text "Refresh rate")
(box :class "pills" :space-evenly true
(for r in {display?.rates ?: []}
(button :class "pill ${r.current ? 'active' : ''}" :onclick "rice-display rate ${r.id}" {r.label})))
(sep)
(switch-row :glyph "󰍜" :title "Bar" :on {display?.bar == true} :onclick "rice-bar toggle")))

View file

@ -0,0 +1,50 @@
;; Control centre (super + x): levels, a line per subsystem, quick switches, now playing.
(defwindow hub
:monitor 0
:geometry (geometry :x "-10px" :y "47px" :width "400px" :anchor "top right")
:stacking "fg"
:wm-ignore true
:resizable false
(box :class "panel" :orientation "v" :space-evenly false
(head :title "Control Centre"
(head-action :glyph "󰍜" :tip "Rice menu" :onclick "rice-panel close; rice-menu main")
(head-action :glyph "󰐥" :tip "Power menu" :onclick "rice-panel close; rice-menu power"))
(slider-row :glyph {audio?.sink?.muted == true ? "󰝟" : "󰕾"}
:value {audio?.sink?.vol ?: 0} :muted {audio?.sink?.muted ?: false}
:onglyph "rice-volume mute" :onchange "rice-volume set {}")
(slider-row :glyph {audio?.source?.muted == true ? "󰍭" : "󰍬"}
:value {audio?.source?.vol ?: 0} :muted {audio?.source?.muted ?: false}
:onglyph "rice-audio mic-mute" :onchange "rice-audio mic {}")
(box :orientation "v" :space-evenly false :visible {display?.ddc == true}
(slider-row :glyph "󰃠" :value {display?.brightness ?: 0} :onchange "rice-display brightness {}"))
(sep)
(row :glyph {net?.active?.type == "ethernet" ? "󰈀" : (net?.active?.type == "wifi" ? "󰖩" : "󰖪")}
:title "Network" :value {net?.active?.name ?: "offline"} :chev true :onclick "rice-panel network")
(row :glyph "󰂯" :title "Bluetooth" :chev true :onclick "rice-panel bluetooth"
:value {bt?.powered != true ? "off" : (bt?.connected > 0 ? "${bt.connected} connected" : "on")})
(row :glyph "󰓃" :title "Sound" :value {audio?.sink?.name ?: ""} :chev true :onclick "rice-panel audio")
(row :glyph "󰍹" :title "Display" :chev true :onclick "rice-panel display"
:value "${display?.mode ?: ''} · ${round(display?.rate ?: 0, 0)} Hz")
(row :glyph "󰍛" :title "System" :chev true :onclick "rice-panel power"
:value "CPU ${power?.cpu?.load ?: 0}% · Memory ${power?.memory?.percent ?: 0}%")
(row :glyph "󰃭" :title "Calendar" :value {formattime(EWW_TIME, "%a %-d %b")} :chev true :onclick "rice-panel calendar")
(sep)
(switch-row :glyph "󰂛" :title "Do not disturb" :on {notifs?.dnd == true} :onclick "rice-notify dnd toggle"
:sub {(notifs?.count ?: 0) > 0 ? "${notifs.count} in the notification centre" : ""})
(switch-row :glyph "󰅶" :title "Stay awake" :on {idle?.awake == true} :onclick "rice-idle toggle"
:sub {idle?.awake == true ? "screensaver and lock paused" : ""})
(box :orientation "v" :space-evenly false :visible {media?.status == "Playing" || media?.status == "Paused"}
(sep)
(box :class "row" :space-evenly false
(label :class "glyph accent" :text "󰎆")
(eventbox :hexpand true :onclick "rice-panel media"
(box :orientation "v" :space-evenly false :valign "center"
(label :class "row-title" :xalign 0 :limit-width 22 :text {media?.title ?: ""})
(label :class "row-sub" :xalign 0 :limit-width 26 :text {media?.artist ?: ""})))
(button :class "media-btn" :onclick "playerctl previous" "󰒮")
(button :class "media-btn accent" :onclick "playerctl play-pause" {media?.status == "Playing" ? "󰏤" : "󰐊"})
(button :class "media-btn" :onclick "playerctl next" "󰒭")))))

View file

@ -0,0 +1,39 @@
;; Media: what is playing, a seek bar, and transport controls.
(defpoll media_pos :interval "1s" :initial "{}" :run-while {open_panel == "media"} "rice-media position")
(defwindow media
:monitor 0
:geometry (geometry :x "0" :y "47px" :width "380px" :anchor "top center")
:stacking "fg"
:wm-ignore true
:resizable false
(box :class "panel" :orientation "v" :space-evenly false
(head :title "Media"
(label :class "dim" :text {media?.player ?: ""}))
(box :orientation "v" :space-evenly false
:visible {media?.status != "Playing" && media?.status != "Paused"}
(empty :text "Nothing is playing"))
(box :orientation "v" :space-evenly false
:visible {media?.status == "Playing" || media?.status == "Paused"}
(box :class "row" :space-evenly false
(box :class "art" :visible {(media?.art ?: "") != ""}
:style "background-image: url('${media?.art ?: ''}');")
(box :orientation "v" :space-evenly false :valign "center" :hexpand true
(label :class "media-title" :xalign 0 :limit-width 26 :text {media?.title ?: ""})
(label :class "media-artist" :xalign 0 :limit-width 32 :text {media?.artist ?: ""})
(label :class "media-artist" :xalign 0 :limit-width 32 :visible {(media?.album ?: "") != ""}
:text {media?.album ?: ""})))
(box :class "row slider-row" :space-evenly false :visible {(media?.length ?: 0) > 0}
(label :class "media-time" :width 36 :xalign 0 :text {media_pos?.text ?: "0:00"})
(scale :class "slider" :hexpand true :min 0 :max {media?.length ?: 1}
:value {media_pos?.pos ?: 0} :onchange "rice-media seek {}")
(label :class "media-time" :width 36 :xalign 1 :text {media?.lengthText ?: ""}))
(box :class "row" :space-evenly true
(button :class "media-btn" :tooltip "Previous" :onclick "playerctl previous" "󰒮")
(button :class "media-btn accent" :tooltip "Play / pause" :onclick "playerctl play-pause"
{media?.status == "Playing" ? "󰏤" : "󰐊"})
(button :class "media-btn" :tooltip "Next" :onclick "playerctl next" "󰒭")))))

View file

@ -0,0 +1,37 @@
;; Network: Wi-Fi radio, the active connection, nearby networks and VPNs.
(defwindow network
:monitor 0
:geometry (geometry :x "-10px" :y "47px" :width "380px" :anchor "top right")
:stacking "fg"
:wm-ignore true
:resizable false
(box :class "panel" :orientation "v" :space-evenly false
(head :title "Network"
(head-action :glyph "󰑓" :tip "Rescan" :onclick "rice-net rescan")
(head-action :glyph "󰐲" :tip "Share Wi-Fi as a QR code" :onclick "rice-panel close; rice-net share")
(head-action :glyph "󰒓" :tip "Connection editor" :onclick "rice-panel close; setsid -f nm-connection-editor"))
(box :orientation "v" :space-evenly false :visible {net?.active?.type == "ethernet"}
(row :glyph "󰈀" :title "Ethernet" :sub {net?.active?.name ?: ""} :active true))
;; Machines without a Wi-Fi adapter (like a wired desktop) skip this part.
(box :orientation "v" :space-evenly false :visible {net?.hasWifi == true}
(switch-row :glyph "󰖩" :title "Wi-Fi" :on {net?.radio == true}
:onclick "rice-net radio toggle"
:sub {net?.active?.type == "wifi" ? net.active.name : (net?.radio == true ? "not connected" : "off")}))
(box :orientation "v" :space-evenly false :visible {net?.hasWifi == true && net?.radio == true}
(section :text "Networks")
(box :orientation "v" :space-evenly false
(for w in {net?.wifi ?: []}
(row :glyph {w.glyph} :title {w.ssid} :sub {w.sub} :active {w.active}
:onclick {w.active ? "rice-net disconnect" : "rice-net connect ${w.id}"})))
(box :visible {arraylength(net?.wifi ?: []) == 0}
(empty :text "No networks found yet")))
(box :orientation "v" :space-evenly false :visible {arraylength(net?.vpn ?: []) > 0}
(section :text "VPN")
(box :orientation "v" :space-evenly false
(for v in {net?.vpn ?: []}
(row :glyph {v.active ? "󰖂" : "󰕥"} :title {v.name} :sub {v.active ? "connected" : ""}
:active {v.active} :onclick "rice-net vpn ${v.id}"))))))

View file

@ -0,0 +1,37 @@
;; Notification centre: recent notifications, dismiss one or all, do-not-disturb.
(defwindow notifications
:monitor 0
:geometry (geometry :x "-10px" :y "47px" :width "380px" :anchor "top right")
:stacking "fg"
:wm-ignore true
:resizable false
(box :class "panel" :orientation "v" :space-evenly false
(head :title "Notifications"
(head-action :glyph {notifs?.dnd == true ? "󰂛" : "󰂚"} :active {notifs?.dnd == true}
:tip "Do not disturb" :onclick "rice-notify dnd toggle")
(head-action :glyph "󰎟" :tip "Clear all" :onclick "rice-notify clear"))
(box :orientation "v" :space-evenly false :visible {notifs?.dnd == true && (notifs?.waiting ?: 0) > 0}
(row :glyph "󰂛" :title "Held back" :sub "They appear when do-not-disturb is off"
:value "${notifs?.waiting ?: 0}" :dim true))
(box :orientation "v" :space-evenly false :visible {(notifs?.count ?: 0) == 0}
(empty :text "You're all caught up"))
(box :orientation "v" :space-evenly false
(for n in {notifs?.items ?: []}
(eventbox :class "notif-hit"
:onclick "rice-panel close; rice-notify open ${n.id}"
(box :class "notif ${n.urgency == 'CRITICAL' ? 'critical' : ''}" :space-evenly false
(box :orientation "v" :hexpand true :space-evenly false
(box :space-evenly false
(label :class "notif-app" :xalign 0 :hexpand true :limit-width 30 :text {n.app})
(label :class "notif-ago" :text {n.ago}))
(label :class "notif-summary" :xalign 0 :limit-width 36 :text {n.summary})
(label :class "notif-body" :xalign 0 :wrap true :limit-width 140
:visible {n.body != ""} :text {n.body}))
(button :class "notif-close" :valign "start" :tooltip "Dismiss"
:onclick "rice-notify remove ${n.id}" "󰅖")))))
(box :orientation "v" :space-evenly false :visible {(notifs?.count ?: 0) > 8}
(row :glyph "󰇘" :title "${notifs?.count - 8} older" :sub "Clear all to start fresh" :dim true))))

View file

@ -0,0 +1,31 @@
;; Power: what the machine is doing, the power profile, and session actions.
(defwindow power
:monitor 0
:geometry (geometry :x "-10px" :y "47px" :width "380px" :anchor "top right")
:stacking "fg"
:wm-ignore true
:resizable false
(box :class "panel" :orientation "v" :space-evenly false
(head :title "Power"
(head-action :glyph "󰍛" :tip "Activity monitor" :onclick "rice-panel close; setsid -f st -c rice-float -e btop"))
(row :glyph "󰍛" :title "CPU"
:value "${power?.cpu?.load ?: 0}%${power?.cpu?.temp != 'null' && power?.cpu?.temp != '' ? ' ' + power.cpu.temp + '°' : ''}")
(row :glyph "󰢮" :title "GPU" :sub {power?.gpu?.vram ?: ""}
:value "${power?.gpu?.load ?: 0}%${power?.gpu?.temp != 'null' && power?.gpu?.temp != '' ? ' ' + power.gpu.temp + '°' : ''}")
(row :glyph "󰘚" :title "Memory" :value "${power?.memory?.used ?: '-'} / ${power?.memory?.total ?: '-'}")
(row :glyph "󰔟" :title "Uptime" :value {power?.uptime ?: ""})
(box :orientation "v" :space-evenly false :visible {arraylength(power?.profiles ?: []) > 0}
(section :text "Power profile")
(box :class "row" :space-evenly true
(for p in {power?.profiles ?: []}
(button :class "pill ${p.active ? 'active' : ''}" :onclick "rice-power profile ${p.name}" {p.label}))))
(sep)
(box :space-evenly true :class "row"
(button :class "media-btn" :tooltip "Lock" :onclick "rice-power lock" "󰌾")
(button :class "media-btn" :tooltip "Suspend" :onclick "rice-power suspend" "󰒲")
(button :class "media-btn" :tooltip "Log out" :onclick "rice-power logout" "󰍃")
(button :class "media-btn" :tooltip "Reboot" :onclick "rice-power reboot" "󰜉")
(button :class "media-btn urgent" :tooltip "Shut down" :onclick "rice-power shutdown" "󰐥"))))

View file

@ -0,0 +1,42 @@
{
"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json",
"logo": null,
"display": {
"separator": " "
},
"modules": [
"break",
{
"type": "os",
"key": "OS ",
"keyColor": "31", // = color1
},
{
"type": "kernel",
"key": "KER ",
"keyColor": "32",
},
{
"type": "packages",
"format": "{} (pacman)",
"key": "PKG ",
"keyColor": "33",
},
{
"type": "shell",
"key": "SH ",
"keyColor": "34",
},
{
"type": "terminal",
"key": "TER ",
"keyColor": "35",
},
{
"type": "wm",
"key": "WM ",
"keyColor": "36",
},
"break",
]
}

View file

@ -0,0 +1,16 @@
.d8 .8uI
88R m888
88R %d88"
88R 88R
88R 88R
88R 88R
88R 88R
88R 88R
88R 88R
88B 88B
888 88*
.88u
%88*
8I*
J"*

View file

@ -0,0 +1,7 @@
.d8 .8u
88R 888
88R d8"
88R 88R
88B 88*
.88u 8I*
88R *IL

28
home/.config/git/config Normal file
View file

@ -0,0 +1,28 @@
# Sakura Line - git defaults. Your name, email and credentials stay in
# ~/.gitconfig, which git reads after this file, so it always wins.
[init]
defaultBranch = main
[pull]
rebase = true
[push]
autoSetupRemote = true
[diff]
algorithm = histogram
colorMoved = plain
mnemonicPrefix = true
[commit]
verbose = true
[column]
ui = auto
[branch]
sort = -committerdate
[tag]
sort = -version:refname
[rerere]
enabled = true
autoupdate = true
[alias]
co = checkout
br = branch
ci = commit
st = status

View file

@ -0,0 +1,11 @@
# Sakura Line - kitty. Font and colours come from the theme (rice-theme).
font_size 11.0
hide_window_decorations yes
confirm_os_window_close 0
window_padding_width 10
enable_audio_bell no
cursor_shape block
cursor_blink_interval 0
scrollback_lines 10000
include ~/.local/state/rice/theme/kitty.conf

View file

@ -0,0 +1,33 @@
# particles - wallpaper layer
# Tuned for bspwm/wallpaper/wallpaper.jpg: a bright, busy image, so the field
# is white and fairly opaque. Preview any change without touching the desktop:
# particles --snapshot /tmp/p.png --snapshot-bg ~/.config/bspwm/wallpaper/wallpaper.png
# Reload a running instance after editing: pkill -HUP particles
density = 55
radius = 2.4
glow = 4.0
opacity = 1.0
color = ffffff
link-distance = 140
link-width = 1.6
link-opacity = 0.85
link-color = ffffff
speed = 24
edges = bounce
cursor = grab,repel
cursor-distance = 180
cursor-strength = 1.0
cursor-response = 9
fps = 60
keep-below = true
pause-when-covered = true
mode = jigsaw
jigsaw-cols = 800
jigsaw-rows = 500
jigsaw-loose = 5

View file

@ -0,0 +1,60 @@
# Sakura Line - picom 13.
# One motion language for everything: windows, rofi, eww popups and dunst
# all appear with the same short scale-and-fade.
backend = "glx";
vsync = true;
use-damage = true;
detect-client-opacity = true;
detect-transient = true;
unredir-if-possible = true;
log-level = "warn";
shadow = false;
blur-background = false;
fading = true;
fade-in-step = 0.08;
fade-out-step = 0.08;
fade-delta = 8;
corner-radius = 12;
animations = (
{ triggers = [ "open", "show" ]; preset = "appear"; scale = 0.94; duration = 0.16; },
{ triggers = [ "close", "hide" ]; preset = "disappear"; scale = 0.96; duration = 0.12; }
);
rules = (
# The bar and desktop layer draw their own shape and never animate.
{ match = "window_type = 'dock' || window_type = 'desktop'"; corner-radius = 0; fade = false; animations = (); },
# Fullscreen video and games: square, and let them unredirect.
{ match = "fullscreen"; corner-radius = 0; },
# Toolkit menus and tooltips pop instantly.
{ match = "window_type = 'tooltip' || window_type = 'popup_menu' || window_type = 'dropdown_menu' || window_type = 'menu'"; corner-radius = 0; animations = (); },
# dunst rounds its own corners; picom rounding on top leaves a halo.
{ match = "class_g = 'Dunst'"; corner-radius = 0; },
# Menus and popups share a quicker, flatter version of the window motion.
{ match = "class_g = 'Rofi' || class_g ~= '^eww'";
animations = (
{ triggers = [ "open", "show" ]; preset = "appear"; scale = 0.98; duration = 0.10; },
{ triggers = [ "close", "hide" ]; preset = "disappear"; scale = 0.98; duration = 0.08; }
);
},
# The drop-down terminal slides from the top edge.
{ match = "class_g = 'rice-scratchpad'";
animations = (
{ triggers = [ "open", "show" ]; preset = "slide-in"; direction = "down"; duration = 0.18; },
{ triggers = [ "close", "hide" ]; preset = "slide-out"; direction = "up"; duration = 0.14; }
);
},
# Capture overlays must not move or fade while you aim.
{ match = "class_g = 'flameshot' || class_g = 'boomer' || class_g = 'slop'"; corner-radius = 0; fade = false; animations = (); },
# The webcam overlay stays a clean rounded tile.
{ match = "class_i = 'rice-webcam'"; corner-radius = 16; }
);

View file

@ -0,0 +1,121 @@
; Sakura Line - polybar. Colours come from the rendered theme.
; Every label opens its panel: left click = panel, right click = quick action.
include-file = ~/.local/state/rice/theme/polybar-colors.ini
include-file = ~/.local/state/rice/theme/polybar-modules.ini
[bar/top]
monitor = ${env:MONITOR:}
wm-restack = bspwm
enable-ipc = true
; Edges line up with tiled windows: bspwm draws borders inside the gap, so their
; outer edges sit one gap (10px) in from each side, so 100% minus 20px.
width = 100%:-20
height = 28
radius = 10
offset-x = 10
offset-y = 5
fixed-center = true
background = ${colors.bg-trans}
foreground = ${colors.text}
border-size = 2
border-color = ${colors.border}
padding = 1
module-margin = 0
cursor-click = pointer
cursor-scroll = ns-resize
font-0 = ${fonts.main}
font-1 = ${fonts.large}
; The clock sits alone in the centre so nothing that comes and goes (the
; player, recording or reminder indicators) can push it off centre.
modules-left = menu bspwm media
modules-center = date
modules-right = indicators cpu sep ram sep gpu sep net sep bt sep vol sep notif
[module/sep]
type = custom/text
format = "|"
format-foreground = ${colors.dim}
format-padding = 0
[module/menu]
type = custom/text
format = "󰣇"
format-foreground = ${colors.accent}
format-font = 2
format-padding = 1
click-left = rice-menu main
click-right = rice-panel hub
[module/bspwm]
type = internal/bspwm
pin-workspaces = true
inline-mode = true
enable-click = true
enable-scroll = true
format = <label-state>
label-focused = "[%name%]"
label-focused-foreground = ${colors.accent}
label-focused-padding = 1
label-occupied = "%name%"
label-occupied-foreground = ${colors.text}
label-occupied-padding = 1
label-empty = "%name%"
label-empty-foreground = ${colors.dim}
label-empty-padding = 1
label-urgent = "!%name%!"
label-urgent-foreground = ${colors.urgent}
label-urgent-padding = 1
[module/indicators]
type = custom/script
exec = rice-status indicators
tail = true
format-padding = 1
[module/media]
type = custom/script
exec = rice-status media
tail = true
format-padding = 1
click-left = rice-panel media
click-right = playerctl play-pause
scroll-up = playerctl previous
scroll-down = playerctl next
[module/net]
type = custom/script
exec = rice-status net
tail = true
format-padding = 1
click-left = rice-panel network
[module/bt]
type = custom/script
exec = rice-status bt
tail = true
format-padding = 1
click-left = rice-panel bluetooth
click-right = rice-bt power toggle
[module/vol]
type = custom/script
exec = rice-status vol
tail = true
format-padding = 1
click-left = rice-panel audio
click-right = rice-volume mute
scroll-up = rice-volume up
scroll-down = rice-volume down
[module/notif]
type = custom/script
exec = rice-status notif
tail = true
format-padding = 1
click-left = rice-panel notifications
click-middle = rice-notify dnd toggle
click-right = rice-notify dismiss-all
[settings]
screenchange-reload = true

View file

@ -0,0 +1,22 @@
# Sakura Line - aliases. Personal ones belong in ~/.bashrc.local.
if command -v eza >/dev/null; then
alias ls='eza -lh --group-directories-first --icons=auto'
alias lsa='ls -a'
alias lt='eza --tree --level=2 --long --icons --git'
alias lta='lt -a'
fi
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'
alias g='git'
alias gs='git status -sb'
alias gcm='git commit -m'
alias gcam='git commit -a -m'
alias gcad='git commit -a --amend'
alias d='docker'
alias t='tmux attach || tmux new -s work'
alias decompress='tar -xzf'

View file

@ -0,0 +1,18 @@
# Sakura Line - everyday functions.
# ff: fuzzy-find a file below the current directory, with a preview (pictures
# show inline in st and kitty). eff opens the pick in $EDITOR.
ff() { fzf --preview 'rice-preview {}' --preview-window 'right,55%' "$@"; }
eff() { local f; f=$(ff) && [[ -n $f ]] && "$EDITOR" "$f"; }
# img <picture>...: show pictures right in the terminal.
img() { local f; for f in "$@"; do rice-preview "$f"; done; }
# open <file|url>: open with the default app, detached from the terminal.
open() { xdg-open "$@" >/dev/null 2>&1 & disown; }
# n: nvim, on the current directory when given nothing.
n() { if (( $# == 0 )); then nvim .; else nvim "$@"; fi; }
# compress <file|dir>: make <name>.tar.gz next to it (decompress is tar -xzf).
compress() { tar -czf "${1%/}.tar.gz" "${1%/}"; }

View file

@ -0,0 +1,24 @@
# Sakura Line - git worktree helpers.
# ga <branch>: new branch in its own worktree next to the repository
# (../<repo>-<branch>), and cd into it.
ga() {
local branch="${1:-}" top main dir
[[ -n $branch ]] || { echo "usage: ga <branch>"; return 1; }
top=$(git rev-parse --show-toplevel 2>/dev/null) || { echo "ga: not inside a git repository"; return 1; }
main=$(dirname "$(git -C "$top" rev-parse --path-format=absolute --git-common-dir)")
dir="$(dirname "$main")/$(basename "$main")-${branch//\//-}"
git -C "$top" worktree add -b "$branch" "$dir" && cd "$dir"
}
# gd: remove the current worktree and its branch (asks first; never the main one).
gd() {
local top main branch answer
top=$(git rev-parse --show-toplevel 2>/dev/null) || { echo "gd: not inside a git repository"; return 1; }
main=$(git worktree list --porcelain | awk '/^worktree / { print $2; exit }')
[[ $top != "$main" ]] || { echo "gd: this is the main worktree; not removing it"; return 1; }
branch=$(git branch --show-current)
read -rp "Remove worktree $top and branch $branch? [y/N] " answer
[[ $answer == [yY]* ]] || return 1
cd "$main" && git worktree remove "$top" && git branch -D "$branch"
}

View file

@ -0,0 +1,34 @@
# Sakura Line - rsync watchers.
# rsw <source> <destination>: keep destination in sync with source in the
# background, re-syncing on every change. The destination can be remote
# (host:path); one SSH connection is reused, so you authenticate once.
rsw() {
(( $# == 2 )) || { echo "usage: rsw <source> <destination>"; return 1; }
local src="${1%/}" dest="$2" sockets="${XDG_RUNTIME_DIR:-$HOME/.ssh}"
local rsh="ssh -o ControlMaster=auto -o ControlPath=$sockets/rsw-%r@%h:%p -o ControlPersist=yes"
setsid --fork env RSYNC_RSH="$rsh" bash -c '
rsync -a "$1/" "$2"
while inotifywait -r -q -e modify,create,delete,move "$1" >/dev/null; do rsync -a "$1/" "$2"; done
' rsw-watch "$src" "$dest" >/dev/null 2>&1
echo "watching $src -> $dest"
}
# lsw: list watchers. dsw: stop them all.
lsw() {
local pid cmd rest found=0
while read -r pid cmd; do
rest="${cmd##*rsw-watch }"
echo "$pid: ${rest% *} -> ${rest##* }"
found=1
done < <(pgrep -af 'rsw-watch ')
(( found )) || echo "no active watchers"
}
dsw() {
local pid found=0
for pid in $(pgrep -f 'rsw-watch '); do
kill -- -"$pid" 2>/dev/null && echo "stopped $pid" && found=1
done
(( found )) || echo "no active watchers"
}

View file

@ -0,0 +1,82 @@
# Sakura Line - ssh helpers.
# ssh: when an interactive session drops, tidy the terminal and reconnect until
# you press Ctrl-C. Only a real dropped shell reconnects: a remote command,
# piped input, or a failure within the first 30 seconds (bad host, bad key)
# returns normally, so nothing is ever replayed by accident.
ssh() {
local rc started=$SECONDS
command ssh "$@"
rc=$?
[[ -t 1 ]] || return $rc
_ssh_disarm
if (( rc != 255 )) || [[ ! -t 0 ]] || ! _ssh_interactive "$@" || (( SECONDS - started < 30 )); then
return $rc
fi
# A subshell, so Ctrl-C stops both the attempt and the loop.
(
while :; do
echo "Connection lost. Reconnecting (Ctrl-C to stop)..."
sleep 2
command ssh "$@"
rc=$?
_ssh_disarm
(( rc != 255 )) && exit $rc
done
)
}
# A connection that dies mid-session leaves the remote tmux or editor's
# terminal modes on (mouse reporting floods the prompt); switch them off.
_ssh_disarm() {
printf '\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1004l\e[?1049l\e[?25h'
}
# True when the arguments open a shell: a destination and no remote command,
# counting a RemoteCommand set in ssh_config.
_ssh_interactive() {
local value_opts="BbcDEeFIiJLlmOoPpQRSWw" argv=("$@") arg letters i dest="" opts_done=""
while (( $# )); do
arg="$1"; shift
if [[ -z $opts_done && $arg == "--" ]]; then
opts_done=1
elif [[ -z $opts_done && $arg == -?* ]]; then
letters="${arg#-}"
for (( i = 0; i < ${#letters}; i++ )); do
if [[ $value_opts == *"${letters:i:1}"* ]]; then
(( i == ${#letters} - 1 )) && shift
break
fi
done
elif [[ -z $dest ]]; then
dest="$arg"
else
return 1
fi
done
[[ -n $dest ]] || return 1
local resolved
resolved=$(command ssh -G "${argv[@]}" 2>/dev/null) || return 1
! grep -i '^remotecommand ' <<<"$resolved" | grep -qvi '^remotecommand none$'
}
# fip <host> <port>...: forward remote ports to localhost (web dev against a remote box).
fip() {
(( $# >= 2 )) || { echo "usage: fip <host> <port>..."; return 1; }
local host="$1" port; shift
for port in "$@"; do
command ssh -f -N -L "$port:localhost:$port" "$host" && echo "localhost:$port -> $host:$port"
done
}
# dip <port>...: stop those forwards. lip: list active forwards.
dip() {
(( $# >= 1 )) || { echo "usage: dip <port>..."; return 1; }
local port
for port in "$@"; do
pkill -f "ssh.*-L $port:localhost:$port" && echo "stopped $port" || echo "no forward on $port"
done
}
lip() { pgrep -af 'ssh.*-L [0-9]+:localhost:[0-9]+' || echo "no active forwards"; }

View file

@ -0,0 +1,22 @@
/* Sakura Line - rofi defaults. Scripts pick a layout with -theme. */
configuration {
modes: "drun,run,window";
show-icons: true;
icon-theme: "Papirus-Dark";
drun-display-format: "{name}";
matching: "fuzzy";
sort: true;
sorting-method: "fzf";
hover-select: true;
me-select-entry: "MousePrimary";
me-accept-entry: "!MousePrimary";
kb-cancel: "Escape,Control+g,Control+bracketleft";
/* Header titles, one per mode (rofi 2 ignores -display-<mode> for some modes). */
drun { display-name: "Apps"; }
run { display-name: "Run"; }
window { display-name: "Windows"; }
emoji { display-name: "Emoji"; }
}
@theme "~/.config/rofi/launcher.rasi"

View file

@ -0,0 +1,36 @@
/* Image grid: wallpaper and theme pickers. */
@import "~/.config/rofi/rice.rasi"
window {
width: 980px;
}
listview {
columns: 4;
lines: 2;
fixed-height: true;
flow: horizontal;
padding: 14px;
spacing: 10px;
}
element {
orientation: vertical;
padding: 10px;
spacing: 8px;
children: [ element-icon, element-text ];
}
element-icon {
size: 190px;
horizontal-align: 0.5;
}
element-text {
horizontal-align: 0.5;
text-color: @dim;
}
element selected.normal element-text {
text-color: @fg;
}

View file

@ -0,0 +1,6 @@
/* App launcher (super + w). */
@import "~/.config/rofi/rice.rasi"
window { width: 620px; }
listview { lines: 9; fixed-height: true; }
element-icon { size: 1.6em; }

View file

@ -0,0 +1,7 @@
/* Text menus: system menu, power, keybindings, clipboard, toggles.
Entries carry their own Nerd Font glyph, so rofi's icon column is off. */
@import "~/.config/rofi/rice.rasi"
window { width: 520px; }
listview { lines: 12; }
element-icon { enabled: false; }

View file

@ -0,0 +1,17 @@
/* Single text input: reminder text, event title, Wi-Fi password. */
@import "~/.config/rofi/rice.rasi"
window { width: 520px; }
mainbox { children: [ inputbar, message ]; }
inputbar {
border: 0px;
}
entry {
placeholder: "";
}
message {
border: 1px 0px 0px 0px;
}

View file

@ -0,0 +1,92 @@
/* Sakura Line - shared rofi layout. Every menu imports this, then overrides
only size and position. Colours come from the rendered theme. */
@import "~/.local/state/rice/theme/rofi-colors.rasi"
* {
background-color: transparent;
text-color: @fg;
font: @font-main;
}
window {
transparency: "real";
location: center;
anchor: center;
width: 560px;
background-color: @bg-trans;
border: 1px;
border-color: @border-col;
border-radius: 12px;
}
mainbox {
spacing: 0px;
children: [ inputbar, message, listview ];
}
inputbar {
padding: 14px 18px;
spacing: 0px;
border: 0px 0px 1px 0px;
border-color: @border-col;
children: [ prompt, entry ];
}
prompt {
text-color: @fg;
padding: 0px 10px 0px 0px;
vertical-align: 0.5;
font: @font-bold;
}
entry {
placeholder: "Search";
placeholder-color: @dim;
cursor: text;
vertical-align: 0.5;
font: @font-entry;
}
message {
padding: 10px 18px;
border: 0px 0px 1px 0px;
border-color: @border-col;
}
textbox {
text-color: @dim;
markup: true;
}
listview {
lines: 10;
columns: 1;
fixed-height: false;
scrollbar: false;
padding: 8px;
spacing: 2px;
}
element {
padding: 8px 10px;
spacing: 10px;
border-radius: 8px;
}
element normal.active, element alternate.active { text-color: @accent; }
element normal.urgent, element alternate.urgent { text-color: @urgent; }
element selected.normal { background-color: @select-bg; text-color: @select-fg; }
element selected.active { background-color: @select-bg; text-color: @accent; }
element selected.urgent { background-color: @select-bg; text-color: @urgent; }
element-icon {
size: 1.3em;
vertical-align: 0.5;
}
element-text {
text-color: inherit;
vertical-align: 0.5;
}
mode-switcher { enabled: false; }

244
home/.config/sxhkd/sxhkdrc Normal file
View file

@ -0,0 +1,244 @@
# Sakura Line - keybindings.
# `rice-keys` builds the cheat sheet (super + k) from this file:
# a "## Section" line starts a group, a "# text" line right above a
# hotkey is its description. Keep that shape when adding bindings.
## Apps
# Terminal
super + Return
st
# Drop-down terminal (scratchpad)
super + grave
rice-scratchpad
# App launcher
super + w
rofi -show drun
# Rice menu
super + m
rice-menu main
# Keybinding cheat sheet
super + k
rice-keys
# Emoji picker
super + ctrl + e
rice-emoji
# Clipboard history
super + ctrl + v
rice-clipboard
## Panels
# Control centre
super + a
rice-panel hub
# Audio / network / bluetooth / display / power / media panel
super + ctrl + {a,w,b,d,p,m}
rice-panel {audio,network,bluetooth,display,power,media}
# Calendar and reminders
super + ctrl + c
rice-panel calendar
# Notification centre
super + n
rice-panel notifications
# Toggle the bar
super + b
rice-bar toggle
## Notifications
# Dismiss the newest notification
super + comma
rice-notify dismiss
# Dismiss all notifications
super + shift + comma
rice-notify dismiss-all
# Do not disturb on/off
super + ctrl + comma
rice-notify dnd toggle
# Run the newest notification's action
super + alt + comma
rice-notify action
# Bring back the last dismissed notification
super + shift + alt + comma
rice-notify restore
## Reminders
# New reminder
super + ctrl + r
rice-reminder new
# Show upcoming reminders
super + ctrl + alt + r
rice-reminder list
## Capture
# Screenshot a region (flameshot)
Print
rice-capture region
# Screenshot the whole screen
shift + Print
rice-capture screen
# Copy text from a region (OCR)
ctrl + Print
rice-capture text
# Start or stop screen recording
alt + Print
rice-capture record
# Pick a colour
super + shift + Print
rice-capture color
# Capture menu: QR, webcam overlay, transcode and the rest
super + Print
rice-menu capture
# Screenshot a region (no PrtSc key)
super + alt + p
rice-capture region
# Screenshot the whole screen (no PrtSc key)
super + alt + shift + p
rice-capture screen
# Copy text from a region (no PrtSc key)
super + alt + t
rice-capture text
# Start or stop screen recording (no PrtSc key)
super + alt + v
rice-capture record
# Pick a colour (no PrtSc key)
super + alt + c
rice-capture color
# Capture menu (no PrtSc key)
super + alt + s
rice-menu capture
# Zoom into the screen
super + ctrl + z
rice-zoom
# Dictation on/off
super + ctrl + x
rice-dictate toggle
## Clipboard
# Copy (works in terminals too)
super + @c
rice-clip copy
# Paste (works in terminals too)
super + @v
rice-clip paste
## Style
# Wallpaper picker
super + ctrl + space
rice-menu wallpaper
# Theme picker
super + shift + ctrl + space
rice-menu theme
# Quick toggles
super + ctrl + o
rice-menu toggles
## Session
# Power menu
super + Escape
rice-menu power
# Lock
super + ctrl + l
rice-lock
# Stay awake on/off
super + ctrl + i
rice-idle toggle
# Reload keybindings
super + alt + Escape
pkill -USR1 -x sxhkd && rice-notify send "Keybindings" "Reloaded"
# Restart bspwm
super + alt + r
bspc wm -r
# Quit bspwm
super + Delete
bspc quit
## Windows
# Close window
super + q
bspc node -c
# Float / tile
super + space
bspc node -t "~"{floating,tiled}
# Fullscreen
super + f
bspc node -t \~fullscreen
# Focus / swap window in a direction
super + {_,shift + }{Left,Down,Up,Right}
bspc node -{f,s} {west,south,north,east}
# Previous window
alt + Tab
bspc node -f last
# Go to desktop / send window to desktop
super + {_,shift + }{1-6}
bspc {desktop -f,node -d} '^{1-6}'
# Previous / next desktop
super + ctrl + {Left,Right}
bspc desktop -f {prev,next}.local
## Media
# Volume up / down / mute
XF86Audio{RaiseVolume,LowerVolume,Mute}
rice-volume {up,down,mute}
# Microphone mute
XF86AudioMicMute
rice-volume mic-mute
# Play-pause / next / previous / stop
XF86Audio{Play,Pause,Next,Prev,Stop}
playerctl {play-pause,play-pause,next,previous,stop}
# Play-pause / next / previous without media keys
ctrl + alt + {c,x,z}
playerctl {play-pause,next,previous}