Compare commits
5 Commits
75aa6b945b
...
7cb7f10ff6
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cb7f10ff6 | |||
| 88484f8495 | |||
| 1babf2e334 | |||
| cc78daea77 | |||
| 0415804b67 |
@@ -46,6 +46,11 @@ stow -d ~/Projects/dotfiles/ -t ~/ dotfiles
|
|||||||
|
|
||||||
This will sync wallpapers/config files to where they are usually located
|
This will sync wallpapers/config files to where they are usually located
|
||||||
|
|
||||||
|
System-level config (e.g. pacman options) lives in `system/` and is installed with sudo:
|
||||||
|
```console
|
||||||
|
~/Projects/dotfiles/system/install.sh
|
||||||
|
```
|
||||||
|
|
||||||
## 📦 Programs
|
## 📦 Programs
|
||||||
|
|
||||||
List of programs and tools I use.
|
List of programs and tools I use.
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ hl.on("hyprland.start", function()
|
|||||||
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
|
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
|
||||||
hl.exec_cmd("systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
|
hl.exec_cmd("systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
|
||||||
|
|
||||||
-- Sunshine
|
-- Sunshine (virtual monitor only on desktop; skipped on laptops, detected by a system battery)
|
||||||
hl.exec_cmd("sleep 1 && hyprctl output create headless")
|
hl.exec_cmd("sleep 1 && ! ls -d /sys/class/power_supply/BAT* >/dev/null 2>&1 && hyprctl output create headless")
|
||||||
|
|
||||||
-- Polkit (Polkit Gnome / KDE)
|
-- Polkit (Polkit Gnome / KDE)
|
||||||
hl.exec_cmd(scriptsDir .. "/Polkit.sh")
|
hl.exec_cmd(scriptsDir .. "/Polkit.sh")
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
accent_primary = "#007acc"
|
|
||||||
accent_secondary = "#06b6d4"
|
|
||||||
background = "#1e1e1e"
|
|
||||||
foreground = "#d4d4d8"
|
|
||||||
opacity = 0.91
|
|
||||||
@@ -222,6 +222,82 @@ Item {
|
|||||||
root.passwordSsid = ""
|
root.passwordSsid = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Captive portal ---------------------------------------------------
|
||||||
|
// NetworkManager tells "portal" (a walled-garden sign-in page sits
|
||||||
|
// between us and the real internet) apart from "full" connectivity only
|
||||||
|
// via an active probe — nmcli's cached CONNECTIVITY field just reflects
|
||||||
|
// NM's own internal check, which runs every few minutes and is far too
|
||||||
|
// slow to catch a portal right after joining a network. So this forces a
|
||||||
|
// fresh probe itself, same shell-out approach as the stats below, and
|
||||||
|
// runs it whenever we're connected — not gated to panel.visible like
|
||||||
|
// those — since the whole point is noticing this before the popup is
|
||||||
|
// ever opened.
|
||||||
|
//
|
||||||
|
// "limited" is treated the same as "portal", not just "full" vs.
|
||||||
|
// everything-else: NM only reports "portal" when the probe request
|
||||||
|
// actually comes back *different* (redirected/rewritten) from what it
|
||||||
|
// expected. Plenty of real captive portals instead black-hole every
|
||||||
|
// request that isn't to their own login domain, so the probe just times
|
||||||
|
// out — which is also why this is slow to resolve — and NM reports
|
||||||
|
// "limited" (it got a route/gateway, but the internet check itself
|
||||||
|
// never landed) rather than "portal". From the user's side both look
|
||||||
|
// identical: connected, but no real internet.
|
||||||
|
property string connectivityState: "unknown"
|
||||||
|
readonly property bool connectivityRestricted: root.connectivityState === "portal" || root.connectivityState === "limited"
|
||||||
|
|
||||||
|
// A plain, unencrypted HTTP page whose only job is to be interceptable
|
||||||
|
// by a captive portal's redirect. An https:// URL mostly just gets
|
||||||
|
// silently blocked instead of redirected, which looks identical to the
|
||||||
|
// portal not existing at all.
|
||||||
|
readonly property string captivePortalCheckUrl: "http://neverssl.com/"
|
||||||
|
|
||||||
|
// Auto-opened once per network join (mirrors TailscaleIndicator's
|
||||||
|
// openAuthUrl/_authUrlOpened) so flapping connectivity on the same
|
||||||
|
// network doesn't keep popping a new browser tab — the manual "Open"
|
||||||
|
// button in the panel banner below still works regardless.
|
||||||
|
property bool _portalAutoOpened: false
|
||||||
|
property bool _wasConnected: false
|
||||||
|
onConnectedDeviceChanged: {
|
||||||
|
const isConnected = root.connectedDevice !== null
|
||||||
|
if (isConnected && !root._wasConnected) {
|
||||||
|
root._portalAutoOpened = false
|
||||||
|
root.connectivityState = "unknown"
|
||||||
|
}
|
||||||
|
root._wasConnected = isConnected
|
||||||
|
}
|
||||||
|
|
||||||
|
onConnectivityRestrictedChanged: {
|
||||||
|
if (root.connectivityRestricted && !root._portalAutoOpened)
|
||||||
|
root.openCaptivePortal()
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCaptivePortal() {
|
||||||
|
root._portalAutoOpened = true
|
||||||
|
Qt.openUrlExternally(root.captivePortalCheckUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: connectivityProc
|
||||||
|
command: ["nmcli", "networking", "connectivity", "check"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: connectivityCollector
|
||||||
|
onStreamFinished: root.connectivityState = connectivityCollector.text.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: connectivityTimer
|
||||||
|
// Poll quickly while things are still unresolved (fresh join, or an
|
||||||
|
// active portal we're waiting to clear) and back off once
|
||||||
|
// connectivity is confirmed full, rather than shelling out every
|
||||||
|
// few seconds forever for no reason.
|
||||||
|
interval: root.connectivityState === "full" ? 20000 : 3000
|
||||||
|
repeat: true
|
||||||
|
triggeredOnStart: true
|
||||||
|
running: root.connectedDevice !== null
|
||||||
|
onTriggered: if (!connectivityProc.running) connectivityProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
// --- Live stats --------------------------------------------------
|
// --- Live stats --------------------------------------------------
|
||||||
// None of this comes from Quickshell's Networking API — it only knows
|
// None of this comes from Quickshell's Networking API — it only knows
|
||||||
// about devices/networks, not throughput, ping, or routing. So this
|
// about devices/networks, not throughput, ping, or routing. So this
|
||||||
@@ -595,6 +671,34 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Small warning badge over the icon's corner when the connection looks
|
||||||
|
// restricted (a captive portal, confirmed or just suspected — see
|
||||||
|
// connectivityRestricted above) — same treatment as TailscaleIndicator's
|
||||||
|
// login-needed badge, so "this needs your attention" reads the same way
|
||||||
|
// across indicators.
|
||||||
|
Rectangle {
|
||||||
|
visible: root.connectivityRestricted
|
||||||
|
width: Math.max(7, label.font.pixelSize * 0.42)
|
||||||
|
height: width
|
||||||
|
radius: width / 2
|
||||||
|
color: Colors.urgent
|
||||||
|
border.color: Colors.surface
|
||||||
|
border.width: 1
|
||||||
|
anchors.right: label.right
|
||||||
|
anchors.bottom: label.bottom
|
||||||
|
anchors.rightMargin: -1
|
||||||
|
anchors.bottomMargin: -1
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "!"
|
||||||
|
color: Colors.surface
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Math.max(6, parent.height * 0.75)
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
@@ -890,6 +994,88 @@ Item {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Captive portal banner — shown when the connectivity probe
|
||||||
|
// above finds either a confirmed sign-in page or a
|
||||||
|
// "limited" result standing between this network and the
|
||||||
|
// real internet (see connectivityRestricted). openCaptivePortal()
|
||||||
|
// already fired once automatically when this was first
|
||||||
|
// detected; this stays up as a manual retry in case that tab
|
||||||
|
// got closed.
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
visible: root.connectivityRestricted
|
||||||
|
radius: 0
|
||||||
|
color: Qt.rgba(Colors.urgent.r, Colors.urgent.g, Colors.urgent.b, 0.18)
|
||||||
|
border.color: Colors.urgent
|
||||||
|
border.width: 1
|
||||||
|
implicitHeight: portalBannerRow.implicitHeight + 12
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: portalBannerRow
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 6
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "lock_open"
|
||||||
|
font.family: Appearance.iconFontFamilyRounded
|
||||||
|
font.variableAxes: Appearance.iconFontAxes
|
||||||
|
font.pixelSize: 16
|
||||||
|
color: Colors.urgent
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Text {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.connectivityState === "portal" ? "Sign-in required" : "No internet access"
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Appearance.fontSize
|
||||||
|
font.bold: true
|
||||||
|
color: Colors.foreground
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.connectivityState === "portal"
|
||||||
|
? "This network needs a web sign-in first"
|
||||||
|
: "This network may need a web sign-in first"
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Appearance.fontSizeSmall
|
||||||
|
color: Colors.muted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
implicitWidth: openPortalText.implicitWidth + 16
|
||||||
|
implicitHeight: 24
|
||||||
|
radius: 0
|
||||||
|
color: openPortalMouse.containsMouse ? Colors.urgent : "transparent"
|
||||||
|
border.color: Colors.urgent
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: openPortalText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "Open"
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Appearance.fontSizeSmall
|
||||||
|
color: openPortalMouse.containsMouse ? Colors.primaryText : Colors.urgent
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
id: openPortalMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.openCaptivePortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Live stats — ping/loss/throughput/IP/gateway for whichever
|
// Live stats — ping/loss/throughput/IP/gateway for whichever
|
||||||
// device is currently connected.
|
// device is currently connected.
|
||||||
GridLayout {
|
GridLayout {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"tooltip-format": "{ipaddr} via {gwaddr}",
|
"tooltip-format": "{ipaddr} via {gwaddr}",
|
||||||
"tooltip-format-disconnected": "Disconnected",
|
"tooltip-format-disconnected": "Disconnected",
|
||||||
"max-length": 50,
|
"max-length": 50,
|
||||||
"on-click": "orbit toggle top-right",
|
"on-click": "nm-connection-editor",
|
||||||
"on-click-right": "kitty -e nmtui",
|
"on-click-right": "kitty -e nmtui",
|
||||||
"format-icons": {
|
"format-icons": {
|
||||||
"wifi": ["", "", "", "", ""],
|
"wifi": ["", "", "", "", ""],
|
||||||
|
|||||||
13
dotfiles/.local/bin/Plex
Executable file
13
dotfiles/.local/bin/Plex
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Wrapper for the plex-desktop package's /usr/bin/Plex (-> /opt/plex-desktop/Plex.sh).
|
||||||
|
#
|
||||||
|
# QT_QUICK_CONTROLS_STYLE=org.hyprland.style is set globally in
|
||||||
|
# hypr/core/env.lua for other Qt/QML apps' theming (hyprland-qt-support), but
|
||||||
|
# Plex Desktop bundles its own private Qt/QML runtime that doesn't ship that
|
||||||
|
# style plugin — inheriting the var makes it fail to load its UI and exit
|
||||||
|
# immediately with "module \"org.hyprland.style\" is not installed" (see
|
||||||
|
# ~/.local/share/plex/Logs/Plex.log). This dir is first on $PATH, so it
|
||||||
|
# shadows /usr/bin/Plex for both a terminal launch and the .desktop entry
|
||||||
|
# (Exec=Plex is PATH-resolved) without touching the global env var.
|
||||||
|
unset QT_QUICK_CONTROLS_STYLE
|
||||||
|
exec /opt/plex-desktop/Plex.sh "$@"
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# User override of /usr/share/applications/tv.plex.PlexDesktop.desktop
|
||||||
|
# (~/.local/share/applications takes XDG precedence over /usr/share for the
|
||||||
|
# same filename, so this is picked up by app launchers instead automatically
|
||||||
|
# — no association/mimeapps changes needed).
|
||||||
|
#
|
||||||
|
# Only change from upstream: Exec unsets QT_QUICK_CONTROLS_STYLE and calls
|
||||||
|
# the package's Plex.sh directly, bypassing $PATH entirely. Plex Desktop
|
||||||
|
# bundles its own private Qt/QML runtime that doesn't ship the
|
||||||
|
# "org.hyprland.style" QQC2 style hypr/core/env.lua sets globally for other
|
||||||
|
# apps' theming — inheriting it makes Plex fail to load its UI and exit
|
||||||
|
# immediately with "module \"org.hyprland.style\" is not installed" (see
|
||||||
|
# ~/.local/share/plex/Logs/Plex.log). Going straight to the fixed install
|
||||||
|
# path (rather than relying on the .local/bin/Plex wrapper shadowing
|
||||||
|
# /usr/bin/Plex on $PATH) means this keeps working even if $PATH order ever
|
||||||
|
# changes.
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Plex
|
||||||
|
Comment=Plex client for desktop computers
|
||||||
|
Exec=env -u QT_QUICK_CONTROLS_STYLE /opt/plex-desktop/Plex.sh
|
||||||
|
Icon=tv.plex.PlexDesktop
|
||||||
|
Terminal=false
|
||||||
|
Categories=AudioVideo;Player;X-Media;
|
||||||
|
X-Flatpak=tv.plex.PlexDesktop
|
||||||
|
StartupWMClass=Plex
|
||||||
|
Name[en_US]=Plex
|
||||||
@@ -51,3 +51,4 @@ export ZED_ALLOW_EMULATED_GPU=1
|
|||||||
fastfetch
|
fastfetch
|
||||||
|
|
||||||
eval "$(oh-my-posh init zsh --config $HOME/.config/ohmyposh/zen.toml)"
|
eval "$(oh-my-posh init zsh --config $HOME/.config/ohmyposh/zen.toml)"
|
||||||
|
export PATH="$HOME/.local/share/turritopsis/flutter-sdk/bin:$PATH"
|
||||||
|
|||||||
11
system/install.sh
Executable file
11
system/install.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Installs system-level config that stow can't manage (needs root).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
# pacman: shared [options] snippet
|
||||||
|
sudo install -m644 "$repo/pacman/options.conf" /etc/pacman.d/options.conf
|
||||||
|
if ! grep -q '^Include = /etc/pacman.d/options.conf' /etc/pacman.conf; then
|
||||||
|
sudo sed -i '/^\[options\]/a Include = /etc/pacman.d/options.conf' /etc/pacman.conf
|
||||||
|
fi
|
||||||
5
system/pacman/options.conf
Normal file
5
system/pacman/options.conf
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Shared pacman [options], included from /etc/pacman.conf.
|
||||||
|
# Installed to /etc/pacman.d/options.conf by system/install.sh.
|
||||||
|
Color
|
||||||
|
#ILoveCandy
|
||||||
|
#VerbosePkgLists
|
||||||
Reference in New Issue
Block a user