mirror of
https://github.com/Nexoniarz/nexoniarz-config-nixos.git
synced 2026-11-03 04:24:27 +00:00
Switch desktop environment from Hyprland to Budgie; app/audio overhaul
Hyprland problems prompted a full switch to Budgie (X11, lightdm). Removes every Hyprland/Quickshell dotfile, theming script, and hypr-specific module (theme.nix, the whole scripts/ theming pipeline, hyprland.conf/hyprpaper.conf, the Quickshell shell QML, kitty/mako/ rofi configs) in favor of Budgie's own panel/session/theming and a GSettings-driven icon/cursor override. App changes: - Konsole/Dolphin replace kitty/Thunar as the default terminal and file manager; dropped xterm too (services.xserver.excludePackages) - Added Okular (PDF/document viewer), KCalc (calculator), Elisa (audio player) -- nothing filled those roles before - Added OBS Studio Audio/voice: - EasyEffects for real-time mic/output effects (creates its own selectable "Easy Effects Source" virtual mic automatically) - A standalone "Virtual Microphone" PipeWire device (loopback module, services.pipewire.extraConfig), fully independent of the real mic, for routing EasyEffects/Soundux output separately from what Discord etc. use by default - qpwgraph for manual patchbay routing into the above - Soundux (soundboard) installed via Flatpak -- no longer packaged in this nixpkgs channel default-apps.nix: Okular wired into mimeapps.list alongside the existing Gwenview/Ark defaults; removed the Thunar-specific "Open Terminal Here" custom-action tmpfiles rule (dead now that Thunar's gone -- Dolphin gets the same via its built-in konsolepart terminal panel for free).
This commit is contained in:
parent
aa89dd729c
commit
d332ed2ef6
45 changed files with 105 additions and 7711 deletions
|
|
@ -7,8 +7,7 @@
|
||||||
./hardware-configuration.nix # Generated hardware scan results
|
./hardware-configuration.nix # Generated hardware scan results
|
||||||
./modules/system.nix # Bootloader, networking, time, sound, hardening, nix-ld
|
./modules/system.nix # Bootloader, networking, time, sound, hardening, nix-ld
|
||||||
./modules/drivers.nix # NVIDIA Open GPU kernel modules and graphics
|
./modules/drivers.nix # NVIDIA Open GPU kernel modules and graphics
|
||||||
./modules/desktop.nix # Hyprland compositor and desktop environment
|
./modules/desktop.nix # Budgie desktop environment
|
||||||
./modules/theme.nix # Shared color palette; GTK dark+squared theming
|
|
||||||
./modules/users.nix # User accounts and global configurations
|
./modules/users.nix # User accounts and global configurations
|
||||||
./modules/apps.nix # All installed packages: apps, CLI tools, dev toolchains
|
./modules/apps.nix # All installed packages: apps, CLI tools, dev toolchains
|
||||||
./modules/scripts.nix # Custom system scripts and helpers
|
./modules/scripts.nix # Custom system scripts and helpers
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
# Kitty terminal configuration
|
|
||||||
# Deployed to ~/.config/kitty/kitty.conf via systemd.tmpfiles in
|
|
||||||
# modules/desktop.nix.
|
|
||||||
|
|
||||||
# --- Shell: run ZSH directly, with shell integration ---
|
|
||||||
shell zsh
|
|
||||||
shell_integration enabled
|
|
||||||
|
|
||||||
# --- Font ---
|
|
||||||
font_family JetBrainsMono Nerd Font
|
|
||||||
bold_font auto
|
|
||||||
italic_font auto
|
|
||||||
bold_italic_font auto
|
|
||||||
font_size 11.0
|
|
||||||
|
|
||||||
# --- Squared, minimal window chrome ---
|
|
||||||
window_padding_width 8
|
|
||||||
window_margin_width 0
|
|
||||||
window_border_width 0
|
|
||||||
draw_minimal_borders yes
|
|
||||||
hide_window_decorations yes
|
|
||||||
confirm_os_window_close 0
|
|
||||||
|
|
||||||
# --- Tab bar: flat, square ---
|
|
||||||
tab_bar_edge top
|
|
||||||
tab_bar_style powerline
|
|
||||||
tab_powerline_style slanted
|
|
||||||
tab_bar_min_tabs 2
|
|
||||||
|
|
||||||
# --- Cursor ---
|
|
||||||
cursor_shape block
|
|
||||||
cursor_blink_interval 0
|
|
||||||
|
|
||||||
# --- Colors ---
|
|
||||||
# Base/accent colors (background, foreground, selection, borders, tabs)
|
|
||||||
# come from the Quickshell Theme picker (dotfiles/quickshell/Theme.qml),
|
|
||||||
# via kitty-theme-set — kitty has a built-in config-file watcher (see
|
|
||||||
# `kitten __watch_conf__` in its process list) that also watches included
|
|
||||||
# files, so this picks up changes live with no restart needed.
|
|
||||||
include theme.conf
|
|
||||||
|
|
||||||
# ANSI color0-15 palette (used for terminal output — ls, git diff, etc.)
|
|
||||||
# stays fixed regardless of accent/mode: these need to stay mutually
|
|
||||||
# distinguishable hues, not track a single accent color.
|
|
||||||
color0 #0d0d0d
|
|
||||||
color8 #444444
|
|
||||||
color1 #e06c75
|
|
||||||
color9 #e06c75
|
|
||||||
color2 #98c379
|
|
||||||
color10 #98c379
|
|
||||||
color3 #e5c07b
|
|
||||||
color11 #e5c07b
|
|
||||||
color4 #61afef
|
|
||||||
color12 #61afef
|
|
||||||
color5 #c678dd
|
|
||||||
color13 #c678dd
|
|
||||||
color6 #56b6c2
|
|
||||||
color14 #56b6c2
|
|
||||||
color7 #e0e0e0
|
|
||||||
color15 #ffffff
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Mako notification daemon configuration
|
|
||||||
# Deployed to ~/.config/mako/config via systemd.tmpfiles in
|
|
||||||
# modules/desktop.nix. Squared, sharp, no rounded corners.
|
|
||||||
#
|
|
||||||
# Anchored top-center to match the custom polkit dialog (PolkitDialog.qml)
|
|
||||||
# so both "system popup" surfaces show up in the same place.
|
|
||||||
|
|
||||||
background-color=#0d0d0dee
|
|
||||||
text-color=#e0e0e0ff
|
|
||||||
border-color=#333333ff
|
|
||||||
border-size=2
|
|
||||||
border-radius=0
|
|
||||||
padding=10
|
|
||||||
margin=12
|
|
||||||
width=420
|
|
||||||
font=JetBrainsMono Nerd Font 10
|
|
||||||
default-timeout=5000
|
|
||||||
max-visible=5
|
|
||||||
layer=overlay
|
|
||||||
anchor=top-center
|
|
||||||
|
|
||||||
[urgency=low]
|
|
||||||
border-color=#333333ff
|
|
||||||
|
|
||||||
[urgency=normal]
|
|
||||||
border-color=#8fbcbbff
|
|
||||||
|
|
||||||
[urgency=critical]
|
|
||||||
border-color=#e06c75ff
|
|
||||||
default-timeout=0
|
|
||||||
|
|
@ -1,447 +0,0 @@
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Hyprland
|
|
||||||
import Quickshell.Io
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Widgets
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: root
|
|
||||||
required property var screen
|
|
||||||
required property var leftBar
|
|
||||||
required property var rightBar
|
|
||||||
|
|
||||||
// Right-click context menu state — set by whichever taskbar window
|
|
||||||
// entry was right-clicked, cleared after an action or a second
|
|
||||||
// right-click on the same entry. Anchored via anchor.window + a
|
|
||||||
// one-shot computed x (not anchor.item!) — anchoring a popup directly
|
|
||||||
// to a Repeater delegate crashed Quickshell (PopupAnchor::setItem /
|
|
||||||
// onItemWindowChanged segfaults when the delegate it's watching gets
|
|
||||||
// destroyed/recreated, which happens often since the window list
|
|
||||||
// rebuilds its model on every toplevel change). root itself is a
|
|
||||||
// stable, never-recreated window, so anchoring to it is safe — same
|
|
||||||
// pattern the Left/RightBar flyouts already use successfully.
|
|
||||||
property var ctxMenuToplevel: null
|
|
||||||
property real ctxMenuX: 0
|
|
||||||
function closeCtxMenu() {
|
|
||||||
root.ctxMenuToplevel = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors { left: true; right: true; bottom: true }
|
|
||||||
implicitHeight: 46
|
|
||||||
exclusiveZone: 46
|
|
||||||
color: Theme.bg
|
|
||||||
focusable: false
|
|
||||||
|
|
||||||
// Special workspaces (e.g. "special:showdesktop", used by the
|
|
||||||
// Super+D show-desktop toggle) are an implementation detail, not a
|
|
||||||
// real switchable workspace — Hyprland keeps their metadata around
|
|
||||||
// even once empty, so they'd otherwise show up here forever.
|
|
||||||
property var visibleWorkspaces: {
|
|
||||||
var out = [];
|
|
||||||
var all = Hyprland.workspaces.values;
|
|
||||||
for (var i = 0; i < all.length; i++) {
|
|
||||||
if (all[i].name.indexOf("special:") !== 0) out.push(all[i]);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only workspaces that actually exist (have windows, or are/were
|
|
||||||
// focused) get a pill — showing all 10 possible slots took up too
|
|
||||||
// much space. Always shown in numeric order (1, 2, 3, ...) regardless
|
|
||||||
// of creation/focus order, matching the Super+1..9,0 keybinds.
|
|
||||||
property var workspacePills: {
|
|
||||||
var out = [];
|
|
||||||
for (var i = 0; i < root.visibleWorkspaces.length; i++) {
|
|
||||||
var w = root.visibleWorkspaces[i];
|
|
||||||
out.push({ number: w.id, ws: w });
|
|
||||||
}
|
|
||||||
out.sort(function (a, b) { return a.number - b.number; });
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Window list ordered by workspace number (1, then 2, ...) instead of
|
|
||||||
// whatever arbitrary order the compositor reports toplevels in.
|
|
||||||
//
|
|
||||||
// The base list comes from ToplevelManager (the generic Wayland
|
|
||||||
// wlr-foreign-toplevel-management module), NOT Hyprland.toplevels —
|
|
||||||
// Hyprland.toplevels is built by parsing Hyprland's own IPC event
|
|
||||||
// stream, and apps that open/close many short-lived helper windows
|
|
||||||
// fast (Steam is the textbook case: steamwebhelper, dummy GL context
|
|
||||||
// windows, etc.) can desync it from reality, leaving phantom entries
|
|
||||||
// that `hyprctl clients` doesn't even show. ToplevelManager reflects
|
|
||||||
// live protocol state directly, so it can't go stale like that.
|
|
||||||
// Hyprland.toplevels is only used as a lookup to find each window's
|
|
||||||
// workspace id for sorting — a lookup miss just sorts that window last,
|
|
||||||
// it can't add a window that isn't really there.
|
|
||||||
property var orderedToplevels: {
|
|
||||||
var genList = ToplevelManager.toplevels.values;
|
|
||||||
var hyprList = Hyprland.toplevels.values;
|
|
||||||
var out = [];
|
|
||||||
for (var i = 0; i < genList.length; i++) {
|
|
||||||
var gt = genList[i];
|
|
||||||
var wsId = 999999;
|
|
||||||
for (var j = 0; j < hyprList.length; j++) {
|
|
||||||
if (hyprList[j].wayland === gt) {
|
|
||||||
wsId = (hyprList[j].workspace && hyprList[j].workspace.id > 0) ? hyprList[j].workspace.id : 999999;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.push({ tl: gt, wsId: wsId });
|
|
||||||
}
|
|
||||||
out.sort(function (a, b) { return a.wsId - b.wsId; });
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle { anchors.top: parent.top; width: parent.width; height: 1; color: Theme.border }
|
|
||||||
|
|
||||||
// --- Left bar toggle ---------------------------------------------
|
|
||||||
Rectangle {
|
|
||||||
id: leftToggle
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 8
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: 32
|
|
||||||
height: 32
|
|
||||||
color: ltMa.containsMouse ? Theme.bgAlt : "transparent"
|
|
||||||
border.width: root.leftBar.expanded ? 2 : 1
|
|
||||||
border.color: root.leftBar.expanded ? Theme.accent : Theme.border
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: root.leftBar.expanded ? "<" : ">"
|
|
||||||
color: root.leftBar.expanded ? Theme.accent : Theme.fg
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
MouseArea {
|
|
||||||
id: ltMa
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
root.leftBar.expanded = !root.leftBar.expanded;
|
|
||||||
if (!root.leftBar.expanded) root.leftBar.activePanel = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: leftToggle.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.leftMargin: 8
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: root.workspacePills
|
|
||||||
delegate: Rectangle {
|
|
||||||
property var ws: modelData.ws
|
|
||||||
property int number: modelData.number
|
|
||||||
width: 32
|
|
||||||
height: 28
|
|
||||||
color: ws.focused ? Theme.accent : (wsMa.containsMouse ? Theme.bgAlt : "transparent")
|
|
||||||
border.width: 1
|
|
||||||
border.color: ws.urgent ? Theme.danger : Theme.border
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: number
|
|
||||||
color: ws.focused ? Theme.bg : Theme.fg
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 12
|
|
||||||
}
|
|
||||||
MouseArea {
|
|
||||||
id: wsMa
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: ws.activate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 8 + 32 + 8 + (root.workspacePills.length * 36) + 4
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: 6
|
|
||||||
width: 1
|
|
||||||
height: 28
|
|
||||||
color: Theme.border
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Weather badge ---------------------------------------------------
|
|
||||||
// Independent of RightFlyout's own weatherPanel (which only exists
|
|
||||||
// while the Loader has it active) — this needs to keep showing a
|
|
||||||
// temperature even when the panel's closed, so it fetches on its own
|
|
||||||
// timer rather than reading state from a panel instance that mostly
|
|
||||||
// doesn't exist. Reads the same city override (if any) so the badge
|
|
||||||
// and the full panel never disagree about which location they're
|
|
||||||
// showing.
|
|
||||||
Item {
|
|
||||||
id: wxBadgeState
|
|
||||||
property var data: ({})
|
|
||||||
property bool haveData: !!data.current
|
|
||||||
property string city: ""
|
|
||||||
|
|
||||||
function describeIcon(code) {
|
|
||||||
if (code === 0) return "☀";
|
|
||||||
if (code === 1) return "🌤";
|
|
||||||
if (code === 2) return "⛅";
|
|
||||||
if (code === 3) return "☁";
|
|
||||||
if (code === 45 || code === 48) return "🌫";
|
|
||||||
if (code >= 51 && code <= 57) return "🌦";
|
|
||||||
if (code >= 61 && code <= 67) return "🌧";
|
|
||||||
if (code >= 71 && code <= 77) return "❄";
|
|
||||||
if (code >= 80 && code <= 82) return "🌧";
|
|
||||||
if (code === 85 || code === 86) return "🌨";
|
|
||||||
if (code >= 95) return "⛈";
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: wxBadgeCityLoadProc
|
|
||||||
command: ["cat", Quickshell.stateDir + "/weather-settings.json"]
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
try {
|
|
||||||
var d = JSON.parse(text);
|
|
||||||
if (d.city) wxBadgeState.city = d.city;
|
|
||||||
} catch (e) { /* no override saved — auto-detect stands */ }
|
|
||||||
wxBadgeFetchProc.command = wxBadgeState.city.length > 0
|
|
||||||
? ["weather-fetch", wxBadgeState.city] : ["weather-fetch"];
|
|
||||||
wxBadgeFetchProc.running = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Process {
|
|
||||||
id: wxBadgeFetchProc
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
try { wxBadgeState.data = JSON.parse(text); } catch (e) { /* keep last good reading */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Timer {
|
|
||||||
interval: 900000
|
|
||||||
running: true
|
|
||||||
repeat: true
|
|
||||||
onTriggered: wxBadgeCityLoadProc.running = true
|
|
||||||
}
|
|
||||||
Component.onCompleted: wxBadgeCityLoadProc.running = true
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: weatherBadge
|
|
||||||
visible: wxBadgeState.haveData
|
|
||||||
anchors.right: rightToggle.left
|
|
||||||
anchors.rightMargin: 8
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: wxBadgeLabel.implicitWidth + 16
|
|
||||||
height: 32
|
|
||||||
color: wxBadgeMa.containsMouse ? Theme.bgAlt : "transparent"
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.border
|
|
||||||
Text {
|
|
||||||
id: wxBadgeLabel
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: wxBadgeState.haveData
|
|
||||||
? (wxBadgeState.describeIcon(wxBadgeState.data.current.weather_code) + " " + Math.round(wxBadgeState.data.current.temperature_2m) + "°C")
|
|
||||||
: ""
|
|
||||||
color: Theme.fg
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 12
|
|
||||||
}
|
|
||||||
MouseArea {
|
|
||||||
id: wxBadgeMa
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
root.rightBar.expanded = true;
|
|
||||||
root.rightBar.toggle("weather");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Right bar toggle ---------------------------------------------
|
|
||||||
Rectangle {
|
|
||||||
id: rightToggle
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 8
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: 32
|
|
||||||
height: 32
|
|
||||||
color: rtMa.containsMouse ? Theme.bgAlt : "transparent"
|
|
||||||
border.width: root.rightBar.expanded ? 2 : 1
|
|
||||||
border.color: root.rightBar.expanded ? Theme.accent : Theme.border
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: root.rightBar.expanded ? ">" : "<"
|
|
||||||
color: root.rightBar.expanded ? Theme.accent : Theme.fg
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
MouseArea {
|
|
||||||
id: rtMa
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
root.rightBar.expanded = !root.rightBar.expanded;
|
|
||||||
if (!root.rightBar.expanded) root.rightBar.activePanel = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 8 + 32 + 8 + (root.workspacePills.length * 36) + 16
|
|
||||||
anchors.right: weatherBadge.left
|
|
||||||
anchors.rightMargin: 8
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 4
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: root.orderedToplevels
|
|
||||||
delegate: Rectangle {
|
|
||||||
id: tlDelegate
|
|
||||||
property var tl: modelData.tl
|
|
||||||
// Resolved via the app's .desktop entry (matched by Wayland
|
|
||||||
// app-id) rather than trusting a raw icon name/path from
|
|
||||||
// the toplevel itself, which isn't exposed by the protocol.
|
|
||||||
property string iconSrc: {
|
|
||||||
if (!tl.appId) return "";
|
|
||||||
var entry = DesktopEntries.heuristicLookup(tl.appId);
|
|
||||||
if (!entry || !entry.icon) return "";
|
|
||||||
return Quickshell.iconPath(entry.icon, "");
|
|
||||||
}
|
|
||||||
width: Math.min(220, label.implicitWidth + (iconSrc !== "" ? 46 : 24))
|
|
||||||
height: 28
|
|
||||||
color: tl.activated ? Theme.accent : (tlMa.containsMouse ? Theme.bgAlt : Theme.bgAlt)
|
|
||||||
border.width: 1
|
|
||||||
border.color: root.ctxMenuToplevel === tl ? Theme.accent : Theme.border
|
|
||||||
|
|
||||||
IconImage {
|
|
||||||
id: appIcon
|
|
||||||
visible: tlDelegate.iconSrc !== ""
|
|
||||||
implicitSize: 16
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 8
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
source: tlDelegate.iconSrc
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
id: label
|
|
||||||
anchors.left: appIcon.visible ? appIcon.right : parent.left
|
|
||||||
anchors.leftMargin: appIcon.visible ? 6 : 8
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 8
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: tl.title || tl.appId || "?"
|
|
||||||
color: tl.activated ? Theme.bg : Theme.fg
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 12
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
MouseArea {
|
|
||||||
id: tlMa
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
|
||||||
onClicked: (mouse) => {
|
|
||||||
if (mouse.button === Qt.RightButton) {
|
|
||||||
if (root.ctxMenuToplevel === tl) { root.closeCtxMenu(); return; }
|
|
||||||
var pos = tlDelegate.mapToItem(null, 0, 0);
|
|
||||||
root.ctxMenuX = pos.x;
|
|
||||||
root.ctxMenuToplevel = tl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mouse.button === Qt.MiddleButton) tl.close();
|
|
||||||
else tl.activate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Taskbar entry context menu ------------------------------------
|
|
||||||
// Window control options exposed by the wlr-foreign-toplevel-management
|
|
||||||
// protocol (fullscreen/maximized are writable properties on any
|
|
||||||
// toplevel — there's no per-app capability flag to check, so these are
|
|
||||||
// always offered as a best-effort "if the app honors it" set).
|
|
||||||
// Loaded lazily (not instantiated until first opened) so the popup
|
|
||||||
// and its anchor only exist while actually needed.
|
|
||||||
Loader {
|
|
||||||
active: root.ctxMenuToplevel !== null
|
|
||||||
sourceComponent: PopupWindow {
|
|
||||||
id: ctxMenu
|
|
||||||
visible: true
|
|
||||||
anchor.window: root
|
|
||||||
anchor.rect.x: root.ctxMenuX
|
|
||||||
anchor.rect.y: 0
|
|
||||||
anchor.edges: Edges.Top
|
|
||||||
anchor.gravity: Edges.Top
|
|
||||||
anchor.margins.bottom: 4
|
|
||||||
implicitWidth: 150
|
|
||||||
implicitHeight: ctxCol.implicitHeight + 12
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: Theme.bg
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.border
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: ctxCol
|
|
||||||
x: 6
|
|
||||||
y: 6
|
|
||||||
width: parent.width - 12
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: {
|
|
||||||
var tl = root.ctxMenuToplevel;
|
|
||||||
if (!tl) return [];
|
|
||||||
return [
|
|
||||||
{ label: tl.fullscreen ? "Exit Fullscreen" : "Fullscreen", action: "fullscreen", danger: false },
|
|
||||||
{ label: tl.maximized ? "Restore" : "Maximize", action: "maximize", danger: false },
|
|
||||||
{ label: "Close", action: "close", danger: true }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
delegate: Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 26
|
|
||||||
color: itemMa.containsMouse ? Theme.bgAlt : "transparent"
|
|
||||||
Text {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 8
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: modelData.label
|
|
||||||
color: modelData.danger ? Theme.danger : Theme.fg
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 12
|
|
||||||
}
|
|
||||||
MouseArea {
|
|
||||||
id: itemMa
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: {
|
|
||||||
var tl = root.ctxMenuToplevel;
|
|
||||||
if (tl) {
|
|
||||||
if (modelData.action === "fullscreen") tl.fullscreen = !tl.fullscreen;
|
|
||||||
else if (modelData.action === "maximize") tl.maximized = !tl.maximized;
|
|
||||||
else if (modelData.action === "close") tl.close();
|
|
||||||
}
|
|
||||||
root.closeCtxMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
// Small close button using a plain "x" glyph, not a nerd-font icon — the
|
|
||||||
// left-bar icons taught us those can silently fail to render.
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
signal clicked()
|
|
||||||
|
|
||||||
width: 24
|
|
||||||
height: 24
|
|
||||||
color: mouseArea.containsMouse ? Theme.borderDim : "transparent"
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.border
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "x"
|
|
||||||
color: Theme.fgDim2
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root.clicked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
// Small "x" corner button for deleting a grid tile (wallpapers) — only
|
|
||||||
// meant to be shown while the tile itself is hovered, matching the
|
|
||||||
// hover-to-reveal pattern used for clearing a single notification.
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
signal clicked()
|
|
||||||
|
|
||||||
width: 18
|
|
||||||
height: 18
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.margins: 4
|
|
||||||
z: 1
|
|
||||||
color: mouseArea.containsMouse ? Theme.danger : Theme.bg
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.danger
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "×"
|
|
||||||
color: mouseArea.containsMouse ? Theme.bg : Theme.danger
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root.clicked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
|
|
||||||
// 24h time + "day monthname year" date, using the system locale (pl_PL) for
|
|
||||||
// the month name — matches how Poland actually writes dates.
|
|
||||||
Column {
|
|
||||||
id: root
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
SystemClock {
|
|
||||||
id: clock
|
|
||||||
precision: SystemClock.Minutes
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
text: Qt.formatTime(clock.date, "HH:mm")
|
|
||||||
color: Theme.fg
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 20
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
text: Qt.formatDate(clock.date, "d MMMM yyyy")
|
|
||||||
color: Theme.fgDim
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 11
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
property string glyph: ""
|
|
||||||
property bool active: false
|
|
||||||
property color fg: Theme.fg
|
|
||||||
property color accent: Theme.accent
|
|
||||||
signal clicked()
|
|
||||||
|
|
||||||
width: 48
|
|
||||||
height: 48
|
|
||||||
color: mouseArea.containsMouse ? Theme.bgAlt : "transparent"
|
|
||||||
border.width: active ? 2 : 1
|
|
||||||
border.color: active ? accent : Theme.borderDim
|
|
||||||
radius: 0
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: root.glyph
|
|
||||||
color: root.active ? root.accent : root.fg
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 21
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root.clicked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,162 +0,0 @@
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Io
|
|
||||||
import Quickshell.Wayland
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: root
|
|
||||||
required property var screen
|
|
||||||
property string activePanel: ""
|
|
||||||
property bool expanded: false
|
|
||||||
|
|
||||||
anchors { top: true; left: true; bottom: true }
|
|
||||||
// Collapsed = zero reserved space; expand/collapse is triggered from
|
|
||||||
// the taskbar (BottomBar), not a button on this bar itself.
|
|
||||||
implicitWidth: expanded ? 220 : 0
|
|
||||||
exclusiveZone: expanded ? 220 : 0
|
|
||||||
color: Theme.bg
|
|
||||||
focusable: false
|
|
||||||
|
|
||||||
Process { id: launcher; command: [] }
|
|
||||||
function launch(cmd) { launcher.command = cmd; launcher.running = true; }
|
|
||||||
|
|
||||||
// Only "booru" needs the popup's keyboard grab (it's the only panel
|
|
||||||
// with a text field).
|
|
||||||
function needsGrab(panel) { return panel === "booru"; }
|
|
||||||
|
|
||||||
function toggle(panel) {
|
|
||||||
if (root.activePanel === panel) {
|
|
||||||
root.activePanel = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (root.activePanel !== "" && root.needsGrab(root.activePanel) !== root.needsGrab(panel)) {
|
|
||||||
// Switching directly between a grabbing and a non-grabbing
|
|
||||||
// panel without closing the popup first doesn't work — an
|
|
||||||
// xdg_popup's grab can only be established when the surface
|
|
||||||
// is first created, not toggled on one that's already
|
|
||||||
// mapped. Force a close, then reopen on the next tick so the
|
|
||||||
// popup actually gets torn down and recreated with the right
|
|
||||||
// grab state.
|
|
||||||
root.activePanel = "";
|
|
||||||
Qt.callLater(function () { root.activePanel = panel; });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
root.activePanel = panel;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolves the real, locale-correct Pictures folder at click time
|
|
||||||
// (e.g. ~/Obrazy on pl_PL) instead of assuming the English name.
|
|
||||||
Process {
|
|
||||||
id: picturesLauncher
|
|
||||||
command: ["sh", "-c", "thunar \"$(xdg-user-dir PICTURES)\""]
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
visible: root.expanded
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.topMargin: 14
|
|
||||||
spacing: 14
|
|
||||||
width: parent.width - 24
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "SHORTCUTS"
|
|
||||||
color: Theme.fgDim
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 9
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: "Dotfiles / NixOS"
|
|
||||||
onClicked: root.launch(["thunar", "/etc/nixos"])
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: "Pictures"
|
|
||||||
onClicked: picturesLauncher.running = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "APPEARANCE"
|
|
||||||
color: Theme.fgDim
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 9
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: "Theme"
|
|
||||||
active: root.activePanel === "theme"
|
|
||||||
onClicked: root.toggle("theme")
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: "Display Settings"
|
|
||||||
active: root.activePanel === "settings"
|
|
||||||
onClicked: root.toggle("settings")
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: "Wallpapers"
|
|
||||||
active: root.activePanel === "wallpapers"
|
|
||||||
onClicked: root.toggle("wallpapers")
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: "Booru"
|
|
||||||
active: root.activePanel === "booru"
|
|
||||||
onClicked: root.toggle("booru")
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: "Cursor"
|
|
||||||
active: root.activePanel === "cursor"
|
|
||||||
onClicked: root.toggle("cursor")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: flyout
|
|
||||||
screen: root.screen
|
|
||||||
anchors { top: true; left: true }
|
|
||||||
// No margin here on purpose: Hyprland already insets layer-shell
|
|
||||||
// surfaces past other surfaces' exclusive zones on the same edge
|
|
||||||
// (confirmed via `hyprctl layers` — BottomBar gets the same
|
|
||||||
// treatment for free, sitting at x=220 with zero margin of its
|
|
||||||
// own). A margin of 220 here was stacking on top of that
|
|
||||||
// automatic inset, pushing the flyout 220px further right than
|
|
||||||
// intended and leaving a ~220px gap between it and the sidebar.
|
|
||||||
visible: root.activePanel !== ""
|
|
||||||
implicitWidth: 320
|
|
||||||
implicitHeight: flyoutContent.implicitHeight
|
|
||||||
exclusiveZone: 0
|
|
||||||
color: "transparent"
|
|
||||||
WlrLayershell.layer: WlrLayer.Overlay
|
|
||||||
// On-demand layer-shell focus, not an xdg_popup grab: this used to
|
|
||||||
// be a PopupWindow with grabFocus, but an xdg_popup grab gets
|
|
||||||
// dismissed by the compositor the instant anything else so much as
|
|
||||||
// touches focus/surface state elsewhere — mapping mpvpaper's
|
|
||||||
// layer-shell surface (or even hyprpaper's own "wallpaper" IPC call
|
|
||||||
// recreating its surface) was enough to trigger that, silently
|
|
||||||
// closing the whole flyout AND dropping keyboard input, which is
|
|
||||||
// what let keystrokes fall through to whatever window was focused
|
|
||||||
// before the flyout opened instead of reaching the text field.
|
|
||||||
// OnDemand only claims focus when something inside is actually
|
|
||||||
// clicked, and isn't torn down by unrelated surfaces. Panels with
|
|
||||||
// no text input don't need it at all.
|
|
||||||
WlrLayershell.keyboardFocus: root.needsGrab(root.activePanel)
|
|
||||||
? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
|
||||||
|
|
||||||
LeftFlyout {
|
|
||||||
id: flyoutContent
|
|
||||||
anchors.fill: parent
|
|
||||||
screen: root.screen
|
|
||||||
activePanel: root.activePanel
|
|
||||||
onRequestClose: root.activePanel = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,26 +0,0 @@
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
// Read-only horizontal usage bar — CPU/RAM/GPU/VRAM/disk meters in the
|
|
||||||
// System Monitor panel all need the same "filled fraction of a track"
|
|
||||||
// visual, just with a different value/color each time.
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
property real value: 0 // 0..1
|
|
||||||
property color barColor: Theme.accent
|
|
||||||
implicitHeight: 8
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: Theme.bgAlt
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.borderDim
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.margins: 1
|
|
||||||
width: Math.max(0, (parent.width - 2) * Math.min(1, Math.max(0, root.value)))
|
|
||||||
color: root.barColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,199 +0,0 @@
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Io
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Services.Pipewire
|
|
||||||
|
|
||||||
// On-screen display: a themed top-center popup (same placement/layer-shell
|
|
||||||
// pattern as PolkitDialog and mako notifications) for volume, mic mute,
|
|
||||||
// brightness, and media changes — the transient overlay every other DE
|
|
||||||
// shows instead of making you open a panel to check.
|
|
||||||
//
|
|
||||||
// Volume/mic react directly to Pipewire, so they fire for ANY change
|
|
||||||
// (function keys, the RightBar sliders, pavucontrol, etc.), not just ones
|
|
||||||
// that went through our own keybinds. Brightness has no such live service
|
|
||||||
// to bind to (DDC/CI writes aren't broadcast anywhere), so its two
|
|
||||||
// hyprland.conf binds explicitly call `quickshell ipc call osd
|
|
||||||
// brightness <pct>` after brightness-ctl. Media is driven by two
|
|
||||||
// long-running `playerctl --follow` processes (status, and metadata for
|
|
||||||
// track-change detection).
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property string osdText: ""
|
|
||||||
property bool showBar: false
|
|
||||||
property real barValue: 0
|
|
||||||
property bool barDanger: false
|
|
||||||
|
|
||||||
function present(text, bar, value, danger) {
|
|
||||||
root.osdText = text;
|
|
||||||
root.showBar = bar;
|
|
||||||
root.barValue = value || 0;
|
|
||||||
root.barDanger = !!danger;
|
|
||||||
osdWindow.visible = true;
|
|
||||||
hideTimer.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
IpcHandler {
|
|
||||||
target: "osd"
|
|
||||||
function brightness(pct: string): void {
|
|
||||||
var v = parseInt(pct);
|
|
||||||
if (isNaN(v)) return;
|
|
||||||
root.present("Brightness " + v + "%", true, v / 100, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: hideTimer
|
|
||||||
interval: 1600
|
|
||||||
onTriggered: osdWindow.visible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Volume / mic --
|
|
||||||
PwObjectTracker {
|
|
||||||
objects: {
|
|
||||||
var arr = [];
|
|
||||||
if (Pipewire.defaultAudioSink) arr.push(Pipewire.defaultAudioSink);
|
|
||||||
if (Pipewire.defaultAudioSource) arr.push(Pipewire.defaultAudioSource);
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pipewire reports the already-current volume/mute state once as soon
|
|
||||||
// as it connects — without these guards that fires an OSD on every
|
|
||||||
// Quickshell start/login, not just on real changes.
|
|
||||||
property bool sinkReady: false
|
|
||||||
property bool sourceReady: false
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: Pipewire.defaultAudioSink ? Pipewire.defaultAudioSink.audio : null
|
|
||||||
function onVolumesChanged() {
|
|
||||||
if (!root.sinkReady) { root.sinkReady = true; return; }
|
|
||||||
var sink = Pipewire.defaultAudioSink;
|
|
||||||
if (sink.audio.muted) return; // covered by onMutedChanged instead
|
|
||||||
root.present("Volume " + Math.round(sink.audio.volume * 100) + "%", true, sink.audio.volume, false);
|
|
||||||
}
|
|
||||||
function onMutedChanged() {
|
|
||||||
if (!root.sinkReady) { root.sinkReady = true; return; }
|
|
||||||
var sink = Pipewire.defaultAudioSink;
|
|
||||||
root.present(sink.audio.muted ? "Volume Muted" : "Volume " + Math.round(sink.audio.volume * 100) + "%",
|
|
||||||
!sink.audio.muted, sink.audio.volume, sink.audio.muted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Connections {
|
|
||||||
target: Pipewire.defaultAudioSource ? Pipewire.defaultAudioSource.audio : null
|
|
||||||
function onMutedChanged() {
|
|
||||||
if (!root.sourceReady) { root.sourceReady = true; return; }
|
|
||||||
var source = Pipewire.defaultAudioSource;
|
|
||||||
root.present(source.audio.muted ? "Microphone Muted" : "Microphone Unmuted",
|
|
||||||
false, 0, source.audio.muted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Media (MPRIS via playerctl) --
|
|
||||||
property string lastTitle: ""
|
|
||||||
property double lastMetaAt: 0
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: statusFollow
|
|
||||||
command: ["playerctl", "--follow", "status"]
|
|
||||||
stdout: SplitParser {
|
|
||||||
splitMarker: "\n"
|
|
||||||
onRead: (data) => {
|
|
||||||
var status = data.trim();
|
|
||||||
if (status === "Paused") {
|
|
||||||
root.present("⏸ Paused", false, 0, false);
|
|
||||||
} else if (status === "Stopped") {
|
|
||||||
root.present("⏹ Stopped", false, 0, false);
|
|
||||||
root.lastTitle = "";
|
|
||||||
} else if (status === "Playing") {
|
|
||||||
// A track change fires both a metadata line and a
|
|
||||||
// Playing status line in short order — the metadata
|
|
||||||
// handler below already popped the OSD for that case,
|
|
||||||
// so only announce "Resumed" if nothing else just did.
|
|
||||||
if (Date.now() - root.lastMetaAt > 400) {
|
|
||||||
root.present("▶ Resumed", false, 0, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: metadataFollow
|
|
||||||
command: ["playerctl", "--follow", "metadata", "--format", "{{title}}\t{{artist}}"]
|
|
||||||
stdout: SplitParser {
|
|
||||||
splitMarker: "\n"
|
|
||||||
onRead: (data) => {
|
|
||||||
var parts = data.split("\t");
|
|
||||||
var title = (parts[0] || "").trim();
|
|
||||||
var artist = (parts[1] || "").trim();
|
|
||||||
if (!title || title === root.lastTitle) return;
|
|
||||||
root.lastTitle = title;
|
|
||||||
root.lastMetaAt = Date.now();
|
|
||||||
root.present("▶ " + title + (artist ? " — " + artist : ""), false, 0, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
statusFollow.running = true;
|
|
||||||
metadataFollow.running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: osdWindow
|
|
||||||
visible: false
|
|
||||||
WlrLayershell.layer: WlrLayer.Overlay
|
|
||||||
WlrLayershell.namespace: "osd"
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
||||||
focusable: false
|
|
||||||
anchors { top: true }
|
|
||||||
margins.top: 12
|
|
||||||
exclusiveZone: 0
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
implicitWidth: 300
|
|
||||||
implicitHeight: osdContent.implicitHeight + 24
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: Theme.bg
|
|
||||||
border.width: 2
|
|
||||||
border.color: root.barDanger ? Theme.danger : Theme.accent
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: osdContent
|
|
||||||
x: 14
|
|
||||||
y: 12
|
|
||||||
width: parent.width - 28
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Text {
|
|
||||||
width: parent.width
|
|
||||||
text: root.osdText
|
|
||||||
color: Theme.fg
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.bold: true
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: root.showBar
|
|
||||||
width: parent.width
|
|
||||||
height: 6
|
|
||||||
color: Theme.bgAlt
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.border
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width * Math.max(0, Math.min(1, root.barValue))
|
|
||||||
height: parent.height
|
|
||||||
color: root.barDanger ? Theme.danger : Theme.accent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,176 +0,0 @@
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Services.Polkit
|
|
||||||
|
|
||||||
// Custom-themed polkit authentication agent, replacing hyprpolkitagent's
|
|
||||||
// default (unthemed, light) dialog. PolkitAgent/AuthFlow handle the actual
|
|
||||||
// D-Bus/PAM plumbing internally — this file is purely the UI around it.
|
|
||||||
//
|
|
||||||
// Rendered as a slim layer-shell bar anchored to the top-center of the
|
|
||||||
// screen (like mako's notification popups) rather than a normal toplevel
|
|
||||||
// window — this sidesteps Hyprland window placement entirely, so there's
|
|
||||||
// no need for float/center window rules.
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
PolkitAgent {
|
|
||||||
id: agent
|
|
||||||
}
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: dialogWindow
|
|
||||||
visible: agent.flow !== null
|
|
||||||
WlrLayershell.layer: WlrLayer.Overlay
|
|
||||||
WlrLayershell.namespace: "polkit-dialog"
|
|
||||||
|
|
||||||
anchors { top: true }
|
|
||||||
margins.top: 12
|
|
||||||
exclusiveZone: 0
|
|
||||||
focusable: visible
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
implicitWidth: 420
|
|
||||||
implicitHeight: content.implicitHeight + 24
|
|
||||||
|
|
||||||
onVisibleChanged: if (visible) responseField.forceActiveFocus()
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: Theme.bg
|
|
||||||
border.width: 2
|
|
||||||
border.color: Theme.accent
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: content
|
|
||||||
x: 12
|
|
||||||
y: 12
|
|
||||||
width: parent.width - 24
|
|
||||||
spacing: 10
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 4
|
|
||||||
height: infoCol.implicitHeight
|
|
||||||
color: Theme.accent
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: infoCol
|
|
||||||
width: content.width - 4 - 10 - actionRow.width - 10
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: agent.flow ? agent.flow.message : ""
|
|
||||||
color: Theme.fg
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.bold: true
|
|
||||||
elide: Text.ElideRight
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
visible: !!(agent.flow && agent.flow.supplementaryMessage)
|
|
||||||
text: agent.flow ? agent.flow.supplementaryMessage : ""
|
|
||||||
color: (agent.flow && agent.flow.supplementaryIsError) ? Theme.danger : Theme.accent
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 10
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: !!(agent.flow && agent.flow.isResponseRequired)
|
|
||||||
width: parent.width
|
|
||||||
height: 26
|
|
||||||
color: Theme.bgAlt
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.border
|
|
||||||
|
|
||||||
Text {
|
|
||||||
visible: responseField.text.length === 0
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
x: 8
|
|
||||||
text: agent.flow ? agent.flow.inputPrompt : ""
|
|
||||||
color: Theme.fgDim
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 11
|
|
||||||
}
|
|
||||||
|
|
||||||
TextInput {
|
|
||||||
id: responseField
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 6
|
|
||||||
echoMode: (agent.flow && agent.flow.responseVisible) ? TextInput.Normal : TextInput.Password
|
|
||||||
color: Theme.fg
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 12
|
|
||||||
focus: true
|
|
||||||
|
|
||||||
Keys.onReturnPressed: submitResponse()
|
|
||||||
Keys.onEnterPressed: submitResponse()
|
|
||||||
Keys.onEscapePressed: if (agent.flow) agent.flow.cancelAuthenticationRequest()
|
|
||||||
|
|
||||||
function submitResponse() {
|
|
||||||
if (agent.flow) {
|
|
||||||
agent.flow.submit(text);
|
|
||||||
text = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: actionRow
|
|
||||||
anchors.verticalCenter: infoCol.verticalCenter
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 90
|
|
||||||
height: 26
|
|
||||||
color: authMa.containsMouse ? Theme.bgAlt : "transparent"
|
|
||||||
border.width: 2
|
|
||||||
border.color: Theme.accent
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "Authenticate"
|
|
||||||
color: Theme.accent
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 10
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
MouseArea {
|
|
||||||
id: authMa
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: responseField.submitResponse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
width: 70
|
|
||||||
height: 26
|
|
||||||
color: cancelMa.containsMouse ? Theme.bgAlt : "transparent"
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.border
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "Cancel"
|
|
||||||
color: Theme.fg
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 10
|
|
||||||
}
|
|
||||||
MouseArea {
|
|
||||||
id: cancelMa
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: if (agent.flow) agent.flow.cancelAuthenticationRequest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,240 +0,0 @@
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Services.Pipewire
|
|
||||||
import Quickshell.Services.SystemTray
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import Quickshell.Wayland
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: root
|
|
||||||
required property var screen
|
|
||||||
property string activePanel: ""
|
|
||||||
property bool expanded: false
|
|
||||||
|
|
||||||
// Keeps the default sink's PwNode subscribed so audio properties
|
|
||||||
// (volume/muted) actually receive live updates.
|
|
||||||
PwObjectTracker {
|
|
||||||
objects: Pipewire.defaultAudioSink ? [Pipewire.defaultAudioSink] : []
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors { top: true; right: true; bottom: true }
|
|
||||||
// Collapsed = zero reserved space; expand/collapse is triggered from
|
|
||||||
// the taskbar (BottomBar), not a button on this bar itself.
|
|
||||||
implicitWidth: expanded ? 220 : 0
|
|
||||||
exclusiveZone: expanded ? 220 : 0
|
|
||||||
color: Theme.bg
|
|
||||||
// Never take keyboard focus — this bar has no text input, and leaving
|
|
||||||
// it focusable was stealing the keyboard grab from things like slurp's
|
|
||||||
// screenshot selection overlay.
|
|
||||||
focusable: false
|
|
||||||
|
|
||||||
// Only "network" needs the popup's keyboard grab (hotspot/wifi
|
|
||||||
// password fields).
|
|
||||||
function needsGrab(panel) { return panel === "network"; }
|
|
||||||
|
|
||||||
function toggle(panel) {
|
|
||||||
if (root.activePanel === panel) {
|
|
||||||
root.activePanel = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (root.activePanel !== "" && root.needsGrab(root.activePanel) !== root.needsGrab(panel)) {
|
|
||||||
// See LeftBar.qml's toggle() for why: an xdg_popup's grab
|
|
||||||
// can't be toggled on an already-mapped surface, only
|
|
||||||
// established fresh — so force a close+reopen when crossing
|
|
||||||
// that boundary instead of just changing the panel in place.
|
|
||||||
root.activePanel = "";
|
|
||||||
Qt.callLater(function () { root.activePanel = panel; });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
root.activePanel = panel;
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
visible: root.expanded
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.topMargin: 14
|
|
||||||
spacing: 14
|
|
||||||
width: parent.width - 24
|
|
||||||
|
|
||||||
DigitalClock {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
// System tray (StatusNotifierItem) — background/status apps like
|
|
||||||
// Steam, Discord, KDE Connect that register a tray icon. Sits
|
|
||||||
// right under the clock, separate from the categorized sections
|
|
||||||
// below the divider.
|
|
||||||
Row {
|
|
||||||
id: trayRow
|
|
||||||
visible: SystemTray.items.values.length > 0
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: SystemTray.items.values
|
|
||||||
delegate: IconImage {
|
|
||||||
id: trayIcon
|
|
||||||
implicitSize: 18
|
|
||||||
// Only resolve through the icon theme when this is
|
|
||||||
// actually a themed icon name (e.g. Steam's
|
|
||||||
// "steam_tray_mono"). Apps that only ever supply a raw
|
|
||||||
// IconPixmap over the SNI protocol and no IconName at
|
|
||||||
// all (confirmed via busctl: Vesktop's tray item has no
|
|
||||||
// IconName property) get an already-usable image:// URL
|
|
||||||
// back from modelData.icon instead — running that
|
|
||||||
// through iconPath(), a name/path resolver, mangled it
|
|
||||||
// into nothing, which is what rendered as a broken-
|
|
||||||
// texture checkerboard instead of the actual icon.
|
|
||||||
source: modelData.icon.includes("://")
|
|
||||||
? modelData.icon
|
|
||||||
: Quickshell.iconPath(modelData.icon, "")
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: (mouse) => {
|
|
||||||
if (mouse.button === Qt.RightButton) {
|
|
||||||
if (modelData.hasMenu) {
|
|
||||||
var pos = trayIcon.mapToItem(null, 0, trayIcon.height);
|
|
||||||
modelData.display(root, pos.x, pos.y);
|
|
||||||
} else {
|
|
||||||
modelData.secondaryActivate();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
modelData.activate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle { width: parent.width; height: 1; color: Theme.borderDim }
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "CONNECTIVITY"
|
|
||||||
color: Theme.fgDim
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 9
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: "Wi-Fi & Network"
|
|
||||||
active: root.activePanel === "network"
|
|
||||||
onClicked: root.toggle("network")
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: "Bluetooth"
|
|
||||||
active: root.activePanel === "bluetooth"
|
|
||||||
onClicked: root.toggle("bluetooth")
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: "Messages"
|
|
||||||
active: root.activePanel === "messages"
|
|
||||||
onClicked: root.toggle("messages")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "SYSTEM"
|
|
||||||
color: Theme.fgDim
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 9
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: "Screenshot"
|
|
||||||
active: root.activePanel === "screenshot"
|
|
||||||
onClicked: root.toggle("screenshot")
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: (Pipewire.defaultAudioSink && Pipewire.defaultAudioSink.audio && Pipewire.defaultAudioSink.audio.muted)
|
|
||||||
? "Volume (muted)" : "Volume"
|
|
||||||
active: root.activePanel === "volume"
|
|
||||||
onClicked: root.toggle("volume")
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: "Brightness"
|
|
||||||
active: root.activePanel === "brightness"
|
|
||||||
onClicked: root.toggle("brightness")
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: "Devices"
|
|
||||||
active: root.activePanel === "devices"
|
|
||||||
onClicked: root.toggle("devices")
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: "System Monitor"
|
|
||||||
active: root.activePanel === "system"
|
|
||||||
onClicked: root.toggle("system")
|
|
||||||
}
|
|
||||||
SidebarRow {
|
|
||||||
label: "Weather"
|
|
||||||
active: root.activePanel === "weather"
|
|
||||||
onClicked: root.toggle("weather")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SidebarRow {
|
|
||||||
visible: root.expanded
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: 14
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
width: parent.width - 24
|
|
||||||
label: "Power"
|
|
||||||
fg: Theme.danger
|
|
||||||
active: root.activePanel === "power"
|
|
||||||
onClicked: root.toggle("power")
|
|
||||||
}
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: flyout
|
|
||||||
screen: root.screen
|
|
||||||
anchors { top: true; right: true }
|
|
||||||
// No margin here on purpose: Hyprland already insets layer-shell
|
|
||||||
// surfaces past other surfaces' exclusive zones on the same edge
|
|
||||||
// (confirmed via `hyprctl layers` — BottomBar gets the same
|
|
||||||
// treatment for free, spanning right up to x=1700 with zero
|
|
||||||
// margin of its own). A margin of 220 here was stacking on top
|
|
||||||
// of that automatic inset, pushing the flyout 220px further left
|
|
||||||
// than intended and leaving a ~220px gap between it and the
|
|
||||||
// sidebar.
|
|
||||||
visible: root.activePanel !== ""
|
|
||||||
implicitWidth: 320
|
|
||||||
implicitHeight: content.implicitHeight
|
|
||||||
exclusiveZone: 0
|
|
||||||
color: "transparent"
|
|
||||||
WlrLayershell.layer: WlrLayer.Overlay
|
|
||||||
// On-demand layer-shell focus, not an xdg_popup grab: this used to
|
|
||||||
// be a PopupWindow with grabFocus, but an xdg_popup grab gets
|
|
||||||
// dismissed by the compositor the instant anything else so much as
|
|
||||||
// touches focus/surface state elsewhere (confirmed: even an
|
|
||||||
// unrelated hyprpaper/mpvpaper surface change was enough), which
|
|
||||||
// silently closed the whole flyout AND dropped keyboard input —
|
|
||||||
// that's what let keystrokes fall through to whatever window was
|
|
||||||
// focused before the flyout opened instead of reaching the text
|
|
||||||
// field. OnDemand only claims focus when something inside is
|
|
||||||
// actually clicked, and isn't torn down by unrelated surfaces.
|
|
||||||
// Panels with no text input don't need it at all.
|
|
||||||
WlrLayershell.keyboardFocus: root.needsGrab(root.activePanel)
|
|
||||||
? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
|
||||||
|
|
||||||
RightFlyout {
|
|
||||||
id: content
|
|
||||||
anchors.fill: parent
|
|
||||||
activePanel: root.activePanel
|
|
||||||
onRequestClose: root.activePanel = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,36 +0,0 @@
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
property string label: ""
|
|
||||||
property bool active: false
|
|
||||||
property color fg: Theme.fg
|
|
||||||
property color accent: Theme.accent
|
|
||||||
signal clicked()
|
|
||||||
|
|
||||||
width: parent ? parent.width : 200
|
|
||||||
height: 40
|
|
||||||
color: mouseArea.containsMouse ? Theme.bgAlt : "transparent"
|
|
||||||
border.width: active ? 2 : 1
|
|
||||||
border.color: active ? accent : Theme.borderDim
|
|
||||||
radius: 0
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 12
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: root.label
|
|
||||||
color: root.active ? root.accent : root.fg
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.bold: root.active
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root.clicked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
// Reusable 0..1 drag slider with a visible handle, used by the volume and
|
|
||||||
// brightness panels.
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
property real value: 0
|
|
||||||
signal moved(real value)
|
|
||||||
height: 28
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: track
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: parent.width
|
|
||||||
height: 6
|
|
||||||
color: Theme.bgAlt
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.border
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: track.width * Math.max(0, Math.min(1, root.value))
|
|
||||||
height: parent.height
|
|
||||||
color: Theme.accent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: handle
|
|
||||||
width: 14
|
|
||||||
height: 20
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
x: Math.max(0, Math.min(track.width - width, track.width * Math.max(0, Math.min(1, root.value)) - width / 2))
|
|
||||||
color: Theme.fg
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.border
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onPositionChanged: (mouse) => { if (pressed) update(mouse.x) }
|
|
||||||
onPressed: (mouse) => update(mouse.x)
|
|
||||||
function update(x) {
|
|
||||||
var v = Math.max(0, Math.min(1, x / width));
|
|
||||||
root.value = v;
|
|
||||||
root.moved(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
||||||
pragma Singleton
|
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Io
|
|
||||||
|
|
||||||
// Single source of truth for every color used across the shell — swapping
|
|
||||||
// accent color or dark/light mode here changes it everywhere, instead of
|
|
||||||
// hunting down hardcoded hex values in 14 different files (which is what
|
|
||||||
// this replaced). Persisted to disk (same pattern as the wallpaper/booru
|
|
||||||
// settings) since PersistentProperties doesn't survive a real restart.
|
|
||||||
//
|
|
||||||
// Plain strings, not `color`-typed properties — QML still accepts them
|
|
||||||
// fine anywhere a `color:` binding is expected (auto-converts at the
|
|
||||||
// point of use), but keeping them as strings means they can also be
|
|
||||||
// handed straight to hypr-theme-set/gtk-theme-set as clean "#rrggbb"
|
|
||||||
// text without a color-to-string round trip appending an alpha suffix.
|
|
||||||
Item {
|
|
||||||
id: theme
|
|
||||||
|
|
||||||
property bool dark: true
|
|
||||||
property string accentName: "teal"
|
|
||||||
|
|
||||||
readonly property var accentPalette: ({
|
|
||||||
teal: "#8fbcbb",
|
|
||||||
blue: "#61afef",
|
|
||||||
green: "#98c379",
|
|
||||||
purple: "#c678dd",
|
|
||||||
orange: "#d19a66",
|
|
||||||
pink: "#ff79c6",
|
|
||||||
yellow: "#e5c07b"
|
|
||||||
})
|
|
||||||
readonly property var accentNames: ["teal", "blue", "green", "purple", "orange", "pink", "yellow"]
|
|
||||||
|
|
||||||
readonly property string accent: theme.accentPalette[theme.accentName] || theme.accentPalette.teal
|
|
||||||
readonly property string danger: "#e06c75"
|
|
||||||
|
|
||||||
// Dark palette (the original, and still the default)
|
|
||||||
readonly property string bgDark: "#0d0d0d"
|
|
||||||
readonly property string bgAltDark: "#1a1a1a"
|
|
||||||
readonly property string bgAlt2Dark: "#131313"
|
|
||||||
readonly property string fgDark: "#e0e0e0"
|
|
||||||
readonly property string fgDimDark: "#666666"
|
|
||||||
readonly property string fgDim2Dark: "#999999"
|
|
||||||
readonly property string borderDark: "#333333"
|
|
||||||
readonly property string borderDimDark: "#262626"
|
|
||||||
|
|
||||||
// Light palette
|
|
||||||
readonly property string bgLight: "#f0f0f0"
|
|
||||||
readonly property string bgAltLight: "#e2e2e2"
|
|
||||||
readonly property string bgAlt2Light: "#d5d5d5"
|
|
||||||
readonly property string fgLight: "#1a1a1a"
|
|
||||||
readonly property string fgDimLight: "#666666"
|
|
||||||
readonly property string fgDim2Light: "#888888"
|
|
||||||
readonly property string borderLight: "#c4c4c4"
|
|
||||||
readonly property string borderDimLight: "#d8d8d8"
|
|
||||||
|
|
||||||
readonly property string bg: theme.dark ? theme.bgDark : theme.bgLight
|
|
||||||
readonly property string bgAlt: theme.dark ? theme.bgAltDark : theme.bgAltLight
|
|
||||||
readonly property string bgAlt2: theme.dark ? theme.bgAlt2Dark : theme.bgAlt2Light
|
|
||||||
readonly property string fg: theme.dark ? theme.fgDark : theme.fgLight
|
|
||||||
readonly property string fgDim: theme.dark ? theme.fgDimDark : theme.fgDimLight
|
|
||||||
readonly property string fgDim2: theme.dark ? theme.fgDim2Dark : theme.fgDim2Light
|
|
||||||
readonly property string border: theme.dark ? theme.borderDark : theme.borderLight
|
|
||||||
readonly property string borderDim: theme.dark ? theme.borderDimDark : theme.borderDimLight
|
|
||||||
|
|
||||||
property string stateFile: Quickshell.stateDir + "/theme-settings.json"
|
|
||||||
|
|
||||||
function setAccent(name) {
|
|
||||||
if (theme.accentPalette[name] === undefined) return;
|
|
||||||
theme.accentName = name;
|
|
||||||
theme.save();
|
|
||||||
theme.syncSystem();
|
|
||||||
}
|
|
||||||
function setDark(isDark) {
|
|
||||||
theme.dark = isDark;
|
|
||||||
theme.save();
|
|
||||||
theme.syncSystem();
|
|
||||||
}
|
|
||||||
function randomAccent() {
|
|
||||||
var names = theme.accentNames;
|
|
||||||
var pick = names[Math.floor(Math.random() * names.length)];
|
|
||||||
theme.setAccent(pick);
|
|
||||||
}
|
|
||||||
|
|
||||||
function save() {
|
|
||||||
var json = JSON.stringify({ dark: theme.dark, accentName: theme.accentName });
|
|
||||||
saveProc.command = ["sh", "-c",
|
|
||||||
"mkdir -p \"$(dirname \"$2\")\" && printf '%s' \"$1\" > \"$2\"",
|
|
||||||
"--", json, theme.stateFile];
|
|
||||||
saveProc.running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pushes the current palette out to the two things that don't read
|
|
||||||
// this file directly: Hyprland's window border colors (hardcoded in
|
|
||||||
// hyprland.conf otherwise) and GTK apps like Thunar (which used to be
|
|
||||||
// a static Nix-rebuild-only CSS file).
|
|
||||||
function syncSystem() {
|
|
||||||
hyprSyncProc.command = ["hypr-theme-set",
|
|
||||||
theme.accent.replace("#", ""), theme.border.replace("#", "")];
|
|
||||||
hyprSyncProc.running = true;
|
|
||||||
|
|
||||||
gtkSyncProc.command = ["gtk-theme-set",
|
|
||||||
theme.accent, theme.bg, theme.bgAlt, theme.fg, theme.fgDim, theme.border,
|
|
||||||
theme.dark ? "1" : "0"];
|
|
||||||
gtkSyncProc.running = true;
|
|
||||||
|
|
||||||
qtSyncProc.command = ["qt-theme-set",
|
|
||||||
theme.accent, theme.bg, theme.bgAlt, theme.fg, theme.fgDim, theme.border,
|
|
||||||
theme.dark ? "1" : "0"];
|
|
||||||
qtSyncProc.running = true;
|
|
||||||
|
|
||||||
kittySyncProc.command = ["kitty-theme-set",
|
|
||||||
theme.accent, theme.bg, theme.bgAlt, theme.fg, theme.fgDim, theme.border];
|
|
||||||
kittySyncProc.running = true;
|
|
||||||
|
|
||||||
rofiSyncProc.command = ["rofi-theme-set",
|
|
||||||
theme.accent, theme.bg, theme.bgAlt, theme.fg, theme.fgDim, theme.border,
|
|
||||||
theme.dark ? "1" : "0"];
|
|
||||||
rofiSyncProc.running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Process { id: saveProc }
|
|
||||||
Process { id: hyprSyncProc }
|
|
||||||
Process { id: gtkSyncProc }
|
|
||||||
Process { id: qtSyncProc }
|
|
||||||
Process { id: kittySyncProc }
|
|
||||||
Process { id: rofiSyncProc }
|
|
||||||
Process {
|
|
||||||
id: loadProc
|
|
||||||
command: ["cat", theme.stateFile]
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
try {
|
|
||||||
var d = JSON.parse(text);
|
|
||||||
if (typeof d.dark === "boolean") theme.dark = d.dark;
|
|
||||||
if (d.accentName) theme.accentName = d.accentName;
|
|
||||||
} catch (e) { /* no state file yet — defaults stand */ }
|
|
||||||
// Always sync on load, even if the state file didn't
|
|
||||||
// exist — Hyprland's border colors and GTK's CSS are
|
|
||||||
// otherwise still whatever was last written (or the
|
|
||||||
// hardcoded hyprland.conf default), independent of
|
|
||||||
// whether Quickshell itself just restarted.
|
|
||||||
theme.syncSystem();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Component.onCompleted: loadProc.running = true
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: root
|
|
||||||
property bool checked: false
|
|
||||||
property string onLabel: "On"
|
|
||||||
property string offLabel: "Off"
|
|
||||||
signal toggled()
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 44
|
|
||||||
height: 22
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
color: root.checked ? Theme.accent : Theme.bgAlt
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.border
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 18
|
|
||||||
height: 18
|
|
||||||
y: 2
|
|
||||||
x: root.checked ? parent.width - width - 2 : 2
|
|
||||||
color: root.checked ? Theme.bg : Theme.fgDim
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root.toggled()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: root.checked ? root.onLabel : root.offLabel
|
|
||||||
color: root.checked ? Theme.accent : Theme.fgDim
|
|
||||||
font.family: "JetBrainsMono Nerd Font"
|
|
||||||
font.pixelSize: 11
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
//@ pragma UseQApplication
|
|
||||||
// Quickshell entry point.
|
|
||||||
// Deployed as the whole directory -> ~/.config/quickshell via
|
|
||||||
// systemd.tmpfiles in modules/desktop.nix (relative imports need the full
|
|
||||||
// directory present, not just this file).
|
|
||||||
// UseQApplication is required for SystemTray items' native right-click
|
|
||||||
// menu (item.display()) — without it, Quickshell logs "Cannot display
|
|
||||||
// PlatformMenuEntry ... was not started in QApplication mode" and the
|
|
||||||
// menu silently does nothing.
|
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Io
|
|
||||||
|
|
||||||
ShellRoot {
|
|
||||||
PolkitDialog {}
|
|
||||||
Osd {}
|
|
||||||
|
|
||||||
Variants {
|
|
||||||
model: Quickshell.screens
|
|
||||||
|
|
||||||
Item {
|
|
||||||
required property var modelData
|
|
||||||
|
|
||||||
LeftBar { id: leftBarInstance; screen: modelData }
|
|
||||||
RightBar { id: rightBarInstance; screen: modelData }
|
|
||||||
BottomBar { screen: modelData; leftBar: leftBarInstance; rightBar: rightBarInstance }
|
|
||||||
|
|
||||||
// Lets Hyprland keybinds (Super+Shift+Z/X, see hyprland.conf)
|
|
||||||
// toggle the sidebars via `quickshell ipc call sidebars
|
|
||||||
// toggleLeft/toggleRight` — same open/close behavior as
|
|
||||||
// clicking the arrows on BottomBar.
|
|
||||||
IpcHandler {
|
|
||||||
target: "sidebars"
|
|
||||||
function toggleLeft(): void {
|
|
||||||
leftBarInstance.expanded = !leftBarInstance.expanded;
|
|
||||||
if (!leftBarInstance.expanded) leftBarInstance.activePanel = "";
|
|
||||||
}
|
|
||||||
function toggleRight(): void {
|
|
||||||
rightBarInstance.expanded = !rightBarInstance.expanded;
|
|
||||||
if (!rightBarInstance.expanded) rightBarInstance.activePanel = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
/* Rofi launcher configuration
|
|
||||||
* Deployed to ~/.config/rofi/config.rasi via systemd.tmpfiles in
|
|
||||||
* modules/desktop.nix. Squared, sharp, no rounded corners.
|
|
||||||
*
|
|
||||||
* Colors live in theme.rasi (imported below), regenerated live by
|
|
||||||
* rofi-theme-set from the Quickshell Theme picker (dotfiles/quickshell/
|
|
||||||
* Theme.qml) — same accent/bg/fg/border values Hyprland, GTK, and Kitty
|
|
||||||
* all sync to. Rofi has no config watcher, but it's launched on demand
|
|
||||||
* rather than staying resident, so the next open just picks up whatever
|
|
||||||
* is on disk — no reload step needed.
|
|
||||||
*/
|
|
||||||
@import "theme.rasi"
|
|
||||||
|
|
||||||
configuration {
|
|
||||||
modi: "drun,run,window";
|
|
||||||
show-icons: true;
|
|
||||||
/* icon-theme comes from theme.rasi (imported above), set live by
|
|
||||||
rofi-theme-set to track dark/light mode. */
|
|
||||||
display-drun: "";
|
|
||||||
display-run: "";
|
|
||||||
display-window: "";
|
|
||||||
drun-display-format: "{name}";
|
|
||||||
font: "JetBrainsMono Nerd Font 11";
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
background-color: @bg;
|
|
||||||
text-color: @fg;
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
spacing: 0px;
|
|
||||||
|
|
||||||
/* Rofi's built-in "default" theme (always loaded first, see
|
|
||||||
`rofi -dump-theme`) defines its own named vars — background,
|
|
||||||
foreground, lightbg, lightfg, blue, red — and alternate/active/
|
|
||||||
urgent row states read THOSE, not our bg/fg/accent. Explicit
|
|
||||||
`element.normal.alternate{...}` overrides below turned out not
|
|
||||||
to reliably win against them, so redefine the base theme's own
|
|
||||||
variables directly instead (same fix shape as GTK's
|
|
||||||
@define-color trick) — this is what actually stopped every
|
|
||||||
other row rendering in stock Solarized cream. */
|
|
||||||
background: @bg;
|
|
||||||
foreground: @fg;
|
|
||||||
lightbg: @bg-alt;
|
|
||||||
lightfg: @fg;
|
|
||||||
blue: @accent;
|
|
||||||
red: @accent;
|
|
||||||
}
|
|
||||||
|
|
||||||
window {
|
|
||||||
width: 600px;
|
|
||||||
border: 2px;
|
|
||||||
border-color: @border-color;
|
|
||||||
border-radius: 0px;
|
|
||||||
background-color: @bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
mainbox {
|
|
||||||
children: [ "inputbar", "listview" ];
|
|
||||||
}
|
|
||||||
|
|
||||||
inputbar {
|
|
||||||
padding: 12px;
|
|
||||||
background-color: @bg-alt;
|
|
||||||
border: 0px 0px 2px 0px;
|
|
||||||
border-color: @border-color;
|
|
||||||
border-radius: 0px;
|
|
||||||
children: [ "prompt", "entry" ];
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt {
|
|
||||||
text-color: @accent;
|
|
||||||
padding: 0px 8px 0px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
entry {
|
|
||||||
placeholder: "Search...";
|
|
||||||
placeholder-color: @placeholder;
|
|
||||||
text-color: @fg;
|
|
||||||
}
|
|
||||||
|
|
||||||
listview {
|
|
||||||
lines: 8;
|
|
||||||
scrollbar: false;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
element {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 0px;
|
|
||||||
spacing: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* State selectors are dot-joined with no space (element.normal.normal,
|
|
||||||
not "element normal.normal") — the latter silently matches nothing,
|
|
||||||
which is why unselected rows never actually picked up @bg/@fg before
|
|
||||||
and fell back to rofi's built-in cream default on every other row. */
|
|
||||||
element.normal.normal, element.normal.alternate {
|
|
||||||
background-color: @bg;
|
|
||||||
text-color: @fg;
|
|
||||||
}
|
|
||||||
|
|
||||||
element.selected.normal, element.selected.alternate {
|
|
||||||
background-color: @accent;
|
|
||||||
text-color: @bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
element-icon {
|
|
||||||
size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
element-text {
|
|
||||||
text-color: inherit;
|
|
||||||
vertical-align: 0.5;
|
|
||||||
}
|
|
||||||
|
|
@ -1,219 +0,0 @@
|
||||||
# Hyprland configuration
|
|
||||||
# Deployed to ~/.config/hypr/hyprland.conf via systemd.tmpfiles in
|
|
||||||
# modules/desktop.nix. Edit this file, not the symlink target.
|
|
||||||
|
|
||||||
# --- Monitors -----------------------------------------------------------
|
|
||||||
# Wildcard fallback for any monitor without its own line below.
|
|
||||||
monitor = , preferred, auto, 1
|
|
||||||
# DP-1 (AOC 27G2G8) explicitly at its real 144Hz spec: its EDID lists a
|
|
||||||
# bogus "239.96Hz" mode, and "preferred" trusts the EDID's preferred-timing
|
|
||||||
# flag — which picked that bogus mode here, silently running the monitor
|
|
||||||
# at a refresh rate it doesn't actually support. display.conf below
|
|
||||||
# normally overrides this anyway, but this is the safe cold-boot default
|
|
||||||
# if that file is ever missing/reset.
|
|
||||||
monitor = DP-1, 1920x1080@144.00Hz, auto, 1
|
|
||||||
|
|
||||||
# User-editable cursor theme (set via the Quickshell cursor picker); empty
|
|
||||||
# by default, created by systemd.tmpfiles so this source never fails.
|
|
||||||
source = ~/.config/hypr/cursor.conf
|
|
||||||
|
|
||||||
# User-set display mode/scale/transform (set via the Quickshell display
|
|
||||||
# picker) — overrides the generic "preferred" fallback above for whatever
|
|
||||||
# monitors have an explicit line in here. Empty by default.
|
|
||||||
source = ~/.config/hypr/display.conf
|
|
||||||
|
|
||||||
# --- Autostart ------------------------------------------------------------
|
|
||||||
# Polkit auth is handled by our own themed PolkitDialog inside quickshell
|
|
||||||
# (dotfiles/quickshell/PolkitDialog.qml) — hyprpolkitagent stays installed
|
|
||||||
# but unstarted as a manual emergency fallback ("hyprpolkitagent &" from a
|
|
||||||
# TTY) since only one agent can be registered at a time.
|
|
||||||
exec-once = mako
|
|
||||||
exec-once = hyprpaper
|
|
||||||
# Reapplies whatever wallpaper was last set — hyprpaper/mpvpaper start every
|
|
||||||
# session with nothing loaded, and wallpaper-set never persisted anything
|
|
||||||
# on its own (see modules/scripts/wallpaper-set.sh, wallpaper-restore.sh).
|
|
||||||
exec-once = wallpaper-restore
|
|
||||||
exec-once = quickshell
|
|
||||||
|
|
||||||
# GTK/Qt apps native-Wayland where possible, X11 fallback otherwise
|
|
||||||
env = XDG_CURRENT_DESKTOP,Hyprland
|
|
||||||
env = XDG_SESSION_TYPE,wayland
|
|
||||||
env = XDG_SESSION_DESKTOP,Hyprland
|
|
||||||
env = GDK_BACKEND,wayland,x11
|
|
||||||
env = QT_QPA_PLATFORM,wayland;xcb
|
|
||||||
env = QT_QPA_PLATFORMTHEME,qt6ct
|
|
||||||
# Kirigami-based KDE apps (confirmed live: Gwenview's toolbar/statusbar)
|
|
||||||
# draw their chrome in QML, not QWidget — outside a real Plasma session
|
|
||||||
# they default to their own hardcoded light "Basic" QQC2 style instead of
|
|
||||||
# reading qt6ct or kdeglobals at all. org.kde.desktop is the QQC2 style
|
|
||||||
# that actually pulls from KColorScheme/kdeglobals like the rest of the
|
|
||||||
# theming pipeline here does.
|
|
||||||
env = QT_QUICK_CONTROLS_STYLE,org.kde.desktop
|
|
||||||
|
|
||||||
# --- Input ------------------------------------------------------------
|
|
||||||
input {
|
|
||||||
kb_layout = pl
|
|
||||||
follow_mouse = 1
|
|
||||||
touchpad {
|
|
||||||
natural_scroll = true
|
|
||||||
}
|
|
||||||
sensitivity = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- General: sharp, square, minimal -----------------------------------
|
|
||||||
general {
|
|
||||||
gaps_in = 0
|
|
||||||
gaps_out = 0
|
|
||||||
border_size = 2
|
|
||||||
col.active_border = rgba(8fbcbbff)
|
|
||||||
col.inactive_border = rgba(333333ff)
|
|
||||||
layout = dwindle
|
|
||||||
resize_on_border = true
|
|
||||||
}
|
|
||||||
|
|
||||||
# Border colors set via the Quickshell Theme picker (dotfiles/quickshell/
|
|
||||||
# Theme.qml) — overrides the general{} defaults above for whatever's been
|
|
||||||
# picked. Empty by default; hypr-theme-set writes to it live.
|
|
||||||
source = ~/.config/hypr/theme.conf
|
|
||||||
|
|
||||||
# Zero rounding, no shadows/blur: sharp squared look everywhere
|
|
||||||
decoration {
|
|
||||||
rounding = 0
|
|
||||||
|
|
||||||
shadow {
|
|
||||||
enabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
blur {
|
|
||||||
enabled = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
dwindle {
|
|
||||||
preserve_split = true
|
|
||||||
}
|
|
||||||
|
|
||||||
master {
|
|
||||||
new_status = master
|
|
||||||
}
|
|
||||||
|
|
||||||
# Don't warp the cursor to whatever window gets focused (e.g. clicking a
|
|
||||||
# taskbar entry) — only move it when the user actually moves the mouse.
|
|
||||||
cursor {
|
|
||||||
no_warps = true
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Window rules ---------------------------------------------------------
|
|
||||||
# (none needed currently — the polkit dialog is a layer-shell surface via
|
|
||||||
# Quickshell's PanelWindow, not a normal toplevel, so it doesn't need a
|
|
||||||
# float/center window rule at all.)
|
|
||||||
|
|
||||||
misc {
|
|
||||||
disable_hyprland_logo = true
|
|
||||||
disable_splash_rendering = true
|
|
||||||
force_default_wallpaper = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Keybinds ------------------------------------------------------------
|
|
||||||
$mod = SUPER
|
|
||||||
$terminal = kitty
|
|
||||||
$fileManager = thunar
|
|
||||||
$launcher = rofi -show drun
|
|
||||||
$imageViewer = imv
|
|
||||||
|
|
||||||
bind = $mod, Return, exec, $terminal
|
|
||||||
bind = $mod, T, exec, $terminal
|
|
||||||
bind = $mod, E, exec, $fileManager
|
|
||||||
bind = $mod, A, exec, $launcher
|
|
||||||
bind = $mod, D, exec, show-desktop-toggle
|
|
||||||
|
|
||||||
# Sidebar toggles — mirrors clicking the arrows on BottomBar, via the
|
|
||||||
# IpcHandler registered in shell.qml.
|
|
||||||
bind = $mod SHIFT, Z, exec, quickshell ipc call sidebars toggleLeft
|
|
||||||
bind = $mod SHIFT, X, exec, quickshell ipc call sidebars toggleRight
|
|
||||||
bind = $mod, Q, killactive,
|
|
||||||
bind = $mod SHIFT, Q, exit,
|
|
||||||
bind = $mod, V, togglefloating,
|
|
||||||
bind = $mod, F, fullscreen,
|
|
||||||
bind = $mod, P, pseudo,
|
|
||||||
bind = $mod, S, layoutmsg, togglesplit
|
|
||||||
|
|
||||||
# Focus movement (vim-style, plus arrow keys as the same thing)
|
|
||||||
bind = $mod, H, movefocus, l
|
|
||||||
bind = $mod, L, movefocus, r
|
|
||||||
bind = $mod, K, movefocus, u
|
|
||||||
bind = $mod, J, movefocus, d
|
|
||||||
bind = $mod, Left, movefocus, l
|
|
||||||
bind = $mod, Right, movefocus, r
|
|
||||||
bind = $mod, Up, movefocus, u
|
|
||||||
bind = $mod, Down, movefocus, d
|
|
||||||
|
|
||||||
# Move windows
|
|
||||||
bind = $mod SHIFT, H, movewindow, l
|
|
||||||
bind = $mod SHIFT, L, movewindow, r
|
|
||||||
bind = $mod SHIFT, K, movewindow, u
|
|
||||||
bind = $mod SHIFT, J, movewindow, d
|
|
||||||
bind = $mod SHIFT, Left, movewindow, l
|
|
||||||
bind = $mod SHIFT, Right, movewindow, r
|
|
||||||
bind = $mod SHIFT, Up, movewindow, u
|
|
||||||
bind = $mod SHIFT, Down, movewindow, d
|
|
||||||
|
|
||||||
# Workspaces 1-10
|
|
||||||
bind = $mod, 1, workspace, 1
|
|
||||||
bind = $mod, 2, workspace, 2
|
|
||||||
bind = $mod, 3, workspace, 3
|
|
||||||
bind = $mod, 4, workspace, 4
|
|
||||||
bind = $mod, 5, workspace, 5
|
|
||||||
bind = $mod, 6, workspace, 6
|
|
||||||
bind = $mod, 7, workspace, 7
|
|
||||||
bind = $mod, 8, workspace, 8
|
|
||||||
bind = $mod, 9, workspace, 9
|
|
||||||
bind = $mod, 0, workspace, 10
|
|
||||||
|
|
||||||
bind = $mod SHIFT, 1, movetoworkspace, 1
|
|
||||||
bind = $mod SHIFT, 2, movetoworkspace, 2
|
|
||||||
bind = $mod SHIFT, 3, movetoworkspace, 3
|
|
||||||
bind = $mod SHIFT, 4, movetoworkspace, 4
|
|
||||||
bind = $mod SHIFT, 5, movetoworkspace, 5
|
|
||||||
bind = $mod SHIFT, 6, movetoworkspace, 6
|
|
||||||
bind = $mod SHIFT, 7, movetoworkspace, 7
|
|
||||||
bind = $mod SHIFT, 8, movetoworkspace, 8
|
|
||||||
bind = $mod SHIFT, 9, movetoworkspace, 9
|
|
||||||
bind = $mod SHIFT, 0, movetoworkspace, 10
|
|
||||||
|
|
||||||
# Function keys — media/volume/brightness. bindl (not bind) so these still
|
|
||||||
# work with the screen locked; bindel additionally repeats while held, for
|
|
||||||
# the ones you'd actually want to hold down (volume/brightness ramps).
|
|
||||||
bindel = , XF86AudioRaiseVolume, exec, wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 5%+
|
|
||||||
bindel = , XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
|
|
||||||
bindl = , XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
|
|
||||||
bindl = , XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
|
|
||||||
# Chained with an OSD call: unlike volume/mic, brightness has no live
|
|
||||||
# service Quickshell can react to (DDC/CI writes aren't broadcast
|
|
||||||
# anywhere), so the keybind has to tell the OSD explicitly.
|
|
||||||
bindel = , XF86MonBrightnessUp, exec, brightness-ctl up && quickshell ipc call osd brightness "$(brightness-ctl get)"
|
|
||||||
bindel = , XF86MonBrightnessDown, exec, brightness-ctl down && quickshell ipc call osd brightness "$(brightness-ctl get)"
|
|
||||||
bindl = , XF86AudioPlay, exec, playerctl play-pause
|
|
||||||
bindl = , XF86AudioPause, exec, playerctl play-pause
|
|
||||||
bindl = , XF86AudioStop, exec, playerctl stop
|
|
||||||
bindl = , XF86AudioNext, exec, playerctl next
|
|
||||||
bindl = , XF86AudioPrev, exec, playerctl previous
|
|
||||||
|
|
||||||
# Mouse: drag/resize floating windows
|
|
||||||
bindm = $mod, mouse:272, movewindow
|
|
||||||
bindm = $mod, mouse:273, resizewindow
|
|
||||||
|
|
||||||
# Screenshots — the "screenshot" script (region|full|window, clipboard|file|both).
|
|
||||||
# Super+Shift+S is the primary bind (this keyboard has no physical Print key) —
|
|
||||||
# it saves a file AND copies to clipboard, so it's never a silent no-op.
|
|
||||||
bind = , Print, exec, screenshot full clipboard
|
|
||||||
bind = $mod, Print, exec, screenshot region clipboard
|
|
||||||
bind = $mod SHIFT, Print, exec, screenshot region both
|
|
||||||
bind = $mod SHIFT, S, exec, screenshot region both
|
|
||||||
|
|
||||||
# Image viewer quick-launch on the screenshots folder
|
|
||||||
bind = $mod, I, exec, $imageViewer ~/Screenshots
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# Hyprpaper wallpaper daemon config.
|
|
||||||
# Deployed to ~/.config/hypr/hyprpaper.conf via systemd.tmpfiles in
|
|
||||||
# modules/desktop.nix. No static preload list here on purpose — wallpapers
|
|
||||||
# are set at runtime via IPC (`wallpaper-set <path>`) from the Quickshell
|
|
||||||
# wallpaper picker (dotfiles/quickshell/LeftFlyout.qml).
|
|
||||||
splash = false
|
|
||||||
ipc = true
|
|
||||||
|
|
@ -1,41 +1,17 @@
|
||||||
{ config, pkgs, ... }:
|
{ config, pkgs, ... }:
|
||||||
|
|
||||||
let
|
|
||||||
# hyprpolkitagent only ships its binary under libexec/, not bin/, so it
|
|
||||||
# never lands on $PATH — wrap it so `exec-once = hyprpolkitagent` in
|
|
||||||
# hyprland.conf can find it.
|
|
||||||
hyprpolkitagentBin = pkgs.writeShellScriptBin "hyprpolkitagent"
|
|
||||||
"exec ${pkgs.hyprpolkitagent}/libexec/hyprpolkitagent";
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
# Every installed package — GUI apps, CLI tools, and dev toolchains alike.
|
# Every installed package — GUI apps, CLI tools, and dev toolchains alike.
|
||||||
# If it's "install X", it goes here.
|
# If it's "install X", it goes here.
|
||||||
users.users."nexoniarz".packages = with pkgs; [
|
users.users."nexoniarz".packages = with pkgs; [
|
||||||
# Hyprland desktop toolchain (Wayland-native, lightweight, squared theme)
|
tumbler # Thumbnails (paired with services.tumbler.enable; also used by Nemo)
|
||||||
kitty # Terminal
|
gnome-screenshot # Screenshot capture (X11)
|
||||||
thunar # File manager
|
|
||||||
thunar-archive-plugin
|
|
||||||
thunar-volman
|
|
||||||
tumbler # Thumbnails (paired with services.tumbler.enable)
|
|
||||||
rofi # Launcher (Wayland support built in since 2.0)
|
|
||||||
quickshell # Bar / UI shell
|
|
||||||
imv # Image viewer
|
imv # Image viewer
|
||||||
mako # Notification daemon
|
|
||||||
grim # Screenshot capture
|
|
||||||
slurp # Screenshot region select
|
|
||||||
wl-clipboard # Wayland clipboard
|
|
||||||
hyprpolkitagentBin # Polkit authentication agent (wrapped, see `let` above)
|
|
||||||
hyprlock # Screen locker (power menu)
|
|
||||||
brightnessctl # Backlight brightness (laptop panels)
|
brightnessctl # Backlight brightness (laptop panels)
|
||||||
ddcutil # DDC/CI brightness (external monitors)
|
ddcutil # DDC/CI brightness (external monitors)
|
||||||
lm_sensors # sensors-detect helps ddcutil find I2C buses
|
lm_sensors # sensors-detect helps ddcutil find I2C buses
|
||||||
hyprpaper # Wallpaper daemon, driven by the Quickshell picker (static images)
|
|
||||||
mpvpaper # Wallpaper daemon for video-loop (animated) wallpapers
|
|
||||||
networkmanager # nmcli, for the Wi-Fi hotspot toggle
|
networkmanager # nmcli, for the Wi-Fi hotspot toggle
|
||||||
xdg-user-dirs # Resolves localized folder names (Obrazy, not Pictures, on pl_PL)
|
xdg-user-dirs # Resolves localized folder names (Obrazy, not Pictures, on pl_PL)
|
||||||
bibata-cursors # Cursor theme(s) for the cursor picker
|
|
||||||
xcur2png # Renders Xcursor files to PNG for the cursor picker's previews
|
|
||||||
imagemagick # `identify`, used to pick the right cursor-preview size
|
|
||||||
playerctl # MPRIS media control, for the function-key media binds
|
playerctl # MPRIS media control, for the function-key media binds
|
||||||
|
|
||||||
# Apps
|
# Apps
|
||||||
|
|
@ -48,6 +24,14 @@ in
|
||||||
vlc
|
vlc
|
||||||
filezilla
|
filezilla
|
||||||
chromium
|
chromium
|
||||||
|
obs-studio
|
||||||
|
easyeffects # Real-time mic/output effects (pitch, EQ, ...) — auto-creates a
|
||||||
|
# selectable "Easy Effects Source" virtual mic once an Input effect
|
||||||
|
# is added, no manual PipeWire routing needed
|
||||||
|
qpwgraph # Patchbay GUI — wires EasyEffects'/Soundux's output into the
|
||||||
|
# standalone "Virtual Microphone Sink" (see system.nix)
|
||||||
|
# Soundux (soundboard) isn't packaged in this nixpkgs channel anymore —
|
||||||
|
# installed via Flatpak instead (io.github.Soundux).
|
||||||
|
|
||||||
# Games
|
# Games
|
||||||
prismlauncher
|
prismlauncher
|
||||||
|
|
@ -80,5 +64,12 @@ in
|
||||||
unrar
|
unrar
|
||||||
kdePackages.gwenview
|
kdePackages.gwenview
|
||||||
kdePackages.kate
|
kdePackages.kate
|
||||||
|
kdePackages.qqc2-desktop-style
|
||||||
|
linphonePackages.linphone-desktop
|
||||||
|
kdePackages.kcalc # Calculator (nothing filled this role before)
|
||||||
|
kdePackages.okular # Document/PDF viewer (nothing filled this role before)
|
||||||
|
kdePackages.elisa # Audio player (vlc/ffmpeg cover video already; nothing dedicated for audio)
|
||||||
|
kdePackages.konsole # Terminal (replaces kitty)
|
||||||
|
kdePackages.dolphin # File manager (replaces Thunar, alongside Budgie's default Nemo)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,24 +42,19 @@
|
||||||
"application/x-7z-compressed" "application/vnd.rar" "application/zip"
|
"application/x-7z-compressed" "application/vnd.rar" "application/zip"
|
||||||
"application/x-java-archive" "application/x-arj"
|
"application/x-java-archive" "application/x-arj"
|
||||||
];
|
];
|
||||||
|
# Taken from Okular's own .desktop file, same as the other two lists.
|
||||||
|
okularMimes = [
|
||||||
|
"application/pdf" "application/x-pdf" "image/vnd.djvu" "application/epub+zip"
|
||||||
|
"application/x-fictionbook+xml" "application/x-mobipocket-ebook"
|
||||||
|
"image/x-portable-document" "application/x-cbr" "application/x-cbz"
|
||||||
|
"application/x-cbt" "application/x-cb7" "application/postscript"
|
||||||
|
];
|
||||||
line = desktop: mime: "${mime}=${desktop}";
|
line = desktop: mime: "${mime}=${desktop}";
|
||||||
in
|
in
|
||||||
''
|
''
|
||||||
[Default Applications]
|
[Default Applications]
|
||||||
${lib.concatMapStringsSep "\n" (line "org.kde.gwenview.desktop") gwenviewMimes}
|
${lib.concatMapStringsSep "\n" (line "org.kde.gwenview.desktop") gwenviewMimes}
|
||||||
${lib.concatMapStringsSep "\n" (line "org.kde.ark.desktop") arkMimes}
|
${lib.concatMapStringsSep "\n" (line "org.kde.ark.desktop") arkMimes}
|
||||||
|
${lib.concatMapStringsSep "\n" (line "org.kde.okular.desktop") okularMimes}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Thunar's own "Open Terminal Here" custom action (Edit > Configure
|
|
||||||
# Custom Actions) shipped pointing at `exo-open --launch
|
|
||||||
# TerminalEmulator` — exo isn't installed on this system at all (no
|
|
||||||
# XFCE session, so its helpers.rc mechanism has nothing to resolve
|
|
||||||
# against), so the action silently did nothing. `f`, not `w`: only
|
|
||||||
# creates this baseline for a fresh account — once it exists, further
|
|
||||||
# edits made through Thunar's own UI (adding more custom actions) are
|
|
||||||
# never overwritten by a rebuild.
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
"d /home/nexoniarz/.config/Thunar 0755 nexoniarz users -"
|
|
||||||
''f /home/nexoniarz/.config/Thunar/uca.xml 0644 nexoniarz users - <?xml version="1.0" encoding="UTF-8"?><actions><action><icon>utilities-terminal</icon><name>Open Terminal Here</name><submenu></submenu><unique-id>1788014825097143-1</unique-id><command>kitty --working-directory %f</command><description>Open a terminal in this folder</description><range></range><patterns>*</patterns><startup-notify/><directories/></action></actions>''
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,101 +1,41 @@
|
||||||
{ config, pkgs, ... }:
|
{ config, pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# --- Hyprland: Wayland compositor ---
|
# --- Budgie: X11 desktop environment, lightdm login manager ---
|
||||||
programs.hyprland = {
|
services.xserver.enable = true;
|
||||||
enable = true;
|
services.xserver.displayManager.lightdm.enable = true;
|
||||||
xwayland.enable = true;
|
services.desktopManager.budgie.enable = true;
|
||||||
};
|
|
||||||
|
|
||||||
# --- greetd + tuigreet: minimal TUI login manager, launches Hyprland directly ---
|
|
||||||
services.greetd = {
|
|
||||||
enable = true;
|
|
||||||
settings.default_session = {
|
|
||||||
# start-hyprland, not the raw Hyprland binary: it's Hyprland's own
|
|
||||||
# watchdog-wrapped launcher (ships alongside Hyprland in the same
|
|
||||||
# package) that also auto-restarts the compositor if it hangs.
|
|
||||||
# Launching the raw binary directly works but logs a startup warning
|
|
||||||
# ("Hyprland is being launched without start-hyprland. This is highly
|
|
||||||
# advised against.") and skips that recovery.
|
|
||||||
command = "${pkgs.tuigreet}/bin/tuigreet --time --remember --remember-session --cmd start-hyprland";
|
|
||||||
user = "greeter";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# --- XDG portals: screen share, file pickers, etc. ---
|
|
||||||
xdg.portal = {
|
|
||||||
enable = true;
|
|
||||||
extraPortals = [ pkgs.xdg-desktop-portal-hyprland pkgs.xdg-desktop-portal-gtk ];
|
|
||||||
config.common.default = [ "hyprland" "gtk" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
security.polkit.enable = true;
|
security.polkit.enable = true;
|
||||||
|
|
||||||
# Keyboard layout for console/XWayland/greetd; Hyprland's own input block
|
# Keyboard layout for console/X11/lightdm
|
||||||
# in hyprland/hyprland.conf mirrors this.
|
|
||||||
services.xserver.xkb = {
|
services.xserver.xkb = {
|
||||||
layout = "pl";
|
layout = "pl";
|
||||||
variant = "";
|
variant = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
# Thunar: thumbnails, trash/volume mount integration, settings storage
|
# Thumbnails + trash/volume mount integration for Dolphin and Budgie's
|
||||||
|
# default Nemo file manager alike.
|
||||||
services.tumbler.enable = true;
|
services.tumbler.enable = true;
|
||||||
services.gvfs.enable = true;
|
services.gvfs.enable = true;
|
||||||
programs.xfconf.enable = true;
|
|
||||||
|
|
||||||
# Native Wayland backend for Electron/Chromium-based apps
|
# Drop the xterm fallback terminal xserver.nix installs unconditionally —
|
||||||
environment.sessionVariables.NIXOS_OZONE_WL = "1";
|
# Konsole is the real terminal here.
|
||||||
|
services.xserver.excludePackages = [ pkgs.xterm ];
|
||||||
|
|
||||||
# --- Dotfiles: symlink tracked configs into the user's home ---
|
# GTK/Qt themselves stay at their stock defaults (no forced dark mode, no
|
||||||
# Editing /etc/nixos/hyprland or /etc/nixos/dotfiles takes effect on the
|
# custom CSS) — only the icon and cursor theme are picked explicitly, via
|
||||||
# next reload of the respective app (no rebuild needed for config-only
|
# Budgie's own GSettings-override hook rather than a custom script.
|
||||||
# tweaks; a rebuild re-runs the symlink if the target path itself changes).
|
# (extraGSettingsOverridePackages is for packages that ship their own
|
||||||
systemd.tmpfiles.rules = [
|
# gsettings-schema overrides, e.g. plugins — not theme packages, which
|
||||||
"d /home/nexoniarz/.config/hypr 0755 nexoniarz users -"
|
# just need to be on the system path for their icons/cursors to resolve.)
|
||||||
"L+ /home/nexoniarz/.config/hypr/hyprland.conf - - - - /etc/nixos/hyprland/hyprland.conf"
|
environment.systemPackages = [
|
||||||
"L+ /home/nexoniarz/.config/hypr/hyprpaper.conf - - - - /etc/nixos/hyprland/hyprpaper.conf"
|
pkgs.gruvbox-plus-icons
|
||||||
|
pkgs.bibata-cursors
|
||||||
"d /home/nexoniarz/.config/kitty 0755 nexoniarz users -"
|
|
||||||
"L+ /home/nexoniarz/.config/kitty/kitty.conf - - - - /etc/nixos/dotfiles/kitty/kitty.conf"
|
|
||||||
# Colors set live via the Quickshell Theme picker — see kitty.conf's
|
|
||||||
# `include theme.conf`. Empty by default; kitty-theme-set writes to it.
|
|
||||||
"f /home/nexoniarz/.config/kitty/theme.conf 0644 nexoniarz users - -"
|
|
||||||
|
|
||||||
"d /home/nexoniarz/.config/rofi 0755 nexoniarz users -"
|
|
||||||
"L+ /home/nexoniarz/.config/rofi/config.rasi - - - - /etc/nixos/dotfiles/rofi/config.rasi"
|
|
||||||
# Colors set live via the Quickshell Theme picker — see config.rasi's
|
|
||||||
# `@import "theme.rasi"`. Empty by default; rofi-theme-set writes to it.
|
|
||||||
"f /home/nexoniarz/.config/rofi/theme.rasi 0644 nexoniarz users - -"
|
|
||||||
|
|
||||||
"d /home/nexoniarz/.config/mako 0755 nexoniarz users -"
|
|
||||||
"L+ /home/nexoniarz/.config/mako/config - - - - /etc/nixos/dotfiles/mako/config"
|
|
||||||
|
|
||||||
# Quickshell is multi-file with relative imports, so the whole directory
|
|
||||||
# is symlinked rather than a single file.
|
|
||||||
"L+ /home/nexoniarz/.config/quickshell - - - - /etc/nixos/dotfiles/quickshell"
|
|
||||||
|
|
||||||
# $HOME/Wallpapers, not $HOME/Pictures/Wallpapers — this system's locale
|
|
||||||
# (pl_PL) means the real XDG Pictures dir is ~/Obrazy, not ~/Pictures.
|
|
||||||
# Living directly under $HOME sidesteps that entirely.
|
|
||||||
"d /home/nexoniarz/Wallpapers 0755 nexoniarz users -"
|
|
||||||
"d /home/nexoniarz/Screenshots 0755 nexoniarz users -"
|
|
||||||
|
|
||||||
# User-editable cursor theme choice (set live via the Quickshell cursor
|
|
||||||
# picker) — created empty if missing, never overwritten if it already
|
|
||||||
# has content, since it's runtime state rather than tracked config.
|
|
||||||
"f /home/nexoniarz/.config/hypr/cursor.conf 0644 nexoniarz users - -"
|
|
||||||
|
|
||||||
# Same deal for display settings (mode/scale/transform per monitor,
|
|
||||||
# set via the Quickshell display picker) — without this file backing
|
|
||||||
# them, they only ever applied live via hyprctl and got silently
|
|
||||||
# reset by hyprland.conf's generic fallback monitor rule any time
|
|
||||||
# Hyprland re-evaluated monitor state.
|
|
||||||
"f /home/nexoniarz/.config/hypr/display.conf 0644 nexoniarz users - -"
|
|
||||||
|
|
||||||
# Same deal for window border colors (accent/border, set live via the
|
|
||||||
# Quickshell Theme picker) — without this file, they only ever applied
|
|
||||||
# via hyprctl and would fall back to hyprland.conf's own general{}
|
|
||||||
# defaults any time Hyprland re-evaluated its config.
|
|
||||||
"f /home/nexoniarz/.config/hypr/theme.conf 0644 nexoniarz users - -"
|
|
||||||
];
|
];
|
||||||
|
services.desktopManager.budgie.extraGSettingsOverrides = ''
|
||||||
|
[org.gnome.desktop.interface]
|
||||||
|
icon-theme='Gruvbox-Plus-Dark'
|
||||||
|
cursor-theme='Bibata-Modern-Classic'
|
||||||
|
'';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,9 @@
|
||||||
#OpenTabletDriver
|
#OpenTabletDriver
|
||||||
hardware.opentabletdriver.enable = true;
|
hardware.opentabletdriver.enable = true;
|
||||||
hardware.uinput.enable = true;
|
hardware.uinput.enable = true;
|
||||||
boot.kernelModules = [ "uinput" "v4l2loopback" ];
|
boot.kernelModules = [ "uinput" ];
|
||||||
|
|
||||||
# Virtual webcam device so ffmpeg (fed by the GoPro's stream) can appear
|
|
||||||
# as a normal camera to apps like Discord. exclusive_caps=1 is required
|
|
||||||
# for Chromium/WebRTC-based apps to recognize it as a real capture device.
|
|
||||||
boot.extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ];
|
|
||||||
boot.extraModprobeConfig = ''
|
boot.extraModprobeConfig = ''
|
||||||
options v4l2loopback devices=1 video_nr=10 card_label="GoPro Webcam" exclusive_caps=1
|
|
||||||
# PS4/PS5 controllers (e.g. DualSense) connect over Bluetooth then
|
# PS4/PS5 controllers (e.g. DualSense) connect over Bluetooth then
|
||||||
# immediately disconnect — a well-known Linux Bluetooth stack issue
|
# immediately disconnect — a well-known Linux Bluetooth stack issue
|
||||||
# where many adapters' Enhanced Retransmission Mode implementation is
|
# where many adapters' Enhanced Retransmission Mode implementation is
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,6 @@ let
|
||||||
vaultToggle = pkgs.writeShellScriptBin "vault-toggle" (builtins.readFile ./scripts/vault-toggle.sh);
|
vaultToggle = pkgs.writeShellScriptBin "vault-toggle" (builtins.readFile ./scripts/vault-toggle.sh);
|
||||||
stripMetadata = pkgs.writeShellScriptBin "strip-metadata" (builtins.readFile ./scripts/strip-metadata.sh);
|
stripMetadata = pkgs.writeShellScriptBin "strip-metadata" (builtins.readFile ./scripts/strip-metadata.sh);
|
||||||
brightnessCtl = pkgs.writeShellScriptBin "brightness-ctl" (builtins.readFile ./scripts/brightness-ctl.sh);
|
brightnessCtl = pkgs.writeShellScriptBin "brightness-ctl" (builtins.readFile ./scripts/brightness-ctl.sh);
|
||||||
showDesktopToggle = pkgs.writeShellScriptBin "show-desktop-toggle" (builtins.readFile ./scripts/show-desktop-toggle.sh);
|
|
||||||
wallpaperSet = pkgs.writeShellScriptBin "wallpaper-set" (builtins.readFile ./scripts/wallpaper-set.sh);
|
|
||||||
wallpaperDelete = pkgs.writeShellScriptBin "wallpaper-delete" (builtins.readFile ./scripts/wallpaper-delete.sh);
|
|
||||||
wallpaperRestore = pkgs.writeShellScriptBin "wallpaper-restore" (builtins.readFile ./scripts/wallpaper-restore.sh);
|
|
||||||
wallpaperThumbs = pkgs.writeShellScriptBin "wallpaper-thumbs" (builtins.readFile ./scripts/wallpaper-thumbs.sh);
|
|
||||||
displaySet = pkgs.writeShellScriptBin "display-set" (builtins.readFile ./scripts/display-set.sh);
|
|
||||||
hyprThemeSet = pkgs.writeShellScriptBin "hypr-theme-set" (builtins.readFile ./scripts/hypr-theme-set.sh);
|
|
||||||
gtkThemeSet = pkgs.writeShellScriptBin "gtk-theme-set" (builtins.readFile ./scripts/gtk-theme-set.sh);
|
|
||||||
qtThemeSet = pkgs.writeShellScriptBin "qt-theme-set" (builtins.readFile ./scripts/qt-theme-set.sh);
|
|
||||||
kittyThemeSet = pkgs.writeShellScriptBin "kitty-theme-set" (builtins.readFile ./scripts/kitty-theme-set.sh);
|
|
||||||
rofiThemeSet = pkgs.writeShellScriptBin "rofi-theme-set" (builtins.readFile ./scripts/rofi-theme-set.sh);
|
|
||||||
listCursorThemes = pkgs.writeShellScriptBin "list-cursor-themes" (builtins.readFile ./scripts/list-cursor-themes.sh);
|
|
||||||
cursorPreviewAll = pkgs.writeShellScriptBin "cursor-preview-all" (builtins.readFile ./scripts/cursor-preview-all.sh);
|
|
||||||
cursorSet = pkgs.writeShellScriptBin "cursor-set" (builtins.readFile ./scripts/cursor-set.sh);
|
|
||||||
screenshot = pkgs.writeShellScriptBin "screenshot" (builtins.readFile ./scripts/screenshot.sh);
|
|
||||||
hotspotStart = pkgs.writeShellScriptBin "hotspot-start" (builtins.readFile ./scripts/hotspot-start.sh);
|
hotspotStart = pkgs.writeShellScriptBin "hotspot-start" (builtins.readFile ./scripts/hotspot-start.sh);
|
||||||
systemTelemetry = pkgs.writeShellScriptBin "system-telemetry" (builtins.readFile ./scripts/system-telemetry.sh);
|
systemTelemetry = pkgs.writeShellScriptBin "system-telemetry" (builtins.readFile ./scripts/system-telemetry.sh);
|
||||||
weatherFetch = pkgs.writeShellScriptBin "weather-fetch" (builtins.readFile ./scripts/weather-fetch.sh);
|
weatherFetch = pkgs.writeShellScriptBin "weather-fetch" (builtins.readFile ./scripts/weather-fetch.sh);
|
||||||
|
|
@ -33,21 +18,6 @@ in
|
||||||
vaultToggle
|
vaultToggle
|
||||||
stripMetadata
|
stripMetadata
|
||||||
brightnessCtl
|
brightnessCtl
|
||||||
showDesktopToggle
|
|
||||||
wallpaperSet
|
|
||||||
wallpaperDelete
|
|
||||||
wallpaperRestore
|
|
||||||
wallpaperThumbs
|
|
||||||
displaySet
|
|
||||||
hyprThemeSet
|
|
||||||
gtkThemeSet
|
|
||||||
qtThemeSet
|
|
||||||
kittyThemeSet
|
|
||||||
rofiThemeSet
|
|
||||||
listCursorThemes
|
|
||||||
cursorPreviewAll
|
|
||||||
cursorSet
|
|
||||||
screenshot
|
|
||||||
hotspotStart
|
hotspotStart
|
||||||
systemTelemetry
|
systemTelemetry
|
||||||
weatherFetch
|
weatherFetch
|
||||||
|
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Extracts a small preview PNG (closest to 32px) for every installed
|
|
||||||
# cursor theme's "left_ptr" cursor into a cache directory, one per theme
|
|
||||||
# named "<theme>.png". Xcursor files are a binary format QML's Image
|
|
||||||
# element can't decode directly, so this is a one-time render step —
|
|
||||||
# already-cached themes are skipped.
|
|
||||||
#
|
|
||||||
# usage: cursor-preview-all <cache-dir>
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
cache_dir="${1:?usage: cursor-preview-all <cache-dir>}"
|
|
||||||
mkdir -p "$cache_dir"
|
|
||||||
|
|
||||||
dirs=(
|
|
||||||
"$HOME/.icons"
|
|
||||||
"$HOME/.local/share/icons"
|
|
||||||
"/run/current-system/sw/share/icons"
|
|
||||||
"/etc/profiles/per-user/$USER/share/icons"
|
|
||||||
)
|
|
||||||
|
|
||||||
render_one() {
|
|
||||||
local theme="$1"
|
|
||||||
local src="$2"
|
|
||||||
local out="$cache_dir/$theme.png"
|
|
||||||
[ -e "$out" ] && return 0
|
|
||||||
|
|
||||||
local tmp
|
|
||||||
tmp=$(mktemp -d)
|
|
||||||
trap 'rm -rf "$tmp"' RETURN
|
|
||||||
|
|
||||||
xcur2png -q -d "$tmp" -c "$tmp/conf" -i 1 "$src" > /dev/null 2>&1 || return 0
|
|
||||||
|
|
||||||
local best=""
|
|
||||||
local best_diff=999999
|
|
||||||
for png in "$tmp"/*.png; do
|
|
||||||
[ -e "$png" ] || continue
|
|
||||||
local w
|
|
||||||
w=$(identify -format "%w" "$png" 2>/dev/null || echo 0)
|
|
||||||
local diff=$(( w > 32 ? w - 32 : 32 - w ))
|
|
||||||
if [ "$diff" -lt "$best_diff" ]; then
|
|
||||||
best_diff=$diff
|
|
||||||
best="$png"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
[ -n "$best" ] && cp "$best" "$out"
|
|
||||||
}
|
|
||||||
|
|
||||||
for d in "${dirs[@]}"; do
|
|
||||||
[ -d "$d" ] || continue
|
|
||||||
for theme_dir in "$d"/*/; do
|
|
||||||
[ -f "${theme_dir}cursors/left_ptr" ] || continue
|
|
||||||
render_one "$(basename "$theme_dir")" "${theme_dir}cursors/left_ptr"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Applies a cursor theme immediately (hyprctl) and persists it across
|
|
||||||
# restarts by writing env vars to a file hyprland.conf sources on startup.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
theme="${1:?usage: cursor-set <theme-name> [size]}"
|
|
||||||
size="${2:-24}"
|
|
||||||
|
|
||||||
hyprctl setcursor "$theme" "$size" > /dev/null
|
|
||||||
|
|
||||||
mkdir -p "$HOME/.config/hypr"
|
|
||||||
cat > "$HOME/.config/hypr/cursor.conf" <<EOF
|
|
||||||
env = HYPRCURSOR_THEME,$theme
|
|
||||||
env = XCURSOR_THEME,$theme
|
|
||||||
env = XCURSOR_SIZE,$size
|
|
||||||
EOF
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Applies a full monitor descriptor immediately (hyprctl) AND persists it
|
|
||||||
# into ~/.config/hypr/display.conf, which hyprland.conf sources — without
|
|
||||||
# that file, hyprland.conf's generic fallback rule
|
|
||||||
# ("monitor = , preferred, auto, 1") is the only thing on record, and
|
|
||||||
# ANYTHING that makes Hyprland re-evaluate monitor state (observed: even
|
|
||||||
# changing the cursor theme via `hyprctl setcursor` does this) reapplies
|
|
||||||
# that fallback, silently dropping back to "preferred" refresh rate
|
|
||||||
# instead of whatever the user actually picked.
|
|
||||||
#
|
|
||||||
# Takes the monitor name and everything after it as one pre-built
|
|
||||||
# descriptor string (mode,position,scale + optional transform/bitdepth/
|
|
||||||
# cm/sdrbrightness/sdrsaturation/vrr/icc keyword,value pairs) rather than
|
|
||||||
# fixed positional args — Hyprland's own `monitor` keyword syntax grew
|
|
||||||
# past "mode/x/y/scale/transform" (color management, HDR, VRR, ICC), and
|
|
||||||
# the caller (LeftFlyout.qml's Displays panel) already has all of that as
|
|
||||||
# structured state, so it's simpler for it to build the one string than
|
|
||||||
# for this script to accept a growing positional argument list.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
mon="${1:?usage: display-set <monitor> <descriptor-tail>}"
|
|
||||||
descriptor_tail="${2:?descriptor required}"
|
|
||||||
|
|
||||||
descriptor="$mon,$descriptor_tail"
|
|
||||||
|
|
||||||
conf="$HOME/.config/hypr/display.conf"
|
|
||||||
mkdir -p "$(dirname "$conf")"
|
|
||||||
touch "$conf"
|
|
||||||
|
|
||||||
line="monitor = $descriptor"
|
|
||||||
if grep -q "^monitor = $mon," "$conf" 2>/dev/null; then
|
|
||||||
# mktemp in the same directory as $conf, not the default /tmp: /tmp is
|
|
||||||
# a different filesystem here, which makes `mv` below a non-atomic
|
|
||||||
# copy+unlink instead of a rename — Hyprland's file watcher (it
|
|
||||||
# watches every `source=`d file, including this one) could catch the
|
|
||||||
# destination mid-replace and its `source=` glob would find nothing,
|
|
||||||
# throwing "globbing error: found no match" (observed live after an
|
|
||||||
# orientation change, which always hits this branch since DP-1
|
|
||||||
# already has an existing line by then).
|
|
||||||
tmp=$(mktemp "$conf.XXXXXX")
|
|
||||||
awk -v prefix="monitor = $mon," -v newline="$line" '
|
|
||||||
index($0, prefix) == 1 { print newline; next } { print }
|
|
||||||
' "$conf" > "$tmp"
|
|
||||||
mv "$tmp" "$conf"
|
|
||||||
else
|
|
||||||
echo "$line" >> "$conf"
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl keyword monitor "$descriptor" > /dev/null
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Regenerates GTK3/4 CSS + settings.ini to match the current Quickshell
|
|
||||||
# theme (accent color + dark/light mode). These used to be static,
|
|
||||||
# Nix-rebuild-only files (symlinked straight into the store) — this makes
|
|
||||||
# them live-editable instead, the same way cursor.conf/display.conf work,
|
|
||||||
# so the Theme picker can actually reach GTK apps like Thunar.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
accent="${1:?usage: gtk-theme-set <accent> <bg> <bgAlt> <fg> <fgDim> <border> <dark:1|0>}"
|
|
||||||
bg="${2:?bg required}"
|
|
||||||
bg_alt="${3:?bgAlt required}"
|
|
||||||
fg="${4:?fg required}"
|
|
||||||
fg_dim="${5:?fgDim required}"
|
|
||||||
border="${6:?border required}"
|
|
||||||
dark="${7:?dark required}"
|
|
||||||
|
|
||||||
dir3="$HOME/.config/gtk-3.0"
|
|
||||||
dir4="$HOME/.config/gtk-4.0"
|
|
||||||
mkdir -p "$dir3" "$dir4"
|
|
||||||
|
|
||||||
css_file=$(mktemp)
|
|
||||||
cat > "$css_file" <<EOF
|
|
||||||
/* Squared corners + accent colors, generated by gtk-theme-set from the
|
|
||||||
Quickshell Theme picker — GTK's CSS is a subset of real CSS: no
|
|
||||||
!important, and zero still needs a unit.
|
|
||||||
|
|
||||||
The @define-color block matters more than it looks: plain selector
|
|
||||||
overrides (window, .background, .view, ...) only get you so far —
|
|
||||||
Thunar's icon-view content area and sidebar tree view turned out to
|
|
||||||
pull their actual fill color from these named theme variables
|
|
||||||
internally, not from the generic background-color cascade. Verified
|
|
||||||
live: without redefining theme_base_color specifically, the sidebar
|
|
||||||
and file grid stayed white no matter how broad the selector list got. */
|
|
||||||
@define-color theme_bg_color $bg;
|
|
||||||
@define-color theme_base_color $bg;
|
|
||||||
@define-color theme_fg_color $fg;
|
|
||||||
@define-color theme_text_color $fg;
|
|
||||||
@define-color theme_selected_bg_color $accent;
|
|
||||||
@define-color theme_selected_fg_color $bg;
|
|
||||||
@define-color borders $border;
|
|
||||||
|
|
||||||
* {
|
|
||||||
border-radius: 0px;
|
|
||||||
-gtk-outline-radius: 0px;
|
|
||||||
}
|
|
||||||
window, .background, .view, iconview, treeview, treeview.view, list, row,
|
|
||||||
notebook, notebook > stack, scrolledwindow, viewport {
|
|
||||||
background-color: $bg;
|
|
||||||
color: $fg;
|
|
||||||
}
|
|
||||||
iconview.cell, iconview text {
|
|
||||||
background-color: $bg;
|
|
||||||
color: $fg;
|
|
||||||
}
|
|
||||||
treeview.view:selected, row:selected, iconview:selected {
|
|
||||||
background-color: $accent;
|
|
||||||
color: $bg;
|
|
||||||
}
|
|
||||||
headerbar, .titlebar {
|
|
||||||
background-color: $bg_alt;
|
|
||||||
color: $fg;
|
|
||||||
border-color: $border;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
border-color: $border;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background-color: $bg_alt;
|
|
||||||
}
|
|
||||||
selection, *:selected {
|
|
||||||
background-color: $accent;
|
|
||||||
color: $bg;
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cp "$css_file" "$dir3/gtk.css"
|
|
||||||
cp "$css_file" "$dir4/gtk.css"
|
|
||||||
rm -f "$css_file"
|
|
||||||
|
|
||||||
prefer_dark="false"
|
|
||||||
theme_name="Adwaita"
|
|
||||||
icon_theme="Gruvbox-Plus-Light"
|
|
||||||
if [ "$dark" = "1" ]; then
|
|
||||||
prefer_dark="true"
|
|
||||||
theme_name="Adwaita-dark"
|
|
||||||
icon_theme="Gruvbox-Plus-Dark"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ini_file=$(mktemp)
|
|
||||||
cat > "$ini_file" <<EOF
|
|
||||||
[Settings]
|
|
||||||
gtk-application-prefer-dark-theme=$prefer_dark
|
|
||||||
gtk-theme-name=$theme_name
|
|
||||||
gtk-icon-theme-name=$icon_theme
|
|
||||||
gtk-font-name=Noto Sans, 10
|
|
||||||
gtk-enable-animations=true
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cp "$ini_file" "$dir3/settings.ini"
|
|
||||||
cp "$ini_file" "$dir4/settings.ini"
|
|
||||||
rm -f "$ini_file"
|
|
||||||
|
|
||||||
# settings.ini alone isn't enough: GTK3 binds gtk-theme-name/icon-theme-name
|
|
||||||
# to org.gnome.desktop.interface via GSettings whenever that schema and a
|
|
||||||
# dconf backend are present (they are here — dconf-service was already
|
|
||||||
# running), and that binding wins over settings.ini unconditionally. This
|
|
||||||
# machine's dconf database had hard values left over (gtk-theme='Breeze',
|
|
||||||
# icon-theme='breeze') that silently overrode every settings.ini edit —
|
|
||||||
# which is why neither accent/dark-mode nor the Gruvbox icon switch ever
|
|
||||||
# reached Thunar. Writing the same values to dconf keeps both sources in
|
|
||||||
# sync so it doesn't matter which one GTK actually reads from.
|
|
||||||
if command -v dconf >/dev/null 2>&1; then
|
|
||||||
color_scheme="default"
|
|
||||||
[ "$dark" = "1" ] && color_scheme="prefer-dark"
|
|
||||||
dconf write /org/gnome/desktop/interface/gtk-theme "'$theme_name'"
|
|
||||||
dconf write /org/gnome/desktop/interface/icon-theme "'$icon_theme'"
|
|
||||||
dconf write /org/gnome/desktop/interface/color-scheme "'$color_scheme'"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# GTK2 compat: separate config format (~/.gtkrc-2.0, not settings.ini),
|
|
||||||
# and no live theme-switching mechanism at all — GTK2 apps only read this
|
|
||||||
# at their own startup. Only icon theme and font actually follow the
|
|
||||||
# current theme; gnome-themes-extra's bundled GTK2 Adwaita engine has no
|
|
||||||
# dark variant to switch to (GTK2 predates that concept), so gtk-theme-
|
|
||||||
# name here is always the one real option, not a broken attempt at dark
|
|
||||||
# mode.
|
|
||||||
cat > "$HOME/.gtkrc-2.0" <<EOF
|
|
||||||
gtk-theme-name="Adwaita"
|
|
||||||
gtk-icon-theme-name="$icon_theme"
|
|
||||||
gtk-font-name="Noto Sans 10"
|
|
||||||
EOF
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Applies Hyprland's active/inactive window border colors immediately AND
|
|
||||||
# persists them into ~/.config/hypr/display.conf's sibling, theme.conf
|
|
||||||
# (sourced by hyprland.conf after the static general{} block, so it
|
|
||||||
# overrides those hardcoded defaults) — without that file, a config
|
|
||||||
# reload/restart would silently revert to whatever's hardcoded in
|
|
||||||
# hyprland.conf, same class of bug as the display-settings one.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
accent="${1:?usage: hypr-theme-set <accent_hex_no_hash> <border_hex_no_hash>}"
|
|
||||||
border="${2:?border required}"
|
|
||||||
|
|
||||||
hyprctl keyword general:col.active_border "rgba(${accent}ff)" > /dev/null
|
|
||||||
hyprctl keyword general:col.inactive_border "rgba(${border}ff)" > /dev/null
|
|
||||||
|
|
||||||
conf="$HOME/.config/hypr/theme.conf"
|
|
||||||
mkdir -p "$(dirname "$conf")"
|
|
||||||
cat > "$conf" <<EOF
|
|
||||||
general {
|
|
||||||
col.active_border = rgba(${accent}ff)
|
|
||||||
col.inactive_border = rgba(${border}ff)
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Regenerates ~/.config/kitty/theme.conf (included from kitty.conf) to
|
|
||||||
# match the current Quickshell theme. No explicit reload needed — kitty
|
|
||||||
# has a built-in config-file watcher (visible as `kitten __watch_conf__`
|
|
||||||
# in its process list) that also watches included files, so this takes
|
|
||||||
# effect live in already-open kitty windows.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
accent="${1:?usage: kitty-theme-set <accent> <bg> <bgAlt> <fg> <fgDim> <border>}"
|
|
||||||
bg="${2:?bg required}"
|
|
||||||
bg_alt="${3:?bgAlt required}"
|
|
||||||
fg="${4:?fg required}"
|
|
||||||
fg_dim="${5:?fgDim required}"
|
|
||||||
border="${6:?border required}"
|
|
||||||
|
|
||||||
conf="$HOME/.config/kitty/theme.conf"
|
|
||||||
mkdir -p "$(dirname "$conf")"
|
|
||||||
|
|
||||||
cat > "$conf" <<EOF
|
|
||||||
background $bg
|
|
||||||
foreground $fg
|
|
||||||
selection_background $border
|
|
||||||
selection_foreground $fg
|
|
||||||
cursor $fg
|
|
||||||
active_border_color $accent
|
|
||||||
inactive_border_color $border
|
|
||||||
active_tab_background $bg_alt
|
|
||||||
active_tab_foreground $fg
|
|
||||||
inactive_tab_background $bg
|
|
||||||
inactive_tab_foreground $fg_dim
|
|
||||||
EOF
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Lists installed Xcursor themes by scanning the real XCURSOR_PATH locations
|
|
||||||
# for a "cursors" subdirectory — no hardcoded theme names.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
dirs=(
|
|
||||||
"$HOME/.icons"
|
|
||||||
"$HOME/.local/share/icons"
|
|
||||||
"/run/current-system/sw/share/icons"
|
|
||||||
# Packages installed via users.users.<name>.packages (as opposed to
|
|
||||||
# environment.systemPackages) land here, not under
|
|
||||||
# /run/current-system/sw — this is where bibata-cursors actually is.
|
|
||||||
"/etc/profiles/per-user/$USER/share/icons"
|
|
||||||
)
|
|
||||||
|
|
||||||
for d in "${dirs[@]}"; do
|
|
||||||
[ -d "$d" ] || continue
|
|
||||||
for theme in "$d"/*/; do
|
|
||||||
[ -d "${theme}cursors" ] || continue
|
|
||||||
basename "$theme"
|
|
||||||
done
|
|
||||||
done | sort -u
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Regenerates qt6ct's config + color scheme to match the current Quickshell
|
|
||||||
# theme, the Qt-side equivalent of gtk-theme-set. Qt apps (Gwenview, Ark,
|
|
||||||
# Kate, ...) had no working platform theme plugin at all before this — the
|
|
||||||
# QT_QPA_PLATFORMTHEME=qt6ct env var (hyprland.conf) was pointing at a
|
|
||||||
# plugin that wasn't even installed, so every Qt app fell back to
|
|
||||||
# whatever default style/palette it shipped with, ignoring the rest of
|
|
||||||
# the desktop entirely. style=Fusion (not a KDE/Breeze style) matters
|
|
||||||
# specifically: Fusion paints straight from QPalette, so a custom color
|
|
||||||
# scheme here is guaranteed to actually show — Breeze-family styles
|
|
||||||
# prefer reading colors from kdeglobals instead, which is a separate,
|
|
||||||
# static, unrelated leftover file on this machine.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
accent="${1:?usage: qt-theme-set <accent> <bg> <bgAlt> <fg> <fgDim> <border> <dark:1|0>}"
|
|
||||||
bg="${2:?bg required}"
|
|
||||||
bg_alt="${3:?bgAlt required}"
|
|
||||||
fg="${4:?fg required}"
|
|
||||||
fg_dim="${5:?fgDim required}"
|
|
||||||
border="${6:?border required}"
|
|
||||||
dark="${7:?dark required}"
|
|
||||||
|
|
||||||
dir="$HOME/.config/qt6ct"
|
|
||||||
mkdir -p "$dir/colors"
|
|
||||||
|
|
||||||
# qt6ct color scheme files store QPalette::ColorRole values as a fixed-
|
|
||||||
# order, comma-separated #aarrggbb list — order and format verified
|
|
||||||
# against the example schemes qt6ct itself ships
|
|
||||||
# (share/qt6ct/colors/darker.conf etc.), not guessed: WindowText, Button,
|
|
||||||
# Light, Midlight, Dark, Mid, Text, BrightText, ButtonText, Base, Window,
|
|
||||||
# Shadow, Highlight, HighlightedText, Link, LinkVisited, AlternateBase,
|
|
||||||
# NoRole, ToolTipBase, ToolTipText, PlaceholderText.
|
|
||||||
a() { printf '#ff%s' "${1#\#}"; } # opaque
|
|
||||||
t() { printf '#80%s' "${1#\#}"; } # translucent, for PlaceholderText
|
|
||||||
|
|
||||||
active="$(a "$fg"), $(a "$bg_alt"), $(a "$border"), $(a "$bg_alt"), $(a "$bg"), $(a "$border"), $(a "$fg"), $(a "$fg"), $(a "$fg"), $(a "$bg"), $(a "$bg"), $(a "$border"), $(a "$accent"), $(a "$bg"), $(a "$accent"), $(a "$accent"), $(a "$bg_alt"), $(a "$bg"), $(a "$bg_alt"), $(a "$fg"), $(t "$fg_dim")"
|
|
||||||
disabled="$(a "$fg_dim"), $(a "$bg_alt"), $(a "$border"), $(a "$bg_alt"), $(a "$bg"), $(a "$border"), $(a "$fg_dim"), $(a "$fg_dim"), $(a "$fg_dim"), $(a "$bg"), $(a "$bg"), $(a "$border"), $(a "$accent"), $(a "$fg_dim"), $(a "$accent"), $(a "$accent"), $(a "$bg_alt"), $(a "$bg"), $(a "$bg_alt"), $(a "$fg_dim"), $(t "$fg_dim")"
|
|
||||||
|
|
||||||
color_file="$dir/colors/quickshell-theme.conf"
|
|
||||||
cat > "$color_file" <<EOF
|
|
||||||
[ColorScheme]
|
|
||||||
active_colors=$active
|
|
||||||
disabled_colors=$disabled
|
|
||||||
inactive_colors=$active
|
|
||||||
EOF
|
|
||||||
|
|
||||||
icon_theme="Gruvbox-Plus-Light"
|
|
||||||
[ "$dark" = "1" ] && icon_theme="Gruvbox-Plus-Dark"
|
|
||||||
|
|
||||||
cat > "$dir/qt6ct.conf" <<EOF
|
|
||||||
[Appearance]
|
|
||||||
style=Fusion
|
|
||||||
icon_theme=$icon_theme
|
|
||||||
custom_palette=true
|
|
||||||
color_scheme_path=$color_file
|
|
||||||
standard_dialogs=default
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# qt6ct's palette only reaches classic QWidget UI. Modern KDE apps
|
|
||||||
# (confirmed live: Gwenview) draw their toolbar/chrome with Kirigami/QML
|
|
||||||
# instead, which ignores QPalette entirely and reads kdeglobals' color
|
|
||||||
# groups directly via KColorScheme — so without this, qt6ct alone left
|
|
||||||
# exactly that chrome stuck on whatever this machine's stale leftover
|
|
||||||
# kdeglobals had (unrelated purple KDE colors from some earlier install),
|
|
||||||
# while everything else (dialogs, the actual image viewport, ...)
|
|
||||||
# correctly followed the theme.
|
|
||||||
rgb() {
|
|
||||||
local hex="${1#\#}"
|
|
||||||
printf '%d,%d,%d' "0x${hex:0:2}" "0x${hex:2:2}" "0x${hex:4:2}"
|
|
||||||
}
|
|
||||||
|
|
||||||
cat > "$HOME/.config/kdeglobals" <<EOF
|
|
||||||
[General]
|
|
||||||
ColorScheme=QuickshellTheme
|
|
||||||
widgetStyle=Fusion
|
|
||||||
|
|
||||||
[KDE]
|
|
||||||
widgetStyle=Fusion
|
|
||||||
|
|
||||||
[Colors:Window]
|
|
||||||
BackgroundNormal=$(rgb "$bg_alt")
|
|
||||||
ForegroundNormal=$(rgb "$fg")
|
|
||||||
|
|
||||||
[Colors:View]
|
|
||||||
BackgroundNormal=$(rgb "$bg")
|
|
||||||
ForegroundNormal=$(rgb "$fg")
|
|
||||||
|
|
||||||
[Colors:Button]
|
|
||||||
BackgroundNormal=$(rgb "$bg_alt")
|
|
||||||
ForegroundNormal=$(rgb "$fg")
|
|
||||||
|
|
||||||
[Colors:Selection]
|
|
||||||
BackgroundNormal=$(rgb "$accent")
|
|
||||||
ForegroundNormal=$(rgb "$bg")
|
|
||||||
DecorationFocus=$(rgb "$accent")
|
|
||||||
DecorationHover=$(rgb "$accent")
|
|
||||||
|
|
||||||
[Colors:Tooltip]
|
|
||||||
BackgroundNormal=$(rgb "$bg_alt")
|
|
||||||
ForegroundNormal=$(rgb "$fg")
|
|
||||||
|
|
||||||
[Colors:Header]
|
|
||||||
BackgroundNormal=$(rgb "$bg_alt")
|
|
||||||
ForegroundNormal=$(rgb "$fg")
|
|
||||||
EOF
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Regenerates ~/.config/rofi/theme.rasi (imported by config.rasi) to match
|
|
||||||
# the current Quickshell theme. Rofi has no config-file watcher — unlike
|
|
||||||
# kitty, it's launched on demand rather than staying resident, so the next
|
|
||||||
# time it's opened it just reads whatever is on disk. No reload step needed.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
accent="${1:?usage: rofi-theme-set <accent> <bg> <bgAlt> <fg> <fgDim> <border> <dark:1|0>}"
|
|
||||||
bg="${2:?bg required}"
|
|
||||||
bg_alt="${3:?bgAlt required}"
|
|
||||||
fg="${4:?fg required}"
|
|
||||||
fg_dim="${5:?fgDim required}"
|
|
||||||
border="${6:?border required}"
|
|
||||||
dark="${7:?dark required}"
|
|
||||||
|
|
||||||
icon_theme="Gruvbox-Plus-Light"
|
|
||||||
if [ "$dark" = "1" ]; then
|
|
||||||
icon_theme="Gruvbox-Plus-Dark"
|
|
||||||
fi
|
|
||||||
|
|
||||||
conf="$HOME/.config/rofi/theme.rasi"
|
|
||||||
mkdir -p "$(dirname "$conf")"
|
|
||||||
|
|
||||||
cat > "$conf" <<EOF
|
|
||||||
* {
|
|
||||||
bg: ${bg}ee;
|
|
||||||
bg-alt: ${bg_alt}ee;
|
|
||||||
fg: ${fg}ff;
|
|
||||||
accent: ${accent}ff;
|
|
||||||
border-color: ${border}ff;
|
|
||||||
placeholder: ${fg_dim}ff;
|
|
||||||
}
|
|
||||||
configuration {
|
|
||||||
icon-theme: "$icon_theme";
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Unified screenshot helper.
|
|
||||||
# screenshot <region|full|window> <clipboard|file|both>
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
mode="${1:-region}"
|
|
||||||
action="${2:-both}"
|
|
||||||
|
|
||||||
geom=()
|
|
||||||
case "$mode" in
|
|
||||||
region)
|
|
||||||
sel="$(slurp)" || exit 0
|
|
||||||
[ -z "$sel" ] && exit 0
|
|
||||||
geom=(-g "$sel")
|
|
||||||
;;
|
|
||||||
window)
|
|
||||||
win="$(hyprctl activewindow -j)"
|
|
||||||
at_x=$(echo "$win" | jq -r '.at[0]')
|
|
||||||
at_y=$(echo "$win" | jq -r '.at[1]')
|
|
||||||
w=$(echo "$win" | jq -r '.size[0]')
|
|
||||||
h=$(echo "$win" | jq -r '.size[1]')
|
|
||||||
geom=(-g "${at_x},${at_y} ${w}x${h}")
|
|
||||||
;;
|
|
||||||
full)
|
|
||||||
geom=()
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "unknown mode: $mode (expected region|full|window)" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
mkdir -p "$HOME/Screenshots"
|
|
||||||
file="$HOME/Screenshots/$(date +%Y-%m-%d_%H-%M-%S).png"
|
|
||||||
|
|
||||||
case "$action" in
|
|
||||||
clipboard)
|
|
||||||
grim "${geom[@]}" - | wl-copy
|
|
||||||
;;
|
|
||||||
file)
|
|
||||||
grim "${geom[@]}" "$file"
|
|
||||||
;;
|
|
||||||
both)
|
|
||||||
grim "${geom[@]}" "$file"
|
|
||||||
wl-copy < "$file"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "unknown action: $action (expected clipboard|file|both)" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# "Show desktop" for Hyprland: stashes every window on the active workspace
|
|
||||||
# into a hidden special workspace, and restores each one to the workspace
|
|
||||||
# it came from on the next press — regardless of which workspace you're on
|
|
||||||
# when you press it again (a single global state file, not one keyed by
|
|
||||||
# "current workspace", which broke the moment you switched workspaces
|
|
||||||
# in between).
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
state_file="${XDG_RUNTIME_DIR:-/tmp}/hypr-show-desktop.json"
|
|
||||||
|
|
||||||
if [ -s "$state_file" ]; then
|
|
||||||
jq -c '.[]' "$state_file" | while IFS= read -r entry; do
|
|
||||||
addr=$(echo "$entry" | jq -r '.address')
|
|
||||||
ws=$(echo "$entry" | jq -r '.workspace')
|
|
||||||
hyprctl dispatch movetoworkspacesilent "$ws,address:$addr" > /dev/null
|
|
||||||
done
|
|
||||||
rm -f "$state_file"
|
|
||||||
else
|
|
||||||
ws_id=$(hyprctl activeworkspace -j | jq -r '.id')
|
|
||||||
hyprctl clients -j | jq -c --argjson ws "$ws_id" \
|
|
||||||
'[.[] | select(.workspace.id == $ws) | {address: .address, workspace: $ws}]' > "$state_file"
|
|
||||||
jq -r '.[].address' "$state_file" | while IFS= read -r addr; do
|
|
||||||
[ -n "$addr" ] && hyprctl dispatch movetoworkspacesilent "special:showdesktop,address:$addr" > /dev/null
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Removes a wallpaper by moving it to the freedesktop.org trash
|
|
||||||
# (~/.local/share/Trash), the same mechanism Thunar's own delete uses —
|
|
||||||
# so it shows up in Thunar's Trash and can be restored from there,
|
|
||||||
# instead of `rm` permanently destroying it with no way back.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
path="${1:?usage: wallpaper-delete <path>}"
|
|
||||||
[ -e "$path" ] || exit 0
|
|
||||||
|
|
||||||
trash_dir="$HOME/.local/share/Trash"
|
|
||||||
mkdir -p "$trash_dir/files" "$trash_dir/info"
|
|
||||||
|
|
||||||
name="$(basename "$path")"
|
|
||||||
dest="$trash_dir/files/$name"
|
|
||||||
info="$trash_dir/info/$name.trashinfo"
|
|
||||||
n=1
|
|
||||||
while [ -e "$dest" ] || [ -e "$info" ]; do
|
|
||||||
dest="$trash_dir/files/${name}.$n"
|
|
||||||
info="$trash_dir/info/${name}.$n.trashinfo"
|
|
||||||
n=$((n + 1))
|
|
||||||
done
|
|
||||||
|
|
||||||
cat > "$info" <<EOF
|
|
||||||
[Trash Info]
|
|
||||||
Path=$path
|
|
||||||
DeletionDate=$(date +%Y-%m-%dT%H:%M:%S)
|
|
||||||
EOF
|
|
||||||
|
|
||||||
mv "$path" "$dest"
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Reapplies the last-set wallpaper on login/reboot. hyprpaper and mpvpaper
|
|
||||||
# both start every session with nothing loaded, and wallpaper-set only ever
|
|
||||||
# ran through live IPC with no persistence of its own — without this the
|
|
||||||
# desktop silently reverted to blank on every fresh login. Run via
|
|
||||||
# exec-once in hyprland.conf, after hyprpaper.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
state_file="$HOME/.config/hypr/wallpaper.conf"
|
|
||||||
[ -f "$state_file" ] || exit 0
|
|
||||||
|
|
||||||
# hyprpaper's IPC socket isn't guaranteed ready the instant exec-once fires
|
|
||||||
# it, since exec-once entries all start back-to-back with no ordering
|
|
||||||
# guarantee beyond process spawn order.
|
|
||||||
for _ in $(seq 1 50); do
|
|
||||||
hyprctl hyprpaper listactive > /dev/null 2>&1 && break
|
|
||||||
sleep 0.1
|
|
||||||
done
|
|
||||||
|
|
||||||
bash "$state_file"
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Sets the desktop wallpaper — static images go through hyprpaper's IPC,
|
|
||||||
# video files loop via mpvpaper. Only one backend should ever be driving
|
|
||||||
# the background at a time, so switching to one always tears the other
|
|
||||||
# down first.
|
|
||||||
#
|
|
||||||
# usage: wallpaper-set <path> [fit_mode] [muted] [speed] [auto_pause]
|
|
||||||
# fit_mode: cover|contain (static images only, default cover)
|
|
||||||
# muted: 1|0 (animated only, default 1)
|
|
||||||
# speed: playback speed (animated only, default 1.0)
|
|
||||||
# auto_pause: 1|0 (animated only — mpvpaper -p, pauses
|
|
||||||
# playback while the wallpaper is hidden
|
|
||||||
# behind a fullscreen window, default 1)
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
image="${1:?usage: wallpaper-set <path> [fit_mode] [muted] [speed] [auto_pause]}"
|
|
||||||
fit_mode="${2:-cover}"
|
|
||||||
muted="${3:-1}"
|
|
||||||
speed="${4:-1.0}"
|
|
||||||
auto_pause="${5:-1}"
|
|
||||||
|
|
||||||
case "${image,,}" in
|
|
||||||
*.mp4|*.webm|*.mkv|*.mov|*.gif)
|
|
||||||
# -9: plain SIGTERM doesn't reliably kill mpvpaper (verified live —
|
|
||||||
# it survives pkill -f without -9), which would leave the old
|
|
||||||
# instance still holding the layer-shell surface underneath a new
|
|
||||||
# one. The short sleep gives it a moment to actually release that
|
|
||||||
# surface before a replacement tries to claim it.
|
|
||||||
pkill -9 -f mpvpaper > /dev/null 2>&1 || true
|
|
||||||
sleep 0.3
|
|
||||||
|
|
||||||
mpv_opts="loop-playlist=inf speed=$speed"
|
|
||||||
[ "$muted" = "1" ] && mpv_opts="no-audio $mpv_opts"
|
|
||||||
|
|
||||||
mpvpaper_flags=()
|
|
||||||
[ "$auto_pause" = "1" ] && mpvpaper_flags+=("-p")
|
|
||||||
|
|
||||||
# setsid + disown so mpvpaper (which runs forever, looping the
|
|
||||||
# video) survives after this script exits.
|
|
||||||
setsid mpvpaper "${mpvpaper_flags[@]}" -o "$mpv_opts" '*' "$image" \
|
|
||||||
> /dev/null 2>&1 < /dev/null &
|
|
||||||
disown
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
# -9: plain SIGTERM doesn't reliably kill mpvpaper (verified live —
|
|
||||||
# it survives pkill -f without -9), which would leave the old
|
|
||||||
# instance still holding the layer-shell surface underneath a new
|
|
||||||
# one. The short sleep gives it a moment to actually release that
|
|
||||||
# surface before a replacement tries to claim it.
|
|
||||||
pkill -9 -f mpvpaper > /dev/null 2>&1 || true
|
|
||||||
sleep 0.3
|
|
||||||
# This hyprpaper version's IPC only exposes a single `wallpaper`
|
|
||||||
# request (confirmed via `hyprctl hyprpaper --help`) — it loads
|
|
||||||
# and swaps the image in one call. preload/unload/listloaded
|
|
||||||
# requests it used to need don't exist anymore and error out with
|
|
||||||
# "invalid hyprpaper request", which — combined with `set -e` —
|
|
||||||
# was aborting this script before it ever got here.
|
|
||||||
hyprctl hyprpaper wallpaper ",$image,$fit_mode" > /dev/null
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Persist so the wallpaper survives a reboot/relogin. hyprpaper/mpvpaper
|
|
||||||
# both start every session with nothing loaded — this was previously only
|
|
||||||
# ever set at runtime via IPC, so the desktop silently reverted to blank
|
|
||||||
# every fresh login. wallpaper-restore (exec-once in hyprland.conf, after
|
|
||||||
# hyprpaper starts) re-runs the exact command saved here.
|
|
||||||
state_file="$HOME/.config/hypr/wallpaper.conf"
|
|
||||||
mkdir -p "$(dirname "$state_file")"
|
|
||||||
printf 'wallpaper-set %q %q %q %q %q\n' "$image" "$fit_mode" "$muted" "$speed" "$auto_pause" > "$state_file"
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Generates (or reuses) a cached preview JPEG for each animated wallpaper
|
|
||||||
# — first frame ~1s in, to skip a common black/fade intro on most clips —
|
|
||||||
# so the Wallpapers panel can show a real thumbnail instead of a generic
|
|
||||||
# play icon. Prints "<source path>\t<thumbnail path>" per file on stdout
|
|
||||||
# once it's ready, so the caller (LeftFlyout.qml) never has to
|
|
||||||
# independently reconstruct the cache filename — it's a hash of the
|
|
||||||
# source path, chosen only to keep filenames short/safe, not something
|
|
||||||
# QML needs to know how to derive.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
cache_dir="${1:?usage: wallpaper-thumbs <cache-dir> <file...>}"
|
|
||||||
shift
|
|
||||||
mkdir -p "$cache_dir"
|
|
||||||
|
|
||||||
for file in "$@"; do
|
|
||||||
[ -f "$file" ] || continue
|
|
||||||
name="$(printf '%s' "$file" | sha1sum | cut -d' ' -f1)"
|
|
||||||
thumb="$cache_dir/$name.jpg"
|
|
||||||
if [ ! -f "$thumb" ] || [ "$file" -nt "$thumb" ]; then
|
|
||||||
ffmpeg -y -ss 00:00:01 -i "$file" -frames:v 1 -vf "scale=320:-1" "$thumb" \
|
|
||||||
> /dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
[ -f "$thumb" ] && printf '%s\t%s\n' "$file" "$thumb"
|
|
||||||
done
|
|
||||||
|
|
@ -58,6 +58,36 @@
|
||||||
alsa.enable = true;
|
alsa.enable = true;
|
||||||
alsa.support32Bit = true;
|
alsa.support32Bit = true;
|
||||||
pulse.enable = true;
|
pulse.enable = true;
|
||||||
|
|
||||||
|
# Standalone "Virtual Microphone" device, fully independent of the real
|
||||||
|
# hardware mic — always present regardless of what's running, so apps
|
||||||
|
# like Discord keep defaulting to the real mic untouched, and only pick
|
||||||
|
# this up if explicitly selected. Feed anything into "Virtual
|
||||||
|
# Microphone Sink" (e.g. via qpwgraph/helvum, routing EasyEffects' or
|
||||||
|
# Soundux's output to it) and it comes out the other side as "Virtual
|
||||||
|
# Microphone", selectable like any other input device.
|
||||||
|
extraConfig.pipewire."99-virtual-microphone" = {
|
||||||
|
"context.modules" = [
|
||||||
|
{
|
||||||
|
name = "libpipewire-module-loopback";
|
||||||
|
args = {
|
||||||
|
"node.description" = "Virtual Microphone";
|
||||||
|
"capture.props" = {
|
||||||
|
"node.name" = "virtual_mic_sink";
|
||||||
|
"node.description" = "Virtual Microphone Sink";
|
||||||
|
"media.class" = "Audio/Sink";
|
||||||
|
"audio.position" = "FL,FR";
|
||||||
|
};
|
||||||
|
"playback.props" = {
|
||||||
|
"node.name" = "virtual_mic_source";
|
||||||
|
"node.description" = "Virtual Microphone";
|
||||||
|
"media.class" = "Audio/Source";
|
||||||
|
"audio.position" = "FL,FR";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
hardware.bluetooth = {
|
hardware.bluetooth = {
|
||||||
|
|
@ -168,6 +198,15 @@
|
||||||
"net.ipv6.conf.default.accept_redirects" = false;
|
"net.ipv6.conf.default.accept_redirects" = false;
|
||||||
"net.ipv4.conf.all.send_redirects" = false;
|
"net.ipv4.conf.all.send_redirects" = false;
|
||||||
"net.ipv4.conf.default.send_redirects" = false;
|
"net.ipv4.conf.default.send_redirects" = false;
|
||||||
|
# Restrict ptrace to a process's own children — blocks a common local
|
||||||
|
# credential-scraping/process-injection vector between unrelated
|
||||||
|
# processes. Only bites ad-hoc `gdb -p <pid>`-style attaches to a
|
||||||
|
# process that isn't your debugger's own child.
|
||||||
|
"kernel.yama.ptrace_scope" = 1;
|
||||||
|
# Drop the kernel-uptime-derived TCP timestamp from outgoing packets
|
||||||
|
# (a passive fingerprinting/uptime-leak vector). Negligible throughput
|
||||||
|
# impact on a normal home connection.
|
||||||
|
"net.ipv4.tcp_timestamps" = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Crash dumps can contain decrypted memory contents, passwords, keys —
|
# Crash dumps can contain decrypted memory contents, passwords, keys —
|
||||||
|
|
@ -201,4 +240,16 @@
|
||||||
networking.networkmanager.wifi.macAddress = "random";
|
networking.networkmanager.wifi.macAddress = "random";
|
||||||
networking.networkmanager.wifi.scanRandMacAddress = true;
|
networking.networkmanager.wifi.scanRandMacAddress = true;
|
||||||
networking.networkmanager.ethernet.macAddress = "stable";
|
networking.networkmanager.ethernet.macAddress = "stable";
|
||||||
|
|
||||||
|
# Disable NetworkManager's periodic external connectivity probe (normally
|
||||||
|
# pings a canary URL to detect captive portals) — one less routine
|
||||||
|
# external phone-home, no functional loss on a directly-connected desktop.
|
||||||
|
networking.networkmanager.settings.connectivity = {
|
||||||
|
uri = "";
|
||||||
|
interval = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Budgie's module turns this on by default (for Budgie Control Center's
|
||||||
|
# Location Services panel) — nothing in this config needs location data.
|
||||||
|
services.geoclue2.enable = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
{ config, pkgs, lib, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
# Dark GTK theme (built into GTK3/4's Adwaita since 3.20) for Thunar and
|
|
||||||
# other GTK apps, as a starting point. The actual palette (accent color,
|
|
||||||
# dark/light) is no longer static here — it's generated at runtime by
|
|
||||||
# gtk-theme-set (modules/scripts/gtk-theme-set.sh), called from the
|
|
||||||
# Quickshell Theme picker (dotfiles/quickshell/Theme.qml) every time the
|
|
||||||
# theme changes, so switching accent/mode there actually reaches GTK
|
|
||||||
# apps instead of requiring a rebuild.
|
|
||||||
environment.systemPackages = with pkgs; [
|
|
||||||
gnome-themes-extra
|
|
||||||
adwaita-icon-theme
|
|
||||||
gruvbox-plus-icons
|
|
||||||
dconf
|
|
||||||
# Qt's side of the same live-theming pipeline as GTK above — see
|
|
||||||
# qt-theme-set (modules/scripts/qt-theme-set.sh). Without this
|
|
||||||
# installed, QT_QPA_PLATFORMTHEME=qt6ct (hyprland.conf) pointed at a
|
|
||||||
# platform theme plugin that didn't exist, so every Qt app (Gwenview,
|
|
||||||
# Ark, Kate, ...) silently fell back to its own default style/palette
|
|
||||||
# instead of anything resembling the rest of the desktop.
|
|
||||||
qt6Packages.qt6ct
|
|
||||||
kdePackages.qqc2-desktop-style # QQC2 style needed for QT_QUICK_CONTROLS_STYLE=
|
|
||||||
# org.kde.desktop (hyprland.conf) to actually
|
|
||||||
# theme Kirigami-based apps' chrome
|
|
||||||
];
|
|
||||||
|
|
||||||
# No environment.sessionVariables.GTK_THEME here on purpose: GTK_THEME
|
|
||||||
# takes priority over settings.ini's gtk-theme-name for GTK3 apps, and
|
|
||||||
# as a session env var it's fixed at login — it can't be changed live.
|
|
||||||
# A leftover "Adwaita:dark" override here was silently overriding
|
|
||||||
# whatever gtk-theme-set wrote to settings.ini, permanently forcing
|
|
||||||
# dark mode and blocking the Theme picker from ever reaching Thunar.
|
|
||||||
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
"d /home/nexoniarz/.config/gtk-3.0 0755 nexoniarz users -"
|
|
||||||
"d /home/nexoniarz/.config/gtk-4.0 0755 nexoniarz users -"
|
|
||||||
"d /home/nexoniarz/.config/qt6ct 0755 nexoniarz users -"
|
|
||||||
"d /home/nexoniarz/.config/qt6ct/colors 0755 nexoniarz users -"
|
|
||||||
# `f`, not `L+`: created empty only if missing, never overwritten on
|
|
||||||
# rebuild — gtk-theme-set/qt-theme-set own the actual content from
|
|
||||||
# here on.
|
|
||||||
"f /home/nexoniarz/.config/gtk-3.0/gtk.css 0644 nexoniarz users - -"
|
|
||||||
"f /home/nexoniarz/.config/gtk-4.0/gtk.css 0644 nexoniarz users - -"
|
|
||||||
"f /home/nexoniarz/.config/gtk-3.0/settings.ini 0644 nexoniarz users - -"
|
|
||||||
"f /home/nexoniarz/.config/gtk-4.0/settings.ini 0644 nexoniarz users - -"
|
|
||||||
"f /home/nexoniarz/.gtkrc-2.0 0644 nexoniarz users - -"
|
|
||||||
"f /home/nexoniarz/.config/qt6ct/qt6ct.conf 0644 nexoniarz users - -"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue