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
|
||||