Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
16e4c38
feat(retroshell): add desktop widgets
itsvlxd Aug 24, 2026
3b64b41
feat(settings): add shell desktop widget settings
itsvlxd Aug 24, 2026
16d18d8
feat(shell): add more ipc commands
itsvlxd Aug 24, 2026
9e8c2e7
feat(retroshell): make worldclock be 1x4
itsvlxd Aug 24, 2026
d4c6130
feat(retroshell): add battery health capacity and watts cards in the …
itsvlxd Aug 24, 2026
55ebf5e
feat(retroshell): add bluetooth, storage, network & power widgets
itsvlxd Aug 24, 2026
40687e2
feat(settings): add newly added widgets
itsvlxd Aug 24, 2026
1738edd
feat(retroshell): add battery ring, more clock faces and hands and no…
itsvlxd Aug 24, 2026
2e1ad24
feat(retroshell): add per monitor widget & news feed widget
itsvlxd Aug 24, 2026
7fb1809
fix(retroshell): fix minor bugs in the news feed widget
itsvlxd Aug 24, 2026
fb0a829
feat(retroshell): add wifi stats and qr code share
itsvlxd Aug 25, 2026
e248575
feat(retroshell): add wifi speed test in menu
itsvlxd Aug 25, 2026
e0b4dc2
chore(retroshell): remove on destruction for wifi panel
itsvlxd Aug 25, 2026
f67f9c1
fix(retroshell): fix storage and bluetooth widget
itsvlxd Aug 25, 2026
98cca1f
chore(retroshell): remove network settings icon
itsvlxd Aug 25, 2026
55e46ea
feat(retro): add keyboard typing sound effects with sound packs
itsvlxd Aug 25, 2026
06f26fc
fix(retroshell): add missing lib input package
itsvlxd Aug 25, 2026
a55f133
chore(modules): fix tmux & zsh packages mode
itsvlxd Aug 25, 2026
8078a2e
feat(retroshell): add system monitor widget
itsvlxd Aug 26, 2026
da235bc
feat(settings): increase power limits in settings
itsvlxd Aug 26, 2026
495d6a6
feat(settings): add wallapper stop on static
itsvlxd Aug 26, 2026
ea37980
feat(zsh): add retro dir and config paths
itsvlxd Aug 26, 2026
b7a4b0c
fix(retroshell): add the right packages to the list
itsvlxd Aug 26, 2026
e5c8f3a
fix(audio): fix audio priority rules
itsvlxd Aug 27, 2026
8ec41eb
feat(disk): add persistent auto mount for ssds
itsvlxd Aug 27, 2026
85fd89c
feat(modules): add docker module
itsvlxd Aug 27, 2026
603069c
feat(retro): add shell check as a package in retro module
itsvlxd Aug 27, 2026
5cef5cc
feat(modules): add typing sounds module
itsvlxd Aug 27, 2026
2c7d278
fix(modules): fix docker module
itsvlxd Aug 27, 2026
88d6adb
chore(retroshell): remove libinput packages
itsvlxd Aug 27, 2026
5bd92a2
fix(power): fix power core to use rx_log_file
itsvlxd Aug 27, 2026
1836099
fix(tmus): fix tmux tpm plugin dir
itsvlxd Aug 31, 2026
1aa6f05
fix(tmux): fix tmux plugin installer
itsvlxd Sep 2, 2026
469a679
feat(zsh): add custom zsh settings source file
itsvlxd Sep 2, 2026
27a9cef
fix(retroshell): fix workspace indicator
itsvlxd Sep 2, 2026
6008197
fix(retroshell): fix retroshell ip keybind commands
itsvlxd Sep 2, 2026
255affa
chore(git): update git ignore
itsvlxd Sep 2, 2026
310c561
fix(iso): fix iso build workflow
itsvlxd Sep 2, 2026
3c08ce1
feat(retroshell): add docker widget integration
itsvlxd Sep 3, 2026
c1166aa
style(retroshell): docker widget ui overhaul
itsvlxd Sep 3, 2026
bbe822d
feat(settings): add docker manager into dashboard too
itsvlxd Sep 3, 2026
14c243e
feat(retroshell): add blur for color varients in the shell
itsvlxd Sep 4, 2026
d66b36c
feat(retroshell): add blur and ignore alpha for widget panels
itsvlxd Sep 6, 2026
cbaf6d7
fix(settings): fix settings save button
itsvlxd Sep 8, 2026
1902f5a
feat(settings): add bluetooth headphones profile enforcer
itsvlxd Sep 9, 2026
e4c59cc
feat(zsh): add nvim as MANPAGER
itsvlxd Sep 9, 2026
2c106e8
feat(tmux): add tmux mouse scroll support
itsvlxd Sep 9, 2026
a1edf27
fix(tmux): fix tmux repeating bug
itsvlxd Sep 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ MangoHud.conf
modules/cava/files/themes/
modules/spotify/files/Themes/

