1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00

Compare commits

...

58 Commits

Author SHA1 Message Date
github-actions[bot]
30dc63c801 Update VERSION to v0.2.4 (from DMS) 2025-10-25 22:33:32 +00:00
bbedward
8db7b8419a remove font check 2025-10-25 18:30:24 -04:00
bbedward
8c626b20e1 dankbar: fix center section spacers 2025-10-25 18:21:22 -04:00
github-actions[bot]
a8929c8046 i18n: update translations 2025-10-25 21:58:03 +00:00
bbedward
f8e4b5e958 remove font deps from copr 2025-10-25 17:57:20 -04:00
bbedward
58cae24157 fonts: bundle Inter + FiraCode Nerd
- remove all font dependencies
2025-10-25 17:53:08 -04:00
bbedward
bb4f5f37cc Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-25 16:16:16 -04:00
bbedward
237333941a font: bundle in material symbols rounded 2025-10-25 16:15:15 -04:00
github-actions[bot]
e1406875aa Update VERSION to v0.2.3 (from DMS) 2025-10-25 18:56:06 +00:00
github-actions[bot]
6ab96898a3 i18n: update translations 2025-10-25 18:35:07 +00:00
github-actions[bot]
7973b2f3da i18n: update source strings from codebase 2025-10-25 18:35:00 +00:00
bbedward
90284625af lock/greetd: fix 12-hour time 2025-10-25 14:34:25 -04:00
bbedward
6b3442512a net: only show eth/wifi on networkmanager backend 2025-10-25 14:23:12 -04:00
github-actions[bot]
f9208136af i18n: update source strings from codebase 2025-10-25 18:03:54 +00:00
bbedward
9895fbde0d dock: dynamic indicator positions 2025-10-25 14:03:20 -04:00
bbedward
30370e4985 lock/greeter: fixed clock widths 2025-10-25 14:00:02 -04:00
github-actions[bot]
7a45f370b5 i18n: update source strings from codebase 2025-10-25 16:43:28 +00:00
Massimo Branchini
38068eeaac better checks before saying SUCCESS (#550) 2025-10-25 12:43:09 -04:00
bbedward
62df30ed6c apps: fix sorting and reactivity 2025-10-25 12:42:54 -04:00
bbedward
7e75c9e510 dankbar: fix widget hover effects 2025-10-25 12:42:54 -04:00
github-actions[bot]
42f9edf566 i18n: update translations 2025-10-25 15:48:30 +00:00
github-actions[bot]
c72b6144a5 i18n: update source strings from codebase 2025-10-25 15:48:23 +00:00
bbedward
032777e32e net: switch to native VPN backend 2025-10-25 11:47:46 -04:00
Jack Grahn
607b5320fd Add clipboard history command to niri spawn-at-startup options (#545) 2025-10-25 10:08:48 -04:00
Massimo Branchini
959766b265 external system update trigger (#546) 2025-10-25 10:07:56 -04:00
github-actions[bot]
9774991b56 i18n: update translations 2025-10-25 06:01:33 +00:00
github-actions[bot]
e1587995d0 i18n: update source strings from codebase 2025-10-25 06:01:26 +00:00
bbedward
08e6e22046 net: lose fail tracking 2025-10-25 02:00:53 -04:00
github-actions[bot]
454d8bdc88 i18n: update source strings from codebase 2025-10-25 03:59:51 +00:00
bbedward
d0ae7431eb net: updates to accomodate iwd + other backends 2025-10-24 23:59:23 -04:00
purian23
d88dc17b21 Format spec 2025-10-24 22:28:27 -04:00
purian23
705f569571 Update dms-greeter colors path 2025-10-24 19:01:58 -04:00
purian23
abc7badfa9 Print the final message 2025-10-24 18:44:28 -04:00
purian23
e8c2469227 Simplify upgrade message 2025-10-24 18:37:30 -04:00
purian23
1e5e8cd246 Fix scope 2025-10-24 18:26:26 -04:00
purian23
adc81cfb95 Moar Copr DMS restart logic 2025-10-24 18:18:21 -04:00
github-actions[bot]
e175fa64cb i18n: update translations 2025-10-24 21:21:52 +00:00
bbedward
f9994d0e42 add turkish 2025-10-24 17:21:15 -04:00
github-actions[bot]
3e5d1c514a i18n: update translations 2025-10-24 21:17:00 +00:00
purian23
6310394034 Add 'dms restart' to Copr upgrades 2025-10-24 17:16:29 -04:00
purian23
f32596053b Update Copr default dir to usr/share 2025-10-24 13:27:48 -04:00
bbedward
cf4a6969d3 plugins: fix set ToggleSetting not saving
fixes #541
2025-10-24 13:12:57 -04:00
github-actions[bot]
0918412916 i18n: update translations 2025-10-24 16:38:47 +00:00
github-actions[bot]
41b1718587 i18n: update source strings from codebase 2025-10-24 16:38:43 +00:00
bbedward
ca2acbc704 niri: ability to blur wallpaper on overview + add a separate layer for
blurred wallpapers
2025-10-24 12:37:57 -04:00
bbedward
1abd3ef8b1 greeter: search /usr/share path for qml files 2025-10-24 09:10:38 -04:00
bbedward
cedba3770c clock: baseline text relative to date
fixes #535
2025-10-24 08:47:09 -04:00
bbedward
f733be1fd1 lock/greeter: seconds precision on clock
fixes #540
2025-10-24 08:38:41 -04:00
github-actions[bot]
01e02232d7 i18n: update source strings from codebase 2025-10-24 03:16:41 +00:00
bbedward
771920b38b dankbar: fix focusedapp & media text clipping
fixes #537
2025-10-23 23:15:53 -04:00
bbedward
0f55bbc148 dankbar: remove hardcoded font weights
fixes #539
2025-10-23 23:11:20 -04:00
bbedward
ab4e9646ad workspace: don't wrap on mousewheel
fixes #538
2025-10-23 23:04:17 -04:00
bbedward
884b73599a dd: add file name to wallpaper tab 2025-10-23 22:56:53 -04:00
bbedward
492c0e7ef7 dankbar: fix separator 2025-10-23 22:44:49 -04:00
purian23
0865ae000b Remove Notepad indicator 2025-10-23 22:13:53 -04:00
purian23
049c9b44e4 Add req accountsservice to Copr 2025-10-23 19:04:49 -04:00
github-actions[bot]
199edd3771 i18n: update translations 2025-10-23 22:17:51 +00:00
bbedward
8806217d25 gh: use workflow_run trigger for copr 2025-10-23 18:17:09 -04:00
96 changed files with 8512 additions and 1130 deletions

View File

@@ -1,11 +1,10 @@
name: DMS Copr Stable Release
on:
push:
tags:
- 'v*'
release:
types: [published]
workflow_run:
workflows: ["Create Release from DMS"]
types: [completed]
branches: [master]
workflow_dispatch:
inputs:
version:
@@ -16,7 +15,8 @@ on:
jobs:
build-and-upload:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -27,20 +27,14 @@ jobs:
if [ -n "${{ github.event.inputs.version }}" ]; then
VERSION="${{ github.event.inputs.version }}"
echo "Using manual version: $VERSION"
elif [ "${{ github.event_name }}" = "release" ]; then
VERSION="${{ github.event.release.tag_name }}"
VERSION="${VERSION#v}"
echo "Using release version: $VERSION"
elif [ "${{ github.event_name }}" = "push" ] && [[ "${{ github.ref }}" == refs/tags/* ]]; then
VERSION="${{ github.ref_name }}"
VERSION="${VERSION#v}"
echo "Using tag version: $VERSION"
elif [ "${{ github.event_name }}" = "workflow_run" ]; then
VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name' | sed 's/^v//')
echo "Using latest release version from workflow_run: $VERSION"
else
# Fallback to latest release
VERSION=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | jq -r '.tag_name' | sed 's/^v//')
VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name' | sed 's/^v//')
echo "Using latest release version: $VERSION"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "✅ Building DMS stable version: $VERSION"
@@ -94,11 +88,9 @@ jobs:
BuildRequires: wget
Requires: (quickshell or quickshell-git)
Requires: accountsservice
Requires: dms-cli
Requires: dgop
Requires: fira-code-fonts
Requires: material-symbols-fonts
Requires: rsms-inter-fonts
Recommends: brightnessctl
Recommends: cava
@@ -179,18 +171,59 @@ jobs:
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
install -Dm755 %{_builddir}/dgop %{buildroot}%{_bindir}/dgop
install -dm755 %{buildroot}%{_sysconfdir}/xdg/quickshell/dms
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.git*
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.github
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/*.spec
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
rm -f %{buildroot}%{_datadir}/quickshell/dms/*.spec
%posttrans
# Clean up old installation path from previous versions (only if empty)
if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
# Remove directories only if empty (preserves any user-added files)
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
fi
# Restart DMS for active users after upgrade
if [ "$1" -ge 2 ]; then
# Find all quickshell DMS processes (PID and username)
while read pid cmd; do
username=$(ps -o user= -p "$pid" 2>/dev/null)
[ "$username" = "root" ] && continue
[ -z "$username" ] && continue
# Get user's UID and validate session
user_uid=$(id -u "$username" 2>/dev/null)
[ -z "$user_uid" ] && continue
[ ! -d "/run/user/$user_uid" ] && continue
wayland_display=$(tr '\0' '\n' < /proc/$pid/environ 2>/dev/null | grep '^WAYLAND_DISPLAY=' | cut -d= -f2)
[ -z "$wayland_display" ] && continue
echo "Restarting DMS for user: $username"
# Run as user with full Wayland session environment
runuser -u "$username" -- /bin/sh -c "
export XDG_RUNTIME_DIR=/run/user/$user_uid
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$user_uid/bus
export WAYLAND_DISPLAY=$wayland_display
export PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:\$PATH
dms restart >/dev/null 2>&1
" 2>/dev/null || true
break
done < <(pgrep -a -f 'quickshell.*dms' 2>/dev/null)
fi
%files
%license LICENSE
%doc README.md CONTRIBUTING.md
%{_sysconfdir}/xdg/quickshell/dms/
%{_datadir}/quickshell/dms/
%files -n dms-cli
%{_bindir}/dms

View File

@@ -113,6 +113,7 @@ jobs:
"ja:translations/poexports/ja.json"
"zh-Hans:translations/poexports/zh_CN.json"
"pt-br:translations/poexports/pt.json"
"tr:translations/poexports/tr.json"
)
ANY_CHANGED=false

View File

@@ -49,9 +49,9 @@ jobs:
set -e
PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || echo "")
if [ -z "$PREVIOUS_TAG" ]; then
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" | head -50)
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" --author='^(?!github-actions\[bot\])' | head -50)
else
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${TAG}")
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" --author='^(?!github-actions\[bot\])' "${PREVIOUS_TAG}..${TAG}")
fi
cat > RELEASE_BODY.md << 'EOF'
@@ -219,25 +219,3 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Extract plain version
id: plain_version
run: |
set -e
if [[ "$TAG" == v* ]]; then
VERSION="${TAG#v}"
else
VERSION="$TAG"
fi
echo "plain=$VERSION" >> "$GITHUB_OUTPUT"
- name: Trigger Copr release workflow
run: |
set -euo pipefail
VERSION="${{ steps.plain_version.outputs.plain }}"
JSON_PAYLOAD=$(jq -n --arg version "$VERSION" '{inputs:{version:$version}}')
curl -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/${{ github.repository }}/actions/workflows/copr-release.yml/dispatches \
-d "$JSON_PAYLOAD"

View File

@@ -66,6 +66,8 @@ Singleton {
property int animationSpeed: SettingsData.AnimationSpeed.Short
property int customAnimationDuration: 500
property string wallpaperFillMode: "Fill"
property bool blurredWallpaperLayer: false
property bool blurWallpaperOnOverview: false
property bool showLauncherButton: true
property bool showWorkspaceSwitcher: true
@@ -269,7 +271,6 @@ Singleton {
Component.onCompleted: {
if (!isGreeterMode) {
loadSettings()
fontCheckTimer.start()
initializeListModels()
fprintdDetectionProcess.running = true
}
@@ -497,6 +498,8 @@ Singleton {
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({})
showOnLastDisplay = settings.showOnLastDisplay !== undefined ? settings.showOnLastDisplay : ({})
wallpaperFillMode = settings.wallpaperFillMode !== undefined ? settings.wallpaperFillMode : "Fill"
blurredWallpaperLayer = settings.blurredWallpaperLayer !== undefined ? settings.blurredWallpaperLayer : false
blurWallpaperOnOverview = settings.blurWallpaperOnOverview !== undefined ? settings.blurWallpaperOnOverview : false
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : SettingsData.AnimationSpeed.Short
customAnimationDuration = settings.customAnimationDuration !== undefined ? settings.customAnimationDuration : 500
acMonitorTimeout = settings.acMonitorTimeout !== undefined ? settings.acMonitorTimeout : 0
@@ -663,6 +666,8 @@ Singleton {
"widgetBackgroundColor": widgetBackgroundColor,
"surfaceBase": surfaceBase,
"wallpaperFillMode": wallpaperFillMode,
"blurredWallpaperLayer": blurredWallpaperLayer,
"blurWallpaperOnOverview": blurWallpaperOnOverview,
"notificationTimeoutLow": notificationTimeoutLow,
"notificationTimeoutNormal": notificationTimeoutNormal,
"notificationTimeoutCritical": notificationTimeoutCritical,
@@ -741,7 +746,7 @@ Singleton {
"dankBarBorderOpacity", "dankBarBorderThickness", "popupGapsAuto", "popupGapsManual",
"dankBarPosition", "lockScreenShowPowerActions", "enableFprint", "maxFprintTries",
"hideBrightnessSlider", "widgetBackgroundColor", "surfaceBase", "wallpaperFillMode",
"notificationTimeoutLow", "notificationTimeoutNormal", "notificationTimeoutCritical",
"blurredWallpaperLayer", "blurWallpaperOnOverview", "notificationTimeoutLow", "notificationTimeoutNormal", "notificationTimeoutCritical",
"notificationPopupPosition", "osdAlwaysShowValue", "powerActionConfirm",
"customPowerActionLock", "customPowerActionLogout", "customPowerActionSuspend",
"customPowerActionHibernate", "customPowerActionReboot", "customPowerActionPowerOff",
@@ -1088,6 +1093,16 @@ Singleton {
saveSettings()
}
function setBlurredWallpaperLayer(enabled) {
blurredWallpaperLayer = enabled
saveSettings()
}
function setBlurWallpaperOnOverview(enabled) {
blurWallpaperOnOverview = enabled
saveSettings()
}
function setShowLauncherButton(enabled) {
showLauncherButton = enabled
saveSettings()
@@ -1868,27 +1883,6 @@ Singleton {
id: rightWidgetsModel
}
Timer {
id: fontCheckTimer
interval: 3000
repeat: false
onTriggered: {
var availableFonts = Qt.fontFamilies()
var missingFonts = []
if (fontFamily === defaultFontFamily && !availableFonts.includes(defaultFontFamily))
missingFonts.push(defaultFontFamily)
if (monoFontFamily === defaultMonoFontFamily && !availableFonts.includes(defaultMonoFontFamily))
missingFonts.push(defaultMonoFontFamily)
if (missingFonts.length > 0) {
var message = "Missing fonts: " + missingFonts.join(", ") + ". Using system defaults."
ToastService.showWarning(message)
}
}
}
property Process testNotificationProcess
testNotificationProcess: Process {

View File

@@ -52,6 +52,14 @@ Item {
}
}
Loader {
id: blurredWallpaperBackgroundLoader
active: SettingsData.blurredWallpaperLayer
asynchronous: false
sourceComponent: BlurredWallpaperBackground {}
}
WallpaperBackground {}
Lock {
@@ -208,8 +216,8 @@ Item {
Connections {
target: NetworkService
function onCredentialsNeeded(token, ssid, setting, fields, hints, reason) {
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason)
function onCredentialsNeeded(token, ssid, setting, fields, hints, reason, connType, connName, vpnService) {
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService)
}
}

View File

@@ -21,6 +21,11 @@ DankModal {
property var promptFields: []
property string promptSetting: ""
property bool isVpnPrompt: false
property string connectionName: ""
property string vpnServiceType: ""
property string connectionType: ""
function show(ssid) {
wifiPasswordSSID = ssid
wifiPasswordInput = ""
@@ -32,6 +37,10 @@ DankModal {
promptReason = ""
promptFields = []
promptSetting = ""
isVpnPrompt = false
connectionName = ""
vpnServiceType = ""
connectionType = ""
const network = NetworkService.wifiNetworks.find(n => n.ssid === ssid)
requiresEnterprise = network?.enterprise || false
@@ -48,13 +57,18 @@ DankModal {
})
}
function showFromPrompt(token, ssid, setting, fields, hints, reason) {
wifiPasswordSSID = ssid
function showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService) {
isPromptMode = true
promptToken = token
promptReason = reason
promptFields = fields || []
promptSetting = setting || "802-11-wireless-security"
connectionType = connType || "802-11-wireless"
connectionName = connName || ssid || ""
vpnServiceType = vpnService || ""
isVpnPrompt = (connectionType === "vpn" || connectionType === "wireguard")
wifiPasswordSSID = isVpnPrompt ? connectionName : ssid
requiresEnterprise = setting === "802-1x"
@@ -163,7 +177,12 @@ DankModal {
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Connect to Wi-Fi")
text: {
if (isVpnPrompt) {
return I18n.tr("Connect to VPN")
}
return I18n.tr("Connect to Wi-Fi")
}
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
@@ -175,6 +194,9 @@ DankModal {
StyledText {
text: {
if (isVpnPrompt) {
return I18n.tr("Enter password for ") + wifiPasswordSSID
}
const prefix = requiresEnterprise ? I18n.tr("Enter credentials for ") : I18n.tr("Enter password for ")
return prefix + wifiPasswordSSID
}
@@ -218,7 +240,7 @@ DankModal {
color: Theme.surfaceHover
border.color: usernameInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: usernameInput.activeFocus ? 2 : 1
visible: requiresEnterprise
visible: requiresEnterprise && !isVpnPrompt
MouseArea {
anchors.fill: parent
@@ -271,7 +293,7 @@ DankModal {
textColor: Theme.surfaceText
text: wifiPasswordInput
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
placeholderText: requiresEnterprise ? I18n.tr("Password") : ""
placeholderText: (requiresEnterprise && !isVpnPrompt) ? I18n.tr("Password") : ""
backgroundColor: "transparent"
focus: !requiresEnterprise
enabled: root.shouldBeVisible
@@ -281,7 +303,9 @@ DankModal {
onAccepted: () => {
if (isPromptMode) {
const secrets = {}
if (promptSetting === "802-11-wireless-security") {
if (isVpnPrompt) {
if (passwordInput.text) secrets["password"] = passwordInput.text
} else if (promptSetting === "802-11-wireless-security") {
secrets["psk"] = passwordInput.text
} else if (promptSetting === "802-1x") {
if (usernameInput.text) secrets["identity"] = usernameInput.text
@@ -340,7 +364,7 @@ DankModal {
}
Rectangle {
visible: requiresEnterprise
visible: requiresEnterprise && !isVpnPrompt
width: parent.width
height: 50
radius: Theme.cornerRadius
@@ -372,7 +396,7 @@ DankModal {
}
Rectangle {
visible: requiresEnterprise
visible: requiresEnterprise && !isVpnPrompt
width: parent.width
height: 50
radius: Theme.cornerRadius
@@ -495,7 +519,12 @@ DankModal {
height: 36
radius: Theme.cornerRadius
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: requiresEnterprise ? (usernameInput.text.length > 0 && passwordInput.text.length > 0) : passwordInput.text.length > 0
enabled: {
if (isVpnPrompt) {
return passwordInput.text.length > 0
}
return requiresEnterprise ? (usernameInput.text.length > 0 && passwordInput.text.length > 0) : passwordInput.text.length > 0
}
opacity: enabled ? 1 : 0.5
StyledText {
@@ -518,7 +547,9 @@ DankModal {
onClicked: () => {
if (isPromptMode) {
const secrets = {}
if (promptSetting === "802-11-wireless-security") {
if (isVpnPrompt) {
if (passwordInput.text) secrets["password"] = passwordInput.text
} else if (promptSetting === "802-11-wireless-security") {
secrets["psk"] = passwordInput.text
} else if (promptSetting === "802-1x") {
if (usernameInput.text) secrets["identity"] = usernameInput.text

View File

@@ -0,0 +1,135 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import Quickshell.Io
import qs.Common
import qs.Widgets
import qs.Modules
Variants {
model: {
if (SessionData.isGreeterMode) {
return Quickshell.screens
}
return SettingsData.getFilteredScreens("wallpaper")
}
PanelWindow {
id: blurWallpaperWindow
required property var modelData
screen: modelData
WlrLayershell.layer: WlrLayer.Background
WlrLayershell.namespace: "dms:blurwallpaper"
WlrLayershell.exclusionMode: ExclusionMode.Ignore
anchors.top: true
anchors.bottom: true
anchors.left: true
anchors.right: true
color: "transparent"
Item {
id: root
anchors.fill: parent
property string source: SessionData.getMonitorWallpaper(modelData.name) || ""
property bool isColorSource: source.startsWith("#")
Connections {
target: SessionData
function onIsLightModeChanged() {
if (SessionData.perModeWallpaper) {
var newSource = SessionData.getMonitorWallpaper(modelData.name) || ""
if (newSource !== root.source) {
root.source = newSource
}
}
}
}
function getFillMode(modeName) {
switch(modeName) {
case "Stretch": return Image.Stretch
case "Fit":
case "PreserveAspectFit": return Image.PreserveAspectFit
case "Fill":
case "PreserveAspectCrop": return Image.PreserveAspectCrop
case "Tile": return Image.Tile
case "TileVertically": return Image.TileVertically
case "TileHorizontally": return Image.TileHorizontally
case "Pad": return Image.Pad
default: return Image.PreserveAspectCrop
}
}
WallpaperEngineProc {
id: weProc
monitor: modelData.name
}
Component.onCompleted: {
if (source) {
const formattedSource = source.startsWith("file://") ? source : "file://" + source
wallpaperImage.source = formattedSource
}
}
Component.onDestruction: {
weProc.stop()
}
onSourceChanged: {
const isWE = source.startsWith("we:")
const isColor = source.startsWith("#")
if (isWE) {
wallpaperImage.source = ""
weProc.start(source.substring(3))
} else {
weProc.stop()
if (!source) {
wallpaperImage.source = ""
} else if (isColor) {
wallpaperImage.source = ""
} else {
wallpaperImage.source = source.startsWith("file://") ? source : "file://" + source
}
}
}
Loader {
anchors.fill: parent
active: !root.source || root.isColorSource
asynchronous: true
sourceComponent: DankBackdrop {
screenName: modelData.name
}
}
Image {
id: wallpaperImage
anchors.fill: parent
visible: false
asynchronous: true
smooth: true
cache: true
fillMode: root.getFillMode(SettingsData.wallpaperFillMode)
}
MultiEffect {
anchors.fill: parent
source: wallpaperImage
blurEnabled: true
blur: 0.8
blurMax: 48
}
}
}
}

View File

@@ -10,26 +10,26 @@ PluginComponent {
id: root
Ref {
service: VpnService
service: DMSNetworkService
}
ccWidgetIcon: VpnService.isBusy ? "sync" : (VpnService.connected ? "vpn_lock" : "vpn_key_off")
ccWidgetIcon: DMSNetworkService.isBusy ? "sync" : (DMSNetworkService.connected ? "vpn_lock" : "vpn_key_off")
ccWidgetPrimaryText: "VPN"
ccWidgetSecondaryText: {
if (!VpnService.connected)
if (!DMSNetworkService.connected)
return "Disconnected"
const names = VpnService.activeNames || []
const names = DMSNetworkService.activeNames || []
if (names.length <= 1)
return names[0] || "Connected"
return names[0] + " +" + (names.length - 1)
}
ccWidgetIsActive: VpnService.connected
ccWidgetIsActive: DMSNetworkService.connected
onCcWidgetToggled: {
if (VpnService.connected) {
VpnService.disconnectAllActive()
} else if (VpnService.profiles.length > 0) {
VpnService.connect(VpnService.profiles[0].uuid)
if (DMSNetworkService.connected) {
DMSNetworkService.disconnectAllActive()
} else if (DMSNetworkService.profiles.length > 0) {
DMSNetworkService.connect(DMSNetworkService.profiles[0].uuid)
}
}
@@ -52,9 +52,9 @@ PluginComponent {
StyledText {
text: {
if (!VpnService.connected)
if (!DMSNetworkService.connected)
return "Active: None"
const names = VpnService.activeNames || []
const names = DMSNetworkService.activeNames || []
if (names.length <= 1)
return "Active: " + (names[0] || "VPN")
return "Active: " + names[0] + " +" + (names.length - 1)
@@ -72,7 +72,7 @@ PluginComponent {
height: 28
radius: 14
color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: VpnService.connected
visible: DMSNetworkService.connected
width: 110
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
@@ -99,7 +99,7 @@ PluginComponent {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.disconnectAllActive()
onClicked: DMSNetworkService.disconnectAllActive()
}
}
}
@@ -123,7 +123,7 @@ PluginComponent {
Item {
width: parent.width
height: VpnService.profiles.length === 0 ? 120 : 0
height: DMSNetworkService.profiles.length === 0 ? 120 : 0
visible: height > 0
Column {
@@ -154,7 +154,7 @@ PluginComponent {
}
Repeater {
model: VpnService.profiles
model: DMSNetworkService.profiles
delegate: Rectangle {
required property var modelData
@@ -162,9 +162,9 @@ PluginComponent {
width: parent ? parent.width : 300
height: 50
radius: Theme.cornerRadius
color: rowArea.containsMouse ? Theme.primaryHoverLight : (VpnService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
border.width: VpnService.isActiveUuid(modelData.uuid) ? 2 : 1
border.color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
color: rowArea.containsMouse ? Theme.primaryHoverLight : (DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
border.width: DMSNetworkService.isActiveUuid(modelData.uuid) ? 2 : 1
border.color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
RowLayout {
anchors.left: parent.left
@@ -174,9 +174,9 @@ PluginComponent {
spacing: Theme.spacingS
DankIcon {
name: VpnService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
name: DMSNetworkService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
size: Theme.iconSize - 4
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
Layout.alignment: Qt.AlignVCenter
}
@@ -187,7 +187,7 @@ PluginComponent {
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeMedium
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
}
StyledText {
@@ -234,7 +234,7 @@ PluginComponent {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.toggle(modelData.uuid)
onClicked: DMSNetworkService.toggle(modelData.uuid)
}
}
}

View File

@@ -332,7 +332,10 @@ Column {
return "Select device"
if (AudioService.sink.audio.muted)
return "Muted"
return Math.round(AudioService.sink.audio.volume * 100) + "%"
const volume = AudioService.sink.audio.volume
if (typeof volume !== "number" || isNaN(volume))
return "0%"
return Math.round(volume * 100) + "%"
}
case "audioInput":
{
@@ -340,7 +343,10 @@ Column {
return "Select device"
if (AudioService.source.audio.muted)
return "Muted"
return Math.round(AudioService.source.audio.volume * 100) + "%"
const volume = AudioService.source.audio.volume
if (typeof volume !== "number" || isNaN(volume))
return "0%"
return Math.round(volume * 100) + "%"
}
default:
return widgetDef?.description || ""

View File

@@ -34,6 +34,10 @@ Rectangle {
return 1
}
if (NetworkService.backend !== "networkmanager" || DMSService.apiVersion <= 10) {
return 1
}
const pref = NetworkService.userPreference
const status = NetworkService.networkStatus
let index = 1
@@ -76,7 +80,7 @@ Rectangle {
DankButtonGroup {
id: preferenceControls
anchors.verticalCenter: parent.verticalCenter
visible: DMSService.apiVersion >= 5
visible: NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
model: ["Ethernet", "WiFi"]
currentIndex: currentPreferenceIndex
@@ -196,7 +200,7 @@ Rectangle {
anchors.bottom: parent.bottom
anchors.margins: Theme.spacingM
anchors.topMargin: Theme.spacingM
visible: currentPreferenceIndex === 0 && DMSService.apiVersion >= 5
visible: currentPreferenceIndex === 0 && NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
contentHeight: wiredColumn.height
clip: true

View File

@@ -143,8 +143,8 @@ QtObject {
"description": "VPN connections",
"icon": "vpn_key",
"type": "builtin_plugin",
"enabled": VpnService.available,
"warning": !VpnService.available ? "VPN not available" : undefined,
"enabled": DMSNetworkService.available,
"warning": !DMSNetworkService.available ? "VPN not available" : undefined,
"isBuiltinPlugin": true
}]

View File

@@ -1,63 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Services.Pipewire
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
property var defaultSource: AudioService.source
iconName: {
if (!defaultSource) return "mic_off"
let volume = defaultSource.audio.volume
let muted = defaultSource.audio.muted
if (muted || volume === 0.0) return "mic_off"
return "mic"
}
isActive: defaultSource && !defaultSource.audio.muted
primaryText: {
if (!defaultSource) {
return "No input device"
}
return defaultSource.description || "Audio Input"
}
secondaryText: {
if (!defaultSource) {
return "Select device"
}
if (defaultSource.audio.muted) {
return "Muted"
}
return Math.round(defaultSource.audio.volume * 100) + "%"
}
onToggled: {
if (defaultSource && defaultSource.audio) {
defaultSource.audio.muted = !defaultSource.audio.muted
}
}
onWheelEvent: function (wheelEvent) {
if (!defaultSource || !defaultSource.audio) return
let delta = wheelEvent.angleDelta.y
let currentVolume = defaultSource.audio.volume * 100
let newVolume
if (delta > 0)
newVolume = Math.min(100, currentVolume + 5)
else
newVolume = Math.max(0, currentVolume - 5)
defaultSource.audio.muted = false
defaultSource.audio.volume = newVolume / 100
wheelEvent.accepted = true
}
}

View File

@@ -1,66 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Services.Pipewire
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
property var defaultSink: AudioService.sink
iconName: {
if (!defaultSink) return "volume_off"
let volume = defaultSink.audio.volume
let muted = defaultSink.audio.muted
if (muted || volume === 0.0) return "volume_off"
if (volume <= 0.33) return "volume_down"
if (volume <= 0.66) return "volume_up"
return "volume_up"
}
isActive: defaultSink && !defaultSink.audio.muted
primaryText: {
if (!defaultSink) {
return "No output device"
}
return defaultSink.description || "Audio Output"
}
secondaryText: {
if (!defaultSink) {
return "Select device"
}
if (defaultSink.audio.muted) {
return "Muted"
}
return Math.round(defaultSink.audio.volume * 100) + "%"
}
onToggled: {
if (defaultSink && defaultSink.audio) {
defaultSink.audio.muted = !defaultSink.audio.muted
}
}
onWheelEvent: function (wheelEvent) {
if (!defaultSink || !defaultSink.audio) return
let delta = wheelEvent.angleDelta.y
let currentVolume = defaultSink.audio.volume * 100
let newVolume
if (delta > 0)
newVolume = Math.min(100, currentVolume + 5)
else
newVolume = Math.max(0, currentVolume - 5)
defaultSink.audio.muted = false
defaultSink.audio.volume = newVolume / 100
AudioService.volumeChanged()
wheelEvent.accepted = true
}
}

View File

@@ -372,9 +372,6 @@ Item {
}
item.widthChanged.connect(() => layoutTimer.restart())
item.heightChanged.connect(() => layoutTimer.restart())
if (model.widgetId === "spacer") {
item.spacerSize = Qt.binding(() => model.size || 20)
}
if (root.axis && "axis" in item) {
item.axis = Qt.binding(() => root.axis)
}

View File

@@ -200,11 +200,6 @@ Item {
property var nativeInhibitor: null
Component.onCompleted: {
const fonts = Qt.fontFamilies()
if (fonts.indexOf("Material Symbols Rounded") === -1) {
ToastService.showError("Please install Material Symbols Rounded and Restart your Shell. See README.md for instructions")
}
if (SettingsData.forceStatusBarLayoutRefresh) {
SettingsData.forceStatusBarLayoutRefresh.connect(() => {
Qt.callLater(() => {
@@ -1239,13 +1234,19 @@ Item {
Component {
id: separatorComponent
Rectangle {
width: barWindow.isVertical ? barWindow.widgetThickness * 0.67 : 1
height: barWindow.isVertical ? 1 : barWindow.widgetThickness * 0.67
Item {
width: barWindow.isVertical ? parent.barThickness : 1
height: barWindow.isVertical ? 1 : parent.barThickness
implicitWidth: width
implicitHeight: height
color: Theme.outline
opacity: 0.3
Rectangle {
width: barWindow.isVertical ? parent.width * 0.6 : 1
height: barWindow.isVertical ? 1 : parent.height * 0.6
anchors.centerIn: parent
color: Theme.outline
opacity: 0.3
}
}
}

View File

@@ -14,7 +14,7 @@ DankPopout {
id: root
Ref {
service: VpnService
service: DMSNetworkService
}
property var triggerScreen: null
@@ -161,11 +161,11 @@ DankPopout {
StyledText {
text: {
if (!VpnService.connected) {
if (!DMSNetworkService.connected) {
return "Active: None";
}
const names = VpnService.activeNames || [];
const names = DMSNetworkService.activeNames || [];
if (names.length <= 1) {
return "Active: " + (names[0] || "VPN");
}
@@ -193,7 +193,7 @@ DankPopout {
height: 28
radius: 14
color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: VpnService.connected
visible: DMSNetworkService.connected
width: 130
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
border.width: 0
@@ -224,7 +224,7 @@ DankPopout {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.disconnectAllActive()
onClicked: DMSNetworkService.disconnectAllActive()
}
}
@@ -251,7 +251,7 @@ DankPopout {
Item {
width: parent.width
height: VpnService.profiles.length === 0 ? 120 : 0
height: DMSNetworkService.profiles.length === 0 ? 120 : 0
visible: height > 0
Column {
@@ -284,7 +284,7 @@ DankPopout {
}
Repeater {
model: VpnService.profiles
model: DMSNetworkService.profiles
delegate: Rectangle {
required property var modelData
@@ -292,9 +292,9 @@ DankPopout {
width: parent ? parent.width : 300
height: 50
radius: Theme.cornerRadius
color: rowArea.containsMouse ? Theme.primaryHoverLight : (VpnService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
border.width: VpnService.isActiveUuid(modelData.uuid) ? 2 : 1
border.color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
color: rowArea.containsMouse ? Theme.primaryHoverLight : (DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
border.width: DMSNetworkService.isActiveUuid(modelData.uuid) ? 2 : 1
border.color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
RowLayout {
anchors.left: parent.left
@@ -304,9 +304,9 @@ DankPopout {
spacing: Theme.spacingS
DankIcon {
name: VpnService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
name: DMSNetworkService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
size: Theme.iconSize - 4
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
Layout.alignment: Qt.AlignVCenter
}
@@ -317,7 +317,7 @@ DankPopout {
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeMedium
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
}
StyledText {
@@ -392,7 +392,7 @@ DankPopout {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.toggle(modelData.uuid)
onClicked: DMSNetworkService.toggle(modelData.uuid)
}
}

View File

@@ -50,7 +50,6 @@ BasePill {
StyledText {
text: BatteryService.batteryLevel.toString()
font.pixelSize: Theme.barTextSize(battery.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: BatteryService.batteryAvailable
@@ -87,7 +86,6 @@ BasePill {
StyledText {
text: `${BatteryService.batteryLevel}%`
font.pixelSize: Theme.barTextSize(battery.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: BatteryService.batteryAvailable
@@ -101,7 +99,6 @@ BasePill {
y: -battery.topMargin
width: battery.width + battery.leftMargin + battery.rightMargin
height: battery.height + battery.topMargin + battery.bottomMargin
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {

View File

@@ -37,9 +37,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
@@ -54,9 +54,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
@@ -68,18 +68,18 @@ BasePill {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(0)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(1)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
@@ -92,18 +92,18 @@ BasePill {
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(0)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(1)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Normal
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
@@ -134,9 +134,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
@@ -149,9 +149,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
@@ -169,9 +169,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
StyledText {
@@ -184,9 +184,9 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
font.weight: Font.Light
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
}
@@ -198,28 +198,30 @@ BasePill {
spacing: Theme.spacingS
StyledText {
id: timeText
text: {
return systemClock?.date?.toLocaleTimeString(Qt.locale(), SettingsData.getEffectiveTimeFormat())
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
anchors.baseline: dateText.baseline
}
StyledText {
id: middleDot
text: "•"
font.pixelSize: Theme.fontSizeSmall
color: Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
anchors.baseline: dateText.baseline
visible: !SettingsData.clockCompactMode
}
StyledText {
id: dateText
text: {
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0) {
return systemClock?.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat)
}
return systemClock?.date?.toLocaleDateString(Qt.locale(), "ddd d")
}
font.pixelSize: Theme.barTextSize(root.barThickness)
@@ -241,7 +243,6 @@ BasePill {
y: -root.topMargin
width: root.width + root.leftMargin + root.rightMargin
height: root.height + root.topMargin + root.bottomMargin
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
if (root.popoutTarget && root.popoutTarget.setTriggerPosition) {

View File

@@ -27,7 +27,6 @@ BasePill {
MouseArea {
z: 1
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
root.colorPickerRequested()

View File

@@ -86,7 +86,6 @@ BasePill {
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y
@@ -188,7 +187,6 @@ BasePill {
id: audioWheelArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y;
@@ -232,7 +230,6 @@ BasePill {
y: -root.topMargin
width: root.width + root.leftMargin + root.rightMargin
height: root.height + root.topMargin + root.bottomMargin
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {

View File

@@ -59,7 +59,6 @@ BasePill {
return DgopService.cpuUsage.toFixed(0);
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -97,7 +96,6 @@ BasePill {
return DgopService.cpuUsage.toFixed(0) + "%";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -106,7 +104,6 @@ BasePill {
StyledTextMetrics {
id: cpuBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "100%"
}
@@ -125,7 +122,6 @@ BasePill {
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {

View File

@@ -59,7 +59,6 @@ BasePill {
return Math.round(DgopService.cpuTemperature).toString();
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -97,7 +96,6 @@ BasePill {
return Math.round(DgopService.cpuTemperature) + "°";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -106,7 +104,6 @@ BasePill {
StyledTextMetrics {
id: tempBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "100°"
}
@@ -125,7 +122,6 @@ BasePill {
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {

View File

@@ -10,6 +10,7 @@ BasePill {
property var widgetData: null
property string mountPath: (widgetData && widgetData.mountPath !== undefined) ? widgetData.mountPath : "/"
property bool isHovered: mouseArea.containsMouse
property var selectedMount: {
if (!DgopService.diskMounts || DgopService.diskMounts.length === 0) {
@@ -114,7 +115,6 @@ BasePill {
return root.diskUsagePercent.toFixed(0)
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -149,7 +149,6 @@ BasePill {
return root.selectedMount.mount
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -164,7 +163,6 @@ BasePill {
return root.diskUsagePercent.toFixed(0) + "%"
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -173,7 +171,6 @@ BasePill {
StyledTextMetrics {
id: diskBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "100%"
}
@@ -197,6 +194,7 @@ BasePill {
}
MouseArea {
id: mouseArea
z: 1
anchors.fill: parent
hoverEnabled: root.isVerticalOrientation

View File

@@ -17,6 +17,7 @@ BasePill {
readonly property int maxCompactWidth: 288
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
property var activeDesktopEntry: null
property bool isHovered: mouseArea.containsMouse
Component.onCompleted: {
updateDesktopEntry()
@@ -98,7 +99,7 @@ BasePill {
return compactMode ? Math.min(baseWidth, maxCompactWidth - root.horizontalPadding * 2) : Math.min(baseWidth, maxNormalWidth - root.horizontalPadding * 2)
}
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
clip: true
clip: false
IconImage {
id: appIcon
@@ -146,7 +147,6 @@ BasePill {
}
font.pixelSize: 10
color: Theme.surfaceText
font.weight: Font.Medium
}
Row {
@@ -166,7 +166,6 @@ BasePill {
return desktopEntry && desktopEntry.name ? desktopEntry.name : activeWindow.appId;
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
@@ -203,7 +202,6 @@ BasePill {
return title;
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight

View File

@@ -123,7 +123,6 @@ BasePill {
return Math.round(root.displayTemp).toString();
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -161,7 +160,6 @@ BasePill {
return Math.round(root.displayTemp) + "°";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -170,7 +168,6 @@ BasePill {
StyledTextMetrics {
id: gpuTempBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "100°"
}
@@ -189,7 +186,6 @@ BasePill {
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {

View File

@@ -26,7 +26,6 @@ BasePill {
MouseArea {
z: 1
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
SessionService.toggleIdleInhibit()

View File

@@ -42,7 +42,6 @@ BasePill {
return root.currentLayout.substring(0, 2).toUpperCase()
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -67,7 +66,6 @@ BasePill {
MouseArea {
z: 1
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (CompositorService.isNiri) {

View File

@@ -84,7 +84,6 @@ BasePill {
MouseArea {
id: customMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.RightButton
onPressed: function (mouse){

View File

@@ -162,7 +162,7 @@ BasePill {
anchors.verticalCenter: parent.verticalCenter
width: textWidth
height: 20
height: root.widgetThickness
visible: SettingsData.mediaSize > 0
clip: true
color: "transparent"
@@ -176,7 +176,6 @@ BasePill {
text: textContainer.displayText
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
font.weight: Font.Medium
wrapMode: Text.NoWrap
x: needsScrolling ? -scrollOffset : 0
onTextChanged: {
@@ -257,7 +256,6 @@ BasePill {
id: prevArea
anchors.fill: parent
enabled: root.playerAvailable
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (activePlayer) {
@@ -315,7 +313,6 @@ BasePill {
id: nextArea
anchors.fill: parent
enabled: root.playerAvailable
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (activePlayer) {

View File

@@ -54,7 +54,6 @@ BasePill {
return (rate / (1024 * 1024)).toFixed(0) + "M"
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.info
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -67,7 +66,6 @@ BasePill {
return (rate / (1024 * 1024)).toFixed(0) + "M"
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.error
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -99,7 +97,6 @@ BasePill {
StyledText {
text: DgopService.networkRxRate > 0 ? root.formatNetworkSpeed(DgopService.networkRxRate) : "0 B/s"
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -109,7 +106,6 @@ BasePill {
StyledTextMetrics {
id: rxBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "88.8 MB/s"
}
@@ -137,7 +133,6 @@ BasePill {
StyledText {
text: DgopService.networkTxRate > 0 ? root.formatNetworkSpeed(DgopService.networkTxRate) : "0 B/s"
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -147,7 +142,6 @@ BasePill {
StyledTextMetrics {
id: txBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "88.8 MB/s"
}

View File

@@ -47,19 +47,6 @@ BasePill {
size: Theme.barIconSize(root.barThickness, -4)
color: root.isActive ? Theme.primary : Theme.surfaceText
}
Rectangle {
width: 6
height: 6
radius: 3
color: Theme.primary
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: SettingsData.dankBarNoBackground ? 0 : 4
anchors.topMargin: SettingsData.dankBarNoBackground ? 0 : 4
visible: NotepadStorageService.tabs && NotepadStorageService.tabs.length > 0
opacity: 0.8
}
}
}

View File

@@ -59,7 +59,6 @@ BasePill {
return DgopService.memoryUsage.toFixed(0);
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -97,7 +96,6 @@ BasePill {
return DgopService.memoryUsage.toFixed(0) + "%";
}
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
@@ -106,7 +104,6 @@ BasePill {
StyledTextMetrics {
id: ramBaseline
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
text: "100%"
}
@@ -125,7 +122,6 @@ BasePill {
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {

View File

@@ -22,6 +22,7 @@ Item {
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
property Item windowRoot: (Window.window ? Window.window.contentItem : null)
property int _workspaceUpdateTrigger: 0
property int _desktopEntriesUpdateTrigger: 0
readonly property var sortedToplevels: {
_workspaceUpdateTrigger
const toplevels = CompositorService.sortedToplevels
@@ -34,6 +35,13 @@ Item {
}
return toplevels
}
Connections {
target: DesktopEntries
function onApplicationsChanged() {
_desktopEntriesUpdateTrigger++
}
}
readonly property var groupedWindows: {
if (!SettingsData.runningAppsGroupByApp) {
return []
@@ -247,6 +255,7 @@ Item {
property var toplevelObject: toplevelData
property int windowCount: isGrouped ? modelData.windows.length : 1
property string tooltipText: {
root._desktopEntriesUpdateTrigger
let appName = "Unknown"
if (appId) {
const desktopEntry = DesktopEntries.heuristicLookup(appId)
@@ -285,6 +294,7 @@ Item {
width: 18
height: 18
source: {
root._desktopEntriesUpdateTrigger
const moddedId = Paths.moddedAppId(appId)
if (moddedId.toLowerCase().includes("steam_app")) {
return ""
@@ -319,6 +329,7 @@ Item {
return !iconImg.visible && !isSteamApp
}
text: {
root._desktopEntriesUpdateTrigger
if (!appId) {
return "?"
}
@@ -332,7 +343,6 @@ Item {
}
font.pixelSize: 10
color: Theme.surfaceText
font.weight: Font.Medium
}
Rectangle {
@@ -352,7 +362,6 @@ Item {
text: windowCount > 9 ? "9+" : windowCount
font.pixelSize: 9
color: Theme.surface
font.weight: Font.Bold
}
}
@@ -367,7 +376,6 @@ Item {
text: windowTitle
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
}
@@ -476,6 +484,7 @@ Item {
property var toplevelObject: toplevelData
property int windowCount: isGrouped ? modelData.windows.length : 1
property string tooltipText: {
root._desktopEntriesUpdateTrigger
let appName = "Unknown"
if (appId) {
const desktopEntry = DesktopEntries.heuristicLookup(appId)
@@ -513,6 +522,7 @@ Item {
width: 18
height: 18
source: {
root._desktopEntriesUpdateTrigger
const moddedId = Paths.moddedAppId(appId)
if (moddedId.toLowerCase().includes("steam_app")) {
return ""
@@ -546,6 +556,7 @@ Item {
return !iconImg.visible && !isSteamApp
}
text: {
root._desktopEntriesUpdateTrigger
if (!appId) {
return "?"
}
@@ -559,7 +570,6 @@ Item {
}
font.pixelSize: 10
color: Theme.surfaceText
font.weight: Font.Medium
}
Rectangle {
@@ -579,7 +589,6 @@ Item {
text: windowCount > 9 ? "9+" : windowCount
font.pixelSize: 9
color: Theme.surface
font.weight: Font.Bold
}
}
@@ -593,7 +602,6 @@ Item {
text: windowTitle
font.pixelSize: Theme.barTextSize(barThickness)
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
}
@@ -780,7 +788,6 @@ Item {
text: I18n.tr("Close")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
}
MouseArea {

View File

@@ -131,7 +131,6 @@ Item {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
if (!delegateRoot.trayItem) {
@@ -218,7 +217,6 @@ Item {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
if (!delegateRoot.trayItem) {
@@ -486,7 +484,6 @@ Item {
MouseArea {
id: backArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: menuRoot.goBack()
}
@@ -522,7 +519,6 @@ Item {
id: itemArea
anchors.fill: parent
enabled: !menuEntry?.isSeparator && (menuEntry?.enabled !== false)
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {

View File

@@ -112,7 +112,6 @@ BasePill {
anchors.verticalCenter: parent.verticalCenter
text: SystemUpdateService.updateCount.toString()
font.pixelSize: Theme.barTextSize(root.barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
visible: root.hasUpdates && !root.isChecking
}
@@ -123,7 +122,6 @@ BasePill {
MouseArea {
z: 1
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
if (popoutTarget && popoutTarget.setTriggerPosition) {

View File

@@ -9,10 +9,11 @@ BasePill {
id: root
Ref {
service: VpnService
service: DMSNetworkService
}
property var popoutTarget: null
property bool isHovered: clickArea.containsMouse
signal toggleVpnPopup()
@@ -24,9 +25,9 @@ BasePill {
DankIcon {
id: icon
name: VpnService.isBusy ? "sync" : (VpnService.connected ? "vpn_lock" : "vpn_key_off")
name: DMSNetworkService.isBusy ? "sync" : (DMSNetworkService.connected ? "vpn_lock" : "vpn_key_off")
size: Theme.barIconSize(root.barThickness, -4)
color: VpnService.connected ? Theme.primary : Theme.surfaceText
color: DMSNetworkService.connected ? Theme.primary : Theme.surfaceText
anchors.centerIn: parent
}
}
@@ -59,10 +60,10 @@ BasePill {
tooltipLoader.active = true
if (tooltipLoader.item) {
let tooltipText = ""
if (!VpnService.connected) {
if (!DMSNetworkService.connected) {
tooltipText = "VPN Disconnected"
} else {
const names = VpnService.activeNames || []
const names = DMSNetworkService.activeNames || []
if (names.length <= 1) {
tooltipText = "VPN Connected • " + (names[0] || "")
} else {

View File

@@ -224,12 +224,28 @@ Item {
const currentIndex = realWorkspaces.findIndex(ws => ws === root.currentWorkspace)
const validIndex = currentIndex === -1 ? 0 : currentIndex
const nextIndex = direction > 0 ? (validIndex + 1) % realWorkspaces.length : (validIndex - 1 + realWorkspaces.length) % realWorkspaces.length
const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0)
if (nextIndex === validIndex) {
return
}
NiriService.switchToWorkspace(realWorkspaces[nextIndex] - 1)
} else if (CompositorService.isHyprland) {
const command = direction > 0 ? "workspace r+1" : "workspace r-1"
Hyprland.dispatch(command)
const realWorkspaces = getRealWorkspaces()
if (realWorkspaces.length < 2) {
return
}
const currentIndex = realWorkspaces.findIndex(ws => ws.id === root.currentWorkspace)
const validIndex = currentIndex === -1 ? 0 : currentIndex
const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0)
if (nextIndex === validIndex) {
return
}
Hyprland.dispatch(`workspace ${realWorkspaces[nextIndex].id}`)
}
}
@@ -572,7 +588,6 @@ Item {
MouseArea {
id: appMouseArea
hoverEnabled: true
anchors.fill: parent
enabled: isActive
cursorShape: Qt.PointingHandCursor
@@ -641,7 +656,6 @@ Item {
MouseArea {
id: appMouseArea
hoverEnabled: true
anchors.fill: parent
enabled: isActive
cursorShape: Qt.PointingHandCursor

View File

@@ -1,10 +1,10 @@
import Qt.labs.folderlistmodel
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modals.FileBrowser
import qs.Services
@@ -16,11 +16,10 @@ Item {
implicitWidth: 700
implicitHeight: 410
property var wallpaperList: []
property string wallpaperDir: ""
property int currentPage: 0
property int itemsPerPage: 16
property int totalPages: Math.max(1, Math.ceil(wallpaperList.length / itemsPerPage))
property int totalPages: Math.max(1, Math.ceil(wallpaperFolderModel.count / itemsPerPage))
property bool active: false
property Item focusTarget: wallpaperGrid
property Item tabBarItem: null
@@ -28,6 +27,8 @@ Item {
property Item keyForwardTarget: null
property int lastPage: 0
property bool enableAnimation: false
property string homeDir: StandardPaths.writableLocation(StandardPaths.HomeLocation)
property string selectedFileName: ""
signal requestTabChange(int newIndex)
@@ -36,6 +37,11 @@ Item {
enableAnimation = false
lastPage = currentPage
}
updateSelectedFileName()
}
onGridIndexChanged: {
updateSelectedFileName()
}
onVisibleChanged: {
@@ -45,10 +51,7 @@ Item {
}
Component.onCompleted: {
loadWallpapers()
if (visible && active) {
setInitialSelection()
}
loadWallpaperDirectory()
}
onActiveChanged: {
@@ -59,16 +62,17 @@ Item {
function handleKeyEvent(event) {
const columns = 4
const rows = 4
const currentRow = Math.floor(gridIndex / columns)
const currentCol = gridIndex % columns
const visibleCount = wallpaperGrid.model.length
const visibleCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage)
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
if (gridIndex >= 0) {
const item = wallpaperGrid.currentItem
if (item && item.wallpaperPath) {
SessionData.setWallpaper(item.wallpaperPath)
if (gridIndex >= 0 && gridIndex < visibleCount) {
const absoluteIndex = currentPage * itemsPerPage + gridIndex
if (absoluteIndex < wallpaperFolderModel.count) {
const filePath = wallpaperFolderModel.get(absoluteIndex, "filePath")
if (filePath) {
SessionData.setWallpaper(filePath.toString().replace(/^file:\/\//, ''))
}
}
}
return true
@@ -76,10 +80,8 @@ Item {
if (event.key === Qt.Key_Right) {
if (gridIndex + 1 < visibleCount) {
// Move right within current page
gridIndex++
} else if (gridIndex === visibleCount - 1 && currentPage < totalPages - 1) {
// At last item in page, go to next page
} else if (currentPage < totalPages - 1) {
gridIndex = 0
currentPage++
}
@@ -88,22 +90,19 @@ Item {
if (event.key === Qt.Key_Left) {
if (gridIndex > 0) {
// Move left within current page
gridIndex--
} else if (gridIndex === 0 && currentPage > 0) {
// At first item in page, go to previous page (last item)
} else if (currentPage > 0) {
currentPage--
gridIndex = Math.min(itemsPerPage - 1, wallpaperList.length - currentPage * itemsPerPage - 1)
const prevPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage)
gridIndex = prevPageCount - 1
}
return true
}
if (event.key === Qt.Key_Down) {
if (gridIndex + columns < visibleCount) {
// Move down within current page
gridIndex += columns
} else if (gridIndex >= visibleCount - columns && currentPage < totalPages - 1) {
// In last row, go to next page
} else if (currentPage < totalPages - 1) {
gridIndex = currentCol
currentPage++
}
@@ -112,12 +111,10 @@ Item {
if (event.key === Qt.Key_Up) {
if (gridIndex >= columns) {
// Move up within current page
gridIndex -= columns
} else if (gridIndex < columns && currentPage > 0) {
// In first row, go to previous page (last row)
} else if (currentPage > 0) {
currentPage--
const prevPageCount = Math.min(itemsPerPage, wallpaperList.length - currentPage * itemsPerPage)
const prevPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage)
const prevPageRows = Math.ceil(prevPageCount / columns)
gridIndex = (prevPageRows - 1) * columns + currentCol
gridIndex = Math.min(gridIndex, prevPageCount - 1)
@@ -144,8 +141,9 @@ Item {
}
if (event.key === Qt.Key_End && event.modifiers & Qt.ControlModifier) {
gridIndex = 0
currentPage = totalPages - 1
const lastPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage)
gridIndex = Math.max(0, lastPageCount - 1)
return true
}
@@ -153,40 +151,38 @@ Item {
}
function setInitialSelection() {
if (!SessionData.wallpaperPath) {
if (!SessionData.wallpaperPath || wallpaperFolderModel.count === 0) {
gridIndex = 0
updateSelectedFileName()
Qt.callLater(() => { enableAnimation = true })
return
}
const startIndex = currentPage * itemsPerPage
const endIndex = Math.min(startIndex + itemsPerPage, wallpaperList.length)
const pageWallpapers = wallpaperList.slice(startIndex, endIndex)
for (let i = 0; i < pageWallpapers.length; i++) {
if (pageWallpapers[i] === SessionData.wallpaperPath) {
gridIndex = i
for (let i = 0; i < wallpaperFolderModel.count; i++) {
const filePath = wallpaperFolderModel.get(i, "filePath")
if (filePath && filePath.toString().replace(/^file:\/\//, '') === SessionData.wallpaperPath) {
const targetPage = Math.floor(i / itemsPerPage)
const targetIndex = i % itemsPerPage
currentPage = targetPage
gridIndex = targetIndex
updateSelectedFileName()
Qt.callLater(() => { enableAnimation = true })
return
}
}
gridIndex = 0
updateSelectedFileName()
Qt.callLater(() => { enableAnimation = true })
}
onWallpaperListChanged: {
if (visible && active) {
setInitialSelection()
}
}
function loadWallpapers() {
function loadWallpaperDirectory() {
const currentWallpaper = SessionData.wallpaperPath
// Try current wallpaper path / fallback to wallpaperLastPath
if (!currentWallpaper || currentWallpaper.startsWith("#") || currentWallpaper.startsWith("we:")) {
if (CacheData.wallpaperLastPath && CacheData.wallpaperLastPath !== "") {
wallpaperDir = CacheData.wallpaperLastPath
} else {
wallpaperDir = ""
wallpaperList = []
}
return
}
@@ -194,44 +190,51 @@ Item {
wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'))
}
function updateWallpaperList() {
if (!wallpaperFolderModel || wallpaperFolderModel.count === 0) {
wallpaperList = []
currentPage = 0
gridIndex = 0
function updateSelectedFileName() {
if (wallpaperFolderModel.count === 0) {
selectedFileName = ""
return
}
// Build list from FolderListModel
const files = []
for (let i = 0; i < wallpaperFolderModel.count; i++) {
const filePath = wallpaperFolderModel.get(i, "filePath")
const absoluteIndex = currentPage * itemsPerPage + gridIndex
if (absoluteIndex < wallpaperFolderModel.count) {
const filePath = wallpaperFolderModel.get(absoluteIndex, "filePath")
if (filePath) {
// Remove file:// prefix if present
const cleanPath = filePath.toString().replace(/^file:\/\//, '')
files.push(cleanPath)
const pathStr = filePath.toString().replace(/^file:\/\//, '')
selectedFileName = pathStr.substring(pathStr.lastIndexOf('/') + 1)
return
}
}
wallpaperList = files
const currentPath = SessionData.wallpaperPath
const selectedIndex = currentPath ? wallpaperList.indexOf(currentPath) : -1
if (selectedIndex >= 0) {
currentPage = Math.floor(selectedIndex / itemsPerPage)
gridIndex = selectedIndex % itemsPerPage
} else {
const maxPage = Math.max(0, Math.ceil(files.length / itemsPerPage) - 1)
currentPage = Math.min(Math.max(0, currentPage), maxPage)
gridIndex = 0
}
selectedFileName = ""
}
Connections {
target: SessionData
function onWallpaperPathChanged() {
loadWallpapers()
loadWallpaperDirectory()
if (visible && active) {
setInitialSelection()
}
}
}
Connections {
target: wallpaperFolderModel
function onCountChanged() {
if (wallpaperFolderModel.status === FolderListModel.Ready) {
if (visible && active) {
setInitialSelection()
}
updateSelectedFileName()
}
}
function onStatusChanged() {
if (wallpaperFolderModel.status === FolderListModel.Ready && wallpaperFolderModel.count > 0) {
if (visible && active) {
setInitialSelection()
}
updateSelectedFileName()
}
}
}
@@ -246,18 +249,6 @@ Item {
showDirs: false
sortField: FolderListModel.Name
folder: wallpaperDir ? "file://" + wallpaperDir : ""
onStatusChanged: {
if (status === FolderListModel.Ready) {
updateWallpaperList()
}
}
onCountChanged: {
if (status === FolderListModel.Ready) {
updateWallpaperList()
}
}
}
Loader {
@@ -275,13 +266,11 @@ Item {
showHiddenFiles: false
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
allowStacking: true
onFileSelected: (path) => {
// Set the selected wallpaper
const cleanPath = path.replace(/^file:\/\//, '')
SessionData.setWallpaper(cleanPath)
// Extract directory from the selected file and load all wallpapers
const dirPath = cleanPath.substring(0, cleanPath.lastIndexOf('/'))
if (dirPath) {
wallpaperDir = dirPath
@@ -290,7 +279,7 @@ Item {
}
close()
}
onDialogClosed: {
Qt.callLater(() => wallpaperBrowserLoader.active = false)
}
@@ -336,8 +325,15 @@ Item {
model: {
const startIndex = currentPage * itemsPerPage
const endIndex = Math.min(startIndex + itemsPerPage, wallpaperList.length)
return wallpaperList.slice(startIndex, endIndex)
const endIndex = Math.min(startIndex + itemsPerPage, wallpaperFolderModel.count)
const items = []
for (let i = startIndex; i < endIndex; i++) {
const filePath = wallpaperFolderModel.get(i, "filePath")
if (filePath) {
items.push(filePath.toString().replace(/^file:\/\//, ''))
}
}
return items
}
onModelChanged: {
@@ -359,8 +355,11 @@ Item {
Connections {
target: root
function onGridIndexChanged() {
if (enableAnimation && wallpaperGrid.count > 0) {
if (wallpaperGrid.count > 0) {
wallpaperGrid.currentIndex = gridIndex
if (!enableAnimation) {
wallpaperGrid.positionViewAtIndex(gridIndex, GridView.Contain)
}
}
}
}
@@ -440,7 +439,6 @@ Item {
if (modelData) {
SessionData.setWallpaper(modelData)
}
// Don't steal focus - let mainContainer keep it for keyboard nav
}
}
}
@@ -449,7 +447,7 @@ Item {
StyledText {
anchors.centerIn: parent
visible: wallpaperList.length === 0
visible: wallpaperFolderModel.count === 0
text: "No wallpapers found\n\nClick the folder icon below to browse"
font.pixelSize: 14
color: Theme.outline
@@ -457,67 +455,84 @@ Item {
}
}
Row {
Column {
width: parent.width
height: 50
spacing: Theme.spacingS
Item {
width: (parent.width - controlsRow.width - browseButton.width - Theme.spacingS) / 2
height: parent.height
}
Row {
id: controlsRow
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: 32
spacing: Theme.spacingS
DankActionButton {
Item {
width: (parent.width - controlsRow.width - browseButton.width - Theme.spacingS) / 2
height: parent.height
}
Row {
id: controlsRow
anchors.verticalCenter: parent.verticalCenter
iconName: "skip_previous"
iconSize: 20
buttonSize: 32
enabled: currentPage > 0
opacity: enabled ? 1.0 : 0.3
onClicked: {
if (currentPage > 0) {
currentPage--
spacing: Theme.spacingS
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "skip_previous"
iconSize: 20
buttonSize: 32
enabled: currentPage > 0
opacity: enabled ? 1.0 : 0.3
onClicked: {
if (currentPage > 0) {
currentPage--
}
}
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: wallpaperFolderModel.count > 0 ? `${wallpaperFolderModel.count} wallpapers ${currentPage + 1} / ${totalPages}` : "No wallpapers"
font.pixelSize: 14
color: Theme.surfaceText
opacity: 0.7
}
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "skip_next"
iconSize: 20
buttonSize: 32
enabled: currentPage < totalPages - 1
opacity: enabled ? 1.0 : 0.3
onClicked: {
if (currentPage < totalPages - 1) {
currentPage++
}
}
}
}
StyledText {
DankActionButton {
id: browseButton
anchors.verticalCenter: parent.verticalCenter
text: wallpaperList.length > 0 ? `${wallpaperList.length} wallpapers ${currentPage + 1} / ${totalPages}` : "No wallpapers"
font.pixelSize: 14
color: Theme.surfaceText
iconName: "folder_open"
iconSize: 20
buttonSize: 32
opacity: 0.7
}
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "skip_next"
iconSize: 20
buttonSize: 32
enabled: currentPage < totalPages - 1
opacity: enabled ? 1.0 : 0.3
onClicked: {
if (currentPage < totalPages - 1) {
currentPage++
}
}
onClicked: wallpaperBrowserLoader.active = true
}
}
DankActionButton {
id: browseButton
anchors.verticalCenter: parent.verticalCenter
iconName: "folder_open"
iconSize: 20
buttonSize: 32
opacity: 0.7
onClicked: wallpaperBrowserLoader.active = true
StyledText {
width: parent.width
height: 18
text: selectedFileName
font.pixelSize: 12
color: Theme.surfaceText
opacity: 0.5
visible: selectedFileName !== ""
elide: Text.ElideMiddle
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}

View File

@@ -452,10 +452,10 @@ Item {
anchors.top: SettingsData.dockPosition === SettingsData.Position.Top ? parent.top : undefined
anchors.left: SettingsData.dockPosition === SettingsData.Position.Left ? parent.left : undefined
anchors.right: SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined
anchors.bottomMargin: SettingsData.dockPosition === SettingsData.Position.Bottom ? -2 : 0
anchors.topMargin: SettingsData.dockPosition === SettingsData.Position.Top ? -2 : 0
anchors.leftMargin: SettingsData.dockPosition === SettingsData.Position.Left ? -2 : 0
anchors.rightMargin: SettingsData.dockPosition === SettingsData.Position.Right ? -2 : 0
anchors.bottomMargin: SettingsData.dockPosition === SettingsData.Position.Bottom ? -(SettingsData.dockSpacing / 2) : 0
anchors.topMargin: SettingsData.dockPosition === SettingsData.Position.Top ? -(SettingsData.dockSpacing / 2) : 0
anchors.leftMargin: SettingsData.dockPosition === SettingsData.Position.Left ? -(SettingsData.dockSpacing / 2) : 0
anchors.rightMargin: SettingsData.dockPosition === SettingsData.Position.Right ? -(SettingsData.dockSpacing / 2) : 0
sourceComponent: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? columnIndicator : rowIndicator

View File

@@ -20,6 +20,7 @@ Singleton {
property string customThemeFile: ""
property string matugenScheme: "scheme-tonal-spot"
property bool use24HourClock: true
property bool showSeconds: false
property bool useFahrenheit: false
property bool nightModeEnabled: false
property string weatherLocation: "New York, NY"
@@ -54,6 +55,7 @@ Singleton {
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : ""
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot"
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true
showSeconds = settings.showSeconds !== undefined ? settings.showSeconds : false
useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false
weatherLocation = settings.weatherLocation !== undefined ? settings.weatherLocation : "New York, NY"

View File

@@ -186,7 +186,7 @@ Item {
SystemClock {
id: systemClock
precision: SystemClock.Minutes
precision: SystemClock.Seconds
}
Rectangle {
@@ -196,40 +196,136 @@ Item {
Item {
anchors.centerIn: parent
anchors.verticalCenterOffset: -100
width: 400
width: parent.width
height: 140
StyledText {
Row {
id: clockText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
text: {
const format = GreetdSettings.use24HourClock ? "HH:mm" : "h:mm AP"
spacing: 0
property string fullTimeStr: {
const format = GreetdSettings.use24HourClock
? (GreetdSettings.showSeconds ? "HH:mm:ss" : "HH:mm")
: (GreetdSettings.showSeconds ? "h:mm:ss AP" : "h:mm AP")
return systemClock.date.toLocaleTimeString(Qt.locale(), format)
}
font.pixelSize: 120
font.weight: Font.Light
color: "white"
lineHeight: 0.8
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: clockText.bottom
anchors.topMargin: -20
text: {
if (GreetdSettings.lockDateFormat && GreetdSettings.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), GreetdSettings.lockDateFormat)
}
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat)
property var timeParts: fullTimeStr.split(':')
property string hours: timeParts[0] || ""
property string minutes: timeParts[1] || ""
property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : ""
property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '')
property string ampm: {
const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i)
return match ? match[0].trim() : ""
}
property bool hasSeconds: timeParts.length > 2
StyledText {
width: 75
text: clockText.hours.length > 1 ? clockText.hours[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
width: 75
text: clockText.hours.length > 1 ? clockText.hours[1] : clockText.hours.length > 0 ? clockText.hours[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: ":"
font.pixelSize: 120
font.weight: Font.Light
color: "white"
}
StyledText {
width: 75
text: clockText.minutes.length > 0 ? clockText.minutes[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
width: 75
text: clockText.minutes.length > 1 ? clockText.minutes[1] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: clockText.hasSeconds ? ":" : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.hasSeconds
}
StyledText {
width: 75
text: clockText.hasSeconds && clockText.seconds.length > 0 ? clockText.seconds[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
visible: clockText.hasSeconds
}
StyledText {
width: 75
text: clockText.hasSeconds && clockText.seconds.length > 1 ? clockText.seconds[1] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
visible: clockText.hasSeconds
}
StyledText {
width: 20
text: " "
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.ampm !== ""
}
StyledText {
text: clockText.ampm
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.ampm !== ""
}
font.pixelSize: Theme.fontSizeXLarge
color: "white"
opacity: 0.9
}
}
StyledText {
anchors.centerIn: parent
anchors.verticalCenterOffset: -10
text: {
if (GreetdSettings.lockDateFormat && GreetdSettings.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), GreetdSettings.lockDateFormat)
}
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat)
}
font.pixelSize: Theme.fontSizeXLarge
color: "white"
opacity: 0.9
}
Item {
anchors.centerIn: parent
anchors.verticalCenterOffset: 80

View File

@@ -68,6 +68,33 @@ if [[ -z "$COMPOSITOR" ]]; then
exit 1
fi
locate_dms_config() {
local config_name="$1"
local search_paths=()
local config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
search_paths+=("$config_home/quickshell/$config_name")
search_paths+=("/usr/share/quickshell/$config_name")
local config_dirs="${XDG_CONFIG_DIRS:-/etc/xdg}"
IFS=':' read -ra dirs <<< "$config_dirs"
for dir in "${dirs[@]}"; do
if [[ -n "$dir" ]]; then
search_paths+=("$dir/quickshell/$config_name")
fi
done
for path in "${search_paths[@]}"; do
if [[ -f "$path/shell.qml" ]]; then
echo "$path"
return 0
fi
done
return 1
}
export XDG_SESSION_TYPE=wayland
export QT_QPA_PLATFORM=wayland
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
@@ -81,7 +108,14 @@ QS_CMD="qs"
if [[ "$DMS_PATH" == /* ]]; then
QS_CMD="qs -p $DMS_PATH"
else
QS_CMD="qs -c $DMS_PATH"
RESOLVED_PATH=$(locate_dms_config "$DMS_PATH")
if [[ $? -eq 0 && -n "$RESOLVED_PATH" ]]; then
echo "Located DMS config at: $RESOLVED_PATH" >&2
QS_CMD="qs -p $RESOLVED_PATH"
else
echo "Error: Could not find DMS config '$DMS_PATH' (shell.qml) in any valid config path" >&2
exit 1
fi
fi
case "$COMPOSITOR" in

View File

@@ -197,7 +197,7 @@ Item {
SystemClock {
id: systemClock
precision: SystemClock.Minutes
precision: SystemClock.Seconds
}
Rectangle {
@@ -207,39 +207,134 @@ Item {
Item {
anchors.centerIn: parent
anchors.verticalCenterOffset: -100
width: 400
width: parent.width
height: 140
StyledText {
Row {
id: clockText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
text: {
return systemClock.date.toLocaleTimeString(Qt.locale(), SettingsData.getEffectiveTimeFormat())
}
font.pixelSize: 120
font.weight: Font.Light
color: "white"
lineHeight: 0.8
}
spacing: 0
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: clockText.bottom
anchors.topMargin: -20
text: {
if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat)
}
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat)
property string fullTimeStr: {
const format = SettingsData.getEffectiveTimeFormat()
return systemClock.date.toLocaleTimeString(Qt.locale(), format)
}
property var timeParts: fullTimeStr.split(':')
property string hours: timeParts[0] || ""
property string minutes: timeParts[1] || ""
property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : ""
property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '')
property string ampm: {
const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i)
return match ? match[0].trim() : ""
}
property bool hasSeconds: timeParts.length > 2
StyledText {
width: 75
text: clockText.hours.length > 1 ? clockText.hours[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
width: 75
text: clockText.hours.length > 1 ? clockText.hours[1] : clockText.hours.length > 0 ? clockText.hours[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: ":"
font.pixelSize: 120
font.weight: Font.Light
color: "white"
}
StyledText {
width: 75
text: clockText.minutes.length > 0 ? clockText.minutes[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
width: 75
text: clockText.minutes.length > 1 ? clockText.minutes[1] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: clockText.hasSeconds ? ":" : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.hasSeconds
}
StyledText {
width: 75
text: clockText.hasSeconds && clockText.seconds.length > 0 ? clockText.seconds[0] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
visible: clockText.hasSeconds
}
StyledText {
width: 75
text: clockText.hasSeconds && clockText.seconds.length > 1 ? clockText.seconds[1] : ""
font.pixelSize: 120
font.weight: Font.Light
color: "white"
horizontalAlignment: Text.AlignHCenter
visible: clockText.hasSeconds
}
StyledText {
width: 20
text: " "
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.ampm !== ""
}
StyledText {
text: clockText.ampm
font.pixelSize: 120
font.weight: Font.Light
color: "white"
visible: clockText.ampm !== ""
}
font.pixelSize: Theme.fontSizeXLarge
color: "white"
opacity: 0.9
}
}
StyledText {
anchors.centerIn: parent
anchors.verticalCenterOffset: -25
text: {
if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat)
}
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat)
}
font.pixelSize: Theme.fontSizeXLarge
color: "white"
opacity: 0.9
}
ColumnLayout {
anchors.centerIn: parent
anchors.verticalCenterOffset: 50

View File

@@ -48,7 +48,8 @@ Item {
return "transparent"
}
const baseColor = mouseArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor
const isHovered = mouseArea.containsMouse || (root.isHovered ?? false)
const baseColor = isHovered ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency)
}

View File

@@ -14,14 +14,23 @@ Row {
width: parent.width
spacing: Theme.spacingM
Component.onCompleted: {
property bool isInitialized: false
function loadValue() {
const settings = findSettings()
if (settings) {
value = settings.loadValue(settingKey, defaultValue)
if (settings && settings.pluginService) {
const loadedValue = settings.loadValue(settingKey, defaultValue)
value = loadedValue
isInitialized = true
}
}
Component.onCompleted: {
loadValue()
}
onValueChanged: {
if (!isInitialized) return
const settings = findSettings()
if (settings) {
settings.saveValue(settingKey, value)

View File

@@ -460,6 +460,58 @@ Item {
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.2
visible: CompositorService.isNiri
}
Row {
width: parent.width
spacing: Theme.spacingM
visible: CompositorService.isNiri
DankIcon {
name: "blur_on"
size: Theme.iconSize
color: SettingsData.blurWallpaperOnOverview ? Theme.primary : Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - Theme.spacingM - blurOverviewToggle.width - Theme.spacingM
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("Blur on Overview")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Blur wallpaper when niri overview is open")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
DankToggle {
id: blurOverviewToggle
anchors.verticalCenter: parent.verticalCenter
checked: SettingsData.blurWallpaperOnOverview
onToggled: checked => {
SettingsData.setBlurWallpaperOnOverview(checked)
}
}
}
// Per-Mode Wallpaper Section - Full Width
Rectangle {
width: parent.width
@@ -959,6 +1011,67 @@ Item {
}
}
StyledRect {
width: parent.width
height: blurLayerColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 0
visible: CompositorService.isNiri
Column {
id: blurLayerColumn
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "blur_on"
size: Theme.iconSize
color: SettingsData.blurredWallpaperLayer ? Theme.primary : Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - Theme.spacingM - blurLayerToggle.width - Theme.spacingM
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("Blur Layer")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
DankToggle {
id: blurLayerToggle
anchors.verticalCenter: parent.verticalCenter
checked: SettingsData.blurredWallpaperLayer
onToggled: checked => {
SettingsData.setBlurredWallpaperLayer(checked)
}
}
}
}
}
StyledRect {
width: parent.width
height: lightModeRow.implicitHeight + Theme.spacingL * 2

View File

@@ -1,4 +1,5 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
@@ -6,6 +7,7 @@ import Quickshell.Io
import qs.Common
import qs.Widgets
import qs.Modules
import qs.Services
Variants {
model: {
@@ -467,6 +469,15 @@ Variants {
})
}
}
MultiEffect {
anchors.fill: parent
source: effectLoader.active ? effectLoader.item : (root.actualTransitionType === "none" ? currentWallpaper : null)
visible: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview
blurEnabled: true
blur: 0.8
blurMax: 48
}
}
}
}

View File

@@ -267,31 +267,9 @@ sudo dnf copr enable avengemedia/danklinux && sudo dnf install quickshell-git
# ! TODO - document other distros
```
#### 2. Install fonts
*Inter Variable* and *Fira Code* are not strictly required, but they are the default fonts of dms.
#### 2. Install the shell
#### 2.1 Install Material Symbols
```bash
sudo curl -L "https://github.com/google/material-design-icons/raw/master/variablefont/MaterialSymbolsRounded%5BFILL%2CGRAD%2Copsz%2Cwght%5D.ttf" -o /usr/share/fonts/MaterialSymbolsRounded.ttf
```
#### 2.2 Install Inter Variable
```bash
sudo curl -L "https://github.com/rsms/inter/raw/refs/tags/v4.1/docs/font-files/InterVariable.ttf" -o /usr/share/fonts/InterVariable.ttf
```
#### 2.3 Install Fira Code (monospace font)
```bash
sudo curl -L "https://github.com/tonsky/FiraCode/releases/latest/download/FiraCode-Regular.ttf" -o /usr/share/fonts/FiraCode-Regular.ttf
```
#### 2.4 Refresh font cache
```bash
fc-cache -fv
```
#### 3. Install the shell
#### 3.1. Clone latest QML
#### 2.1. Clone latest QML
```bash
mkdir ~/.config/quickshell && git clone https://github.com/AvengeMedia/DankMaterialShell.git ~/.config/quickshell/dms
@@ -305,7 +283,7 @@ cd ~/.config/quickshell/dms
git checkout $(git describe --tags --abbrev=0)
```
#### 3.2. Install latest dms CLI
#### 2.2. Install latest dms CLI
```bash
sudo sh -c "curl -L https://github.com/AvengeMedia/danklinux/releases/latest/download/dms-$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').gz | gunzip | tee /usr/local/bin/dms > /dev/null && chmod +x /usr/local/bin/dms"
@@ -319,9 +297,9 @@ git clone https://github.com/AvengeMedia/danklinux.git && cd danklinux
make && sudo make install
```
#### 4. Optional Features (system monitoring, clipboard history, brightness controls, etc.)
#### 3. Optional Features (system monitoring, clipboard history, brightness controls, etc.)
#### 4.1 Core optional dependencies
#### 3.1 Core optional dependencies
```bash
# Arch Linux
sudo pacman -S cava wl-clipboard cliphist brightnessctl qt6-multimedia accountsservice
@@ -329,13 +307,13 @@ paru -S matugen-bin dgop
# Fedora
sudo dnf install cava wl-clipboard brightnessctl qt6-qtmultimedia accountsservice
sudo dnf copr enable avengemedia/danklinux && sudo dnf install cliphist ghostty hyprpicker material-symbols-fonts matugen
sudo dnf copr enable avengemedia/danklinux && sudo dnf install cliphist ghostty hyprpicker matugen
```
Note: by enabling and installing the avengemedia/dms copr above, these core dependencies will automatically be available for use.
*Other distros will just need to find sources for the above packages*
#### 4.2 - dgop manual installation
#### 3.2 - dgop manual installation
`dgop` is available via AUR and a nix flake, other distributions can install it manually.
@@ -439,6 +417,37 @@ binds {
spawn "dms" "ipc" "call" "night" "toggle";
}
}
// You probably want this too
debug {
honor-xdg-activation-with-invalid-serial
}
```
#### niri - place wallpaper on overview (blurred or non-blurred)
Place the wallpaper on backdrop using a layer-rule for a contiguous surface.
```kdl
layer-rule {
match namespace="quickshell"
place-within-backdrop true
}
```
If using "Blur Layer" option, you may want the blurred layer to appear on overview only, that can be done with some layer rules:
```kdl
layer-rule {
match namespace="dms:blurwallpaper"
opacity 0.0
}
layer-rule {
match namespace="dms:blurwallpaper"
place-within-backdrop true
opacity 1.0
}
```
#### niri theming
@@ -790,7 +799,6 @@ All settings are configurable in
**Common issues:**
- **Missing icons:** Verify Material Symbols font installation with `fc-list | grep Material`
- **No dynamic theming:** Install matugen and enable in settings
- **Qt apps not themed:** Configure qt5ct/qt6ct and set QT_QPA_PLATFORMTHEME
- **Calendar not syncing:** Check vdirsyncer credentials and network connectivity

View File

@@ -76,6 +76,10 @@ Singleton {
target: Hyprland
function onFocusedWorkspaceChanged() { root.scheduleSort() }
}
Connections {
target: NiriService
function onWindowsChanged() { root.scheduleSort() }
}
Component.onCompleted: {
detectCompositor()

View File

@@ -11,6 +11,7 @@ Singleton {
id: root
property bool networkAvailable: false
property string backend: ""
property string networkStatus: "disconnected"
property string primaryConnection: ""
@@ -68,6 +69,24 @@ Singleton {
property string wifiPassword: ""
property string forgetSSID: ""
property var vpnProfiles: []
property var vpnActive: []
property bool vpnAvailable: false
property bool vpnIsBusy: false
property alias profiles: root.vpnProfiles
property alias activeConnections: root.vpnActive
property var activeUuids: vpnActive.map(v => v.uuid).filter(u => !!u)
property var activeNames: vpnActive.map(v => v.name).filter(n => !!n)
property string activeUuid: activeUuids.length > 0 ? activeUuids[0] : ""
property string activeName: activeNames.length > 0 ? activeNames[0] : ""
property string activeDevice: vpnActive.length > 0 ? (vpnActive[0].device || "") : ""
property string activeState: vpnActive.length > 0 ? (vpnActive[0].state || "") : ""
property bool vpnConnected: activeUuids.length > 0
property alias available: root.vpnAvailable
property alias isBusy: root.vpnIsBusy
property alias connected: root.vpnConnected
property string networkInfoSSID: ""
property string networkInfoDetails: ""
property bool networkInfoLoading: false
@@ -93,7 +112,7 @@ Singleton {
signal networksUpdated
signal connectionChanged
signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason)
signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason, string connType, string connName, string vpnService)
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
@@ -109,7 +128,7 @@ Singleton {
function onNetworkStateUpdate(data) {
const networksCount = data.wifiNetworks?.length ?? "null"
console.log("NetworkManagerService: Subscription update received, networks:", networksCount)
console.log("DMSNetworkService: Subscription update received, networks:", networksCount)
updateState(data)
}
}
@@ -148,8 +167,6 @@ Singleton {
networkAvailable = DMSService.capabilities.includes("network")
console.log("NetworkManagerService: Network available:", networkAvailable)
if (networkAvailable && !stateInitialized) {
stateInitialized = true
getState()
@@ -165,7 +182,11 @@ Singleton {
credentialsReason = data.reason || "Credentials required"
credentialsRequested = true
credentialsNeeded(credentialsToken, credentialsSSID, credentialsSetting, credentialsFields, credentialsHints, credentialsReason)
const connType = data.connType || ""
const connName = data.name || data.connectionId || ""
const vpnService = data.vpnService || ""
credentialsNeeded(credentialsToken, credentialsSSID, credentialsSetting, credentialsFields, credentialsHints, credentialsReason, connType, connName, vpnService)
}
function addRef() {
@@ -191,7 +212,6 @@ Singleton {
if (response.result) {
updateState(response.result)
if (!initialStateFetched && response.result.wifiEnabled && (!response.result.wifiNetworks || response.result.wifiNetworks.length === 0)) {
console.log("NetworkManagerService: Initial state has no networks, triggering scan")
initialStateFetched = true
Qt.callLater(() => scanWifi())
}
@@ -203,6 +223,8 @@ Singleton {
const previousConnecting = isConnecting
const previousConnectingSSID = connectingSSID
backend = state.backend || ""
vpnAvailable = networkAvailable && backend === "networkmanager"
networkStatus = state.networkStatus || "disconnected"
primaryConnection = state.primaryConnection || ""
@@ -245,6 +267,12 @@ Singleton {
networksUpdated()
}
if (state.vpnProfiles) {
vpnProfiles = state.vpnProfiles
}
vpnActive = state.vpnActive || []
userPreference = state.preference || "auto"
isConnecting = state.isConnecting || false
connectingSSID = state.connectingSSID || ""
@@ -254,7 +282,7 @@ Singleton {
if (pendingConnectionSSID) {
if (wifiConnected && currentWifiSSID === pendingConnectionSSID && wifiIP) {
const elapsed = Date.now() - pendingConnectionStartTime
console.info("NetworkManagerService: Successfully connected to", pendingConnectionSSID, "in", elapsed, "ms")
console.info("DMSNetworkService: Successfully connected to", pendingConnectionSSID, "in", elapsed, "ms")
ToastService.showInfo(`Connected to ${pendingConnectionSSID}`)
if (userPreference === "wifi" || userPreference === "auto") {
@@ -264,16 +292,17 @@ Singleton {
pendingConnectionSSID = ""
connectionStatus = "connected"
} else if (previousConnecting && !isConnecting && !wifiConnected) {
const elapsed = Date.now() - pendingConnectionStartTime
const isCancellationError = connectionError === "user-canceled"
const isBadCredentials = connectionError === "bad-credentials"
if (elapsed < 5000) {
console.log("NetworkManagerService: Quick connection failure, likely authentication error")
if (isCancellationError) {
connectionStatus = "cancelled"
pendingConnectionSSID = ""
} else if (isBadCredentials) {
connectionStatus = "invalid_password"
pendingConnectionSSID = ""
} else {
console.log("NetworkManagerService: Connection failed for", pendingConnectionSSID)
if (connectionError === "connection-failed") {
ToastService.showError(I18n.tr("Connection failed. Check password and try again."))
} else if (connectionError) {
if (connectionError) {
ToastService.showError(I18n.tr("Failed to connect to ") + pendingConnectionSSID)
}
connectionStatus = "failed"
@@ -315,14 +344,12 @@ Singleton {
function scanWifi() {
if (!networkAvailable || isScanning || !wifiEnabled) return
console.log("NetworkManagerService: Starting WiFi scan...")
isScanning = true
DMSService.sendRequest("network.wifi.scan", null, response => {
isScanning = false
if (response.error) {
console.warn("NetworkManagerService: WiFi scan failed:", response.error)
console.warn("DMSNetworkService: WiFi scan failed:", response.error)
} else {
console.info("NetworkManagerService: Scan completed")
Qt.callLater(() => getState())
}
})
@@ -362,15 +389,15 @@ Singleton {
DMSService.sendRequest("network.wifi.connect", params, response => {
if (response.error) {
console.log("NetworkManagerService: Connection request failed:", response.error)
if (connectionStatus === "cancelled") {
return
}
connectionError = response.error
lastConnectionError = response.error
pendingConnectionSSID = ""
connectionStatus = "failed"
ToastService.showError(I18n.tr("Failed to start connection to ") + ssid)
} else {
console.log("NetworkManagerService: Connection request sent for", ssid)
}
})
}
@@ -390,7 +417,12 @@ Singleton {
}
function submitCredentials(token, secrets, save) {
if (!networkAvailable || DMSService.apiVersion < 7) return
console.log("submitCredentials: networkAvailable=" + networkAvailable + " apiVersion=" + DMSService.apiVersion)
if (!networkAvailable || DMSService.apiVersion < 7) {
console.warn("submitCredentials: Aborting - networkAvailable=" + networkAvailable + " apiVersion=" + DMSService.apiVersion)
return
}
const params = {
token: token,
@@ -398,13 +430,11 @@ Singleton {
save: save || false
}
console.log("NetworkManagerService: Submitting credentials for token", token)
credentialsRequested = false
DMSService.sendRequest("network.credentials.submit", params, response => {
if (response.error) {
console.warn("NetworkManagerService: Failed to submit credentials:", response.error)
console.warn("DMSNetworkService: Failed to submit credentials:", response.error)
}
})
}
@@ -413,19 +443,16 @@ Singleton {
if (!networkAvailable || DMSService.apiVersion < 7) return
const params = {
token: token,
cancel: true
token: token
}
console.log("NetworkManagerService: Cancelling credentials for token", token)
credentialsRequested = false
pendingConnectionSSID = ""
connectionStatus = "cancelled"
DMSService.sendRequest("network.credentials.submit", params, response => {
DMSService.sendRequest("network.credentials.cancel", params, response => {
if (response.error) {
console.warn("NetworkManagerService: Failed to cancel credentials:", response.error)
console.warn("DMSNetworkService: Failed to cancel credentials:", response.error)
}
})
}
@@ -679,31 +706,125 @@ Singleton {
}
}
function refreshVpnProfiles() {
if (!vpnAvailable) return
DMSService.sendRequest("network.vpn.profiles", null, response => {
if (response.result) {
vpnProfiles = response.result
}
})
}
function refreshVpnActive() {
if (!vpnAvailable) return
DMSService.sendRequest("network.vpn.active", null, response => {
if (response.result) {
vpnActive = response.result
}
})
}
function connectVpn(uuidOrName, singleActive = false) {
if (!vpnAvailable || vpnIsBusy) return
vpnIsBusy = true
const params = {
uuidOrName: uuidOrName,
singleActive: singleActive
}
DMSService.sendRequest("network.vpn.connect", params, response => {
vpnIsBusy = false
if (response.error) {
ToastService.showError(I18n.tr("Failed to connect VPN"))
} else {
Qt.callLater(() => getState())
}
})
}
function connect(uuidOrName, singleActive = false) {
connectVpn(uuidOrName, singleActive)
}
function disconnectVpn(uuidOrName) {
if (!vpnAvailable || vpnIsBusy) return
vpnIsBusy = true
const params = {
uuidOrName: uuidOrName
}
DMSService.sendRequest("network.vpn.disconnect", params, response => {
vpnIsBusy = false
if (response.error) {
ToastService.showError(I18n.tr("Failed to disconnect VPN"))
} else {
Qt.callLater(() => getState())
}
})
}
function disconnect(uuidOrName) {
disconnectVpn(uuidOrName)
}
function disconnectAllVpns() {
if (!vpnAvailable || vpnIsBusy) return
vpnIsBusy = true
DMSService.sendRequest("network.vpn.disconnectAll", null, response => {
vpnIsBusy = false
if (response.error) {
ToastService.showError(I18n.tr("Failed to disconnect VPNs"))
} else {
Qt.callLater(() => getState())
}
})
}
function disconnectAllActive() {
disconnectAllVpns()
}
function toggleVpn(uuid) {
if (uuid) {
if (isActiveVpnUuid(uuid)) {
disconnectVpn(uuid)
} else {
connectVpn(uuid)
}
return
}
if (vpnProfiles.length > 0) {
connectVpn(vpnProfiles[0].uuid)
}
}
function toggle(uuid) {
toggleVpn(uuid)
}
function isActiveVpnUuid(uuid) {
return activeUuids && activeUuids.indexOf(uuid) !== -1
}
function isActiveUuid(uuid) {
return isActiveVpnUuid(uuid)
}
function refreshNetworkState() {
if (networkAvailable) {
getState()
}
}
function splitNmcliFields(line) {
const parts = []
let cur = ""
let escape = false
for (var i = 0; i < line.length; i++) {
const ch = line[i]
if (escape) {
cur += ch
escape = false
} else if (ch === '\\') {
escape = true
} else if (ch === ':') {
parts.push(cur)
cur = ""
} else {
cur += ch
}
}
parts.push(cur)
return parts
}
}

View File

@@ -271,6 +271,7 @@ Singleton {
function sendRequest(method, params, callback) {
if (!isConnected) {
console.warn("DMSService.sendRequest: Not connected, method:", method)
if (callback) {
callback({
"error": "not connected to DMS socket"
@@ -294,6 +295,7 @@ Singleton {
pendingRequests[id] = callback
}
console.log("DMSService.sendRequest: Sending request id=" + id + " method=" + method)
requestSocket.send(request)
}

View File

@@ -11,6 +11,7 @@ Singleton {
id: root
property bool networkAvailable: activeService !== null
property string backend: activeService?.backend ?? ""
property string networkStatus: activeService?.networkStatus ?? "disconnected"
property string primaryConnection: activeService?.primaryConnection ?? ""
@@ -79,7 +80,7 @@ Singleton {
signal networksUpdated
signal connectionChanged
signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason)
signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason, string connType, string connName, string vpnService)
property bool usingLegacy: false
property var activeService: null
@@ -97,16 +98,16 @@ Singleton {
}
Connections {
target: NetworkManagerService
target: DMSNetworkService
function onNetworkAvailableChanged() {
if (!activeService && NetworkManagerService.networkAvailable) {
console.info("NetworkService: Network capability detected, using NetworkManagerService")
activeService = NetworkManagerService
if (!activeService && DMSNetworkService.networkAvailable) {
console.info("NetworkService: Network capability detected, using DMSNetworkService")
activeService = DMSNetworkService
usingLegacy = false
console.info("NetworkService: Switched to NetworkManagerService, networkAvailable:", networkAvailable)
console.info("NetworkService: Switched to DMSNetworkService, networkAvailable:", networkAvailable)
connectSignals()
} else if (!activeService && !NetworkManagerService.networkAvailable && socketPath && socketPath.length > 0) {
} else if (!activeService && !DMSNetworkService.networkAvailable && socketPath && socketPath.length > 0) {
console.info("NetworkService: Network capability not available in DMS, using LegacyNetworkService")
useLegacyService()
}

View File

@@ -177,49 +177,47 @@ Singleton {
}
function sortWindowsByLayout(windowList) {
return [...windowList].sort((a, b) => {
const aWorkspace = workspaces[a.workspace_id]
const bWorkspace = workspaces[b.workspace_id]
const enriched = windowList.map(w => {
const ws = workspaces[w.workspace_id]
if (!ws) {
return {
window: w,
outputX: 999999,
outputY: 999999,
wsIdx: 999999,
col: 999999,
row: 999999
}
}
if (aWorkspace && bWorkspace) {
const aOutput = aWorkspace.output
const bOutput = bWorkspace.output
const outputInfo = outputs[ws.output]
const outputX = (outputInfo && outputInfo.logical) ? outputInfo.logical.x : 999999
const outputY = (outputInfo && outputInfo.logical) ? outputInfo.logical.y : 999999
const aOutputInfo = outputs[aOutput]
const bOutputInfo = outputs[bOutput]
const pos = w.layout?.pos_in_scrolling_layout
const col = (pos && pos.length >= 2) ? pos[0] : 999999
const row = (pos && pos.length >= 2) ? pos[1] : 999999
if (aOutputInfo && bOutputInfo && aOutputInfo.logical && bOutputInfo.logical) {
if (aOutputInfo.logical.x !== bOutputInfo.logical.x) {
return aOutputInfo.logical.x - bOutputInfo.logical.x
}
if (aOutputInfo.logical.y !== bOutputInfo.logical.y) {
return aOutputInfo.logical.y - bOutputInfo.logical.y
}
}
return {
window: w,
outputX: outputX,
outputY: outputY,
wsIdx: ws.idx,
col: col,
row: row
}
})
if (aOutput === bOutput && aWorkspace.idx !== bWorkspace.idx) {
return aWorkspace.idx - bWorkspace.idx
}
}
enriched.sort((a, b) => {
if (a.outputX !== b.outputX) return a.outputX - b.outputX
if (a.outputY !== b.outputY) return a.outputY - b.outputY
if (a.wsIdx !== b.wsIdx) return a.wsIdx - b.wsIdx
if (a.col !== b.col) return a.col - b.col
if (a.row !== b.row) return a.row - b.row
return a.window.id - b.window.id
})
if (a.workspace_id === b.workspace_id && a.layout && b.layout) {
if (a.layout.pos_in_scrolling_layout && b.layout.pos_in_scrolling_layout) {
const aPos = a.layout.pos_in_scrolling_layout
const bPos = b.layout.pos_in_scrolling_layout
if (aPos.length > 1 && bPos.length > 1) {
if (aPos[0] !== bPos[0]) {
return aPos[0] - bPos[0]
}
if (aPos[1] !== bPos[1]) {
return aPos[1] - bPos[1]
}
}
}
}
return a.id - b.id
})
return enriched.map(e => e.window)
}
function handleNiriEvent(event) {
@@ -235,6 +233,9 @@ Singleton {
case 'WorkspaceActiveWindowChanged':
handleWorkspaceActiveWindowChanged(event.WorkspaceActiveWindowChanged)
break
case 'WindowFocusChanged':
handleWindowFocusChanged(event.WindowFocusChanged)
break
case 'WindowsChanged':
handleWindowsChanged(event.WindowsChanged)
break
@@ -269,14 +270,18 @@ Singleton {
}
function handleWorkspacesChanged(data) {
const workspaces = {}
const newWorkspaces = {}
for (const ws of data.workspaces) {
workspaces[ws.id] = ws
const oldWs = root.workspaces[ws.id]
newWorkspaces[ws.id] = ws
if (oldWs && oldWs.active_window_id !== undefined) {
newWorkspaces[ws.id].active_window_id = oldWs.active_window_id
}
}
root.workspaces = workspaces
allWorkspaces = [...data.workspaces].sort((a, b) => a.idx - b.idx)
root.workspaces = newWorkspaces
allWorkspaces = Object.values(newWorkspaces).sort((a, b) => a.idx - b.idx)
focusedWorkspaceIndex = allWorkspaces.findIndex(w => w.is_focused)
if (focusedWorkspaceIndex >= 0) {
@@ -298,33 +303,101 @@ Singleton {
}
const output = ws.output
const updatedWorkspaces = {}
for (const id in root.workspaces) {
const workspace = root.workspaces[id]
const got_activated = workspace.id === data.id
const updatedWs = {}
for (let prop in workspace) {
updatedWs[prop] = workspace[prop]
}
if (workspace.output === output) {
workspace.is_active = got_activated
updatedWs.is_active = got_activated
}
if (data.focused) {
workspace.is_focused = got_activated
updatedWs.is_focused = got_activated
}
updatedWorkspaces[id] = updatedWs
}
root.workspaces = updatedWorkspaces
focusedWorkspaceId = data.id
focusedWorkspaceIndex = allWorkspaces.findIndex(w => w.id === data.id)
focusedWorkspaceIndex = Object.values(updatedWorkspaces).findIndex(w => w.id === data.id)
if (focusedWorkspaceIndex >= 0) {
currentOutput = allWorkspaces[focusedWorkspaceIndex].output || ""
const ws = Object.values(updatedWorkspaces)[focusedWorkspaceIndex]
currentOutput = ws.output || ""
}
allWorkspaces = Object.values(root.workspaces).sort((a, b) => a.idx - b.idx)
allWorkspaces = Object.values(updatedWorkspaces).sort((a, b) => a.idx - b.idx)
updateCurrentOutputWorkspaces()
workspacesChanged()
}
function handleWindowFocusChanged(data) {
const focusedWindowId = data.id
let focusedWindow = null
const updatedWindows = []
for (var i = 0; i < windows.length; i++) {
const w = windows[i]
const updatedWindow = {}
for (let prop in w) {
updatedWindow[prop] = w[prop]
}
updatedWindow.is_focused = (w.id === focusedWindowId)
if (updatedWindow.is_focused) {
focusedWindow = updatedWindow
}
updatedWindows.push(updatedWindow)
}
windows = updatedWindows
if (focusedWindow) {
const ws = root.workspaces[focusedWindow.workspace_id]
if (ws && ws.active_window_id !== focusedWindowId) {
const updatedWs = {}
for (let prop in ws) {
updatedWs[prop] = ws[prop]
}
updatedWs.active_window_id = focusedWindowId
const updatedWorkspaces = {}
for (const id in root.workspaces) {
updatedWorkspaces[id] = id === focusedWindow.workspace_id ? updatedWs : root.workspaces[id]
}
root.workspaces = updatedWorkspaces
}
}
}
function handleWorkspaceActiveWindowChanged(data) {
const ws = root.workspaces[data.workspace_id]
if (ws) {
const updatedWs = {}
for (let prop in ws) {
updatedWs[prop] = ws[prop]
}
updatedWs.active_window_id = data.active_window_id
const updatedWorkspaces = {}
for (const id in root.workspaces) {
updatedWorkspaces[id] = id === data.workspace_id ? updatedWs : root.workspaces[id]
}
root.workspaces = updatedWorkspaces
}
const updatedWindows = []
for (var i = 0; i < windows.length; i++) {
@@ -366,10 +439,9 @@ Singleton {
const updatedWindows = [...windows]
updatedWindows[existingIndex] = window
windows = sortWindowsByLayout(updatedWindows)
return
} else {
windows = sortWindowsByLayout([...windows, window])
}
windows = sortWindowsByLayout([...windows, window])
}
function handleWindowLayoutsChanged(data) {
@@ -400,7 +472,6 @@ Singleton {
return
windows = sortWindowsByLayout(updatedWindows)
windowsChanged()
}
function handleOutputsChanged(data) {
@@ -452,12 +523,19 @@ Singleton {
if (!ws)
return
ws.is_urgent = data.urgent
const idx = allWorkspaces.findIndex(w => w.id === data.id)
if (idx >= 0) {
allWorkspaces[idx].is_urgent = data.urgent
const updatedWs = {}
for (let prop in ws) {
updatedWs[prop] = ws[prop]
}
updatedWs.is_urgent = data.urgent
const updatedWorkspaces = {}
for (const id in root.workspaces) {
updatedWorkspaces[id] = id === data.id ? updatedWs : root.workspaces[id]
}
root.workspaces = updatedWorkspaces
allWorkspaces = Object.values(updatedWorkspaces).sort((a, b) => a.idx - b.idx)
windowUrgentChanged()
}
@@ -637,10 +715,13 @@ Singleton {
usedToplevels.add(bestMatch)
const workspace = workspaces[niriWindow.workspace_id]
const isFocused = niriWindow.is_focused ?? (workspace && workspace.active_window_id === niriWindow.id) ?? false
const enrichedToplevel = {
"appId": bestMatch.appId,
"title": bestMatch.title,
"activated": niriWindow.is_focused ?? false,
"activated": isFocused,
"niriWindowId": niriWindow.id,
"niriWorkspaceId": niriWindow.workspace_id,
"activate": function () {
@@ -723,10 +804,13 @@ Singleton {
usedToplevels.add(bestMatch)
const workspace = workspaces[niriWindow.workspace_id]
const isFocused = niriWindow.is_focused ?? (workspace && workspace.active_window_id === niriWindow.id) ?? false
const enrichedToplevel = {
"appId": bestMatch.appId,
"title": bestMatch.title,
"activated": niriWindow.is_focused ?? false,
"activated": isFocused,
"niriWindowId": niriWindow.id,
"niriWorkspaceId": niriWindow.workspace_id,
"activate": function () {

View File

@@ -263,4 +263,22 @@ Singleton {
running: refCount > 0 && distributionSupported && (pkgManager || updChecker)
onTriggered: checkForUpdates()
}
IpcHandler {
target: "systemupdater"
function updatestatus(): string {
if (root.isChecking) {
return "ERROR: already checking"
}
if (!distributionSupported) {
return "ERROR: distribution not supported"
}
if (!pkgManager && !updChecker) {
return "ERROR: update checker not available"
}
root.checkForUpdates()
return "SUCCESS: Now checking..."
}
}
}

View File

@@ -1,246 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
// Minimal VPN controller backed by NetworkManager (nmcli + D-Bus monitor)
Singleton {
id: root
property int refCount: 0
onRefCountChanged: {
console.log("VpnService: refCount changed to", refCount)
if (refCount > 0 && !nmMonitor.running) {
console.log("VpnService: Starting nmMonitor")
nmMonitor.running = true
refreshAll()
} else if (refCount === 0 && nmMonitor.running) {
console.log("VpnService: Stopping nmMonitor")
nmMonitor.running = false
}
}
// State
property bool available: true
property bool isBusy: false
property string errorMessage: ""
// Profiles discovered on the system
// [{ name, uuid, type }]
property var profiles: []
// Allow multiple active VPNs (set true to allow concurrent connections)
// Default: allow multiple, to align with NetworkManager capability
property bool singleActive: false
// Active VPN connections (may be multiple)
// Full list and convenience projections
property var activeConnections: [] // [{ name, uuid, device, state }]
property var activeUuids: []
property var activeNames: []
// Back-compat single values (first active if present)
property string activeUuid: activeUuids.length > 0 ? activeUuids[0] : ""
property string activeName: activeNames.length > 0 ? activeNames[0] : ""
property string activeDevice: activeConnections.length > 0 ? (activeConnections[0].device || "") : ""
property string activeState: activeConnections.length > 0 ? (activeConnections[0].state || "") : ""
property bool connected: activeUuids.length > 0
// Use implicit property notify signals (profilesChanged, activeUuidChanged, etc.)
function refreshAll() {
listProfiles()
refreshActive()
}
// Monitor NetworkManager changes and refresh on activity
Process {
id: nmMonitor
command: ["gdbus", "monitor", "--system", "--dest", "org.freedesktop.NetworkManager"]
running: false
stdout: SplitParser {
splitMarker: "\n"
onRead: line => {
if (line.includes("ActiveConnection") || line.includes("PropertiesChanged") || line.includes("StateChanged")) {
refreshAll()
}
}
}
}
// Query all VPN profiles
function listProfiles() {
getProfiles.running = true
}
Process {
id: getProfiles
command: ["bash", "-lc", "nmcli -t -f NAME,UUID,TYPE connection show | while IFS=: read -r name uuid type; do case \"$type\" in vpn) svc=$(nmcli -g vpn.service-type connection show uuid \"$uuid\" 2>/dev/null); echo \"$name:$uuid:$type:$svc\" ;; wireguard) echo \"$name:$uuid:$type:\" ;; *) : ;; esac; done"]
running: false
stdout: StdioCollector {
onStreamFinished: {
const lines = text.trim().length ? text.trim().split('\n') : []
const out = []
for (const line of lines) {
const parts = line.split(':')
if (parts.length >= 3 && (parts[2] === "vpn" || parts[2] === "wireguard")) {
const svc = parts.length >= 4 ? parts[3] : ""
out.push({ name: parts[0], uuid: parts[1], type: parts[2], serviceType: svc })
}
}
root.profiles = out
}
}
}
// Query active VPN connection
function refreshActive() {
getActive.running = true
}
Process {
id: getActive
command: ["nmcli", "-t", "-f", "NAME,UUID,TYPE,DEVICE,STATE", "connection", "show", "--active"]
running: false
stdout: StdioCollector {
onStreamFinished: {
const lines = text.trim().length ? text.trim().split('\n') : []
let act = []
for (const line of lines) {
const parts = line.split(':')
if (parts.length >= 5 && (parts[2] === "vpn" || parts[2] === "wireguard")) {
act.push({ name: parts[0], uuid: parts[1], device: parts[3], state: parts[4] })
}
}
root.activeConnections = act
root.activeUuids = act.map(a => a.uuid).filter(u => !!u)
root.activeNames = act.map(a => a.name).filter(n => !!n)
}
}
}
function isActiveUuid(uuid) {
return root.activeUuids && root.activeUuids.indexOf(uuid) !== -1
}
function _looksLikeUuid(s) {
// Very loose check for UUID pattern
return s && s.indexOf('-') !== -1 && s.length >= 8
}
function connect(uuidOrName) {
if (root.isBusy) return
root.isBusy = true
root.errorMessage = ""
if (root.singleActive) {
// Bring down all active VPNs, then bring up the requested one
const isUuid = _looksLikeUuid(uuidOrName)
const escaped = ('' + uuidOrName).replace(/'/g, "'\\''")
const upCmd = isUuid ? `nmcli connection up uuid '${escaped}'` : `nmcli connection up id '${escaped}'`
const script = `set -e\n` +
`nmcli -t -f UUID,TYPE connection show --active | awk -F: '$2 ~ /^(vpn|wireguard)$/ {print $1}' | while read u; do [ -n \"$u\" ] && nmcli connection down uuid \"$u\" || true; done\n` +
upCmd + `\n`
vpnSwitch.command = ["bash", "-lc", script]
vpnSwitch.running = true
} else {
if (_looksLikeUuid(uuidOrName)) {
vpnUp.command = ["nmcli", "connection", "up", "uuid", uuidOrName]
} else {
vpnUp.command = ["nmcli", "connection", "up", "id", uuidOrName]
}
vpnUp.running = true
}
}
function disconnect(uuidOrName) {
if (root.isBusy) return
root.isBusy = true
root.errorMessage = ""
if (_looksLikeUuid(uuidOrName)) {
vpnDown.command = ["nmcli", "connection", "down", "uuid", uuidOrName]
} else {
vpnDown.command = ["nmcli", "connection", "down", "id", uuidOrName]
}
vpnDown.running = true
}
function toggle(uuid) {
if (uuid) {
if (isActiveUuid(uuid)) disconnect(uuid)
else connect(uuid)
return
}
if (root.profiles.length > 0) {
connect(root.profiles[0].uuid)
}
}
Process {
id: vpnUp
running: false
stdout: StdioCollector {
onStreamFinished: {
root.isBusy = false
if (!text.toLowerCase().includes("successfully")) {
root.errorMessage = text.trim()
}
refreshAll()
}
}
onExited: exitCode => {
root.isBusy = false
if (exitCode !== 0 && root.errorMessage === "") {
root.errorMessage = "Failed to connect VPN"
}
}
}
Process {
id: vpnDown
running: false
stdout: StdioCollector {
onStreamFinished: {
root.isBusy = false
if (!text.toLowerCase().includes("deactivated") && !text.toLowerCase().includes("successfully")) {
root.errorMessage = text.trim()
}
refreshAll()
}
}
onExited: exitCode => {
root.isBusy = false
if (exitCode !== 0 && root.errorMessage === "") {
root.errorMessage = "Failed to disconnect VPN"
}
}
}
function disconnectAllActive() {
if (root.isBusy) return
root.isBusy = true
const script = `nmcli -t -f UUID,TYPE connection show --active | awk -F: '$2 ~ /^(vpn|wireguard)$/ {print $1}' | while read u; do [ -n \"$u\" ] && nmcli connection down uuid \"$u\" || true; done`
vpnSwitch.command = ["bash", "-lc", script]
vpnSwitch.running = true
}
// Sequenced down/up using a single shell for exclusive switch
Process {
id: vpnSwitch
running: false
stdout: StdioCollector {
onStreamFinished: {
root.isBusy = false
refreshAll()
}
}
onExited: exitCode => {
root.isBusy = false
if (exitCode !== 0 && root.errorMessage === "") {
root.errorMessage = "Failed to switch VPN"
}
}
}
}

View File

@@ -1 +1 @@
v0.2.2
v0.2.4

View File

@@ -1,9 +1,9 @@
import QtQuick
import qs.Common
StyledText {
id: icon
Item {
id: root
property alias name: icon.text
property alias size: icon.font.pixelSize
property alias color: icon.color
@@ -11,46 +11,60 @@ StyledText {
property real fill: filled ? 1.0 : 0.0
property int grade: Theme.isLightMode ? 0 : -25
property int weight: filled ? 500 : 400
implicitWidth: icon.implicitWidth
implicitHeight: icon.implicitHeight
signal rotationCompleted()
font.family: "Material Symbols Rounded"
font.pixelSize: Theme.fontSizeMedium
font.weight: weight
color: Theme.surfaceText
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
renderType: Text.NativeRendering
antialiasing: true
font.variableAxes: {
"FILL": fill.toFixed(1),
"GRAD": grade,
"opsz": 24,
"wght": weight
FontLoader {
id: materialSymbolsFont
source: Qt.resolvedUrl("../assets/fonts/material-design-icons/variablefont/MaterialSymbolsRounded[FILL,GRAD,opsz,wght].ttf")
}
StyledText {
id: icon
anchors.fill: parent
font.family: materialSymbolsFont.name
font.pixelSize: Theme.fontSizeMedium
font.weight: root.weight
color: Theme.surfaceText
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
renderType: Text.NativeRendering
antialiasing: true
font.variableAxes: {
"FILL": root.fill.toFixed(1),
"GRAD": root.grade,
"opsz": 24,
"wght": root.weight
}
Behavior on font.weight {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
Behavior on fill {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on weight {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Timer {
id: rotationTimer
interval: 16
repeat: false
onTriggered: icon.rotationCompleted()
onTriggered: root.rotationCompleted()
}
onRotationChanged: {
rotationTimer.restart()
}
}
}

View File

@@ -5,15 +5,22 @@ import qs.Services
Text {
property bool isMonospace: false
FontLoader {
id: interFont
source: Qt.resolvedUrl("../assets/fonts/inter/InterVariable.ttf")
}
FontLoader {
id: firaCodeFont
source: Qt.resolvedUrl("../assets/fonts/nerd-fonts/FiraCodeNerdFont-Regular.ttf")
}
readonly property string resolvedFontFamily: {
const requestedFont = isMonospace ? SettingsData.monoFontFamily : SettingsData.fontFamily
const defaultFont = isMonospace ? SettingsData.defaultMonoFontFamily : SettingsData.defaultFontFamily
if (requestedFont === defaultFont) {
const availableFonts = Qt.fontFamilies()
if (!availableFonts.includes(requestedFont)) {
return isMonospace ? "Monospace" : "DejaVu Sans"
}
return isMonospace ? firaCodeFont.name : interFont.name
}
return requestedFont
}

Binary file not shown.

View File

@@ -0,0 +1,92 @@
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION AND CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,164 @@
# Inter
Inter is a typeface carefully crafted & designed for computer screens.
Inter features a tall x-height to aid in readability of mixed-case and lower-case text.
Inter is a [variable font](https://rsms.me/inter/#variable) with
several [OpenType features](https://rsms.me/inter/#features), like contextual alternates that adjusts punctuation depending on the shape of surrounding glyphs, slashed zero for when you need to disambiguate "0" from "o", tabular numbers, etc.
[**Download Inter font files…**](https://github.com/rsms/inter/releases/latest)
<br>
[![Sample](misc/readme/intro.png)](https://rsms.me/inter/)
### Quick questions
- **Where can I get Inter?** [Here](https://github.com/rsms/inter/releases/latest)
- **I think I found a bug. How can I let you know?** [Open an issue here](https://github.com/rsms/inter/issues/new?template=bug_report.md)
- **I have a question. Where can I get help?** [Post in Discussions Q&A](https://github.com/rsms/inter/discussions/categories/q-a)
- **Should I use Inter from Google Fonts?** No, unless you have no other choice.
(outdated, no italics)
- **Can I legally use Inter for my purpose?** Most likely _yes!_ Inter is free and open source.
([Read the license](LICENSE.txt) for details.)
## Using & installing Inter
[**Download the latest font files…**](https://github.com/rsms/inter/releases/latest)
Using Inter on a web page:
```html
<link rel="preconnect" href="https://your-font-file-host/">
<link rel="stylesheet" href="https://your-font-file-host/inter.css">
```
```css
:root { font-family: 'Inter', sans-serif; }
@supports (font-variation-settings: normal) {
:root { font-family: 'Inter var', sans-serif; }
}
```
For web pages, there's an official [CDN distribution](https://rsms.me/inter/inter.css) that you can use directly without having to host the font files yourself:
```html
<link rel="preconnect" href="https://rsms.me/">
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
```
### Alternate distributions
- [NPM `inter-ui`](https://www.npmjs.com/package/inter-ui)
- [Homebrew `font-inter`](https://github.com/Homebrew/homebrew-cask-fonts)
- [Ubuntu `fonts-inter`](https://packages.ubuntu.com/search?keywords=fonts-inter)
- [List of Inter available on various Linux distributions…](https://repology.org/project/fonts:inter/versions)
- [Google Fonts](https://fonts.google.com/specimen/Inter)
**Disclaimer:** Alternate distributions may not always be up-to-date.
## Notable uses of Inter
- [ElementaryOS](https://elementary.io/)
- [Figma](https://figma.com/)
- [GitLab](https://gitlab.com/)
- [Guggenheim museums](https://www.pentagram.com/work/guggenheim-3) ([case study](https://www.pentagram.com/work/guggenheim-3/))
- [ISO (International Organization for Standardization)](https://www.iso.org/) ([case study](https://www.motherbird.com.au/projects/iso/))
- [Minimalissimo magazine](https://minimalissimo.com/)
- [Mozilla](https://mozilla.design/firefox/typography/)
- [NASA](https://www.nasa.gov/specials/artemis-ii/)
- [Pixar Presto](https://en.wikipedia.org/wiki/Presto_(animation_software))
- [Switzerland, Canton of Zurich](https://www.zh.ch/)
- [Unity](https://unity.com/)
- [Zurich Airport](https://flughafen-zuerich.ch/)
> **Have you made something nice with Inter?**<br>
> [Please share in Show & Tell! →](https://github.com/rsms/inter/discussions/categories/show-and-tell)
### Notable forks
- [Open Runde](https://github.com/lauridskern/open-runde) is a rounded variant of Inter
- [Interalia](https://github.com/Shavian-info/interalia) extends Inter with Shavian characters
- [Raveo](https://github.com/jakubfoglar/raveo) is a "warmer version" of Inter
## Supporters & contributors
A wholehearted **Thank You** to everyone who supports the Inter project!
Special thanks to
[@thundernixon](https://github.com/thundernixon) and
[@KatjaSchimmel](https://github.com/KatjaSchimmel)
who have put in significant effort into making Inter what it is through
their contributions ♡
See [graphs/contributors](https://github.com/rsms/inter/graphs/contributors)
for a complete list of all contributors.
## Contributing to this project
For instructions on how to work with the source files and how to
[compile & build font files](CONTRIBUTING.md#compiling-font-files),
refer to [**CONTRIBUTING.md**](CONTRIBUTING.md).
Inter is licensed under the [SIL Open Font License](LICENSE.txt)
## Creating derivative fonts
Inter is open source which means you can make your own versions with your own changes.
However when doing so, please [**read LICENSE.txt carefully.**](LICENSE.txt) It is a standard **SIL Open Font License 1.1**:
> The goals of the Open Font License (OFL) are to stimulate worldwide
> development of collaborative font projects, to support the font creation
> efforts of academic and linguistic communities, and to provide a free and
> open framework in which fonts may be shared and improved in partnership
> with others.
>
> The OFL allows the licensed fonts to be used, studied, modified and
> redistributed freely as long as they are not sold by themselves. The
> fonts, including any derivative works, can be bundled, embedded,
> redistributed and/or sold with any software provided that any reserved
> names are not used by derivative works. The fonts and derivatives,
> however, cannot be released under any other type of license. The
> requirement for fonts to remain under this license does not apply
> to any document created using the fonts or their derivatives.
While you are allowed to use Inter commercially, i.e. bundled with product or service which makes you money, you are NOT allowed to sell Inter itself or derivatives of Inter. If you would like to do so, please [reach out](https://github.com/rsms) and we can talk about it.
Inter a trademark of Rasmus Andersson (DBA: RSMS)
"Inter" is a Reserved Font Name by Rasmus Andersson
([font vendor code RSMS.](https://learn.microsoft.com/en-us/typography/vendors/#r))
## Design
_This section discusses some of the design choices made for Inter._
Inter can be classified as a geometric neo-grotesque, similar in style to Roboto, Apple San Francisco, Akkurat, Asap, Lucida Grande and more. Some trade-offs were made in order to make this typeface work really well at small sizes:
- Early versions of Inter was not suitable for very large sizes because of some small-scale glyph optimizations (like "pits" and "traps") that help rasterization at small sizes but stand out and interfere at large sizes. However today Inter works well at large sizes and a [Display subfamily](https://github.com/rsms/inter/releases/tag/display-beta-1) is in the works for really large "display" sizes.
- Rasterized at sizes below 12px, some stems—like the horizontal center of "E", "F", or vertical center of "m"—are drawn with two semi-opaque pixels instead of one solid. This is because we "prioritize" (optimize for) higher-density rasterizations. If we move these stems to an off-center position—so that they can be drawn sharply at e.g. 11px—text will be less legible at higher resolutions.
Inter is a [variable font](https://rsms.me/inter/#variable) and is in addition also distributed as a set of traditional distinct font files in the following styles:
| Roman (upright) name | Italic name | Weight
| -------------------- | -------------------- | ------------
| Thin | Thin Italic | 100
| Extra Light | Extra Light Italic | 200
| Light | Light Italic | 300
| Regular | Italic | 400
| Medium | Medium Italic | 500
| Semi Bold | Semi Bold Italic | 600
| Bold | Bold Italic | 700
| Extra Bold | Extra Bold Italic | 800
| Black | Black Italic | 900

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,141 @@
## Material Symbols / Material Icons
These are two different official icon sets from Google, using the same underlying designs. Material Symbols is the current set, introduced in April 2022, built on variable font technology. Material Icons is the classic set, but no longer updated. More details below.
The icons can be browsed in a more user-friendly way at https://fonts.google.com/icons. Use the popdown menu near top left to choose between the two sets; Material Symbols is the default.
The icons are designed under the [material design guidelines](https://material.io/guidelines/).
## Icon Requests
Wed love to support your icon needs! Please submit your request here on GitHub as an issue.
Please note that Google Fonts does not accept user submissions of finished icon designs! There are fairly strict guidelines for Material icons, plus Google has upstream source files from which this repo is generated. Therefore, Google does not accept pull requests for icon files (whether new icon suggestions, or fixes for existing icons). Concepts are appreciated—just dont design SVGs and submit them via pull request.
However, users are perfectly welcome to point at outside files or images as examples—for the kind of thing they want, but they wont just be taken “as is.” This works especially well if you have multiple examples for a single icon, to help us understand the “essence” of the idea.
> For example, there is a fairly universal conceptual logo/icon for “agender,” so if you were proposing Google add an agender icon in the Material style, either mentioning that, or pointing at https://www.google.com/search?q=agender+icon would be a helpful tip.
### Third-party logos
Currently, Google does not include 3rd-party logos among the Material Symbols or Material Icons due to legal reasons. Some 3rd-party logos that were included in the past have since been removed.
## npm Packages
Google does not currently maintain the npm package for this repo, past v3 (2016). However, user @marella is hosting the following. He tells us these are automatically updated and published using GitHub Actions. Note: Google does **not** monitor or vet these packages.
### [material-symbols](https://github.com/marella/material-symbols/tree/main/material-symbols#readme) [![npm](https://img.shields.io/npm/v/material-symbols)](https://www.npmjs.com/package/material-symbols) [![install size](https://packagephobia.com/badge?p=material-symbols)](https://packagephobia.com/result?p=material-symbols)
- Only WOFF2 variable fonts and CSS for Material Symbols
- Includes outlined, rounded, and sharp icons and all variations of fill, weight, grade, and optical size
- Supports Sass
### [material-icons](https://github.com/marella/material-icons#readme) [![npm](https://img.shields.io/npm/v/material-icons)](https://www.npmjs.com/package/material-icons) [![install size](https://packagephobia.com/badge?p=material-icons)](https://packagephobia.com/result?p=material-icons) [![Downloads](https://img.shields.io/npm/dm/material-icons)](https://www.npmjs.com/package/material-icons)
- Only WOFF2, WOFF fonts and CSS
- Includes outlined, round, sharp and two-tone icons
- Supports Sass
### [@material-design-icons/font](https://github.com/marella/material-design-icons/tree/main/font#readme) [![npm (scoped)](https://img.shields.io/npm/v/@material-design-icons/font)](https://www.npmjs.com/package/@material-design-icons/font) [![install size](https://packagephobia.com/badge?p=@material-design-icons/font)](https://packagephobia.com/result?p=@material-design-icons/font)
- Only WOFF2 fonts and CSS
- Lighter version of `material-icons` package
- Doesn't support [older browsers](https://caniuse.com/woff2) such as Internet Explorer because of dropping WOFF (v1)
### [@material-design-icons/svg](https://github.com/marella/material-design-icons/tree/main/svg#readme) [![npm (scoped)](https://img.shields.io/npm/v/@material-design-icons/svg)](https://www.npmjs.com/package/@material-design-icons/svg) [![install size](https://packagephobia.com/badge?p=@material-design-icons/svg)](https://packagephobia.com/result?p=@material-design-icons/svg)
- Only SVGs
- Optimizes SVGs using SVGO
## Material Symbols
These newer icons can be browsed in a more user-friendly way at https://fonts.google.com/icons. Use the popdown menu near top left to choose between the two sets; Material Symbols is the default.
These icons were built/designed as variable fonts first (based on the 24 px designs from Material Icons). There are three separate Material Symbols variable fonts, which also have static icons available (but those do not have all the variations available, as that would be hundreds of styles):
- Outlined
- Rounded
- Sharp
- Note that although there is no separate Filled font, the Fill axis allows access to filled styles, in all three fonts. It can also be manipulated for an animated fill effect, to indicate user selection.
Each of the fonts has these design axes, which can be varied in CSS, or in many more modern design apps:
- Optical Size (opsz) from 20 to 48 px. The default is 24.
- Weight from 100 (Thin) to 700 (Bold). Regular is 400.
- Grade from -50 to 200. The default is 0 (zero). -50 is suggested for reversed contrast (e.g. white icons on black background)
- Fill from 0 to 100. The default is 0 (zero).
The following directories in this repo contain specifically Material Symbols (not Material Icons) content:
- symbols
- variablefont
What is currently _not_ available in Material Symbols?
- only the 20 and 24 px versions are designed with perfect pixel-grid alignment
- the only pre-made fonts are the variable fonts
- there are no two-tone icons
## Material Icons
The icons can be browsed in a more user-friendly way at https://fonts.google.com/icons?icon.set=Material+Icons
These classic icons are available in five distinct styles:
- Outlined
- Filled (the font version is just called Material Icons, as this is the oldest style)
- Rounded
- Sharp
- Two tone
The following directories in this repo contain specifically Material Icons (not Material Symbols) content:
- android
- font
- ios
- png
- src
What is currently _not_ available in Material Icons?
- variable fonts
- weights other than Regular
- grades other than Regular
- a means to animate Fill transitions
- new icons (since updates were halted in 2022)
## Material Icons update history
### 4.0.0 Update
* 2020 Aug 31
* Restructured repository, updated assets.
### 3.0.1 Update
* 2016 Sep 01
* Changed license in package.json.
* Added missing device symbol sprites.
### 3.0.0 Update
* 2016 Aug 25
* License change to Apache 2.0!
### 2.0
* 2016 May 28
## Getting Started
Read the [developer guide](https://developers.google.com/fonts/docs/material_icons) on how to use the material design icons in your project.
### Using a font
The `font` and `variablefont` folders contain pre-generated font files that can be included in a project. This is especially convenient for the web; however, it is generally better to link to the web font hosted on Google Fonts:
```html
<link href="https://fonts.googleapis.com/css2?family=Material+Icons"
rel="stylesheet">
```
```html
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined"
rel="stylesheet">
```
Read more on [Material Symbols](https://developers.google.com/fonts/docs/material_symbols/) or [Material Icons](https://developers.google.com/fonts/docs/material_icons/) in the Google Fonts developer guide.
## License
We have made these icons available for you to incorporate into your products under the [Apache License Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt). Feel free to remix and re-share these icons and documentation in your products.
We'd love attribution in your app's *about* screen, but it's not required.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,93 @@
Copyright (c) 2014, The Fira Code Project Authors (https://github.com/tonsky/FiraCode)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,48 @@
# Nerd Fonts
This is an archived font from the Nerd Fonts release v3.4.0.
For more information see:
* https://github.com/ryanoasis/nerd-fonts/
* https://github.com/ryanoasis/nerd-fonts/releases/latest/
# Fira Code
**Fira Code** is a free monospaced font with programming ligatures.
For more information have a look at the upstream website: https://github.com/tonsky/FiraCode
Version: 6.2
## Which font?
### TL;DR
* Pick your font family:
* If you are limited to monospaced fonts (because of your terminal, etc) then pick a font with `Nerd Font Mono` (or `NFM`).
* If you want to have bigger icons (usually around 1.5 normal letters wide) pick a font without `Mono` i.e. `Nerd Font` (or `NF`). Most terminals support this, but ymmv.
* If you work in a proportional context (GUI elements or edit a presentation etc) pick a font with `Nerd Font Propo` (or `NFP`).
### Ligatures
Ligatures are generally preserved in the patched fonts.
Nerd Fonts `v2.0.0` had no ligatures in the `Nerd Font Mono` fonts, this has been dropped with `v2.1.0`.
If you have a ligature-aware terminal and don't want ligatures you can (usually) disable them in the terminal settings.
### Explanation
Once you narrow down your font choice of family (`Droid Sans`, `Inconsolata`, etc) and style (`bold`, `italic`, etc) you have 2 main choices:
#### `Option 1: Download already patched font`
* For a stable version download a font package from the [release page](https://github.com/ryanoasis/nerd-fonts/releases)
* Or download the development version from the folders here
#### `Option 2: Patch your own font`
* Patch your own variations with the various options provided by the font patcher (i.e. not include all symbols for smaller font size)
For more information see: [The FAQ](https://github.com/ryanoasis/nerd-fonts/wiki/FAQ-and-Troubleshooting#which-font)
[SIL-RFN]:http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web_fonts_and_RFNs#14cbfd4a

View File

@@ -19,7 +19,6 @@ BuildRequires: rpkg
Requires: greetd
Requires: (quickshell-git or quickshell)
Requires: material-symbols-fonts
Requires(post): /usr/sbin/useradd
Requires(post): /usr/sbin/groupadd
@@ -46,9 +45,9 @@ authentication, and dynamic theming.
# QML-based application
%install
# Install greeter files to XDG config location
install -dm755 %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter
cp -r * %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter/
# Install greeter files to shared data location
install -dm755 %{buildroot}%{_datadir}/quickshell/dms-greeter
cp -r * %{buildroot}%{_datadir}/quickshell/dms-greeter/
# Install launcher script
install -Dm755 Modules/Greetd/assets/dms-greeter %{buildroot}%{_bindir}/dms-greeter
@@ -123,7 +122,7 @@ echo "Creating symlinks to sync theme..."
declare -A links=(
["$HOME/.config/DankMaterialShell/settings.json"]="$CACHE_DIR/settings.json"
["$HOME/.local/state/DankMaterialShell/session.json"]="$CACHE_DIR/session.json"
["$HOME/.cache/quickshell/dankshell/dms-colors.json"]="$CACHE_DIR/colors.json"
["$HOME/.cache/DankMaterialShell/dms-colors.json"]="$CACHE_DIR/colors.json"
)
for source in "${!links[@]}"; do
@@ -160,19 +159,28 @@ install -dm755 %{buildroot}%{_sharedstatedir}/greeter
# Instead, we verify/fix it in %post if needed
# Remove build and development files
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter/.git*
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter/.gitignore
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter/.github
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter/*.spec
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter/dms.spec
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms-greeter/dms-greeter.spec
rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms-greeter/.gitignore
rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/.github
rm -f %{buildroot}%{_datadir}/quickshell/dms-greeter/*.spec
rm -f %{buildroot}%{_datadir}/quickshell/dms-greeter/dms.spec
rm -f %{buildroot}%{_datadir}/quickshell/dms-greeter/dms-greeter.spec
%posttrans
# Clean up old installation path from previous versions (only if empty)
if [ -d "%{_sysconfdir}/xdg/quickshell/dms-greeter" ]; then
# Remove directories only if empty (preserves any user-added files)
rmdir "%{_sysconfdir}/xdg/quickshell/dms-greeter" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
fi
%files
%license LICENSE
%doc %{_docdir}/dms-greeter/README.md
%{_bindir}/dms-greeter
%{_bindir}/dms-greeter-sync
%{_sysconfdir}/xdg/quickshell/dms-greeter/
%{_datadir}/quickshell/dms-greeter/
%dir %attr(0750,greeter,greeter) %{_localstatedir}/cache/dms-greeter
%dir %attr(0755,greeter,greeter) %{_sharedstatedir}/greeter
@@ -200,9 +208,9 @@ if [ -x /usr/sbin/semanage ] && [ -x /usr/sbin/restorecon ]; then
semanage fcontext -a -t cache_home_t '%{_localstatedir}/cache/dms-greeter(/.*)?' >/dev/null 2>&1 || true
restorecon -R %{_localstatedir}/cache/dms-greeter >/dev/null 2>&1 || true
# Config directory
semanage fcontext -a -t etc_t '%{_sysconfdir}/xdg/quickshell/dms-greeter(/.*)?' >/dev/null 2>&1 || true
restorecon -R %{_sysconfdir}/xdg/quickshell/dms-greeter >/dev/null 2>&1 || true
# Shared data directory
semanage fcontext -a -t usr_t '%{_datadir}/quickshell/dms-greeter(/.*)?' >/dev/null 2>&1 || true
restorecon -R %{_datadir}/quickshell/dms-greeter >/dev/null 2>&1 || true
# PAM configuration
restorecon %{_sysconfdir}/pam.d/greetd >/dev/null 2>&1 || true
@@ -300,17 +308,19 @@ fi
# Only show banner on initial install
if [ "$1" -eq 1 ]; then
cat << EOF
cat << 'EOF'
===============================================================================
DMS Greeter Installation Complete!
===============================================================================
=========================================================================
DMS Greeter Installation Complete!
=========================================================================
Status:
- Greeter user: Created
- Greeter directories: /var/cache/dms-greeter, /var/lib/greeter
- SELinux contexts: Applied
- Greetd config: $CONFIG_STATUS
Greeter user: Created
Greeter directories: /var/cache/dms-greeter, /var/lib/greeter
SELinux contexts: Applied
EOF
echo " Greetd config: $CONFIG_STATUS"
cat << 'EOF'
Next steps:
@@ -327,7 +337,7 @@ Next steps:
Ready to test? Reboot or run: sudo systemctl start greetd
Documentation: /usr/share/doc/dms-greeter/README.md
===============================================================================
=========================================================================
EOF
fi
@@ -338,7 +348,7 @@ if [ "$1" -eq 0 ] && [ -x /usr/sbin/semanage ]; then
semanage fcontext -d '%{_bindir}/dms-greeter' 2>/dev/null || true
semanage fcontext -d '%{_sharedstatedir}/greeter(/.*)?' 2>/dev/null || true
semanage fcontext -d '%{_localstatedir}/cache/dms-greeter(/.*)?' 2>/dev/null || true
semanage fcontext -d '%{_sysconfdir}/xdg/quickshell/dms-greeter(/.*)?' 2>/dev/null || true
semanage fcontext -d '%{_datadir}/quickshell/dms-greeter(/.*)?' 2>/dev/null || true
fi
%changelog

View File

@@ -27,11 +27,9 @@ BuildRequires: wget
# Core requirements
Requires: (quickshell-git or quickshell)
Requires: accountsservice
Requires: dms-cli
Requires: dgop
Requires: fira-code-fonts
Requires: material-symbols-fonts
Requires: rsms-inter-fonts
# Core utilities (Highly recommended for DMS functionality)
Recommends: brightnessctl
@@ -128,20 +126,61 @@ install -Dm755 %{_builddir}/danklinux-master/bin/${DMS_BINARY} %{buildroot}%{_bi
# Install dgop binary
install -Dm755 %{_builddir}/dgop %{buildroot}%{_bindir}/dgop
# Install shell files to XDG config location
install -dm755 %{buildroot}%{_sysconfdir}/xdg/quickshell/dms
cp -r * %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/
# Install shell files to shared data location
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
cp -r * %{buildroot}%{_datadir}/quickshell/dms/
# Remove build files
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.git*
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.github
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/*.spec
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
rm -f %{buildroot}%{_datadir}/quickshell/dms/*.spec
%posttrans
# Clean up old installation path from previous versions (only if empty)
if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
# Remove directories only if empty (preserves any user-added files)
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
fi
# Restart DMS for active users after upgrade
if [ "$1" -ge 2 ]; then
# Find all quickshell DMS processes (PID and username)
while read pid cmd; do
username=$(ps -o user= -p "$pid" 2>/dev/null)
[ "$username" = "root" ] && continue
[ -z "$username" ] && continue
# Get user's UID and validate session
user_uid=$(id -u "$username" 2>/dev/null)
[ -z "$user_uid" ] && continue
[ ! -d "/run/user/$user_uid" ] && continue
wayland_display=$(tr '\0' '\n' < /proc/$pid/environ 2>/dev/null | grep '^WAYLAND_DISPLAY=' | cut -d= -f2)
[ -z "$wayland_display" ] && continue
echo "Restarting DMS for user: $username"
# Run as user with full Wayland session environment
runuser -u "$username" -- /bin/sh -c "
export XDG_RUNTIME_DIR=/run/user/$user_uid
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$user_uid/bus
export WAYLAND_DISPLAY=$wayland_display
export PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:\$PATH
dms restart >/dev/null 2>&1
" 2>/dev/null || true
break
done < <(pgrep -a -f 'quickshell.*dms' 2>/dev/null)
fi
%files
%license LICENSE
%doc README.md CONTRIBUTING.md
%{_sysconfdir}/xdg/quickshell/dms/
%{_datadir}/quickshell/dms/
%files -n dms-cli
%{_bindir}/dms

View File

@@ -407,6 +407,21 @@ dms ipc call bar hide
dms ipc call bar status
```
## Target: `systemupdater`
System updater external check request.
### Functions
**`updatestatus`**
- Trigger a system update check
- Returns: Success confirmation
### Examples
```bash
dms ipc call systemupdater updatestatus
```
## Modal Controls
These targets control various modal windows and overlays.

View File

@@ -90,9 +90,15 @@ in {
})
(lib.mkIf cfg.niri.enableSpawn {
spawn-at-startup = [
{command = ["dms" "run"];}
];
spawn-at-startup =
[
{command = ["dms" "run"];}
]
++ lib.optionals cfg.enableClipboard [
{
command = ["wl-paste" "--watch" "cliphist" "store"];
}
];
})
];
};

View File

@@ -38,7 +38,7 @@
{
"term": "24-hour format",
"context": "24-hour format",
"reference": "Modules/Settings/PersonalizationTab.qml:887",
"reference": "Modules/Settings/PersonalizationTab.qml:939",
"comment": ""
},
{
@@ -152,13 +152,13 @@
{
"term": "Animation Speed",
"context": "Animation Speed",
"reference": "Modules/Settings/PersonalizationTab.qml:1047",
"reference": "Modules/Settings/PersonalizationTab.qml:1160",
"comment": ""
},
{
"term": "Anonymous Identity (optional)",
"context": "Anonymous Identity (optional)",
"reference": "Modals/WifiPasswordModal.qml:365",
"reference": "Modals/WifiPasswordModal.qml:389",
"comment": ""
},
{
@@ -332,7 +332,7 @@
{
"term": "Automatic Cycling",
"context": "Automatic Cycling",
"reference": "Modules/Settings/PersonalizationTab.qml:640",
"reference": "Modules/Settings/PersonalizationTab.qml:692",
"comment": ""
},
{
@@ -344,7 +344,7 @@
{
"term": "Automatically cycle through wallpapers in the same folder",
"context": "Automatically cycle through wallpapers in the same folder",
"reference": "Modules/Settings/PersonalizationTab.qml:647",
"reference": "Modules/Settings/PersonalizationTab.qml:699",
"comment": ""
},
{
@@ -362,7 +362,7 @@
{
"term": "Automatically extract colors from wallpaper",
"context": "Automatically extract colors from wallpaper",
"reference": "Modules/Settings/PersonalizationTab.qml:1251",
"reference": "Modules/Settings/PersonalizationTab.qml:1364",
"comment": ""
},
{
@@ -404,7 +404,7 @@
{
"term": "Back",
"context": "Back",
"reference": "Modules/DankBar/Widgets/SystemTrayBar.qml:479",
"reference": "Modules/DankBar/Widgets/SystemTrayBar.qml:477",
"comment": ""
},
{
@@ -449,6 +449,24 @@
"reference": "Modules/ControlCenter/Details/BluetoothDetail.qml:69",
"comment": ""
},
{
"term": "Blur Layer",
"context": "Blur Layer",
"reference": "Modules/Settings/PersonalizationTab.qml:1047",
"comment": ""
},
{
"term": "Blur on Overview",
"context": "Blur on Overview",
"reference": "Modules/Settings/PersonalizationTab.qml:489",
"comment": ""
},
{
"term": "Blur wallpaper when niri overview is open",
"context": "Blur wallpaper when niri overview is open",
"reference": "Modules/Settings/PersonalizationTab.qml:496",
"comment": ""
},
{
"term": "Border",
"context": "Border",
@@ -542,7 +560,7 @@
{
"term": "Cancel",
"context": "Cancel",
"reference": "Modals/BluetoothPairingModal.qml:249, Modals/DankColorPickerModal.qml:510, Modals/WifiPasswordModal.qml:468, Modules/Settings/PluginsTab.qml:1296, Modals/FileBrowser/FileBrowserModal.qml:952",
"reference": "Modals/BluetoothPairingModal.qml:249, Modals/DankColorPickerModal.qml:510, Modals/WifiPasswordModal.qml:492, Modules/Settings/PluginsTab.qml:1296, Modals/FileBrowser/FileBrowserModal.qml:952",
"comment": ""
},
{
@@ -638,7 +656,7 @@
{
"term": "Close",
"context": "Close",
"reference": "Modals/NetworkInfoModal.qml:129, Modals/NetworkWiredInfoModal.qml:129, Modules/SystemUpdatePopout.qml:333, Modules/DankBar/Widgets/RunningApps.qml:780",
"reference": "Modals/NetworkInfoModal.qml:129, Modals/NetworkWiredInfoModal.qml:129, Modules/SystemUpdatePopout.qml:333, Modules/DankBar/Widgets/RunningApps.qml:788",
"comment": ""
},
{
@@ -716,7 +734,7 @@
{
"term": "Configuration activated",
"context": "Configuration activated",
"reference": "Services/NetworkManagerService.qml:308",
"reference": "Services/DMSNetworkService.qml:337",
"comment": ""
},
{
@@ -746,13 +764,19 @@
{
"term": "Connect",
"context": "Connect",
"reference": "Modals/WifiPasswordModal.qml:505",
"reference": "Modals/WifiPasswordModal.qml:534",
"comment": ""
},
{
"term": "Connect to VPN",
"context": "Connect to VPN",
"reference": "Modals/WifiPasswordModal.qml:182",
"comment": ""
},
{
"term": "Connect to Wi-Fi",
"context": "Connect to Wi-Fi",
"reference": "Modals/WifiPasswordModal.qml:166",
"reference": "Modals/WifiPasswordModal.qml:184",
"comment": ""
},
{
@@ -761,12 +785,6 @@
"reference": "Modules/Settings/DisplaysTab.qml:501",
"comment": ""
},
{
"term": "Connection failed. Check password and try again.",
"context": "Connection failed. Check password and try again.",
"reference": "Services/NetworkManagerService.qml:275",
"comment": ""
},
{
"term": "Contrast",
"context": "Contrast",
@@ -896,7 +914,7 @@
{
"term": "DEMO MODE - Click anywhere to exit",
"context": "DEMO MODE - Click anywhere to exit",
"reference": "Modules/Lock/LockScreenContent.qml:654",
"reference": "Modules/Lock/LockScreenContent.qml:749",
"comment": ""
},
{
@@ -920,7 +938,7 @@
{
"term": "Daily at:",
"context": "Daily at:",
"reference": "Modules/Settings/PersonalizationTab.qml:812",
"reference": "Modules/Settings/PersonalizationTab.qml:864",
"comment": ""
},
{
@@ -1010,7 +1028,7 @@
{
"term": "Disconnected from WiFi",
"context": "Disconnected from WiFi",
"reference": "Services/NetworkManagerService.qml:385",
"reference": "Services/DMSNetworkService.qml:412",
"comment": ""
},
{
@@ -1106,7 +1124,7 @@
{
"term": "Domain (optional)",
"context": "Domain (optional)",
"reference": "Modals/WifiPasswordModal.qml:397",
"reference": "Modals/WifiPasswordModal.qml:421",
"comment": ""
},
{
@@ -1124,13 +1142,13 @@
{
"term": "Duration",
"context": "Duration",
"reference": "Modules/Settings/PersonalizationTab.qml:1100",
"reference": "Modules/Settings/PersonalizationTab.qml:1213",
"comment": ""
},
{
"term": "Dynamic Theming",
"context": "Dynamic Theming",
"reference": "Modules/Settings/PersonalizationTab.qml:1244",
"reference": "Modules/Settings/PersonalizationTab.qml:1357",
"comment": ""
},
{
@@ -1160,7 +1178,7 @@
{
"term": "Enable System Sounds",
"context": "Enable System Sounds",
"reference": "Modules/Settings/PersonalizationTab.qml:1398",
"reference": "Modules/Settings/PersonalizationTab.qml:1511",
"comment": ""
},
{
@@ -1172,7 +1190,13 @@
{
"term": "Enable WiFi",
"context": "Enable WiFi",
"reference": "Modules/ControlCenter/Details/NetworkDetail.qml:173",
"reference": "Modules/ControlCenter/Details/NetworkDetail.qml:177",
"comment": ""
},
{
"term": "Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.",
"context": "Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.",
"reference": "Modules/Settings/PersonalizationTab.qml:1054",
"comment": ""
},
{
@@ -1214,7 +1238,7 @@
{
"term": "Enter credentials for ",
"context": "Enter credentials for ",
"reference": "Modals/WifiPasswordModal.qml:178",
"reference": "Modals/WifiPasswordModal.qml:200",
"comment": ""
},
{
@@ -1244,7 +1268,7 @@
{
"term": "Enter password for ",
"context": "Enter password for ",
"reference": "Modals/WifiPasswordModal.qml:178",
"reference": "Modals/WifiPasswordModal.qml:198, Modals/WifiPasswordModal.qml:200",
"comment": ""
},
{
@@ -1256,7 +1280,7 @@
{
"term": "Execute templates from ~/.config/matugen/config.toml",
"context": "Execute templates from ~/.config/matugen/config.toml",
"reference": "Modules/Settings/PersonalizationTab.qml:1335",
"reference": "Modules/Settings/PersonalizationTab.qml:1448",
"comment": ""
},
{
@@ -1268,25 +1292,43 @@
{
"term": "Failed to activate configuration",
"context": "Failed to activate configuration",
"reference": "Services/NetworkManagerService.qml:304",
"reference": "Services/DMSNetworkService.qml:333",
"comment": ""
},
{
"term": "Failed to connect VPN",
"context": "Failed to connect VPN",
"reference": "Services/DMSNetworkService.qml:743",
"comment": ""
},
{
"term": "Failed to connect to ",
"context": "Failed to connect to ",
"reference": "Services/NetworkManagerService.qml:277",
"reference": "Services/DMSNetworkService.qml:306",
"comment": ""
},
{
"term": "Failed to disconnect VPN",
"context": "Failed to disconnect VPN",
"reference": "Services/DMSNetworkService.qml:767",
"comment": ""
},
{
"term": "Failed to disconnect VPNs",
"context": "Failed to disconnect VPNs",
"reference": "Services/DMSNetworkService.qml:787",
"comment": ""
},
{
"term": "Failed to disconnect WiFi",
"context": "Failed to disconnect WiFi",
"reference": "Services/NetworkManagerService.qml:383",
"reference": "Services/DMSNetworkService.qml:410",
"comment": ""
},
{
"term": "Failed to enable WiFi",
"context": "Failed to enable WiFi",
"reference": "Services/NetworkManagerService.qml:484",
"reference": "Services/DMSNetworkService.qml:511",
"comment": ""
},
{
@@ -1310,7 +1352,7 @@
{
"term": "Failed to start connection to ",
"context": "Failed to start connection to ",
"reference": "Services/NetworkManagerService.qml:371",
"reference": "Services/DMSNetworkService.qml:400",
"comment": ""
},
{
@@ -1394,13 +1436,13 @@
{
"term": "Forget Network",
"context": "Forget Network",
"reference": "Modules/ControlCenter/Details/NetworkDetail.qml:606",
"reference": "Modules/ControlCenter/Details/NetworkDetail.qml:610",
"comment": ""
},
{
"term": "Forgot network ",
"context": "Forgot network ",
"reference": "Services/NetworkManagerService.qml:441",
"reference": "Services/DMSNetworkService.qml:468",
"comment": ""
},
{
@@ -1544,7 +1586,7 @@
{
"term": "How often to change wallpaper",
"context": "How often to change wallpaper",
"reference": "Modules/Settings/PersonalizationTab.qml:758",
"reference": "Modules/Settings/PersonalizationTab.qml:810",
"comment": ""
},
{
@@ -1598,13 +1640,13 @@
{
"term": "Include Transitions",
"context": "Include Transitions",
"reference": "Modules/Settings/PersonalizationTab.qml:923",
"reference": "Modules/Settings/PersonalizationTab.qml:975",
"comment": ""
},
{
"term": "Incorrect password",
"context": "Incorrect password",
"reference": "Modals/WifiPasswordModal.qml:189",
"reference": "Modals/WifiPasswordModal.qml:211",
"comment": ""
},
{
@@ -1640,7 +1682,7 @@
{
"term": "Interval",
"context": "Interval",
"reference": "Modules/Settings/PersonalizationTab.qml:757",
"reference": "Modules/Settings/PersonalizationTab.qml:809",
"comment": ""
},
{
@@ -1742,7 +1784,7 @@
{
"term": "Light Mode",
"context": "Light Mode",
"reference": "Modules/Settings/PersonalizationTab.qml:991",
"reference": "Modules/Settings/PersonalizationTab.qml:1104",
"comment": ""
},
{
@@ -1844,13 +1886,13 @@
{
"term": "Matugen Palette",
"context": "Matugen Palette",
"reference": "Modules/Settings/PersonalizationTab.qml:1276, Modules/Settings/ThemeColorsTab.qml:629",
"reference": "Modules/Settings/PersonalizationTab.qml:1389, Modules/Settings/ThemeColorsTab.qml:629",
"comment": ""
},
{
"term": "Matugen Settings",
"context": "Matugen Settings",
"reference": "Modules/Settings/PersonalizationTab.qml:1219",
"reference": "Modules/Settings/PersonalizationTab.qml:1332",
"comment": ""
},
{
@@ -1916,19 +1958,19 @@
{
"term": "Mode:",
"context": "Mode:",
"reference": "Modules/Settings/PersonalizationTab.qml:689",
"reference": "Modules/Settings/PersonalizationTab.qml:741",
"comment": ""
},
{
"term": "Monitor",
"context": "Monitor",
"reference": "Modules/Settings/PersonalizationTab.qml:592",
"reference": "Modules/Settings/PersonalizationTab.qml:644",
"comment": ""
},
{
"term": "Monitor Selection:",
"context": "Monitor Selection:",
"reference": "Modules/Settings/PersonalizationTab.qml:583",
"reference": "Modules/Settings/PersonalizationTab.qml:635",
"comment": ""
},
{
@@ -1982,7 +2024,7 @@
{
"term": "Network Info",
"context": "Network Info",
"reference": "Modules/ControlCenter/Details/NetworkDetail.qml:340, Modules/ControlCenter/Details/NetworkDetail.qml:583",
"reference": "Modules/ControlCenter/Details/NetworkDetail.qml:344, Modules/ControlCenter/Details/NetworkDetail.qml:587",
"comment": ""
},
{
@@ -1994,7 +2036,7 @@
{
"term": "Network Settings",
"context": "Network Settings",
"reference": "Modules/ControlCenter/Details/NetworkDetail.qml:64",
"reference": "Modules/ControlCenter/Details/NetworkDetail.qml:68",
"comment": ""
},
{
@@ -2018,7 +2060,7 @@
{
"term": "New Notification",
"context": "New Notification",
"reference": "Modules/Settings/PersonalizationTab.qml:1512",
"reference": "Modules/Settings/PersonalizationTab.qml:1625",
"comment": ""
},
{
@@ -2108,7 +2150,7 @@
{
"term": "Notepad",
"context": "Notepad",
"reference": "DMSShell.qml:413, Modules/Settings/DankBarTab.qml:175",
"reference": "DMSShell.qml:421, Modules/Settings/DankBarTab.qml:175",
"comment": ""
},
{
@@ -2270,19 +2312,19 @@
{
"term": "Password",
"context": "Password",
"reference": "Modals/WifiPasswordModal.qml:274",
"reference": "Modals/WifiPasswordModal.qml:296",
"comment": ""
},
{
"term": "Per-Mode Wallpapers",
"context": "Per-Mode Wallpapers",
"reference": "Modules/Settings/PersonalizationTab.qml:494",
"reference": "Modules/Settings/PersonalizationTab.qml:546",
"comment": ""
},
{
"term": "Per-Monitor Wallpapers",
"context": "Per-Monitor Wallpapers",
"reference": "Modules/Settings/PersonalizationTab.qml:551",
"reference": "Modules/Settings/PersonalizationTab.qml:603",
"comment": ""
},
{
@@ -2330,31 +2372,31 @@
{
"term": "Play sound when new notification arrives",
"context": "Play sound when new notification arrives",
"reference": "Modules/Settings/PersonalizationTab.qml:1518",
"reference": "Modules/Settings/PersonalizationTab.qml:1631",
"comment": ""
},
{
"term": "Play sound when power cable is connected",
"context": "Play sound when power cable is connected",
"reference": "Modules/Settings/PersonalizationTab.qml:1587",
"reference": "Modules/Settings/PersonalizationTab.qml:1700",
"comment": ""
},
{
"term": "Play sound when volume is adjusted",
"context": "Play sound when volume is adjusted",
"reference": "Modules/Settings/PersonalizationTab.qml:1552",
"reference": "Modules/Settings/PersonalizationTab.qml:1665",
"comment": ""
},
{
"term": "Play sounds for system events",
"context": "Play sounds for system events",
"reference": "Modules/Settings/PersonalizationTab.qml:1405",
"reference": "Modules/Settings/PersonalizationTab.qml:1518",
"comment": ""
},
{
"term": "Plugged In",
"context": "Plugged In",
"reference": "Modules/Settings/PersonalizationTab.qml:1581",
"reference": "Modules/Settings/PersonalizationTab.qml:1694",
"comment": ""
},
{
@@ -2576,7 +2618,7 @@
{
"term": "Run User Templates",
"context": "Run User Templates",
"reference": "Modules/Settings/PersonalizationTab.qml:1328",
"reference": "Modules/Settings/PersonalizationTab.qml:1441",
"comment": ""
},
{
@@ -2678,7 +2720,7 @@
{
"term": "Select a preset or drag the slider to customize",
"context": "Select a preset or drag the slider to customize",
"reference": "Modules/Settings/PersonalizationTab.qml:1181",
"reference": "Modules/Settings/PersonalizationTab.qml:1294",
"comment": ""
},
{
@@ -2702,7 +2744,7 @@
{
"term": "Select monitor to configure wallpaper",
"context": "Select monitor to configure wallpaper",
"reference": "Modules/Settings/PersonalizationTab.qml:593",
"reference": "Modules/Settings/PersonalizationTab.qml:645",
"comment": ""
},
{
@@ -2720,19 +2762,19 @@
{
"term": "Select system sound theme",
"context": "Select system sound theme",
"reference": "Modules/Settings/PersonalizationTab.qml:1476",
"reference": "Modules/Settings/PersonalizationTab.qml:1589",
"comment": ""
},
{
"term": "Select the palette algorithm used for wallpaper-based colors",
"context": "Select the palette algorithm used for wallpaper-based colors",
"reference": "Modules/Settings/PersonalizationTab.qml:1277, Modules/Settings/ThemeColorsTab.qml:630",
"reference": "Modules/Settings/PersonalizationTab.qml:1390, Modules/Settings/ThemeColorsTab.qml:630",
"comment": ""
},
{
"term": "Select which transitions to include in randomization",
"context": "Select which transitions to include in randomization",
"reference": "Modules/Settings/PersonalizationTab.qml:930",
"reference": "Modules/Settings/PersonalizationTab.qml:982",
"comment": ""
},
{
@@ -2750,13 +2792,13 @@
{
"term": "Set different wallpapers for each connected monitor",
"context": "Set different wallpapers for each connected monitor",
"reference": "Modules/Settings/PersonalizationTab.qml:558",
"reference": "Modules/Settings/PersonalizationTab.qml:610",
"comment": ""
},
{
"term": "Set different wallpapers for light and dark mode",
"context": "Set different wallpapers for light and dark mode",
"reference": "Modules/Settings/PersonalizationTab.qml:501",
"reference": "Modules/Settings/PersonalizationTab.qml:553",
"comment": ""
},
{
@@ -2840,7 +2882,7 @@
{
"term": "Show password",
"context": "Show password",
"reference": "Modals/WifiPasswordModal.qml:440",
"reference": "Modals/WifiPasswordModal.qml:464",
"comment": ""
},
{
@@ -2906,7 +2948,7 @@
{
"term": "Sound Theme",
"context": "Sound Theme",
"reference": "Modules/Settings/PersonalizationTab.qml:1475",
"reference": "Modules/Settings/PersonalizationTab.qml:1588",
"comment": ""
},
{
@@ -2984,7 +3026,7 @@
{
"term": "Switch User",
"context": "Switch User",
"reference": "Modules/Greetd/GreeterContent.qml:549",
"reference": "Modules/Greetd/GreeterContent.qml:645",
"comment": ""
},
{
@@ -3218,7 +3260,7 @@
{
"term": "Transition Effect",
"context": "Transition Effect",
"reference": "Modules/Settings/PersonalizationTab.qml:904",
"reference": "Modules/Settings/PersonalizationTab.qml:956",
"comment": ""
},
{
@@ -3314,7 +3356,7 @@
{
"term": "Use System Theme",
"context": "Use System Theme",
"reference": "Modules/Settings/PersonalizationTab.qml:1447",
"reference": "Modules/Settings/PersonalizationTab.qml:1560",
"comment": ""
},
{
@@ -3338,13 +3380,13 @@
{
"term": "Use light theme instead of dark theme",
"context": "Use light theme instead of dark theme",
"reference": "Modules/Settings/PersonalizationTab.qml:998",
"reference": "Modules/Settings/PersonalizationTab.qml:1111",
"comment": ""
},
{
"term": "Use sound theme from system settings",
"context": "Use sound theme from system settings",
"reference": "Modules/Settings/PersonalizationTab.qml:1453",
"reference": "Modules/Settings/PersonalizationTab.qml:1566",
"comment": ""
},
{
@@ -3362,7 +3404,7 @@
{
"term": "Username",
"context": "Username",
"reference": "Modals/WifiPasswordModal.qml:237",
"reference": "Modals/WifiPasswordModal.qml:259",
"comment": ""
},
{
@@ -3416,13 +3458,13 @@
{
"term": "Visual effect used when wallpaper changes",
"context": "Visual effect used when wallpaper changes",
"reference": "Modules/Settings/PersonalizationTab.qml:905",
"reference": "Modules/Settings/PersonalizationTab.qml:957",
"comment": ""
},
{
"term": "Volume Changed",
"context": "Volume Changed",
"reference": "Modules/Settings/PersonalizationTab.qml:1546",
"reference": "Modules/Settings/PersonalizationTab.qml:1659",
"comment": ""
},
{
@@ -3476,19 +3518,19 @@
{
"term": "WiFi disabled",
"context": "WiFi disabled",
"reference": "Services/NetworkManagerService.qml:474",
"reference": "Services/DMSNetworkService.qml:501",
"comment": ""
},
{
"term": "WiFi enabled",
"context": "WiFi enabled",
"reference": "Services/NetworkManagerService.qml:474, Services/NetworkManagerService.qml:486",
"reference": "Services/DMSNetworkService.qml:501, Services/DMSNetworkService.qml:513",
"comment": ""
},
{
"term": "WiFi is off",
"context": "WiFi is off",
"reference": "Modules/ControlCenter/Details/NetworkDetail.qml:155",
"reference": "Modules/ControlCenter/Details/NetworkDetail.qml:159",
"comment": ""
},
{
@@ -3590,7 +3632,7 @@
{
"term": "matugen not detected - dynamic theming unavailable",
"context": "matugen not detected - dynamic theming unavailable",
"reference": "Modules/Settings/PersonalizationTab.qml:1355",
"reference": "Modules/Settings/PersonalizationTab.qml:1468",
"comment": ""
},
{

View File

@@ -227,6 +227,15 @@
"Bluetooth Settings": {
"Bluetooth Settings": "Bluetooth設定"
},
"Blur Layer": {
"Blur Layer": "ぼかしレイヤー"
},
"Blur on Overview": {
"Blur on Overview": "概要でぼかす"
},
"Blur wallpaper when niri overview is open": {
"Blur wallpaper when niri overview is open": "Niri概要が開いているときに壁紙をぼかす"
},
"Border": {
"Border": "ボーダー"
},
@@ -380,6 +389,9 @@
"Connect": {
"Connect": "接続"
},
"Connect to VPN": {
"Connect to VPN": ""
},
"Connect to Wi-Fi": {
"Connect to Wi-Fi": "Wi-Fiに接続"
},
@@ -564,7 +576,7 @@
"Domain (optional)": "ドメイン (オプション)"
},
"Donate on Ko-fi": {
"Donate on Ko-fi": ""
"Donate on Ko-fi": "Ko-fiで寄付"
},
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "ウィジェットをドラッグしてセクション内で順序を変更できます。目のアイコンでウィジェットを表示/非表示にスペースは維持、Xで完全に削除できます。"
@@ -596,6 +608,9 @@
"Enable WiFi": {
"Enable WiFi": "WiFiを有効にする"
},
"Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.": {
"Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.": "コンポジターをターゲットに設定できるぼかしレイヤー(名前空間dms:blurwallpaper)を有効にします。Niri を手動で設定する必要があります。"
},
"Enable fingerprint authentication": {
"Enable fingerprint authentication": "指紋認証を有効に"
},
@@ -644,9 +659,18 @@
"Failed to activate configuration": {
"Failed to activate configuration": "設定が適用できませんでした"
},
"Failed to connect VPN": {
"Failed to connect VPN": ""
},
"Failed to connect to ": {
"Failed to connect to ": "接続ができませんでした "
},
"Failed to disconnect VPN": {
"Failed to disconnect VPN": ""
},
"Failed to disconnect VPNs": {
"Failed to disconnect VPNs": ""
},
"Failed to disconnect WiFi": {
"Failed to disconnect WiFi": "WiFiの切断ができませんでした"
},
@@ -654,7 +678,7 @@
"Failed to enable WiFi": "WiFiを有効化にできませんでした"
},
"Failed to remove device": {
"Failed to remove device": ""
"Failed to remove device": "デバイスの削除に失敗しました"
},
"Failed to set profile image": {
"Failed to set profile image": "プロフィール画像の設定に失敗しました"
@@ -1209,7 +1233,7 @@
"Plugins": "プラグイン"
},
"Plugins:": {
"Plugins:": ""
"Plugins:": "プラグイン:"
},
"Popup Position": {
"Popup Position": "ポップアップの位置"
@@ -1299,7 +1323,7 @@
"Reset": "リセット"
},
"Resources": {
"Resources": ""
"Resources": "リソース"
},
"Right": {
"Right": "右"
@@ -1497,7 +1521,7 @@
"Storage & Disks": "ストレージとディスク"
},
"Support Development": {
"Support Development": ""
"Support Development": "開発をサポート"
},
"Surface": {
"Surface": "表面"
@@ -1758,7 +1782,7 @@
"Weather Widget": "天気ウィジェット"
},
"Website:": {
"Website:": ""
"Website:": "ウェブサイト:"
},
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": {
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "有効にすると、アプリはアルファベット順に並べ替えられます。無効にすると、アプリは使用頻度で並べ替えられます。"

View File

@@ -227,6 +227,15 @@
"Bluetooth Settings": {
"Bluetooth Settings": "Configurações do Bluetooth"
},
"Blur Layer": {
"Blur Layer": ""
},
"Blur on Overview": {
"Blur on Overview": ""
},
"Blur wallpaper when niri overview is open": {
"Blur wallpaper when niri overview is open": ""
},
"Border": {
"Border": "Borda"
},
@@ -380,6 +389,9 @@
"Connect": {
"Connect": "Conectar"
},
"Connect to VPN": {
"Connect to VPN": ""
},
"Connect to Wi-Fi": {
"Connect to Wi-Fi": "Conectar ao Wi-Fi"
},
@@ -596,6 +608,9 @@
"Enable WiFi": {
"Enable WiFi": "Ativar Wi-fi "
},
"Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.": {
"Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.": ""
},
"Enable fingerprint authentication": {
"Enable fingerprint authentication": "Habilitar autenticação por impressão digital"
},
@@ -644,9 +659,18 @@
"Failed to activate configuration": {
"Failed to activate configuration": ""
},
"Failed to connect VPN": {
"Failed to connect VPN": ""
},
"Failed to connect to ": {
"Failed to connect to ": ""
},
"Failed to disconnect VPN": {
"Failed to disconnect VPN": ""
},
"Failed to disconnect VPNs": {
"Failed to disconnect VPNs": ""
},
"Failed to disconnect WiFi": {
"Failed to disconnect WiFi": ""
},

File diff suppressed because it is too large Load Diff

View File

@@ -227,6 +227,15 @@
"Bluetooth Settings": {
"Bluetooth Settings": "蓝牙设置"
},
"Blur Layer": {
"Blur Layer": "模糊层"
},
"Blur on Overview": {
"Blur on Overview": "在概览中模糊"
},
"Blur wallpaper when niri overview is open": {
"Blur wallpaper when niri overview is open": "当打开概览时模糊壁纸"
},
"Border": {
"Border": "边框"
},
@@ -380,6 +389,9 @@
"Connect": {
"Connect": "连接"
},
"Connect to VPN": {
"Connect to VPN": "连接到 VPN"
},
"Connect to Wi-Fi": {
"Connect to Wi-Fi": "连接到Wi-Fi"
},
@@ -564,7 +576,7 @@
"Domain (optional)": "域(可选)"
},
"Donate on Ko-fi": {
"Donate on Ko-fi": ""
"Donate on Ko-fi": "在Ko-fi上赞助"
},
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "拖动组件以在各区块内重新排序。点击眼睛图标可隐藏/显示组件(保留间距),点击 X 可彻底移除。"
@@ -596,6 +608,9 @@
"Enable WiFi": {
"Enable WiFi": "启用 Wi-Fi"
},
"Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.": {
"Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.": "启用合成器可定位的模糊层命名空间dms:blurwallpaper需要手动配置 niri。"
},
"Enable fingerprint authentication": {
"Enable fingerprint authentication": "启用指纹认证"
},
@@ -644,9 +659,18 @@
"Failed to activate configuration": {
"Failed to activate configuration": "无法应用配置"
},
"Failed to connect VPN": {
"Failed to connect VPN": "连接 VPN 失败"
},
"Failed to connect to ": {
"Failed to connect to ": "无法连接至 "
},
"Failed to disconnect VPN": {
"Failed to disconnect VPN": "断开 VPN 失败"
},
"Failed to disconnect VPNs": {
"Failed to disconnect VPNs": "断开 VPNs 失败"
},
"Failed to disconnect WiFi": {
"Failed to disconnect WiFi": "无法断开 Wi-Fi 连接"
},
@@ -654,7 +678,7 @@
"Failed to enable WiFi": "无法启用 Wi-Fi"
},
"Failed to remove device": {
"Failed to remove device": ""
"Failed to remove device": "删除设备失败"
},
"Failed to set profile image": {
"Failed to set profile image": "无法设置个人资料图片"
@@ -1209,7 +1233,7 @@
"Plugins": "插件"
},
"Plugins:": {
"Plugins:": ""
"Plugins:": "插件:"
},
"Popup Position": {
"Popup Position": "弹出位置"
@@ -1299,7 +1323,7 @@
"Reset": "重置"
},
"Resources": {
"Resources": ""
"Resources": "资源"
},
"Right": {
"Right": "右侧"
@@ -1497,7 +1521,7 @@
"Storage & Disks": "存储与磁盘"
},
"Support Development": {
"Support Development": ""
"Support Development": "支持开发"
},
"Surface": {
"Surface": "表面色"
@@ -1758,7 +1782,7 @@
"Weather Widget": "天气小组件"
},
"Website:": {
"Website:": ""
"Website:": "网站:"
},
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": {
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "启用后,应用按字母顺序排序;禁用则按使用频率排序"

View File

@@ -524,6 +524,27 @@
"reference": "",
"comment": ""
},
{
"term": "Blur Layer",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Blur on Overview",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Blur wallpaper when niri overview is open",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Border",
"translation": "",
@@ -874,6 +895,13 @@
"reference": "",
"comment": ""
},
{
"term": "Connect to VPN",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Connect to Wi-Fi",
"translation": "",
@@ -888,13 +916,6 @@
"reference": "",
"comment": ""
},
{
"term": "Connection failed. Check password and try again.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Contrast",
"translation": "",
@@ -1371,6 +1392,13 @@
"reference": "",
"comment": ""
},
{
"term": "Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Enable fingerprint authentication",
"translation": "",
@@ -1483,6 +1511,13 @@
"reference": "",
"comment": ""
},
{
"term": "Failed to connect VPN",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Failed to connect to ",
"translation": "",
@@ -1490,6 +1525,20 @@
"reference": "",
"comment": ""
},
{
"term": "Failed to disconnect VPN",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Failed to disconnect VPNs",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Failed to disconnect WiFi",
"translation": "",