mirror of
https://github.com/Nexoniarz/nexoniarz-config-nixos.git
synced 2026-11-03 04:24:27 +00:00
nixos config
This commit is contained in:
commit
5dc9fb020e
46 changed files with 6194 additions and 0 deletions
60
dotfiles/kitty/kitty.conf
Normal file
60
dotfiles/kitty/kitty.conf
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# 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
|
||||
30
dotfiles/mako/config
Normal file
30
dotfiles/mako/config
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# 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
|
||||
352
dotfiles/quickshell/BottomBar.qml
Normal file
352
dotfiles/quickshell/BottomBar.qml
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
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
|
||||
}
|
||||
|
||||
// --- 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: rightToggle.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
dotfiles/quickshell/CloseButton.qml
Normal file
31
dotfiles/quickshell/CloseButton.qml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
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()
|
||||
}
|
||||
}
|
||||
36
dotfiles/quickshell/DeleteTileButton.qml
Normal file
36
dotfiles/quickshell/DeleteTileButton.qml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
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()
|
||||
}
|
||||
}
|
||||
31
dotfiles/quickshell/DigitalClock.qml
Normal file
31
dotfiles/quickshell/DigitalClock.qml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
33
dotfiles/quickshell/IconButton.qml
Normal file
33
dotfiles/quickshell/IconButton.qml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
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()
|
||||
}
|
||||
}
|
||||
162
dotfiles/quickshell/LeftBar.qml
Normal file
162
dotfiles/quickshell/LeftBar.qml
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
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 = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
1799
dotfiles/quickshell/LeftFlyout.qml
Normal file
1799
dotfiles/quickshell/LeftFlyout.qml
Normal file
File diff suppressed because it is too large
Load diff
176
dotfiles/quickshell/PolkitDialog.qml
Normal file
176
dotfiles/quickshell/PolkitDialog.qml
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
218
dotfiles/quickshell/RightBar.qml
Normal file
218
dotfiles/quickshell/RightBar.qml
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
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
|
||||
source: 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 {
|
||||
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 = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
1485
dotfiles/quickshell/RightFlyout.qml
Normal file
1485
dotfiles/quickshell/RightFlyout.qml
Normal file
File diff suppressed because it is too large
Load diff
36
dotfiles/quickshell/SidebarRow.qml
Normal file
36
dotfiles/quickshell/SidebarRow.qml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
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()
|
||||
}
|
||||
}
|
||||
48
dotfiles/quickshell/SquareSlider.qml
Normal file
48
dotfiles/quickshell/SquareSlider.qml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
142
dotfiles/quickshell/Theme.qml
Normal file
142
dotfiles/quickshell/Theme.qml
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
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;
|
||||
|
||||
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: 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
|
||||
}
|
||||
41
dotfiles/quickshell/ToggleSwitch.qml
Normal file
41
dotfiles/quickshell/ToggleSwitch.qml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
27
dotfiles/quickshell/shell.qml
Normal file
27
dotfiles/quickshell/shell.qml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
//@ 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
|
||||
|
||||
ShellRoot {
|
||||
PolkitDialog {}
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
115
dotfiles/rofi/config.rasi
Normal file
115
dotfiles/rofi/config.rasi
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/* 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;
|
||||
}
|
||||
Loading…
Reference in a new issue