axctl.*
TODO
retro-settings.pyz

Expand Down
6 changes: 6 additions & 0 deletions cmds/system/setup/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ context.modules = [
]
EOF

# Add user to input group for typing sounds (libinput/evtest access)
if ! id -nG "$USER" | tr ' ' '\n' | grep -qx input; then
sudo usermod -aG input "$USER" 2>/dev/null || true
rx_log "info" "Added user to input group"
fi

retro polkit setup --needed -y
if [[ ${FIREWALL_ENABLED:-true} == "true" ]]; then
sudo systemctl enable nftables 2>/dev/null || true
Expand Down
12 changes: 6 additions & 6 deletions cmds/tools/power.sh
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,12 @@ cmd_power() {
use_bat_s="$sg_bats"; use_bat_b="$sg_batb"; use_bat_p="$sg_batp"
else
rx_log "info" "Configure custom wattage limits:"
use_ac_s=$(rx_input_numeric "AC Saver (W)" "$sg_ac" 1 200)
use_ac_b=$(rx_input_numeric "AC Balanced (W)" "$sg_bal" 1 200)
use_ac_p=$(rx_input_numeric "AC Performance (W)" "$sg_perf" 1 200)
use_bat_s=$(rx_input_numeric "BAT Saver (W)" "$sg_bats" 1 200)
use_bat_b=$(rx_input_numeric "BAT Balanced (W)" "$sg_batb" 1 200)
use_bat_p=$(rx_input_numeric "BAT Performance (W)" "$sg_batp" 1 200)
use_ac_s=$(rx_input_numeric "AC Saver (W)" "$sg_ac" 1 250)
use_ac_b=$(rx_input_numeric "AC Balanced (W)" "$sg_bal" 1 250)
use_ac_p=$(rx_input_numeric "AC Performance (W)" "$sg_perf" 1 250)
use_bat_s=$(rx_input_numeric "BAT Saver (W)" "$sg_bats" 1 250)
use_bat_b=$(rx_input_numeric "BAT Balanced (W)" "$sg_batb" 1 250)
use_bat_p=$(rx_input_numeric "BAT Performance (W)" "$sg_batp" 1 250)
fi

rx_setup_summary "󰐋" "Power Setup Summary" \
Expand Down
3 changes: 3 additions & 0 deletions cmds/tools/settings/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ class ConfigSections:
layer_rules: list[str] | None = None
window_rules_nodes: list["Rule"] | None = None
layer_rules_nodes: list["Rule"] | None = None
theme_layer_rules: list[str] | None = None
gestures: list[str] | None = None


Expand Down Expand Up @@ -708,6 +709,8 @@ def _build_document(values: dict[str, str], sections: ConfigSections) -> Documen
doc.lines.extend(sections.layer_rules_nodes)
elif sections.layer_rules:
_add_section(doc, "Layer rules", sections.layer_rules)
if sections.theme_layer_rules:
_add_section(doc, "Theme blur rules", sections.theme_layer_rules)
# Autostart last: ``exec`` re-runs on every reload, so config later in
# the file that affects the exec'd process (env vars, monitor layout)
# is already in effect by the time the commands run.
Expand Down
192 changes: 190 additions & 2 deletions cmds/tools/settings/core/shell_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,14 @@ def save_shell_json(name: str, data: dict) -> None:
"lens": ("Google Lens", "Reverse image search", "system-search-symbolic", None),
"shazam": ("Shazam", "Recognize playing music", "shazam-symbolic", "songrec"),
"webcam": ("Webcam Overlay", "Floating webcam preview", "camera-video-symbolic", None),
"docker": ("Docker Manager", "Monitor and manage Docker containers", "utilities-system-monitor-symbolic", "docker"),
}

