diff --git a/README.md b/README.md
index e10f7c2..82f0c5c 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
Brain_Shell
-
+
A dynamic, highly modular Wayland desktop shell built with Quickshell and QML, tailored for Hyprland.
@@ -55,7 +55,7 @@
---
-
+
Installation
@@ -65,6 +65,63 @@
curl -fsSL https://raw.githubusercontent.com/Brainitech/Brain_Shell/refs/heads/main/install.sh | bash
```
+---
+
+### NixOS
+
+### 1. Create or edit `/etc/nixos/flake.nix`
+
+```nix
+{
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
+ brain-shell = {
+ url = "github:Brainitech/Brain_Shell?ref=main";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+
+ outputs = { nixpkgs, brain-shell, ... }: {
+ # Replace "hostname" with whatever your actual hostname is
+ nixosConfigurations.hostname = nixpkgs.lib.nixosSystem {
+ modules = [
+ brain-shell.nixosModules.default
+ ./configuration.nix
+ ];
+ };
+ };
+}
+
+```
+
+### 2. Enable it in `/etc/nixos/configuration.nix`
+
+```nix
+programs.brain-shell.enable = true;
+
+# Note: If this is a fresh install, ensure flakes are enabled:
+nix.settings.experimental-features = [ "nix-command" "flakes" ];
+
+```
+
+### 3. Rebuild the system
+
+Run the rebuild command targeting the flake.
+
+```bash
+sudo nixos-rebuild switch --flake# /etc/nixos/#hostname
+
+```
+
+### 4. Run the user installer
+
+Once the system rebuild is finished, run the setup script to initialize your local ~/.config files and Hyprland autostarts.
+```bash
+bash <(curl -s https://raw.githubusercontent.com/Brainitech/Brain_Shell/main/install.sh)
+```
+
+---
+
### Manual installation
```bash
@@ -74,6 +131,8 @@ chmod +x install.sh
./install.sh
```
+Restart Hyprland, and that's the complete end-to-end user experience.
+
The installer automatically:
- ✓ Detects your Linux distribution
@@ -175,7 +234,7 @@ The installer automatically:
---
-
+
Roadmap
@@ -208,7 +267,7 @@ The installer automatically:
---
-
+
Known Issues
@@ -218,12 +277,9 @@ Known Issues
- **Shutdown Menu (Hyprshutdown) State:** Canceling a shutdown or logout action can sometimes leave the Hyprland session in an empty state with most applications unintentionally closed. It may also occasionally struggle to terminate all running apps smoothly.
-> [!WARNING]
-> **NixOS & Flakes Support:** The current NixOS installation pipeline and Flake implementation are highly experimental and currently known to be broken. This is actively under testing and will be properly addressed in an upcoming patch. If you are on NixOS, manual configuration is currently required.
-
---
-
+
Contributing
@@ -236,7 +292,7 @@ Brain Shell is actively developed and welcomes contributions!
---
-
+
Special Thanks
@@ -250,7 +306,7 @@ Brain Shell is actively developed and welcomes contributions!
---
-
+
Brain Cells Collected
@@ -266,7 +322,7 @@ Brain Shell is actively developed and welcomes contributions!
---
-
+
License
diff --git a/dots-extra/install-nix.sh b/dots-extra/install-nix.sh
new file mode 100644
index 0000000..a214e9a
--- /dev/null
+++ b/dots-extra/install-nix.sh
@@ -0,0 +1,196 @@
+#!/bin/bash
+# ─────────────────────────────────────────────────────────────────────────────
+# Brain Shell — NixOS Installer
+# Invoked by install.sh: $1=HYPRLAND_CONF $2=BACKUP_DIR $3=CONFIG_TYPE
+# ─────────────────────────────────────────────────────────────────────────────
+
+set -eo pipefail
+
+HYPRLAND_CONF="${1:?Missing arg: HYPRLAND_CONF path}"
+BACKUP_DIR="${2:?Missing arg: BACKUP_DIR}"
+CONFIG_TYPE="${3:?Missing arg: CONFIG_TYPE (conf|lua)}"
+REPO_DIR="$HOME/.local/src/Brain_Shell"
+
+RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
+BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'
+DIM='\033[2m'; NC='\033[0m'
+
+log_info() { echo -e " ${BLUE}·${NC} $1"; }
+log_ok() { echo -e " ${GREEN}✓${NC} $1"; }
+log_warn() { echo -e " ${YELLOW}⚠${NC} $1"; }
+
+TOTAL_STEPS=3
+step() {
+ echo ""
+ echo -e "${BOLD}${CYAN} [$1/$TOTAL_STEPS] $2${NC}"
+ echo -e " ${DIM}$(printf '%.0s─' {1..50})${NC}"
+}
+
+# ══════════════════════════════════════════════════════════════════════════════
+# STEP 1 — Hyprland Config
+# ══════════════════════════════════════════════════════════════════════════════
+step 1 "Hyprland Config"
+
+_MARKER="quickshell.*Brain_Shell"
+
+_append_conf() {
+ cat << 'EOF' >> "$1"
+
+# Brain Shell Autostarts
+exec-once = awww-daemon
+exec-once = hypridle -c $HOME/.local/src/Brain_Shell/src/config/hypridle.conf
+exec-once = quickshell -c $HOME/.local/src/Brain_Shell/.
+exec-once = systemctl --user start hyprpolkitagent
+exec-once = wl-paste --type text --watch cliphist store
+exec-once = wl-paste --type image --watch cliphist store
+EOF
+}
+
+_append_lua() {
+ cat << 'EOF' >> "$1"
+
+-- Brain Shell Autostarts
+hl.on("hyprland.start", function()
+ hl.exec_cmd("awww-daemon")
+ hl.exec_cmd("hypridle -c " .. os.getenv("HOME") .. "/.local/src/Brain_Shell/src/config/hypridle.conf")
+ hl.exec_cmd("quickshell -c " .. os.getenv("HOME") .. "/.local/src/Brain_Shell")
+ hl.exec_cmd("systemctl --user start hyprpolkitagent")
+ hl.exec_cmd("wl-paste --type text --watch cliphist store")
+ hl.exec_cmd("wl-paste --type image --watch cliphist store")
+end)
+EOF
+}
+
+if grep -q "$_MARKER" "$HYPRLAND_CONF" 2>/dev/null; then
+ log_warn "Autostart block already present — skipping."
+else
+ case "$CONFIG_TYPE" in
+ conf)
+ _append_conf "$HYPRLAND_CONF"
+ log_ok "Autostart block appended to hyprland.conf"
+ ;;
+ lua)
+ cp "$HYPRLAND_CONF" "${HYPRLAND_CONF}.pre-brain-shell"
+ _append_lua "$HYPRLAND_CONF"
+ log_ok "Autostart block appended to hyprland.lua"
+ ;;
+ esac
+fi
+
+# ══════════════════════════════════════════════════════════════════════════════
+# STEP 2 — Brain Shell Config & Keybind Check
+# ══════════════════════════════════════════════════════════════════════════════
+step 2 "Brain Shell Config"
+
+USER_DATA="$HOME/.config/Brain_Shell/src/user_data"
+
+mkdir -p "$USER_DATA" \
+ "$HOME/.config/hypr/shaders" \
+ "$HOME/.config/matugen/templates" \
+ "$HOME/.cache/brain-shell" \
+ "$HOME/Pictures/Wallpapers"
+
+cp -n "$REPO_DIR/src/config/hypridle.conf" "$HOME/.config/hypr/" 2>/dev/null || true
+touch "$HOME/.cache/brain-shell/colors.json"
+cp -n -r "$REPO_DIR/src/assets/wallpapers"/* "$HOME/Pictures/Wallpapers/" 2>/dev/null || true
+touch "$USER_DATA/keybinds.json"
+
+printf '{"configProvider": "%s"}\n' "$CONFIG_TYPE" > "$USER_DATA/config_Provider.json"
+printf '{}\n' > "$USER_DATA/keybinds.json"
+
+log_ok "Config and cache directories initialized."
+
+# ── Keybind Conflict Detection ────────────────────────────────────────────────
+echo ""
+log_info "Checking keybind conflicts against active Hyprland session..."
+
+python3 << 'PYEOF' || log_warn "Keybind check skipped (Python error or no Hyprland session)."
+import subprocess, json, os, sys
+
+DEFAULTS = {
+ "dashboard-home": {"mods": "SUPER", "key": "D", "label": "Dashboard: System"},
+ "dashboard-stats": {"mods": "CTRL + SHIFT", "key": "ESCAPE", "label": "Dashboard: Home"},
+ "dashboard-kanban": {"mods": "SUPER", "key": "Z", "label": "Dashboard: Tasks"},
+ "dashboard-launcher": {"mods": "SUPER", "key": "Q", "label": "Dashboard: Apps"},
+ "dashboard-config": {"mods": "SUPER", "key": "C", "label": "Dashboard: Config"},
+ "PowerMenu-toggle": {"mods": "SUPER", "key": "ESCAPE", "label": "Power Menu"},
+ "notification-toggle": {"mods": "SUPER", "key": "N", "label": "Notifications"},
+ "wallpaper-toggle": {"mods": "SUPER", "key": "W", "label": "Wallpaper"},
+ "clipboard-toggle": {"mods": "SUPER", "key": "V", "label": "Clipboard"},
+ "wifi-toggle": {"mods": "SUPER + ALT", "key": "W", "label": "Network: Wi-Fi"},
+ "bluetooth-toggle": {"mods": "SUPER + ALT", "key": "B", "label": "Network: Bluetooth"},
+ "vpn-toggle": {"mods": "SUPER + ALT", "key": "G", "label": "Network: VPN"},
+ "hotspot-toggle": {"mods": "SUPER + ALT", "key": "H", "label": "Network: Hotspot"},
+ "audioOut-toggle": {"mods": "SUPER", "key": "A", "label": "Audio: Output"},
+ "audioIn-toggle": {"mods": "SUPER + ALT", "key": "I", "label": "Audio: Input"},
+ "audioMix-toggle": {"mods": "SUPER", "key": "M", "label": "Audio: Mixer"},
+ "focus-toggle": {"mods": "SUPER", "key": "B", "label": "Focus Mode"},
+ "screenrec-on": {"mods": "ALT", "key": "F9", "label": "Screen Record"},
+}
+
+MOD_BITS = {"SHIFT": 1, "CTRL": 4, "ALT": 8, "SUPER": 64}
+
+def mods_to_mask(mods_str):
+ mask = 0
+ for part in mods_str.upper().split("+"):
+ mask |= MOD_BITS.get(part.strip(), 0)
+ return mask
+
+try:
+ raw = subprocess.check_output(["hyprctl", "binds", "-j"], stderr=subprocess.DEVNULL).decode()
+ hypr_binds = json.loads(raw)
+except Exception:
+ print(" \033[2m(not inside Hyprland — skipping live conflict check)\033[0m")
+ sys.exit(0)
+
+conflicts = {}
+for action, data in DEFAULTS.items():
+ mask = mods_to_mask(data["mods"])
+ key = data["key"].lower()
+ for hb in hypr_binds:
+ if hb.get("submap", "") or hb.get("mouse"):
+ continue
+ if hb.get("modmask") == mask and str(hb.get("key", "")).lower() == key:
+ desc = hb.get("dispatcher", "")
+ arg = hb.get("arg", "")
+ conflicts[action] = {
+ "bind": f"{data['mods']} + {data['key']}",
+ "label": data["label"],
+ "used_by": f"{desc} {arg}".strip(),
+ }
+ break
+
+if not conflicts:
+ print(" \033[0;32m✓\033[0m No keybind conflicts detected.")
+ sys.exit(0)
+
+print(f"\n \033[0;31m✗\033[0m {len(conflicts)} conflict(s) found:\n")
+unbound = {}
+for action, info in conflicts.items():
+ print(f" \033[1m{info['bind']:<24}\033[0m {info['label']}")
+ print(f" {'':24} already used by: {info['used_by']}\n")
+ unbound[action] = {"mods": "", "key": ""}
+
+config_path = os.path.expanduser("~/.config/Brain_Shell/src/user_data/keybinds.json")
+with open(config_path, "w") as f:
+ json.dump(unbound, f, indent=2)
+
+print(" \033[1;33m⚠\033[0m Conflicting binds left unbound in Brain Shell.")
+print(" Re-assign them: Dashboard → Config → Keybinds\n")
+PYEOF
+
+# ══════════════════════════════════════════════════════════════════════════════
+# STEP 3 — Done
+# ══════════════════════════════════════════════════════════════════════════════
+step 3 "Done"
+
+echo ""
+log_ok "NixOS setup complete."
+log_info "System packages and dependencies are managed entirely by your flake."
+echo ""
+echo -e " ${BOLD}Restart Hyprland to activate Brain Shell:${NC}"
+log_info "Log out and log back in ${DIM}(recommended)${NC}"
+log_info "hyprctl dispatch exit"
+echo ""
+
+exit 0
diff --git a/flake.lock b/flake.lock
index 8680d9e..b8e8d37 100644
--- a/flake.lock
+++ b/flake.lock
@@ -1,61 +1,61 @@
{
- "nodes": {
- "flake-utils": {
- "inputs": {
- "systems": "systems"
- },
- "locked": {
- "lastModified": 1726560853,
- "narHash": "sha256-X6rJYSESBVr3hLYL3Er7tcQmV8I1EuRRTmU1hHJ58EA=",
- "owner": "numtide",
- "repo": "flake-utils",
- "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "flake-utils",
- "type": "github"
- }
+ "nodes": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1780749050,
+ "narHash": "sha256-3av0pIjlOWQ6rDbNOmpUSvbNnJkGORQKKjb4LtCZsIY=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "a799d3e3886da994fa307f817a6bc705ae538eeb",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
},
- "nixpkgs": {
- "locked": {
- "lastModified": 1748707769,
- "narHash": "sha256-pending-run-nix-flake-update",
- "owner": "nixos",
- "repo": "nixpkgs",
- "rev": "331800de5053fcebacf6813adb5db9c9dca22a0c",
- "type": "github"
- },
- "original": {
- "owner": "nixos",
- "repo": "nixpkgs",
- "ref": "nixos-unstable",
- "type": "github"
- }
- },
- "root": {
- "inputs": {
- "flake-utils": "flake-utils",
- "nixpkgs": "nixpkgs"
- }
- },
- "systems": {
- "locked": {
- "lastModified": 1681028828,
- "narHash": "sha256-Vy12ghalO9X1K6PDcKmM7URBQRdkelmlmAC5XhUltA=",
- "owner": "nix-systems",
- "repo": "default",
- "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
- "type": "github"
- },
- "original": {
- "owner": "nix-systems",
- "repo": "default",
- "type": "github"
- }
- }
- },
- "root": "root",
- "version": 7
+ "root": "root",
+ "version": 7
}
diff --git a/flake.nix b/flake.nix
index c27a0c1..2e49689 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,5 +1,5 @@
{
- description = "Brain Shell — Modular Quickshell/QML desktop shell for Hyprland";
+ description = "Brain Shell - Modular session shell for Hyprland";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
@@ -9,176 +9,84 @@
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
- pkgs = nixpkgs.legacyPackages.${system};
-
- # ── Runtime dependencies (required at launch) ──────────────────────
- runtimeDeps = with pkgs; [
- # Core shell runtime
- quickshell
- hyprland
- qt6.full
- qt6ct
-
- # Audio
- pipewire
- pipewire-pulse
- wireplumber
- playerctl
- mpv-mpris
- mpd-mpris
-
- # Network / Bluetooth
- networkmanager
- bluez
- bluez-utils
-
- # Display / input utilities
- brightnessctl
- wl-clipboard
- slurp
- xdg-user-dirs
- xdg-desktop-portal-hyprland
-
- # System info
- upower
- libnotify
- polkit
- lm_sensors
- rfkill
-
- # Media & visualiser
- cava
- python3
-
- # Screen recording
- wf-recorder
-
- # Wallpaper & theming
- imagemagick
- awww
- matugen
-
- # Clipboard integration
- wtype
- cliphist
-
- # Power & hardware management
- envycontrol
- auto-cpufreq
-
- # Hyprland ecosystem
- hyprsunset
- hyprlock
- hypridle
- ];
-
- # ── Development extras (not needed at runtime) ─────────────────────
- devDeps = with pkgs; [
- git
- bash
- shellcheck
- python3Packages.python-lsp-server
- ];
-
- # ── Fonts ──────────────────────────────────────────────────────────
- fonts = with pkgs; [
- (nerdfonts.override { fonts = [ "JetBrainsMono" ]; })
- ];
-
- # ── The Brain Shell package ────────────────────────────────────────
- brain-shell = pkgs.stdenv.mkDerivation {
- pname = "brain-shell";
- version = "0.1.0";
-
+ pkgs = import nixpkgs { inherit system; };
+ in {
+ packages.default = pkgs.stdenv.mkDerivation {
+ name = "brain-shell";
src = ./.;
-
- nativeBuildInputs = [ pkgs.makeWrapper ];
- buildInputs = runtimeDeps ++ fonts;
-
+ phases = [ "installPhase" ];
installPhase = ''
- runHook preInstall
-
- mkdir -p $out/share/brain-shell
- cp -r . $out/share/brain-shell/
-
- mkdir -p $out/bin
- makeWrapper ${pkgs.quickshell}/bin/quickshell $out/bin/brain-shell \
- --add-flags "-c $out/share/brain-shell" \
- --set QT_QPA_PLATFORMTHEME qt6ct \
- --prefix PATH : ${pkgs.lib.makeBinPath runtimeDeps}
-
- runHook postInstall
- '';
-
- meta = with pkgs.lib; {
- description = "A modular Quickshell/QML desktop shell for Hyprland";
- homepage = "https://github.com/Brainitech/Brain_Shell";
- license = licenses.mit;
- platforms = platforms.linux;
- mainProgram = "brain-shell";
- };
- };
-
- in
- {
- # ── Packages ───────────────────────────────────────────────────────
- packages = {
- default = brain-shell;
- brain-shell = brain-shell;
+ mkdir -p $out
+ cp -r $src/src $src/shell.qml $out/
+ '';
};
- # ── Dev shell (nix develop) ────────────────────────────────────────
devShells.default = pkgs.mkShell {
- name = "brain-shell-dev";
-
- buildInputs = runtimeDeps ++ devDeps ++ fonts;
-
- shellHook = ''
- export QT_QPA_PLATFORMTHEME=qt6ct
- export BRAIN_SHELL_ROOT="$(pwd)"
-
- echo ""
- echo " Brain Shell dev environment"
- echo " Run: quickshell -c \$BRAIN_SHELL_ROOT"
- echo " Lint: shellcheck install.sh dots-extra/install-arch.sh"
- echo ""
- '';
+ packages = with pkgs; [
+ git
+ python3
+ ];
};
+ }
+ ) // {
+ nixosModules.default = { config, pkgs, lib, ... }:
+ with lib;
+ let
+ cfg = config.programs.brain-shell;
+ brainShellDeps = with pkgs; [
+ quickshell
+ hyprland
+ qt6.qtbase
+ qt6.qtdeclarative
+ qt6.qtwayland
+ qt6Packages.qt6ct
+ pipewire
+ wireplumber
+ networkmanager
+ bluez
+ brightnessctl
+ upower
+ libnotify
+ polkit
+ python3
+ wl-clipboard
+ slurp
+ xdg-user-dirs
+ wtype
+ imagemagick
+ wf-recorder
+ cava
+ playerctl
+ awww
+ matugen
+ lm_sensors
+ hyprlock
+ hypridle
+ hyprsunset
+ xdg-desktop-portal-hyprland
+ cliphist
+ nerd-fonts.jetbrains-mono
+ nerd-fonts.symbols-only
+ git
+ ];
+ in {
+ options.programs.brain-shell = {
+ enable = mkEnableOption "Brain Shell session";
+ };
- # ── NixOS module ───────────────────────────────────────────────────
- nixosModules.default = { config, lib, pkgs, ... }:
- let cfg = config.programs.brain-shell;
- in {
- options.programs.brain-shell = {
- enable = lib.mkEnableOption "Brain Shell desktop shell";
+ config = mkIf cfg.enable {
+ environment.systemPackages = brainShellDeps ++ [ self.packages.${pkgs.system}.default ];
- autostart = lib.mkOption {
- type = lib.types.bool;
- default = true;
- description = "Add brain-shell to Hyprland exec-once.";
- };
- };
+ fonts.packages = with pkgs; [
+ nerd-fonts.jetbrains-mono
+ nerd-fonts.symbols-only
+ ];
- config = lib.mkIf cfg.enable {
- environment.systemPackages = [ brain-shell ];
+ environment.variables.QT_QPA_PLATFORMTHEME = "qt6ct";
- wayland.windowManager.hyprland.settings = lib.mkIf cfg.autostart {
- exec-once = [
- "brain-shell"
- "hypridle"
- "awww-daemon"
- "systemctl --user start hyprpolkitagent"
- "wl-paste --type text --watch cliphist store"
- "wl-paste --type image --watch cliphist store"
- ];
- };
- };
+ services.pipewire.enable = true;
+ services.blueman.enable = true;
};
-
- # ── Checks (run by `nix flake check`) ─────────────────────────────
- checks = {
- build = brain-shell;
};
- }
- );
+ };
}
diff --git a/install.sh b/install.sh
index 3c31400..8cf6cfb 100644
--- a/install.sh
+++ b/install.sh
@@ -1,6 +1,6 @@
#!/bin/bash
# ─────────────────────────────────────────────────────────────────────────────
-# Brain Shell — Main Installer
+# Brain Shell — main Installer
# github.com/Brainitech/Brain_Shell v0.1.0
# ─────────────────────────────────────────────────────────────────────────────
# Hesitation is Defeat — Isshin Ashina