Arch: CachyOS + Niri + Noctalia + Steam + OBS + Code
I've used Nobara for almost 6 months, and now I've transitioned to Niri. So far I can say that I don't miss it. Below is my guide on how to set it up including the apps I've used on Nobara.
First, install cachyos using the settings here.
Install yay and flatpak:
# Install required dependencies
sudo pacman -S --needed base-devel git
# Clone yay repository
cd ~
git clone https://aur.archlinux.org/yay.git
# Build and install yay
cd yay
makepkg -si
# Clean up
cd ..
rm -rf yay
sudo pacman -S flatpak
Install requirements:
sudo pacman -S bluez bluez-utils && sudo systemctl start bluetooth.service && sudo systemctl enable bluetooth.servicesudo pacman -S xdg-desktop-portal-gnome && gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' && gsettings set org.gnome.desktop.interface gtk-theme 'Adwaita-dark'sudo pacman -S steamsudo pacman -S discordyay -S obsidian google-chromesudo pacman -S obs-studiosudo pacman -S vlcyay -S fsearchyay -S visual-studio-code-binsudo pacman -S podman podman-composesudo pacman -S gnome-keyring( & set it up in vscode: Configure Runtime Arguments and add"password-store":"gnome-libsecret")sudo pacman -S dbeaverflatpak install flathub io.missioncenter.MissionCentersudo pacman -S filelightyay -S github-desktop-binsudo pacman -S grim slurpsudo pacman -S wl-clipboardsudo pacman -S swappycurl -fsSL https://tailscale.com/install.sh | sh
Noctalia changes:
Begin by reading the FAQ For Noctalia: https://docs.noctalia.dev/getting-started/faq/
- Add your wallpaper and profile picture.
- Add dock
- Set location
- Set opacity
- Change the size of the dock (in accordance with the value you put for struts in niri)
- Fix the icons by adding
QT_QPA_PLATFORMTHEME=gtk3to/etc/environment(as root)
Niri changes
Note: before you quit niri and log back in, run niri validate to make sure the config is still working, otherwise niri is going to start with a blank config.
Change your keyboard layout:
input {
keyboard {
xkb {
layout "gb"
}
numlock
}
}
Add your displays:
output "DP-1" {
mode "3440x1440@165.001"
position x=0 y=0
}
output "HDMI-A-1" {
mode "1920x1080"
position x=720 y=1440
}
You can find their info with the command niri msg outputs
Add key bindings
// Core Noctalia binds
Mod+Space { spawn "qs" "-c" "noctalia-shell" "ipc" "call" "launcher" "toggle"; }
Mod+S { spawn "qs" "-c" "noctalia-shell" "ipc" "call" "controlCenter" "toggle"; }
Mod+Comma { spawn "qs" "-c" "noctalia-shell" "ipc" "call" "settings" "toggle"; }
// Audio controls
XF86AudioRaiseVolume { spawn "qs" "-c" "noctalia-shell" "ipc" "call" "volume" "increase"; }
XF86AudioLowerVolume { spawn "qs" "-c" "noctalia-shell" "ipc" "call" "volume" "decrease"; }
XF86AudioMute { spawn "qs" "-c" "noctalia-shell" "ipc" "call" "volume" "muteOutput"; }
// Brightness controls
XF86MonBrightnessUp { spawn "qs" "-c" "noctalia-shell" "ipc" "call" "brightness" "increase"; }
XF86MonBrightnessDown { spawn "qs" "-c" "noctalia-shell" "ipc" "call" "brightness" "decrease"; }
// Screenshot region to clipboard
Mod+Shift+S { spawn "sh" "-c" "grim -g \"$(slurp)\" - | wl-copy"; }
// Screenshot region with editing (Swappy)
Mod+Print { spawn "sh" "-c" "grim -g \"$(slurp)\" - | swappy -f -"; }
Make sure you remove previosu entries for Mod+Space, XF86AudioRaiseVolume, XF86AudioLowerVolume, XF86AudioMute, Mod+Print
First, make room for the dock at the bottom of the screen
layout {
gaps 7
struts {
left 0
right 0
top 0
bottom 40
}
}
Startup script with windows in the right place
Note: to find chrome app, use:
grep -il bitwarden ~/.local/share/applications/chrome-*.desktop
In my case I want the following:
First screen (large): Workspace 1:
- Todoist (50% width) /home/ioan/.local/share/applications/chrome-knaiokfnmjjldlfhlioejgcompgenfhb-Default.desktop
- Chrome ( 75% width)
- Claude ( 75% width) /home/ioan/.local/share/applications/chrome-fmpnliohjhemenmnlpbfagaolkdacoja-Default.desktop
Workspace 2:
- Vscode main projects (75% width) opens /home/ioan/Documents/repos/ps2mono with code)
- Terminal allacrity (50% default)
Workspace 3:
- Vscode secondary projects (75% width) (has code open /home/ioan/Documents/repos/mysql-downloader/)
Workspace 4:
- SSH sessions running in vscode (flatcar project) (opens /home/ioan/Documents/repos/flatcar with code) (75% width)
Workspace 5:
- Gaming + my app in another browser session chrome http://ps2immersion.com/ (game off by default) (35% width)
Secondary screen (small): Workspace 1:
- Bitwarden (chrome app) (35% width) /home/ioan/.local/share/applications/chrome-fflifmfnonladkgkdehllhbcghakccgh-Default.desktop
- Chrome (75% width)
- Terminal allacrity (default) (50% width)
Workspace 2:
- Discord (60% width)
- Obsidian (60% width) /home/ioan/Documents/Obsidian/SaMearga
Find the script below.
Discord servers:
- Niri: https://discord.gg/vT8Sfjy7sx
- CachyOS: https://discord.com/invite/cachyos-862292009423470592
- Noctalia: https://discord.noctalia.dev/
The script for setting up the workspaces:
#!/bin/bash
# Workspace Setup Script for Niri - v2.2
# Automates opening applications across multiple workspaces and monitors
echo "===========================================
Niri Workspace Layout Setup Script v2.2
==========================================="
echo ""
# ==================== CONFIGURATION ====================
# Debug mode (set to true to see what commands are being run)
DEBUG=true
# Monitor/Output names (from niri msg outputs)
PRIMARY_MONITOR="DP-1"
SECONDARY_MONITOR="HDMI-A-1"
# Paths to Chrome Apps
TODOIST_APP="/home/ioan/.local/share/applications/chrome-knaiokfnmjjldlfhlioejgcompgenfhb-Default.desktop"
CLAUDE_APP="/home/ioan/.local/share/applications/chrome-fmpnliohjhemenmnlpbfagaolkdacoja-Default.desktop"
BITWARDEN_APP="/home/ioan/.local/share/applications/chrome-fflifmfnonladkgkdehllhbcghakccgh-Default.desktop"
# Project Paths
PS2MONO_PATH="/home/ioan/Documents/repos/ps2mono"
MYSQL_DOWNLOADER_PATH="/home/ioan/Documents/repos/mysql-downloader"
FLATCAR_PATH="/home/ioan/Documents/repos/flatcar"
OBSIDIAN_VAULT="/home/ioan/Documents/Obsidian/SaMearga"
# URLs
PS2_IMMERSION_URL="http://ps2immersion.com/"
# Application commands
TERMINAL_CMD="alacritty"
CHROME_CMD="google-chrome-stable"
DISCORD_CMD="discord" # or "Discord" depending on your installation
OBSIDIAN_CMD="obsidian"
# Wait times (in seconds)
APP_LAUNCH_WAIT=2.0
VSCODE_LAUNCH_WAIT=3.0
WORKSPACE_SWITCH_WAIT=2.5
WINDOW_OPERATION_WAIT=0.3
# ======================================================
# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_debug() {
if [ "$DEBUG" = true ]; then
echo -e "${BLUE}[DEBUG]${NC} $1"
fi
}
# Execute niri command with optional debug output
niri_cmd() {
log_debug "Executing: niri msg $*"
niri msg "$@"
}
# Check required tools
check_dependencies() {
local missing_deps=()
# Check for niri
if ! command -v niri &> /dev/null; then
log_error "Niri window manager not found"
missing_deps+=("niri")
fi
# Check for niri msg (IPC command)
if ! niri msg version &> /dev/null; then
log_error "Cannot communicate with Niri (is it running?)"
exit 1
fi
for cmd in alacritty; do
if ! command -v "$cmd" &> /dev/null; then
missing_deps+=("$cmd")
fi
done
if [ ${#missing_deps[@]} -gt 0 ]; then
log_error "Missing required dependencies: ${missing_deps[*]}"
exit 1
fi
log_info "Niri detected and running ✓"
}
# Get monitor/output information from Niri
get_monitor_info() {
log_info "Detecting outputs from Niri..."
# Get output information from niri
local outputs=$(niri_cmd outputs 2>&1)
# Verify the configured outputs exist
if ! echo "$outputs" | grep -q "$PRIMARY_MONITOR"; then
log_warn "Primary monitor '$PRIMARY_MONITOR' not found in niri outputs"
log_info "Available outputs:"
echo "$outputs" | head -20
else
log_info "Primary Output: $PRIMARY_MONITOR ✓"
fi
if [ -n "$SECONDARY_MONITOR" ]; then
if ! echo "$outputs" | grep -q "$SECONDARY_MONITOR"; then
log_warn "Secondary monitor '$SECONDARY_MONITOR' not found in niri outputs"
log_info "Disabling secondary monitor setup"
SECONDARY_MONITOR=""
else
log_info "Secondary Output: $SECONDARY_MONITOR ✓"
fi
else
log_info "No secondary monitor configured"
fi
}
# Function to launch application
launch_app() {
local app_command="$1"
local app_name="$2"
log_info "Launching $app_name..."
log_debug "Command: $app_command"
eval "$app_command" &>/dev/null &
}
# Function to wait for a new window to appear
wait_for_window() {
local app_name="$1"
local max_wait="${2:-5}"
local wait_time=0
local initial_count=$(niri msg windows 2>/dev/null | wc -l)
log_debug "Waiting for $app_name window to appear (max ${max_wait}s)..."
while [ $wait_time -lt $((max_wait * 2)) ]; do
sleep 0.5
wait_time=$((wait_time + 1))
local current_count=$(niri msg windows 2>/dev/null | wc -l)
if [ $current_count -gt $initial_count ]; then
log_debug "Window appeared after ${wait_time}x0.5s"
# Add extra delay to ensure window is fully rendered and placed
log_debug "Waiting 0.5s for window to fully render..."
sleep 0.5
return 0
fi
done
log_debug "Window for $app_name didn't appear within ${max_wait}s, but continuing (app may reuse existing window)..."
# Always return success - some apps reuse windows
return 0
}
# Function to switch to workspace using Niri
switch_to_workspace() {
local workspace="$1"
local output="${2:-$PRIMARY_MONITOR}"
log_info "Switching to workspace $((workspace + 1)) on $output"
niri msg action focus-workspace "$((workspace + 1))"
sleep "$WINDOW_OPERATION_WAIT"
}
# Function to set window width using Niri
set_window_width() {
local width_percent="$1"
log_info "Setting focused window width to ${width_percent}%"
niri msg action set-column-width "${width_percent}%"
sleep "$WINDOW_OPERATION_WAIT"
}
# Function to move focused window to output
move_window_to_output() {
local output="$1"
if [ -n "$output" ]; then
log_info "Moving focused window to output: $output"
niri msg action move-column-to-output "$output"
sleep "$WINDOW_OPERATION_WAIT"
fi
}
# Function to move focused window to workspace
move_window_to_workspace() {
local workspace="$1"
log_info "Moving focused window to workspace $((workspace + 1))"
niri msg action move-column-to-workspace "$((workspace + 1))"
sleep "$WINDOW_OPERATION_WAIT"
}
# Main setup function for Niri
setup_workspaces() {
log_info "Starting workspace setup for Niri..."
# Get monitor information
get_monitor_info
# Focus primary monitor and go to workspace 1
log_info "Setting up on primary monitor..."
niri_cmd action focus-monitor "$PRIMARY_MONITOR"
log_debug "Sleeping 1.0s..."
sleep 1.0
# Assume user is already on workspace 1 or will manually switch there
log_info "Starting from current workspace (should be workspace 1)"
# === PRIMARY OUTPUT - WORKSPACE 1 ===
log_info "=== Workspace 1: Launching apps ==="
launch_app "gtk-launch $(basename $TODOIST_APP)" "Todoist"
wait_for_window "Todoist" 5
niri_cmd action set-column-width "50%"
sleep 1.0
launch_app "$CHROME_CMD" "Chrome"
wait_for_window "Chrome" 5
niri_cmd action set-column-width "75%"
sleep 1.0
launch_app "gtk-launch $(basename $CLAUDE_APP)" "Claude"
wait_for_window "Claude" 5
niri_cmd action set-column-width "75%"
sleep 1.0
# === PRIMARY OUTPUT - WORKSPACE 2 ===
log_info "=== Workspace 2: Switching and launching apps ==="
niri_cmd action focus-workspace 2
log_debug "Sleeping ${WORKSPACE_SWITCH_WAIT}s for workspace switch..."
sleep "$WORKSPACE_SWITCH_WAIT"
launch_app "code $PS2MONO_PATH" "VSCode ps2mono"
wait_for_window "VSCode" 8
niri_cmd action set-column-width "75%"
sleep 1.0
launch_app "$TERMINAL_CMD" "Alacritty"
wait_for_window "Alacritty" 5
niri_cmd action set-column-width "50%"
sleep 1.0
# === PRIMARY OUTPUT - WORKSPACE 3 ===
log_info "=== Workspace 3: Switching and launching apps ==="
niri_cmd action focus-workspace 3
log_debug "Sleeping ${WORKSPACE_SWITCH_WAIT}s for workspace switch..."
sleep "$WORKSPACE_SWITCH_WAIT"
launch_app "code $MYSQL_DOWNLOADER_PATH" "VSCode mysql-downloader"
wait_for_window "VSCode" 8
niri_cmd action set-column-width "75%"
sleep 1.0
# === PRIMARY OUTPUT - WORKSPACE 4 ===
log_info "=== Workspace 4: Switching and launching apps ==="
niri_cmd action focus-workspace 4
log_debug "Sleeping ${WORKSPACE_SWITCH_WAIT}s for workspace switch..."
sleep "$WORKSPACE_SWITCH_WAIT"
launch_app "code $FLATCAR_PATH" "VSCode flatcar"
wait_for_window "VSCode" 8
niri_cmd action set-column-width "75%"
sleep 1.0
# === PRIMARY OUTPUT - WORKSPACE 5 ===
log_info "=== Workspace 5: Switching and launching apps ==="
niri_cmd action focus-workspace 5
log_debug "Sleeping ${WORKSPACE_SWITCH_WAIT}s for workspace switch..."
sleep "$WORKSPACE_SWITCH_WAIT"
launch_app "$CHROME_CMD --new-window http://ps2immersion.com/" "PS2 Immersion"
wait_for_window "Chrome" 5
niri_cmd action set-column-width "35%"
sleep 1.0
# === SECONDARY OUTPUT - WORKSPACE 1 ===
if [ -n "$SECONDARY_MONITOR" ]; then
log_info "=== Secondary Monitor: Workspace 1 ==="
niri_cmd action focus-monitor "$SECONDARY_MONITOR"
log_debug "Sleeping 1.0s..."
sleep 1.0
log_info "Starting from current workspace on secondary monitor (should be workspace 1)"
launch_app "gtk-launch $(basename $BITWARDEN_APP)" "Bitwarden"
wait_for_window "Bitwarden" 5
niri_cmd action set-column-width "35%"
sleep 1.0
launch_app "$CHROME_CMD --new-window" "Chrome"
wait_for_window "Chrome" 5
niri_cmd action set-column-width "75%"
sleep 1.0
launch_app "$TERMINAL_CMD" "Alacritty"
wait_for_window "Alacritty" 5
niri_cmd action set-column-width "50%"
sleep 1.0
# === SECONDARY OUTPUT - WORKSPACE 2 ===
log_info "=== Secondary Monitor: Workspace 2 ==="
niri_cmd action focus-workspace 2
log_debug "Sleeping ${WORKSPACE_SWITCH_WAIT}s for workspace switch..."
sleep "$WORKSPACE_SWITCH_WAIT"
launch_app "$DISCORD_CMD" "Discord"
wait_for_window "Discord" 5
niri_cmd action set-column-width "60%"
sleep 1.0
launch_app "$OBSIDIAN_CMD $OBSIDIAN_VAULT" "Obsidian"
wait_for_window "Obsidian" 5
niri_cmd action set-column-width "60%"
sleep 1.0
fi
# Return to workspace 1 on primary output
log_info "Returning to primary monitor..."
niri_cmd action focus-monitor "$PRIMARY_MONITOR"
log_debug "Sleeping 1.0s..."
sleep 1.0
log_info "Workspace setup complete!"
log_info "Note: If apps are on wrong workspaces, try increasing wait times in the config"
}
# Main execution
main() {
check_dependencies
setup_workspaces
echo ""
log_info "All done! Your Niri workspaces are ready."
}
main "$@"