# Default toolbox order mirrors the current hardcoded layout in ToolsMenu.qml.
TOOLBOX_DEFAULT_ORDER: list = [
"screenshot", "screenshots", "separator",
"recorder", "recordings", "separator",
"colorpicker", "ocr", "qr", "lens", "shazam", "webcam",
"colorpicker", "ocr", "qr", "lens", "shazam", "webcam", "docker",
]

TOOLBOX_MIN_ITEMS = 5
Expand Down Expand Up @@ -136,6 +137,8 @@ def save_shell_json(name: str, data: dict) -> None:
"battery": ("Battery", "Show battery level", "battery-symbolic"),
"clock": ("Time, Weather & Calendar", "Time, date, calendar and weather", "clock-symbolic"),
"power": ("Power Button", "Open the power menu", "system-shutdown-symbolic"),
"typingSounds": ("Typing Sounds", "Mechanical keyboard typing sounds", "input-keyboard-symbolic"),
"docker": ("Docker", "Monitor and manage Docker containers", "utilities-system-monitor-symbolic"),
}

# Unified catalog of every reorderable bar item (left + right), so an item keeps
Expand Down Expand Up @@ -330,6 +333,7 @@ def _linear_variant(label: str, gradient, *, dot: str = "surface",
"fontSize": 14,
"monoFont": "Iosevka Nerd Font Mono",
"monoFontSize": 14,
"emojiFont": "Noto Color Emoji",
"tintIcons": False,
"enableCorners": True,
"animDuration": 300,
Expand Down Expand Up @@ -503,11 +507,85 @@ def save_dock(data: dict) -> None:
# Mirrors ``modules/retroshell/files/config/defaults/desktop.js``.
DESKTOP_DEFAULTS: dict = {
"enabled": False,
"showIcons": True,
"iconSize": 40,
"spacingVertical": 16,
"textColor": "overBackground",
"editMode": False,
"perMonitor": False,
"widgetOrder": [],
"widgets": [],
}

# Catalog of desktop widgets the user can add/remove.
# ``id`` -> (label, description, icon). Extend this list to add more widgets.
DESKTOP_WIDGET_CATALOG: dict = {
"calendar": ("Calendar 4x4", "Large square month calendar", "x-office-calendar-symbolic"),
"calendar2x4": ("Calendar 2x4", "Wide compact month calendar", "x-office-calendar-symbolic"),
"weather": ("Weather", "Animated weather scene with 7-day forecast", "weather-clear-symbolic"),
"weather2x4": ("Weather 2x4", "Wide weather card with condition disc and forecast", "weather-clear-symbolic"),
"weather2x2": ("Weather 2x2", "Simple square animated weather card", "weather-clear-symbolic"),
"music2x2": ("Music Player 2x2", "Square music player with artwork and controls", "multimedia-player-symbolic"),
"music2x4": ("Music Player 2x4", "Tall music player with circular seek disc and controls", "multimedia-player-symbolic"),
"clockdigital": ("Digital Clock", "Big time and date", "appointment-new-symbolic"),
"clockanalog": ("Analog Clock", "Circular clock face with hands", "appointment-new-symbolic"),
"worldclock": ("World Clock", "Up to 4 timezones side by side", "appointment-new-symbolic"),
"storage": ("Device Storage", "Apple-style storage breakdown card", "drive-harddisk-symbolic"),
"storage2x4": ("Device Storage 2x4", "Wide storage card with per-category sizes", "drive-harddisk-symbolic"),
"network": ("Network Monitor", "Live bandwidth, sparkline and IPs", "network-wireless-symbolic"),
"network2x4": ("Network Monitor 2x4", "Wide bandwidth card with big sparkline", "network-wireless-symbolic"),
"network1x4": ("Network Monitor Slim", "Slim horizontal bandwidth strip", "network-wireless-symbolic"),
"network1x3": ("Network Monitor 1x3", "Compact strip with speeds and toggle", "network-wireless-symbolic"),
"power": ("Power & Performance", "Power draw, thermals and profile switcher", "battery-level-80-symbolic"),
"power1x3": ("Power & Performance 1x3", "Slim strip with profile switcher", "battery-level-80-symbolic"),
"bluetooth": ("Bluetooth", "Connected devices, battery and quick pair", "bluetooth-symbolic"),
"note": ("Note", "Pinned editable note on the desktop", "text-x-generic-symbolic"),
"batteryring": ("Battery Rings", "Apple-style circular battery gauge", "battery-symbolic"),
"batteryring2x4": ("Battery Rings 2x4", "Battery rings for up to 4 devices", "battery-symbolic"),
"feed": ("Dev Feed", "Full-cover article feed (DEV.to / Hacker News / daily.dev)", "globe-symbolic"),
"sysmonitor": ("System Monitor", "CPU, RAM and disk usage", "utilities-system-monitor-symbolic"),
"sysmonitor2x4": ("System Monitor 2x4", "CPU/GPU/RAM sparkline graph", "utilities-system-monitor-symbolic"),
"battery": ("Battery", "Battery level and charging state", "battery-symbolic"),
"photo": ("Photo 2x2", "Display an image on your desktop", "image-x-generic-symbolic"),
"photo2x4": ("Photo 2x4", "Display a wide image on your desktop", "image-x-generic-symbolic"),
"photo4x2": ("Photo 4x2", "Display a portrait image on your desktop", "image-x-generic-symbolic"),
}

# Fixed default size (px) for each widget type, used when none is stored.
DESKTOP_WIDGET_SIZES: dict = {
"calendar": (320, 320),
"calendar2x4": (320, 160),
"weather": (320, 240),
"weather2x4": (320, 160),
"weather2x2": (160, 160),
"music2x2": (160, 160),
"music2x4": (320, 160),
"clockdigital": (160, 160),
"clockanalog": (160, 160),
"worldclock": (320, 160),
"storage": (160, 160),
"storage2x4": (320, 160),
"network": (160, 160),
"network2x4": (320, 160),
"network1x4": (320, 80),
"network1x3": (240, 80),
"power": (160, 160),
"power1x3": (240, 80),
"bluetooth": (160, 160),
"note": (160, 160),
"batteryring": (160, 160),
"batteryring2x4": (320, 160),
"feed": (320, 160),
"sysmonitor": (160, 160),
"sysmonitor2x4": (320, 160),
"battery": (160, 160),
"photo": (160, 160),
"photo2x4": (320, 160),
"photo4x2": (160, 320),
}

DESKTOP_MIN_WIDGETS = 0


def desktop_path() -> Path:
return shell_config_dir() / "desktop.json"
Expand All @@ -521,6 +599,45 @@ def save_desktop(data: dict) -> None:
save_shell_json("desktop", data)


# ── desktop_widgets.json ────────────────────────────────────────────────
# Dedicated file for desktop widget instances. Kept separate from
# desktop.json because Quickshell's JsonAdapter does not load top-level
# array properties; the shell's DesktopWidgets reads/writes this file with
# manual JSON so the widget list/positions round-trip reliably.

def desktop_widgets_path() -> Path:
return shell_config_dir() / "desktop_widgets.json"


def load_desktop_widgets() -> list[dict]:
"""Return the list of desktop widget instances ``[{id,type,x,y,width,height}]``."""
try:
data = json.loads(desktop_widgets_path().read_text())
except (json.JSONDecodeError, OSError):
return []
if isinstance(data, dict) and isinstance(data.get("widgets"), list):
return data["widgets"]
return []


def save_desktop_widgets(widgets: list[dict]) -> None:
"""Atomically write the desktop widget instances to desktop_widgets.json."""
path = desktop_widgets_path()
path.parent.mkdir(parents=True, exist_ok=True)
fd, tmp = tempfile.mkstemp(dir=str(path.parent), prefix=".desktop_widgets.", suffix=".tmp")
try:
with os.fdopen(fd, "w", encoding="utf-8") as fh:
json.dump({"widgets": widgets}, fh, indent=1)
fh.write("\n")
os.replace(tmp, path)
except BaseException:
try:
os.unlink(tmp)
except OSError:
pass
raise


# ── system.json (Misc/OCR) ──────────────────────────────────────────────

# OCR defaults from ``modules/retroshell/files/config/defaults/system.js``.
Expand Down Expand Up @@ -864,7 +981,7 @@ def save_tools(data: dict) -> None:
# hot-action buttons. Groups are all reorderable but never removable; the
# QuickControls buttons can be added/removed down to DASHBOARD_MIN_CONTROLS.
DASHBOARD_WIDGET_IDS = ("player", "quickactions", "notifications", "controls")
DASHBOARD_CONTROL_IDS = ("wifi", "bluetooth", "quickshare", "caffeine", "darkmode", "nightlight")
DASHBOARD_CONTROL_IDS = ("wifi", "bluetooth", "quickshare", "caffeine", "typingSounds", "darkmode", "nightlight", "docker")
DASHBOARD_MIN_CONTROLS = 5

DASHBOARD_DEFAULTS: dict = {
Expand Down Expand Up @@ -904,3 +1021,74 @@ def load_notifications() -> dict:

def save_notifications(data: dict) -> None:
save_shell_json("notifications", data)


# ── typing_sounds.json ────────────────────────────────────────────────

# Bundled sound pack directory inside the retroshell module.
TYPING_SOUNDS_PACKS_DIR = Path("/opt/retrolinux/modules/retroshell/files/assets/typing-sounds-soundpacks")

# Catalog of bundled sound packs: id -> display name.
# Scanned dynamically at runtime; this dict provides fallback names.
TYPING_SOUNDS_PACK_NAMES: dict = {
"nk-cream": "NK Cream",
"cherrymx-black-abs": "Cherry MX Black (ABS)",
"cherrymx-black-pbt": "Cherry MX Black (PBT)",
"cherrymx-blue-abs": "Cherry MX Blue (ABS)",
"cherrymx-blue-pbt": "Cherry MX Blue (PBT)",
"cherrymx-brown-abs": "Cherry MX Brown (ABS)",
"cherrymx-brown-pbt": "Cherry MX Brown (PBT)",
"cherrymx-red-abs": "Cherry MX Red (ABS)",
"cherrymx-red-pbt": "Cherry MX Red (PBT)",
"cream-travel": "Cream Travel",
"eg-crystal-purple": "EG Crystal Purple",
"eg-oreo": "EG Oreo",
"holy-pandas": "Holy Pandas",
"mxblack-travel": "MX Black Travel",
"mxblue-travel": "MX Blue Travel",
"mxbrown-travel": "MX Brown Travel",
"topre-purple-hybrid-pbt": "Topre Purple Hybrid (PBT)",
"turquoise": "Turquoise",
}

TYPING_SOUNDS_DEFAULTS: dict = {
"enabled": False,
"volume": 100,
"mouseEnabled": False,
"selectedPackId": "nk-cream",
"selectedDevicePath": "all",
}


def typing_sounds_path() -> Path:
return shell_config_dir() / "typing_sounds.json"


def load_typing_sounds() -> dict:
return load_shell_json("typing_sounds", TYPING_SOUNDS_DEFAULTS)


def save_typing_sounds(data: dict) -> None:
save_shell_json("typing_sounds", data)


def scan_typing_sound_packs() -> list[dict]:
"""Return list of {id, name} for every bundled sound pack."""
packs: list[dict] = []
if not TYPING_SOUNDS_PACKS_DIR.is_dir():
return packs
for d in sorted(TYPING_SOUNDS_PACKS_DIR.iterdir()):
if not d.is_dir():
continue
config_file = d / "config.json"
if config_file.exists():
try:
import json
cfg = json.loads(config_file.read_text())
name = cfg.get("name", d.name)
except (json.JSONDecodeError, OSError):
name = TYPING_SOUNDS_PACK_NAMES.get(d.name, d.name)
else:
name = TYPING_SOUNDS_PACK_NAMES.get(d.name, d.name)
packs.append({"id": d.name, "name": name})
return packs
Loading
Loading