NoSignal — fully-offline Arch → Hyprland desktop installer
Single-script builder (nosignal.sh) that turns a stock Arch Linux ISO into a fully-offline installer for a themed Hyprland + caelestia (Quickshell) desktop: matching SDDM greeter, Btrfs/Limine bootable snapshots, chwd-style GPU detection, and a curated "os updates" layer (keybind cheatsheet, settings panels, system polish, on-box management skill). See README.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
36
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Build artifacts
|
||||||
|
work/
|
||||||
|
*.iso
|
||||||
|
build*.log
|
||||||
|
|
||||||
|
# Editor / OS cruft
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Offline payload cache (multi-GB: built AUR pkgs + repo mirror + chroot)
|
||||||
|
offline/
|
||||||
|
|
||||||
|
# VM test rig (QEMU certification harness) — maintainer-only dev tooling,
|
||||||
|
# kept LOCAL (scripts + disks/firmware/screenshots/logs all stay off the repo).
|
||||||
|
vm/
|
||||||
|
|
||||||
|
# squashfs debug extracts
|
||||||
|
sq/
|
||||||
|
*.sfs
|
||||||
|
|
||||||
|
# Integration inbox from live test boxes — content is fully folded into
|
||||||
|
# "os updates/" + nosignal.sh; kept locally for reference only
|
||||||
|
# (-done = archived rounds; hypros-* = pre-rename rounds 2+3)
|
||||||
|
nosignal-handoff/
|
||||||
|
nosignal-handoff-done/
|
||||||
|
hypros-handoff/
|
||||||
|
hypros-handoff-done/
|
||||||
|
|
||||||
|
# Dev-internal docs — kept LOCAL only, never in the public repo:
|
||||||
|
# the dev status log, hardware-test briefs, QA/builder handoff, and the
|
||||||
|
# internal phase/design docs (infra hostnames, dev-machine specs, process).
|
||||||
|
NOTES.md
|
||||||
|
ONBOX-BRIEF-*.md
|
||||||
|
dev-docs/
|
||||||
|
docs/PHASE2-DESIGN.md
|
||||||
|
docs/PHASE3-OFFLINE.md
|
||||||
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Gavin Nugent
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
202
README.md
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
# NoSignal OS
|
||||||
|
|
||||||
|
A single-script builder that turns a stock Arch Linux ISO into a **fully
|
||||||
|
offline installer** for a ready-to-use **Hyprland desktop**: Arch +
|
||||||
|
[Hyprland](https://hyprland.org) + the
|
||||||
|
[caelestia](https://github.com/caelestia-dots/caelestia) dotfiles/shell, with
|
||||||
|
a themed [SDDM](https://github.com/sddm/sddm) login screen that matches the
|
||||||
|
desktop.
|
||||||
|
|
||||||
|
Everything needed for the install is bundled **on the ISO** — a complete
|
||||||
|
pacman repo (every package incl. all GPU driver variants, with the caelestia
|
||||||
|
stack pre-built from the AUR at ISO-build time), the pinned dotfiles, the
|
||||||
|
theme. **No internet is needed to install or on first boot**: the machine
|
||||||
|
boots straight into the themed login screen and a fully configured desktop.
|
||||||
|
|
||||||
|
> NoSignal is the *ISO builder*. It bundles and installs the upstream caelestia
|
||||||
|
> dotfiles; it is not affiliated with that project.
|
||||||
|
|
||||||
|
> ⚠️ **Experimental — and built to be modified.** NoSignal OS is young and
|
||||||
|
> opinionated; expect rough edges. It is designed to be reshaped by *you*: every
|
||||||
|
> NoSignal tweak sits in a config-override layer on top of stock caelestia
|
||||||
|
> (delete a file to revert one), and the installed system ships an **on-box AI
|
||||||
|
> guide** so you can customise and fix it with
|
||||||
|
> [Claude Code](https://claude.com/claude-code) — see
|
||||||
|
> **[Customising the desktop](#customising-the-desktop-on-box-ai-guide)** below.
|
||||||
|
|
||||||
|
## What you get on the installed system
|
||||||
|
|
||||||
|
- **Hyprland + caelestia shell** (Quickshell), restyled with a restrained
|
||||||
|
flat look (no blur/glassmorphism) and the OS's own Moebius-style wallpaper
|
||||||
|
set (dynamic Material palette generated from the wallpaper).
|
||||||
|
- **Themed SDDM login** that mirrors the desktop palette/wallpaper/font
|
||||||
|
(`sudo sddm-theme-sync` re-syncs it after you change wallpaper).
|
||||||
|
- **Omarchy-style keybindings** — `Super+K` opens a searchable cheatsheet of
|
||||||
|
every bind. Universal `Super+C`/`Super+V` copy/paste, `Print` = region
|
||||||
|
screenshot, `Super+Space` launcher, `Super+D` dashboard/calendar,
|
||||||
|
`Super+Grave` workspace overview.
|
||||||
|
- **Software story**: `yay` (AUR), the **Shelly** GUI store on `Super+I`
|
||||||
|
(repos + AUR + Flathub), flatpak preconfigured.
|
||||||
|
- **Btrfs + bootable snapshots**: every package transaction snapshots
|
||||||
|
(snapper + snap-pac), and each snapshot appears as a bootable entry in the
|
||||||
|
**Limine** menu (10-second timeout) — roll back a bad update from the boot
|
||||||
|
screen.
|
||||||
|
- **`nosignal-update`**: one command for snapshot → package upgrade → layer
|
||||||
|
migrations; a Settings → Updates page shows pending counts.
|
||||||
|
- **Gaming (opt-in)**: the `[multilib]` repo is enabled and Omarchy-installer
|
||||||
|
shims are included, so
|
||||||
|
[DeckShift](https://github.com/28allday/DeckShift)-style Steam gaming
|
||||||
|
sessions can be added post-install — including a login fix that keeps a
|
||||||
|
password on cold boots. Not active unless you set it up.
|
||||||
|
- Bluetooth, printing (CUPS + network discovery), power-profiles, zram swap,
|
||||||
|
ufw on, locale/timezone/keymap of your choice.
|
||||||
|
|
||||||
|
## Customising the desktop (on-box AI guide)
|
||||||
|
|
||||||
|
NoSignal OS is meant to be tinkered with, so nothing is locked down. Every
|
||||||
|
NoSignal change lives in a **config-override layer** at `~/.config/caelestia/`
|
||||||
|
on top of stock caelestia — edit a file to change behaviour, delete it to fall
|
||||||
|
back to the default. Updates and rollbacks are safe: `nosignal-update` snapshots
|
||||||
|
before every upgrade, and each snapshot is a bootable entry in the Limine menu.
|
||||||
|
|
||||||
|
To make changes easy — even if you don't know the internals — the installed
|
||||||
|
system ships an **on-box AI skill for [Claude Code](https://claude.com/claude-code)**.
|
||||||
|
It explains exactly what's safe to edit, how to change keybinds / monitors /
|
||||||
|
wallpaper / theme, how updates and snapshots work, how to install software, and
|
||||||
|
how to recover the desktop if the shell ever fails to start.
|
||||||
|
|
||||||
|
- **Just ask.** Run `claude` on a NoSignal machine and it **auto-discovers** the
|
||||||
|
`nosignal` skill — say *"customise my NoSignal desktop"* or *"the bar didn't
|
||||||
|
load, fix it"* and it reads the guide before touching anything.
|
||||||
|
- **Point an assistant at the files directly** if you prefer:
|
||||||
|
- `~/.claude/skills/nosignal/SKILL.md` — the Claude skill (guardrails + tasks)
|
||||||
|
- `~/.local/share/nosignal/ONBOX-AI-NOTES.md` — the full system briefing
|
||||||
|
- `~/.local/share/nosignal/NoSignal-keybindings.md` — the keybinding reference
|
||||||
|
|
||||||
|
The skill is strictly for managing **your installed system** — it carries no
|
||||||
|
build/release tooling, and it always reads the guardrails before editing config.
|
||||||
|
|
||||||
|
## Build the ISO
|
||||||
|
|
||||||
|
The builder runs on an **Arch Linux** (or Arch-based) host with an internet
|
||||||
|
connection — it uses `pacman`, `makepkg`, and `devtools` to assemble the offline
|
||||||
|
package set, so it can't run on a non-Arch distro.
|
||||||
|
|
||||||
|
**1. Install the build dependencies:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo pacman -S --needed git libisoburn squashfs-tools coreutils devtools pacman-contrib
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Clone this repository and enter it:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/28allday/NoSignal-OS.git
|
||||||
|
cd NoSignal-OS
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Download a stock Arch Linux ISO into this folder.** The filename must start
|
||||||
|
with `archlinux-`. Grab the latest from any mirror, e.g.:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -LO https://geo.mirror.pkgbuild.com/iso/latest/archlinux-x86_64.iso
|
||||||
|
```
|
||||||
|
|
||||||
|
(or pick a mirror from <https://archlinux.org/download/>.)
|
||||||
|
|
||||||
|
**4. Run the builder:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./nosignal.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
It downloads the full package closure and builds the AUR pieces in a clean
|
||||||
|
chroot — allow roughly **15–40 minutes** on the first run (you'll be asked for
|
||||||
|
`sudo`). Everything is cached in `./offline/`, so later rebuilds take a couple of
|
||||||
|
minutes.
|
||||||
|
|
||||||
|
**5. Done.** `nosignal-arch-YYYYMMDD.iso` (~4 GB) appears in the folder —
|
||||||
|
Ventoy-compatible and `dd`-bootable. Write it to a USB stick and install (see
|
||||||
|
**Install** below).
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
**1. Write the ISO to a USB stick.** Replace `/dev/sdX` with your USB device
|
||||||
|
(check it with `lsblk` first — this erases the whole stick):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo dd if=nosignal-arch-YYYYMMDD.iso of=/dev/sdX bs=4M status=progress conv=fsync
|
||||||
|
```
|
||||||
|
|
||||||
|
(or just drop the `.iso` onto a [Ventoy](https://www.ventoy.net) USB.)
|
||||||
|
|
||||||
|
**2. Boot the USB** on the target machine — **UEFI only** (no BIOS/MBR).
|
||||||
|
|
||||||
|
**3. Follow the installer.** It is **fully offline** (no network questions) and
|
||||||
|
asks for: hostname, user, password, your region (timezone / locale / keyboard),
|
||||||
|
and the target disk. It then **wipes that disk** — 1G EFI + btrfs with
|
||||||
|
`@`/`@home`/`@snapshots`/`@log` subvolumes and the Limine bootloader — and runs
|
||||||
|
a clean, centred progress screen. **It erases the chosen disk — back up first.**
|
||||||
|
|
||||||
|
**4. Reboot** into the themed SDDM login and your desktop. Connect Wi-Fi from the
|
||||||
|
desktop's network panel; once you're online, run `sudo pacman -Syu` one time to
|
||||||
|
sync the package databases.
|
||||||
|
|
||||||
|
## GPU detection
|
||||||
|
|
||||||
|
The installer auto-detects the GPU(s) and installs the right drivers,
|
||||||
|
borrowing CachyOS [`chwd`](https://github.com/CachyOS/chwd)'s **method** (PCI
|
||||||
|
display-class scan → vendor-id → driver set) but implemented natively, so the
|
||||||
|
ISO stays vanilla Arch (no CachyOS repos / lib32 / cachyos-kernel deps).
|
||||||
|
Multiple GPUs union their package sets (e.g. an Intel/AMD iGPU + an NVIDIA
|
||||||
|
dGPU). All variants are bundled in the offline repo; only the matching subset
|
||||||
|
is installed.
|
||||||
|
|
||||||
|
| Detected | Packages |
|
||||||
|
|----------|----------|
|
||||||
|
| NVIDIA (`10de`) | `nvidia-open-dkms nvidia-utils egl-wayland libva-nvidia-driver nvidia-settings linux-headers` + DRM/KMS setup |
|
||||||
|
| AMD (`1002`) | `mesa vulkan-radeon` |
|
||||||
|
| Intel (`8086`) | `mesa vulkan-intel intel-media-driver` |
|
||||||
|
| VM guest (`1af4`/`1b36`/`1234`/`15ad`/`80ee`) | `qemu-guest-agent spice-vdagent` |
|
||||||
|
| Unknown | `vulkan-swrast` (software fallback) |
|
||||||
|
|
||||||
|
For NVIDIA it also does the Wayland-critical bits (the same ones chwd does):
|
||||||
|
early-loads `nvidia nvidia_modeset nvidia_uvm nvidia_drm` in the initramfs,
|
||||||
|
drops the `kms` hook so nouveau doesn't grab the card, sets
|
||||||
|
`nvidia_drm.modeset=1`, and writes the EGL/VA env to `/etc/environment`.
|
||||||
|
|
||||||
|
## Notes / current limitations
|
||||||
|
|
||||||
|
- **UEFI only.** No BIOS/MBR boot.
|
||||||
|
- **No disk encryption** yet.
|
||||||
|
- **NVIDIA uses the open kernel module** (`nvidia-open-dkms`), which is for
|
||||||
|
Turing and newer (RTX 20-series+). Older Maxwell/Pascal cards would need
|
||||||
|
the proprietary/legacy branch — not yet selected by generation.
|
||||||
|
- The bundled packages are a snapshot from build day — run `sudo pacman -Syu`
|
||||||
|
once online.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
NoSignal OS is a thin builder around excellent upstream work — it bundles,
|
||||||
|
themes, and installs these projects rather than replacing them. Please support
|
||||||
|
the originals. Each remains under its own licence.
|
||||||
|
|
||||||
|
- **[Arch Linux](https://archlinux.org)** — the base system and live ISO.
|
||||||
|
- **[Hyprland](https://hyprland.org)** — the Wayland compositor.
|
||||||
|
- **[caelestia](https://github.com/caelestia-dots/caelestia)** — the desktop
|
||||||
|
dotfiles and shell that define the look and most of the UX. The installed
|
||||||
|
shell is a light soft-fork; the CLI / theming engine is upstream, unchanged.
|
||||||
|
- **[Quickshell](https://quickshell.outfoxxed.me)** — the runtime the shell runs on.
|
||||||
|
- **[SDDM](https://github.com/sddm/sddm)** — the login manager (themed to match).
|
||||||
|
- **[Limine](https://github.com/limine-bootloader/limine)**, with **snapper** and
|
||||||
|
**snap-pac** — bootloader and Btrfs snapshot tooling (bootable snapshots).
|
||||||
|
- **[Omarchy](https://omarchy.org)** — keybinding layout and UX inspiration, and
|
||||||
|
its prebuilt `[omarchy]` package repo for the Limine snapshot tools.
|
||||||
|
- **[CachyOS `chwd`](https://github.com/CachyOS/chwd)** — the GPU-detection
|
||||||
|
*method* (PCI scan → vendor-id → driver set), reimplemented natively here.
|
||||||
|
- Fonts: **JetBrains Mono Nerd Font** and **Material Symbols**.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT — see [LICENSE](LICENSE). The wallpaper art set is the project author's
|
||||||
|
own original work and ships with the ISO under the same licence.
|
||||||
138
assets/fastfetch-config.jsonc
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json",
|
||||||
|
"logo": {
|
||||||
|
"type": "file",
|
||||||
|
"source": "~/.config/fastfetch/logo.txt",
|
||||||
|
"color": { "1": "green" },
|
||||||
|
"padding": {
|
||||||
|
"top": 2,
|
||||||
|
"right": 6,
|
||||||
|
"left": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
"break",
|
||||||
|
{
|
||||||
|
"type": "custom",
|
||||||
|
"format": "\u001b[90m┌──────────────────────Hardware──────────────────────┐"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "host",
|
||||||
|
"key": " PC",
|
||||||
|
"keyColor": "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "cpu",
|
||||||
|
"key": "│ ├",
|
||||||
|
"showPeCoreCount": true,
|
||||||
|
"keyColor": "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "gpu",
|
||||||
|
"key": "│ ├",
|
||||||
|
"detectionMethod": "pci",
|
||||||
|
"keyColor": "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "display",
|
||||||
|
"key": "│ ├",
|
||||||
|
"keyColor": "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "disk",
|
||||||
|
"key": "│ ├",
|
||||||
|
"keyColor": "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "memory",
|
||||||
|
"key": "│ ├",
|
||||||
|
"keyColor": "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "swap",
|
||||||
|
"key": "└ └ ",
|
||||||
|
"keyColor": "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "custom",
|
||||||
|
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
|
||||||
|
},
|
||||||
|
"break",
|
||||||
|
{
|
||||||
|
"type": "custom",
|
||||||
|
"format": "\u001b[90m┌──────────────────────Software──────────────────────┐"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"key": " OS",
|
||||||
|
"keyColor": "blue",
|
||||||
|
"text": "echo \"NoSignal\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "kernel",
|
||||||
|
"key": "│ ├",
|
||||||
|
"keyColor": "blue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "wm",
|
||||||
|
"key": "│ ├",
|
||||||
|
"keyColor": "blue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "de",
|
||||||
|
"key": " DE",
|
||||||
|
"keyColor": "blue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "terminal",
|
||||||
|
"key": "│ ├",
|
||||||
|
"keyColor": "blue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "packages",
|
||||||
|
"key": "│ ├",
|
||||||
|
"keyColor": "blue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "wmtheme",
|
||||||
|
"key": "│ ├",
|
||||||
|
"keyColor": "blue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"key": "│ ├",
|
||||||
|
"keyColor": "blue",
|
||||||
|
"text": "theme=$(caelestia scheme get 2>/dev/null | awk -F': *' '/^Name:/{print $2; exit}'); [ -n \"$theme\" ] || theme=dynamic; echo -e \"$theme \\e[38m●\\e[37m●\\e[36m●\\e[35m●\\e[34m●\\e[33m●\\e[32m●\\e[31m●\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "terminalfont",
|
||||||
|
"key": "└ └",
|
||||||
|
"keyColor": "blue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "custom",
|
||||||
|
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
|
||||||
|
},
|
||||||
|
"break",
|
||||||
|
{
|
||||||
|
"type": "custom",
|
||||||
|
"format": "\u001b[90m┌────────────────────Age / Uptime────────────────────┐"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"key": " OS Age",
|
||||||
|
"keyColor": "magenta",
|
||||||
|
"text": "birth_install=$(stat -c %W /); current=$(date +%s); time_progression=$((current - birth_install)); days_difference=$((time_progression / 86400)); echo $days_difference days"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "uptime",
|
||||||
|
"key": " Uptime",
|
||||||
|
"keyColor": "magenta"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "custom",
|
||||||
|
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
|
||||||
|
},
|
||||||
|
"break"
|
||||||
|
]
|
||||||
|
}
|
||||||
26
assets/fastfetch-logo.txt
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
⢀⣀⣤⣴⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣦⣤⣀⡀
|
||||||
|
⢀⣠⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣄⡀
|
||||||
|
⢀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⡀
|
||||||
|
⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀
|
||||||
|
⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀
|
||||||
|
⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄
|
||||||
|
⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆
|
||||||
|
⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆
|
||||||
|
⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢿⣿⣿⣿⣿⣿⠿⡿⠿⠿⠿⣿⣿⣿⠿⠿⠿⠿⢿⣿⣿⣿⣿⠿⣿⣿⣿⣿⣿⡿⢿⣿⣿⣿⣿⡆
|
||||||
|
⢀⣿⣿⣿⣿⣶⡶⠸⣿⣿⣿⣿⠛⠛⠃⠛⠛⠛⢻⣿⡟⢐⣓⣒⣒⣒⣛⣿⣿⠰⠶⠶⠶ ⣿⣿⣛⡋⢈⣉⡭⠿⢿⣿⡇⢸⣿⣿⣿⣿⣿⡀
|
||||||
|
⢸⣿⣿⣿⣿⣿⢃⡄⢻⣿⣿⣿⣿⣿⠃⣿⣿⡏⢸⣏⡄⢸⣗⣒⣒⣒⣺⣯⣉⡉⣉⣉⣉⣉⣉⣿⡿⢁⣿⣷⡒⣶⣾⣿⡇⢸⣿⣿⣿⣿⣿⡇
|
||||||
|
⣿⣿⣿⣿⠿⢁⣾⣿⣄⠻⢿⣿⡿⢃⣼⣿⣿⡇⣸⣿⡇⢸⠁⣤⣤⡤⢸⣿⣿⣤⣶⣶⣶⢀⣿⣿⣥⣾⠩⣤⠄⣙⢻⣿⣇⠸⠿⠿⠟⣩⣿⣿
|
||||||
|
⣿⣿⣿⣷⣶⣿⣿⣿⣿⣷⣶⣷⣶⣿⣿⣿⣤⣴⣿⣿⣧⣾⣦⣶⣶⣶⣼⣿⣿⣿⣿⣤⣤⣾⣿⣿⣿⣿⣶⣶⣾⣿⣿⣿⣿⣷⣶⣶⣿⣿⣿⣿
|
||||||
|
⣿⣿⣿⣿⣿⣿⣿ ⠙⢿ ⢸⡟⢁⣠⡀⠙⣿⣿⣿⡏⠠⣤⣿⡇⢸⡟⠁⣠⣄⣙⣿ ⠈⢻⡇⢸⣿⡏ ⠘⣿⡇ ⣿⣿⣿⣿⣿⣿⣿⣿
|
||||||
|
⣿⣿⣿⣿⣿⣿⣿ ⣦⡀ ⢸⡄⠘⠿⠟ ⣿⣿⣿⡷⠦ ⢹⡇⢸⣇ ⠿⠄ ⣿ ⢰⡄⠁⢸⡟ ⠘ ⢹⡇ ⠿⢿⣿⣿⣿⣿⣿⣿
|
||||||
|
⢸⣿⣿⣿⣿⣿⣿⣶⣿⣿⣶⣾⣿⣶⣦⣶⣾⣿⣿⣿⣶⣶⣶⣿⣷⣾⣿⣷⣶⣶⣾⣿⣶⣾⣿⣶⣾⣷⣾⣿⣷⣶⣷⣶⣶⣾⣿⣿⣿⣿⣿⡇
|
||||||
|
⠈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁
|
||||||
|
⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇
|
||||||
|
⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏
|
||||||
|
⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏
|
||||||
|
⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋
|
||||||
|
⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁
|
||||||
|
⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁
|
||||||
|
⠈⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠁
|
||||||
|
⠈⠙⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠋⠁
|
||||||
|
⠈⠉⠛⠻⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠟⠛⠉⠁
|
||||||
BIN
assets/plymouth/nosignal/logo.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
8
assets/plymouth/nosignal/nosignal.plymouth
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[Plymouth Theme]
|
||||||
|
Name=NoSignal
|
||||||
|
Description=NoSignal boot splash — NO SIGNAL logo on black
|
||||||
|
ModuleName=script
|
||||||
|
|
||||||
|
[script]
|
||||||
|
ImageDir=/usr/share/plymouth/themes/nosignal
|
||||||
|
ScriptFile=/usr/share/plymouth/themes/nosignal/nosignal.script
|
||||||
43
assets/plymouth/nosignal/nosignal.script
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# NoSignal Plymouth splash — the "NO SIGNAL / 入力信号なし" logo centred on black.
|
||||||
|
# Shows between Limine and the SDDM greeter (and on shutdown/reboot). The logo's
|
||||||
|
# black disc blends into the background, so only the green text floats on black.
|
||||||
|
|
||||||
|
# Solid black background.
|
||||||
|
Window.SetBackgroundTopColor(0, 0, 0);
|
||||||
|
Window.SetBackgroundBottomColor(0, 0, 0);
|
||||||
|
|
||||||
|
screen_width = Window.GetWidth();
|
||||||
|
screen_height = Window.GetHeight();
|
||||||
|
|
||||||
|
# Scale the logo to ~67.5% of the shorter screen edge (0.45 enlarged by half),
|
||||||
|
# keeping aspect ratio, so it is a sensible size on both 1080p and 4K.
|
||||||
|
short_edge = screen_height;
|
||||||
|
if (screen_width < screen_height)
|
||||||
|
short_edge = screen_width;
|
||||||
|
target = short_edge * 0.675;
|
||||||
|
|
||||||
|
orig = Image("logo.png");
|
||||||
|
scale = target / orig.GetHeight();
|
||||||
|
logo.image = orig.Scale(orig.GetWidth() * scale, orig.GetHeight() * scale);
|
||||||
|
logo.sprite = Sprite(logo.image);
|
||||||
|
logo.sprite.SetX(screen_width / 2 - logo.image.GetWidth() / 2);
|
||||||
|
logo.sprite.SetY(screen_height / 2 - logo.image.GetHeight() / 2);
|
||||||
|
|
||||||
|
# Static splash — full opacity, no animation.
|
||||||
|
logo.sprite.SetOpacity(1);
|
||||||
|
|
||||||
|
# Subtle status line (fsck / "press a key" style messages) below the logo.
|
||||||
|
message.sprite = Sprite();
|
||||||
|
message.sprite.SetX(20);
|
||||||
|
message.sprite.SetY(screen_height - 36);
|
||||||
|
fun message_callback(text) {
|
||||||
|
message.image = Image.Text(text, 0.66, 0.84, 0.45); # muted NoSignal green
|
||||||
|
message.sprite.SetImage(message.image);
|
||||||
|
}
|
||||||
|
Plymouth.SetMessageFunction(message_callback);
|
||||||
|
|
||||||
|
# On quit (handing off to the greeter), snap the logo to full opacity.
|
||||||
|
fun quit_callback() {
|
||||||
|
logo.sprite.SetOpacity(1);
|
||||||
|
}
|
||||||
|
Plymouth.SetQuitFunction(quit_callback);
|
||||||
1
assets/scheme-shadotheme.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"name": "shadotheme", "flavour": "default", "mode": "dark", "variant": "tonalspot", "colours": {"primary_paletteKeyColor": "6f72ac", "secondary_paletteKeyColor": "75758e", "tertiary_paletteKeyColor": "9d648f", "neutral_paletteKeyColor": "78767b", "neutral_variant_paletteKeyColor": "777680", "background": "131317", "onBackground": "e5e1e7", "surface": "131317", "surfaceDim": "131317", "surfaceBright": "39393d", "surfaceContainerLowest": "0e0e11", "surfaceContainerLow": "1b1b1f", "surfaceContainer": "1f1f23", "surfaceContainerHigh": "2a292e", "surfaceContainerHighest": "353438", "onSurface": "e5e1e7", "surfaceVariant": "46464f", "onSurfaceVariant": "c7c5d1", "inverseSurface": "e5e1e7", "inverseOnSurface": "303034", "outline": "918f9a", "outlineVariant": "46464f", "shadow": "000000", "scrim": "000000", "surfaceTint": "bfc1ff", "primary": "bfc1ff", "onPrimary": "282b60", "primaryContainer": "6f72ac", "onPrimaryContainer": "000028", "inversePrimary": "565992", "secondary": "c5c4e0", "onSecondary": "2e2f44", "secondaryContainer": "44455c", "onSecondaryContainer": "b3b3ce", "tertiary": "f4b2e2", "onTertiary": "4e1e45", "tertiaryContainer": "ba7eaa", "onTertiaryContainer": "000000", "error": "ffb4ab", "onError": "690005", "errorContainer": "93000a", "onErrorContainer": "ffdad6", "primaryFixed": "e0e0ff", "primaryFixedDim": "bfc1ff", "onPrimaryFixed": "12144a", "onPrimaryFixedVariant": "3e4278", "secondaryFixed": "e1e0fd", "secondaryFixedDim": "c5c4e0", "onSecondaryFixed": "191a2e", "onSecondaryFixedVariant": "44455c", "tertiaryFixed": "ffd7f1", "tertiaryFixedDim": "f4b2e2", "onTertiaryFixed": "35082f", "onTertiaryFixedVariant": "67355d", "term0": "353434", "term1": "a875ff", "term2": "44def5", "term3": "ffdcf2", "term4": "97aad7", "term5": "b29feb", "term6": "9dceff", "term7": "e8d3de", "term8": "ac9fa9", "term9": "bd95ff", "term10": "89ecff", "term11": "fff0f6", "term12": "b4c1dc", "term13": "c8b5f5", "term14": "bae0ff", "term15": "ffffff", "rosewater": "f7eff9", "flamingo": "e8def3", "pink": "e1d8ff", "mauve": "bdb9ff", "red": "bfa6fe", "maroon": "c7b6ed", "peach": "e0c2f9", "yellow": "ffecf3", "green": "c8e3ff", "teal": "d2e0ff", "sky": "cedaff", "sapphire": "b5c5ff", "blue": "aeb8ff", "lavender": "c6c8ff", "klink": "7083d2", "klinkSelection": "6f83d2", "kvisited": "7e73db", "kvisitedSelection": "7d73db", "knegative": "9d69ff", "knegativeSelection": "9b6aff", "kneutral": "c794ff", "kneutralSelection": "c794ff", "kpositive": "60adff", "kpositiveSelection": "60adff", "text": "e5e1e7", "subtext1": "c7c5d1", "subtext0": "918f9a", "overlay2": "7e7c86", "overlay1": "6b6972", "overlay0": "595860", "surface2": "48474e", "surface1": "37373d", "surface0": "25252a", "base": "131317", "mantle": "131317", "crust": "121216", "success": "B5CCBA", "onSuccess": "213528", "successContainer": "374B3E", "onSuccessContainer": "D1E9D6"}}
|
||||||
BIN
assets/wallpapers/bridge-city.png
Normal file
|
After Width: | Height: | Size: 6.5 MiB |
BIN
assets/wallpapers/cloud-harbour.png
Normal file
|
After Width: | Height: | Size: 7.4 MiB |
BIN
assets/wallpapers/desert-monolith.png
Normal file
|
After Width: | Height: | Size: 6.3 MiB |
BIN
assets/wallpapers/desert-outpost.png
Normal file
|
After Width: | Height: | Size: 7.2 MiB |
BIN
assets/wallpapers/foam-sea.png
Normal file
|
After Width: | Height: | Size: 7.1 MiB |
BIN
assets/wallpapers/ice-fleet.png
Normal file
|
After Width: | Height: | Size: 7 MiB |
BIN
assets/wallpapers/sail-tracker.png
Normal file
|
After Width: | Height: | Size: 6.7 MiB |
BIN
assets/wallpapers/sky-towers.png
Normal file
|
After Width: | Height: | Size: 6.4 MiB |
BIN
assets/wallpapers/tidepools.png
Normal file
|
After Width: | Height: | Size: 6.8 MiB |
BIN
assets/wallpapers/wanderer.png
Normal file
|
After Width: | Height: | Size: 6 MiB |
BIN
assets/wallpapers/wave-watchers.png
Normal file
|
After Width: | Height: | Size: 7.2 MiB |
2366
nosignal.sh
Executable file
299
os updates/NoSignal-keybindings.md
Normal file
|
|
@ -0,0 +1,299 @@
|
||||||
|
# NoSignal — Keybinding Map
|
||||||
|
|
||||||
|
> **System:** Arch Linux (rolling), hostname `nosignal`, Wayland compositor **Hyprland**.
|
||||||
|
> **Base:** [Caelestia](https://github.com/caelestia-dots) dotfiles + shell (QuickShell).
|
||||||
|
> **NoSignal layer:** styling flattened, stock apps rebound, overview added, and
|
||||||
|
> **the key layout follows Omarchy's defaults** (see
|
||||||
|
> `omarchy-keys/README.md` for the full mapping and deviations).
|
||||||
|
>
|
||||||
|
> **Modifier legend:** `Super` = Windows/Meta key · `Ctrl` · `Alt` · `Shift`.
|
||||||
|
> `XF86*` = laptop media/function keys. `mouse_up/down` = scroll wheel.
|
||||||
|
>
|
||||||
|
> Bindings are **variable-driven**. The tables below show the *resolved* keys
|
||||||
|
> (variables from `variables.conf` + overrides applied). See "Source map" at the
|
||||||
|
> end for where each value comes from and how to change it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Built-in help (NoSignal addition)
|
||||||
|
|
||||||
|
| Keys | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `Super+K` | Open the searchable on-screen keybinding cheatsheet (Omarchy key) |
|
||||||
|
| `Super+/` | Same (alias) |
|
||||||
|
| `Super+F1` | Same (alias) |
|
||||||
|
|
||||||
|
The cheatsheet is a fuzzel dmenu list — start typing to filter by key or by
|
||||||
|
category tag (`[Window]`, `[Apps]`, `[Audio]`, …). Selecting a line copies that
|
||||||
|
shortcut to the clipboard (if `wl-copy` is present); it never executes anything.
|
||||||
|
|
||||||
|
**This very document is the single source of truth.** The on-screen help is
|
||||||
|
*generated* from the tables below — `nosignal-keybinds` regenerates the list from
|
||||||
|
this `.md` on every launch, so the cheatsheet can never drift from the doc.
|
||||||
|
Edit the keymap here and the help updates itself.
|
||||||
|
|
||||||
|
## 1. Shell & session
|
||||||
|
|
||||||
|
| Keys | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `Super+Space` | Open app launcher (Omarchy key) |
|
||||||
|
| `Super` (tap & release) | Open app launcher (tapping another key/mouse cancels it) |
|
||||||
|
| `Super+Escape` | Session menu (logout / shutdown / reboot) |
|
||||||
|
| `Ctrl+Alt+Delete` | Session menu (alias, old key) |
|
||||||
|
| `Super+D` | Toggle dashboard — calendar + widgets (was hover-only) |
|
||||||
|
| `Super+Alt+Space` | Open Settings (Caelestia nexus) |
|
||||||
|
| `Super+N` | Toggle sidebar |
|
||||||
|
| `Super+Ctrl+K` | Show all shell panels |
|
||||||
|
| `Super+Ctrl+L` | Lock screen (Omarchy key) |
|
||||||
|
| `Super+Alt+L` | Restart shell daemon + re-lock (recover a broken lock) |
|
||||||
|
| `Super+Shift+Comma` | Clear all notifications (Omarchy key) |
|
||||||
|
| `Ctrl+Alt+C` | Clear all notifications (alias, old key) |
|
||||||
|
| `Ctrl+Super+Shift+R` | Kill the Caelestia shell (`qs -c caelestia kill`) |
|
||||||
|
| `Ctrl+Super+Alt+R` | Restart the Caelestia shell |
|
||||||
|
| `Super+Alt+F12` | Fire a test notification (dev/debug) |
|
||||||
|
|
||||||
|
## 2. Overview (NoSignal addition)
|
||||||
|
|
||||||
|
| Keys | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `Super+Grave` | Toggle the overview sidecar (moved off Super+Tab for Omarchy) |
|
||||||
|
|
||||||
|
## 3. Workspaces
|
||||||
|
|
||||||
|
| Keys | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `Super+1`…`9`,`0` | Go to workspace 1–10 |
|
||||||
|
| `Super+Tab` | Next workspace (Omarchy key) |
|
||||||
|
| `Super+Shift+Tab` | Previous workspace (Omarchy key) |
|
||||||
|
| `Super+Ctrl+Tab` | Back to former workspace (Omarchy key) |
|
||||||
|
| `Ctrl+Super+1`…`9`,`0` | Go to workspace **group** 1–10 |
|
||||||
|
| `Ctrl+Super+Left` / `Right` | Previous / next workspace |
|
||||||
|
| `Super+Page_Up` / `Page_Down` | Previous / next workspace |
|
||||||
|
| `Super` + scroll up/down | Next / previous workspace |
|
||||||
|
| `Ctrl+Super` + scroll up/down | Jump workspace group ±10 |
|
||||||
|
| `Super+S` | Toggle special (scratchpad) workspace |
|
||||||
|
|
||||||
|
## 4. Moving windows between workspaces
|
||||||
|
|
||||||
|
| Keys | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `Super+Shift+1`…`9`,`0` | Move window to workspace 1–10 (Omarchy key) |
|
||||||
|
| `Super+Alt+1`…`9`,`0` | Move window to workspace 1–10 (alias, old key) |
|
||||||
|
| `Ctrl+Super+Alt+1`…`9`,`0` | Move window to workspace **group** 1–10 |
|
||||||
|
| `Super+Alt+Page_Up` / `Page_Down` | Move window to workspace ±1 |
|
||||||
|
| `Super+Alt` + scroll up/down | Move window to workspace ±1 |
|
||||||
|
| `Ctrl+Super+Shift+Right` / `Left` | Move window to workspace +1 / −1 |
|
||||||
|
| `Ctrl+Super+Shift+Up` | Move window to special workspace |
|
||||||
|
| `Ctrl+Super+Shift+Down` | Move window to first empty workspace |
|
||||||
|
| `Super+Alt+S` | Move window to special workspace |
|
||||||
|
|
||||||
|
## 5. Window focus, move & resize
|
||||||
|
|
||||||
|
| Keys | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `Super+←/→/↑/↓` | Move focus left/right/up/down |
|
||||||
|
| `Alt+Tab` / `Shift+Alt+Tab` | Cycle focus next / previous window (Omarchy key) |
|
||||||
|
| `Super+Shift+←/→/↑/↓` | Move window in that direction |
|
||||||
|
| `Super+Minus` / `Super+Equal` | Resize narrower / wider (−/+10% width) |
|
||||||
|
| `Super+Shift+Minus` / `Equal` | Resize shorter / taller (−/+10% height) |
|
||||||
|
| `Super+Alt+←/→/↑/↓` | Resize active window |
|
||||||
|
| `Super` + left-drag | Move window (mouse) |
|
||||||
|
| `Super+Z` + drag | Move window (mouse) |
|
||||||
|
| `Super` + right-drag | Resize window (mouse) |
|
||||||
|
| `Super+X` + drag | Resize window (mouse) |
|
||||||
|
| `Ctrl+Super+\` | Center window |
|
||||||
|
| `Ctrl+Super+Alt+\` | Resize to 55%×70% and center |
|
||||||
|
| `Super+Alt+\` | Picture-in-picture (PiP) mode |
|
||||||
|
| `Super+T` | Toggle floating (Omarchy key) |
|
||||||
|
| `Super+J` | Toggle window split (Omarchy key) |
|
||||||
|
| `Super+P` | Pseudo window (Omarchy key) |
|
||||||
|
| `Super+O` | Pin window — show on all workspaces (Omarchy "pop out") |
|
||||||
|
| `Super+F` | Fullscreen |
|
||||||
|
| `Super+Alt+F` | Fullscreen **with borders** |
|
||||||
|
| `Super+W` | Close window (Omarchy key) |
|
||||||
|
|
||||||
|
## 6. Window groups (tabbed stacks)
|
||||||
|
|
||||||
|
| Keys | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `Super+G` | Toggle group on active window (Omarchy key) |
|
||||||
|
| `Super+Alt+G` | Move window out of group (Omarchy key) |
|
||||||
|
| `Super+Alt+Tab` | Cycle to next window in group (Omarchy key) |
|
||||||
|
| `Super+Alt+Shift+Tab` | Cycle to previous window in group (Omarchy key) |
|
||||||
|
| `Ctrl+Alt+Tab` | Change active group member (forward) |
|
||||||
|
| `Ctrl+Shift+Alt+Tab` | Change active group member (backward) |
|
||||||
|
|
||||||
|
## 7. Apps
|
||||||
|
|
||||||
|
| Keys | Action | App (NoSignal default) |
|
||||||
|
|------|--------|----------------------|
|
||||||
|
| `Super+Return` | Terminal | **kitty** |
|
||||||
|
| `Super+Shift+Return` | Browser | **chromium** |
|
||||||
|
| `Super+Shift+B` | Browser (alias) | **chromium** |
|
||||||
|
| `Super+Shift+N` | Text editor | **gnome-text-editor** |
|
||||||
|
| `Super+Shift+F` | File manager | **nautilus** |
|
||||||
|
| `Super+Alt+E` | File manager (alt) | nemo |
|
||||||
|
| `Super+Shift+G` | GitHub Desktop | github-desktop |
|
||||||
|
| `Super+I` | Install software (app store) | **Shelly** |
|
||||||
|
| `Ctrl+Alt+Escape` | Process/task viewer | qps |
|
||||||
|
| `Super+Ctrl+A` | Audio control (Omarchy key) | pavucontrol |
|
||||||
|
| `Ctrl+Alt+V` | Audio control (alias) | pavucontrol |
|
||||||
|
| `Super+Ctrl+H` | Monitor layout editor & profiles | **hyprmoncfg** (TUI) |
|
||||||
|
|
||||||
|
> All app binds use Omarchy's keys. Caelestia's stock app picks
|
||||||
|
> (foot / zen-browser / codium / thunar) are **not installed**; NoSignal points
|
||||||
|
> these binds at shipped apps via `hypr-vars.conf`.
|
||||||
|
|
||||||
|
## 8. Special-workspace toggles
|
||||||
|
|
||||||
|
| Keys | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `Super+Ctrl+T` | System monitor — "Activity" (Omarchy key) |
|
||||||
|
| `Ctrl+Shift+Escape` | System monitor (alias) |
|
||||||
|
| `Super+Shift+M` | Music (Omarchy key) |
|
||||||
|
| `Super+M` | Music player — CLIAmp, floating (default for audio files) |
|
||||||
|
| `Super+Shift+D` | Communication (moved from Super+D — that's the dashboard now) |
|
||||||
|
| `Super+R` | To-do |
|
||||||
|
|
||||||
|
## 9. Screenshots & screen recording
|
||||||
|
|
||||||
|
| Keys | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `Print` | Region capture (crosshair) → clipboard + ~/Pictures/Screenshots |
|
||||||
|
| `Shift+Print` | Full screen → clipboard + ~/Pictures/Screenshots |
|
||||||
|
| `Super+Shift+S` | Region capture, frozen screen (swappy → ~/Pictures/Screenshots) — becomes **Gaming Mode** after opting in (see §14) |
|
||||||
|
| `Super+Shift+Alt+S` | Region capture, live (swappy → ~/Pictures/Screenshots) |
|
||||||
|
| `Super+Print` | Color picker — hyprpicker (Omarchy key) |
|
||||||
|
| `Super+Shift+C` | Color picker (alias) |
|
||||||
|
| `Ctrl+Alt+R` | Record screen (no audio) |
|
||||||
|
| `Super+Alt+R` | Record screen **with sound** |
|
||||||
|
| `Super+Shift+Alt+R` | Record a region |
|
||||||
|
|
||||||
|
## 10. Audio & brightness
|
||||||
|
|
||||||
|
| Keys | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `XF86AudioRaiseVolume` / `LowerVolume` | Volume ±10% (unmutes first) |
|
||||||
|
| `XF86AudioMute` | Mute / unmute output |
|
||||||
|
| `XF86AudioMicMute` | Mute / unmute microphone |
|
||||||
|
| `XF86MonBrightnessUp` / `Down` | Screen brightness up / down |
|
||||||
|
|
||||||
|
## 11. Media playback
|
||||||
|
|
||||||
|
| Keys | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `Ctrl+Super+Space` or `XF86AudioPlay`/`Pause` | Play / pause |
|
||||||
|
| `Ctrl+Super+Equal` or `XF86AudioNext` | Next track |
|
||||||
|
| `Ctrl+Super+Minus` or `XF86AudioPrev` | Previous track |
|
||||||
|
| `XF86AudioStop` | Stop |
|
||||||
|
|
||||||
|
## 12. Clipboard & emoji
|
||||||
|
|
||||||
|
| Keys | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `Super+C` | **Copy** — universal (terminals → Ctrl+Shift+C, GUI → Ctrl+C) |
|
||||||
|
| `Super+V` | **Paste** — universal (terminals → Ctrl+Shift+V, GUI → Ctrl+V) |
|
||||||
|
| `Super+Ctrl+V` | Clipboard history (Omarchy key) |
|
||||||
|
| `Super+Alt+V` | Clipboard history — delete-entry mode |
|
||||||
|
| `Super+Ctrl+E` | Emoji picker (Omarchy key) |
|
||||||
|
| `Super+Period` | Emoji picker (alias) |
|
||||||
|
| `Ctrl+Shift+Alt+V` | Alternate paste — types last clip (needs ydotool, NOT installed: dead bind) |
|
||||||
|
|
||||||
|
## 13. Power
|
||||||
|
|
||||||
|
| Keys | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `Super+Shift+L` | Suspend-then-hibernate |
|
||||||
|
|
||||||
|
## 14. Gaming Mode (opt-in)
|
||||||
|
|
||||||
|
Not active on a fresh install. Opt in on the installed system with
|
||||||
|
`sh ~/deckshift/deckshift.sh`, then
|
||||||
|
`sh ~/.local/share/nosignal/deckshift-login/install-deckshift-login.sh`.
|
||||||
|
|
||||||
|
`Super+Shift+S` is bound in the **base** and self-guards: it launches Gaming Mode
|
||||||
|
only if DeckShift is installed and **does nothing** otherwise. (It replaces
|
||||||
|
caelestia's Super+Shift+S screenshot-freeze for everyone — region shots stay on
|
||||||
|
Print / Shift+Print / Super+Shift+Alt+S.)
|
||||||
|
|
||||||
|
| Keys | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `Super+Shift+S` | Switch to Gaming Mode (full DeckShift gamescope/Steam session via SDDM restart) **if DeckShift is installed**; does nothing otherwise |
|
||||||
|
| `Super+Shift+R` | Exit Gaming Mode back to the desktop — works **inside the gaming session only** (or Steam > Power > Exit to Desktop) |
|
||||||
|
|
||||||
|
Cold boots always show the SDDM password greeter; only the desktop↔gaming
|
||||||
|
switch itself skips the password (one-shot autologin).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Source map (for the install-system handoff)
|
||||||
|
|
||||||
|
All paths below; `~/.config/hypr` → symlink → `~/.local/share/caelestia/hypr`.
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|------|------|
|
||||||
|
| `~/.config/hypr/hyprland.conf` | Top-level; sources everything in order |
|
||||||
|
| `~/.config/hypr/hyprland/keybinds.conf` | **The bind definitions** (uses `$kb*` variables) |
|
||||||
|
| `~/.config/hypr/variables.conf` | **Default key + app values** (`$kbCloseWindow = Super, Q`, etc.) |
|
||||||
|
| `~/.config/caelestia/hypr-vars.conf` | **NoSignal overrides** — app rebinds + flattened styling + **Omarchy `$kb*` remaps** |
|
||||||
|
| `~/.config/caelestia/hypr-user.conf` | **NoSignal overrides** — overview (Super+Grave), help binds, **Omarchy unbinds/extra binds**, `kb_layout = gb`, window rules |
|
||||||
|
| `~/.local/bin/nosignal-keybinds-gen` | **Generator** — parses this `.md` → display-ready cheatsheet lines (single source of truth) |
|
||||||
|
| `~/.local/bin/nosignal-keybinds` | **NoSignal help launcher** — regenerates from this doc each run, shows the fuzzel dmenu |
|
||||||
|
| `~/.local/share/nosignal/keybinds.list` | Generated cache (auto-rebuilt; used as fallback if the doc is missing) |
|
||||||
|
|
||||||
|
**How the override layering works** (load order matters — later wins):
|
||||||
|
`variables.conf` sets stock defaults → `hyprland.conf` sources `hypr-vars.conf`
|
||||||
|
*before* `keybinds.conf` (so `$kb*` remaps take effect) and `hypr-user.conf`
|
||||||
|
*after everything* (so its `unbind`/`bind` lines win).
|
||||||
|
|
||||||
|
**NoSignal deltas vs. stock Caelestia:**
|
||||||
|
- **Key layout remapped to Omarchy defaults** — full mapping table
|
||||||
|
and deliberate deviations in `omarchy-keys/README.md`.
|
||||||
|
- Apps: terminal `foot→kitty`, browser `zen-browser→chromium`,
|
||||||
|
editor `codium→gnome-text-editor`, file manager `thunar→nautilus`.
|
||||||
|
- Added bind: `Super+Grave` → overview sidecar (`exec-once = qs -c overview -d`).
|
||||||
|
- Added bind: `Super+K` (+ `Super+/`, `Super+F1`) → searchable keybinding
|
||||||
|
cheatsheet, **generated from this document** so the two never drift.
|
||||||
|
- Added bind: `Super+I` → Shelly software store (repos + AUR + Flathub); yay
|
||||||
|
and flatpak/Flathub added underneath (see `software-install/`).
|
||||||
|
- Added binds: `Super+C` / `Super+V` universal copy/paste (clipboard
|
||||||
|
history → `Super+Ctrl+V`); `Print` region screenshot;
|
||||||
|
`Super+D` dashboard (communication → `Super+Shift+D`).
|
||||||
|
- Keyboard layout forced to `gb` (UK).
|
||||||
|
- Glassmorphism off: blur/shadows disabled, opacity 1.0, smaller rounding/gaps.
|
||||||
|
- Keys left unbound by the Omarchy remap: `Super+Q`, `Super+E`,
|
||||||
|
`Super+U`, `Super+Comma`, `Super+L`, `Super+Alt+Space`.
|
||||||
|
|
||||||
|
### Rebuild: this whole component ships in Downloads
|
||||||
|
|
||||||
|
Because the live system gets wiped and only Downloads is kept, the entire
|
||||||
|
feature is packaged there and reinstalled with one command per component:
|
||||||
|
|
||||||
|
| File in Downloads | Installs to |
|
||||||
|
|-------------------|-------------|
|
||||||
|
| `nosignal-keybinds-gen` | `~/.local/bin/` (the markdown→list generator) |
|
||||||
|
| `nosignal-keybinds` | `~/.local/bin/` (the cheatsheet launcher) |
|
||||||
|
| `NoSignal-keybindings.md` (this file) | `~/.local/share/nosignal/` (canonical source of truth) |
|
||||||
|
| `hyprland-keybinds-help.conf` | appended to `~/.config/caelestia/hypr-user.conf` (Super+/ + F1 binds) |
|
||||||
|
| `install-keybinds-help.sh` | the installer — run it after rebuild |
|
||||||
|
| `omarchy-keys/` | the Omarchy remap (vars + user conf + installer) |
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sh ~/Downloads/install-keybinds-help.sh
|
||||||
|
sh ~/Downloads/omarchy-keys/install-omarchy-keys.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Installers are idempotent (safe to re-run) and reload Hyprland if it's
|
||||||
|
running. **No PATH dependency:** binds use the absolute path
|
||||||
|
`~/.local/bin/nosignal-keybinds`, and that script locates its generator next to
|
||||||
|
itself — so it works even if `~/.local/bin` isn't on the session `PATH`.
|
||||||
|
No keymap data lives in the scripts; editing this doc is all that's needed to
|
||||||
|
change the on-screen help. Deps: `awk`, `fuzzel`, optional `wl-copy`.
|
||||||
|
|
||||||
|
**To change a binding:** edit `keybinds.conf` for new actions, or redefine the
|
||||||
|
matching `$kb*` variable. Put NoSignal-specific changes in `hypr-user.conf` /
|
||||||
|
`hypr-vars.conf` so they survive a Caelestia update (the `hypr` symlink target
|
||||||
|
gets overwritten on upgrade; the two override files do not).
|
||||||
|
|
||||||
|
---
|
||||||
|
*Keybindings follow Omarchy defaults; the Super+K cheatsheet is generated from this file.*
|
||||||
128
os updates/ONBOX-AI-NOTES.md
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# NoSignal — notes for an AI agent working on this machine
|
||||||
|
|
||||||
|
You are on **NoSignal**: Arch Linux + Hyprland + the Caelestia shell (Quickshell)
|
||||||
|
+ SDDM (themed greeter). A "NoSignal layer" of config and tooling sits ON TOP of
|
||||||
|
stock Caelestia. Read this before changing desktop/system config — your job is to
|
||||||
|
help the user manage their machine safely.
|
||||||
|
|
||||||
|
## Config layout — what is safe to edit
|
||||||
|
|
||||||
|
- `~/.config/caelestia/` — **the NoSignal override layer** (edit here):
|
||||||
|
- `hypr-user.conf` — user binds/rules/exec-once; sourced LAST, so it wins.
|
||||||
|
This is the right place for new keybinds. `unbind =` works here.
|
||||||
|
- `hypr-vars.conf` — `$variables` ($terminal, $kb*, gaps, blur…); sourced
|
||||||
|
BEFORE keybinds.conf consumes them.
|
||||||
|
- `shell.json`, `shell-tokens.json` — Caelestia shell appearance/behaviour.
|
||||||
|
- Deleting any of these files restores stock Caelestia behaviour.
|
||||||
|
- `~/.config/hypr` is a **SYMLINK** into `~/.local/share/caelestia` (the
|
||||||
|
dotfiles clone). Several other `~/.config` entries (foot, fish, fastfetch,
|
||||||
|
uwsm) are symlinks into it too. **Never replace these symlinks with copies**
|
||||||
|
and never edit the clone's hyprland.conf directly — use hypr-user.conf.
|
||||||
|
- `~/.config/hypr/scripts/wsaction.fish` and `configs.fish` are **bash** ports
|
||||||
|
kept under their original .fish names (the stock config calls those paths).
|
||||||
|
Keep the names and the bash shebang.
|
||||||
|
|
||||||
|
## Updates
|
||||||
|
|
||||||
|
- `~/.local/share/nosignal/` — the on-system layer: component sources, the
|
||||||
|
keybindings doc, and `nosignal-update/` (the update command + its migrations).
|
||||||
|
- **`nosignal-update`** = the system update command: snapper snapshot →
|
||||||
|
`yay -Syu` → run any NEW migrations. Applied migrations are tracked in
|
||||||
|
`~/.local/state/nosignal/applied` — do not re-run or edit by hand.
|
||||||
|
|
||||||
|
## Keybindings
|
||||||
|
|
||||||
|
- `Super+K` (aliases `Super+/`, `Super+F1`) = searchable fuzzel cheatsheet.
|
||||||
|
- **Source of truth: `~/.local/share/nosignal/NoSignal-keybindings.md`** — the
|
||||||
|
cheatsheet is GENERATED from it (`nosignal-keybinds-gen`). If you add a bind
|
||||||
|
in hypr-user.conf, document it in that file too.
|
||||||
|
- Layout follows **Omarchy defaults**: Super+Return terminal (kitty, bash),
|
||||||
|
Super+W close, Super+Space launcher, Super+Tab next workspace,
|
||||||
|
**Super+Grave = workspace overview**, Super+I Shelly store,
|
||||||
|
Super+Ctrl+H monitor editor, Super+Shift+number move-to-workspace
|
||||||
|
(bound by KEYCODE — GB layout).
|
||||||
|
- **Super+C / Super+V = universal copy/paste** (class-aware sendshortcut;
|
||||||
|
clipboard HISTORY is Super+Ctrl+V). **Print = region screenshot** →
|
||||||
|
clipboard + ~/Pictures/Screenshots (Shift+Print full). **Super+D =
|
||||||
|
dashboard/calendar** (communication workspace → Super+Shift+D).
|
||||||
|
**Super+M = CLIAmp**, the default music player (floating TUI; audio files
|
||||||
|
open in it via `~/.config/mimeapps.list`). The shell's music/MPRIS panel
|
||||||
|
is **Super+Shift+M**.
|
||||||
|
|
||||||
|
## Shell / desktop architecture gotchas
|
||||||
|
|
||||||
|
- The Caelestia shell runs from `/etc/xdg/quickshell/caelestia`. NoSignal
|
||||||
|
patches THREE things there, each re-applied by its own pacman hook after
|
||||||
|
every caelestia-shell upgrade: the Settings → System → **Updates** page
|
||||||
|
(`PageCompRegistry.qml` + `pages/UpdatesPage.qml`), the Settings →
|
||||||
|
**Additions** page (same registry + `pages/AdditionsPage.qml` +
|
||||||
|
Plugins→Additions relabel in `PageRegistry.qml`; backend =
|
||||||
|
`nosignal-additions` + `additions.json` in the layer tree), and the Wi-Fi
|
||||||
|
wrong-password recovery in `utils/NetworkConnection.qml`.
|
||||||
|
**Do NOT create `~/.config/quickshell/caelestia`** — a user copy shadows
|
||||||
|
/etc/xdg and silently drops the patches (and future ones).
|
||||||
|
If the shell ever fails at login (background but no bar): reproduce with
|
||||||
|
`qs -c caelestia -n` from a TTY (`WAYLAND_DISPLAY=wayland-1`) to get the QML
|
||||||
|
error chain; suspect the page patches first.
|
||||||
|
- The workspace **overview is a separate Quickshell instance**
|
||||||
|
(`~/.config/quickshell/overview`, `qs -c overview -d`, IPC-toggled by
|
||||||
|
Super+Grave). It is independent of the main shell.
|
||||||
|
- The update-status timer (`nosignal-update-check.timer`, user),
|
||||||
|
`hyprmoncfgd` (monitor hotplug profiles, user) and
|
||||||
|
`hyprmoncfgd-rescan.path` (watches `~/.config/hyprmoncfg/profiles/` and
|
||||||
|
bounces the daemon so plain profile saves apply live) are enabled systemd
|
||||||
|
USER units. Status: `systemctl --user status <unit>`.
|
||||||
|
|
||||||
|
## Monitors
|
||||||
|
|
||||||
|
- Use **hyprmoncfg** (`Super+Ctrl+H`, TUI) — it writes
|
||||||
|
`~/.config/hypr/monitors.conf` (+ workspaces.conf), which hypr-user.conf
|
||||||
|
sources. Keep those `source =` lines; hyprmoncfg refuses to write otherwise.
|
||||||
|
- Both the TUI bind and the `hyprmoncfgd` daemon run with
|
||||||
|
`--hypr-config ~/.config/caelestia/hypr-user.conf --monitors-conf
|
||||||
|
~/.config/hypr/monitors.conf` (hyprmoncfg doesn't follow nested `source =`
|
||||||
|
includes). Keep the flags; the daemon's drop-in lives at
|
||||||
|
`~/.config/systemd/user/hyprmoncfgd.service.d/override.conf`.
|
||||||
|
|
||||||
|
## Login / display manager / gaming
|
||||||
|
|
||||||
|
- **SDDM** is the display manager. Config drop-ins in `/etc/sddm.conf.d/`:
|
||||||
|
`10-nosignal.conf` (X11 greeter, uwsm session notes) + `20-sddm-theme.conf`
|
||||||
|
(themed greeter). The desktop session MUST stay `hyprland-uwsm.desktop`
|
||||||
|
(uwsm-managed) — plain hyprland.desktop loses caelestia's session env.
|
||||||
|
- The greeter theme (`/usr/share/sddm/themes/caelestia`) mirrors the desktop
|
||||||
|
scheme. After changing wallpaper, run **`sudo sddm-theme-sync`** to update
|
||||||
|
the login screen (it does not auto-follow).
|
||||||
|
- **Gaming Mode is OPT-IN**: run `sh ~/deckshift/deckshift.sh`, then
|
||||||
|
`sh ~/.local/share/nosignal/deckshift-login/install-deckshift-login.sh`
|
||||||
|
(re-run the fix after any deckshift.sh re-run). Then Super+Shift+S enters
|
||||||
|
the full gamescope/Steam session; Super+Shift+R (inside it) returns. Cold
|
||||||
|
boots always show the password greeter (one-shot autologin). `[multilib]`
|
||||||
|
is enabled and `omarchy-pkg-add` & friends are shims in `~/.local/bin`.
|
||||||
|
|
||||||
|
## Packages / system
|
||||||
|
|
||||||
|
- `yay` for AUR; **Shelly** (`shelly-ui`, Super+I) is the GUI store
|
||||||
|
(repos + AUR + Flathub). Flathub remote is preconfigured. Settings →
|
||||||
|
**Additions** installs curated optional software (DeckShift, Spotify,
|
||||||
|
Once, Obsidian, OBS, Claude Code, Codex, opencode) from official sources
|
||||||
|
only — extend via `additions.json` in the layer tree, no code changes.
|
||||||
|
- The firewall is **ufw**, enabled with Omarchy-style defaults — it is easy
|
||||||
|
to miss because nothing advertises it: `sudo ufw status`.
|
||||||
|
- The `fish` package is REQUIRED by caelestia-meta — do not remove it, even
|
||||||
|
though bash is the login/terminal shell.
|
||||||
|
- Btrfs + snapper: every pacman transaction snapshots (snap-pac); snapshots
|
||||||
|
are BOOTABLE from the Limine menu (limine-snapper-sync, UKI at
|
||||||
|
/boot/EFI/Linux/nosignal_linux.efi). Worst case: reboot → pick a snapshot.
|
||||||
|
- `[omarchy]` repo in pacman.conf provides prebuilt limine-snapper tools.
|
||||||
|
- omarchy-send is installed (LAN file transfer; receive dir `~/Omarchy-Send`,
|
||||||
|
TUI in launcher, CLI: `omarchy-send -to <alias> <files>`). Its config
|
||||||
|
contains a private key — never copy/share `~/.config/omarchy-send/`.
|
||||||
|
|
||||||
|
## Theme
|
||||||
|
|
||||||
|
- Wallpapers: `~/Pictures/Wallpapers/nosignal/` (the OS art set; default
|
||||||
|
foam-sea.png). The colour scheme is Caelestia's **dynamic** Material scheme,
|
||||||
|
generated FROM the current wallpaper — change wallpaper via the Caelestia
|
||||||
|
UI or `caelestia wallpaper -f <file>` and the palette follows. The SDDM
|
||||||
|
greeter does NOT auto-follow: run `sudo sddm-theme-sync` after.
|
||||||
34
os updates/additions-extra/README.md
Executable file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# additions-extra (Settings → Additions: 7 new items)
|
||||||
|
|
||||||
|
Merges seven items into `additions.json` (8 → 15 items).
|
||||||
|
|
||||||
|
Adds seven curated optional apps to Settings → Additions. All follow the
|
||||||
|
manifest policy (**official repos `[extra]`/`[omarchy]`, or official upstream
|
||||||
|
installers — no AUR, no Flatpak**) and are **GPU-agnostic** in wording and
|
||||||
|
behaviour (so they're correct on AMD boxes too).
|
||||||
|
|
||||||
|
| id | name | source | notes |
|
||||||
|
|----|------|--------|-------|
|
||||||
|
| `ollama` | Ollama | `[extra]` | install picks `ollama-cuda` (NVIDIA) / `ollama-rocm` (AMD) / `ollama` (CPU) by detecting the GPU vendor via `lspci` — stays agnostic |
|
||||||
|
| `lmstudio` | LM Studio | `[omarchy]` | `lmstudio-bin` (Provides/Conflicts `lmstudio`) |
|
||||||
|
| `dropbox` | Dropbox | `[omarchy]` | daemon + cli + nautilus-dropbox; enables the user service; links account on first start |
|
||||||
|
| `tailscale` | Tailscale | `[extra]` | installs + enables `tailscaled`; user runs `sudo tailscale up` |
|
||||||
|
| `pinta` | Pinta | `[omarchy]` | Paint.NET-style image editor |
|
||||||
|
| `kdenlive` | Kdenlive | `[extra]` | video editor; hardware-accelerated export via ffmpeg |
|
||||||
|
| `pi` | Pi Coding Agent | `curl pi.dev/install.sh` | AI coding-agent CLI, same family as claude-code/codex/opencode |
|
||||||
|
|
||||||
|
## Files
|
||||||
|
- `additions.json` — the **full merged manifest** (15 items) for the builder to
|
||||||
|
ship as the canonical Settings→Additions manifest.
|
||||||
|
- `merge-additions.sh` — idempotent merge of the 7 new items (by id) into an
|
||||||
|
already-installed system's manifest (skips ones already present). No root.
|
||||||
|
- `migrations/1781449200-additions-extra.sh` — delegates to it.
|
||||||
|
|
||||||
|
## Builder integration
|
||||||
|
Replace the layer's `additions-installer/additions.json` with the bundled
|
||||||
|
15-item file (or cherry-pick the 7 new entries). The merge script covers
|
||||||
|
already-installed machines on `nosignal-update`.
|
||||||
|
|
||||||
|
## Icon note
|
||||||
|
`kdenlive` uses `movie_edit` and `pi` uses `robot_2` — newer Material Symbols.
|
||||||
|
If the shipped icon font is older, fall back to `movie` and `smart_toy`.
|
||||||
125
os updates/additions-extra/additions.json
Executable file
|
|
@ -0,0 +1,125 @@
|
||||||
|
{
|
||||||
|
"_comment": "NoSignal additions manifest — drives Settings → Additions. Each item: id (slug), name, desc (one line, shown until installed), icon (Material Symbol), check (sh, exit 0 = installed), install (sh, runs in a visible floating terminal — interactive sudo is fine). Sources policy: official pacman repos ([extra]/[omarchy]) or official upstream installers/git only — no AUR, no Flatpak.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "deckshift",
|
||||||
|
"name": "DeckShift Gaming Mode",
|
||||||
|
"desc": "Steam Big Picture gamescope session (Super+Shift+S)",
|
||||||
|
"icon": "sports_esports",
|
||||||
|
"check": "test -x /usr/local/bin/switch-to-gaming",
|
||||||
|
"install": "set -e; [ -d \"$HOME/deckshift/.git\" ] || git clone https://git.no-signal.uk/nosignal/deckshift.git \"$HOME/deckshift\"; cd \"$HOME/deckshift\"; chmod +x deckshift.sh; ./deckshift.sh; sh \"$HOME/.local/share/nosignal/deckshift-login/install-deckshift-login.sh\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "spotify",
|
||||||
|
"name": "Spotify",
|
||||||
|
"desc": "Music streaming client",
|
||||||
|
"icon": "music_note",
|
||||||
|
"check": "pacman -Q spotify",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm spotify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "once",
|
||||||
|
"name": "Once",
|
||||||
|
"desc": "Basecamp self-hosted web app manager",
|
||||||
|
"icon": "deployed_code",
|
||||||
|
"check": "pacman -Q once-bin",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm once-bin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "obsidian",
|
||||||
|
"name": "Obsidian",
|
||||||
|
"desc": "Knowledge base and Markdown notes",
|
||||||
|
"icon": "edit_note",
|
||||||
|
"check": "pacman -Q obsidian",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm obsidian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "obs",
|
||||||
|
"name": "OBS Studio",
|
||||||
|
"desc": "Screen recording and streaming (+ hardware encoders)",
|
||||||
|
"icon": "videocam",
|
||||||
|
"check": "sh \"$HOME/.local/share/nosignal/additions-installer/obs-extras.sh\" check",
|
||||||
|
"install": "sh \"$HOME/.local/share/nosignal/additions-installer/obs-extras.sh\" install"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "claude-code",
|
||||||
|
"name": "Claude Code",
|
||||||
|
"desc": "Anthropic terminal coding agent",
|
||||||
|
"icon": "smart_toy",
|
||||||
|
"check": "command -v claude",
|
||||||
|
"install": "curl -fsSL https://claude.ai/install.sh | bash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "codex",
|
||||||
|
"name": "Codex CLI",
|
||||||
|
"desc": "OpenAI terminal coding agent",
|
||||||
|
"icon": "code",
|
||||||
|
"check": "command -v codex",
|
||||||
|
"install": "curl -fsSL https://chatgpt.com/codex/install.sh | sh"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "opencode",
|
||||||
|
"name": "opencode",
|
||||||
|
"desc": "Open-source terminal coding agent",
|
||||||
|
"icon": "data_object",
|
||||||
|
"check": "command -v opencode",
|
||||||
|
"install": "curl -fsSL https://opencode.ai/install | bash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ollama",
|
||||||
|
"name": "Ollama",
|
||||||
|
"desc": "Run LLMs locally (GPU-accelerated)",
|
||||||
|
"icon": "neurology",
|
||||||
|
"check": "pacman -Q ollama",
|
||||||
|
"install": "gpu=$(lspci -nn 2>/dev/null | grep -iE 'VGA|3D|Display'); if echo \"$gpu\" | grep -qi '10de'; then pkg=ollama-cuda; elif echo \"$gpu\" | grep -qi '1002'; then pkg=ollama-rocm; else pkg=ollama; fi; echo \"Installing $pkg\"; sudo pacman -S --needed --noconfirm \"$pkg\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "lmstudio",
|
||||||
|
"name": "LM Studio",
|
||||||
|
"desc": "Desktop app to run LLMs locally (GPU-accelerated)",
|
||||||
|
"icon": "psychology",
|
||||||
|
"check": "pacman -Q lmstudio-bin || pacman -Q lmstudio",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm lmstudio-bin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dropbox",
|
||||||
|
"name": "Dropbox",
|
||||||
|
"desc": "Cloud file sync and sharing",
|
||||||
|
"icon": "cloud_sync",
|
||||||
|
"check": "pacman -Q dropbox",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm dropbox dropbox-cli nautilus-dropbox; systemctl --user enable --now dropbox 2>/dev/null || true; echo 'Dropbox installed — it will open a browser to link your account on first start.'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tailscale",
|
||||||
|
"name": "Tailscale",
|
||||||
|
"desc": "Mesh VPN to connect your devices",
|
||||||
|
"icon": "vpn_lock",
|
||||||
|
"check": "pacman -Q tailscale",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm tailscale; sudo systemctl enable --now tailscaled; echo 'Tailscale installed. Connect with: sudo tailscale up'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pinta",
|
||||||
|
"name": "Pinta",
|
||||||
|
"desc": "Simple image editor (Paint.NET-style)",
|
||||||
|
"icon": "brush",
|
||||||
|
"check": "pacman -Q pinta",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm pinta"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kdenlive",
|
||||||
|
"name": "Kdenlive",
|
||||||
|
"desc": "Video editor (hardware-accelerated export)",
|
||||||
|
"icon": "movie_edit",
|
||||||
|
"check": "pacman -Q kdenlive",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm kdenlive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pi",
|
||||||
|
"name": "Pi Coding Agent",
|
||||||
|
"desc": "Interactive AI coding agent (multi-provider)",
|
||||||
|
"icon": "robot_2",
|
||||||
|
"check": "command -v pi",
|
||||||
|
"install": "curl -fsSL https://pi.dev/install.sh | sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
25
os updates/additions-extra/merge-additions.sh
Executable file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# merge-additions.sh — idempotent. User-level (no root).
|
||||||
|
#
|
||||||
|
# Adds this component's additions.json items (by id) into the installed
|
||||||
|
# Settings -> Additions manifest, skipping ones already present. For the ISO the
|
||||||
|
# builder should instead ship this additions.json as the canonical manifest.
|
||||||
|
set -eu
|
||||||
|
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
LIVE="$HERE/../additions-installer/additions.json"
|
||||||
|
SRC="$HERE/additions.json"
|
||||||
|
[ -f "$LIVE" ] || { echo "live additions.json not found at $LIVE" >&2; exit 1; }
|
||||||
|
[ -f "$SRC" ] || { echo "component additions.json missing" >&2; exit 1; }
|
||||||
|
cp -a "$LIVE" "$LIVE.bak.$(date +%Y%m%d%H%M%S)" 2>/dev/null || true
|
||||||
|
python3 - "$LIVE" "$SRC" <<'PY'
|
||||||
|
import json,sys
|
||||||
|
live_p,src_p=sys.argv[1:3]
|
||||||
|
live=json.load(open(live_p)); src=json.load(open(src_p))
|
||||||
|
have={i["id"] for i in live["items"]}
|
||||||
|
added=[i for i in src["items"] if i["id"] not in have]
|
||||||
|
live["items"].extend(added)
|
||||||
|
json.dump(live,open(live_p,"w"),indent=2,ensure_ascii=False); open(live_p,"a").write("\n")
|
||||||
|
print("merged:", ", ".join(i["id"] for i in added) if added else "(none new)")
|
||||||
|
print("total items:", len(live["items"]))
|
||||||
|
PY
|
||||||
|
echo "additions-extra: ok"
|
||||||
120
os updates/additions-installer/AdditionsPage.qml
Executable file
|
|
@ -0,0 +1,120 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell.Io
|
||||||
|
import Caelestia.Config
|
||||||
|
import qs.components
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.nexus.common
|
||||||
|
|
||||||
|
// NoSignal: the Additions settings page (replaces the upstream Plugins
|
||||||
|
// placeholder). Optional software installed on demand from official sources
|
||||||
|
// (pacman repos / upstream installers — no AUR, no Flatpak). Items come from
|
||||||
|
// the additions.json manifest via the status cache written by
|
||||||
|
// nosignal-additions; Install runs in a visible floating terminal so
|
||||||
|
// git/sudo/pacman output and prompts stay in front of the user.
|
||||||
|
PageBase {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title: qsTr("Additions")
|
||||||
|
|
||||||
|
property var status: ({})
|
||||||
|
readonly property var items: status.items || []
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: parent.top
|
||||||
|
width: root.cappedWidth
|
||||||
|
spacing: Tokens.spacing.extraSmall / 2
|
||||||
|
|
||||||
|
// The Process objects live INSIDE the layout (its `data` accepts
|
||||||
|
// non-visual objects) — PageBase's default property is a single
|
||||||
|
// `Item`, so declaring them at page level kills the whole shell.
|
||||||
|
// Same pattern as UpdatesPage / the upstream AboutPage.
|
||||||
|
|
||||||
|
// Read the cached status (instant).
|
||||||
|
Process {
|
||||||
|
id: readProc
|
||||||
|
|
||||||
|
running: true
|
||||||
|
command: ["sh", "-c", "cat \"${XDG_STATE_HOME:-$HOME/.local/state}/nosignal/additions-status.json\" 2>/dev/null"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
try {
|
||||||
|
root.status = JSON.parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
root.status = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh the cache on demand (re-runs every item's check).
|
||||||
|
Process {
|
||||||
|
id: checkProc
|
||||||
|
|
||||||
|
command: ["sh", "-c", "\"$HOME/.local/bin/nosignal-additions\" status >/dev/null 2>&1"]
|
||||||
|
onExited: readProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run an installer in a visible floating terminal.
|
||||||
|
Process {
|
||||||
|
id: installProc
|
||||||
|
|
||||||
|
property string addId: ""
|
||||||
|
|
||||||
|
command: ["kitty", "--class", "TUI.float", "-e", "sh", "-c", "\"$HOME/.local/bin/nosignal-additions\" install " + addId + "; printf '\\nPress Enter to close...'; read _"]
|
||||||
|
onExited: readProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionHeader {
|
||||||
|
text: qsTr("Optional software")
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.items
|
||||||
|
|
||||||
|
NavRow {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
first: index === 0
|
||||||
|
last: index === root.items.length - 1
|
||||||
|
icon: modelData.icon || "extension"
|
||||||
|
label: modelData.name
|
||||||
|
status: modelData.installed ? qsTr("Installed") : modelData.desc
|
||||||
|
onClicked: {
|
||||||
|
if (!modelData.installed && !installProc.running) {
|
||||||
|
installProc.addId = modelData.id;
|
||||||
|
installProc.running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoRow {
|
||||||
|
visible: root.items.length === 0
|
||||||
|
first: true
|
||||||
|
last: true
|
||||||
|
label: qsTr("No additions manifest")
|
||||||
|
value: "—"
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionHeader {
|
||||||
|
text: qsTr("Actions")
|
||||||
|
}
|
||||||
|
|
||||||
|
NavRow {
|
||||||
|
first: true
|
||||||
|
last: true
|
||||||
|
icon: "refresh"
|
||||||
|
label: qsTr("Re-check installed state")
|
||||||
|
status: installProc.running ? qsTr("Install running in terminal…") : (checkProc.running ? qsTr("Checking…") : qsTr("Refreshes the list above"))
|
||||||
|
onClicked: {
|
||||||
|
if (!checkProc.running)
|
||||||
|
checkProc.running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
91
os updates/additions-installer/README.md
Executable file
|
|
@ -0,0 +1,91 @@
|
||||||
|
# additions-installer — Settings → Additions (optional software on demand)
|
||||||
|
|
||||||
|
Turns the Settings app's placeholder **Plugins** section
|
||||||
|
into an **Additions** page: a curated list of optional software with
|
||||||
|
one-click installs.
|
||||||
|
|
||||||
|
## Sources policy
|
||||||
|
|
||||||
|
**No AUR. No Flatpak.** Official pacman repos or official upstream
|
||||||
|
installers/git only:
|
||||||
|
|
||||||
|
| Item | Source |
|
||||||
|
|------|--------|
|
||||||
|
| DeckShift Gaming Mode | official git — `https://git.no-signal.uk/nosignal/deckshift.git` → `deckshift.sh` → **deckshift-login installer** (the one-shot-autologin fix, always run after deckshift.sh) |
|
||||||
|
| Spotify | `[omarchy]` pacman repo (proprietary — no upstream git exists) |
|
||||||
|
| Once (Basecamp) | `[omarchy]` pacman repo (`once-bin`) |
|
||||||
|
| Obsidian | Arch `[extra]` |
|
||||||
|
| OBS Studio | Arch `[extra]` (`obs-studio`) + **GPU-detected encoder runtimes** via `obs-extras.sh` (see below) |
|
||||||
|
| Claude Code | official installer — `curl -fsSL https://claude.ai/install.sh \| bash` |
|
||||||
|
| Codex CLI | official installer — `curl -fsSL https://chatgpt.com/codex/install.sh \| sh` |
|
||||||
|
| opencode | official installer — `curl -fsSL https://opencode.ai/install \| bash` |
|
||||||
|
|
||||||
|
## OBS hardware encoders (obs-extras.sh)
|
||||||
|
|
||||||
|
First-round testing found `obs-studio` alone ships only the x264
|
||||||
|
software encoder path. The ISO lands on arbitrary GPUs (AMD / Intel /
|
||||||
|
NVIDIA, any mix), so the OBS entry delegates check AND install to
|
||||||
|
**`obs-extras.sh`**, which detects GPUs via `lspci` per machine:
|
||||||
|
|
||||||
|
- **AMD** — VAAPI (H.264/HEVC/AV1) ships inside mesa: nothing to add.
|
||||||
|
- **Intel** — installs `intel-media-driver` + `vpl-gpu-rt` (QSV).
|
||||||
|
- **NVIDIA** — NVENC comes with the proprietary driver; deliberately
|
||||||
|
not auto-installed (driver branch is a system decision — see
|
||||||
|
gaming-enablement); prints a pointer if the driver is absent.
|
||||||
|
|
||||||
|
Because the *check* also requires the detected runtimes, an OBS
|
||||||
|
installed without them shows as not-installed — clicking Install
|
||||||
|
completes the set idempotently.
|
||||||
|
|
||||||
|
## How it works (same mechanism as the Updates page)
|
||||||
|
|
||||||
|
- **`additions.json`** — the manifest, single source of truth. Per item:
|
||||||
|
`id`, `name`, `desc`, `icon` (Material Symbol), `check` (sh, exit 0 =
|
||||||
|
installed), `install` (sh). Edit this file to add/remove items — no
|
||||||
|
code changes needed.
|
||||||
|
- **`nosignal-additions`** (`~/.local/bin`) — `status` re-runs every check
|
||||||
|
and writes `~/.local/state/nosignal/additions-status.json`;
|
||||||
|
`install <id>` runs the item's installer then re-statuses;` list`.
|
||||||
|
- **`AdditionsPage.qml`** — reads the status cache; each item is a row
|
||||||
|
(Installed ✓ or description + click-to-install). Installs run in a
|
||||||
|
visible floating terminal (`kitty --class TUI.float`) so git/sudo/
|
||||||
|
pacman prompts stay in front of the user. Modeled line-for-line on
|
||||||
|
UpdatesPage (Process objects inside the layout — see the comment).
|
||||||
|
- **`patch-additions-page.sh`** — root-run, idempotent: installs the
|
||||||
|
page QML, swaps the *remaining* System `PlaceholderComp` (the Plugins
|
||||||
|
stub) for the page, and relabels the menu entry
|
||||||
|
Plugins → Additions / "Install optional software".
|
||||||
|
**Ordering: requires the Updates-page patch first** — before it,
|
||||||
|
the first placeholder is the Updates stub (guarded: warns and skips).
|
||||||
|
Backups at `*.pre-nosignal-additions`; warns and degrades gracefully on
|
||||||
|
upstream drift.
|
||||||
|
- **pacman hook** (`nosignal-additions-page.hook`) re-applies after every
|
||||||
|
caelestia-shell upgrade.
|
||||||
|
|
||||||
|
## Packaging
|
||||||
|
|
||||||
|
- Ship the component in the layer tree + COPY-list; migration
|
||||||
|
`1781420400-additions-installer.sh` (sorts after the cliamp-music migration; add to the
|
||||||
|
baked `applied` list).
|
||||||
|
- The patch can be baked at image build (after the Updates-page patch).
|
||||||
|
- Do NOT pre-install any of the additions — the page is the opt-in.
|
||||||
|
|
||||||
|
## Version pins / drift
|
||||||
|
|
||||||
|
- Registry patch pinned against caelestia-shell **2.0.2** (same pin as
|
||||||
|
the Updates page; both patch scripts warn-and-degrade on drift).
|
||||||
|
- The three `curl | sh` installers and the DeckShift git URL are
|
||||||
|
upstream-controlled endpoints; if one moves, only `additions.json`
|
||||||
|
needs editing.
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
1. Settings → System shows **Additions** (not "Plugins"); page lists 8
|
||||||
|
items with sensible installed/not-installed state.
|
||||||
|
2. Install something small (e.g. Obsidian) → floating terminal, sudo
|
||||||
|
prompt, pacman runs → row flips to "Installed" after the terminal
|
||||||
|
closes.
|
||||||
|
3. "Re-check installed state" refreshes after installing/removing
|
||||||
|
something from a terminal.
|
||||||
|
4. DeckShift entry: full chain (clone → deckshift.sh → deckshift-login
|
||||||
|
fix), then the Gaming Mode checklist items apply (Super+Shift+S etc.).
|
||||||
125
os updates/additions-installer/additions.json
Executable file
|
|
@ -0,0 +1,125 @@
|
||||||
|
{
|
||||||
|
"_comment": "NoSignal additions manifest — drives Settings → Additions. Each item: id (slug), name, desc (one line, shown until installed), icon (Material Symbol), check (sh, exit 0 = installed), install (sh, runs in a visible floating terminal — interactive sudo is fine). Sources policy: official pacman repos ([extra]/[omarchy]) or official upstream installers/git only — no AUR, no Flatpak.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "deckshift",
|
||||||
|
"name": "DeckShift Gaming Mode",
|
||||||
|
"desc": "Steam Big Picture gamescope session (Super+Shift+S)",
|
||||||
|
"icon": "sports_esports",
|
||||||
|
"check": "test -x /usr/local/bin/switch-to-gaming",
|
||||||
|
"install": "set -e; [ -d \"$HOME/deckshift/.git\" ] || git clone https://git.no-signal.uk/nosignal/deckshift.git \"$HOME/deckshift\"; cd \"$HOME/deckshift\"; chmod +x deckshift.sh; ./deckshift.sh; sh \"$HOME/.local/share/nosignal/deckshift-login/install-deckshift-login.sh\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "spotify",
|
||||||
|
"name": "Spotify",
|
||||||
|
"desc": "Music streaming client",
|
||||||
|
"icon": "music_note",
|
||||||
|
"check": "pacman -Q spotify",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm spotify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "once",
|
||||||
|
"name": "Once",
|
||||||
|
"desc": "Basecamp self-hosted web app manager (installs + starts Docker)",
|
||||||
|
"icon": "deployed_code",
|
||||||
|
"check": "pacman -Q once-bin",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm once-bin docker; sudo systemctl enable --now docker.service; sudo usermod -aG docker \"$USER\"; echo 'Once + Docker installed and Docker is running. Log out and back in (or run: newgrp docker) so docker works without sudo.'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "obsidian",
|
||||||
|
"name": "Obsidian",
|
||||||
|
"desc": "Knowledge base and Markdown notes",
|
||||||
|
"icon": "edit_note",
|
||||||
|
"check": "pacman -Q obsidian",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm obsidian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "obs",
|
||||||
|
"name": "OBS Studio",
|
||||||
|
"desc": "Screen recording and streaming (+ hardware encoders)",
|
||||||
|
"icon": "videocam",
|
||||||
|
"check": "sh \"$HOME/.local/share/nosignal/additions-installer/obs-extras.sh\" check",
|
||||||
|
"install": "sh \"$HOME/.local/share/nosignal/additions-installer/obs-extras.sh\" install"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "claude-code",
|
||||||
|
"name": "Claude Code",
|
||||||
|
"desc": "Anthropic terminal coding agent",
|
||||||
|
"icon": "smart_toy",
|
||||||
|
"check": "command -v claude",
|
||||||
|
"install": "curl -fsSL https://claude.ai/install.sh | bash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "codex",
|
||||||
|
"name": "Codex CLI",
|
||||||
|
"desc": "OpenAI terminal coding agent",
|
||||||
|
"icon": "code",
|
||||||
|
"check": "command -v codex",
|
||||||
|
"install": "curl -fsSL https://chatgpt.com/codex/install.sh | sh"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "opencode",
|
||||||
|
"name": "opencode",
|
||||||
|
"desc": "Open-source terminal coding agent",
|
||||||
|
"icon": "data_object",
|
||||||
|
"check": "command -v opencode",
|
||||||
|
"install": "curl -fsSL https://opencode.ai/install | bash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ollama",
|
||||||
|
"name": "Ollama",
|
||||||
|
"desc": "Run LLMs locally (GPU-accelerated)",
|
||||||
|
"icon": "neurology",
|
||||||
|
"check": "pacman -Q ollama",
|
||||||
|
"install": "gpu=$(lspci -nn 2>/dev/null | grep -iE 'VGA|3D|Display'); if echo \"$gpu\" | grep -qi '10de'; then pkg=ollama-cuda; elif echo \"$gpu\" | grep -qi '1002'; then pkg=ollama-rocm; else pkg=ollama; fi; echo \"Installing $pkg\"; sudo pacman -S --needed --noconfirm \"$pkg\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "lmstudio",
|
||||||
|
"name": "LM Studio",
|
||||||
|
"desc": "Desktop app to run LLMs locally (GPU-accelerated)",
|
||||||
|
"icon": "psychology",
|
||||||
|
"check": "pacman -Q lmstudio-bin || pacman -Q lmstudio",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm lmstudio-bin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dropbox",
|
||||||
|
"name": "Dropbox",
|
||||||
|
"desc": "Cloud file sync and sharing",
|
||||||
|
"icon": "cloud_sync",
|
||||||
|
"check": "pacman -Q dropbox",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm dropbox dropbox-cli nautilus-dropbox; systemctl --user enable --now dropbox 2>/dev/null || true; echo 'Dropbox installed — it will open a browser to link your account on first start.'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tailscale",
|
||||||
|
"name": "Tailscale",
|
||||||
|
"desc": "Mesh VPN to connect your devices",
|
||||||
|
"icon": "vpn_lock",
|
||||||
|
"check": "pacman -Q tailscale",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm tailscale; sudo systemctl enable --now tailscaled; echo 'Tailscale installed. Connect with: sudo tailscale up'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pinta",
|
||||||
|
"name": "Pinta",
|
||||||
|
"desc": "Simple image editor (Paint.NET-style)",
|
||||||
|
"icon": "brush",
|
||||||
|
"check": "pacman -Q pinta",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm pinta"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kdenlive",
|
||||||
|
"name": "Kdenlive",
|
||||||
|
"desc": "Video editor (hardware-accelerated export)",
|
||||||
|
"icon": "movie_edit",
|
||||||
|
"check": "pacman -Q kdenlive",
|
||||||
|
"install": "sudo pacman -S --needed --noconfirm kdenlive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pi",
|
||||||
|
"name": "Pi Coding Agent",
|
||||||
|
"desc": "Interactive AI coding agent (multi-provider)",
|
||||||
|
"icon": "robot_2",
|
||||||
|
"check": "command -v pi",
|
||||||
|
"install": "curl -fsSL https://pi.dev/install.sh | sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
51
os updates/additions-installer/install-additions-installer.sh
Executable file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# install-additions-installer.sh — Settings → Additions page: install
|
||||||
|
# optional software on demand from official sources (no AUR, no Flatpak).
|
||||||
|
#
|
||||||
|
# - nosignal-additions -> ~/.local/bin (status cache + installer runner)
|
||||||
|
# - additions.json -> stable on-system copy (the manifest)
|
||||||
|
# - AdditionsPage.qml -> patched into caelestia-shell (sudo)
|
||||||
|
# - pacman hook -> re-applies the patch after shell upgrades
|
||||||
|
#
|
||||||
|
# Safe to re-run (idempotent). Needs sudo for the QML patch + hook.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SRC=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
BIN="$HOME/.local/bin"
|
||||||
|
SHARE="$HOME/.local/share/nosignal/additions-installer"
|
||||||
|
|
||||||
|
# 1. Backend + manifest.
|
||||||
|
mkdir -p "$BIN" "$SHARE"
|
||||||
|
install -m 0755 "$SRC/nosignal-additions" "$BIN/nosignal-additions"
|
||||||
|
install -m 0644 "$SRC/additions.json" "$SHARE/additions.json"
|
||||||
|
install -m 0755 "$SRC/obs-extras.sh" "$SHARE/obs-extras.sh"
|
||||||
|
|
||||||
|
# 2. Stable on-system copies for the pacman hook to point at.
|
||||||
|
install -m 0644 "$SRC/AdditionsPage.qml" "$SHARE/AdditionsPage.qml"
|
||||||
|
install -m 0755 "$SRC/patch-additions-page.sh" "$SHARE/patch-additions-page.sh"
|
||||||
|
|
||||||
|
# 3. Apply the QML patch now.
|
||||||
|
sudo sh "$SHARE/patch-additions-page.sh"
|
||||||
|
|
||||||
|
# 4. Pacman hook: caelestia-shell upgrades revert both registries — re-patch.
|
||||||
|
HOOK=/etc/pacman.d/hooks/nosignal-additions-page.hook
|
||||||
|
sudo mkdir -p /etc/pacman.d/hooks
|
||||||
|
sudo tee "$HOOK" > /dev/null <<EOF
|
||||||
|
[Trigger]
|
||||||
|
Operation = Install
|
||||||
|
Operation = Upgrade
|
||||||
|
Type = Package
|
||||||
|
Target = nosignal-shell
|
||||||
|
Target = caelestia-shell
|
||||||
|
|
||||||
|
[Action]
|
||||||
|
Description = Re-applying NoSignal Additions settings page...
|
||||||
|
When = PostTransaction
|
||||||
|
Exec = /bin/sh $SHARE/patch-additions-page.sh
|
||||||
|
EOF
|
||||||
|
echo ":: pacman hook installed -> $HOOK"
|
||||||
|
|
||||||
|
# 5. Seed the status cache so the page has data on first open.
|
||||||
|
"$BIN/nosignal-additions" status >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
echo "Done. Restart the shell (Ctrl+Super+Alt+R) to see Settings -> Additions."
|
||||||
58
os updates/additions-installer/nosignal-additions
Executable file
|
|
@ -0,0 +1,58 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# nosignal-additions — backend for Settings → Additions (optional software).
|
||||||
|
# The manifest (additions.json, next to this component's on-system copy) is
|
||||||
|
# the single source of truth: id/name/desc/icon per item, a `check` command
|
||||||
|
# (exit 0 = installed) and an `install` command (run in a visible terminal).
|
||||||
|
#
|
||||||
|
# nosignal-additions status rewrite the status cache (JSON)
|
||||||
|
# nosignal-additions list list manifest items (id<TAB>name)
|
||||||
|
# nosignal-additions install <id> run the item's installer, then re-status
|
||||||
|
#
|
||||||
|
# Sources policy: official pacman repos or official upstream installers/git
|
||||||
|
# only — no AUR, no Flatpak.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SHARE="$HOME/.local/share/nosignal/additions-installer"
|
||||||
|
MANIFEST="${NOSIGNAL_ADDITIONS_MANIFEST:-$SHARE/additions.json}"
|
||||||
|
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/nosignal"
|
||||||
|
STATE="$STATE_DIR/additions-status.json"
|
||||||
|
|
||||||
|
[ -f "$MANIFEST" ] || { echo "nosignal-additions: manifest not found: $MANIFEST" >&2; exit 1; }
|
||||||
|
|
||||||
|
status() {
|
||||||
|
mkdir -p "$STATE_DIR"
|
||||||
|
{
|
||||||
|
printf '{"checked":"%s","items":[' "$(date -Iseconds)"
|
||||||
|
sep=""
|
||||||
|
jq -c '.items[]' "$MANIFEST" | while IFS= read -r item; do
|
||||||
|
check=$(printf '%s' "$item" | jq -r '.check')
|
||||||
|
if sh -c "$check" >/dev/null 2>&1; then inst=true; else inst=false; fi
|
||||||
|
printf '%s' "$sep"
|
||||||
|
printf '%s' "$item" | jq -c --argjson inst "$inst" 'del(.check, .install) + {installed: $inst}'
|
||||||
|
sep=","
|
||||||
|
done
|
||||||
|
printf ']}\n'
|
||||||
|
} > "$STATE.tmp"
|
||||||
|
jq -e . "$STATE.tmp" >/dev/null # never replace the cache with broken JSON
|
||||||
|
mv "$STATE.tmp" "$STATE"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_one() {
|
||||||
|
id="$1"
|
||||||
|
item=$(jq -ce --arg id "$id" '.items[] | select(.id == $id)' "$MANIFEST") \
|
||||||
|
|| { echo "nosignal-additions: unknown addition: $id" >&2; exit 1; }
|
||||||
|
name=$(printf '%s' "$item" | jq -r '.name')
|
||||||
|
cmd=$(printf '%s' "$item" | jq -r '.install')
|
||||||
|
printf '\033[1;34m::\033[0m installing %s\n' "$name"
|
||||||
|
sh -ec "$cmd"
|
||||||
|
printf '\033[1;34m::\033[0m %s — installer finished\n' "$name"
|
||||||
|
status
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
status) status ;;
|
||||||
|
list) jq -r '.items[] | "\(.id)\t\(.name)"' "$MANIFEST" ;;
|
||||||
|
install) [ -n "${2:-}" ] || { echo "usage: nosignal-additions install <id>" >&2; exit 2; }
|
||||||
|
install_one "$2" ;;
|
||||||
|
*) echo "usage: nosignal-additions status | list | install <id>" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
58
os updates/additions-installer/obs-extras.sh
Executable file
|
|
@ -0,0 +1,58 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# obs-extras.sh — OBS Studio with the right hardware encoders for THIS box.
|
||||||
|
# The Additions page ships on every install and GPUs differ (AMD / Intel /
|
||||||
|
# NVIDIA, any mix — e.g. AMD discrete + Intel iGPU), so the
|
||||||
|
# encoder runtime packages are DETECTED per machine, not hardcoded.
|
||||||
|
#
|
||||||
|
# obs-extras.sh check exit 0 only if OBS + everything this box's GPUs
|
||||||
|
# need is installed (drives the Additions row state)
|
||||||
|
# obs-extras.sh install install OBS + the detected encoder runtimes
|
||||||
|
#
|
||||||
|
# Per-vendor encoder support on Arch:
|
||||||
|
# AMD — VAAPI (H.264/HEVC/AV1) ships inside mesa (base system): no extra
|
||||||
|
# package since the libva-mesa-driver merge.
|
||||||
|
# Intel — intel-media-driver (VAAPI) + vpl-gpu-rt (QuickSync/QSV runtime).
|
||||||
|
# NVIDIA — NVENC comes with the proprietary driver (nvidia-utils or a
|
||||||
|
# legacy branch). Deliberately NOT auto-installed here: the driver
|
||||||
|
# branch is a system-level decision (see gaming-enablement's
|
||||||
|
# NVIDIA notes) — we print a pointer instead.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
gpus=$(lspci -nn 2>/dev/null | grep -Ei 'vga|3d|display' || true)
|
||||||
|
|
||||||
|
needed=""
|
||||||
|
case "$gpus" in *[Ii]ntel*) needed="intel-media-driver vpl-gpu-rt" ;; esac
|
||||||
|
|
||||||
|
check() {
|
||||||
|
pacman -Q obs-studio >/dev/null 2>&1 || return 1
|
||||||
|
for p in $needed; do
|
||||||
|
pacman -Q "$p" >/dev/null 2>&1 || return 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
do_install() {
|
||||||
|
# shellcheck disable=SC2086 — $needed is a deliberate word-split list
|
||||||
|
sudo pacman -S --needed --noconfirm obs-studio $needed
|
||||||
|
case "$gpus" in
|
||||||
|
*[Aa][Mm][Dd]*|*ATI*)
|
||||||
|
echo ":: AMD GPU: VAAPI encoders ship in mesa — already present." ;;
|
||||||
|
esac
|
||||||
|
case "$gpus" in
|
||||||
|
*[Nn][Vv][Ii][Dd][Ii][Aa]*)
|
||||||
|
if pacman -Q nvidia-utils >/dev/null 2>&1; then
|
||||||
|
echo ":: NVIDIA GPU: NVENC available via the installed driver."
|
||||||
|
else
|
||||||
|
echo "NOTE: NVIDIA GPU detected but no proprietary driver — NVENC needs"
|
||||||
|
echo " it (nvidia-utils, or a legacy branch for pre-Turing cards)."
|
||||||
|
fi ;;
|
||||||
|
esac
|
||||||
|
[ -n "$needed" ] && echo ":: Intel GPU: VAAPI + QuickSync runtimes installed."
|
||||||
|
echo ":: restart OBS to pick up new encoders."
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
check) check ;;
|
||||||
|
install) do_install ;;
|
||||||
|
*) echo "usage: obs-extras.sh check|install" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
62
os updates/additions-installer/patch-additions-page.sh
Executable file
|
|
@ -0,0 +1,62 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# patch-additions-page.sh — register the NoSignal Additions page in
|
||||||
|
# caelestia-shell. Runs as root (via sudo from the installer, or via the
|
||||||
|
# pacman hook after every caelestia-shell upgrade, which reverts both
|
||||||
|
# registry files). Idempotent.
|
||||||
|
#
|
||||||
|
# What it does:
|
||||||
|
# 1. copies AdditionsPage.qml (next to this script) into the shell's
|
||||||
|
# pages/ dir (untracked file — pacman upgrades leave it alone)
|
||||||
|
# 2. swaps the REMAINING "System" PlaceholderComp in PageCompRegistry.qml
|
||||||
|
# (the Plugins stub) for a StackPage hosting AdditionsPage.
|
||||||
|
# REQUIRES the Updates-page patch to be applied first — before it,
|
||||||
|
# the first placeholder is the Updates stub, and this patch would grab
|
||||||
|
# the wrong slot (guarded below).
|
||||||
|
# 3. relabels the menu entry in PageRegistry.qml: "Plugins" → "Additions".
|
||||||
|
#
|
||||||
|
# NEXUS is overridable for testing the regexes against copies.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SELF_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
NEXUS=${NEXUS:-/etc/xdg/quickshell/caelestia/modules/nexus}
|
||||||
|
COMPREG="$NEXUS/PageCompRegistry.qml"
|
||||||
|
PAGEREG="$NEXUS/PageRegistry.qml"
|
||||||
|
|
||||||
|
[ -f "$COMPREG" ] || { echo "caelestia-shell not found at $NEXUS — nothing to patch"; exit 0; }
|
||||||
|
|
||||||
|
install -m 0644 "$SELF_DIR/AdditionsPage.qml" "$NEXUS/pages/AdditionsPage.qml"
|
||||||
|
|
||||||
|
# --- component registry: Plugins placeholder -> AdditionsPage ---------------
|
||||||
|
if grep -q 'AdditionsPage' "$COMPREG"; then
|
||||||
|
echo ":: component registry already patched"
|
||||||
|
elif ! grep -q 'UpdatesPage' "$COMPREG"; then
|
||||||
|
echo "WARNING: Updates patch not applied — the first placeholder" >&2
|
||||||
|
echo " is the Updates stub, not Plugins. Run the updates-panel patch" >&2
|
||||||
|
echo " first, then re-run $(basename "$0")." >&2
|
||||||
|
else
|
||||||
|
cp -n "$COMPREG" "$COMPREG.pre-nosignal-additions"
|
||||||
|
perl -0pi -e 's/Component \{\s*\n\s*PlaceholderComp \{\}\s*\n\s*\},/Component {\n \/\/ Additions (NoSignal)\n StackPage {\n Component {\n AdditionsPage {}\n }\n }\n },/' "$COMPREG"
|
||||||
|
if grep -q 'AdditionsPage' "$COMPREG"; then
|
||||||
|
echo ":: patched $COMPREG (Additions page registered)"
|
||||||
|
else
|
||||||
|
echo "WARNING: patch did not apply — upstream PageCompRegistry.qml changed" >&2
|
||||||
|
echo " shape. The Additions page is installed but not registered;" >&2
|
||||||
|
echo " update the regex in $(basename "$0")." >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- menu registry: relabel Plugins -> Additions -----------------------------
|
||||||
|
if [ -f "$PAGEREG" ]; then
|
||||||
|
if grep -q '"Additions"' "$PAGEREG"; then
|
||||||
|
echo ":: menu entry already relabeled"
|
||||||
|
else
|
||||||
|
cp -n "$PAGEREG" "$PAGEREG.pre-nosignal-additions"
|
||||||
|
perl -pi -e 's/label: qsTr\("Plugins"\),/label: qsTr("Additions"),/; s/description: qsTr\("Manage plugins"\),/description: qsTr("Install optional software"),/' "$PAGEREG"
|
||||||
|
if grep -q '"Additions"' "$PAGEREG"; then
|
||||||
|
echo ":: relabeled menu entry Plugins -> Additions"
|
||||||
|
else
|
||||||
|
echo "WARNING: relabel did not apply (upstream PageRegistry.qml changed)." >&2
|
||||||
|
echo " Cosmetic only — the page still works under the old label." >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
67
os updates/app-theme-awareness/README.md
Executable file
|
|
@ -0,0 +1,67 @@
|
||||||
|
# app-theme-awareness
|
||||||
|
|
||||||
|
User-level, no root.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Make external apps (Chrome/Chromium, Electron, Firefox, GTK, Qt) **follow
|
||||||
|
Caelestia's light/dark mode** automatically — dark when Caelestia is dark.
|
||||||
|
|
||||||
|
## Why they don't today
|
||||||
|
Modern apps read one signal: `org.freedesktop.appearance` → `color-scheme`,
|
||||||
|
published by **xdg-desktop-portal**. By default that value is **0
|
||||||
|
("no preference")** because nothing sets it — `gsettings
|
||||||
|
org.gnome.desktop.interface color-scheme` is `'default'`. So every app defaults
|
||||||
|
to light. All portal pieces are already installed (`xdg-desktop-portal`,
|
||||||
|
`-gtk`, `-hyprland`); `libadwaita` is present.
|
||||||
|
|
||||||
|
## How this works
|
||||||
|
- **`nosignal-app-theme-sync`**: reads `caelestia scheme get` ("Mode: dark|
|
||||||
|
light") and sets:
|
||||||
|
- `gsettings org.gnome.desktop.interface color-scheme` → `prefer-dark` /
|
||||||
|
`prefer-light`. xdg-desktop-portal-gtk republishes this as the freedesktop
|
||||||
|
appearance `color-scheme`, which **Chrome, Electron, Firefox, GTK4/libadwaita
|
||||||
|
and Qt6 honor** (UI + web `prefer-color-scheme`).
|
||||||
|
- `gtk-application-prefer-dark-theme` in `gtk-3.0`/`gtk-4.0` `settings.ini`
|
||||||
|
for legacy GTK3 apps that ignore the portal.
|
||||||
|
- **`nosignal-app-theme.path` + `.service`** (user units, mirroring NoSignal's
|
||||||
|
`hyprmoncfgd-rescan.path`): watch `~/.local/state/caelestia/scheme.json` and
|
||||||
|
re-run the sync on every scheme change, and run it once at login. So flipping
|
||||||
|
Caelestia light↔dark flips the apps live.
|
||||||
|
- **`portals.conf`**: pins the Settings portal backend to `gtk` (keeping
|
||||||
|
Hyprland's portal default for screencast), so the appearance value is served
|
||||||
|
reliably. Installed only if the user has no `portals.conf`.
|
||||||
|
|
||||||
|
`install-app-theme-awareness.sh` does all of it idempotently — entirely
|
||||||
|
user-level (no root, no pacman hook).
|
||||||
|
|
||||||
|
## Per-app notes
|
||||||
|
- **Chrome/Chromium (Wayland):** follows the portal automatically. If the UI
|
||||||
|
stays light, set `chrome://settings` → Appearance → **GTK / "Use system"**.
|
||||||
|
- **Qt:** `QT_QPA_PLATFORMTHEME=qtengine` is already set; Qt6 honors the portal.
|
||||||
|
Old Qt5 apps may need a dark qt5ct/Kvantum style (out of scope).
|
||||||
|
- **GTK3 dark fidelity:** prefer-dark uses Adwaita's built-in dark rendering. For
|
||||||
|
a fuller dark GTK3 theme the builder can add `gnome-themes-extra` (ships
|
||||||
|
`Adwaita-dark`) — optional.
|
||||||
|
|
||||||
|
## Builder integration
|
||||||
|
Ship the sync script in the layer bin, the two user units (enabled by default,
|
||||||
|
like the other NoSignal user units), and seed `portals.conf`. Ideally Caelestia
|
||||||
|
itself could set `color-scheme` when its scheme flips; until then these units
|
||||||
|
bridge it. Mode is read from `caelestia scheme get`, so it tracks the dynamic
|
||||||
|
Material scheme.
|
||||||
|
|
||||||
|
## Verify (after install)
|
||||||
|
```
|
||||||
|
caelestia scheme set -m light # or via the Caelestia UI
|
||||||
|
gsettings get org.gnome.desktop.interface color-scheme # -> 'prefer-light'
|
||||||
|
caelestia scheme set -m dark
|
||||||
|
gsettings get org.gnome.desktop.interface color-scheme # -> 'prefer-dark'
|
||||||
|
# Chrome/GTK apps follow within a moment.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files
|
||||||
|
- `nosignal-app-theme-sync` — mode → portal/GTK sync.
|
||||||
|
- `nosignal-app-theme.service` / `.path` — login run + watch-on-change.
|
||||||
|
- `portals.conf` — Settings backend = gtk.
|
||||||
|
- `install-app-theme-awareness.sh` — idempotent user-level installer.
|
||||||
|
- `migrations/1781442000-app-theme-awareness.sh` — delegates to it.
|
||||||
39
os updates/app-theme-awareness/install-app-theme-awareness.sh
Executable file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# install-app-theme-awareness.sh — idempotent. User-level (no root).
|
||||||
|
#
|
||||||
|
# External apps (Chrome, Electron, Firefox, GTK, Qt) don't follow
|
||||||
|
# the desktop theme because nothing publishes a color-scheme preference — the
|
||||||
|
# portal reports "no preference" (0). This installs a sync that mirrors
|
||||||
|
# Caelestia's light/dark mode to the freedesktop appearance portal + GTK, and
|
||||||
|
# units that re-run it on every scheme change.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
BIN="$HOME/.local/bin"
|
||||||
|
UNITS="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
|
||||||
|
PORTAL="${XDG_CONFIG_HOME:-$HOME/.config}/xdg-desktop-portal"
|
||||||
|
|
||||||
|
install -Dm0755 "$HERE/nosignal-app-theme-sync" "$BIN/nosignal-app-theme-sync"
|
||||||
|
install -Dm0644 "$HERE/nosignal-app-theme.service" "$UNITS/nosignal-app-theme.service"
|
||||||
|
install -Dm0644 "$HERE/nosignal-app-theme.path" "$UNITS/nosignal-app-theme.path"
|
||||||
|
echo "installed sync script + user units"
|
||||||
|
|
||||||
|
# portals.conf: only create if the user doesn't already have one (don't clobber)
|
||||||
|
if [ ! -e "$PORTAL/portals.conf" ]; then
|
||||||
|
install -Dm0644 "$HERE/portals.conf" "$PORTAL/portals.conf"
|
||||||
|
echo "installed $PORTAL/portals.conf (Settings=gtk)"
|
||||||
|
else
|
||||||
|
echo "note: $PORTAL/portals.conf exists — leaving it; ensure it has org.freedesktop.impl.portal.Settings=gtk"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# enable the watcher + run an initial sync (needs a running user systemd bus)
|
||||||
|
if systemctl --user show-environment >/dev/null 2>&1; then
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user enable --now nosignal-app-theme.path nosignal-app-theme.service || true
|
||||||
|
echo "enabled nosignal-app-theme.{path,service}"
|
||||||
|
else
|
||||||
|
echo "note: no user systemd bus here — units installed; they start at next login"
|
||||||
|
"$BIN/nosignal-app-theme-sync" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "app-theme-awareness: ok"
|
||||||
39
os updates/app-theme-awareness/nosignal-app-theme-sync
Executable file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# nosignal-app-theme-sync — make external apps follow Caelestia's light/dark mode.
|
||||||
|
#
|
||||||
|
# Reads Caelestia's current mode (`caelestia scheme get` -> "Mode: dark|light")
|
||||||
|
# and publishes it so toolkit apps follow:
|
||||||
|
# * gsettings color-scheme -> xdg-desktop-portal(-gtk) org.freedesktop.appearance
|
||||||
|
# => Chrome/Chromium, Electron, Firefox, GTK4/libadwaita, Qt6 all follow.
|
||||||
|
# * GTK3 prefer-dark flag in gtk-3.0/gtk-4.0 settings.ini for legacy GTK apps
|
||||||
|
# that don't read the portal.
|
||||||
|
# Idempotent; safe to run repeatedly. Invoked at login and on scheme change by
|
||||||
|
# the nosignal-app-theme .service/.path units.
|
||||||
|
set -u
|
||||||
|
|
||||||
|
mode=$(caelestia scheme get 2>/dev/null | awk -F': *' '/Mode:/{print tolower($2); exit}')
|
||||||
|
case "$mode" in
|
||||||
|
dark) cs=prefer-dark; pd=true ;;
|
||||||
|
light) cs=prefer-light; pd=false ;;
|
||||||
|
*) cs=default; pd=false ;; # unknown -> no preference
|
||||||
|
esac
|
||||||
|
|
||||||
|
# 1. master switch (portal): Chrome / Electron / Firefox / GTK4 / Qt6
|
||||||
|
if command -v gsettings >/dev/null 2>&1; then
|
||||||
|
gsettings set org.gnome.desktop.interface color-scheme "$cs" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. legacy GTK3 (+GTK4) prefer-dark flag for apps that ignore the portal
|
||||||
|
for v in 3.0 4.0; do
|
||||||
|
d="${XDG_CONFIG_HOME:-$HOME/.config}/gtk-$v"; f="$d/settings.ini"
|
||||||
|
mkdir -p "$d"
|
||||||
|
[ -f "$f" ] || printf '[Settings]\n' > "$f"
|
||||||
|
grep -q '^\[Settings\]' "$f" || printf '[Settings]\n' >> "$f"
|
||||||
|
if grep -q '^gtk-application-prefer-dark-theme' "$f"; then
|
||||||
|
sed -i "s/^gtk-application-prefer-dark-theme.*/gtk-application-prefer-dark-theme=$pd/" "$f"
|
||||||
|
else
|
||||||
|
printf 'gtk-application-prefer-dark-theme=%s\n' "$pd" >> "$f"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "app-theme: caelestia mode=${mode:-unknown} -> color-scheme=$cs, gtk prefer-dark=$pd"
|
||||||
9
os updates/app-theme-awareness/nosignal-app-theme.path
Executable file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Watch Caelestia scheme and re-sync external app theme on change
|
||||||
|
|
||||||
|
[Path]
|
||||||
|
PathChanged=%h/.local/state/caelestia/scheme.json
|
||||||
|
Unit=nosignal-app-theme.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
9
os updates/app-theme-awareness/nosignal-app-theme.service
Executable file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Sync external app theme (portal/GTK) to Caelestia's light/dark mode
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=%h/.local/bin/nosignal-app-theme-sync
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
8
os updates/app-theme-awareness/portals.conf
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# NoSignal: keep Hyprland's portal as default (screencast/screenshot) but let
|
||||||
|
# xdg-desktop-portal-gtk serve the Settings interface, so the
|
||||||
|
# org.freedesktop.appearance color-scheme (driven by gsettings) is published
|
||||||
|
# reliably to apps. Installed to ~/.config/xdg-desktop-portal/portals.conf
|
||||||
|
# only if no user portals.conf already exists.
|
||||||
|
[preferred]
|
||||||
|
default=hyprland;gtk
|
||||||
|
org.freedesktop.impl.portal.Settings=gtk
|
||||||
24
os updates/base-default-packages/README.md
Executable file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# base-default-packages (installed by default, not opt-in)
|
||||||
|
|
||||||
|
The install command needs root — run it once.
|
||||||
|
|
||||||
|
Packages that should ship **installed by default** in the base build (distinct
|
||||||
|
from the opt-in Settings → Additions). Official repos only; GPU-agnostic.
|
||||||
|
|
||||||
|
| Package | Provides | Repo | Why |
|
||||||
|
|---------|----------|------|-----|
|
||||||
|
| `github-cli` | `gh` | extra | GitHub CLI — default dev tooling (PRs, issues, repo ops, auth) |
|
||||||
|
|
||||||
|
## Builder integration
|
||||||
|
Add the package(s) above to the ISO base package set. `install-base-default-
|
||||||
|
packages.sh` covers already-installed machines (idempotent `pacman -S --needed`)
|
||||||
|
and is wired via the migration below.
|
||||||
|
|
||||||
|
## Apply on an existing box
|
||||||
|
```
|
||||||
|
sudo pacman -S --needed github-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files
|
||||||
|
- `install-base-default-packages.sh` — idempotent installer (root).
|
||||||
|
- `migrations/1781452800-base-default-packages.sh` — delegates to it.
|
||||||
20
os updates/base-default-packages/install-base-default-packages.sh
Executable file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# install-base-default-packages.sh — idempotent. Needs root (pacman).
|
||||||
|
#
|
||||||
|
# Ensures packages that should be installed BY DEFAULT in the base build (not
|
||||||
|
# opt-in Additions). For the ISO, the builder should add these to the base
|
||||||
|
# package set; this covers already-installed systems via nosignal-update.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Base default packages (official repos; GPU-agnostic):
|
||||||
|
PKGS="github-cli"
|
||||||
|
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
pacman -S --needed --noconfirm $PKGS
|
||||||
|
elif command -v sudo >/dev/null 2>&1; then
|
||||||
|
sudo pacman -S --needed --noconfirm $PKGS
|
||||||
|
else
|
||||||
|
echo "need root to install: $PKGS" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "base default packages ensured: $PKGS"
|
||||||
3
os updates/cachyos-repo-switch/02-nosignal-cachy
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# NoSignal: flip the CachyOS repos from the settings toggle without a password.
|
||||||
|
# Pinned to the fixed, root-owned helper, which itself only accepts detect/status/enable/disable.
|
||||||
|
%wheel ALL=(ALL) NOPASSWD: /usr/local/bin/nosignal-cachy-repo detect, /usr/local/bin/nosignal-cachy-repo status, /usr/local/bin/nosignal-cachy-repo enable, /usr/local/bin/nosignal-cachy-repo disable
|
||||||
73
os updates/cachyos-repo-switch/CachyRepoToggleRow.qml
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
// CachyRepoToggleRow.qml (NoSignal) — Settings -> Services toggle to enable /
|
||||||
|
// disable the CachyOS pacman repositories. Auto-detects the best x86-64 microarch
|
||||||
|
// tier the CPU supports (v4 > v3). Live state polls `nosignal-cachy-repo status`
|
||||||
|
// (no root). enable/disable run in a floating terminal so the pacman/download
|
||||||
|
// output stays visible; the grant is passwordless via sudo -n
|
||||||
|
// (/etc/sudoers.d/02-nosignal-cachy). enable adds repos, converts userspace to
|
||||||
|
// the optimized builds (pacman -Suu, pacman pinned stock), and installs the
|
||||||
|
// linux-cachyos kernel; disable reverts to stock and removes the cachy kernel
|
||||||
|
// (keeping stock `linux` bootable). A reboot switches the running kernel.
|
||||||
|
//
|
||||||
|
// Untracked file under modules/nexus/common — auto-discovered as the type
|
||||||
|
// `CachyRepoToggleRow` via `import qs.modules.nexus.common`. Survives caelestia
|
||||||
|
// upgrades; pairs with the one-line insert in ServicesPage.qml.
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.modules.nexus.common
|
||||||
|
|
||||||
|
ToggleRow {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool repoOn: false // NB: not `enabled` (reserved Item property)
|
||||||
|
|
||||||
|
text: qsTr("CachyOS repositories")
|
||||||
|
subtext: repoOn
|
||||||
|
? qsTr("On — CachyOS optimized builds + linux-cachyos kernel (reboot to run it)")
|
||||||
|
: qsTr("Switch to CachyOS optimized builds + linux-cachyos kernel (reboot after)")
|
||||||
|
|
||||||
|
onToggled: {
|
||||||
|
if (checked)
|
||||||
|
enableProc.running = true;
|
||||||
|
else
|
||||||
|
disableProc.running = true;
|
||||||
|
reconcile.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- live state -----------------------------------------------------------
|
||||||
|
Process {
|
||||||
|
id: statusProc
|
||||||
|
command: ["/usr/local/bin/nosignal-cachy-repo", "status"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
root.repoOn = text.trim() === "enabled";
|
||||||
|
root.checked = root.repoOn; // drive switch from real state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- actions (passwordless; shown in a terminal for pacman output) ---------
|
||||||
|
Process {
|
||||||
|
id: enableProc
|
||||||
|
command: ["kitty", "--class", "TUI.float", "-e", "sh", "-c", "sudo -n /usr/local/bin/nosignal-cachy-repo enable; printf '\\nPress Enter to close...'; read _"]
|
||||||
|
onExited: reconcile.restart()
|
||||||
|
}
|
||||||
|
Process {
|
||||||
|
id: disableProc
|
||||||
|
command: ["kitty", "--class", "TUI.float", "-e", "sh", "-c", "sudo -n /usr/local/bin/nosignal-cachy-repo disable; printf '\\nPress Enter to close...'; read _"]
|
||||||
|
onExited: reconcile.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- polling --------------------------------------------------------------
|
||||||
|
Timer {
|
||||||
|
interval: 5000
|
||||||
|
repeat: true
|
||||||
|
running: true
|
||||||
|
triggeredOnStart: true
|
||||||
|
onTriggered: statusProc.running = true
|
||||||
|
}
|
||||||
|
Timer {
|
||||||
|
id: reconcile
|
||||||
|
interval: 2000
|
||||||
|
onTriggered: statusProc.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
79
os updates/cachyos-repo-switch/README.md
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
# cachyos-repo-switch — CachyOS optimized repos + kernel switch (sudoless, auto-tier)
|
||||||
|
|
||||||
|
Works **both directions** — ON → reboot into `linux-cachyos`; OFF → reboot back to stock.
|
||||||
|
|
||||||
|
## What it is
|
||||||
|
One toggle in **Settings → Services** (under "Passwordless sudo") that switches the
|
||||||
|
system **to/from** CachyOS-optimized builds **and** the `linux-cachyos` kernel —
|
||||||
|
passwordless, auto-detecting the best x86-64 tier (v4 > v3), fully unattended
|
||||||
|
(`--noconfirm`). Refuses below x86-64-v3.
|
||||||
|
|
||||||
|
## Pieces
|
||||||
|
- **`nosignal-cachy-repo`** → `/usr/local/bin` — `detect` / `status` / `enable` /
|
||||||
|
`disable` (+ `--dry-run`).
|
||||||
|
- **`02-nosignal-cachy`** → `/etc/sudoers.d` (0440, `visudo -c`-validated at install)
|
||||||
|
— `%wheel` NOPASSWD, pinned to the four exact subcommands of the fixed helper.
|
||||||
|
- **`CachyRepoToggleRow.qml`** → `nexus/common/` — baked into the pinned
|
||||||
|
nosignal-shell fork; this copy + `patch-servicespage.sh` are the migration
|
||||||
|
path for old stock-caelestia installs.
|
||||||
|
- **`patch-servicespage.sh`** — inserts the toggle into `ServicesPage.qml` (only used
|
||||||
|
by the nosignal-update migration; the builder skips it via NOSIGNAL_SKIP_SHELL_PATCH).
|
||||||
|
|
||||||
|
## Two design fixes baked in — DO NOT reintroduce the bugs
|
||||||
|
1. **Architecture (critical).** CachyOS v3/v4 packages have arch `x86_64_v3`/`x86_64_v4`.
|
||||||
|
Upstream relies on its OWN pacman (reads `Architecture = auto` as "include v3/v4").
|
||||||
|
We keep **stock pacman** (reads `auto` as just `x86_64`) → it would arch-reject every
|
||||||
|
v3/v4 package and the conversion would silently do nothing. So `enable` sets
|
||||||
|
`Architecture` EXPLICITLY to the tier (`x86_64 x86_64_v3 x86_64_v4` for v4,
|
||||||
|
`x86_64 x86_64_v3` for v3); `disable` restores `Architecture = auto`.
|
||||||
|
2. **Targeted revert (no whole-base pull).** `disable` reinstalls ONLY packages whose
|
||||||
|
local-DB `Packager` is `CachyOS …` (excluding the cachy keyring/mirrorlist pkgs,
|
||||||
|
removed separately) — never the full base. Nothing converted ⇒ nothing to do.
|
||||||
|
|
||||||
|
## Why stock pacman (and `--ignore pacman`)
|
||||||
|
Upstream `cachyos-repo.sh` is interactive, ends with `pacman -Syu`, and swaps in a
|
||||||
|
CachyOS `pacman`. We reuse its signed keyring/mirrorlists + `.awk` stanza files but
|
||||||
|
drive pacman ourselves (stock, unattended). The CachyOS `pacman` build stamps an
|
||||||
|
`%INSTALLED_DB%` key into the local DB that stock pacman then warns about on every op,
|
||||||
|
so `enable` runs `pacman -Suu --ignore pacman` to pin pacman stock. **Keep that flag.**
|
||||||
|
|
||||||
|
## Kernel guard
|
||||||
|
`enable` installs `linux-cachyos` + `-headers` (the limine-mkinitcpio-hook generates
|
||||||
|
the boot entry; stock `linux` stays as a fallback). `disable` removes the cachy kernel
|
||||||
|
ONLY after confirming stock `linux` is installed — never strips the last bootable
|
||||||
|
kernel. A **reboot** is needed after ON (to run the new kernel) and after OFF (to leave
|
||||||
|
it); pick the entry at the Limine menu.
|
||||||
|
|
||||||
|
## Live-fetch vs vendored (build decision: LIVE-FETCH)
|
||||||
|
`enable`/`disable` fetch upstream `cachyos-repo.tar.xz` (keyring + mirrorlists + `.awk`).
|
||||||
|
Enabling CachyOS is inherently online (it pulls optimized builds + the kernel from
|
||||||
|
CachyOS mirrors), so there is no offline scenario to vendor for — live-fetch needs no
|
||||||
|
bundled artifacts and never goes stale. If a future air-gapped image needs it, vendor
|
||||||
|
the tarball and drop the fetch in `fetch_upstream()`.
|
||||||
|
|
||||||
|
## CachyOS-exclusive packages don't revert (by design)
|
||||||
|
Any package with no stock equivalent stays a CachyOS build on OFF and is reported, not
|
||||||
|
removed (e.g. `quickshell-git`, `gamescope-session-git` —
|
||||||
|
harmless; self-clear on the next `yay -Sua`). The `linux-cachyos` kernel is the one
|
||||||
|
exception (removed explicitly, guarded as above). Snapshot entries keep historical
|
||||||
|
cachy UKIs under the Snapshots submenu (cosmetic; age out as snapshots rotate).
|
||||||
|
|
||||||
|
## Closure deps
|
||||||
|
`curl`, `tar`/`xz`, `gawk` (all via Arch `base`), `kitty`, plus `limine` +
|
||||||
|
`limine-mkinitcpio-hook` for the kernel boot entry — all already on the NoSignal base.
|
||||||
|
|
||||||
|
## Install (root)
|
||||||
|
```
|
||||||
|
sudo sh ~/.local/share/nosignal/cachyos-repo-switch/install-cachyos-repo-switch.sh
|
||||||
|
```
|
||||||
|
Then reload caelestia (or log out/in) so the toggle loads.
|
||||||
|
|
||||||
|
## Use
|
||||||
|
- Settings → Services → **CachyOS repositories**: on opens a floating terminal showing
|
||||||
|
the full pacman transaction (passwordless); off reverts to stock.
|
||||||
|
- Or terminal: `sudo nosignal-cachy-repo enable` / `disable`,
|
||||||
|
`nosignal-cachy-repo status` / `detect` (add `--dry-run` to preview).
|
||||||
|
|
||||||
|
## Files
|
||||||
|
- `nosignal-cachy-repo`, `02-nosignal-cachy`, `install-cachyos-repo-switch.sh`,
|
||||||
|
`CachyRepoToggleRow.qml`, `patch-servicespage.sh`.
|
||||||
58
os updates/cachyos-repo-switch/install-cachyos-repo-switch.sh
Executable file
|
|
@ -0,0 +1,58 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# install-cachyos-repo-switch.sh — idempotent. REQUIRES ROOT.
|
||||||
|
#
|
||||||
|
# Installs the CachyOS repo + kernel switch feature:
|
||||||
|
# 1. /usr/local/bin/nosignal-cachy-repo (detect/status/enable/disable CLI)
|
||||||
|
# 2. /etc/sudoers.d/02-nosignal-cachy (sudoless grant, validated first)
|
||||||
|
# 3. patches caelestia ServicesPage to show the toggle (via patch-servicespage.sh)
|
||||||
|
#
|
||||||
|
# Does NOT enable the CachyOS repos — that's a deliberate user action via the
|
||||||
|
# toggle (it is heavy, networked and reboots the kernel).
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SELF_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
[ "$(id -u)" -eq 0 ] || { echo "must run as root (writes /usr/local/bin + sudoers.d)" >&2; exit 1; }
|
||||||
|
|
||||||
|
# 1. CLI helper
|
||||||
|
install -m 0755 "$SELF_DIR/nosignal-cachy-repo" /usr/local/bin/nosignal-cachy-repo
|
||||||
|
echo ":: installed /usr/local/bin/nosignal-cachy-repo"
|
||||||
|
|
||||||
|
# 1b. standalone DB repair tool. A normal -Syu while
|
||||||
|
# CachyOS is enabled pulls the CachyOS pacman, which stamps `%INSTALLED_DB%`
|
||||||
|
# into the local DB; stock pacman then warns about it on every op. The
|
||||||
|
# corrected helper above pins pacman durably (IgnorePkg) while enabled and
|
||||||
|
# strips the field on disable, but ship the standalone repair tool too — and
|
||||||
|
# one-shot-clean any contamination already present (no-op on a fresh box).
|
||||||
|
install -m 0755 "$SELF_DIR/nosignal-cachy-db-clean" /usr/local/bin/nosignal-cachy-db-clean
|
||||||
|
echo ":: installed /usr/local/bin/nosignal-cachy-db-clean"
|
||||||
|
if grep -lrx '%INSTALLED_DB%' /var/lib/pacman/local/*/desc >/dev/null 2>&1; then
|
||||||
|
echo ":: local DB has %INSTALLED_DB% contamination — cleaning now…"
|
||||||
|
/usr/local/bin/nosignal-cachy-db-clean || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. sudoers drop-in — validate with `visudo -c` BEFORE it reaches /etc/sudoers.d
|
||||||
|
# (a malformed sudoers can lock out sudo entirely). install to a temp path,
|
||||||
|
# check, then move into place at mode 0440.
|
||||||
|
TMP_SUDO=$(mktemp /tmp/02-nosignal-cachy.XXXXXX)
|
||||||
|
install -m 0440 "$SELF_DIR/02-nosignal-cachy" "$TMP_SUDO"
|
||||||
|
if visudo -cf "$TMP_SUDO" >/dev/null 2>&1; then
|
||||||
|
install -m 0440 -o root -g root "$TMP_SUDO" /etc/sudoers.d/02-nosignal-cachy
|
||||||
|
echo ":: installed /etc/sudoers.d/02-nosignal-cachy (validated)"
|
||||||
|
else
|
||||||
|
echo "ERROR: 02-nosignal-cachy failed visudo -c — NOT installing the sudoers drop-in." >&2
|
||||||
|
echo " (the toggle will prompt for a password instead of running passwordless)" >&2
|
||||||
|
fi
|
||||||
|
rm -f "$TMP_SUDO"
|
||||||
|
|
||||||
|
# 3. panel toggle (caelestia ServicesPage). Non-fatal if the shell isn't present.
|
||||||
|
# Skipped when NOSIGNAL_SKIP_SHELL_PATCH is set — the NoSignal builder bakes
|
||||||
|
# CachyRepoToggleRow + the page insert into the pinned nosignal-shell fork, so
|
||||||
|
# the patch is redundant there. Migrations of old stock-caelestia boxes
|
||||||
|
# leave it unset and patch.
|
||||||
|
if [ -n "${NOSIGNAL_SKIP_SHELL_PATCH:-}" ]; then
|
||||||
|
echo ":: skipping ServicesPage patch (NOSIGNAL_SKIP_SHELL_PATCH — fork bakes the toggle)"
|
||||||
|
elif [ -x "$SELF_DIR/patch-servicespage.sh" ]; then
|
||||||
|
sh "$SELF_DIR/patch-servicespage.sh" || echo "WARNING: ServicesPage patch failed (CLI still usable from a terminal)" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ":: done. Toggle it in Settings -> Services, or: sudo nosignal-cachy-repo enable"
|
||||||
54
os updates/cachyos-repo-switch/nosignal-cachy-db-clean
Executable file
|
|
@ -0,0 +1,54 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# nosignal-cachy-db-clean — repair tool for boxes already contaminated by a
|
||||||
|
# CachyOS pacman run.
|
||||||
|
#
|
||||||
|
# Symptom this fixes: every pacman/yay op prints a flood of
|
||||||
|
# warning: <pkg>: unknown key '%INSTALLED_DB%' in local database
|
||||||
|
# `%INSTALLED_DB%` is a field the CachyOS `pacman` build writes into the local DB;
|
||||||
|
# stock Arch pacman doesn't understand it and warns about it on EVERY operation.
|
||||||
|
# It is harmless (the DB is structurally valid — `pacman -Dk` passes — and
|
||||||
|
# transactions resolve fine), but it looks exactly like database corruption.
|
||||||
|
#
|
||||||
|
# This strips the `%INSTALLED_DB%` field from every /var/lib/pacman/local/*/desc.
|
||||||
|
# Each block is exactly: a `%INSTALLED_DB%` line + one value line + a blank
|
||||||
|
# separator. Idempotent — only rewrites files that contain the field; safe to
|
||||||
|
# re-run. Use --dry-run to preview. REQUIRES ROOT (writes the local DB) unless
|
||||||
|
# --dry-run.
|
||||||
|
set -u
|
||||||
|
|
||||||
|
DB="${PACMAN_LOCAL_DB:-/var/lib/pacman/local}"
|
||||||
|
DRY=0
|
||||||
|
[[ "${1:-}" == "--dry-run" ]] && DRY=1
|
||||||
|
|
||||||
|
if [[ $DRY -eq 0 && $EUID -ne 0 ]]; then
|
||||||
|
echo "Run as root (writes $DB), e.g.: sudo $0 — or preview with: $0 --dry-run" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mapfile -t hits < <(grep -lrx '%INSTALLED_DB%' "$DB"/*/desc 2>/dev/null)
|
||||||
|
if [[ ${#hits[@]} -eq 0 ]]; then
|
||||||
|
echo "Local pacman DB already clean — no %INSTALLED_DB% fields found."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found %INSTALLED_DB% in ${#hits[@]} package entr(y/ies)."
|
||||||
|
n=0
|
||||||
|
for desc in "${hits[@]}"; do
|
||||||
|
if [[ $DRY -eq 1 ]]; then
|
||||||
|
echo "+ would strip: $desc"
|
||||||
|
n=$((n+1)); continue
|
||||||
|
fi
|
||||||
|
tmp="$desc.nsclean.$$"
|
||||||
|
if awk '/^%INSTALLED_DB%$/{s=1;next} s&&/^$/{s=0;next} s{next} {print}' "$desc" > "$tmp" \
|
||||||
|
&& chmod --reference="$desc" "$tmp" 2>/dev/null; then
|
||||||
|
mv -f "$tmp" "$desc"; n=$((n+1))
|
||||||
|
else
|
||||||
|
rm -f "$tmp"; echo " ! failed to rewrite: $desc" >&2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $DRY -eq 1 ]]; then
|
||||||
|
echo "(dry-run) would have cleaned $n entr(y/ies). Re-run as root without --dry-run."
|
||||||
|
else
|
||||||
|
echo "Cleaned $n local-DB entr(y/ies). Verify with: pacman -Dk and a quiet pacman -Q"
|
||||||
|
fi
|
||||||
306
os updates/cachyos-repo-switch/nosignal-cachy-repo
Executable file
|
|
@ -0,0 +1,306 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# nosignal-cachy-repo — enable/disable the CachyOS pacman repositories, auto-
|
||||||
|
# selecting the best x86-64 microarchitecture tier the CPU supports (v4 > v3).
|
||||||
|
# Optimized CachyOS repos require x86-64-v3 or newer; below that this refuses.
|
||||||
|
#
|
||||||
|
# Driven by the sudoless GUI toggle:
|
||||||
|
# sudo -n /usr/local/bin/nosignal-cachy-repo {enable|disable|status|detect} [--dry-run]
|
||||||
|
#
|
||||||
|
# enable: trust keys + install mirrorlists + add repo stanzas, then run
|
||||||
|
# `pacman -Suu --noconfirm` to switch the system over to the CachyOS optimized
|
||||||
|
# builds — UNATTENDED, so a single toggle flip auto-converts (the GUI still shows
|
||||||
|
# the transaction in the floating terminal). --noconfirm auto-accepts any
|
||||||
|
# replacements/removals pacman proposes. We do NOT swap in the upstream CachyOS
|
||||||
|
# `pacman` package — it is pinned stock via --ignore pacman (keeps the local DB
|
||||||
|
# clean). enable also installs the CachyOS kernel (linux-cachyos).
|
||||||
|
# disable strips the repos, then reverts ONLY the CachyOS-built packages back to
|
||||||
|
# their stock Arch builds (-Suuy for version diffs + a targeted reinstall of the
|
||||||
|
# same-version rebuilds, identified by Packager "CachyOS …" in the local DB) and
|
||||||
|
# removes the keyring/key — a true round-trip back to stock, UNATTENDED. Touches
|
||||||
|
# only what was actually converted (nothing converted => instant, no base pull).
|
||||||
|
# A CachyOS-exclusive package with no stock equivalent is auto-skipped + REPORTED
|
||||||
|
# (not auto-removed). The CachyOS KERNEL is the exception: disable removes
|
||||||
|
# linux-cachyos, but only after confirming stock `linux` is installed so the box
|
||||||
|
# stays bootable.
|
||||||
|
set -u
|
||||||
|
|
||||||
|
PACCONF="/etc/pacman.conf"
|
||||||
|
PACBAK="/etc/pacman.conf.nosignal-cachy.bak"
|
||||||
|
CACHY_TARBALL="https://mirror.cachyos.org/cachyos-repo.tar.xz"
|
||||||
|
MIRROR="https://mirror.cachyos.org/repo/x86_64/cachyos"
|
||||||
|
CACHY_KEY="F3B607488DB35A47"
|
||||||
|
KEYSERVER="keyserver.ubuntu.com"
|
||||||
|
|
||||||
|
DRYRUN=0
|
||||||
|
ARGS=()
|
||||||
|
for a in "$@"; do
|
||||||
|
if [[ "$a" == "--dry-run" ]]; then DRYRUN=1; else ARGS+=("$a"); fi
|
||||||
|
done
|
||||||
|
set -- ${ARGS+"${ARGS[@]}"}
|
||||||
|
CMD="${1:-}"
|
||||||
|
|
||||||
|
run() { echo "+ $*"; [[ $DRYRUN -eq 1 ]] && return 0; "$@"; }
|
||||||
|
|
||||||
|
detect_tier() {
|
||||||
|
local h; h=$(/lib/ld-linux-x86-64.so.2 --help 2>/dev/null)
|
||||||
|
if grep -q "x86-64-v4 (supported" <<<"$h"; then echo v4
|
||||||
|
elif grep -q "x86-64-v3 (supported" <<<"$h"; then echo v3
|
||||||
|
else echo none; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
is_enabled() { grep -qE '^\[cachyos' "$PACCONF" 2>/dev/null; }
|
||||||
|
|
||||||
|
# --- durable stock-pacman pin -------------------------------------------------
|
||||||
|
# `--ignore pacman` only pins pacman for THIS helper's own transaction. While the
|
||||||
|
# CachyOS repos are enabled, a routine `pacman -Syu` / `yay -Syu` / nosignal-update
|
||||||
|
# upgrades pacman to the CachyOS build, which stamps `%INSTALLED_DB%` into every
|
||||||
|
# local-DB entry it touches — stock pacman then warns about that unknown key on
|
||||||
|
# EVERY op (a flood that looks exactly like DB corruption). So while enabled we
|
||||||
|
# pin pacman in pacman.conf via IgnorePkg too; disable removes the pin. NoSignal
|
||||||
|
# ships no IgnorePkg by default, so we manage a single standalone line.
|
||||||
|
ensure_ignore_pacman() {
|
||||||
|
grep -qE '^[[:space:]]*IgnorePkg[[:space:]]*=.*\bpacman\b' "$PACCONF" && return 0
|
||||||
|
if grep -qE '^[[:space:]]*IgnorePkg[[:space:]]*=' "$PACCONF"; then
|
||||||
|
run sed -i -E 's/^([[:space:]]*IgnorePkg[[:space:]]*=.*)$/\1 pacman/' "$PACCONF"
|
||||||
|
else
|
||||||
|
run sed -i -E '/^\[options\]/a IgnorePkg = pacman' "$PACCONF"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_ignore_pacman() {
|
||||||
|
# remove a standalone `IgnorePkg = pacman` line we added…
|
||||||
|
run sed -i -E '/^[[:space:]]*IgnorePkg[[:space:]]*=[[:space:]]*pacman[[:space:]]*$/d' "$PACCONF"
|
||||||
|
# …or drop just the `pacman` token from a shared IgnorePkg list
|
||||||
|
run sed -i -E 's/^([[:space:]]*IgnorePkg[[:space:]]*=.*[[:space:]])pacman([[:space:],]|$)/\1\2/' "$PACCONF"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Strip the CachyOS `%INSTALLED_DB%` field from the pacman LOCAL DB (left behind
|
||||||
|
# whenever a CachyOS pacman ran while enabled — e.g. a normal -Syu). The field is
|
||||||
|
# informational only; stock pacman doesn't use it and just warns. Removing it
|
||||||
|
# silences the warning flood. Each block is exactly `%INSTALLED_DB%` + one value
|
||||||
|
# line + a blank separator. Idempotent: only rewrites desc files that contain it.
|
||||||
|
strip_installed_db() {
|
||||||
|
local db="/var/lib/pacman/local" n=0 desc tmp hits
|
||||||
|
hits=$(grep -lrx '%INSTALLED_DB%' "$db"/*/desc 2>/dev/null) || true
|
||||||
|
[[ -z "$hits" ]] && { echo "Local pacman DB already clean (no %INSTALLED_DB%)."; return 0; }
|
||||||
|
while IFS= read -r desc; do
|
||||||
|
[[ -z "$desc" ]] && continue
|
||||||
|
if [[ $DRYRUN -eq 1 ]]; then echo "+ strip %INSTALLED_DB% from $desc"; n=$((n+1)); continue; fi
|
||||||
|
tmp="$desc.nsclean.$$"
|
||||||
|
if awk '/^%INSTALLED_DB%$/{s=1;next} s&&/^$/{s=0;next} s{next} {print}' "$desc" > "$tmp" \
|
||||||
|
&& chmod --reference="$desc" "$tmp" 2>/dev/null; then
|
||||||
|
mv -f "$tmp" "$desc"; n=$((n+1))
|
||||||
|
else
|
||||||
|
rm -f "$tmp"
|
||||||
|
fi
|
||||||
|
done <<< "$hits"
|
||||||
|
echo "Stripped %INSTALLED_DB% from $n local-DB entr(y/ies); stock pacman is quiet again."
|
||||||
|
}
|
||||||
|
|
||||||
|
need_root() {
|
||||||
|
[[ $EUID -eq 0 ]] && return 0
|
||||||
|
echo "Error: must run as root (the toggle uses: sudo -n $0 $CMD)" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fetch + extract the upstream repo tarball (for the maintained .awk stanza
|
||||||
|
# files + current keyring/mirrorlist package versions). Echoes the dir.
|
||||||
|
fetch_upstream() {
|
||||||
|
local d; d=$(mktemp -d /tmp/nosignal-cachy.XXXXXX) || return 1
|
||||||
|
if [[ $DRYRUN -eq 1 ]]; then echo "$d"; return 0; fi
|
||||||
|
curl -fsSL "$CACHY_TARBALL" | tar -xJ -C "$d" || return 1
|
||||||
|
echo "$d"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$CMD" in
|
||||||
|
detect) detect_tier ;;
|
||||||
|
|
||||||
|
status) if is_enabled; then echo enabled; exit 0; else echo disabled; exit 1; fi ;;
|
||||||
|
|
||||||
|
enable)
|
||||||
|
need_root
|
||||||
|
if ! is_enabled; then
|
||||||
|
tier=$(detect_tier)
|
||||||
|
if [[ "$tier" == none ]]; then
|
||||||
|
echo "Error: CPU supports neither x86-64-v3 nor v4; CachyOS optimized repos" >&2
|
||||||
|
echo " unavailable. Nothing changed." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
echo "Detected microarchitecture tier: x86-64-$tier"
|
||||||
|
up=$(fetch_upstream) || { echo "Error: could not fetch CachyOS repo tarball (network?)." >&2; exit 4; }
|
||||||
|
trap 'rm -rf "$up"' EXIT
|
||||||
|
src="$up/cachyos-repo"
|
||||||
|
|
||||||
|
# 1) trust the CachyOS signing key
|
||||||
|
run pacman-key --recv-keys "$CACHY_KEY" --keyserver "$KEYSERVER"
|
||||||
|
run pacman-key --lsign-key "$CACHY_KEY"
|
||||||
|
|
||||||
|
# 2) install keyring + mirrorlists (versions tracked from upstream script;
|
||||||
|
# deliberately EXCLUDE the upstream 'pacman-*' replacement — keep stock pacman)
|
||||||
|
pkgs=()
|
||||||
|
if [[ $DRYRUN -eq 1 ]]; then
|
||||||
|
pkgs=(cachyos-keyring-XX cachyos-mirrorlist-XX cachyos-v3-mirrorlist-XX cachyos-v4-mirrorlist-XX)
|
||||||
|
else
|
||||||
|
mapfile -t pkgs < <(grep -oE '[a-z0-9.+_-]+\.pkg\.tar\.zst' "$src/cachyos-repo.sh" \
|
||||||
|
| grep -vE '^pacman-' | sort -u)
|
||||||
|
fi
|
||||||
|
urls=(); for p in "${pkgs[@]}"; do urls+=("$MIRROR/$p"); done
|
||||||
|
run pacman -U --noconfirm "${urls[@]}"
|
||||||
|
|
||||||
|
# 3) add the repo stanzas via upstream's maintained awk (tier-appropriate).
|
||||||
|
case "$tier" in
|
||||||
|
v4) awkf="install-v4-repo.awk" ;;
|
||||||
|
v3) awkf="install-repo.awk" ;;
|
||||||
|
esac
|
||||||
|
run cp -a "$PACCONF" "$PACBAK"
|
||||||
|
run gawk -i inplace -f "$src/$awkf" "$PACCONF"
|
||||||
|
|
||||||
|
# The upstream awk sets `Architecture = auto`, which STOCK pacman reads as
|
||||||
|
# just x86_64 — so it would REJECT every x86-64-v3/v4 package ("does not
|
||||||
|
# have a valid architecture") and the conversion would silently do nothing.
|
||||||
|
# (CachyOS's own pacman build reads `auto` as v3/v4; we keep stock pacman.)
|
||||||
|
# So set the tier arch EXPLICITLY.
|
||||||
|
case "$tier" in
|
||||||
|
v4) arch="x86_64 x86_64_v3 x86_64_v4" ;;
|
||||||
|
v3) arch="x86_64 x86_64_v3" ;;
|
||||||
|
esac
|
||||||
|
run sed -i "s/^Architecture = .*/Architecture = $arch/" "$PACCONF"
|
||||||
|
run pacman -Sy
|
||||||
|
|
||||||
|
if [[ $DRYRUN -eq 0 ]] && ! is_enabled; then
|
||||||
|
echo "Error: CachyOS repos not present in $PACCONF after setup." >&2
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
echo "CachyOS repos added (x86-64-$tier)."
|
||||||
|
else
|
||||||
|
echo "CachyOS repos already present — upgrading to optimized builds…"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pull the optimized builds, UNATTENDED (--noconfirm) so a single toggle flip
|
||||||
|
# auto-converts the system without a prompt. The GUI still shows the full
|
||||||
|
# transaction in the floating terminal for visibility. NB: --noconfirm
|
||||||
|
# auto-accepts replacements/removals pacman proposes — that's the trade-off
|
||||||
|
# for hands-off conversion.
|
||||||
|
#
|
||||||
|
# --ignore pacman: the cachyos repo ships its OWN pacman build; without this
|
||||||
|
# the -Suu swaps stock pacman for it, which stamps a `%INSTALLED_DB%` key into
|
||||||
|
# every local-DB entry that stock pacman then warns about on every op. Pin
|
||||||
|
# pacman to the stock build so the DB stays clean (matches the keyring/mirror-
|
||||||
|
# list exclusion in step 2 — stock pacman is kept throughout).
|
||||||
|
#
|
||||||
|
# Make that pin DURABLE: --ignore only covers this transaction, but the repos
|
||||||
|
# stay enabled afterwards, so a later routine -Syu would pull the CachyOS pacman
|
||||||
|
# and contaminate the local DB with %INSTALLED_DB%. Pin pacman in pacman.conf
|
||||||
|
# for as long as CachyOS is enabled (disable removes the pin).
|
||||||
|
ensure_ignore_pacman
|
||||||
|
run pacman -Suu --noconfirm --ignore pacman
|
||||||
|
|
||||||
|
# Install the CachyOS KERNEL. The repo switch above converts USERSPACE only;
|
||||||
|
# the kernel is a separate, explicit install. linux-cachyos is CachyOS's
|
||||||
|
# default (sched-ext + BORE, LTO, v3/v4-tuned). The limine-mkinitcpio-hook
|
||||||
|
# auto-generates the boot entry — no manual limine.conf editing. --needed keeps
|
||||||
|
# a re-flip idempotent; stock `linux` is left in place as a fallback entry.
|
||||||
|
run pacman -S --noconfirm --needed linux-cachyos linux-cachyos-headers
|
||||||
|
echo "CachyOS optimized builds + linux-cachyos kernel installed."
|
||||||
|
echo ">> REBOOT and pick the linux-cachyos entry at the Limine menu to run it."
|
||||||
|
;;
|
||||||
|
|
||||||
|
disable)
|
||||||
|
need_root
|
||||||
|
if ! is_enabled; then echo "CachyOS repos already disabled."; exit 0; fi
|
||||||
|
up=$(fetch_upstream) || up=""
|
||||||
|
[[ -n "$up" ]] && trap 'rm -rf "$up"' EXIT
|
||||||
|
|
||||||
|
# 1) strip the CachyOS repo stanzas (pacman now sees only the stock repos)
|
||||||
|
run cp -a "$PACCONF" "$PACBAK"
|
||||||
|
if [[ -n "$up" && ( $DRYRUN -eq 1 || -f "$up/cachyos-repo/remove-repo.awk" ) ]]; then
|
||||||
|
run gawk -i inplace -f "$up/cachyos-repo/remove-repo.awk" "$PACCONF"
|
||||||
|
fi
|
||||||
|
if [[ $DRYRUN -eq 0 ]] && is_enabled; then # belt-and-braces if any remain
|
||||||
|
run sed -i '/^\[cachyos/,/^$/d' "$PACCONF"
|
||||||
|
fi
|
||||||
|
# restore the stock Architecture (enable set it to the v3/v4 tier explicitly)
|
||||||
|
run sed -i 's/^Architecture = .*/Architecture = auto/' "$PACCONF"
|
||||||
|
# drop the durable stock-pacman pin enable added (no longer needed off cachy)
|
||||||
|
remove_ignore_pacman
|
||||||
|
|
||||||
|
# 2) TARGETED REVERT — put ONLY the CachyOS-built packages back on stock.
|
||||||
|
# -Suuy refreshes the stock DBs and downgrades/upgrades any cachy package
|
||||||
|
# whose version differs from stock (its Packager flips back to Arch). Then
|
||||||
|
# force-reinstall the REMAINING same-version CachyOS rebuilds — found by
|
||||||
|
# their Packager ("CachyOS <admin@cachyos.org>") recorded in the local DB —
|
||||||
|
# so we touch ONLY what was actually converted, never the whole base
|
||||||
|
# (nothing converted => nothing to do). UNATTENDED (--noconfirm).
|
||||||
|
# A CachyOS-built package with no stock equivalent (e.g. a linux-cachyos
|
||||||
|
# kernel) is auto-skipped and REPORTED — not auto-removed (could be the
|
||||||
|
# running kernel); the user decides.
|
||||||
|
run pacman -Suuy --noconfirm
|
||||||
|
|
||||||
|
# Remove the CachyOS kernel — but ONLY once stock `linux` is confirmed present,
|
||||||
|
# so the box never loses its last bootable kernel. If stock linux is somehow
|
||||||
|
# missing, install it FIRST; if it still can't be installed, KEEP linux-cachyos.
|
||||||
|
# The limine-mkinitcpio-hook drops the boot entry automatically on removal.
|
||||||
|
# (linux-cachyos is CachyOS-exclusive, so the targeted revert below would
|
||||||
|
# otherwise just report it as an orphan and leave it installed + running.)
|
||||||
|
if [[ $DRYRUN -eq 1 ]]; then
|
||||||
|
echo "+ (if linux-cachyos present) ensure stock linux installed, then:"
|
||||||
|
echo "+ pacman -Rns --noconfirm linux-cachyos linux-cachyos-headers"
|
||||||
|
elif pacman -Qq linux-cachyos >/dev/null 2>&1; then
|
||||||
|
if ! pacman -Qq linux >/dev/null 2>&1; then
|
||||||
|
echo "Stock 'linux' missing — installing it before removing the CachyOS kernel…"
|
||||||
|
run pacman -S --noconfirm --needed linux linux-headers
|
||||||
|
fi
|
||||||
|
if pacman -Qq linux >/dev/null 2>&1; then
|
||||||
|
run pacman -Rns --noconfirm linux-cachyos linux-cachyos-headers
|
||||||
|
echo "Removed the CachyOS kernel; stock 'linux' remains as the boot entry."
|
||||||
|
echo ">> REBOOT into stock 'linux' at the Limine menu to stop running the CachyOS kernel."
|
||||||
|
else
|
||||||
|
echo "WARNING: could not ensure stock 'linux' is installed —"
|
||||||
|
echo " KEEPING linux-cachyos so the box stays bootable. Remove it manually"
|
||||||
|
echo " only after a stock kernel is installed."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $DRYRUN -eq 1 ]]; then
|
||||||
|
echo "+ pacman -S --noconfirm <CachyOS-built pkgs (by Packager) that exist in stock>"
|
||||||
|
echo "+ (CachyOS-exclusive pkgs are reported, not reinstalled or removed)"
|
||||||
|
else
|
||||||
|
# CachyOS-built packages (by Packager), EXCLUDING the cachy keyring/mirrorlist
|
||||||
|
# packages themselves — those are removed in step 3, not reinstalled.
|
||||||
|
mapfile -t cachy_built < <(LC_ALL=C pacman -Qi 2>/dev/null \
|
||||||
|
| awk -F': ' '/^Name +:/{n=$2} /^Packager +:/{if ($2 ~ /CachyOS/) print n}' \
|
||||||
|
| grep -vxE 'cachyos-(keyring|mirrorlist|v3-mirrorlist|v4-mirrorlist)')
|
||||||
|
if [[ ${#cachy_built[@]} -eq 0 ]]; then
|
||||||
|
echo "No CachyOS-built packages remain — nothing to reinstall."
|
||||||
|
else
|
||||||
|
mapfile -t avail < <(pacman -Slq)
|
||||||
|
declare -A in_stock=(); for p in "${avail[@]}"; do in_stock["$p"]=1; done
|
||||||
|
reinstall=(); orphan=()
|
||||||
|
for p in "${cachy_built[@]}"; do
|
||||||
|
if [[ -n "${in_stock[$p]:-}" ]]; then reinstall+=("$p"); else orphan+=("$p"); fi
|
||||||
|
done
|
||||||
|
[[ ${#reinstall[@]} -gt 0 ]] && run pacman -S --noconfirm "${reinstall[@]}"
|
||||||
|
if [[ ${#orphan[@]} -gt 0 ]]; then
|
||||||
|
echo
|
||||||
|
echo "NOTE: ${#orphan[@]} CachyOS-built package(s) have no stock equivalent"
|
||||||
|
echo " (CachyOS-exclusive) — left in place, review/remove manually:"
|
||||||
|
printf ' %s\n' "${orphan[@]}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3) remove the CachyOS keyring/mirrorlists + trust key (best-effort)
|
||||||
|
run sh -c "pacman -Rns --noconfirm cachyos-keyring cachyos-mirrorlist cachyos-v3-mirrorlist cachyos-v4-mirrorlist 2>/dev/null || true"
|
||||||
|
run sh -c "pacman-key --delete $CACHY_KEY 2>/dev/null || true"
|
||||||
|
|
||||||
|
# 4) clean up any %INSTALLED_DB% the CachyOS pacman stamped into the local DB
|
||||||
|
# while enabled (otherwise stock pacman warns about it on every op forever).
|
||||||
|
strip_installed_db
|
||||||
|
echo "CachyOS disabled; native packages reverted to stock Arch builds."
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {detect|status|enable|disable} [--dry-run]" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
44
os updates/cachyos-repo-switch/patch-servicespage.sh
Executable file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# patch-servicespage.sh — add the "CachyOS repositories" toggle to the caelestia
|
||||||
|
# Settings -> Services page. Runs as root (installer, or the pacman hook after a
|
||||||
|
# caelestia-shell upgrade reverts ServicesPage.qml). Idempotent.
|
||||||
|
#
|
||||||
|
# 1. install CachyRepoToggleRow.qml into nexus/common/ (untracked -> survives upgrades)
|
||||||
|
# 2. insert `CachyRepoToggleRow {}` after the "Smart colour scheme" ToggleRow in
|
||||||
|
# ServicesPage.qml (same anchor the sudo toggle uses; both land together)
|
||||||
|
#
|
||||||
|
# NoSignal bakes both the component and the page insert into the pinned
|
||||||
|
# nosignal-shell fork, so this patch is only for migration of older
|
||||||
|
# stock-caelestia installs (run by the nosignal-update migration).
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SELF_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
NEXUS=${NEXUS:-/etc/xdg/quickshell/caelestia/modules/nexus}
|
||||||
|
COMMON="$NEXUS/common"
|
||||||
|
PAGE="$NEXUS/pages/ServicesPage.qml"
|
||||||
|
|
||||||
|
[ -f "$PAGE" ] || { echo "caelestia ServicesPage not found at $PAGE — nothing to patch"; exit 0; }
|
||||||
|
|
||||||
|
# 1. component type (auto-discovered via `import qs.modules.nexus.common`)
|
||||||
|
install -m 0644 "$SELF_DIR/CachyRepoToggleRow.qml" "$COMMON/CachyRepoToggleRow.qml"
|
||||||
|
|
||||||
|
# 2. insert the row (idempotent)
|
||||||
|
if grep -q 'CachyRepoToggleRow' "$PAGE"; then
|
||||||
|
echo ":: ServicesPage already has the CachyOS toggle"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
grep -q 'smartScheme = checked' "$PAGE" || {
|
||||||
|
echo "WARNING: anchor (Smart colour scheme ToggleRow) not found in ServicesPage — skipping insert" >&2
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
cp -n "$PAGE" "$PAGE.pre-nosignal-cachy" 2>/dev/null || true
|
||||||
|
perl -0pi -e 's/(onToggled: GlobalConfig\.services\.smartScheme = checked\n\s*\})/$1\n\n CachyRepoToggleRow {\n Layout.fillWidth: true\n }/' "$PAGE"
|
||||||
|
|
||||||
|
if grep -q 'CachyRepoToggleRow' "$PAGE"; then
|
||||||
|
echo ":: patched ServicesPage (CachyOS toggle added)"
|
||||||
|
else
|
||||||
|
echo "WARNING: ServicesPage insert did not take; restoring" >&2
|
||||||
|
[ -f "$PAGE.pre-nosignal-cachy" ] && cp "$PAGE.pre-nosignal-cachy" "$PAGE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
60
os updates/caelestia-lock-faillock/README.md
Executable file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# caelestia-lock-faillock
|
||||||
|
|
||||||
|
## Symptom
|
||||||
|
User locked the screen; the Caelestia lock screen **would not accept the
|
||||||
|
correct password**. Only recovery was a hard **reboot**.
|
||||||
|
|
||||||
|
## Evidence (prior-boot journal, `journalctl --user -b -1`)
|
||||||
|
```
|
||||||
|
qs[…]: PAM _pam_init_handlers: no default config other
|
||||||
|
qs[…]: pam_unix(passwd:auth): authentication failure; … user=q (×3)
|
||||||
|
qs[…]: pam_faillock(passwd:auth): Consecutive login failures for user q account temporarily locked
|
||||||
|
```
|
||||||
|
(`qs` = Quickshell = the Caelestia shell hosting the lock.)
|
||||||
|
|
||||||
|
## Root cause (lock-out mechanism — high confidence)
|
||||||
|
- The lock authenticates via `PamContext { config: "passwd" }`
|
||||||
|
(`modules/lock/Pam.qml:14`) → the **`passwd` PAM service**.
|
||||||
|
- On this build `/etc/pam.d/passwd` was extended to `auth include system-auth`,
|
||||||
|
and **system-auth runs `pam_faillock`**.
|
||||||
|
- Failed unlocks are counted by faillock; past the threshold it **temporarily
|
||||||
|
locks the account**, after which *every* attempt — including the correct
|
||||||
|
password — is refused until the `/run/faillock` tally clears (reboot, or
|
||||||
|
`faillock --reset`). That is exactly the reboot-to-recover the user hit.
|
||||||
|
|
||||||
|
## The initial failure trigger
|
||||||
|
Two possible causes of the first failures (before faillock engaged):
|
||||||
|
1. **Keyboard layout** on the lock surface (this install is **GB**; if the lock
|
||||||
|
comes up US-layout, `@ " # £ \ | ~` mistype → auth fails).
|
||||||
|
2. **PAM stack init** (`no default config other`; module ordering under a
|
||||||
|
uid-1000 Quickshell process).
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
1. **Stop the lock from locking the account.** Ship a dedicated, **faillock-free**
|
||||||
|
PAM service `/etc/pam.d/caelestia` (provided: `etc-pam.d-caelestia`;
|
||||||
|
installed by `install-caelestia-lock-pam.sh`, root).
|
||||||
|
2. **Repoint the lock at it.** `modules/lock/Pam.qml`: `config: "passwd"` →
|
||||||
|
`config: "caelestia"`.
|
||||||
|
|
||||||
|
A desktop lock must fail **closed-but-recoverable** (re-prompt), never lock the
|
||||||
|
account such that the correct password is refused.
|
||||||
|
|
||||||
|
## Builder integration (important)
|
||||||
|
`modules/lock/Pam.qml` lives under `/etc/xdg/quickshell/caelestia`, which is
|
||||||
|
re-applied by a pacman hook after every `caelestia-shell` upgrade. The `config:`
|
||||||
|
repoint must therefore be added to NoSignal's **quickshell-patch + pacman-hook**
|
||||||
|
mechanism — the same one that re-applies the Updates page, Additions page, and
|
||||||
|
`NetworkConnection.qml` patches — as a 4th patched file. Do **not** hand-edit it
|
||||||
|
in place (a shell upgrade would revert it). Expected behaviour: lock → wrong
|
||||||
|
password ×5 (keeps re-prompting, account stays usable) → correct password
|
||||||
|
unlocks; the lock-surface layout is GB.
|
||||||
|
|
||||||
|
## Recovery for the user, no reboot
|
||||||
|
TTY (Ctrl+Alt+F2) login → `sudo faillock --user "$USER" --reset` → back to the
|
||||||
|
session (Ctrl+Alt+F1) and unlock. `Super+Alt+L` recovers a *hung* lock but does
|
||||||
|
**not** clear a faillock lock.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
- `etc-pam.d-caelestia` — proposed faillock-free PAM service.
|
||||||
|
- `install-caelestia-lock-pam.sh` — idempotent installer (root).
|
||||||
|
- `migrations/1781431200-caelestia-lock-pam.sh` — delegates to it.
|
||||||
20
os updates/caelestia-lock-faillock/etc-pam.d-caelestia
Executable file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#%PAM-1.0
|
||||||
|
# Caelestia lock-screen authentication (NoSignal).
|
||||||
|
#
|
||||||
|
# Deliberately does NOT use pam_faillock: a desktop screen-lock must never lock
|
||||||
|
# the user out of their OWN running session. With faillock in the path (the old
|
||||||
|
# "passwd"->system-auth route), a few failed unlocks tripped a temporary account
|
||||||
|
# lock that then refused the *correct* password until /run/faillock cleared on
|
||||||
|
# reboot. See README.md.
|
||||||
|
#
|
||||||
|
# pam_unix verifies the password supplied via the PAM conversation (the lock UI)
|
||||||
|
# using the setuid unix_chkpwd helper, so it works for the uid-1000 Quickshell
|
||||||
|
# process. Add pam_systemd_home below if/when systemd-homed users are supported.
|
||||||
|
#
|
||||||
|
# F8a: deliberately NO `nullok` — a screen lock must never accept an empty
|
||||||
|
# password. (Only changes behaviour for empty-password accounts, which are
|
||||||
|
# correctly rejected; normal password auth is unaffected.)
|
||||||
|
auth required pam_unix.so
|
||||||
|
account required pam_unix.so
|
||||||
|
password required pam_unix.so
|
||||||
|
session required pam_unix.so
|
||||||
19
os updates/caelestia-lock-faillock/install-caelestia-lock-pam.sh
Executable file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# install-caelestia-lock-pam.sh — idempotent. REQUIRES ROOT.
|
||||||
|
#
|
||||||
|
# Thin wrapper kept for back-compat (older migrations / docs reference it).
|
||||||
|
# The real work — DELIVER the faillock-free PAM service into the directory the
|
||||||
|
# lock actually reads (shellDir/assets/pam.d/caelestia) AND repoint Pam.qml — is
|
||||||
|
# done by patch-lock-pam.sh, which is also what the caelestia-shell pacman hook
|
||||||
|
# runs after every upgrade. Delegating keeps a single source of truth and avoids
|
||||||
|
# the old F1 bug where this script wrote /etc/pam.d/caelestia (a path PamContext
|
||||||
|
# never reads), leaving the lock on the faillock'd assets/pam.d/passwd service.
|
||||||
|
set -eu
|
||||||
|
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
echo "must run as root" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec sh "$HERE/patch-lock-pam.sh"
|
||||||
72
os updates/caelestia-lock-faillock/patch-lock-pam.sh
Executable file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# patch-lock-pam.sh — make the Caelestia lock screen use a faillock-free PAM
|
||||||
|
# service. Two parts, both idempotent:
|
||||||
|
#
|
||||||
|
# 1. DELIVER the faillock-free `caelestia` service into the directory the lock
|
||||||
|
# actually reads — Quickshell.shellDir + "/assets/pam.d" — i.e.
|
||||||
|
# /etc/xdg/quickshell/caelestia/assets/pam.d/caelestia. (Placing it in
|
||||||
|
# /etc/pam.d/caelestia, which PamContext never reads, makes the lock
|
||||||
|
# fall through to assets/pam.d/passwd — still faillock'd — and reject even
|
||||||
|
# the correct password.)
|
||||||
|
# 2. REPOINT the lock's PamContext at it: config: "passwd" -> "caelestia".
|
||||||
|
#
|
||||||
|
# Runs as root (via the installer, or the pacman hook after every caelestia-shell
|
||||||
|
# upgrade, which restores the shell tree under /etc/xdg — so BOTH the asset file
|
||||||
|
# and the repoint must be re-applied here each time, or an upgrade drops them).
|
||||||
|
#
|
||||||
|
# Symptom (before this fix): the lock screen rejects the CORRECT password; only a
|
||||||
|
# reboot escapes. The stock lock authenticates via PamContext { config: "passwd",
|
||||||
|
# configDirectory: shellDir + "/assets/pam.d" } -> assets/pam.d/passwd, which
|
||||||
|
# calls pam_faillock; a few failed unlocks trip a temporary account lock that
|
||||||
|
# then refuses every attempt (right password included) until /run/faillock clears.
|
||||||
|
#
|
||||||
|
# TARGET (Pam.qml) is overridable for testing the change against a copy; the
|
||||||
|
# asset destination is derived from it (shellDir = TARGET/../../..).
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
SRC_PAM="$HERE/etc-pam.d-caelestia" # the faillock-free service we ship
|
||||||
|
|
||||||
|
TARGET=${TARGET:-/etc/xdg/quickshell/caelestia/modules/lock/Pam.qml}
|
||||||
|
|
||||||
|
[ -f "$TARGET" ] || { echo "caelestia-shell lock not found at $TARGET — nothing to patch"; exit 0; }
|
||||||
|
|
||||||
|
# shellDir = the caelestia shell root = three levels up from modules/lock/Pam.qml.
|
||||||
|
SHELLDIR=$(CDPATH= cd -- "$(dirname -- "$TARGET")/../.." && pwd)
|
||||||
|
ASSETS_PAMD="$SHELLDIR/assets/pam.d"
|
||||||
|
|
||||||
|
# --- 1. deliver the faillock-free service where the lock reads it -------------
|
||||||
|
if [ -f "$SRC_PAM" ]; then
|
||||||
|
install -d -m 0755 "$ASSETS_PAMD"
|
||||||
|
install -m 0644 "$SRC_PAM" "$ASSETS_PAMD/caelestia"
|
||||||
|
echo ":: installed $ASSETS_PAMD/caelestia (faillock-free lock auth)"
|
||||||
|
else
|
||||||
|
echo "WARNING: source PAM service $SRC_PAM missing — cannot deliver the lock service." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 2. repoint the lock's PamContext at it ----------------------------------
|
||||||
|
# Already pointed at our service?
|
||||||
|
if grep -q 'config:[[:space:]]*"caelestia"' "$TARGET"; then
|
||||||
|
echo ":: Pam.qml already points at the caelestia lock service"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only act if the stock "passwd" service is referenced.
|
||||||
|
if ! grep -q 'config:[[:space:]]*"passwd"' "$TARGET"; then
|
||||||
|
echo "WARNING: Pam.qml does not reference config: \"passwd\" — upstream shape changed." >&2
|
||||||
|
echo " Leaving it untouched; review the lock PamContext for the new" >&2
|
||||||
|
echo " service name and update the regex in $(basename "$0")." >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp -n "$TARGET" "$TARGET.pre-nosignal" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Repoint only the PAM service name; keep everything else as-is.
|
||||||
|
sed -i 's/config:\([[:space:]]*\)"passwd"/config:\1"caelestia"/g' "$TARGET"
|
||||||
|
|
||||||
|
if grep -q 'config:[[:space:]]*"caelestia"' "$TARGET"; then
|
||||||
|
echo ":: patched $TARGET (lock now uses the faillock-free caelestia PAM service)"
|
||||||
|
else
|
||||||
|
echo "WARNING: patch did not apply — Pam.qml left untouched." >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
44
os updates/cheatsheet-keymap-path/README.md
Executable file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# cheatsheet-keymap-path
|
||||||
|
|
||||||
|
User-level, no root.
|
||||||
|
|
||||||
|
## Symptom / risk
|
||||||
|
The Super+K cheatsheet is generated by `nosignal-keybinds-gen` from the keymap
|
||||||
|
markdown. Its source-resolution order is:
|
||||||
|
|
||||||
|
```
|
||||||
|
$1 arg → $NOSIGNAL_KEYMAP_MD → $HOME/Downloads/NoSignal-keybindings.md
|
||||||
|
→ $HOME/.local/share/nosignal/NoSignal-keybindings.md
|
||||||
|
```
|
||||||
|
|
||||||
|
`~/Downloads` is checked **before** the canonical layer copy. Today that copy is
|
||||||
|
absent so it falls through and works — but any stale `NoSignal-keybindings.md`
|
||||||
|
left in `~/Downloads` (a common scratch location) would **silently shadow** the
|
||||||
|
real keymap, so the cheatsheet would show wrong/old keys. The `nosignal-keybinds`
|
||||||
|
header comment also still says the doc lives in `~/Downloads`.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
- `nosignal-keybinds-gen`: drop the `~/Downloads` candidate so the canonical
|
||||||
|
`~/.local/share/nosignal/NoSignal-keybindings.md` is the only implicit source.
|
||||||
|
The explicit `$1` arg and `$NOSIGNAL_KEYMAP_MD` override are kept for dev use.
|
||||||
|
- `nosignal-keybinds`: correct the header comment to point at the canonical path.
|
||||||
|
|
||||||
|
`fix-cheatsheet-keymap-path.sh` patches the on-system scripts idempotently
|
||||||
|
(awk for the candidate line + a sanity `sh -n` before saving; sed for the
|
||||||
|
comment), then refreshes the cached `keybinds.list`. No-op if already fixed.
|
||||||
|
|
||||||
|
## Builder integration
|
||||||
|
Make the canonical change in the layer's `nosignal-keybinds-gen` and
|
||||||
|
`nosignal-keybinds` sources directly; this component is the equivalent patch for
|
||||||
|
an already-installed system.
|
||||||
|
|
||||||
|
## Verify
|
||||||
|
```
|
||||||
|
grep -n 'NoSignal-keybindings.md' "$(readlink -f "$(command -v nosignal-keybinds-gen)")"
|
||||||
|
# only the ~/.local/share/nosignal path should remain (no ~/Downloads)
|
||||||
|
nosignal-keybinds-gen | head -3 # still generates the keymap
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files
|
||||||
|
- `fix-cheatsheet-keymap-path.sh` — idempotent patcher.
|
||||||
|
- `migrations/1781445600-cheatsheet-keymap-path.sh` — delegates to it.
|
||||||
51
os updates/cheatsheet-keymap-path/fix-cheatsheet-keymap-path.sh
Executable file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# fix-cheatsheet-keymap-path.sh — idempotent. User-level (no root).
|
||||||
|
#
|
||||||
|
# The Super+K cheatsheet generator resolves its keymap doc from
|
||||||
|
# $HOME/Downloads/NoSignal-keybindings.md *before* the canonical
|
||||||
|
# $HOME/.local/share/nosignal/NoSignal-keybindings.md. A stale copy left in
|
||||||
|
# ~/Downloads would silently shadow the real keymap. The nosignal-keybinds
|
||||||
|
# header comment also still claims the doc lives in ~/Downloads.
|
||||||
|
#
|
||||||
|
# Fix: drop the ~/Downloads candidate from nosignal-keybinds-gen (the $1 arg and
|
||||||
|
# $NOSIGNAL_KEYMAP_MD env override remain for dev use), and correct the comment.
|
||||||
|
# Edits the real files behind the ~/.local/bin symlinks. Re-runnable.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
resolve() { command -v "$1" >/dev/null 2>&1 && readlink -f -- "$(command -v "$1")"; }
|
||||||
|
GEN=$(resolve nosignal-keybinds-gen || true)
|
||||||
|
NK=$(resolve nosignal-keybinds || true)
|
||||||
|
|
||||||
|
# 1. nosignal-keybinds-gen: remove the ~/Downloads candidate line.
|
||||||
|
if [ -n "${GEN:-}" ] && grep -q 'Downloads/NoSignal-keybindings.md' "$GEN"; then
|
||||||
|
tmp=$(mktemp)
|
||||||
|
awk '
|
||||||
|
/for cand in "\$HOME\/Downloads\/NoSignal-keybindings\.md"/ { print " for cand in \\"; next }
|
||||||
|
{ print }
|
||||||
|
' "$GEN" > "$tmp"
|
||||||
|
# sanity: canonical path still present, Downloads gone, still parses
|
||||||
|
if grep -q '\.local/share/nosignal/NoSignal-keybindings.md' "$tmp" \
|
||||||
|
&& ! grep -q 'Downloads/NoSignal-keybindings.md' "$tmp" \
|
||||||
|
&& sh -n "$tmp" 2>/dev/null; then
|
||||||
|
cat "$tmp" > "$GEN"; echo "patched: $GEN (dropped ~/Downloads candidate)"
|
||||||
|
else
|
||||||
|
echo "WARN: gen rewrite failed sanity — left $GEN untouched" >&2
|
||||||
|
fi
|
||||||
|
rm -f "$tmp"
|
||||||
|
else
|
||||||
|
echo "nosignal-keybinds-gen already canonical (or not found) — skipped"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. nosignal-keybinds: fix the header comment path.
|
||||||
|
if [ -n "${NK:-}" ] && grep -q '~/Downloads/NoSignal-keybindings.md' "$NK"; then
|
||||||
|
sed -i 's#~/Downloads/NoSignal-keybindings.md#~/.local/share/nosignal/NoSignal-keybindings.md#g' "$NK"
|
||||||
|
echo "patched: $NK (comment path corrected)"
|
||||||
|
else
|
||||||
|
echo "nosignal-keybinds comment already correct (or not found) — skipped"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# refresh the cached cheatsheet list so the change takes effect immediately
|
||||||
|
[ -n "${GEN:-}" ] && "$GEN" > "$HOME/.local/share/nosignal/keybinds.list.tmp" 2>/dev/null \
|
||||||
|
&& mv "$HOME/.local/share/nosignal/keybinds.list.tmp" "$HOME/.local/share/nosignal/keybinds.list" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "cheatsheet-keymap-path: ok"
|
||||||
54
os updates/cheatsheet-tidy/README.md
Executable file
|
|
@ -0,0 +1,54 @@
|
||||||
|
# cheatsheet-tidy — Super+K panel shows key + action only, fitted
|
||||||
|
|
||||||
|
Reworks the cheatsheet output so the Super+K panel stays compact.
|
||||||
|
|
||||||
|
## Symptom
|
||||||
|
|
||||||
|
The Super+K panel was cluttered and overflowed: a `[Section]` tag column
|
||||||
|
ate ~20 chars of every line, actions carried doc-provenance notes like
|
||||||
|
"(Omarchy key)", and lines ran up to 200 chars in a
|
||||||
|
72-char fuzzel panel — long actions were simply cut off mid-word.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
Full replacements of the two keybinds-help scripts (kept small on purpose —
|
||||||
|
the markdown stays the single source of truth and is untouched):
|
||||||
|
|
||||||
|
- **`nosignal-keybinds-gen`** — output is now two columns, key + action:
|
||||||
|
- section tag column dropped (the markdown keeps its sections);
|
||||||
|
- doc-only parentheticals stripped from actions — anything matching
|
||||||
|
provenance patterns (`Omarchy`, `change N`, `(was …`, `moved`,
|
||||||
|
`NoSignal`, `dev/debug`); meaningful ones like
|
||||||
|
"(logout / shutdown / reboot)" and "(alias, old key)" stay;
|
||||||
|
- actions ellipsis-truncated to `$NOSIGNAL_KEYS_COLS` (default 98) so
|
||||||
|
every line fits the panel.
|
||||||
|
- **`nosignal-keybinds`** — fuzzel `--width` 72 → 100, in sync with the
|
||||||
|
generator budget (98 = 100 − margin). Comment in each file points at
|
||||||
|
the other.
|
||||||
|
|
||||||
|
Result on the current keymap: 108 rows, all ≤ 98 chars, 6 lightly
|
||||||
|
ellipsis-truncated, none cut off by the panel.
|
||||||
|
|
||||||
|
The installer also refreshes the flat layer-root copies of both scripts
|
||||||
|
so a re-run of `install-keybinds-help.sh` cannot
|
||||||
|
regress the panel.
|
||||||
|
|
||||||
|
## Packaging
|
||||||
|
|
||||||
|
Replace the two scripts in the keybinds-help source with
|
||||||
|
these versions — this component then becomes part of keybinds-help rather
|
||||||
|
than a permanent overlay. No package/config delta, no keybind changes.
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
1. `Super+K` → every visible line is `KEY action`, no `[Section]`
|
||||||
|
tags, no "(Omarchy key)"-style noise, no line wider than the panel.
|
||||||
|
2. Type to filter (e.g. "workspace") — still searchable.
|
||||||
|
3. Select a line → still copies the shortcut to the clipboard.
|
||||||
|
4. `nosignal-keybinds-gen | awk '{print length($0)}' | sort -rn | head -1`
|
||||||
|
≤ 98.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Cached list regenerated; max line length 98/98;
|
||||||
|
row count identical before/after (108).
|
||||||
40
os updates/cheatsheet-tidy/install-cheatsheet-tidy.sh
Executable file
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# install-cheatsheet-tidy.sh — tidy the Super+K cheatsheet panel: key +
|
||||||
|
# action only, every line fits the panel.
|
||||||
|
#
|
||||||
|
# Ships full replacements of the keybinds-help scripts:
|
||||||
|
# - nosignal-keybinds-gen -> ~/.local/bin/ (no tag column, strips doc-only
|
||||||
|
# parentheticals, truncates actions to the panel budget)
|
||||||
|
# - nosignal-keybinds -> ~/.local/bin/ (fuzzel width 72 -> 100, kept in
|
||||||
|
# sync with the generator budget)
|
||||||
|
#
|
||||||
|
# Also refreshes the layer-root source copies when present, so a
|
||||||
|
# re-run of install-keybinds-help.sh cannot regress the panel.
|
||||||
|
# Safe to re-run (idempotent). No sudo needed.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SRC=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
BIN="$HOME/.local/bin"
|
||||||
|
ROOT=$(dirname -- "$SRC") # the layer root when run from the layer tree
|
||||||
|
|
||||||
|
mkdir -p "$BIN"
|
||||||
|
install -m 0755 "$SRC/nosignal-keybinds-gen" "$BIN/nosignal-keybinds-gen"
|
||||||
|
install -m 0755 "$SRC/nosignal-keybinds" "$BIN/nosignal-keybinds"
|
||||||
|
echo ":: installed tidied cheatsheet scripts -> $BIN"
|
||||||
|
|
||||||
|
# Keep the keybinds-help source copies in step (they live flat at the layer root).
|
||||||
|
# Check both the tree this component sits in and the installed layer location,
|
||||||
|
# so running from a handoff folder still fixes the on-system layer copies.
|
||||||
|
for d in "$ROOT" "$HOME/.local/share/nosignal"; do
|
||||||
|
for f in nosignal-keybinds-gen nosignal-keybinds; do
|
||||||
|
if [ -f "$d/$f" ] && ! cmp -s "$SRC/$f" "$d/$f"; then
|
||||||
|
install -m 0755 "$SRC/$f" "$d/$f"
|
||||||
|
echo ":: refreshed layer copy -> $d/$f"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# Refresh the cached list so the next Super+K shows the new format at once.
|
||||||
|
"$BIN/nosignal-keybinds-gen" > "$HOME/.local/share/nosignal/keybinds.list" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "Done. Super+K now shows key + action only, fitted to the panel."
|
||||||
46
os updates/cheatsheet-tidy/nosignal-keybinds
Executable file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# nosignal-keybinds — searchable on-screen keybinding cheatsheet for NoSignal.
|
||||||
|
# The keymap is NOT stored here: it is generated from the markdown doc by
|
||||||
|
# nosignal-keybinds-gen on every launch, so the cheatsheet can never drift from
|
||||||
|
# ~/Downloads/NoSignal-keybindings.md. The generated list is cached so the help
|
||||||
|
# still works if the markdown is temporarily missing.
|
||||||
|
#
|
||||||
|
# Read-only: typing filters the list; selecting a line copies that shortcut to
|
||||||
|
# the clipboard (if wl-copy is present) and never executes anything.
|
||||||
|
#
|
||||||
|
# --width 100 must stay in sync with the generator's $NOSIGNAL_KEYS_COLS default
|
||||||
|
# (98 = 100 minus margin) — the generator truncates actions to that budget so
|
||||||
|
# every line fits the panel.
|
||||||
|
set -u
|
||||||
|
|
||||||
|
DATA_DIR="$HOME/.local/share/nosignal"
|
||||||
|
LIST="$DATA_DIR/keybinds.list"
|
||||||
|
mkdir -p "$DATA_DIR"
|
||||||
|
|
||||||
|
# Find the generator next to this script (no PATH dependency), else fall back.
|
||||||
|
SELF_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
GEN="$SELF_DIR/nosignal-keybinds-gen"
|
||||||
|
[ -x "$GEN" ] || GEN="nosignal-keybinds-gen"
|
||||||
|
|
||||||
|
# Regenerate from the markdown when available; otherwise keep the cached list.
|
||||||
|
if "$GEN" > "$LIST.tmp" 2>/dev/null && [ -s "$LIST.tmp" ]; then
|
||||||
|
mv "$LIST.tmp" "$LIST"
|
||||||
|
else
|
||||||
|
rm -f "$LIST.tmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -s "$LIST" ]; then
|
||||||
|
command -v notify-send >/dev/null 2>&1 &&
|
||||||
|
notify-send -u critical "NoSignal keys" "Cheatsheet unavailable — keymap markdown not found."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
selection=$(fuzzel --dmenu \
|
||||||
|
--prompt "keys> " \
|
||||||
|
--width 100 \
|
||||||
|
--lines 20 \
|
||||||
|
--no-icons < "$LIST" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "${selection:-}" ] && command -v wl-copy >/dev/null 2>&1; then
|
||||||
|
printf '%s' "$selection" | wl-copy
|
||||||
|
fi
|
||||||
82
os updates/cheatsheet-tidy/nosignal-keybinds-gen
Executable file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# nosignal-keybinds-gen — parse the NoSignal keymap markdown and emit display-ready
|
||||||
|
# cheatsheet lines on stdout. The markdown is the SINGLE SOURCE OF TRUTH; the
|
||||||
|
# on-screen help (nosignal-keybinds) is always derived from it, so the two cannot
|
||||||
|
# drift. Reads every "| keys | action |" table row except the "Source map"
|
||||||
|
# section.
|
||||||
|
#
|
||||||
|
# Panel format: two columns only — key, action — sized so every
|
||||||
|
# line fits the fuzzel panel:
|
||||||
|
# - no section tag column (visual noise in the popup; the markdown keeps it)
|
||||||
|
# - doc-only parentheticals are stripped from actions: provenance notes like
|
||||||
|
# "(Omarchy key)", "(was hover-only)" belong in the doc,
|
||||||
|
# not the panel. Meaningful ones ("(logout / shutdown / reboot)") stay.
|
||||||
|
# - actions are ellipsis-truncated so no line exceeds $NOSIGNAL_KEYS_COLS
|
||||||
|
# characters (default 98 — the nosignal-keybinds fuzzel width of 100 minus
|
||||||
|
# margin). Keep the two in sync.
|
||||||
|
#
|
||||||
|
# Usage: nosignal-keybinds-gen [path-to-markdown]
|
||||||
|
# Source resolution order: $1 arg → $NOSIGNAL_KEYMAP_MD → known locations.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
MD="${1:-${NOSIGNAL_KEYMAP_MD:-}}"
|
||||||
|
if [ -z "$MD" ]; then
|
||||||
|
for cand in "$HOME/Downloads/NoSignal-keybindings.md" \
|
||||||
|
"$HOME/.local/share/nosignal/NoSignal-keybindings.md"; do
|
||||||
|
[ -f "$cand" ] && { MD="$cand"; break; }
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
[ -n "${MD:-}" ] && [ -f "$MD" ] || {
|
||||||
|
echo "nosignal-keybinds-gen: keymap markdown not found (set \$NOSIGNAL_KEYMAP_MD)" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
awk -v cols="${NOSIGNAL_KEYS_COLS:-98}" '
|
||||||
|
function trim(s){ gsub(/\*\*/, "", s); gsub(/^[ \t]+|[ \t]+$/, "", s); return s }
|
||||||
|
|
||||||
|
# ---- section headings -------------------------------------------------------
|
||||||
|
/^## / {
|
||||||
|
insrc = (substr($0, 4) ~ /^Source map/) # not keybinds: skip the section
|
||||||
|
next
|
||||||
|
}
|
||||||
|
insrc { next }
|
||||||
|
|
||||||
|
# ---- table rows -------------------------------------------------------------
|
||||||
|
/^\|/ {
|
||||||
|
if ($0 ~ /^[|: -]+$/) next # markdown separator row
|
||||||
|
n = split($0, c, "|") # c[1] is empty (text before first |)
|
||||||
|
keys = c[2]; gsub(/`/, "", keys); keys = trim(keys)
|
||||||
|
if (keys == "" || keys == "Keys" || keys == "File") next
|
||||||
|
desc = ""
|
||||||
|
for (i = 3; i < n; i++) { # join any remaining columns
|
||||||
|
cell = c[i]; gsub(/`/, "", cell); cell = trim(cell)
|
||||||
|
if (cell != "") desc = (desc == "") ? cell : desc " — " cell
|
||||||
|
}
|
||||||
|
|
||||||
|
# strip doc-only parentheticals (provenance/changelog), keep the rest
|
||||||
|
out = ""; rest = desc
|
||||||
|
while (match(rest, /\([^()]*\)/)) {
|
||||||
|
pre = substr(rest, 1, RSTART - 1)
|
||||||
|
par = substr(rest, RSTART, RLENGTH)
|
||||||
|
rest = substr(rest, RSTART + RLENGTH)
|
||||||
|
if (par ~ /Omarchy|change [0-9]+|^\(was |moved |NoSignal|dev\/debug/) par = ""
|
||||||
|
out = out pre par
|
||||||
|
}
|
||||||
|
desc = out rest
|
||||||
|
gsub(/ +/, " ", desc) # collapse gaps left by stripping
|
||||||
|
gsub(/[ \t]+$/, "", desc)
|
||||||
|
sub(/ — $/, "", desc)
|
||||||
|
|
||||||
|
KEY[++r] = keys; DSC[r] = desc
|
||||||
|
if (length(keys) > wk) wk = length(keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
budget = cols - wk - 2 # action space after the key column
|
||||||
|
for (i = 1; i <= r; i++) {
|
||||||
|
d = DSC[i]
|
||||||
|
if (length(d) > budget) d = substr(d, 1, budget - 1) "…"
|
||||||
|
printf "%-*s %s\n", wk, KEY[i], d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$MD"
|
||||||
57
os updates/cliamp-music/README.md
Executable file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# cliamp-music — CLIAmp as the default music player
|
||||||
|
|
||||||
|
Adds a default music player — NoSignal previously had none.
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
[CLIAmp](https://github.com/search?q=cliamp) — "a retro terminal music
|
||||||
|
player inspired by Winamp 2.x" — is what Omarchy ships for terminal
|
||||||
|
music (their `omarchy-launch-or-focus-tui cliamp` bind), and it is
|
||||||
|
prebuilt in the `[omarchy]` pacman repo NoSignal already carries
|
||||||
|
(v1.57.0 at time of writing; deps pull in ffmpeg and yt-dlp).
|
||||||
|
|
||||||
|
- **Package:** `cliamp` from `[omarchy]` (repo install, no AUR build).
|
||||||
|
- **Default player:** `cliamp.desktop` (launches in kitty, class
|
||||||
|
`cliamp`) + `xdg-mime default` for 12 common audio types, mirroring
|
||||||
|
the style of omarchy's `install/config/mimetypes.sh` (which sets no
|
||||||
|
audio defaults itself). Opening an audio file anywhere now starts
|
||||||
|
CLIAmp.
|
||||||
|
- **Keybind:** `Super+M` → launch-or-focus CLIAmp, floating 1100×700
|
||||||
|
(same shape as the other NoSignal TUI tools). Before this change both
|
||||||
|
`Super+M` and `Super+Shift+M` toggled the shell's music panel (an
|
||||||
|
MPRIS controller, not a player); the panel keeps `Super+Shift+M`.
|
||||||
|
Launch-or-focus is inlined (`hyprctl dispatch focuswindow … || kitty …`)
|
||||||
|
since NoSignal does not ship omarchy's helper — note `hyprctl` exits 0
|
||||||
|
even when no window matches, so the check greps for `ok`.
|
||||||
|
- **Keymap doc:** `Super+M` row updated (the Super+K cheatsheet
|
||||||
|
regenerates from it on next open).
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|------|------|
|
||||||
|
| `install-cliamp-music.sh` | idempotent installer — package (sudo), desktop entry, MIME defaults, marked bind block, keymap doc row |
|
||||||
|
| `cliamp.desktop` | desktop entry: `kitty --class cliamp -e cliamp %F` + audio MimeType list |
|
||||||
|
| `hyprland-cliamp.conf` | `# >>> cliamp music player >>>` marked block (float rules + Super+M), appended to hypr-user.conf |
|
||||||
|
|
||||||
|
## Packaging
|
||||||
|
|
||||||
|
- Add `cliamp` to the package list (from `[omarchy]`).
|
||||||
|
- Bake the desktop entry, the MIME defaults (skeleton
|
||||||
|
`~/.config/mimeapps.list`), the marked block (verbatim, markers
|
||||||
|
included — the installer greps for it), and the keymap doc row.
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
1. `Super+M` → CLIAmp opens floating; `Super+M` again focuses it
|
||||||
|
(no second instance). `Super+Shift+M` still toggles the music panel.
|
||||||
|
2. Open an `.mp3`/`.flac` from the file manager → opens in CLIAmp.
|
||||||
|
3. `xdg-mime query default audio/mpeg` → `cliamp.desktop`.
|
||||||
|
4. Super+K lists `Super+M Music player — CLIAmp…`.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
cliamp 1.57.0-1 installed; audio/mpeg and audio/flac
|
||||||
|
default to cliamp.desktop; runtime binds confirm Super+M →
|
||||||
|
launch-or-focus CLIAmp and Super+Shift+M → music panel; keymap doc row
|
||||||
|
present.
|
||||||
10
os updates/cliamp-music/cliamp.desktop
Executable file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=CLIAmp
|
||||||
|
GenericName=Music Player
|
||||||
|
Comment=Retro terminal music player inspired by Winamp 2.x
|
||||||
|
Exec=kitty --class cliamp -e cliamp %F
|
||||||
|
Icon=audio-x-generic
|
||||||
|
Terminal=false
|
||||||
|
Categories=AudioVideo;Audio;Player;
|
||||||
|
MimeType=audio/mpeg;audio/mp4;audio/x-m4a;audio/aac;audio/flac;audio/x-flac;audio/ogg;audio/x-vorbis+ogg;audio/x-opus+ogg;audio/wav;audio/x-wav;audio/webm;
|
||||||
9
os updates/cliamp-music/hyprland-cliamp.conf
Executable file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# >>> cliamp music player >>>
|
||||||
|
# CLIAmp (Winamp-style TUI, [omarchy] repo) is the default music player.
|
||||||
|
# Super+M launches/focuses it (was a second alias for the music panel toggle —
|
||||||
|
# the panel keeps Super+Shift+M). Floats like the other NoSignal TUI tools.
|
||||||
|
windowrule = float true, match:class cliamp
|
||||||
|
windowrule = size 1100 700, match:class cliamp
|
||||||
|
unbind = Super, M
|
||||||
|
bind = Super, M, exec, sh -c 'hyprctl dispatch focuswindow "class:^cliamp$" | grep -q ok || kitty --class cliamp -e cliamp'
|
||||||
|
# <<< cliamp music player <<<
|
||||||
61
os updates/cliamp-music/install-cliamp-music.sh
Executable file
|
|
@ -0,0 +1,61 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# install-cliamp-music.sh — CLIAmp as the default music player.
|
||||||
|
#
|
||||||
|
# - cliamp -> installed from the [omarchy] repo (sudo)
|
||||||
|
# - cliamp.desktop -> ~/.local/share/applications (launch in kitty)
|
||||||
|
# - XDG MIME defaults -> audio/* opens CLIAmp (omarchy mimetypes style)
|
||||||
|
# - Super+M -> launch-or-focus CLIAmp, floating
|
||||||
|
# (Super+Shift+M keeps the shell music panel)
|
||||||
|
# - keymap doc -> Super+M row updated
|
||||||
|
#
|
||||||
|
# Safe to re-run (idempotent). Needs sudo only for the package install.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SRC=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
APPS="$HOME/.local/share/applications"
|
||||||
|
HYPRUSER="$HOME/.config/caelestia/hypr-user.conf"
|
||||||
|
KEYMAP="$HOME/.local/share/nosignal/NoSignal-keybindings.md"
|
||||||
|
|
||||||
|
# 1. Package (prebuilt in the [omarchy] repo — no AUR build).
|
||||||
|
if ! command -v cliamp >/dev/null 2>&1; then
|
||||||
|
sudo pacman -S --needed --noconfirm cliamp
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Desktop entry (ours wins over any packaged one — same desktop id).
|
||||||
|
mkdir -p "$APPS"
|
||||||
|
install -m 0644 "$SRC/cliamp.desktop" "$APPS/cliamp.desktop"
|
||||||
|
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$APPS" || true
|
||||||
|
|
||||||
|
# 3. Default player for audio files (mirrors omarchy's mimetypes.sh style).
|
||||||
|
if command -v xdg-mime >/dev/null 2>&1; then
|
||||||
|
for m in audio/mpeg audio/mp4 audio/x-m4a audio/aac audio/flac audio/x-flac \
|
||||||
|
audio/ogg audio/x-vorbis+ogg audio/x-opus+ogg audio/wav audio/x-wav \
|
||||||
|
audio/webm; do
|
||||||
|
xdg-mime default cliamp.desktop "$m"
|
||||||
|
done
|
||||||
|
echo ":: audio/* now opens CLIAmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Super+M bind + float rule (marked block, end of hypr-user.conf).
|
||||||
|
if [ -f "$HYPRUSER" ]; then
|
||||||
|
if grep -q '>>> cliamp music player >>>' "$HYPRUSER"; then
|
||||||
|
echo ":: hypr-user.conf already has the cliamp block"
|
||||||
|
else
|
||||||
|
printf '\n' >> "$HYPRUSER"
|
||||||
|
cat "$SRC/hyprland-cliamp.conf" >> "$HYPRUSER"
|
||||||
|
echo ":: Super+M -> CLIAmp (block appended to hypr-user.conf)"
|
||||||
|
fi
|
||||||
|
command -v hyprctl >/dev/null 2>&1 && hyprctl reload >/dev/null 2>&1 || true
|
||||||
|
else
|
||||||
|
echo "NOTE: $HYPRUSER not found — append hyprland-cliamp.conf to your Hyprland user config."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. Keymap doc (single source of truth for the Super+K cheatsheet).
|
||||||
|
if [ -f "$KEYMAP" ] && ! grep -q 'CLIAmp' "$KEYMAP"; then
|
||||||
|
sed -i 's#^| `Super+M` | Music (alias) |#| `Super+M` | Music player — CLIAmp, floating (default for audio files) |#' "$KEYMAP"
|
||||||
|
grep -q 'CLIAmp' "$KEYMAP" \
|
||||||
|
&& echo ":: keymap doc updated (Super+M row)" \
|
||||||
|
|| echo "NOTE: keymap doc row not found — document Super+M manually in $KEYMAP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Done. Super+M opens CLIAmp; audio files open in it by default."
|
||||||
38
os updates/dashboard-key/README.md
Executable file
|
|
@ -0,0 +1,38 @@
|
||||||
|
# dashboard-key — Super+D opens the dashboard / calendar
|
||||||
|
|
||||||
|
caelestia's calendar lives in its **dashboard**, which by default opens only by
|
||||||
|
**hovering the top screen edge** (`Config.dashboard.showOnHover` +
|
||||||
|
`modules/drawers/Interactions.qml`). Easy to miss and fiddly. This adds a
|
||||||
|
keybind.
|
||||||
|
|
||||||
|
| Key | Action |
|
||||||
|
|-----|--------|
|
||||||
|
| `Super+D` | Toggle the dashboard (calendar + media/performance widgets) |
|
||||||
|
| `Super+Shift+D` | Communication special workspace (moved here from `Super+D`) |
|
||||||
|
|
||||||
|
`Super+D` was caelestia's `$kbCommunication` (communication scratchpad
|
||||||
|
workspace). To avoid losing it, it moves to `Super+Shift+D`. The dashboard is
|
||||||
|
toggled via the drawer IPC (`qs -c caelestia ipc call drawers toggle
|
||||||
|
dashboard`) — no shell edits, no package, works offline.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- binds appended to `~/.config/caelestia/hypr-user.conf` (idempotent marked block)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sh install-dashboard-key.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Builder notes — fold into omarchy-keys
|
||||||
|
|
||||||
|
Add to `omarchy-keys/omarchy-keys-user.conf`:
|
||||||
|
```
|
||||||
|
unbind = Super, D
|
||||||
|
bind = Super, D, exec, qs -c caelestia ipc call drawers toggle dashboard
|
||||||
|
bind = Super+Shift, D, exec, caelestia toggle communication
|
||||||
|
```
|
||||||
|
And document `Super+D` (dashboard) + `Super+Shift+D` (communication) in
|
||||||
|
`NoSignal-keybindings.md`. The bar's `calendar_month` icon stays decorative
|
||||||
|
(making it clickable needs a shell-level change, deliberately avoided).
|
||||||
37
os updates/dashboard-key/install-dashboard-key.sh
Executable file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# install-dashboard-key.sh — Super+D toggles the dashboard (calendar + widgets).
|
||||||
|
# Idempotent.
|
||||||
|
#
|
||||||
|
# The dashboard (which holds caelestia's calendar) was reachable only by
|
||||||
|
# hovering the top screen edge. This adds a keybind. Super+D was caelestia's
|
||||||
|
# `$kbCommunication` (communication special-workspace toggle), so that moves to
|
||||||
|
# Super+Shift+D — nothing is lost.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
HYPRUSER="$HOME/.config/caelestia/hypr-user.conf"
|
||||||
|
MARK='dashboard-key: Super+D dashboard'
|
||||||
|
|
||||||
|
if [ ! -f "$HYPRUSER" ]; then
|
||||||
|
echo "NOTE: $HYPRUSER not found — add the Super+D bind manually."
|
||||||
|
elif grep -qF "$MARK" "$HYPRUSER"; then
|
||||||
|
echo ":: Super+D already bound in $HYPRUSER"
|
||||||
|
else
|
||||||
|
cat >> "$HYPRUSER" <<'EOF'
|
||||||
|
|
||||||
|
# >>> dashboard-key: Super+D dashboard >>>
|
||||||
|
# Super+D toggles the dashboard (calendar + media/perf widgets), previously
|
||||||
|
# reachable only by hovering the top edge. Super+D was the communication
|
||||||
|
# special-workspace toggle ($kbCommunication) — moved to Super+Shift+D.
|
||||||
|
unbind = Super, D
|
||||||
|
bind = Super, D, exec, qs -c caelestia ipc call drawers toggle dashboard # Dashboard / calendar
|
||||||
|
bind = Super+Shift, D, exec, caelestia toggle communication # Communication workspace (moved from Super+D)
|
||||||
|
# <<< dashboard-key: Super+D dashboard <<<
|
||||||
|
EOF
|
||||||
|
echo ":: bound Super+D -> dashboard; communication -> Super+Shift+D"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v hyprctl >/dev/null 2>&1 && hyprctl version >/dev/null 2>&1; then
|
||||||
|
hyprctl reload >/dev/null 2>&1 && echo ":: reloaded Hyprland"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Done. Super+D opens the calendar/dashboard; Super+Shift+D = communication."
|
||||||
140
os updates/deckshift-login/README.md
Executable file
|
|
@ -0,0 +1,140 @@
|
||||||
|
# deckshift-login — full-session DeckShift gaming, password at boot
|
||||||
|
|
||||||
|
Keeps DeckShift's gaming model exactly as designed — a **dedicated, full
|
||||||
|
gamescope session** (`gamescope-session-steam-nm`), switched by rewriting
|
||||||
|
SDDM's autologin session and restarting SDDM — but fixes the login behaviour:
|
||||||
|
|
||||||
|
| Event | Stock DeckShift | With this component |
|
||||||
|
|-------|-----------------|---------------------|
|
||||||
|
| Cold boot | autologin, no password ever | **normal SDDM password login** |
|
||||||
|
| Desktop → Gaming (`Super+Shift+S`) | autologin into gaming session | same (one-shot autologin) |
|
||||||
|
| Gaming → Desktop (`Super+Shift+R` / Steam > Exit to Desktop) | autologin back to desktop | same (one-shot autologin) |
|
||||||
|
| SDDM crash / manual restart | autologin | password login |
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
Stock DeckShift keeps a **permanent** `[Autologin]` drop-in
|
||||||
|
(`/etc/sddm.conf.d/zz-gaming-session.conf`, `Relogin=true`) and its
|
||||||
|
`gaming-session-switch` helper just flips the `Session=` line — so the box
|
||||||
|
never shows a password screen again.
|
||||||
|
|
||||||
|
This component makes the autologin **one-shot**:
|
||||||
|
|
||||||
|
- `gaming-session-switch` (drop-in replacement, same path + interface, so
|
||||||
|
DeckShift's `switch-to-gaming` / `switch-to-desktop` / `os-session-select`
|
||||||
|
and the existing sudoers rule keep working) writes the autologin drop-in
|
||||||
|
fresh on each switch (`Relogin=false`) **and arms a marker in `/run`**.
|
||||||
|
- `sddm-autologin-gate` runs as `ExecStartPre=` on `sddm.service` (systemd
|
||||||
|
drop-in): marker present → consume it, keep the autologin for that single
|
||||||
|
restart; marker absent (cold boot, crash, manual restart — `/run` is tmpfs)
|
||||||
|
→ delete the autologin drop-in, password greeter shows.
|
||||||
|
|
||||||
|
Also adds the `Super+Shift+S → /usr/local/bin/switch-to-gaming` Hyprland bind:
|
||||||
|
DeckShift appends its bind to `~/.config/hypr/bindings.conf`, an **Omarchy**
|
||||||
|
path that doesn't exist on caelestia, so on this system DeckShift silently
|
||||||
|
skips the keybind — this component adds it to
|
||||||
|
`~/.config/caelestia/hypr-user.conf` instead (replacing caelestia's
|
||||||
|
`Super+Shift+S` screenshot-freeze; region shots stay on
|
||||||
|
Print / Shift+Print / Super+Shift+Alt+S). No exit bind is needed on the
|
||||||
|
desktop side: exiting happens inside the gaming session (DeckShift's evdev
|
||||||
|
keybind monitor handles `Super+Shift+R`, and Steam > Power > Exit to Desktop
|
||||||
|
calls `/usr/lib/os-session-select`).
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
```
|
||||||
|
gaming-session-switch -> /usr/local/bin/ (replaces DeckShift's; root 755)
|
||||||
|
switch-to-desktop -> /usr/local/bin/ (replaces DeckShift's; root 755)
|
||||||
|
sddm-autologin-gate -> /usr/local/bin/ (root 755)
|
||||||
|
deckshift-autologin-gate.conf-> /etc/systemd/system/sddm.service.d/
|
||||||
|
os-session-select -> /usr/lib/ (DeckShift's original, restored
|
||||||
|
if something overwrote it)
|
||||||
|
install-deckshift-login.sh idempotent installer (needs sudo)
|
||||||
|
```
|
||||||
|
|
||||||
|
**`deckshift.sh` itself is never modified** — this component only overlays
|
||||||
|
files that deckshift.sh has already installed (same paths, same interfaces,
|
||||||
|
the existing sudoers rules keep applying). That is the contract: the ISO
|
||||||
|
mimics Omarchy's SDDM setup, stock DeckShift installs onto it unchanged, and
|
||||||
|
this overlay is applied after it.
|
||||||
|
|
||||||
|
**Why `switch-to-desktop` is overlaid too**: DeckShift's original pkills gamescope *before* its final
|
||||||
|
`systemctl restart sddm`. Killing gamescope ends the login session, and
|
||||||
|
logind then kills the session's processes — including the script itself (it
|
||||||
|
runs inside the gaming session, launched by the keybind monitor) — so the
|
||||||
|
restart never executes. Stock DeckShift survives this because its permanent
|
||||||
|
`Relogin=true` autologin re-logs the user in anyway; with one-shot autologin
|
||||||
|
it stranded the user at the greeter. The overlay keeps all of DeckShift's
|
||||||
|
cleanup (power-profile restore, suspend unmask, Bluetooth, portal marker,
|
||||||
|
Steam shutdown) but queues the SDDM restart as a detached root job — the
|
||||||
|
same pattern DeckShift's own `os-session-select` uses, which is why Steam's
|
||||||
|
"Exit to Desktop" worked all along — and lets the service stop tear down
|
||||||
|
gamescope.
|
||||||
|
|
||||||
|
The installer also **removes the withdrawn nested gaming-mode experiment**
|
||||||
|
(`nosignal-gaming-mode*` scripts, its launcher entry, and its
|
||||||
|
`# >>> gaming-mode (nested) >>>` bind block) if the box ever had it.
|
||||||
|
|
||||||
|
## Install (opt-in, post-install — in this order)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# 1. DeckShift first (gaming session, switch scripts, sudoers, keybind monitor)
|
||||||
|
sh ~/deckshift/deckshift.sh
|
||||||
|
# 2. then the login fix
|
||||||
|
sh ~/.local/share/nosignal/deckshift-login/install-deckshift-login.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Re-run step 2 after any re-run of `deckshift.sh`** — deckshift.sh restores
|
||||||
|
its permanent-autologin `gaming-session-switch` and recreates the standing
|
||||||
|
autologin drop-in.
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
1. Reboot → SDDM **password** prompt (session: "Hyprland (uwsm-managed)").
|
||||||
|
2. `Super+Shift+S` → screen flickers (VT switch + SDDM restart) → Steam Big
|
||||||
|
Picture in a full gamescope session. No password asked.
|
||||||
|
3. Steam > Power > Exit to Desktop (or `Super+Shift+R`) → back to the Hyprland
|
||||||
|
desktop. No password asked.
|
||||||
|
4. Reboot again → password prompt is back (the switch autologin didn't stick).
|
||||||
|
5. `sudo systemctl restart sddm` from the desktop → password prompt (gate
|
||||||
|
cleans up; nothing autologins outside a real switch).
|
||||||
|
|
||||||
|
## Known quirks
|
||||||
|
|
||||||
|
- SDDM remembers the **last session** per user, so after a reboot from inside
|
||||||
|
Gaming Mode the greeter may preselect "Gaming Mode (ChimeraOS)" — pick
|
||||||
|
"Hyprland (uwsm-managed)" from the session menu.
|
||||||
|
- The switch necessarily restarts the whole graphical session: open desktop
|
||||||
|
apps close when entering Gaming Mode. That is inherent to DeckShift's
|
||||||
|
full-session design (and why it works better for games than a nested
|
||||||
|
window: gamescope owns the display, HDR/VRR/input pass through cleanly).
|
||||||
|
|
||||||
|
## Builder notes — how to put this in the ISO (opt-in, NOT pre-enabled)
|
||||||
|
|
||||||
|
1. **Bake the Omarchy-style SDDM setup — REQUIRED.** DeckShift's
|
||||||
|
switching is built on SDDM; the ISO must boot to an SDDM password greeter
|
||||||
|
with `hyprland-uwsm` as the default session and **no autologin baked**.
|
||||||
|
2. Copy `deckshift-login/` into the layer (lands at
|
||||||
|
`~/.local/share/nosignal/deckshift-login/`) + the copy-list line in
|
||||||
|
`nosignal-update/install-nosignal-update.sh`:
|
||||||
|
```sh
|
||||||
|
[ -d "$SRC/deckshift-login" ] && cp -a "$SRC/deckshift-login" "$DEST/"
|
||||||
|
```
|
||||||
|
3. Ship migration `1781395200-deckshift-login.sh` as-is — it is
|
||||||
|
**conditional**: it only (re-)applies where DeckShift is actually installed
|
||||||
|
(`/usr/local/bin/switch-to-gaming` exists), so `nosignal-update` keeps
|
||||||
|
opted-in boxes patched and is a no-op everywhere else.
|
||||||
|
4. Do **NOT** run deckshift.sh or this installer at build time, do **NOT**
|
||||||
|
bake the `# >>> deckshift gaming keys >>>` block into the shipped
|
||||||
|
`hypr-user.conf`, and do **NOT** bake steam/gamescope/DeckShift packages.
|
||||||
|
Gaming stays opt-in: the user runs `deckshift.sh` then this installer.
|
||||||
|
5. Prerequisites already in the base: `[multilib]` + the `omarchy-*` shims
|
||||||
|
(gaming-enablement) — deckshift.sh depends on both.
|
||||||
|
6. Drop the withdrawn `gaming-mode/` (nested) component entirely if any copy
|
||||||
|
is still in the builder tree; this component supersedes it.
|
||||||
|
7. Document in `NoSignal-keybindings.md`: `Super+Shift+S` = Gaming Mode
|
||||||
|
**only after opting in** (until then it stays caelestia's
|
||||||
|
screenshot-freeze); `Super+Shift+R` works inside Gaming Mode only.
|
||||||
|
8. Verify on a clean VM: install → password login; run deckshift.sh + this
|
||||||
|
installer → switch in/out of Gaming Mode with no password; reboot →
|
||||||
|
password login is back.
|
||||||
7
os updates/deckshift-login/deckshift-autologin-gate.conf
Executable file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# /etc/systemd/system/sddm.service.d/deckshift-autologin-gate.conf
|
||||||
|
# One-shot autologin gate for DeckShift session switching: keeps the SDDM
|
||||||
|
# autologin drop-in only when a desktop<->gaming switch armed it (marker in
|
||||||
|
# /run); every other SDDM start (cold boot included) shows the password
|
||||||
|
# greeter. See /usr/local/bin/sddm-autologin-gate.
|
||||||
|
[Service]
|
||||||
|
ExecStartPre=/usr/local/bin/sddm-autologin-gate
|
||||||
49
os updates/deckshift-login/gaming-session-switch
Executable file
|
|
@ -0,0 +1,49 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# gaming-session-switch — DeckShift-compatible session-switch helper, patched
|
||||||
|
# for password-at-boot. Same path + same {gaming|desktop} interface as
|
||||||
|
# DeckShift's original (the existing sudoers rule and switch-to-gaming /
|
||||||
|
# switch-to-desktop / os-session-select callers all keep working).
|
||||||
|
#
|
||||||
|
# DeckShift's original keeps a PERMANENT SDDM autologin drop-in and only flips
|
||||||
|
# its Session= line, so the machine never shows a password screen. This version
|
||||||
|
# writes the drop-in fresh on every switch and arms a one-shot marker in /run;
|
||||||
|
# sddm-autologin-gate (ExecStartPre on sddm.service) consumes the marker so the
|
||||||
|
# autologin applies to THAT restart only. Cold boots, crashes, and manual SDDM
|
||||||
|
# restarts find no marker and get the normal password greeter.
|
||||||
|
set -u
|
||||||
|
|
||||||
|
CONF="/etc/sddm.conf.d/zz-gaming-session.conf"
|
||||||
|
MARKER="/run/deckshift-session-switch"
|
||||||
|
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
echo "Error: must run as root (callers use: sudo -n $0 ...)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The desktop user comes from sudo (all DeckShift callers invoke via sudo -n).
|
||||||
|
USER_NAME="${SUDO_USER:-}"
|
||||||
|
if [[ -z "$USER_NAME" ]]; then
|
||||||
|
USER_NAME=$(loginctl list-sessions --no-legend 2>/dev/null | awk '$3 != "sddm" && $3 != "" { print $3; exit }')
|
||||||
|
fi
|
||||||
|
if [[ -z "$USER_NAME" ]]; then
|
||||||
|
echo "Error: cannot determine the user to autologin" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
gaming) SESSION="gamescope-session-steam-nm" ;;
|
||||||
|
desktop) SESSION="hyprland-uwsm" ;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {gaming|desktop}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cat > "$CONF" << EOF
|
||||||
|
[Autologin]
|
||||||
|
User=${USER_NAME}
|
||||||
|
Session=${SESSION}
|
||||||
|
Relogin=false
|
||||||
|
EOF
|
||||||
|
touch "$MARKER"
|
||||||
|
echo "Session set to: $1 mode (one-shot autologin armed)"
|
||||||
99
os updates/deckshift-login/install-deckshift-login.sh
Executable file
|
|
@ -0,0 +1,99 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# install-deckshift-login.sh — full-session DeckShift gaming with a password
|
||||||
|
# at boot. Keeps DeckShift's design (dedicated gamescope session, switched via
|
||||||
|
# SDDM restart) but makes the autologin ONE-SHOT: it only fires for the
|
||||||
|
# desktop<->gaming switch itself; every cold boot shows the password greeter.
|
||||||
|
#
|
||||||
|
# OPT-IN, POST-INSTALL component: the ISO ships this folder in the layer but
|
||||||
|
# does NOT run it. Order on the installed system:
|
||||||
|
# 1. run deckshift.sh (installs the gaming session, switch scripts, sudoers)
|
||||||
|
# 2. run THIS script (patches the login behaviour + adds the Hyprland bind)
|
||||||
|
# Re-run this script after any re-run of deckshift.sh (deckshift.sh restores
|
||||||
|
# its permanent-autologin versions of these files).
|
||||||
|
#
|
||||||
|
# Needs sudo for: /usr/local/bin, /usr/lib, /etc/systemd, /etc/sddm.conf.d.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SRC=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
HYPRUSER="$HOME/.config/caelestia/hypr-user.conf"
|
||||||
|
MARK='deckshift gaming keys'
|
||||||
|
|
||||||
|
# 0. DeckShift must already be installed.
|
||||||
|
if [ ! -x /usr/local/bin/switch-to-gaming ] || [ ! -f /usr/share/wayland-sessions/gamescope-session-steam-nm.desktop ]; then
|
||||||
|
echo "ERROR: DeckShift is not installed (run deckshift.sh first, then re-run this)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1. Replace the session-switch helper with the one-shot-autologin version.
|
||||||
|
sudo install -m 0755 "$SRC/gaming-session-switch" /usr/local/bin/gaming-session-switch
|
||||||
|
echo ":: patched gaming-session-switch (one-shot autologin per switch)"
|
||||||
|
|
||||||
|
# 2. Install the SDDM start gate (script + sddm.service drop-in).
|
||||||
|
sudo install -m 0755 "$SRC/sddm-autologin-gate" /usr/local/bin/sddm-autologin-gate
|
||||||
|
sudo install -d -m 0755 /etc/systemd/system/sddm.service.d
|
||||||
|
sudo install -m 0644 "$SRC/deckshift-autologin-gate.conf" /etc/systemd/system/sddm.service.d/deckshift-autologin-gate.conf
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
echo ":: installed sddm autologin gate (password greeter on every cold boot)"
|
||||||
|
|
||||||
|
# 3. Drop the standing autologin drop-in; from now on it only exists
|
||||||
|
# transiently between "switch requested" and "SDDM restarted".
|
||||||
|
if [ -f /etc/sddm.conf.d/zz-gaming-session.conf ]; then
|
||||||
|
sudo rm -f /etc/sddm.conf.d/zz-gaming-session.conf
|
||||||
|
echo ":: removed standing SDDM autologin drop-in"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Ensure /usr/lib/os-session-select is DeckShift's handler (restores it if
|
||||||
|
# the withdrawn nested-gaming experiment overwrote it).
|
||||||
|
if ! cmp -s "$SRC/os-session-select" /usr/lib/os-session-select 2>/dev/null; then
|
||||||
|
sudo install -m 0755 "$SRC/os-session-select" /usr/lib/os-session-select
|
||||||
|
echo ":: restored DeckShift os-session-select (Steam Exit to Desktop)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4b. Overlay switch-to-desktop: DeckShift's original pkills gamescope before
|
||||||
|
# its final `systemctl restart sddm`; the session teardown kills the
|
||||||
|
# script (it runs inside the gaming session) and the restart never fires —
|
||||||
|
# with one-shot autologin that strands the user at the greeter. Our
|
||||||
|
# version queues the restart as a detached root job instead (the proven
|
||||||
|
# os-session-select pattern), so Super+Shift+R returns to the desktop.
|
||||||
|
sudo install -m 0755 "$SRC/switch-to-desktop" /usr/local/bin/switch-to-desktop
|
||||||
|
echo ":: overlaid switch-to-desktop (queued SDDM restart, fixes Super+Shift+R exit)"
|
||||||
|
|
||||||
|
# 5. Remove the withdrawn nested-gaming-mode bits if this box ever had them.
|
||||||
|
rm -f "$HOME/.local/bin/nosignal-gaming-mode" "$HOME/.local/bin/nosignal-gaming-mode-exit" \
|
||||||
|
"$HOME/.local/share/applications/gaming-mode.desktop"
|
||||||
|
if [ -f "$HYPRUSER" ] && grep -qF 'gaming-mode (nested)' "$HYPRUSER"; then
|
||||||
|
sed -i '/# >>> gaming-mode (nested) >>>/,/# <<< gaming-mode (nested) <<</d' "$HYPRUSER"
|
||||||
|
echo ":: removed withdrawn nested gaming-mode binds"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. Hyprland bind: Super+Shift+S -> switch-to-gaming. The BASE config now ships a
|
||||||
|
# self-guarding Super+Shift+S bind (omarchy-keys-user.conf) that launches the
|
||||||
|
# gaming session only if DeckShift is installed and does nothing otherwise, so
|
||||||
|
# this installer no longer adds the bind itself. We only add a GUARDED bind as a
|
||||||
|
# fallback if no switch-to-gaming bind exists at all (e.g. an older base without
|
||||||
|
# it) — never the old unguarded form, which would break Super+Shift+S on a box
|
||||||
|
# where the gaming session is missing. Exit from Gaming Mode is handled INSIDE
|
||||||
|
# the session (DeckShift's Super+Shift+R monitor, or Steam > Power > Exit).
|
||||||
|
if [ ! -f "$HYPRUSER" ]; then
|
||||||
|
echo "NOTE: $HYPRUSER not found — base provides the guarded Super+Shift+S bind."
|
||||||
|
elif grep -qF 'switch-to-gaming' "$HYPRUSER"; then
|
||||||
|
echo ":: gaming keybind already present (base provides the guarded Super+Shift+S)"
|
||||||
|
else
|
||||||
|
cat >> "$HYPRUSER" << 'EOF'
|
||||||
|
|
||||||
|
# >>> deckshift gaming keys >>>
|
||||||
|
# Super+Shift+S = Gaming Mode IF DeckShift is installed; otherwise does nothing.
|
||||||
|
# Self-guards on the gaming session file + switch-to-gaming helper. Exit happens
|
||||||
|
# inside the session (Super+Shift+R, or Steam > Power > Exit to Desktop).
|
||||||
|
unbind = Super+Shift, S
|
||||||
|
bind = Super+Shift, S, exec, sh -c '[ -x /usr/local/bin/switch-to-gaming ] && [ -f /usr/share/wayland-sessions/gamescope-session-steam-nm.desktop ] && exec /usr/local/bin/switch-to-gaming'
|
||||||
|
# <<< deckshift gaming keys <<<
|
||||||
|
EOF
|
||||||
|
echo ":: added guarded Super+Shift+S -> switch-to-gaming (base bind was absent)"
|
||||||
|
fi
|
||||||
|
if command -v hyprctl >/dev/null 2>&1 && hyprctl version >/dev/null 2>&1; then
|
||||||
|
hyprctl reload >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Done. Cold boot = password login. Super+Shift+S = Gaming Mode (full session);"
|
||||||
|
echo "Super+Shift+R or Steam > Exit to Desktop returns without a login screen."
|
||||||
15
os updates/deckshift-login/os-session-select
Executable file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# /usr/lib/os-session-select — Steam's "Exit to Desktop" handler. This is
|
||||||
|
# DeckShift's original version, verbatim (shipped here so the installer can
|
||||||
|
# restore it if something else overwrote it). It flips the session back to
|
||||||
|
# desktop, shuts Steam down, and restarts SDDM; with the patched
|
||||||
|
# gaming-session-switch the autologin back to the desktop is one-shot.
|
||||||
|
rm -f /tmp/.gaming-session-active
|
||||||
|
sudo -n /usr/local/bin/gaming-session-switch desktop 2>/dev/null || {
|
||||||
|
echo "Warning: Failed to update session config"
|
||||||
|
}
|
||||||
|
timeout 5 steam -shutdown 2>/dev/null || true
|
||||||
|
sleep 1
|
||||||
|
nohup sudo -n systemctl restart sddm &>/dev/null &
|
||||||
|
disown
|
||||||
|
exit 0
|
||||||
20
os updates/deckshift-login/sddm-autologin-gate
Executable file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# sddm-autologin-gate — ExecStartPre for sddm.service (one-shot autologin).
|
||||||
|
#
|
||||||
|
# A DeckShift session switch arms autologin right before restarting SDDM:
|
||||||
|
# gaming-session-switch writes the autologin drop-in AND touches the marker.
|
||||||
|
# This gate runs every time SDDM starts:
|
||||||
|
# - marker present -> a switch is in flight: consume the marker, keep the
|
||||||
|
# drop-in, SDDM autologins into the target session once;
|
||||||
|
# - marker absent -> cold boot / crash / manual restart: delete any stale
|
||||||
|
# drop-in so the normal password greeter shows.
|
||||||
|
# /run is tmpfs, so the marker can never survive a reboot.
|
||||||
|
CONF="/etc/sddm.conf.d/zz-gaming-session.conf"
|
||||||
|
MARKER="/run/deckshift-session-switch"
|
||||||
|
|
||||||
|
if [[ -f "$MARKER" ]]; then
|
||||||
|
rm -f "$MARKER"
|
||||||
|
else
|
||||||
|
rm -f "$CONF"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
64
os updates/deckshift-login/switch-to-desktop
Executable file
|
|
@ -0,0 +1,64 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# switch-to-desktop — deckshift-login overlay of DeckShift's original.
|
||||||
|
#
|
||||||
|
# WHY THIS OVERLAY EXISTS: the original pkilled gamescope and slept BEFORE its
|
||||||
|
# final `systemctl restart sddm`. Killing gamescope ends the login session, and
|
||||||
|
# logind then kills the session's processes — including this very script (it
|
||||||
|
# is launched by the in-session keybind monitor) — so the restart never ran.
|
||||||
|
# Stock DeckShift got away with that because its PERMANENT Relogin=true
|
||||||
|
# autologin re-logged the user in anyway; with deckshift-login's one-shot
|
||||||
|
# autologin it left the SDDM greeter instead.
|
||||||
|
#
|
||||||
|
# Fix, copied from the os-session-select path that already works: do the
|
||||||
|
# cleanup, arm the one-shot switch, shut Steam down, then queue the SDDM
|
||||||
|
# restart as a detached root job (nohup + disown). The restart job runs inside
|
||||||
|
# PID 1, so it completes even though stopping SDDM kills this script and the
|
||||||
|
# rest of the gaming session — no manual pkill of gamescope needed.
|
||||||
|
if [[ ! -f /tmp/.gaming-session-active ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
rm -f /tmp/.gaming-session-active
|
||||||
|
|
||||||
|
# SYNCHRONOUS POWER RESTORE — done first because the trap-based restore in
|
||||||
|
# gamescope-session-nm-wrapper can be SIGKILL'd by `systemctl restart sddm`
|
||||||
|
# before it completes, leaving CPU governor / power profile stuck at
|
||||||
|
# "performance". (Verbatim from DeckShift's original.)
|
||||||
|
SAVED_STATE_FILE="$HOME/.cache/deckshift/saved-state"
|
||||||
|
if [[ -f "$SAVED_STATE_FILE" ]]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$SAVED_STATE_FILE"
|
||||||
|
for gov in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
|
||||||
|
echo "${PRE_GAMING_CPU_GOVERNOR:-powersave}" > "$gov" 2>/dev/null
|
||||||
|
done
|
||||||
|
if command -v powerprofilesctl &>/dev/null && [[ -n "${PRE_GAMING_POWER_PROFILE:-}" ]]; then
|
||||||
|
sudo -n powerprofilesctl set "$PRE_GAMING_POWER_PROFILE" 2>/dev/null || \
|
||||||
|
powerprofilesctl set "$PRE_GAMING_POWER_PROFILE" 2>/dev/null
|
||||||
|
fi
|
||||||
|
rm -f "$SAVED_STATE_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unmask suspend targets + daemon-reload (verbatim from DeckShift's original).
|
||||||
|
sudo -n systemctl unmask sleep.target suspend.target hibernate.target hybrid-sleep.target 2>/dev/null
|
||||||
|
sudo -n systemctl unmask --runtime sleep.target suspend.target hibernate.target hybrid-sleep.target 2>/dev/null
|
||||||
|
sudo -n systemctl daemon-reload 2>/dev/null
|
||||||
|
|
||||||
|
# Arm the one-shot autologin back to the desktop BEFORE anything can kill us.
|
||||||
|
sudo -n /usr/local/bin/gaming-session-switch desktop 2>/dev/null || true
|
||||||
|
|
||||||
|
# Re-enable Bluetooth (verbatim from DeckShift's original).
|
||||||
|
sudo -n /usr/bin/rfkill unblock bluetooth 2>/dev/null || true
|
||||||
|
sudo -n /usr/bin/systemctl start bluetooth.service 2>/dev/null || true
|
||||||
|
|
||||||
|
# Portal-recovery marker for the next Hyprland startup (from the original;
|
||||||
|
# moved earlier so it is written even if session teardown kills this script).
|
||||||
|
touch /tmp/.deckshift-just-returned 2>/dev/null || true
|
||||||
|
|
||||||
|
# Give Steam a clean shutdown, then hand over to the SDDM restart. Stopping
|
||||||
|
# sddm.service tears down the whole gaming session (gamescope included) — the
|
||||||
|
# same mechanism the working Steam "Exit to Desktop" path relies on.
|
||||||
|
timeout 5 steam -shutdown 2>/dev/null || true
|
||||||
|
sleep 1
|
||||||
|
sudo -n chvt 2 2>/dev/null || true
|
||||||
|
nohup sudo -n systemctl restart sddm &>/dev/null &
|
||||||
|
disown
|
||||||
|
exit 0
|
||||||
63
os updates/display-manager-sddm/README.md
Executable file
|
|
@ -0,0 +1,63 @@
|
||||||
|
# display-manager-sddm — switch greetd → SDDM (for DeckShift gaming mode)
|
||||||
|
|
||||||
|
DeckShift's desktop↔gaming-mode switching is built on **SDDM**: `switch-to-gaming`
|
||||||
|
rewrites SDDM's config to point at the Gamescope/Steam session and restarts SDDM
|
||||||
|
to auto-log into it (and back). NoSignal ships **greetd + tuigreet**, which has no
|
||||||
|
equivalent, so this change replaces the display manager with SDDM.
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
- Installs `sddm` (+ `xorg-server` for the reliable X11 greeter — the Hyprland
|
||||||
|
**session** stays Wayland; only SDDM's greeter uses X).
|
||||||
|
- Installs `/etc/sddm.conf.d/10-nosignal.conf` (X11 greeter, Numlock on, notes on
|
||||||
|
the preferred session).
|
||||||
|
- Disables `greetd.service`, enables `sddm.service`. **Takes effect on reboot.**
|
||||||
|
|
||||||
|
## Critical: preserve the uwsm session
|
||||||
|
|
||||||
|
greetd launched Hyprland via uwsm:
|
||||||
|
`uwsm start -e -D Hyprland hyprland.desktop`. The matching session entry is
|
||||||
|
**`/usr/share/wayland-sessions/hyprland-uwsm.desktop`** ("Hyprland
|
||||||
|
(uwsm-managed)"). Under SDDM, pick **that** session, not plain
|
||||||
|
`hyprland.desktop` (which runs `start-hyprland` directly and loses the
|
||||||
|
uwsm/systemd-managed session caelestia relies on). SDDM remembers the last
|
||||||
|
session per user.
|
||||||
|
|
||||||
|
## Install (live)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sh install-display-manager-sddm.sh # sudo; reboot afterwards
|
||||||
|
```
|
||||||
|
|
||||||
|
To revert: `sudo systemctl disable sddm; sudo systemctl enable greetd`.
|
||||||
|
|
||||||
|
## Builder notes — display-manager swap (package + service delta)
|
||||||
|
|
||||||
|
This is **not** a no-delta change. For the ISO:
|
||||||
|
|
||||||
|
- **Packages:** add `sddm` (+ `xorg-server` if using the X11 greeter, OR `weston`
|
||||||
|
if using the Wayland greeter — pick one). Remove `greetd`, `greetd-tuigreet`,
|
||||||
|
`greetd-agreety` from the image package list (or keep as fallback).
|
||||||
|
- **Services:** enable `sddm.service` instead of `greetd.service` in the image.
|
||||||
|
- **Greeter display server (decision):**
|
||||||
|
- *X11 greeter* (this component's default): most reliable, but adds
|
||||||
|
`xorg-server` to an otherwise Wayland-only image.
|
||||||
|
- *Wayland greeter*: keeps the image X-free — set `DisplayServer=wayland` +
|
||||||
|
`[Wayland] CompositorCommand=weston --shell=kiosk` and add `weston`.
|
||||||
|
- **Session default:** ensure `hyprland-uwsm.desktop` is the preferred desktop
|
||||||
|
session (uwsm launch — matches the old greetd `--cmd`). Don't let plain
|
||||||
|
`hyprland.desktop` win.
|
||||||
|
- **Login behaviour:** greetd here did **not** autologin (tuigreet prompts).
|
||||||
|
SDDM keeps a password prompt unless `[Autologin]` is set. DeckShift configures
|
||||||
|
its own autologin/session entries on top — let it.
|
||||||
|
- **DeckShift autologin (interaction to know about):** DeckShift writes
|
||||||
|
`/etc/sddm.conf.d/zz-gaming-session.conf` with `[Autologin] User=… /
|
||||||
|
Session=hyprland-uwsm / Relogin=true`. Once DeckShift runs, this **overrides
|
||||||
|
the password prompt** — the box boots straight into the desktop, and the
|
||||||
|
desktop↔gaming switch flips `Session=` then restarts SDDM (autologin, no
|
||||||
|
greeter). That's the intended Steam-Deck-style behaviour. If the ISO wants a
|
||||||
|
password on normal boots, ship a higher-priority SDDM drop-in or adjust
|
||||||
|
DeckShift's `gaming-session-switch` to only autologin while in gaming mode.
|
||||||
|
- Pairs with gaming-enablement (multilib + omarchy-pkg-add). Together
|
||||||
|
these two make NoSignal DeckShift/Steam-Deck capable.
|
||||||
|
- The base's plymouth/limine boot splash is unaffected.
|
||||||
32
os updates/display-manager-sddm/install-display-manager-sddm.sh
Executable file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# install-display-manager-sddm.sh — switch the display manager from greetd to
|
||||||
|
# SDDM, so DeckShift's SDDM-based desktop<->gaming session switching works.
|
||||||
|
# Idempotent. Needs sudo. Takes effect on next reboot.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SRC=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
|
||||||
|
# 1. Install SDDM (+ xorg-server for the reliable X11 greeter; the Hyprland
|
||||||
|
# session itself stays Wayland). For an X-free image use the Wayland greeter
|
||||||
|
# instead (see sddm-10-nosignal.conf) and drop xorg-server here.
|
||||||
|
echo ":: installing sddm + xorg-server (sudo)..."
|
||||||
|
sudo pacman -S --needed --noconfirm sddm xorg-server
|
||||||
|
|
||||||
|
# 2. Config drop-in (default desktop session = the uwsm-managed Hyprland).
|
||||||
|
sudo install -D -m 0644 "$SRC/sddm-10-nosignal.conf" /etc/sddm.conf.d/10-nosignal.conf
|
||||||
|
echo ":: installed /etc/sddm.conf.d/10-nosignal.conf"
|
||||||
|
|
||||||
|
# 3. Swap the active display-manager service: greetd -> sddm.
|
||||||
|
if systemctl is-enabled greetd.service >/dev/null 2>&1; then
|
||||||
|
sudo systemctl disable greetd.service
|
||||||
|
echo ":: disabled greetd.service"
|
||||||
|
fi
|
||||||
|
sudo systemctl enable sddm.service
|
||||||
|
echo ":: enabled sddm.service (display-manager.service -> sddm)"
|
||||||
|
|
||||||
|
cat <<'EOF'
|
||||||
|
Done. Reboot to log in via SDDM.
|
||||||
|
- At the SDDM session picker choose "Hyprland (uwsm-managed)" once; SDDM remembers it.
|
||||||
|
- DeckShift's switch-to-gaming / switch-to-desktop then drive SDDM to flip
|
||||||
|
between your desktop and the Gamescope/Steam session.
|
||||||
|
EOF
|
||||||
17
os updates/display-manager-sddm/sddm-10-nosignal.conf
Executable file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# NoSignal SDDM config (installed to /etc/sddm.conf.d/10-nosignal.conf).
|
||||||
|
[General]
|
||||||
|
# X11 greeter — the reliable, well-trodden path (needs xorg-server). The Hyprland
|
||||||
|
# SESSION still runs on Wayland; only SDDM's own greeter uses X here.
|
||||||
|
# For an X-free image instead, use the Wayland greeter:
|
||||||
|
# DisplayServer=wayland
|
||||||
|
# [Wayland]
|
||||||
|
# CompositorCommand=weston --shell=kiosk (adds `weston` to the image)
|
||||||
|
DisplayServer=x11
|
||||||
|
Numlock=on
|
||||||
|
|
||||||
|
[Users]
|
||||||
|
# The desktop session to prefer. hyprland-uwsm.desktop launches Hyprland through
|
||||||
|
# uwsm exactly like the old greetd config — keep this (NOT plain hyprland.desktop),
|
||||||
|
# or caelestia's uwsm/systemd-managed session env won't apply.
|
||||||
|
# (SDDM remembers the last-used session per user; DeckShift overrides this when
|
||||||
|
# switching to/from its Gamescope gaming session.)
|
||||||
61
os updates/fish-to-bash/README.md
Executable file
|
|
@ -0,0 +1,61 @@
|
||||||
|
# NoSignal — fish → bash migration (Omarchy bash setup)
|
||||||
|
|
||||||
|
Switches this Caelestia/NoSignal box from the fish shell to **bash, configured to
|
||||||
|
match [Omarchy](https://github.com/basecamp/omarchy)**. Self-contained so it
|
||||||
|
survives a wipe — copy this folder back with the rest of Downloads and run the
|
||||||
|
installer.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sh ~/Downloads/fish-to-bash/install-fish-to-bash.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Idempotent. Open a **new** terminal afterwards to land in bash.
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
| Step | Effect |
|
||||||
|
|------|--------|
|
||||||
|
| Vendor `bash/` tree | Copied verbatim from Omarchy to `~/.local/share/omarchy/default/bash/` (`rc`, `shell`, `aliases`, `envs`, `init`, `functions`, `inputrc`, `completions`, `fns/*`) |
|
||||||
|
| `~/.bashrc` | Omarchy's template; sources the tree above. Existing `~/.bashrc` backed up to `~/.bashrc.pre-omarchy` |
|
||||||
|
| kitty | `~/.config/kitty/kitty.conf`: `shell fish` → `shell bash` |
|
||||||
|
| foot | `~/.config/foot/foot.ini`: `shell=fish` → `shell=bash` |
|
||||||
|
| Hyprland helpers | `wsaction.fish` & `configs.fish` replaced with bash ports (same filenames, bash shebang) so the stock keybinds keep working |
|
||||||
|
|
||||||
|
## Why the layout matches Omarchy exactly
|
||||||
|
|
||||||
|
The vendored files are byte-for-byte Omarchy and live at Omarchy's own path
|
||||||
|
(`~/.local/share/omarchy/...`). That means: (a) the prompt/aliases/history/
|
||||||
|
completion behaviour is identical to Omarchy, and (b) you can refresh them from
|
||||||
|
upstream by re-copying `default/bash/` with no edits. `envs` sets
|
||||||
|
`OMARCHY_PATH` and prepends `~/.local/bin` to `PATH` (so the keybind-help
|
||||||
|
scripts resolve too). References to omarchy-only tools (`omarchy`, `mise`,
|
||||||
|
`opencode`, `tdl`, `gum`, …) are all guarded with `command -v` or are function
|
||||||
|
definitions, so missing tools are harmless until you actually call them.
|
||||||
|
|
||||||
|
## NoSignal-specific additions (the only deviations from stock Omarchy)
|
||||||
|
|
||||||
|
- `~/.bashrc` exports `EDITOR=nvim` if unset (Omarchy sets this in `uwsm/env`,
|
||||||
|
which we don't manage here).
|
||||||
|
- `~/.bashrc` runs `cat ~/.local/state/caelestia/sequences.txt` so the terminal
|
||||||
|
picks up Caelestia's dynamic colour scheme.
|
||||||
|
|
||||||
|
## Notes / things that changed from the old fish config
|
||||||
|
|
||||||
|
- **`ga` / `gd` now mean Omarchy's git-worktree helpers**, not the old fish
|
||||||
|
abbreviations (`git add .` / `git diff`). Other git shortcuts: `g`, `gcm`,
|
||||||
|
`gcam`, `gcad`.
|
||||||
|
- **direnv** was hooked in the old fish config; Omarchy's bash doesn't include
|
||||||
|
it. Add `eval "$(direnv hook bash)"` to the bottom of `~/.bashrc` if you want
|
||||||
|
it back.
|
||||||
|
- The **fish package is left installed** (it may be pulled in as a Caelestia
|
||||||
|
dependency). Nothing interactive uses it anymore. Remove it only after
|
||||||
|
confirming nothing else needs it: `sudo pacman -Rns fish`.
|
||||||
|
- The fish config dir (`~/.config/fish`, a Caelestia symlink) is left in place
|
||||||
|
but unused.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
`bash`, and ideally `starship`, `zoxide`, `eza`, `fzf`, `bat` for the full
|
||||||
|
Omarchy experience (all guarded — bash works without them).
|
||||||
60
os updates/fish-to-bash/bash/aliases
Executable file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# File system
|
||||||
|
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
|
||||||
|
|
||||||
|
if [[ "$TERM" == "xterm-kitty" ]]; then
|
||||||
|
alias ff="fzf --preview 'case \$(file --mime-type -b {}) in image/*) kitty icat --clear --transfer-mode=memory --stdin=no --place=\${FZF_PREVIEW_COLUMNS}x\${FZF_PREVIEW_LINES}@0x0 {} ;; *) bat --style=numbers --color=always {} ;; esac'"
|
||||||
|
else
|
||||||
|
alias ff="fzf --preview 'bat --style=numbers --color=always {}'"
|
||||||
|
fi
|
||||||
|
alias eff='$EDITOR "$(ff)"'
|
||||||
|
sff() { if [ $# -eq 0 ]; then echo "Usage: sff <destination> (e.g. sff host:/tmp/)"; return 1; fi; local file; file=$(find . -type f -printf '%T@\t%p\n' | sort -rn | cut -f2- | ff) && [ -n "$file" ] && scp "$file" "$1"; }
|
||||||
|
|
||||||
|
if command -v zoxide &> /dev/null; then
|
||||||
|
alias cd="zd"
|
||||||
|
zd() {
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
builtin cd ~ || return
|
||||||
|
elif [[ -d $1 ]]; then
|
||||||
|
builtin cd "$1" || return
|
||||||
|
else
|
||||||
|
if ! z "$@"; then
|
||||||
|
echo "Error: Directory not found"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "\U000F17A9 "
|
||||||
|
pwd
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
open() (
|
||||||
|
xdg-open "$@" >/dev/null 2>&1 &
|
||||||
|
)
|
||||||
|
|
||||||
|
# Directories
|
||||||
|
alias ..='cd ..'
|
||||||
|
alias ...='cd ../..'
|
||||||
|
alias ....='cd ../../..'
|
||||||
|
|
||||||
|
# Tools
|
||||||
|
alias c='opencode'
|
||||||
|
alias cx='printf "\033[2J\033[3J\033[H" && claude --permission-mode bypassPermissions'
|
||||||
|
alias d='docker'
|
||||||
|
alias r='rails'
|
||||||
|
alias t='tmux attach || tmux new -s Work'
|
||||||
|
alias ic='tdl c'
|
||||||
|
alias ix='tdl cx'
|
||||||
|
alias icx='tdl c cx'
|
||||||
|
n() { if [ "$#" -eq 0 ]; then command nvim . ; else command nvim "$@"; fi; }
|
||||||
|
|
||||||
|
# Git
|
||||||
|
alias g='git'
|
||||||
|
alias gcm='git commit -m'
|
||||||
|
alias gcam='git commit -a -m'
|
||||||
|
alias gcad='git commit -a --amend'
|
||||||
64
os updates/fish-to-bash/bash/completions
Executable file
|
|
@ -0,0 +1,64 @@
|
||||||
|
_omarchy_complete() {
|
||||||
|
COMPREPLY=()
|
||||||
|
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
|
||||||
|
local omarchy_path bin_dir
|
||||||
|
omarchy_path=$(command -v omarchy 2>/dev/null) || return 0
|
||||||
|
bin_dir=$(dirname -- "$(readlink -f -- "$omarchy_path" 2>/dev/null || printf '%s' "$omarchy_path")")
|
||||||
|
[[ -d $bin_dir ]] || return 0
|
||||||
|
|
||||||
|
local prefix="omarchy"
|
||||||
|
local i part
|
||||||
|
for ((i = 1; i < COMP_CWORD; i++)); do
|
||||||
|
part="${COMP_WORDS[i]}"
|
||||||
|
[[ -z $part || $part == -* ]] && continue
|
||||||
|
prefix+="-$part"
|
||||||
|
done
|
||||||
|
|
||||||
|
local -A seen=()
|
||||||
|
local candidates=()
|
||||||
|
local file basename rest next
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
for file in "$bin_dir/$prefix"-*; do
|
||||||
|
[[ -f $file && -x $file ]] || continue
|
||||||
|
basename="${file##*/}"
|
||||||
|
rest="${basename#"$prefix"-}"
|
||||||
|
next="${rest%%-*}"
|
||||||
|
if [[ -n $next && -z ${seen[$next]:-} ]]; then
|
||||||
|
seen[$next]=1
|
||||||
|
candidates+=("$next")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
|
||||||
|
if (( COMP_CWORD == 1 )); then
|
||||||
|
candidates+=("commands")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${COMP_WORDS[1]:-} == "commands" ]] && (( COMP_CWORD >= 2 )); then
|
||||||
|
candidates+=("--all" "--json" "--markdown" "--check")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( ${#candidates[@]} == 0 )) && [[ -x $bin_dir/$prefix ]]; then
|
||||||
|
local args enum
|
||||||
|
args=$(grep -m 1 '^# omarchy:args=<' "$bin_dir/$prefix" 2>/dev/null)
|
||||||
|
enum="${args#*<}"
|
||||||
|
enum="${enum%%>*}"
|
||||||
|
|
||||||
|
if [[ $enum == *"|"* && $enum != *" "* ]]; then
|
||||||
|
read -r -a candidates <<<"${enum//|/ }"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( ${#candidates[@]} > 0 )); then
|
||||||
|
local IFS=$'\n'
|
||||||
|
COMPREPLY=($(compgen -W "${candidates[*]}" -- "$cur"))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -o default -F _omarchy_complete omarchy
|
||||||
|
|
||||||
|
# Hide individual omarchy-* binaries from initial-word command completion;
|
||||||
|
# the unified `omarchy` dispatcher is the user-facing entry point.
|
||||||
|
complete -I -A command -X 'omarchy-*'
|
||||||
11
os updates/fish-to-bash/bash/envs
Executable file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Editor used by CLI
|
||||||
|
export SUDO_EDITOR="$EDITOR"
|
||||||
|
export BAT_THEME=ansi
|
||||||
|
|
||||||
|
# Color man pages with bat
|
||||||
|
export MANROFFOPT="-c"
|
||||||
|
export MANPAGER="sh -c 'col -bx | bat -l man -p'"
|
||||||
|
|
||||||
|
# Duplicated from .config/uwsm/env so SSH works too
|
||||||
|
export OMARCHY_PATH=$HOME/.local/share/omarchy
|
||||||
|
export PATH=$OMARCHY_PATH/bin:$PATH:$HOME/.local/bin
|
||||||
3
os updates/fish-to-bash/bash/fns/compression
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Compression
|
||||||
|
compress() { tar -czf "${1%/}.tar.gz" "${1%/}"; }
|
||||||
|
alias decompress="tar -xzf"
|
||||||
59
os updates/fish-to-bash/bash/fns/drives
Executable file
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Write iso file to sd card
|
||||||
|
iso2sd() {
|
||||||
|
if (( $# < 1 )); then
|
||||||
|
echo "Usage: iso2sd <input_file> [output_device]"
|
||||||
|
echo "Example: iso2sd ~/Downloads/ubuntu-25.04-desktop-amd64.iso /dev/sda"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local iso="$1"
|
||||||
|
local drive="$2"
|
||||||
|
|
||||||
|
if [[ -z $drive ]]; then
|
||||||
|
local available_sds=$(lsblk -dpno NAME | grep -E '/dev/sd')
|
||||||
|
|
||||||
|
if [[ -z $available_sds ]]; then
|
||||||
|
echo "No SD drives found and no drive specified"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
drive=$(omarchy-drive-select "$available_sds")
|
||||||
|
|
||||||
|
if [[ -z $drive ]]; then
|
||||||
|
echo "No drive selected"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo dd bs=4M status=progress oflag=sync if="$iso" of="$drive"
|
||||||
|
sudo eject "$drive"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Format an entire drive for a single partition using exFAT
|
||||||
|
format-drive() {
|
||||||
|
if (( $# != 2 )); then
|
||||||
|
echo "Usage: format-drive <device> <name>"
|
||||||
|
echo "Example: format-drive /dev/sda 'My Stuff'"
|
||||||
|
echo -e "\nAvailable drives:"
|
||||||
|
lsblk -d -o NAME -n | awk '{print "/dev/"$1}'
|
||||||
|
else
|
||||||
|
echo "WARNING: This will completely erase all data on $1 and label it '$2'."
|
||||||
|
read -rp "Are you sure you want to continue? (y/N): " confirm
|
||||||
|
|
||||||
|
if [[ $confirm =~ ^[Yy]$ ]]; then
|
||||||
|
sudo wipefs -a "$1"
|
||||||
|
sudo dd if=/dev/zero of="$1" bs=1M count=100 status=progress
|
||||||
|
sudo parted -s "$1" mklabel gpt
|
||||||
|
sudo parted -s "$1" mkpart primary 1MiB 100%
|
||||||
|
sudo parted -s "$1" set 1 msftdata on
|
||||||
|
|
||||||
|
partition="$([[ $1 == *"nvme"* ]] && echo "${1}p1" || echo "${1}1")"
|
||||||
|
sudo partprobe "$1" || true
|
||||||
|
sudo udevadm settle || true
|
||||||
|
|
||||||
|
sudo mkfs.exfat -n "$2" "$partition"
|
||||||
|
|
||||||
|
echo "Drive $1 formatted as exFAT and labeled '$2'."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
19
os updates/fish-to-bash/bash/fns/ssh-port-forwarding
Executable file
|
|
@ -0,0 +1,19 @@
|
||||||
|
fip() {
|
||||||
|
(( $# < 2 )) && echo "Usage: fip <host> <port1> [port2] ..." && return 1
|
||||||
|
local host="$1"
|
||||||
|
shift
|
||||||
|
for port in "$@"; do
|
||||||
|
ssh -f -N -L "$port:localhost:$port" "$host" && echo "Forwarding localhost:$port -> $host:$port"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
dip() {
|
||||||
|
(( $# == 0 )) && echo "Usage: dip <port1> [port2] ..." && return 1
|
||||||
|
for port in "$@"; do
|
||||||
|
pkill -f "ssh.*-L $port:localhost:$port" && echo "Stopped forwarding port $port" || echo "No forwarding on port $port"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
lip() {
|
||||||
|
pgrep -af "ssh.*-L [0-9]+:localhost:[0-9]+" || echo "No active forwards"
|
||||||
|
}
|
||||||
97
os updates/fish-to-bash/bash/fns/tmux
Executable file
|
|
@ -0,0 +1,97 @@
|
||||||
|
# Create a Tmux Dev Layout with editor, ai, and terminal
|
||||||
|
# Usage: tdl <c|cx|codex|other_ai> [<second_ai>]
|
||||||
|
tdl() {
|
||||||
|
[[ -z $1 ]] && { echo "Usage: tdl <c|cx|codex|other_ai> [<second_ai>]"; return 1; }
|
||||||
|
[[ -z $TMUX ]] && { echo "You must start tmux to use tdl."; return 1; }
|
||||||
|
|
||||||
|
local current_dir="${PWD}"
|
||||||
|
local editor_pane ai_pane ai2_pane
|
||||||
|
local ai="$1"
|
||||||
|
local ai2="$2"
|
||||||
|
|
||||||
|
# Use TMUX_PANE for the pane we're running in (stable even if active window changes)
|
||||||
|
editor_pane="$TMUX_PANE"
|
||||||
|
|
||||||
|
# Name the current window after the base directory name
|
||||||
|
tmux rename-window -t "$editor_pane" "$(basename "$current_dir")"
|
||||||
|
|
||||||
|
# Split window vertically - top 85%, bottom 15% (target editor pane explicitly)
|
||||||
|
tmux split-window -v -p 15 -t "$editor_pane" -c "$current_dir"
|
||||||
|
|
||||||
|
# Split editor pane horizontally - AI on right 30% (capture new pane ID directly)
|
||||||
|
ai_pane=$(tmux split-window -h -p 30 -t "$editor_pane" -c "$current_dir" -P -F '#{pane_id}')
|
||||||
|
|
||||||
|
# If second AI provided, split the AI pane vertically
|
||||||
|
if [[ -n $ai2 ]]; then
|
||||||
|
ai2_pane=$(tmux split-window -v -t "$ai_pane" -c "$current_dir" -P -F '#{pane_id}')
|
||||||
|
tmux send-keys -t "$ai2_pane" "$ai2" C-m
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run ai in the right pane
|
||||||
|
tmux send-keys -t "$ai_pane" "$ai" C-m
|
||||||
|
|
||||||
|
# Run nvim in the left pane
|
||||||
|
tmux send-keys -t "$editor_pane" "$EDITOR ." C-m
|
||||||
|
|
||||||
|
# Select the nvim pane for focus
|
||||||
|
tmux select-pane -t "$editor_pane"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create multiple tdl windows with one per subdirectory in the current directory
|
||||||
|
# Usage: tdlm <c|cx|codex|other_ai> [<second_ai>]
|
||||||
|
tdlm() {
|
||||||
|
[[ -z $1 ]] && { echo "Usage: tdlm <c|cx|codex|other_ai> [<second_ai>]"; return 1; }
|
||||||
|
[[ -z $TMUX ]] && { echo "You must start tmux to use tdlm."; return 1; }
|
||||||
|
|
||||||
|
local ai="$1"
|
||||||
|
local ai2="$2"
|
||||||
|
local base_dir="$PWD"
|
||||||
|
local first=true
|
||||||
|
|
||||||
|
# Rename the session to the current directory name (replace dots/colons which tmux disallows)
|
||||||
|
tmux rename-session "$(basename "$base_dir" | tr '.:' '--')"
|
||||||
|
|
||||||
|
for dir in "$base_dir"/*/; do
|
||||||
|
[[ -d $dir ]] || continue
|
||||||
|
local dirpath="${dir%/}"
|
||||||
|
|
||||||
|
if $first; then
|
||||||
|
# Reuse the current window for the first project
|
||||||
|
tmux send-keys -t "$TMUX_PANE" "cd '$dirpath' && tdl $ai $ai2" C-m
|
||||||
|
first=false
|
||||||
|
else
|
||||||
|
local pane_id=$(tmux new-window -c "$dirpath" -P -F '#{pane_id}')
|
||||||
|
tmux send-keys -t "$pane_id" "tdl $ai $ai2" C-m
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a multi-pane swarm layout with the same command started in each pane (great for AI)
|
||||||
|
# Usage: tsl <pane_count> <command>
|
||||||
|
tsl() {
|
||||||
|
[[ -z $1 || -z $2 ]] && { echo "Usage: tsl <pane_count> <command>"; return 1; }
|
||||||
|
[[ -z $TMUX ]] && { echo "You must start tmux to use tsl."; return 1; }
|
||||||
|
|
||||||
|
local count="$1"
|
||||||
|
local cmd="$2"
|
||||||
|
local current_dir="${PWD}"
|
||||||
|
local -a panes
|
||||||
|
|
||||||
|
tmux rename-window -t "$TMUX_PANE" "$(basename "$current_dir")"
|
||||||
|
|
||||||
|
panes+=("$TMUX_PANE")
|
||||||
|
|
||||||
|
while (( ${#panes[@]} < count )); do
|
||||||
|
local new_pane
|
||||||
|
local split_target="${panes[-1]}"
|
||||||
|
new_pane=$(tmux split-window -h -t "$split_target" -c "$current_dir" -P -F '#{pane_id}')
|
||||||
|
panes+=("$new_pane")
|
||||||
|
tmux select-layout -t "${panes[0]}" tiled
|
||||||
|
done
|
||||||
|
|
||||||
|
for pane in "${panes[@]}"; do
|
||||||
|
tmux send-keys -t "$pane" "$cmd" C-m
|
||||||
|
done
|
||||||
|
|
||||||
|
tmux select-pane -t "${panes[0]}"
|
||||||
|
}
|
||||||
33
os updates/fish-to-bash/bash/fns/transcoding
Executable file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Transcoding helpers have moved to omarchy-transcode.
|
||||||
|
|
||||||
|
transcode-video-1080p() {
|
||||||
|
omarchy-transcode "$1" mp4 1080p
|
||||||
|
}
|
||||||
|
|
||||||
|
transcode-video-4K() {
|
||||||
|
omarchy-transcode "$1" mp4 4k
|
||||||
|
}
|
||||||
|
|
||||||
|
transcode-video-gif() {
|
||||||
|
omarchy-transcode "$1" gif 1080p
|
||||||
|
}
|
||||||
|
|
||||||
|
img2jpg() {
|
||||||
|
omarchy-transcode "$1" jpg high
|
||||||
|
}
|
||||||
|
|
||||||
|
img2jpg-small() {
|
||||||
|
omarchy-transcode "$1" jpg low
|
||||||
|
}
|
||||||
|
|
||||||
|
img2jpg-medium() {
|
||||||
|
omarchy-transcode "$1" jpg medium
|
||||||
|
}
|
||||||
|
|
||||||
|
img2jpg-large() {
|
||||||
|
omarchy-transcode "$1" jpg high
|
||||||
|
}
|
||||||
|
|
||||||
|
img2png() {
|
||||||
|
omarchy-transcode "$1" png high
|
||||||
|
}
|
||||||
36
os updates/fish-to-bash/bash/fns/worktrees
Executable file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Create a new worktree and branch from within current git directory.
|
||||||
|
ga() {
|
||||||
|
if [[ -z "$1" ]]; then
|
||||||
|
echo "Usage: ga [branch name]"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local branch="$1"
|
||||||
|
local base="$(basename "$PWD")"
|
||||||
|
local wt_path="../${base}--${branch}"
|
||||||
|
|
||||||
|
git worktree add -b "$branch" "$wt_path"
|
||||||
|
mise trust "$wt_path"
|
||||||
|
cd "$wt_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove worktree and branch from within active worktree directory.
|
||||||
|
gd() {
|
||||||
|
if gum confirm "Remove worktree and branch?"; then
|
||||||
|
local cwd base branch root worktree
|
||||||
|
|
||||||
|
cwd="$(pwd)"
|
||||||
|
worktree="$(basename "$cwd")"
|
||||||
|
|
||||||
|
# split on first `--`
|
||||||
|
root="${worktree%%--*}"
|
||||||
|
branch="${worktree#*--}"
|
||||||
|
|
||||||
|
# Protect against accidentally nuking a non-worktree directory
|
||||||
|
if [[ "$root" != "$worktree" ]]; then
|
||||||
|
cd "../$root"
|
||||||
|
git worktree remove "$cwd" --force || return 1
|
||||||
|
git branch -D "$branch"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
1
os updates/fish-to-bash/bash/functions
Executable file
|
|
@ -0,0 +1 @@
|
||||||
|
for f in $OMARCHY_PATH/default/bash/fns/*; do source "$f"; done
|
||||||
30
os updates/fish-to-bash/bash/init
Executable file
|
|
@ -0,0 +1,30 @@
|
||||||
|
if command -v mise &> /dev/null; then
|
||||||
|
eval "$(mise activate bash)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $- == *i* ]] && [[ ${TERM:-} != "dumb" ]] && command -v starship &> /dev/null; then
|
||||||
|
eval "$(starship init bash)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v zoxide &> /dev/null; then
|
||||||
|
eval "$(zoxide init bash)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v try &> /dev/null; then
|
||||||
|
try() {
|
||||||
|
unset -f try
|
||||||
|
eval "$(SHELL=/bin/bash command try init ~/Work/tries)"
|
||||||
|
try "$@"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v fzf &> /dev/null; then
|
||||||
|
if [[ -f /usr/share/fzf/completion.bash ]]; then
|
||||||
|
source /usr/share/fzf/completion.bash
|
||||||
|
fi
|
||||||
|
if [[ -f /usr/share/fzf/key-bindings.bash ]]; then
|
||||||
|
source /usr/share/fzf/key-bindings.bash
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
source "$OMARCHY_PATH/default/bash/completions"
|
||||||
47
os updates/fish-to-bash/bash/inputrc
Executable file
|
|
@ -0,0 +1,47 @@
|
||||||
|
set meta-flag on
|
||||||
|
set input-meta on
|
||||||
|
set output-meta on
|
||||||
|
set convert-meta off
|
||||||
|
set completion-ignore-case on
|
||||||
|
set completion-prefix-display-length 2
|
||||||
|
set show-all-if-ambiguous on
|
||||||
|
set show-all-if-unmodified on
|
||||||
|
|
||||||
|
# Arrow keys match what you've typed so far against your command history
|
||||||
|
"\e[A": history-search-backward
|
||||||
|
"\e[B": history-search-forward
|
||||||
|
"\e[C": forward-char
|
||||||
|
"\e[D": backward-char
|
||||||
|
|
||||||
|
# Immediately add a trailing slash when autocompleting symlinks to directories
|
||||||
|
set mark-symlinked-directories on
|
||||||
|
|
||||||
|
# Do not autocomplete hidden files unless the pattern explicitly begins with a dot
|
||||||
|
set match-hidden-files off
|
||||||
|
|
||||||
|
# Show all autocomplete results at once
|
||||||
|
set page-completions off
|
||||||
|
|
||||||
|
# If there are more than 200 possible completions for a word, ask to show them all
|
||||||
|
set completion-query-items 200
|
||||||
|
|
||||||
|
# Show extra file information when completing, like `ls -F` does
|
||||||
|
set visible-stats on
|
||||||
|
|
||||||
|
# Be more intelligent when autocompleting by also looking at the text after
|
||||||
|
# the cursor. For example, when the current line is "cd ~/src/mozil", and
|
||||||
|
# the cursor is on the "z", pressing Tab will not autocomplete it to "cd
|
||||||
|
# ~/src/mozillail", but to "cd ~/src/mozilla". (This is supported by the
|
||||||
|
# Readline used by Bash 4.)
|
||||||
|
set skip-completed-text on
|
||||||
|
|
||||||
|
# Coloring for Bash 4 tab completions.
|
||||||
|
set colored-stats on
|
||||||
|
|
||||||
|
# Cycle forward and backward through completion candidates (tab/shift+tab)
|
||||||
|
# (completion listing and display behavior configured above)
|
||||||
|
TAB: menu-complete
|
||||||
|
"\e[Z": menu-complete-backward
|
||||||
|
|
||||||
|
# On first Tab, complete the common prefix before cycling candidates
|
||||||
|
set menu-complete-display-prefix on
|
||||||
6
os updates/fish-to-bash/bash/rc
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
source ~/.local/share/omarchy/default/bash/envs
|
||||||
|
source ~/.local/share/omarchy/default/bash/shell
|
||||||
|
source ~/.local/share/omarchy/default/bash/aliases
|
||||||
|
source ~/.local/share/omarchy/default/bash/functions
|
||||||
|
source ~/.local/share/omarchy/default/bash/init
|
||||||
|
[[ $- == *i* ]] && bind -f ~/.local/share/omarchy/default/bash/inputrc
|
||||||
13
os updates/fish-to-bash/bash/shell
Executable file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# History control
|
||||||
|
shopt -s histappend
|
||||||
|
HISTCONTROL=ignoreboth
|
||||||
|
HISTSIZE=32768
|
||||||
|
HISTFILESIZE="${HISTSIZE}"
|
||||||
|
|
||||||
|
# Autocompletion
|
||||||
|
if [[ ! -v BASH_COMPLETION_VERSINFO && -f /usr/share/bash-completion/bash_completion ]]; then
|
||||||
|
source /usr/share/bash-completion/bash_completion
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure command hashing is off for mise
|
||||||
|
set +h
|
||||||
17
os updates/fish-to-bash/bashrc
Executable file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# If not running interactively, don't do anything (leave this at the top of this file)
|
||||||
|
[[ $- != *i* ]] && return
|
||||||
|
|
||||||
|
# NoSignal: default CLI editor (Omarchy normally exports this from uwsm/env).
|
||||||
|
export EDITOR="${EDITOR:-nvim}"
|
||||||
|
|
||||||
|
# All the default Omarchy aliases and functions
|
||||||
|
# (don't mess with these directly, just overwrite them here!)
|
||||||
|
source ~/.local/share/omarchy/default/bash/rc
|
||||||
|
|
||||||
|
# NoSignal: apply the active Caelestia colour scheme to this terminal.
|
||||||
|
[[ $- == *i* ]] && cat ~/.local/state/caelestia/sequences.txt 2>/dev/null
|
||||||
|
|
||||||
|
# Add your own exports, aliases, and functions here.
|
||||||
|
#
|
||||||
|
# Make an alias for invoking commands you use constantly
|
||||||
|
# alias p='python'
|
||||||
22
os updates/fish-to-bash/hypr-configs.bash
Executable file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Ensure the NoSignal user-override files exist — bash port of configs.fish.
|
||||||
|
# Installed to ~/.config/hypr/scripts/configs.fish (the path hyprland.conf
|
||||||
|
# exec's at startup); runs as bash via this shebang.
|
||||||
|
# Usage: configs <config-dir>
|
||||||
|
|
||||||
|
reload=false
|
||||||
|
dir="$1"
|
||||||
|
|
||||||
|
[[ -d "$dir" ]] || mkdir -p "$dir"
|
||||||
|
|
||||||
|
if [[ ! -f "$dir/hypr-vars.conf" ]]; then
|
||||||
|
touch "$dir/hypr-vars.conf"
|
||||||
|
reload=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$dir/hypr-user.conf" ]]; then
|
||||||
|
touch "$dir/hypr-user.conf"
|
||||||
|
reload=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
$reload && hyprctl reload
|
||||||
28
os updates/fish-to-bash/hypr-wsaction.bash
Executable file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Workspace dispatcher helper — bash port of Caelestia's wsaction.fish.
|
||||||
|
# Installed to ~/.config/hypr/scripts/wsaction.fish (the path the stock
|
||||||
|
# keybinds.conf calls); runs as bash via this shebang.
|
||||||
|
# Usage: wsaction [-g] <dispatcher> <workspace>
|
||||||
|
|
||||||
|
group=0
|
||||||
|
if [[ "$1" == "-g" ]]; then
|
||||||
|
group=1
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $# -ne 2 ]]; then
|
||||||
|
echo "Wrong number of arguments. Usage: wsaction [-g] <dispatcher> <workspace>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
dispatcher="$1"
|
||||||
|
target="$2"
|
||||||
|
active_ws=$(hyprctl activeworkspace -j | jq -r '.id')
|
||||||
|
|
||||||
|
if (( group )); then
|
||||||
|
# Move to the same slot within workspace group <target>
|
||||||
|
hyprctl dispatch "$dispatcher" "$(( (target - 1) * 10 + active_ws % 10 ))"
|
||||||
|
else
|
||||||
|
# Move to workspace <target> within the current group
|
||||||
|
hyprctl dispatch "$dispatcher" "$(( (active_ws - 1) / 10 * 10 + target ))"
|
||||||
|
fi
|
||||||
68
os updates/fish-to-bash/install-fish-to-bash.sh
Executable file
|
|
@ -0,0 +1,68 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# install-fish-to-bash.sh — migrate this NoSignal box from fish to bash, matching
|
||||||
|
# the Omarchy bash setup. Everything ships in this folder so it survives a wipe.
|
||||||
|
#
|
||||||
|
# Applies:
|
||||||
|
# 1. Vendors Omarchy's default/bash tree -> ~/.local/share/omarchy/default/bash
|
||||||
|
# 2. Installs ~/.bashrc (Omarchy template + Caelestia colour-scheme line)
|
||||||
|
# 3. Points kitty + foot at bash
|
||||||
|
# 4. Replaces the two Hyprland helper scripts with bash ports (kept at their
|
||||||
|
# .fish paths so the stock keybinds.conf / hyprland.conf keep working)
|
||||||
|
#
|
||||||
|
# Idempotent (safe to re-run). Backs up an existing ~/.bashrc once.
|
||||||
|
# Does NOT remove the fish package — see README.md.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SRC=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
OMARCHY_BASH="$HOME/.local/share/omarchy/default/bash"
|
||||||
|
|
||||||
|
# 1. Vendor the Omarchy bash tree.
|
||||||
|
mkdir -p "$OMARCHY_BASH/fns"
|
||||||
|
for f in rc shell aliases envs init functions inputrc completions; do
|
||||||
|
cp "$SRC/bash/$f" "$OMARCHY_BASH/$f"
|
||||||
|
done
|
||||||
|
cp "$SRC/bash/fns/"* "$OMARCHY_BASH/fns/"
|
||||||
|
echo "vendored Omarchy bash tree -> $OMARCHY_BASH"
|
||||||
|
|
||||||
|
# 2. Install ~/.bashrc (back up any existing one, once).
|
||||||
|
if [ -f "$HOME/.bashrc" ] && [ ! -f "$HOME/.bashrc.pre-omarchy" ]; then
|
||||||
|
cp "$HOME/.bashrc" "$HOME/.bashrc.pre-omarchy"
|
||||||
|
echo "backed up existing ~/.bashrc -> ~/.bashrc.pre-omarchy"
|
||||||
|
fi
|
||||||
|
cp "$SRC/bashrc" "$HOME/.bashrc"
|
||||||
|
echo "installed ~/.bashrc"
|
||||||
|
|
||||||
|
# 3. Terminals -> bash.
|
||||||
|
KITTY="$HOME/.config/kitty/kitty.conf"
|
||||||
|
if [ -f "$KITTY" ]; then
|
||||||
|
if grep -q '^shell ' "$KITTY"; then
|
||||||
|
sed -i 's/^shell .*/shell bash/' "$KITTY"
|
||||||
|
else
|
||||||
|
printf '\nshell bash\n' >> "$KITTY"
|
||||||
|
fi
|
||||||
|
echo "kitty -> bash"
|
||||||
|
fi
|
||||||
|
FOOT="$HOME/.config/foot/foot.ini"
|
||||||
|
if [ -f "$FOOT" ]; then
|
||||||
|
if grep -q '^shell=' "$FOOT"; then
|
||||||
|
sed -i 's/^shell=.*/shell=bash/' "$FOOT"
|
||||||
|
else
|
||||||
|
sed -i '1i shell=bash' "$FOOT"
|
||||||
|
fi
|
||||||
|
echo "foot -> bash"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Hyprland helper scripts -> bash (keep the .fish filenames the config calls).
|
||||||
|
SCRIPTS="$HOME/.config/hypr/scripts"
|
||||||
|
if [ -d "$SCRIPTS" ]; then
|
||||||
|
install -m 0755 "$SRC/hypr-wsaction.bash" "$SCRIPTS/wsaction.fish"
|
||||||
|
install -m 0755 "$SRC/hypr-configs.bash" "$SCRIPTS/configs.fish"
|
||||||
|
echo "Hyprland helper scripts -> bash"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. Reload Hyprland if running.
|
||||||
|
if command -v hyprctl >/dev/null 2>&1 && hyprctl version >/dev/null 2>&1; then
|
||||||
|
hyprctl reload >/dev/null 2>&1 && echo "reloaded Hyprland"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Done. Open a NEW terminal to land in bash."
|
||||||
80
os updates/gamemode-toggle-deckshift/README.md
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
# gamemode-toggle-deckshift — Game Mode toggle launches DeckShift
|
||||||
|
|
||||||
|
Companion to `gaming-key-guard` (which does the Super+Shift+S keybind). Patches
|
||||||
|
the package-owned nosignal-shell file; the canonical fix is in the fork.
|
||||||
|
|
||||||
|
## Request
|
||||||
|
The utilities **Game Mode** button (gamepad icon) should launch the DeckShift
|
||||||
|
gaming session when DeckShift is installed, and do nothing otherwise. Previously
|
||||||
|
it ran caelestia's cosmetic Game Mode (disable Hyprland animations/blur/gaps).
|
||||||
|
|
||||||
|
## Change
|
||||||
|
`modules/utilities/cards/Toggles.qml`, the `gameMode` `DelegateChoice`:
|
||||||
|
|
||||||
|
before
|
||||||
|
```qml
|
||||||
|
delegate: Toggle {
|
||||||
|
icon: "gamepad"
|
||||||
|
checked: GameMode.enabled
|
||||||
|
onClicked: GameMode.enabled = !GameMode.enabled
|
||||||
|
}
|
||||||
|
```
|
||||||
|
after (hardened)
|
||||||
|
```qml
|
||||||
|
delegate: Toggle {
|
||||||
|
id: gmTog
|
||||||
|
property bool launching: false
|
||||||
|
icon: "gamepad"
|
||||||
|
disabled: launching
|
||||||
|
onClicked: {
|
||||||
|
if (gmTog.launching) return;
|
||||||
|
gmTog.launching = true;
|
||||||
|
gmTog.internalChecked = false; // don't latch "on" — it's a launcher
|
||||||
|
relockTimer.start();
|
||||||
|
Quickshell.execDetached(["sh", "-c", "[ -x /usr/local/bin/switch-to-gaming ] && [ -f /usr/share/wayland-sessions/gamescope-session-steam-nm.desktop ] && exec /usr/local/bin/switch-to-gaming"]);
|
||||||
|
}
|
||||||
|
Timer { id: relockTimer; interval: 5000; onTriggered: gmTog.launching = false }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Kept styled as an off-toggle** (default `isToggle: true`, no `checked`) so it
|
||||||
|
keeps the muted/outline look of the other toggles. An earlier version set
|
||||||
|
`isToggle: false`, which flipped the icon to filled/accent — that was the
|
||||||
|
reported "changed colour"; reverted. `internalChecked` is reset to false on
|
||||||
|
click so the launcher never latches "on".
|
||||||
|
- **Debounced.** A fast double-click previously fired `switch-to-gaming` twice →
|
||||||
|
two `systemctl restart sddm` calls → the first restart consumed the one-shot
|
||||||
|
autologin marker and the second fell back to the password greeter (the reported
|
||||||
|
"takes you to the login screen, not gamescope"). The `launching` guard +
|
||||||
|
`disabled` + 5 s `relockTimer` ensure one launch per press.
|
||||||
|
- The `sh -c '… && … && exec switch-to-gaming'` guard mirrors the Super+Shift+S
|
||||||
|
bind and the deckshift-login install-check: nothing happens if DeckShift is absent.
|
||||||
|
- Adds `import Quickshell` (for `execDetached`; the file only had
|
||||||
|
`Quickshell.Bluetooth`). `GameMode` (from `qs.services`) is no longer referenced
|
||||||
|
here — the cosmetic Game Mode is still available via its IPC (`gameMode` target)
|
||||||
|
if a future build wants to re-expose it elsewhere.
|
||||||
|
- **Hidden when DeckShift isn't installed.** A `deckshiftProbe` Process
|
||||||
|
(`test -f /usr/share/wayland-sessions/gamescope-session-steam-nm.desktop`) sets
|
||||||
|
`root.deckshiftInstalled` at load, and the `quickToggles` filter drops the
|
||||||
|
`gameMode` entry when it's false (same pattern as the existing `vpn` special-case)
|
||||||
|
— so the tile cleanly leaves the grid (no dead button, no gap) on non-gaming
|
||||||
|
boxes, and appears once DeckShift is installed (after a shell reload). Needs
|
||||||
|
`import Quickshell.Io` for `Process`.
|
||||||
|
|
||||||
|
## Pieces / integration
|
||||||
|
1. **`Toggles.qml`** — corrected file (drop-in for
|
||||||
|
`modules/utilities/cards/Toggles.qml` in the nosignal-shell fork). Rebuild the
|
||||||
|
package; set `NOSIGNAL_SKIP_SHELL_PATCH` for the build.
|
||||||
|
2. **`install-gamemode-toggle-deckshift.sh`** — idempotent fallback patch (backup +
|
||||||
|
overwrite). Reverted by the next nosignal-shell upgrade (which carries the fix).
|
||||||
|
3. **`1781478000-gamemode-toggle-deckshift.sh`** → `nosignal-update/migrations/`
|
||||||
|
(delegates via `$NOSIGNAL_SRC`).
|
||||||
|
|
||||||
|
**Shell restart required:** `qs -c caelestia -n -d` disables the file watcher, so
|
||||||
|
the patched file is not hot-reloaded — restart the shell (**Ctrl+Super+Alt+R**) or
|
||||||
|
log out/in after patching.
|
||||||
|
|
||||||
|
## Apply on this box now (needs root)
|
||||||
|
```sh
|
||||||
|
sudo sh ~/nosignal-handoff/gamemode-toggle-deckshift/install-gamemode-toggle-deckshift.sh
|
||||||
|
```
|
||||||
|
then **Ctrl+Super+Alt+R**.
|
||||||
207
os updates/gamemode-toggle-deckshift/Toggles.qml
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Bluetooth
|
||||||
|
import Caelestia.Components
|
||||||
|
import Caelestia.Config
|
||||||
|
import qs.components
|
||||||
|
import qs.components.controls
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.nexus
|
||||||
|
import qs.modules.bar.popouts as BarPopouts
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property DrawerVisibilities visibilities
|
||||||
|
required property BarPopouts.Wrapper popouts
|
||||||
|
|
||||||
|
// NoSignal: the gamepad "Game Mode" toggle launches DeckShift gaming. Hide it
|
||||||
|
// entirely when DeckShift isn't installed (no gamescope session file) — checked
|
||||||
|
// once at load by deckshiftProbe below.
|
||||||
|
property bool deckshiftInstalled: false
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: deckshiftProbe
|
||||||
|
running: true
|
||||||
|
command: ["test", "-f", "/usr/share/wayland-sessions/gamescope-session-steam-nm.desktop"]
|
||||||
|
onExited: (exitCode, exitStatus) => root.deckshiftInstalled = exitCode === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property var quickToggles: {
|
||||||
|
const seenIds = new Set();
|
||||||
|
|
||||||
|
return Config.utilities.quickToggles.filter(item => {
|
||||||
|
if (!(item.enabled ?? true))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (seenIds.has(item.id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.id === "vpn") {
|
||||||
|
return GlobalConfig.utilities.vpn.provider.some(p => typeof p === "object" ? (p.enabled === true) : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoSignal: drop the Game Mode tile unless DeckShift is installed.
|
||||||
|
if (item.id === "gameMode") {
|
||||||
|
return root.deckshiftInstalled;
|
||||||
|
}
|
||||||
|
|
||||||
|
seenIds.add(item.id);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
readonly property int splitIndex: Math.ceil(quickToggles.length / 2)
|
||||||
|
readonly property bool needExtraRow: quickToggles.length > 6
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: layout.implicitHeight + Tokens.padding.extraLargeIncreased
|
||||||
|
|
||||||
|
radius: Tokens.rounding.large
|
||||||
|
color: Colours.tPalette.m3surfaceContainer
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: layout
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Tokens.padding.large
|
||||||
|
spacing: Tokens.spacing.medium
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: qsTr("Quick Toggles")
|
||||||
|
font: Tokens.font.body.medium
|
||||||
|
}
|
||||||
|
|
||||||
|
QuickToggleRow {
|
||||||
|
model: root.needExtraRow ? root.quickToggles.slice(0, root.splitIndex) : root.quickToggles
|
||||||
|
}
|
||||||
|
|
||||||
|
QuickToggleRow {
|
||||||
|
visible: root.needExtraRow
|
||||||
|
model: root.needExtraRow ? root.quickToggles.slice(root.splitIndex) : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component QuickToggleRow: ButtonRow {
|
||||||
|
property alias model: repeater.model
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Tokens.spacing.small
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: repeater
|
||||||
|
|
||||||
|
delegate: DelegateChooser {
|
||||||
|
role: "id"
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "wifi"
|
||||||
|
delegate: Toggle {
|
||||||
|
icon: "wifi"
|
||||||
|
checked: Nmcli.wifiEnabled
|
||||||
|
onClicked: Nmcli.toggleWifi()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "bluetooth"
|
||||||
|
delegate: Toggle {
|
||||||
|
icon: "bluetooth"
|
||||||
|
checked: Bluetooth.defaultAdapter?.enabled ?? false // qmllint disable unresolved-type
|
||||||
|
onClicked: {
|
||||||
|
const adapter = Bluetooth.defaultAdapter; // qmllint disable unresolved-type
|
||||||
|
if (adapter)
|
||||||
|
adapter.enabled = !adapter.enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "mic"
|
||||||
|
delegate: Toggle {
|
||||||
|
icon: "mic"
|
||||||
|
checked: !Audio.sourceMuted
|
||||||
|
onClicked: {
|
||||||
|
const audio = Audio.source?.audio;
|
||||||
|
if (audio)
|
||||||
|
audio.muted = !audio.muted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "settings"
|
||||||
|
delegate: Toggle {
|
||||||
|
icon: "settings"
|
||||||
|
inactiveOnColour: Colours.palette.m3onSurfaceVariant
|
||||||
|
isToggle: false
|
||||||
|
onClicked: {
|
||||||
|
root.visibilities.utilities = false;
|
||||||
|
WindowFactory.create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "gameMode"
|
||||||
|
delegate: Toggle {
|
||||||
|
id: gmTog
|
||||||
|
// NoSignal: launch the DeckShift gaming session if installed;
|
||||||
|
// otherwise do nothing. (Was caelestia's cosmetic Game Mode.)
|
||||||
|
// Kept styled as an off-toggle (muted, outline icon) — the
|
||||||
|
// gamepad is a momentary LAUNCHER, not a stateful toggle.
|
||||||
|
// DEBOUNCED: a fast double-click must not fire two
|
||||||
|
// `switch-to-gaming` runs — two SDDM restarts race the
|
||||||
|
// one-shot autologin and drop you at the password greeter.
|
||||||
|
// Guard mirrors the Super+Shift+S bind + the deckshift-login
|
||||||
|
// install-check, so it no-ops if DeckShift is absent.
|
||||||
|
property bool launching: false
|
||||||
|
icon: "gamepad"
|
||||||
|
disabled: launching
|
||||||
|
onClicked: {
|
||||||
|
if (gmTog.launching)
|
||||||
|
return;
|
||||||
|
gmTog.launching = true;
|
||||||
|
gmTog.internalChecked = false; // don't latch "on"
|
||||||
|
relockTimer.start();
|
||||||
|
Quickshell.execDetached(["sh", "-c", "[ -x /usr/local/bin/switch-to-gaming ] && [ -f /usr/share/wayland-sessions/gamescope-session-steam-nm.desktop ] && exec /usr/local/bin/switch-to-gaming"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: relockTimer
|
||||||
|
interval: 5000
|
||||||
|
onTriggered: gmTog.launching = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "dnd"
|
||||||
|
delegate: Toggle {
|
||||||
|
icon: "notifications_off"
|
||||||
|
checked: Notifs.dnd
|
||||||
|
onClicked: Notifs.dnd = !Notifs.dnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "vpn"
|
||||||
|
delegate: Toggle {
|
||||||
|
icon: "vpn_key"
|
||||||
|
checked: VPN.connected && VPN.status.state !== "needs-auth" && VPN.status.state !== "error"
|
||||||
|
enabled: !VPN.connecting
|
||||||
|
isToggle: VPN.status.state !== "needs-auth" && VPN.status.state !== "error"
|
||||||
|
inactiveOnColour: Colours.palette.m3onSurfaceVariant
|
||||||
|
onClicked: VPN.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component Toggle: IconButton {
|
||||||
|
inactiveColour: Colours.layer(Colours.palette.m3surfaceContainerHighest, 2)
|
||||||
|
fillWidth: true
|
||||||
|
isToggle: true
|
||||||
|
isRound: true
|
||||||
|
shapeMorph: true
|
||||||
|
}
|
||||||
|
}
|
||||||
40
os updates/gamemode-toggle-deckshift/install-gamemode-toggle-deckshift.sh
Executable file
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# install-gamemode-toggle-deckshift.sh — idempotent. REQUIRES ROOT (writes the
|
||||||
|
# package-owned shell file under /etc/xdg).
|
||||||
|
#
|
||||||
|
# Repoints the utilities "Game Mode" gamepad toggle to launch the DeckShift gaming
|
||||||
|
# session if DeckShift is installed; otherwise it does nothing. (Was caelestia's
|
||||||
|
# cosmetic Game Mode toggle — disable animations/blur/gaps.) See README.
|
||||||
|
#
|
||||||
|
# Toggles.qml is shipped by the `nosignal-shell` package (baked into the fork). The
|
||||||
|
# REAL fix is in the fork source — this fallback patch is for already-installed boxes
|
||||||
|
# and is reverted by the next nosignal-shell upgrade (which carries the fixed file).
|
||||||
|
# The builder bakes it into the fork and sets NOSIGNAL_SKIP_SHELL_PATCH to skip this.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SELF_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
TARGET=/etc/xdg/quickshell/caelestia/modules/utilities/cards/Toggles.qml
|
||||||
|
|
||||||
|
if [ -n "${NOSIGNAL_SKIP_SHELL_PATCH:-}" ]; then
|
||||||
|
echo ":: skipping fallback shell patch (NOSIGNAL_SKIP_SHELL_PATCH — fork bakes the fix)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ "$(id -u)" -eq 0 ] || { echo "must run as root (writes $TARGET)" >&2; exit 1; }
|
||||||
|
|
||||||
|
if [ ! -f "$TARGET" ]; then
|
||||||
|
echo "WARNING: $TARGET not present — nosignal-shell not installed here; nothing to patch." >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if cmp -s "$SELF_DIR/Toggles.qml" "$TARGET"; then
|
||||||
|
echo ":: Game Mode toggle already up to date — nothing to do."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# preserve the ORIGINAL (pre-NoSignal) file only on the first patch, so re-running
|
||||||
|
# with a newer Toggles.qml (e.g. the hardened version) doesn't overwrite the backup
|
||||||
|
[ -f "$TARGET.prefix.bak" ] || cp -a "$TARGET" "$TARGET.prefix.bak"
|
||||||
|
install -m 0644 "$SELF_DIR/Toggles.qml" "$TARGET"
|
||||||
|
echo ":: patched $TARGET (backup: $TARGET.prefix.bak)"
|
||||||
|
echo ":: restart the shell to apply: Ctrl+Super+Alt+R, or log out/in."
|
||||||
55
os updates/gaming-enablement/README.md
Executable file
|
|
@ -0,0 +1,55 @@
|
||||||
|
# gaming-enablement — multilib repo + omarchy-pkg-add shim
|
||||||
|
|
||||||
|
Makes Omarchy-targeted Steam/gaming install scripts (e.g. **DeckShift**) work on
|
||||||
|
NoSignal. Two gaps were blocking them:
|
||||||
|
|
||||||
|
1. **`[multilib]` was disabled** in `/etc/pacman.conf` — the 32-bit repo every
|
||||||
|
`lib32-*` package (Steam, Wine, gaming libs) lives in. Without it, pacman/yay
|
||||||
|
can't see those packages at all.
|
||||||
|
2. **`omarchy-pkg-add` doesn't exist** on NoSignal — it ships `yay`/Shelly, not the
|
||||||
|
Omarchy CLI helpers, so scripts die at `omarchy-pkg-add: command not found`.
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
- Uncomments the `[multilib]` block in `/etc/pacman.conf` (leaving
|
||||||
|
`[multilib-testing]` alone) and runs `pacman -Sy`.
|
||||||
|
- Installs **Omarchy CLI shims** → `~/.local/bin` (NoSignal ships yay + the
|
||||||
|
caelestia launcher, not the Omarchy helpers DeckShift expects):
|
||||||
|
|
||||||
|
| Shim | NoSignal behaviour |
|
||||||
|
|------|------------------|
|
||||||
|
| `omarchy-pkg-add` | `yay -S --needed --noconfirm "$@"` (idempotent installs) |
|
||||||
|
| `omarchy-install-gaming-steam` | `yay -S steam` + best-effort first launch (needs multilib) |
|
||||||
|
| `omarchy-hw-nvidia-gsp` | exit 0 if an NVIDIA GPU is present (modern/GSP branch) |
|
||||||
|
| `omarchy-hw-nvidia-without-gsp` | exit 1 (defaults to modern branch; see note) |
|
||||||
|
| `omarchy-restart-walker` | no Walker here — refresh desktop DB, succeed |
|
||||||
|
|
||||||
|
The NVIDIA shims default any present card to the modern (`nvidia-utils`)
|
||||||
|
branch; pre-Turing cards need `nvidia-580xx-*` by hand. On this AMD+Intel box
|
||||||
|
both correctly report "no NVIDIA".
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sh install-gaming-enablement.sh # needs sudo for the pacman.conf edit + sync
|
||||||
|
```
|
||||||
|
|
||||||
|
After this, `./deckshift.sh` (and similar) find their deps and proceed. Note:
|
||||||
|
DeckShift also *optionally* calls `omarchy-install-gaming-steam`,
|
||||||
|
`omarchy-hw-nvidia-gsp`, `omarchy-restart-walker` — those are `command -v`-guarded
|
||||||
|
in the script, so they're skipped harmlessly (only `omarchy-pkg-add` was fatal).
|
||||||
|
|
||||||
|
## Builder notes
|
||||||
|
|
||||||
|
For a gaming-capable ISO:
|
||||||
|
- **Enable `[multilib]` in the image's `/etc/pacman.conf` by default** (uncomment
|
||||||
|
the two lines) and sync in the build.
|
||||||
|
- **Ship `omarchy-pkg-add`** in `~/.local/bin` (or `/usr/local/bin`). Consider
|
||||||
|
shipping the other commonly-referenced Omarchy helpers
|
||||||
|
(`omarchy-install-gaming-steam`, `omarchy-hw-nvidia-*`, `omarchy-restart-walker`)
|
||||||
|
as shims if broad Omarchy-script compatibility is wanted.
|
||||||
|
- This pairs naturally with shipping `gamemode` / `lib32-*` Steam deps in the
|
||||||
|
base if gaming is a first-class use case.
|
||||||
|
|
||||||
|
This is a **package/repo-config delta** (unlike the other changes) — it touches
|
||||||
|
the package list / pacman.conf rather than dropping a self-contained component.
|
||||||
31
os updates/gaming-enablement/install-gaming-enablement.sh
Executable file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# install-gaming-enablement.sh — enable the multilib repo + install the
|
||||||
|
# omarchy-pkg-add shim, so Omarchy-targeted Steam/gaming install scripts (e.g.
|
||||||
|
# DeckShift) work on NoSignal. Idempotent. Needs sudo for the pacman.conf edit and
|
||||||
|
# database sync (it will prompt).
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SRC=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
BIN="$HOME/.local/bin"
|
||||||
|
|
||||||
|
# 1. Enable [multilib] — the 32-bit repo every lib32-* package lives in.
|
||||||
|
if pacman-conf --repo-list 2>/dev/null | grep -qx multilib; then
|
||||||
|
echo ":: [multilib] already enabled"
|
||||||
|
else
|
||||||
|
echo ":: enabling [multilib] in /etc/pacman.conf (sudo)..."
|
||||||
|
# Uncomment ONLY the [multilib] block (leaves [multilib-testing] alone).
|
||||||
|
sudo sed -i '/^#\[multilib\]/,/^#Include/ s/^#//' /etc/pacman.conf
|
||||||
|
sudo pacman -Sy
|
||||||
|
echo ":: [multilib] enabled and databases synced"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Omarchy CLI shims (NoSignal ships yay/caelestia, not the Omarchy helpers).
|
||||||
|
mkdir -p "$BIN"
|
||||||
|
for shim in omarchy-pkg-add omarchy-install-gaming-steam \
|
||||||
|
omarchy-hw-nvidia-gsp omarchy-hw-nvidia-without-gsp \
|
||||||
|
omarchy-restart-walker; do
|
||||||
|
install -m 0755 "$SRC/$shim" "$BIN/$shim"
|
||||||
|
echo ":: installed $shim -> $BIN/$shim"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Done. multilib + Omarchy shims ready — re-run your gaming installer."
|
||||||
13
os updates/gaming-enablement/omarchy-hw-nvidia-gsp
Executable file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# omarchy-hw-nvidia-gsp — NoSignal shim. Exit 0 if an NVIDIA GPU is present (and
|
||||||
|
# thus should use the modern, GSP-firmware driver branch: nvidia-utils). Exit 1
|
||||||
|
# if no NVIDIA GPU. DeckShift only calls this when it has already detected an
|
||||||
|
# NVIDIA card, to choose modern vs legacy.
|
||||||
|
#
|
||||||
|
# Limitation: this defaults any present NVIDIA card to the modern/GSP branch
|
||||||
|
# (correct for Turing/RTX-20 and newer, ~2018+). It does not distinguish very
|
||||||
|
# old pre-Turing cards — see omarchy-hw-nvidia-without-gsp.
|
||||||
|
if lspci -d 10de:: 2>/dev/null | grep -qiE 'VGA|3D|Display'; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
9
os updates/gaming-enablement/omarchy-hw-nvidia-without-gsp
Executable file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# omarchy-hw-nvidia-without-gsp — NoSignal shim. Exit 0 only for legacy pre-GSP
|
||||||
|
# NVIDIA (Maxwell/Pascal/Volta → the nvidia-580xx driver branch).
|
||||||
|
#
|
||||||
|
# This shim cannot reliably tell GPU generations apart, so it defaults to the
|
||||||
|
# modern branch (always exit 1; omarchy-hw-nvidia-gsp handles present cards). If
|
||||||
|
# you actually run a pre-Turing NVIDIA card, install nvidia-580xx-utils /
|
||||||
|
# nvidia-580xx-dkms by hand.
|
||||||
|
exit 1
|
||||||