mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 05:25:41 -05:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04fdfa2a35 | ||
|
|
8f3085290d | ||
|
|
0839fe45f5 | ||
|
|
18f4795fda | ||
|
|
55d9fa622a | ||
|
|
7dc723c764 | ||
|
|
5a63205972 | ||
|
|
a4ceeafb1e | ||
|
|
242e05cc0e | ||
|
|
065dddbe6e | ||
|
|
fa6825252b | ||
|
|
b06e48a444 | ||
|
|
97dbd40f07 | ||
|
|
bc23109f99 | ||
|
|
ecb9675e9c | ||
|
|
e1f9b9e7a4 | ||
|
|
067b485bb3 | ||
|
|
67a4e3074e | ||
|
|
010bc4e8c3 | ||
|
|
9de5e3253e | ||
|
|
e32622ac48 | ||
|
|
5e2371c2cb | ||
|
|
a6ce26ee87 | ||
|
|
2a72c126f1 | ||
|
|
36e1a5d379 | ||
|
|
c12eafa1db | ||
|
|
9e26d8755c | ||
|
|
90bd30e351 |
109
Common/I18n.qml
109
Common/I18n.qml
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import Qt.labs.folderlistmodel
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
@@ -7,60 +8,106 @@ pragma ComponentBehavior: Bound
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property string currentLocale: Qt.locale().name.substring(0, 2)
|
||||
property var translations: ({})
|
||||
property bool translationsLoaded: false
|
||||
readonly property string _rawLocale: Qt.locale().name
|
||||
readonly property string _lang: _rawLocale.split(/[_-]/)[0]
|
||||
readonly property var _candidates: {
|
||||
const fullUnderscore = _rawLocale;
|
||||
const fullHyphen = _rawLocale.replace("_", "-");
|
||||
return [fullUnderscore, fullHyphen, _lang].filter(c => c && c !== "en");
|
||||
}
|
||||
|
||||
|
||||
readonly property url translationsFolder: Qt.resolvedUrl("../translations/poexports")
|
||||
|
||||
property string currentLocale: "en"
|
||||
property var translations: ({})
|
||||
property bool translationsLoaded: false
|
||||
|
||||
property url _selectedPath: ""
|
||||
|
||||
FolderListModel {
|
||||
id: dir
|
||||
folder: root.translationsFolder
|
||||
nameFilters: ["*.json"]
|
||||
showDirs: false
|
||||
showDotAndDotDot: false
|
||||
|
||||
onStatusChanged: if (status === FolderListModel.Ready) root._pickTranslation()
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: translationLoader
|
||||
path: root.currentLocale === "en" ? "" : Qt.resolvedUrl(`../translations/${root.currentLocale}.json`)
|
||||
path: root._selectedPath
|
||||
|
||||
onLoaded: {
|
||||
try {
|
||||
root.translations = JSON.parse(text())
|
||||
root.translationsLoaded = true
|
||||
console.log(`I18n: Loaded translations for locale '${root.currentLocale}' (${Object.keys(root.translations).length} contexts)`)
|
||||
console.log(`I18n: Loaded translations for '${root.currentLocale}' ` +
|
||||
`(${Object.keys(root.translations).length} contexts)`)
|
||||
} catch (e) {
|
||||
console.warn(`I18n: Error parsing translations for locale '${root.currentLocale}':`, e, "- falling back to English")
|
||||
root.translationsLoaded = false
|
||||
console.warn(`I18n: Error parsing '${root.currentLocale}':`, e,
|
||||
"- falling back to English")
|
||||
root._fallbackToEnglish()
|
||||
}
|
||||
}
|
||||
|
||||
onLoadFailed: (error) => {
|
||||
console.warn(`I18n: Failed to load translations for locale '${root.currentLocale}' (${error}), falling back to English`)
|
||||
root.translationsLoaded = false
|
||||
console.warn(`I18n: Failed to load '${root.currentLocale}' (${error}), ` +
|
||||
"falling back to English")
|
||||
root._fallbackToEnglish()
|
||||
}
|
||||
}
|
||||
|
||||
function tr(term, context) {
|
||||
if (!translationsLoaded || !translations) {
|
||||
return term
|
||||
}
|
||||
|
||||
const actualContext = context || term
|
||||
|
||||
if (translations[actualContext] && translations[actualContext][term]) {
|
||||
return translations[actualContext][term]
|
||||
}
|
||||
|
||||
for (const ctx in translations) {
|
||||
if (translations[ctx][term]) {
|
||||
return translations[ctx][term]
|
||||
function _pickTranslation() {
|
||||
const present = new Set()
|
||||
for (let i = 0; i < dir.count; i++) {
|
||||
const name = dir.get(i, "fileName") // e.g. "zh_CN.json"
|
||||
if (name && name.endsWith(".json")) {
|
||||
present.add(name.slice(0, -5))
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < _candidates.length; i++) {
|
||||
const cand = _candidates[i]
|
||||
if (present.has(cand)) {
|
||||
_useLocale(cand, dir.folder + "/" + cand + ".json")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_fallbackToEnglish()
|
||||
}
|
||||
|
||||
function _useLocale(localeTag, fileUrl) {
|
||||
currentLocale = localeTag
|
||||
_selectedPath = fileUrl
|
||||
translationsLoaded = false
|
||||
translations = ({})
|
||||
console.log(`I18n: Using locale '${localeTag}' from ${fileUrl}`)
|
||||
}
|
||||
|
||||
function _fallbackToEnglish() {
|
||||
currentLocale = "en"
|
||||
_selectedPath = ""
|
||||
translationsLoaded = false
|
||||
translations = ({})
|
||||
console.warn("I18n: Falling back to built-in English strings")
|
||||
}
|
||||
|
||||
function tr(term, context) {
|
||||
if (!translationsLoaded || !translations) return term
|
||||
const ctx = context || term
|
||||
if (translations[ctx] && translations[ctx][term]) return translations[ctx][term]
|
||||
for (const c in translations) {
|
||||
if (translations[c] && translations[c][term]) return translations[c][term]
|
||||
}
|
||||
return term
|
||||
}
|
||||
|
||||
function trContext(context, term) {
|
||||
if (!translationsLoaded || !translations) {
|
||||
return term
|
||||
}
|
||||
|
||||
if (translations[context] && translations[context][term]) {
|
||||
return translations[context][term]
|
||||
}
|
||||
|
||||
if (!translationsLoaded || !translations) return term
|
||||
if (translations[context] && translations[context][term]) return translations[context][term]
|
||||
return term
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@ Singleton {
|
||||
return stringify(path).replace("file://", "")
|
||||
}
|
||||
|
||||
function toFileUrl(path: string): string {
|
||||
return path.startsWith("file://") ? path : "file://" + path
|
||||
}
|
||||
|
||||
function mkdir(path: url): void {
|
||||
Quickshell.execDetached(["mkdir", "-p", strip(path)])
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ Singleton {
|
||||
property int batteryHibernateTimeout: 0 // Never
|
||||
|
||||
property bool lockBeforeSuspend: false
|
||||
property bool loginctlLockIntegration: true
|
||||
property var recentColors: []
|
||||
property bool showThirdPartyPlugins: false
|
||||
|
||||
@@ -152,6 +153,7 @@ Singleton {
|
||||
batterySuspendTimeout = settings.batterySuspendTimeout !== undefined ? settings.batterySuspendTimeout : 0
|
||||
batteryHibernateTimeout = settings.batteryHibernateTimeout !== undefined ? settings.batteryHibernateTimeout : 0
|
||||
lockBeforeSuspend = settings.lockBeforeSuspend !== undefined ? settings.lockBeforeSuspend : false
|
||||
loginctlLockIntegration = settings.loginctlLockIntegration !== undefined ? settings.loginctlLockIntegration : true
|
||||
recentColors = settings.recentColors !== undefined ? settings.recentColors : []
|
||||
showThirdPartyPlugins = settings.showThirdPartyPlugins !== undefined ? settings.showThirdPartyPlugins : false
|
||||
|
||||
@@ -215,6 +217,7 @@ Singleton {
|
||||
"batterySuspendTimeout": batterySuspendTimeout,
|
||||
"batteryHibernateTimeout": batteryHibernateTimeout,
|
||||
"lockBeforeSuspend": lockBeforeSuspend,
|
||||
"loginctlLockIntegration": loginctlLockIntegration,
|
||||
"recentColors": recentColors,
|
||||
"showThirdPartyPlugins": showThirdPartyPlugins
|
||||
}, null, 2))
|
||||
@@ -643,6 +646,11 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLoginctlLockIntegration(enabled) {
|
||||
loginctlLockIntegration = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setShowThirdPartyPlugins(enabled) {
|
||||
showThirdPartyPlugins = enabled
|
||||
saveSettings()
|
||||
|
||||
@@ -74,7 +74,6 @@ Singleton {
|
||||
property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false
|
||||
property var workerRunning: false
|
||||
property var matugenColors: ({})
|
||||
property int colorUpdateTrigger: 0
|
||||
property var customThemeData: null
|
||||
|
||||
readonly property string stateDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.CacheLocation).toString()) + "/dankshell"
|
||||
@@ -84,7 +83,6 @@ Singleton {
|
||||
matugenCheck.running = true
|
||||
if (typeof SessionData !== "undefined") {
|
||||
SessionData.isLightModeChanged.connect(root.onLightModeChanged)
|
||||
isLightMode = SessionData.isLightMode
|
||||
}
|
||||
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) {
|
||||
@@ -100,7 +98,6 @@ Singleton {
|
||||
}
|
||||
|
||||
function getMatugenColor(path, fallback) {
|
||||
colorUpdateTrigger
|
||||
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"
|
||||
let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode]
|
||||
for (const part of path.split(".")) {
|
||||
@@ -578,10 +575,6 @@ Singleton {
|
||||
|
||||
|
||||
function onLightModeChanged() {
|
||||
if (matugenColors && Object.keys(matugenColors).length > 0) {
|
||||
colorUpdateTrigger++
|
||||
}
|
||||
|
||||
if (currentTheme === "custom" && customThemeFileView.path) {
|
||||
customThemeFileView.reload()
|
||||
}
|
||||
@@ -905,7 +898,6 @@ Singleton {
|
||||
const colorsText = dynamicColorsFileView.text()
|
||||
if (colorsText) {
|
||||
root.matugenColors = JSON.parse(colorsText)
|
||||
root.colorUpdateTrigger++
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.clearWallpaperError()
|
||||
}
|
||||
|
||||
@@ -621,7 +621,6 @@ DankModal {
|
||||
required property bool fileIsDir
|
||||
required property string filePath
|
||||
required property string fileName
|
||||
required property url fileURL
|
||||
required property int index
|
||||
|
||||
width: weMode ? 245 : 140
|
||||
|
||||
@@ -219,11 +219,20 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Enable loginctl lock integration")
|
||||
description: "Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen."
|
||||
checked: SessionData.loginctlLockIntegration
|
||||
onToggled: checked => SessionData.setLoginctlLockIntegration(checked)
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Lock before suspend")
|
||||
description: "Automatically lock the screen when the system prepares to suspend"
|
||||
checked: SessionData.lockBeforeSuspend
|
||||
visible: SessionData.loginctlLockIntegration
|
||||
onToggled: checked => SessionData.setLockBeforeSuspend(checked)
|
||||
}
|
||||
|
||||
|
||||
@@ -491,7 +491,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color { ColorAnimation { duration: Anims.durShort } }
|
||||
Behavior on border.color { ColorAnimation { duration: Anims.durShort } }
|
||||
}
|
||||
}
|
||||
@@ -675,14 +674,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Anims.durShort
|
||||
@@ -858,14 +849,6 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1024,14 +1007,6 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +322,21 @@ Item {
|
||||
}
|
||||
}
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
if (appData && appData.appId) {
|
||||
if (appData && appData.type === "window") {
|
||||
const sortedToplevels = CompositorService.sortedToplevels
|
||||
for (var i = 0; i < sortedToplevels.length; i++) {
|
||||
const toplevel = sortedToplevels[i]
|
||||
const checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i
|
||||
if (checkId === appData.uniqueId) {
|
||||
toplevel.close()
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if (appData && appData.type === "grouped") {
|
||||
if (contextMenu) {
|
||||
contextMenu.showForButton(root, appData, 40, false, cachedDesktopEntry)
|
||||
}
|
||||
} else if (appData && appData.appId) {
|
||||
const desktopEntry = cachedDesktopEntry
|
||||
if (desktopEntry) {
|
||||
AppUsageHistoryData.addAppUsage({
|
||||
@@ -333,7 +347,7 @@ Item {
|
||||
"comment": desktopEntry.comment || ""
|
||||
})
|
||||
}
|
||||
SessionService.launchDesktopEntry(desktopEntry)
|
||||
SessionService.launchDesktopEntry(desktopEntry)
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
if (contextMenu && appData) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import Quickshell.Services.Mpris
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.Lock
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -322,6 +323,8 @@ Item {
|
||||
TextInput {
|
||||
id: inputField
|
||||
|
||||
property bool syncingFromState: false
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: lockIcon.width + Theme.spacingM * 2
|
||||
anchors.rightMargin: {
|
||||
@@ -329,6 +332,9 @@ Item {
|
||||
if (GreeterState.showPasswordInput && revealButton.visible) {
|
||||
margin += revealButton.width
|
||||
}
|
||||
if (virtualKeyboardButton.visible) {
|
||||
margin += virtualKeyboardButton.width
|
||||
}
|
||||
if (enterButton.visible) {
|
||||
margin += enterButton.width + 2
|
||||
}
|
||||
@@ -338,6 +344,7 @@ Item {
|
||||
focus: true
|
||||
echoMode: GreeterState.showPasswordInput ? (parent.showPassword ? TextInput.Normal : TextInput.Password) : TextInput.Normal
|
||||
onTextChanged: {
|
||||
if (syncingFromState) return
|
||||
if (GreeterState.showPasswordInput) {
|
||||
GreeterState.passwordBuffer = text
|
||||
} else {
|
||||
@@ -355,13 +362,17 @@ Item {
|
||||
GreeterState.showPasswordInput = true
|
||||
PortalService.getGreeterUserProfileImage(GreeterState.username)
|
||||
GreeterState.passwordBuffer = ""
|
||||
inputField.text = ""
|
||||
syncingFromState = true
|
||||
text = ""
|
||||
syncingFromState = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
syncingFromState = true
|
||||
text = GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput
|
||||
syncingFromState = false
|
||||
if (isPrimaryScreen)
|
||||
forceActiveFocus()
|
||||
}
|
||||
@@ -371,12 +382,18 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
KeyboardController {
|
||||
id: keyboard_controller
|
||||
target: inputField
|
||||
rootObject: root
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: placeholder
|
||||
|
||||
anchors.left: lockIcon.right
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (enterButton.visible ? enterButton.left : parent.right))
|
||||
anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)))
|
||||
anchors.rightMargin: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: {
|
||||
@@ -413,7 +430,7 @@ Item {
|
||||
StyledText {
|
||||
anchors.left: lockIcon.right
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (enterButton.visible ? enterButton.left : parent.right))
|
||||
anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)))
|
||||
anchors.rightMargin: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: {
|
||||
@@ -441,8 +458,8 @@ Item {
|
||||
DankActionButton {
|
||||
id: revealButton
|
||||
|
||||
anchors.right: enterButton.visible ? enterButton.left : parent.right
|
||||
anchors.rightMargin: enterButton.visible ? 0 : Theme.spacingS
|
||||
anchors.right: virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)
|
||||
anchors.rightMargin: 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: parent.showPassword ? "visibility_off" : "visibility"
|
||||
buttonSize: 32
|
||||
@@ -450,6 +467,24 @@ Item {
|
||||
enabled: visible
|
||||
onClicked: parent.showPassword = !parent.showPassword
|
||||
}
|
||||
DankActionButton {
|
||||
id: virtualKeyboardButton
|
||||
|
||||
anchors.right: enterButton.visible ? enterButton.left : parent.right
|
||||
anchors.rightMargin: enterButton.visible ? 0 : Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "keyboard"
|
||||
buttonSize: 32
|
||||
visible: Greetd.state === GreetdState.Inactive && !GreeterState.unlocking
|
||||
enabled: visible
|
||||
onClicked: {
|
||||
if (keyboard_controller.isKeyboardActive) {
|
||||
keyboard_controller.hide()
|
||||
} else {
|
||||
keyboard_controller.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: enterButton
|
||||
@@ -1256,7 +1291,7 @@ Item {
|
||||
if (sessionCmd) {
|
||||
GreetdMemory.setLastSessionId(GreeterState.sessionPaths[GreeterState.currentSessionIndex])
|
||||
GreetdMemory.setLastSuccessfulUser(GreeterState.username)
|
||||
Greetd.launch(sessionCmd.split(" "), ["XDG_SESSION_TYPE=wayland"], true)
|
||||
Greetd.launch(sessionCmd.split(" "), ["XDG_SESSION_TYPE=wayland"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,34 +18,43 @@ The easiest thing is to run `dms greeter install` or `dms` for interactive insta
|
||||
|
||||
### Manual
|
||||
|
||||
1. Install `greetd` (in most distro's standard repositories)
|
||||
2. Copy `assets/dms-niri.kdl` or `assets/dms-hypr.conf` to `/etc/greetd`
|
||||
- niri if you want to run the greeter under niri, hypr if you want to run the greeter under Hyprland
|
||||
3. Copy `assets/greet-niri.sh` or `assets/greet-hyprland.sh` to `/usr/local/bin/start-dms-greetd.sh`
|
||||
4. Edit `/etc/greetd/dms-niri.kdl` or `/etc/greetd/dms-hypr.conf` and replace `_DMS_PATH_` with the absolute path to dms, e.g. `/home/joecool/.config/quickshell/dms`
|
||||
5. Edit or create `/etc/greetd/config.toml`
|
||||
1. Install `greetd` (in most distro's standard repositories) and `quickshell`
|
||||
2. Clone the dms project to `/etc/xdg/quickshell/dms-greeter`
|
||||
```bash
|
||||
sudo git clone https://github.com/AvengeMedia/DankMaterialShell.git /etc/xdg/quickshell/dms-greeter
|
||||
```
|
||||
3. Copy `assets/dms-greeter` to `/usr/local/bin/dms-greeter`:
|
||||
```bash
|
||||
sudo cp assets/dms-greeter /usr/local/bin/dms-greeter
|
||||
sudo chmod +x /usr/local/bin/dms-greeter
|
||||
```
|
||||
4. Create greeter cache directory with proper permissions:
|
||||
```bash
|
||||
sudo mkdir -p /var/cache/dms-greeter
|
||||
sudo chown greeter:greeter /var/cache/dms-greeter
|
||||
sudo chmod 770 /var/cache/dms-greeter
|
||||
```
|
||||
6. Edit or create `/etc/greetd/config.toml`:
|
||||
```toml
|
||||
[terminal]
|
||||
# The VT to run the greeter on. Can be "next", "current" or a number
|
||||
# designating the VT.
|
||||
vt = 1
|
||||
|
||||
# The default session, also known as the greeter.
|
||||
[default_session]
|
||||
|
||||
# `agreety` is the bundled agetty/login-lookalike. You can replace `/bin/sh`
|
||||
# with whatever you want started, such as `sway`.
|
||||
|
||||
# The user to run the command as. The privileges this user must have depends
|
||||
# on the greeter. A graphical greeter may for example require the user to be
|
||||
# in the `video` group.
|
||||
user = "greeter"
|
||||
|
||||
command = "/usr/local/bin/start-dms-greetd.sh"
|
||||
# Change compositor to sway or hyprland if preferred
|
||||
command = "/usr/local/bin/dms-greeter --command niri"
|
||||
```
|
||||
|
||||
Enable the greeter with `sudo systemctl enable greetd`
|
||||
|
||||
#### Legacy installation (deprecated)
|
||||
|
||||
If you prefer the old method with separate shell scripts and config files:
|
||||
1. Copy `assets/dms-niri.kdl` or `assets/dms-hypr.conf` to `/etc/greetd`
|
||||
2. Copy `assets/greet-niri.sh` or `assets/greet-hyprland.sh` to `/usr/local/bin/start-dms-greetd.sh`
|
||||
3. Edit the config file and replace `_DMS_PATH_` with your DMS installation path
|
||||
4. Configure greetd to use `/usr/local/bin/start-dms-greetd.sh`
|
||||
|
||||
### NixOS
|
||||
|
||||
To install the greeter on NixOS add the repo to your flake inputs as described in the readme. Then somewhere in your NixOS config add this to imports:
|
||||
@@ -66,7 +75,30 @@ programs.dankMaterialShell.greeter = {
|
||||
|
||||
## Usage
|
||||
|
||||
To run dms in greeter mode you just need to set `DMS_RUN_GREETER=1` in the environment.
|
||||
### Using dms-greeter wrapper (recommended)
|
||||
|
||||
The `dms-greeter` wrapper simplifies running the greeter with any compositor:
|
||||
|
||||
```bash
|
||||
dms-greeter --command niri
|
||||
dms-greeter --command hyprland
|
||||
dms-greeter --command sway
|
||||
dms-greeter --command niri -C /path/to/custom-niri.kdl
|
||||
```
|
||||
|
||||
Configure greetd to use it in `/etc/greetd/config.toml`:
|
||||
```toml
|
||||
[terminal]
|
||||
vt = 1
|
||||
|
||||
[default_session]
|
||||
user = "greeter"
|
||||
command = "/usr/local/bin/dms-greeter --command niri"
|
||||
```
|
||||
|
||||
### Manual usage
|
||||
|
||||
To run dms in greeter mode you can also manually set environment variables:
|
||||
|
||||
```bash
|
||||
DMS_RUN_GREETER=1 qs -p /path/to/dms
|
||||
@@ -86,15 +118,17 @@ Wallpapers and themes and weather and clock formats and things are a TODO on the
|
||||
|
||||
You can synchronize those configurations with a specific user if you want greeter settings to always mirror the shell.
|
||||
|
||||
The greeter uses the `dms-greeter` group for file access permissions, so ensure your user and the greeter user are both members of this group.
|
||||
|
||||
```bash
|
||||
# For core settings (theme, clock formats, etc)
|
||||
sudo ln -sf ~/.config/DankMaterialShell/settings.json /etc/greetd/.dms/settings.json
|
||||
sudo ln -sf ~/.config/DankMaterialShell/settings.json /var/cache/dms-greeter/settings.json
|
||||
# For state (mainly you would configure wallpaper in this file)
|
||||
sudo ln -sf ~/.local/state/DankMaterialShell/session.json /etc/greetd/.dms/session.json
|
||||
sudo ln -sf ~/.local/state/DankMaterialShell/session.json /var/cache/dms-greeter/session.json
|
||||
# For wallpaper based theming
|
||||
sudo ln -sf ~/.cache/quickshell/dankshell/dms-colors.json /etc/greetd/.dms/dms-colors.json
|
||||
sudo ln -sf ~/.cache/quickshell/dankshell/dms-colors.json /var/cache/dms-greeter/dms-colors.json
|
||||
```
|
||||
|
||||
You can override the configuration path with the `DMS_GREET_CFG_DIR` environment variable, the default is `/etc/greetd/.dms`
|
||||
You can override the configuration path with the `DMS_GREET_CFG_DIR` environment variable or the `--cache-dir` flag when using `dms-greeter`. The default is `/var/cache/dms-greeter`.
|
||||
|
||||
It should be writable by the greeter user.
|
||||
The cache directory should be owned by `greeter:greeter` with `770` permissions.
|
||||
174
Modules/Greetd/assets/dms-greeter
Executable file
174
Modules/Greetd/assets/dms-greeter
Executable file
@@ -0,0 +1,174 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
COMPOSITOR=""
|
||||
COMPOSITOR_CONFIG=""
|
||||
DMS_PATH="dms-greeter"
|
||||
CACHE_DIR="/var/cache/dms-greeter"
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
dms-greeter - DankMaterialShell greeter launcher
|
||||
|
||||
Usage: dms-greeter --command COMPOSITOR [OPTIONS]
|
||||
|
||||
Required:
|
||||
--command COMPOSITOR Compositor to use (niri, hyprland, or sway)
|
||||
|
||||
Options:
|
||||
-C, --config PATH Custom compositor config file
|
||||
-p, --path PATH DMS path (config name or absolute path)
|
||||
(default: dms-greeter)
|
||||
--cache-dir PATH Cache directory for greeter data
|
||||
(default: /var/cache/dms-greeter)
|
||||
-h, --help Show this help message
|
||||
|
||||
Examples:
|
||||
dms-greeter --command niri
|
||||
dms-greeter --command hyprland -C /etc/greetd/custom-hypr.conf
|
||||
dms-greeter --command sway -p /home/user/.config/quickshell/custom-dms
|
||||
dms-greeter --command niri --cache-dir /tmp/dmsgreeter
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--command)
|
||||
COMPOSITOR="$2"
|
||||
shift 2
|
||||
;;
|
||||
-C|--config)
|
||||
COMPOSITOR_CONFIG="$2"
|
||||
shift 2
|
||||
;;
|
||||
-p|--path)
|
||||
DMS_PATH="$2"
|
||||
shift 2
|
||||
;;
|
||||
--cache-dir)
|
||||
CACHE_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$COMPOSITOR" ]]; then
|
||||
echo "Error: --command COMPOSITOR is required" >&2
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export XDG_SESSION_TYPE=wayland
|
||||
export QT_QPA_PLATFORM=wayland
|
||||
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
|
||||
export EGL_PLATFORM=gbm
|
||||
export DMS_RUN_GREETER=1
|
||||
export DMS_GREET_CFG_DIR="$CACHE_DIR"
|
||||
|
||||
mkdir -p "$CACHE_DIR"
|
||||
|
||||
QS_CMD="qs"
|
||||
if [[ "$DMS_PATH" == /* ]]; then
|
||||
QS_CMD="qs -p $DMS_PATH"
|
||||
else
|
||||
QS_CMD="qs -c $DMS_PATH"
|
||||
fi
|
||||
|
||||
case "$COMPOSITOR" in
|
||||
niri)
|
||||
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat > "$TEMP_CONFIG" << NIRI_EOF
|
||||
hotkey-overlay {
|
||||
skip-at-startup
|
||||
}
|
||||
|
||||
environment {
|
||||
DMS_RUN_GREETER "1"
|
||||
}
|
||||
|
||||
spawn-at-startup "sh" "-c" "$QS_CMD; niri msg action quit --skip-confirmation"
|
||||
|
||||
debug {
|
||||
keep-max-bpc-unchanged
|
||||
}
|
||||
|
||||
gestures {
|
||||
hot-corners {
|
||||
off
|
||||
}
|
||||
}
|
||||
|
||||
layout {
|
||||
background-color "#000000"
|
||||
}
|
||||
NIRI_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
else
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||
cat >> "$TEMP_CONFIG" << NIRI_EOF
|
||||
|
||||
spawn-at-startup "sh" "-c" "$QS_CMD; niri msg action quit --skip-confirmation"
|
||||
NIRI_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
fi
|
||||
exec niri -c "$COMPOSITOR_CONFIG"
|
||||
;;
|
||||
|
||||
hyprland)
|
||||
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat > "$TEMP_CONFIG" << HYPRLAND_EOF
|
||||
env = DMS_RUN_GREETER,1
|
||||
|
||||
exec = sh -c "$QS_CMD; hyprctl dispatch exit"
|
||||
HYPRLAND_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
else
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||
cat >> "$TEMP_CONFIG" << HYPRLAND_EOF
|
||||
|
||||
exec = sh -c "$QS_CMD; hyprctl dispatch exit"
|
||||
HYPRLAND_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
fi
|
||||
exec Hyprland -c "$COMPOSITOR_CONFIG"
|
||||
;;
|
||||
|
||||
sway)
|
||||
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat > "$TEMP_CONFIG" << SWAY_EOF
|
||||
exec "$QS_CMD; swaymsg exit"
|
||||
SWAY_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
else
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||
cat >> "$TEMP_CONFIG" << SWAY_EOF
|
||||
|
||||
exec "$QS_CMD; swaymsg exit"
|
||||
SWAY_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
fi
|
||||
exec sway -c "$COMPOSITOR_CONFIG"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Error: Unsupported compositor: $COMPOSITOR" >&2
|
||||
echo "Supported compositors: niri, hyprland, sway" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -8,81 +8,54 @@ import qs.Services
|
||||
Item {
|
||||
id: root
|
||||
|
||||
function activate() {
|
||||
loader.activeAsync = true
|
||||
}
|
||||
property string sharedPasswordBuffer: ""
|
||||
property bool shouldLock: false
|
||||
|
||||
Component.onCompleted: {
|
||||
if (SessionService.loginctlAvailable || SessionService.sessionPath) {
|
||||
if (SessionService.locked || SessionService.lockedHint) {
|
||||
console.log("Lock: Session locked on startup")
|
||||
loader.activeAsync = true
|
||||
}
|
||||
}
|
||||
IdleService.lockComponent = root
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: IdleService
|
||||
function onLockRequested() {
|
||||
console.log("Lock: Received lock request from IdleService")
|
||||
loader.activeAsync = true
|
||||
}
|
||||
function activate() {
|
||||
shouldLock = true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionService
|
||||
|
||||
function onSessionLocked() {
|
||||
console.log("Lock: Lock signal received -> show lock")
|
||||
loader.activeAsync = true
|
||||
shouldLock = true
|
||||
}
|
||||
|
||||
function onSessionUnlocked() {
|
||||
console.log("Lock: Unlock signal received -> hide lock")
|
||||
loader.active = false
|
||||
}
|
||||
|
||||
function onLoginctlStateChanged() {
|
||||
if (SessionService.lockedHint && !loader.active) {
|
||||
console.log("Lock: LockedHint=true -> show lock")
|
||||
loader.activeAsync = true
|
||||
} else if (!SessionService.locked && !SessionService.lockedHint && loader.active) {
|
||||
console.log("Lock: LockedHint=false -> hide lock")
|
||||
loader.active = false
|
||||
}
|
||||
}
|
||||
|
||||
function onPrepareForSleep() {
|
||||
if (SessionService.preparingForSleep && SessionData.lockBeforeSuspend) {
|
||||
console.log("Lock: PrepareForSleep -> lock before suspend")
|
||||
loader.activeAsync = true
|
||||
}
|
||||
shouldLock = false
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: loader
|
||||
Connections {
|
||||
target: IdleService
|
||||
|
||||
WlSessionLock {
|
||||
id: sessionLock
|
||||
function onLockRequested() {
|
||||
shouldLock = true
|
||||
}
|
||||
}
|
||||
|
||||
property bool unlocked: false
|
||||
property string sharedPasswordBuffer: ""
|
||||
WlSessionLock {
|
||||
id: sessionLock
|
||||
|
||||
locked: true
|
||||
locked: root.shouldLock
|
||||
|
||||
onLockedChanged: {
|
||||
if (!locked) {
|
||||
loader.active = false
|
||||
}
|
||||
}
|
||||
WlSessionLockSurface {
|
||||
color: "transparent"
|
||||
|
||||
LockSurface {
|
||||
id: lockSurface
|
||||
anchors.fill: parent
|
||||
lock: sessionLock
|
||||
sharedPasswordBuffer: sessionLock.sharedPasswordBuffer
|
||||
sharedPasswordBuffer: root.sharedPasswordBuffer
|
||||
onUnlockRequested: {
|
||||
root.shouldLock = false
|
||||
}
|
||||
onPasswordChanged: newPassword => {
|
||||
sessionLock.sharedPasswordBuffer = newPassword
|
||||
root.sharedPasswordBuffer = newPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,17 +69,15 @@ Item {
|
||||
target: "lock"
|
||||
|
||||
function lock() {
|
||||
console.log("Lock screen requested via IPC")
|
||||
loader.activeAsync = true
|
||||
shouldLock = true
|
||||
}
|
||||
|
||||
function demo() {
|
||||
console.log("Lock screen DEMO mode requested via IPC")
|
||||
demoWindow.showDemo()
|
||||
}
|
||||
|
||||
function isLocked(): bool {
|
||||
return SessionService.locked || loader.active
|
||||
return sessionLock.locked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,33 +4,26 @@ import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
|
||||
WlSessionLockSurface {
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
required property WlSessionLock lock
|
||||
required property string sharedPasswordBuffer
|
||||
|
||||
signal passwordChanged(string newPassword)
|
||||
|
||||
readonly property bool locked: lock && !lock.locked
|
||||
|
||||
function unlock(): void {
|
||||
lock.locked = false
|
||||
}
|
||||
signal unlockRequested()
|
||||
|
||||
color: "transparent"
|
||||
|
||||
Loader {
|
||||
LockScreenContent {
|
||||
anchors.fill: parent
|
||||
sourceComponent: LockScreenContent {
|
||||
demoMode: false
|
||||
passwordBuffer: root.sharedPasswordBuffer
|
||||
screenName: root.screen?.name ?? ""
|
||||
onUnlockRequested: root.unlock()
|
||||
onPasswordBufferChanged: {
|
||||
if (root.sharedPasswordBuffer !== passwordBuffer) {
|
||||
root.passwordChanged(passwordBuffer)
|
||||
}
|
||||
demoMode: false
|
||||
passwordBuffer: root.sharedPasswordBuffer
|
||||
screenName: ""
|
||||
onUnlockRequested: root.unlockRequested()
|
||||
onPasswordBufferChanged: {
|
||||
if (root.sharedPasswordBuffer !== passwordBuffer) {
|
||||
root.passwordChanged(passwordBuffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,14 @@ Item {
|
||||
implicitHeight: hasPermission ? settingsColumn.implicitHeight : errorText.implicitHeight
|
||||
height: implicitHeight
|
||||
|
||||
readonly property bool hasPermission: pluginService && pluginService.hasPermission ? pluginService.hasPermission(pluginId, "settings_write") : true
|
||||
readonly property bool hasPermission: {
|
||||
if (!pluginService || !pluginId) return true
|
||||
const allPlugins = pluginService.availablePlugins
|
||||
const plugin = allPlugins[pluginId]
|
||||
if (!plugin) return true
|
||||
const permissions = Array.isArray(plugin.permissions) ? plugin.permissions : []
|
||||
return permissions.indexOf("settings_write") !== -1
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
loadVariants()
|
||||
|
||||
@@ -600,6 +600,9 @@ FocusScope {
|
||||
pluginsTab.expandedPluginId = ""
|
||||
}
|
||||
}
|
||||
function onPluginListUpdated() {
|
||||
refreshPluginList()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
||||
@@ -171,7 +171,7 @@ DankPopout {
|
||||
return "Failed to check for updates:\n" + SystemUpdateService.errorMessage;
|
||||
}
|
||||
if (!SystemUpdateService.helperAvailable) {
|
||||
return "No package manager found. Please install 'paru' or 'yay' to check for updates.";
|
||||
return "No package manager found. Please install 'paru' or 'yay' on Arch-based systems to check for updates.";
|
||||
}
|
||||
if (SystemUpdateService.isChecking) {
|
||||
return "Checking for updates...";
|
||||
|
||||
10
README.md
10
README.md
@@ -661,6 +661,16 @@ cp -R ./PLUGINS/ExampleEmojiPlugin ~/.config/DankMaterialShell/plugins
|
||||
|
||||
**Only install plugins from TRUSTED sources.** Plugins execute QML and javascript at runtime, plugins from third parties should be reviewed before enabling them in dms.
|
||||
|
||||
### nixOS - via home-manager
|
||||
|
||||
Add the following to your home-manager config to install a plugin:
|
||||
|
||||
```nix
|
||||
programs.dankMaterialShell.plugins = {
|
||||
ExampleEmojiPlugin.src = "${inputs.dankMaterialShell}/PLUGINS/ExampleEmojiPlugin";
|
||||
};
|
||||
```
|
||||
|
||||
### Calendar Setup
|
||||
|
||||
Sync your caldev compatible calendar (Google, Office365, etc.) for dashboard integration:
|
||||
|
||||
@@ -136,6 +136,15 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionService
|
||||
function onPrepareForSleep() {
|
||||
if (SessionData.lockBeforeSuspend) {
|
||||
root.lockRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!idleMonitorAvailable) {
|
||||
console.warn("IdleService: IdleMonitor not available - power management disabled. This requires a newer version of Quickshell.")
|
||||
|
||||
@@ -4,6 +4,7 @@ pragma ComponentBehavior: Bound
|
||||
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import Qt.labs.folderlistmodel
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
@@ -24,197 +25,202 @@ Singleton {
|
||||
return configDirStr + "/DankMaterialShell/plugins"
|
||||
}
|
||||
property string systemPluginDirectory: "/etc/xdg/quickshell/dms-plugins"
|
||||
property var pluginDirectories: [pluginDirectory, systemPluginDirectory]
|
||||
|
||||
property var knownManifests: ({})
|
||||
property var pathToPluginId: ({})
|
||||
property var pluginInstances: ({})
|
||||
|
||||
signal pluginLoaded(string pluginId)
|
||||
signal pluginUnloaded(string pluginId)
|
||||
signal pluginLoadFailed(string pluginId, string error)
|
||||
signal pluginDataChanged(string pluginId)
|
||||
signal pluginListUpdated()
|
||||
|
||||
Timer {
|
||||
id: resyncDebounce
|
||||
interval: 120
|
||||
repeat: false
|
||||
onTriggered: resyncAll()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Qt.callLater(initializePlugins)
|
||||
userWatcher.folder = Paths.toFileUrl(root.pluginDirectory)
|
||||
systemWatcher.folder = Paths.toFileUrl(root.systemPluginDirectory)
|
||||
Qt.callLater(resyncAll)
|
||||
}
|
||||
|
||||
function initializePlugins() {
|
||||
scanPlugins()
|
||||
FolderListModel {
|
||||
id: userWatcher
|
||||
showDirs: true
|
||||
showFiles: false
|
||||
showDotAndDotDot: false
|
||||
nameFilters: ["plugin.json"]
|
||||
|
||||
onCountChanged: resyncDebounce.restart()
|
||||
onStatusChanged: if (status === FolderListModel.Ready) resyncDebounce.restart()
|
||||
}
|
||||
|
||||
property int currentScanIndex: 0
|
||||
property var scanResults: []
|
||||
property var foundPlugins: ({})
|
||||
FolderListModel {
|
||||
id: systemWatcher
|
||||
showDirs: true
|
||||
showFiles: false
|
||||
showDotAndDotDot: false
|
||||
nameFilters: ["plugin.json"]
|
||||
|
||||
property var lsProcess: Process {
|
||||
id: dirScanner
|
||||
onCountChanged: resyncDebounce.restart()
|
||||
onStatusChanged: if (status === FolderListModel.Ready) resyncDebounce.restart()
|
||||
}
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
var output = text.trim()
|
||||
var currentDir = pluginDirectories[currentScanIndex]
|
||||
if (output) {
|
||||
var directories = output.split('\n')
|
||||
for (var i = 0; i < directories.length; i++) {
|
||||
var dir = directories[i].trim()
|
||||
if (dir) {
|
||||
var manifestPath = currentDir + "/" + dir + "/plugin.json"
|
||||
loadPluginManifest(manifestPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
function snapshotModel(model, sourceTag) {
|
||||
const out = []
|
||||
const n = model.count
|
||||
const baseDir = sourceTag === "user" ? pluginDirectory : systemPluginDirectory
|
||||
for (let i = 0; i < n; i++) {
|
||||
let dirPath = model.get(i, "filePath")
|
||||
if (dirPath.startsWith("file://")) {
|
||||
dirPath = dirPath.substring(7)
|
||||
}
|
||||
if (!dirPath.startsWith(baseDir)) {
|
||||
continue
|
||||
}
|
||||
const manifestPath = dirPath + "/plugin.json"
|
||||
out.push({ path: manifestPath, source: sourceTag })
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function resyncAll() {
|
||||
const userList = snapshotModel(userWatcher, "user")
|
||||
const sysList = snapshotModel(systemWatcher, "system")
|
||||
const seenPaths = {}
|
||||
|
||||
function consider(entry) {
|
||||
const key = entry.path
|
||||
seenPaths[key] = true
|
||||
const prev = knownManifests[key]
|
||||
if (!prev) {
|
||||
loadPluginManifestFile(entry.path, entry.source, Date.now())
|
||||
}
|
||||
}
|
||||
for (let i=0;i<userList.length;i++) consider(userList[i])
|
||||
for (let i=0;i<sysList.length;i++) consider(sysList[i])
|
||||
|
||||
onExited: function(exitCode) {
|
||||
currentScanIndex++
|
||||
if (currentScanIndex < pluginDirectories.length) {
|
||||
scanNextDirectory()
|
||||
} else {
|
||||
currentScanIndex = 0
|
||||
cleanupRemovedPlugins()
|
||||
}
|
||||
const removed = []
|
||||
for (const path in knownManifests) {
|
||||
if (!seenPaths[path]) removed.push(path)
|
||||
}
|
||||
}
|
||||
|
||||
function scanPlugins() {
|
||||
currentScanIndex = 0
|
||||
foundPlugins = {}
|
||||
scanNextDirectory()
|
||||
}
|
||||
|
||||
function scanNextDirectory() {
|
||||
var dir = pluginDirectories[currentScanIndex]
|
||||
lsProcess.command = ["find", "-L", dir, "-maxdepth", "1", "-type", "d", "-not", "-path", dir, "-exec", "basename", "{}", ";"]
|
||||
lsProcess.running = true
|
||||
}
|
||||
|
||||
property var manifestReaders: ({})
|
||||
|
||||
function loadPluginManifest(manifestPath) {
|
||||
var readerId = "reader_" + Date.now() + "_" + Math.random()
|
||||
|
||||
var checkProcess = Qt.createComponent("data:text/plain,import Quickshell.Io; Process { stdout: StdioCollector { } }")
|
||||
if (checkProcess.status === Component.Ready) {
|
||||
var checker = checkProcess.createObject(root)
|
||||
checker.command = ["test", "-f", manifestPath]
|
||||
checker.exited.connect(function(exitCode) {
|
||||
if (exitCode !== 0) {
|
||||
checker.destroy()
|
||||
delete manifestReaders[readerId]
|
||||
return
|
||||
if (removed.length) {
|
||||
removed.forEach(function(path) {
|
||||
const pid = pathToPluginId[path]
|
||||
if (pid) {
|
||||
unregisterPluginByPath(path, pid)
|
||||
}
|
||||
|
||||
var catProcess = Qt.createComponent("data:text/plain,import Quickshell.Io; Process { stdout: StdioCollector { } }")
|
||||
if (catProcess.status === Component.Ready) {
|
||||
var process = catProcess.createObject(root)
|
||||
process.command = ["cat", manifestPath]
|
||||
process.stdout.streamFinished.connect(function() {
|
||||
try {
|
||||
var manifest = JSON.parse(process.stdout.text.trim())
|
||||
processManifest(manifest, manifestPath)
|
||||
} catch (e) {
|
||||
console.error("PluginService: Failed to parse manifest", manifestPath, ":", e.message)
|
||||
}
|
||||
process.destroy()
|
||||
delete manifestReaders[readerId]
|
||||
})
|
||||
process.exited.connect(function(exitCode) {
|
||||
if (exitCode !== 0) {
|
||||
console.error("PluginService: Failed to read manifest file:", manifestPath, "exit code:", exitCode)
|
||||
process.destroy()
|
||||
delete manifestReaders[readerId]
|
||||
}
|
||||
})
|
||||
manifestReaders[readerId] = process
|
||||
process.running = true
|
||||
} else {
|
||||
console.error("PluginService: Failed to create manifest reader process")
|
||||
}
|
||||
|
||||
checker.destroy()
|
||||
delete knownManifests[path]
|
||||
delete pathToPluginId[path]
|
||||
})
|
||||
manifestReaders[readerId] = checker
|
||||
checker.running = true
|
||||
} else {
|
||||
console.error("PluginService: Failed to create file check process")
|
||||
pluginListUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
function processManifest(manifest, manifestPath) {
|
||||
registerPlugin(manifest, manifestPath)
|
||||
|
||||
var enabled = SettingsData.getPluginSetting(manifest.id, "enabled", false)
|
||||
if (enabled) {
|
||||
loadPlugin(manifest.id)
|
||||
}
|
||||
function loadPluginManifestFile(manifestPathNoScheme, sourceTag, mtimeEpochMs) {
|
||||
const manifestId = "m_" + Math.random().toString(36).slice(2)
|
||||
const qml = `
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
FileView {
|
||||
id: fv
|
||||
property string absPath: ""
|
||||
onLoaded: {
|
||||
try {
|
||||
let raw = text()
|
||||
if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1)
|
||||
const manifest = JSON.parse(raw)
|
||||
root._onManifestParsed(absPath, manifest, "${sourceTag}", ${mtimeEpochMs})
|
||||
} catch (e) {
|
||||
console.error("PluginService: bad manifest", absPath, e.message)
|
||||
knownManifests[absPath] = { mtime: ${mtimeEpochMs}, source: "${sourceTag}", bad: true }
|
||||
}
|
||||
fv.destroy()
|
||||
}
|
||||
onLoadFailed: (err) => {
|
||||
console.warn("PluginService: manifest load failed", absPath, err)
|
||||
fv.destroy()
|
||||
}
|
||||
}
|
||||
`
|
||||
const loader = Qt.createQmlObject(qml, root, "mf_" + manifestId)
|
||||
loader.absPath = manifestPathNoScheme
|
||||
loader.path = manifestPathNoScheme
|
||||
}
|
||||
|
||||
function registerPlugin(manifest, manifestPath) {
|
||||
if (!manifest.id || !manifest.name || !manifest.component) {
|
||||
console.error("PluginService: Invalid manifest, missing required fields:", manifestPath)
|
||||
function _onManifestParsed(absPath, manifest, sourceTag, mtimeEpochMs) {
|
||||
if (!manifest || !manifest.id || !manifest.name || !manifest.component) {
|
||||
console.error("PluginService: invalid manifest fields:", absPath)
|
||||
knownManifests[absPath] = { mtime: mtimeEpochMs, source: sourceTag, bad: true }
|
||||
return
|
||||
}
|
||||
|
||||
var pluginDir = manifestPath.substring(0, manifestPath.lastIndexOf('/'))
|
||||
const dir = absPath.substring(0, absPath.lastIndexOf('/'))
|
||||
let comp = manifest.component
|
||||
if (comp.startsWith("./")) comp = comp.slice(2)
|
||||
let settings = manifest.settings
|
||||
if (settings && settings.startsWith("./")) settings = settings.slice(2)
|
||||
|
||||
// Clean up relative paths by removing './' prefix
|
||||
var componentFile = manifest.component
|
||||
if (componentFile.startsWith('./')) {
|
||||
componentFile = componentFile.substring(2)
|
||||
const info = {}
|
||||
for (const k in manifest) info[k] = manifest[k]
|
||||
|
||||
let perms = manifest.permissions
|
||||
if (typeof perms === "string") {
|
||||
perms = perms.split(/\s*,\s*/)
|
||||
}
|
||||
|
||||
var settingsFile = manifest.settings
|
||||
if (settingsFile && settingsFile.startsWith('./')) {
|
||||
settingsFile = settingsFile.substring(2)
|
||||
if (!Array.isArray(perms)) {
|
||||
perms = []
|
||||
}
|
||||
info.permissions = perms.map(p => String(p).trim())
|
||||
|
||||
var pluginInfo = {}
|
||||
for (var key in manifest) {
|
||||
pluginInfo[key] = manifest[key]
|
||||
info.manifestPath = absPath
|
||||
info.pluginDirectory = dir
|
||||
info.componentPath = dir + "/" + comp
|
||||
info.settingsPath = settings ? (dir + "/" + settings) : null
|
||||
info.loaded = isPluginLoaded(manifest.id)
|
||||
info.type = manifest.type || "widget"
|
||||
info.source = sourceTag
|
||||
|
||||
const existing = availablePlugins[manifest.id]
|
||||
const shouldReplace =
|
||||
(!existing) ||
|
||||
(existing && existing.source === "system" && sourceTag === "user")
|
||||
|
||||
if (shouldReplace) {
|
||||
if (existing && existing.loaded && existing.source !== sourceTag) {
|
||||
unloadPlugin(manifest.id)
|
||||
}
|
||||
const newMap = Object.assign({}, availablePlugins)
|
||||
newMap[manifest.id] = info
|
||||
availablePlugins = newMap
|
||||
pathToPluginId[absPath] = manifest.id
|
||||
knownManifests[absPath] = { mtime: mtimeEpochMs, source: sourceTag }
|
||||
pluginListUpdated()
|
||||
const enabled = SettingsData.getPluginSetting(manifest.id, "enabled", false)
|
||||
if (enabled && !info.loaded) loadPlugin(manifest.id)
|
||||
} else {
|
||||
knownManifests[absPath] = { mtime: mtimeEpochMs, source: sourceTag, shadowedBy: existing.source }
|
||||
pathToPluginId[absPath] = manifest.id
|
||||
}
|
||||
pluginInfo.manifestPath = manifestPath
|
||||
pluginInfo.pluginDirectory = pluginDir
|
||||
pluginInfo.componentPath = pluginDir + '/' + componentFile
|
||||
pluginInfo.settingsPath = settingsFile ? pluginDir + '/' + settingsFile : null
|
||||
pluginInfo.loaded = false
|
||||
pluginInfo.type = manifest.type || "widget"
|
||||
|
||||
var newPlugins = Object.assign({}, availablePlugins)
|
||||
newPlugins[manifest.id] = pluginInfo
|
||||
availablePlugins = newPlugins
|
||||
foundPlugins[manifest.id] = true
|
||||
}
|
||||
|
||||
function hasPermission(pluginId, permission) {
|
||||
var plugin = availablePlugins[pluginId]
|
||||
if (!plugin) {
|
||||
return false
|
||||
}
|
||||
var permissions = plugin.permissions || []
|
||||
return permissions.indexOf(permission) !== -1
|
||||
}
|
||||
|
||||
function cleanupRemovedPlugins() {
|
||||
var pluginsToRemove = []
|
||||
for (var pluginId in availablePlugins) {
|
||||
if (!foundPlugins[pluginId]) {
|
||||
pluginsToRemove.push(pluginId)
|
||||
}
|
||||
}
|
||||
|
||||
if (pluginsToRemove.length > 0) {
|
||||
var newPlugins = Object.assign({}, availablePlugins)
|
||||
for (var i = 0; i < pluginsToRemove.length; i++) {
|
||||
var pluginId = pluginsToRemove[i]
|
||||
if (isPluginLoaded(pluginId)) {
|
||||
unloadPlugin(pluginId)
|
||||
}
|
||||
delete newPlugins[pluginId]
|
||||
}
|
||||
availablePlugins = newPlugins
|
||||
function unregisterPluginByPath(absPath, pluginId) {
|
||||
const current = availablePlugins[pluginId]
|
||||
if (current && current.manifestPath === absPath) {
|
||||
if (current.loaded) unloadPlugin(pluginId)
|
||||
const newMap = Object.assign({}, availablePlugins)
|
||||
delete newMap[pluginId]
|
||||
availablePlugins = newMap
|
||||
}
|
||||
}
|
||||
|
||||
function loadPlugin(pluginId) {
|
||||
var plugin = availablePlugins[pluginId]
|
||||
const plugin = availablePlugins[pluginId]
|
||||
if (!plugin) {
|
||||
console.error("PluginService: Plugin not found:", pluginId)
|
||||
pluginLoadFailed(pluginId, "Plugin not found")
|
||||
@@ -225,48 +231,43 @@ Singleton {
|
||||
return true
|
||||
}
|
||||
|
||||
var isDaemon = plugin.type === "daemon"
|
||||
var componentMap = isDaemon ? pluginDaemonComponents : pluginWidgetComponents
|
||||
const isDaemon = plugin.type === "daemon"
|
||||
const map = isDaemon ? pluginDaemonComponents : pluginWidgetComponents
|
||||
|
||||
if (componentMap[pluginId]) {
|
||||
componentMap[pluginId]?.destroy()
|
||||
if (isDaemon) {
|
||||
var newDaemons = Object.assign({}, pluginDaemonComponents)
|
||||
delete newDaemons[pluginId]
|
||||
pluginDaemonComponents = newDaemons
|
||||
} else {
|
||||
var newComponents = Object.assign({}, pluginWidgetComponents)
|
||||
delete newComponents[pluginId]
|
||||
pluginWidgetComponents = newComponents
|
||||
}
|
||||
const prevInstance = pluginInstances[pluginId]
|
||||
if (prevInstance) {
|
||||
prevInstance.destroy()
|
||||
const newInstances = Object.assign({}, pluginInstances)
|
||||
delete newInstances[pluginId]
|
||||
pluginInstances = newInstances
|
||||
}
|
||||
|
||||
try {
|
||||
var componentUrl = "file://" + plugin.componentPath
|
||||
var component = Qt.createComponent(componentUrl, Component.PreferSynchronous)
|
||||
|
||||
if (component.status === Component.Loading) {
|
||||
component.statusChanged.connect(function() {
|
||||
if (component.status === Component.Error) {
|
||||
console.error("PluginService: Failed to create component for plugin:", pluginId, "Error:", component.errorString())
|
||||
pluginLoadFailed(pluginId, component.errorString())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (component.status === Component.Error) {
|
||||
console.error("PluginService: Failed to create component for plugin:", pluginId, "Error:", component.errorString())
|
||||
pluginLoadFailed(pluginId, component.errorString())
|
||||
const url = "file://" + plugin.componentPath
|
||||
const comp = Qt.createComponent(url, Component.PreferSynchronous)
|
||||
if (comp.status === Component.Error) {
|
||||
console.error("PluginService: component error", pluginId, comp.errorString())
|
||||
pluginLoadFailed(pluginId, comp.errorString())
|
||||
return false
|
||||
}
|
||||
|
||||
if (isDaemon) {
|
||||
var newDaemons = Object.assign({}, pluginDaemonComponents)
|
||||
newDaemons[pluginId] = component
|
||||
const instance = comp.createObject(root, { "pluginId": pluginId })
|
||||
if (!instance) {
|
||||
console.error("PluginService: failed to instantiate daemon:", pluginId, comp.errorString())
|
||||
pluginLoadFailed(pluginId, comp.errorString())
|
||||
return false
|
||||
}
|
||||
const newInstances = Object.assign({}, pluginInstances)
|
||||
newInstances[pluginId] = instance
|
||||
pluginInstances = newInstances
|
||||
|
||||
const newDaemons = Object.assign({}, pluginDaemonComponents)
|
||||
newDaemons[pluginId] = comp
|
||||
pluginDaemonComponents = newDaemons
|
||||
} else {
|
||||
var newComponents = Object.assign({}, pluginWidgetComponents)
|
||||
newComponents[pluginId] = component
|
||||
const newComponents = Object.assign({}, pluginWidgetComponents)
|
||||
newComponents[pluginId] = comp
|
||||
pluginWidgetComponents = newComponents
|
||||
}
|
||||
|
||||
@@ -276,31 +277,37 @@ Singleton {
|
||||
pluginLoaded(pluginId)
|
||||
return true
|
||||
|
||||
} catch (error) {
|
||||
console.error("PluginService: Error loading plugin:", pluginId, "Error:", error.message)
|
||||
pluginLoadFailed(pluginId, error.message)
|
||||
} catch (e) {
|
||||
console.error("PluginService: Error loading plugin:", pluginId, e.message)
|
||||
pluginLoadFailed(pluginId, e.message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function unloadPlugin(pluginId) {
|
||||
var plugin = loadedPlugins[pluginId]
|
||||
const plugin = loadedPlugins[pluginId]
|
||||
if (!plugin) {
|
||||
console.warn("PluginService: Plugin not loaded:", pluginId)
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
var isDaemon = plugin.type === "daemon"
|
||||
const isDaemon = plugin.type === "daemon"
|
||||
|
||||
const instance = pluginInstances[pluginId]
|
||||
if (instance) {
|
||||
instance.destroy()
|
||||
const newInstances = Object.assign({}, pluginInstances)
|
||||
delete newInstances[pluginId]
|
||||
pluginInstances = newInstances
|
||||
}
|
||||
|
||||
if (isDaemon && pluginDaemonComponents[pluginId]) {
|
||||
pluginDaemonComponents[pluginId]?.destroy()
|
||||
var newDaemons = Object.assign({}, pluginDaemonComponents)
|
||||
const newDaemons = Object.assign({}, pluginDaemonComponents)
|
||||
delete newDaemons[pluginId]
|
||||
pluginDaemonComponents = newDaemons
|
||||
} else if (pluginWidgetComponents[pluginId]) {
|
||||
pluginWidgetComponents[pluginId]?.destroy()
|
||||
var newComponents = Object.assign({}, pluginWidgetComponents)
|
||||
const newComponents = Object.assign({}, pluginWidgetComponents)
|
||||
delete newComponents[pluginId]
|
||||
pluginWidgetComponents = newComponents
|
||||
}
|
||||
@@ -326,30 +333,30 @@ Singleton {
|
||||
}
|
||||
|
||||
function getAvailablePlugins() {
|
||||
var result = []
|
||||
for (var key in availablePlugins) {
|
||||
const result = []
|
||||
for (const key in availablePlugins) {
|
||||
result.push(availablePlugins[key])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function getPluginVariants(pluginId) {
|
||||
var plugin = availablePlugins[pluginId]
|
||||
const plugin = availablePlugins[pluginId]
|
||||
if (!plugin) {
|
||||
return []
|
||||
}
|
||||
var variants = SettingsData.getPluginSetting(pluginId, "variants", [])
|
||||
const variants = SettingsData.getPluginSetting(pluginId, "variants", [])
|
||||
return variants
|
||||
}
|
||||
|
||||
function getAllPluginVariants() {
|
||||
var result = []
|
||||
for (var pluginId in availablePlugins) {
|
||||
var plugin = availablePlugins[pluginId]
|
||||
const result = []
|
||||
for (const pluginId in availablePlugins) {
|
||||
const plugin = availablePlugins[pluginId]
|
||||
if (plugin.type !== "widget") {
|
||||
continue
|
||||
}
|
||||
var variants = getPluginVariants(pluginId)
|
||||
const variants = getPluginVariants(pluginId)
|
||||
if (variants.length === 0) {
|
||||
result.push({
|
||||
pluginId: pluginId,
|
||||
@@ -361,8 +368,8 @@ Singleton {
|
||||
loaded: plugin.loaded
|
||||
})
|
||||
} else {
|
||||
for (var i = 0; i < variants.length; i++) {
|
||||
var variant = variants[i]
|
||||
for (let i = 0; i < variants.length; i++) {
|
||||
const variant = variants[i]
|
||||
result.push({
|
||||
pluginId: pluginId,
|
||||
variantId: variant.id,
|
||||
@@ -379,9 +386,9 @@ Singleton {
|
||||
}
|
||||
|
||||
function createPluginVariant(pluginId, variantName, variantConfig) {
|
||||
var variants = getPluginVariants(pluginId)
|
||||
var variantId = "variant_" + Date.now()
|
||||
var newVariant = Object.assign({}, variantConfig, {
|
||||
const variants = getPluginVariants(pluginId)
|
||||
const variantId = "variant_" + Date.now()
|
||||
const newVariant = Object.assign({}, variantConfig, {
|
||||
id: variantId,
|
||||
name: variantName
|
||||
})
|
||||
@@ -392,15 +399,15 @@ Singleton {
|
||||
}
|
||||
|
||||
function removePluginVariant(pluginId, variantId) {
|
||||
var variants = getPluginVariants(pluginId)
|
||||
var newVariants = variants.filter(function(v) { return v.id !== variantId })
|
||||
const variants = getPluginVariants(pluginId)
|
||||
const newVariants = variants.filter(function(v) { return v.id !== variantId })
|
||||
SettingsData.setPluginSetting(pluginId, "variants", newVariants)
|
||||
pluginDataChanged(pluginId)
|
||||
}
|
||||
|
||||
function updatePluginVariant(pluginId, variantId, variantConfig) {
|
||||
var variants = getPluginVariants(pluginId)
|
||||
for (var i = 0; i < variants.length; i++) {
|
||||
const variants = getPluginVariants(pluginId)
|
||||
for (let i = 0; i < variants.length; i++) {
|
||||
if (variants[i].id === variantId) {
|
||||
variants[i] = Object.assign({}, variants[i], variantConfig)
|
||||
break
|
||||
@@ -411,8 +418,8 @@ Singleton {
|
||||
}
|
||||
|
||||
function getPluginVariantData(pluginId, variantId) {
|
||||
var variants = getPluginVariants(pluginId)
|
||||
for (var i = 0; i < variants.length; i++) {
|
||||
const variants = getPluginVariants(pluginId)
|
||||
for (let i = 0; i < variants.length; i++) {
|
||||
if (variants[i].id === variantId) {
|
||||
return variants[i]
|
||||
}
|
||||
@@ -421,8 +428,8 @@ Singleton {
|
||||
}
|
||||
|
||||
function getLoadedPlugins() {
|
||||
var result = []
|
||||
for (var key in loadedPlugins) {
|
||||
const result = []
|
||||
for (const key in loadedPlugins) {
|
||||
result.push(loadedPlugins[key])
|
||||
}
|
||||
return result
|
||||
@@ -463,10 +470,14 @@ Singleton {
|
||||
SettingsData.savePluginSettings()
|
||||
}
|
||||
|
||||
function scanPlugins() {
|
||||
resyncDebounce.restart()
|
||||
}
|
||||
|
||||
function createPluginDirectory() {
|
||||
var mkdirProcess = Qt.createComponent("data:text/plain,import Quickshell.Io; Process { }")
|
||||
const mkdirProcess = Qt.createComponent("data:text/plain,import Quickshell.Io; Process { }")
|
||||
if (mkdirProcess.status === Component.Ready) {
|
||||
var process = mkdirProcess.createObject(root)
|
||||
const process = mkdirProcess.createObject(root)
|
||||
process.command = ["mkdir", "-p", pluginDirectory]
|
||||
process.exited.connect(function(exitCode) {
|
||||
if (exitCode !== 0) {
|
||||
|
||||
@@ -61,6 +61,10 @@ Singleton {
|
||||
detectHibernateProcess.running = true
|
||||
detectPrimeRunProcess.running = true
|
||||
console.log("SessionService: Native inhibitor available:", nativeInhibitorAvailable)
|
||||
if (!SessionData.loginctlLockIntegration) {
|
||||
console.log("SessionService: loginctl lock integration disabled by user")
|
||||
return
|
||||
}
|
||||
if (socketPath && socketPath.length > 0) {
|
||||
checkDMSCapabilities()
|
||||
} else {
|
||||
@@ -291,10 +295,29 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
|
||||
function onLoginctlLockIntegrationChanged() {
|
||||
if (SessionData.loginctlLockIntegration) {
|
||||
if (socketPath && socketPath.length > 0) {
|
||||
checkDMSCapabilities()
|
||||
} else {
|
||||
initFallbackLoginctl()
|
||||
}
|
||||
} else {
|
||||
subscriptionSocket.connected = false
|
||||
lockStateMonitorFallback.running = false
|
||||
loginctlAvailable = false
|
||||
stateInitialized = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankSocket {
|
||||
id: subscriptionSocket
|
||||
path: root.socketPath
|
||||
connected: loginctlAvailable
|
||||
connected: loginctlAvailable && SessionData.loginctlLockIntegration
|
||||
|
||||
onConnectionStateChanged: {
|
||||
root.subscriptionConnected = connected
|
||||
@@ -342,6 +365,10 @@ Singleton {
|
||||
return
|
||||
}
|
||||
|
||||
if (!SessionData.loginctlLockIntegration) {
|
||||
return
|
||||
}
|
||||
|
||||
if (DMSService.capabilities.includes("loginctl")) {
|
||||
loginctlAvailable = true
|
||||
if (!stateInitialized) {
|
||||
@@ -366,6 +393,8 @@ Singleton {
|
||||
}
|
||||
|
||||
function updateLoginctlState(state) {
|
||||
const wasLocked = locked
|
||||
|
||||
sessionId = state.sessionId || ""
|
||||
sessionPath = state.sessionPath || ""
|
||||
locked = state.locked || false
|
||||
@@ -384,6 +413,12 @@ Singleton {
|
||||
prepareForSleep()
|
||||
}
|
||||
|
||||
if (locked && !wasLocked) {
|
||||
sessionLocked()
|
||||
} else if (!locked && wasLocked) {
|
||||
sessionUnlocked()
|
||||
}
|
||||
|
||||
loginctlStateChanged()
|
||||
}
|
||||
|
||||
@@ -483,10 +518,4 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: lockSessionFallback
|
||||
command: ["loginctl", "lock-session"]
|
||||
running: false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,48 @@ Singleton {
|
||||
property bool distributionSupported: false
|
||||
property string shellVersion: ""
|
||||
|
||||
readonly property list<string> supportedDistributions: ["arch", "cachyos", "manjaro", "endeavouros"]
|
||||
readonly property var archBasedSettings: {
|
||||
"listUpdatesParams": ["-Qu"],
|
||||
"upgradeSettings": {
|
||||
"params": ["-Syu"],
|
||||
"requiresSudo": false
|
||||
},
|
||||
"parserSettings": {
|
||||
"lineRegex": /^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/,
|
||||
"entryProducer": function (match) {
|
||||
return {
|
||||
"name": match[1],
|
||||
"currentVersion": match[2],
|
||||
"newVersion": match[3],
|
||||
"description": `${match[1]} ${match[2]} → ${match[3]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var packageManagerParams: {
|
||||
"yay": archBasedSettings,
|
||||
"paru": archBasedSettings,
|
||||
"dnf": {
|
||||
"listUpdatesParams": ["list", "--upgrades", "--quiet", "--color=never"],
|
||||
"upgradeSettings": {
|
||||
"params": ["upgrade"],
|
||||
"requiresSudo": true
|
||||
},
|
||||
"parserSettings": {
|
||||
"lineRegex": /^([^\s]+)\s+([^\s]+)\s+.*$/,
|
||||
"entryProducer": function (match) {
|
||||
return {
|
||||
"name": match[1],
|
||||
"currentVersion": "",
|
||||
"newVersion": match[2],
|
||||
"description": `${match[1]} → ${match[2]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
readonly property list<string> supportedDistributions: ["arch", "cachyos", "manjaro", "endeavouros", "fedora"]
|
||||
readonly property int updateCount: availableUpdates.length
|
||||
readonly property bool helperAvailable: pkgManager !== "" && distributionSupported
|
||||
|
||||
@@ -63,7 +104,7 @@ Singleton {
|
||||
|
||||
Process {
|
||||
id: helperDetection
|
||||
command: ["sh", "-c", "which paru || which yay"]
|
||||
command: ["sh", "-c", "which paru || which yay || which dnf"]
|
||||
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode === 0) {
|
||||
@@ -110,7 +151,7 @@ Singleton {
|
||||
|
||||
isChecking = true
|
||||
hasError = false
|
||||
updateChecker.command = [pkgManager, "-Qu"]
|
||||
updateChecker.command = [pkgManager].concat(packageManagerParams[pkgManager].listUpdatesParams)
|
||||
updateChecker.running = true
|
||||
}
|
||||
|
||||
@@ -118,15 +159,13 @@ Singleton {
|
||||
const lines = output.trim().split('\n').filter(line => line.trim())
|
||||
const updates = []
|
||||
|
||||
const regex = packageManagerParams[pkgManager].parserSettings.lineRegex
|
||||
const entryProducer = packageManagerParams[pkgManager].parserSettings.entryProducer
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/)
|
||||
const match = line.match(regex)
|
||||
if (match) {
|
||||
updates.push({
|
||||
name: match[1],
|
||||
currentVersion: match[2],
|
||||
newVersion: match[3],
|
||||
description: `${match[1]} ${match[2]} → ${match[3]}`
|
||||
})
|
||||
updates.push(entryProducer(match))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +176,9 @@ Singleton {
|
||||
if (!distributionSupported || !pkgManager || updateCount === 0) return
|
||||
|
||||
const terminal = Quickshell.env("TERMINAL") || "xterm"
|
||||
const updateCommand = `${pkgManager} -Syu && echo "Updates complete! Press Enter to close..." && read`
|
||||
const params = packageManagerParams[pkgManager].upgradeSettings.params.join(" ")
|
||||
const sudo = packageManagerParams[pkgManager].upgradeSettings.requiresSudo ? "sudo" : ""
|
||||
const updateCommand = `${sudo} ${pkgManager} ${params} && echo "Updates complete! Press Enter to close..." && read`
|
||||
|
||||
updater.command = [terminal, "-e", "sh", "-c", updateCommand]
|
||||
updater.running = true
|
||||
@@ -149,4 +190,4 @@ Singleton {
|
||||
running: distributionSupported && pkgManager
|
||||
onTriggered: checkForUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
105
dms.spec
Normal file
105
dms.spec
Normal file
@@ -0,0 +1,105 @@
|
||||
# Spec for DMS - uses rpkg macros for both stable and git builds
|
||||
|
||||
%global debug_package %{nil}
|
||||
%global version {{{ git_dir_version }}}
|
||||
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||
|
||||
Name: dms
|
||||
Version: %{version}
|
||||
Release: 1%{?dist}
|
||||
Summary: %{pkg_summary}
|
||||
|
||||
License: GPL-3.0-only
|
||||
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||
VCS: {{{ git_dir_vcs }}}
|
||||
Source0: {{{ git_dir_pack }}}
|
||||
|
||||
# dms CLI tool sources - compiled from danklinux
|
||||
Source1: https://github.com/AvengeMedia/danklinux/archive/refs/heads/master.tar.gz#/danklinux-master.tar.gz
|
||||
|
||||
BuildRequires: git-core
|
||||
BuildRequires: golang >= 1.21
|
||||
BuildRequires: rpkg
|
||||
|
||||
# Core requirements - Shell and fonts
|
||||
Requires: (quickshell or quickshell-git)
|
||||
Recommends: quickshell-git
|
||||
Requires: fira-code-fonts
|
||||
Requires: rsms-inter-fonts
|
||||
|
||||
# Core utilities (REQUIRED for DMS functionality)
|
||||
Requires: dgop
|
||||
Requires: cava
|
||||
Requires: wl-clipboard
|
||||
Requires: brightnessctl
|
||||
Requires: matugen
|
||||
Requires: cliphist
|
||||
Requires: material-symbols-fonts
|
||||
|
||||
# Recommended system packages
|
||||
Recommends: NetworkManager
|
||||
Recommends: gammastep
|
||||
Recommends: qt6ct
|
||||
|
||||
# Auto-require the CLI sub-package
|
||||
Requires: dms-cli = %{version}-%{release}
|
||||
|
||||
%description
|
||||
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
|
||||
and optimized for the niri and Hyprland compositors. Features notifications,
|
||||
app launcher, wallpaper customization, and fully customizable with plugins.
|
||||
|
||||
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
|
||||
process monitoring, notification center, clipboard history, dock, control center,
|
||||
lock screen, and comprehensive plugin system.
|
||||
|
||||
%package -n dms-cli
|
||||
Summary: DankMaterialShell CLI tool
|
||||
License: GPL-3.0-only
|
||||
URL: https://github.com/AvengeMedia/danklinux
|
||||
|
||||
%description -n dms-cli
|
||||
Command-line interface for DankMaterialShell configuration and management.
|
||||
Provides native DBus bindings, NetworkManager integration, and system utilities.
|
||||
|
||||
%prep
|
||||
{{{ git_dir_setup_macro }}}
|
||||
|
||||
# Extract danklinux for building dms CLI
|
||||
tar -xzf %{SOURCE1} -C %{_builddir}
|
||||
|
||||
%build
|
||||
# Compile dms CLI from danklinux source
|
||||
pushd %{_builddir}/danklinux-master
|
||||
export CGO_CPPFLAGS="${CPPFLAGS}"
|
||||
export CGO_CFLAGS="${CFLAGS}"
|
||||
export CGO_CXXFLAGS="${CXXFLAGS}"
|
||||
export CGO_LDFLAGS="${LDFLAGS}"
|
||||
export GOFLAGS="-buildmode=pie -trimpath -ldflags=-linkmode=external -mod=readonly -modcacherw"
|
||||
|
||||
go build -o dms ./cmd/dms
|
||||
popd
|
||||
|
||||
%install
|
||||
# Install dms-cli binary
|
||||
install -Dm755 %{_builddir}/danklinux-master/dms %{buildroot}%{_bindir}/dms-cli
|
||||
|
||||
# Install shell files to XDG config location
|
||||
install -dm755 %{buildroot}%{_sysconfdir}/xdg/quickshell/dms
|
||||
cp -r ./* %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/
|
||||
|
||||
# Remove git-related 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
|
||||
|
||||
%files
|
||||
%license LICENSE
|
||||
%doc README.md CONTRIBUTING.md
|
||||
%{_sysconfdir}/xdg/quickshell/dms/
|
||||
|
||||
%files -n dms-cli
|
||||
%{_bindir}/dms-cli
|
||||
|
||||
%changelog
|
||||
{{{ git_dir_changelog }}}
|
||||
6
flake.lock
generated
6
flake.lock
generated
@@ -27,11 +27,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1759946376,
|
||||
"narHash": "sha256-/kQpJPH1y+U6V7N3bbGzvNRGfk9VuxdZev9Os4bS5ZQ=",
|
||||
"lastModified": 1759982027,
|
||||
"narHash": "sha256-4deRT98VwfZWZ685wIGevyYl3CzpuZJPjdjfulABH00=",
|
||||
"owner": "AvengeMedia",
|
||||
"repo": "danklinux",
|
||||
"rev": "98db89ffba290265bc4a886d13b8a27a53fdaca1",
|
||||
"rev": "5cdfeeae2e14089079dcb0d6b61f014ce754021f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
...
|
||||
}: let
|
||||
cfg = config.programs.dankMaterialShell;
|
||||
jsonFormat = pkgs.formats.json { };
|
||||
in {
|
||||
options.programs.dankMaterialShell = with lib.types; {
|
||||
enable = lib.mkEnableOption "DankMaterialShell";
|
||||
@@ -54,6 +55,37 @@ in {
|
||||
quickshell = {
|
||||
package = lib.mkPackageOption pkgs "quickshell" {};
|
||||
};
|
||||
|
||||
default = {
|
||||
settings = lib.mkOption {
|
||||
type = jsonFormat.type;
|
||||
default = { };
|
||||
description = "The default settings are only read if the settings.json file don't exist";
|
||||
};
|
||||
session = lib.mkOption {
|
||||
type = jsonFormat.type;
|
||||
default = { };
|
||||
description = "The default session are only read if the session.json file don't exist";
|
||||
};
|
||||
};
|
||||
|
||||
plugins = lib.mkOption {
|
||||
type = attrsOf (types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
enable = lib.mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether to link this plugin";
|
||||
};
|
||||
src = lib.mkOption {
|
||||
type = types.path;
|
||||
description = "Source to link to DMS plugins directory";
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = {};
|
||||
description = "DMS Plugins to install";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable
|
||||
@@ -65,14 +97,39 @@ in {
|
||||
configs.dms = "${
|
||||
dmsPkgs.dankMaterialShell
|
||||
}/etc/xdg/quickshell/DankMaterialShell";
|
||||
activeConfig = lib.mkIf cfg.enableSystemd "dms";
|
||||
|
||||
systemd = lib.mkIf cfg.enableSystemd {
|
||||
enable = true;
|
||||
target = "graphical-session.target";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.user.services.dms = lib.mkIf cfg.enableSystemd {
|
||||
Unit = {
|
||||
Description = "DankMaterialShell";
|
||||
PartOf = [ config.wayland.systemd.target ];
|
||||
After = [ config.wayland.systemd.target ];
|
||||
};
|
||||
|
||||
Service = {
|
||||
ExecStart = lib.getExe dmsPkgs.dmsCli + " run";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
|
||||
Install.WantedBy = [ config.wayland.systemd.target ];
|
||||
};
|
||||
|
||||
xdg.stateFile."DankMaterialShell/default-session.json" = lib.mkIf (cfg.default.session != { }) {
|
||||
source = jsonFormat.generate "default-session.json" cfg.default.session;
|
||||
};
|
||||
|
||||
xdg.configFile = lib.mkMerge [
|
||||
(lib.mapAttrs' (name: plugin: {
|
||||
name = "DankMaterialShell/plugins/${name}";
|
||||
value.source = plugin.src;
|
||||
}) (lib.filterAttrs (n: v: v.enable) cfg.plugins))
|
||||
{
|
||||
"DankMaterialShell/default-settings.json" = lib.mkIf (cfg.default.settings != { }) {
|
||||
source = jsonFormat.generate "default-settings.json" cfg.default.settings;
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
home.packages =
|
||||
[
|
||||
pkgs.material-symbols
|
||||
|
||||
@@ -30,16 +30,16 @@
|
||||
"A file with this name already exists. Do you want to overwrite it?": "この名前のファイルは既に存在します。上書きしてもよろしいですか?"
|
||||
},
|
||||
"About": {
|
||||
"About": ""
|
||||
"About": "詳細"
|
||||
},
|
||||
"Access clipboard history": {
|
||||
"Access clipboard history": ""
|
||||
"Access clipboard history": "クリップボードの履歴へのアクセス"
|
||||
},
|
||||
"Access to notifications and do not disturb": {
|
||||
"Access to notifications and do not disturb": ""
|
||||
"Access to notifications and do not disturb": "通知へのアクセスおよびサイレントモード"
|
||||
},
|
||||
"Access to system controls and settings": {
|
||||
"Access to system controls and settings": ""
|
||||
"Access to system controls and settings": "システム制御へのアクセスおよび設定"
|
||||
},
|
||||
"Actions": {
|
||||
"Actions": "アクション"
|
||||
@@ -63,7 +63,7 @@
|
||||
"All displays": "すべてのディスプレイ"
|
||||
},
|
||||
"Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": {
|
||||
"Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": ""
|
||||
"Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": "Alt+←/Backspace: 戻る • F1/I: ファイル情報 • F10: ヘルプ • Esc: 閉じる"
|
||||
},
|
||||
"Always Show OSD Percentage": {
|
||||
"Always Show OSD Percentage": "常に OSD パーセンテージを表示"
|
||||
@@ -75,10 +75,10 @@
|
||||
"Animations": "アニメーション"
|
||||
},
|
||||
"App Launcher": {
|
||||
"App Launcher": ""
|
||||
"App Launcher": "アプリランチャー"
|
||||
},
|
||||
"Applications": {
|
||||
"Applications": ""
|
||||
"Applications": "アプリ"
|
||||
},
|
||||
"Apply": {
|
||||
"Apply": "適用"
|
||||
@@ -93,7 +93,7 @@
|
||||
"Apps Icon": "アプリアイコン"
|
||||
},
|
||||
"Apps are ordered by usage frequency, then last used, then alphabetically.": {
|
||||
"Apps are ordered by usage frequency, then last used, then alphabetically.": ""
|
||||
"Apps are ordered by usage frequency, then last used, then alphabetically.": "アプリは使用頻度、最終使用日、アルファベット順の優先順位で並べられています。"
|
||||
},
|
||||
"Are you sure you want to hibernate the system?": {
|
||||
"Are you sure you want to hibernate the system?": "システムを休止状態にしますか?"
|
||||
@@ -171,13 +171,13 @@
|
||||
"Available Screens (": "利用可能なスクリーン("
|
||||
},
|
||||
"Back": {
|
||||
"Back": ""
|
||||
"Back": "戻る"
|
||||
},
|
||||
"Battery": {
|
||||
"Battery": ""
|
||||
"Battery": "バッテリー"
|
||||
},
|
||||
"Battery level and power management": {
|
||||
"Battery level and power management": ""
|
||||
"Battery level and power management": "バッテリーレベルおよび電源管理"
|
||||
},
|
||||
"Battery not detected - only AC power settings available": {
|
||||
"Battery not detected - only AC power settings available": "バッテリーが検出されません - AC電源設定のみ利用可能です"
|
||||
@@ -192,7 +192,7 @@
|
||||
"Border": "ボーダー"
|
||||
},
|
||||
"Brightness": {
|
||||
"Brightness": ""
|
||||
"Brightness": "明るさ"
|
||||
},
|
||||
"Browse": {
|
||||
"Browse": "ブラウズ"
|
||||
@@ -204,16 +204,16 @@
|
||||
"CPU": "CPU"
|
||||
},
|
||||
"CPU Temperature": {
|
||||
"CPU Temperature": ""
|
||||
"CPU Temperature": "CPU温度"
|
||||
},
|
||||
"CPU Usage": {
|
||||
"CPU Usage": ""
|
||||
"CPU Usage": "CPU使用率"
|
||||
},
|
||||
"CPU temperature display": {
|
||||
"CPU temperature display": ""
|
||||
"CPU temperature display": "CPU温度表示"
|
||||
},
|
||||
"CPU usage indicator": {
|
||||
"CPU usage indicator": ""
|
||||
"CPU usage indicator": "CPU使用率インジケーター"
|
||||
},
|
||||
"Cancel": {
|
||||
"Cancel": "キャンセル"
|
||||
@@ -225,37 +225,37 @@
|
||||
"Center Section": "センターセクション"
|
||||
},
|
||||
"Check for system updates": {
|
||||
"Check for system updates": ""
|
||||
"Check for system updates": "システムアップデートを検査"
|
||||
},
|
||||
"Choose Launcher Logo Color": {
|
||||
"Choose Launcher Logo Color": ""
|
||||
"Choose Launcher Logo Color": "ランチャーロゴの色を選ぶ"
|
||||
},
|
||||
"Choose icon": {
|
||||
"Choose icon": "アイコンを選択"
|
||||
"Choose icon": "アイコンを選ぶ"
|
||||
},
|
||||
"Choose the logo displayed on the launcher button in DankBar": {
|
||||
"Choose the logo displayed on the launcher button in DankBar": "Dank Barのランチャーボタンに表示されるロゴを選択"
|
||||
"Choose the logo displayed on the launcher button in DankBar": "Dank Barのランチャーボタンに表示されるロゴを選ぶ"
|
||||
},
|
||||
"Choose where notification popups appear on screen": {
|
||||
"Choose where notification popups appear on screen": ""
|
||||
"Choose where notification popups appear on screen": "通知ポップアップが画面に表示される場所を選ぶ"
|
||||
},
|
||||
"Clear": {
|
||||
"Clear": "クリア"
|
||||
},
|
||||
"Clear All": {
|
||||
"Clear All": ""
|
||||
"Clear All": "すべてクリア"
|
||||
},
|
||||
"Clear All History?": {
|
||||
"Clear All History?": ""
|
||||
"Clear All History?": "すべての履歴をクリアしますか?"
|
||||
},
|
||||
"Clipboard History": {
|
||||
"Clipboard History": ""
|
||||
"Clipboard History": "クリップボード履歴"
|
||||
},
|
||||
"Clipboard Manager": {
|
||||
"Clipboard Manager": ""
|
||||
"Clipboard Manager": "クリップボード管理"
|
||||
},
|
||||
"Clock": {
|
||||
"Clock": ""
|
||||
"Clock": "時計"
|
||||
},
|
||||
"Close": {
|
||||
"Close": "閉じる"
|
||||
@@ -264,10 +264,10 @@
|
||||
"Color Override": "色のオーバーライド"
|
||||
},
|
||||
"Color Picker": {
|
||||
"Color Picker": ""
|
||||
"Color Picker": "カラーピッカー"
|
||||
},
|
||||
"Color temperature for night mode": {
|
||||
"Color temperature for night mode": ""
|
||||
"Color temperature for night mode": "ナイトモードの色温度"
|
||||
},
|
||||
"Communication": {
|
||||
"Communication": "コミュニケーション"
|
||||
@@ -294,19 +294,19 @@
|
||||
"Connected Displays": "接続されたディスプレイ"
|
||||
},
|
||||
"Contrast": {
|
||||
"Contrast": ""
|
||||
"Contrast": "コントラスト"
|
||||
},
|
||||
"Control Center": {
|
||||
"Control Center": ""
|
||||
"Control Center": "コントロールセンター"
|
||||
},
|
||||
"Control currently playing media": {
|
||||
"Control currently playing media": ""
|
||||
"Control currently playing media": "現在再生中のメディアを制御"
|
||||
},
|
||||
"Control the speed of animations throughout the interface": {
|
||||
"Control the speed of animations throughout the interface": "インターフェース全体のアニメーションの速度を制御する"
|
||||
"Control the speed of animations throughout the interface": "インターフェース全体のアニメーションの速度を制御"
|
||||
},
|
||||
"Copied to clipboard": {
|
||||
"Copied to clipboard": ""
|
||||
"Copied to clipboard": "クリップボードにコピーしました"
|
||||
},
|
||||
"Copied!": {
|
||||
"Copied!": "コピーしました!"
|
||||
@@ -321,7 +321,7 @@
|
||||
"Copy Process Name": "プロセス名をコピー"
|
||||
},
|
||||
"Corner Radius (0 = square corners)": {
|
||||
"Corner Radius (0 = square corners)": ""
|
||||
"Corner Radius (0 = square corners)": "コーナー半径(0 = 角丸なし)"
|
||||
},
|
||||
"Create Dir": {
|
||||
"Create Dir": "ディレクトリを作成"
|
||||
@@ -333,10 +333,10 @@
|
||||
"Current Items": "現在のアイテム"
|
||||
},
|
||||
"Current time and date display": {
|
||||
"Current time and date display": ""
|
||||
"Current time and date display": "現在の日時を表示"
|
||||
},
|
||||
"Current weather conditions and temperature": {
|
||||
"Current weather conditions and temperature": ""
|
||||
"Current weather conditions and temperature": "現在の天気状況と気温"
|
||||
},
|
||||
"Custom": {
|
||||
"Custom": "カスタム"
|
||||
@@ -351,7 +351,7 @@
|
||||
"Custom: ": "カスタム: "
|
||||
},
|
||||
"Customizable empty space": {
|
||||
"Customizable empty space": ""
|
||||
"Customizable empty space": "カスタマイズ可能な空きスペース"
|
||||
},
|
||||
"DEMO MODE - Click anywhere to exit": {
|
||||
"DEMO MODE - Click anywhere to exit": "デモモード -任意の場所をクリックして 終了"
|
||||
@@ -360,13 +360,13 @@
|
||||
"DMS Plugin Manager Unavailable": "DMS プラグイン マネージャーが利用できません"
|
||||
},
|
||||
"DMS_SOCKET not available": {
|
||||
"DMS_SOCKET not available": ""
|
||||
"DMS_SOCKET not available": "DMS_SOCKETが利用できません"
|
||||
},
|
||||
"Daily at:": {
|
||||
"Daily at:": "毎日:"
|
||||
},
|
||||
"Dank Bar": {
|
||||
"Dank Bar": ""
|
||||
"Dank Bar": "Dank Bar"
|
||||
},
|
||||
"Dank Bar Transparency": {
|
||||
"Dank Bar Transparency": "Dank Barの透明性"
|
||||
@@ -384,7 +384,7 @@
|
||||
"Date Format": "日付形式"
|
||||
},
|
||||
"Default": {
|
||||
"Default": ""
|
||||
"Default": "デフォルト"
|
||||
},
|
||||
"Defaults": {
|
||||
"Defaults": "デフォルト"
|
||||
@@ -405,7 +405,7 @@
|
||||
"Disk": "ディスク"
|
||||
},
|
||||
"Disk Usage": {
|
||||
"Disk Usage": ""
|
||||
"Disk Usage": "ディスク使用率"
|
||||
},
|
||||
"Display Settings": {
|
||||
"Display Settings": "表示設定"
|
||||
@@ -417,22 +417,22 @@
|
||||
"Display all priorities over fullscreen apps": "フルスクリーンアプリよりもすべての優先度を表示する"
|
||||
},
|
||||
"Display currently focused application title": {
|
||||
"Display currently focused application title": ""
|
||||
"Display currently focused application title": "現在フォーカスされているアプリケーションのタイトルを表示"
|
||||
},
|
||||
"Display volume and brightness percentage values by default in OSD popups": {
|
||||
"Display volume and brightness percentage values by default in OSD popups": ""
|
||||
"Display volume and brightness percentage values by default in OSD popups": "OSDポップアップに音量と輝度のパーセンテージ値をデフォルトで表示"
|
||||
},
|
||||
"Displays": {
|
||||
"Displays": ""
|
||||
"Displays": "表示"
|
||||
},
|
||||
"Displays the active keyboard layout and allows switching": {
|
||||
"Displays the active keyboard layout and allows switching": ""
|
||||
"Displays the active keyboard layout and allows switching": "アクティブなキーボードレイアウトを表示、切り替えを可能に"
|
||||
},
|
||||
"Do Not Disturb": {
|
||||
"Do Not Disturb": "サイレントモード"
|
||||
},
|
||||
"Dock": {
|
||||
"Dock": ""
|
||||
"Dock": "ドック"
|
||||
},
|
||||
"Dock Position": {
|
||||
"Dock Position": "ドック位置"
|
||||
@@ -441,13 +441,13 @@
|
||||
"Dock Transparency": "ドックの透明度"
|
||||
},
|
||||
"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.": ""
|
||||
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "ウィジェットをドラッグしてセクション内で順序を変更できます。目のアイコンでウィジェットを表示/非表示に(スペースは維持)、Xで完全に削除できます。"
|
||||
},
|
||||
"Dynamic Theming": {
|
||||
"Dynamic Theming": "ダイナミックテーマ"
|
||||
},
|
||||
"Edge Spacing (0 = edge-to-edge)": {
|
||||
"Edge Spacing (0 = edge-to-edge)": ""
|
||||
"Edge Spacing (0 = edge-to-edge)": "エッジ間隔(0 = エッジ・トゥ・エッジ)"
|
||||
},
|
||||
"Education": {
|
||||
"Education": "教育"
|
||||
@@ -480,7 +480,7 @@
|
||||
"Exclusive Zone Offset": "排他ゾーンオフセット"
|
||||
},
|
||||
"F1/I: Toggle • F10: Help": {
|
||||
"F1/I: Toggle • F10: Help": ""
|
||||
"F1/I: Toggle • F10: Help": "F1/I: 切り替え • F10: ヘルプ"
|
||||
},
|
||||
"Feels Like": {
|
||||
"Feels Like": "どうやら"
|
||||
@@ -498,7 +498,7 @@
|
||||
"Find in note...": "メモで検索..."
|
||||
},
|
||||
"Focused Window": {
|
||||
"Focused Window": ""
|
||||
"Focused Window": "フォーカスされたウィンドウ"
|
||||
},
|
||||
"Font Family": {
|
||||
"Font Family": "フォントファミリー"
|
||||
@@ -534,13 +534,13 @@
|
||||
"Fun": "娯楽"
|
||||
},
|
||||
"GPU": {
|
||||
"GPU": "グラフィックプロセッサ"
|
||||
"GPU": "GPU"
|
||||
},
|
||||
"GPU Temperature": {
|
||||
"GPU Temperature": ""
|
||||
"GPU Temperature": "GPU温度"
|
||||
},
|
||||
"GPU temperature display": {
|
||||
"GPU temperature display": ""
|
||||
"GPU temperature display": "GPU温度表示"
|
||||
},
|
||||
"Games": {
|
||||
"Games": "ゲーム"
|
||||
@@ -585,7 +585,7 @@
|
||||
"Hour": "時間"
|
||||
},
|
||||
"How often to change wallpaper": {
|
||||
"How often to change wallpaper": ""
|
||||
"How often to change wallpaper": "壁紙を切り替える間隔"
|
||||
},
|
||||
"Humidity": {
|
||||
"Humidity": "湿度"
|
||||
@@ -597,7 +597,7 @@
|
||||
"Icon Theme": "アイコンテーマ"
|
||||
},
|
||||
"Idle Inhibitor": {
|
||||
"Idle Inhibitor": ""
|
||||
"Idle Inhibitor": "アイドルインヒビター"
|
||||
},
|
||||
"Idle Settings": {
|
||||
"Idle Settings": "アイドル設定"
|
||||
@@ -606,7 +606,7 @@
|
||||
"Idle monitoring not supported - requires newer Quickshell version": "アイドル監視はサポートされていません - 新しい Quickshell バージョンが必要です"
|
||||
},
|
||||
"Image": {
|
||||
"Image": ""
|
||||
"Image": "画像"
|
||||
},
|
||||
"Include Transitions": {
|
||||
"Include Transitions": "トランジションを含める"
|
||||
@@ -624,10 +624,10 @@
|
||||
"Interval": "間隔"
|
||||
},
|
||||
"Invert on mode change": {
|
||||
"Invert on mode change": ""
|
||||
"Invert on mode change": "モード変更時に反転"
|
||||
},
|
||||
"Keyboard Layout Name": {
|
||||
"Keyboard Layout Name": ""
|
||||
"Keyboard Layout Name": "キーボードレイアウト名"
|
||||
},
|
||||
"Kill Process": {
|
||||
"Kill Process": "プロセスを強制終了"
|
||||
@@ -642,13 +642,13 @@
|
||||
"Launch": "起動"
|
||||
},
|
||||
"Launch Prefix": {
|
||||
"Launch Prefix": ""
|
||||
"Launch Prefix": "起動プリフィックス"
|
||||
},
|
||||
"Launch on dGPU": {
|
||||
"Launch on dGPU": "dGPUで起動"
|
||||
},
|
||||
"Launcher": {
|
||||
"Launcher": ""
|
||||
"Launcher": "ランチャー"
|
||||
},
|
||||
"Launcher Button Logo": {
|
||||
"Launcher Button Logo": "ランチャーのボタンロゴ"
|
||||
@@ -678,7 +678,7 @@
|
||||
"Log Out": "ログアウト"
|
||||
},
|
||||
"Long Text": {
|
||||
"Long Text": ""
|
||||
"Long Text": "長文"
|
||||
},
|
||||
"Longitude": {
|
||||
"Longitude": "経度"
|
||||
@@ -693,7 +693,7 @@
|
||||
"Manual Coordinates": "手動座標"
|
||||
},
|
||||
"Manual Show/Hide": {
|
||||
"Manual Show/Hide": ""
|
||||
"Manual Show/Hide": "手動で表示/非表示"
|
||||
},
|
||||
"Material Colors": {
|
||||
"Material Colors": "Material Colors"
|
||||
@@ -708,7 +708,7 @@
|
||||
"Media": "メディア"
|
||||
},
|
||||
"Media Controls": {
|
||||
"Media Controls": ""
|
||||
"Media Controls": "メディアコントロール"
|
||||
},
|
||||
"Media Player Settings": {
|
||||
"Media Player Settings": "メディアプレーヤーの設定"
|
||||
@@ -720,10 +720,10 @@
|
||||
"Memory": "メモリ"
|
||||
},
|
||||
"Memory Usage": {
|
||||
"Memory Usage": ""
|
||||
"Memory Usage": "メモリ使用率"
|
||||
},
|
||||
"Memory usage indicator": {
|
||||
"Memory usage indicator": ""
|
||||
"Memory usage indicator": "メモリ使用率インジケーター"
|
||||
},
|
||||
"Minute": {
|
||||
"Minute": "分"
|
||||
@@ -744,7 +744,7 @@
|
||||
"Mount": "マウント"
|
||||
},
|
||||
"NM not supported": {
|
||||
"NM not supported": ""
|
||||
"NM not supported": "NMが利用できません"
|
||||
},
|
||||
"Named Workspace Icons": {
|
||||
"Named Workspace Icons": "名前付きワークスペースアイコン"
|
||||
@@ -768,10 +768,10 @@
|
||||
"Network Settings": "ネットワーク設定"
|
||||
},
|
||||
"Network Speed Monitor": {
|
||||
"Network Speed Monitor": ""
|
||||
"Network Speed Monitor": "ネットワーク速度モニター"
|
||||
},
|
||||
"Network download and upload speed display": {
|
||||
"Network download and upload speed display": ""
|
||||
"Network download and upload speed display": "ネットワークのダウンロードおよびアップロード速度を表示"
|
||||
},
|
||||
"New": {
|
||||
"New": "新しい"
|
||||
@@ -813,7 +813,7 @@
|
||||
"No plugins found": "プラグインが見つかりませんでした"
|
||||
},
|
||||
"No plugins found.": {
|
||||
"No plugins found.": ""
|
||||
"No plugins found.": "プラグインが見つかりませんでした。"
|
||||
},
|
||||
"Normal Priority": {
|
||||
"Normal Priority": "通常の優先度"
|
||||
@@ -828,7 +828,7 @@
|
||||
"Nothing to see here": "ここには何もありません"
|
||||
},
|
||||
"Notification Center": {
|
||||
"Notification Center": ""
|
||||
"Notification Center": "通知センター"
|
||||
},
|
||||
"Notification Overlay": {
|
||||
"Notification Overlay": "通知オーバーレイ"
|
||||
@@ -885,19 +885,19 @@
|
||||
"Per-Monitor Workspaces": "モニターごとのワークスペース"
|
||||
},
|
||||
"Percentage": {
|
||||
"Percentage": ""
|
||||
"Percentage": "百分率"
|
||||
},
|
||||
"Personalization": {
|
||||
"Personalization": ""
|
||||
"Personalization": "パーソナライゼーション"
|
||||
},
|
||||
"Pin to Dock": {
|
||||
"Pin to Dock": ""
|
||||
"Pin to Dock": "ドックにピン留め"
|
||||
},
|
||||
"Place plugin directories here. Each plugin should have a plugin.json manifest file.": {
|
||||
"Place plugin directories here. Each plugin should have a plugin.json manifest file.": "プラグインディレクトリをここに配置します。各プラグインには plugin.json マニフェストファイルが必要です。"
|
||||
},
|
||||
"Place plugins in": {
|
||||
"Place plugins in": ""
|
||||
"Place plugins in": "プラグインを配置する場所"
|
||||
},
|
||||
"Plugin Directory": {
|
||||
"Plugin Directory": "プラグインディレクトリ"
|
||||
@@ -906,10 +906,10 @@
|
||||
"Plugin Management": "プラグイン管理"
|
||||
},
|
||||
"Plugin is disabled - enable in Plugins settings to use": {
|
||||
"Plugin is disabled - enable in Plugins settings to use": ""
|
||||
"Plugin is disabled - enable in Plugins settings to use": "プラグインは無効です - 使用するにはプラグイン設定で有効にしてください"
|
||||
},
|
||||
"Plugins": {
|
||||
"Plugins": ""
|
||||
"Plugins": "プラグイン"
|
||||
},
|
||||
"Popup Position": {
|
||||
"Popup Position": "ポップアップの位置"
|
||||
@@ -921,7 +921,7 @@
|
||||
"Position": "位置"
|
||||
},
|
||||
"Power": {
|
||||
"Power": ""
|
||||
"Power": "電源"
|
||||
},
|
||||
"Power Off": {
|
||||
"Power Off": "電源オフ"
|
||||
@@ -936,13 +936,13 @@
|
||||
"Pressure": "プレッシャー"
|
||||
},
|
||||
"Prevent screen timeout": {
|
||||
"Prevent screen timeout": ""
|
||||
"Prevent screen timeout": "画面のタイムアウトを防止"
|
||||
},
|
||||
"Primary": {
|
||||
"Primary": ""
|
||||
"Primary": "プライマリー"
|
||||
},
|
||||
"Privacy Indicator": {
|
||||
"Privacy Indicator": ""
|
||||
"Privacy Indicator": "プライバシーインジケーター"
|
||||
},
|
||||
"Process": {
|
||||
"Process": "プロセス"
|
||||
@@ -951,13 +951,13 @@
|
||||
"QML (Qt Modeling Language)": "QML (Qt モデリング言語)"
|
||||
},
|
||||
"Quick access to application launcher": {
|
||||
"Quick access to application launcher": ""
|
||||
"Quick access to application launcher": "アプリケーションランチャーへのクイックアクセス"
|
||||
},
|
||||
"Quick access to color picker": {
|
||||
"Quick access to color picker": ""
|
||||
"Quick access to color picker": "カラーピッカーへのクイックアクセス"
|
||||
},
|
||||
"Quick access to notepad": {
|
||||
"Quick access to notepad": ""
|
||||
"Quick access to notepad": "メモ帳へのクイックアクセス"
|
||||
},
|
||||
"Rain Chance": {
|
||||
"Rain Chance": "降水確率"
|
||||
@@ -969,7 +969,7 @@
|
||||
"Recent Colors": "最近の色"
|
||||
},
|
||||
"Recently Used Apps": {
|
||||
"Recently Used Apps": ""
|
||||
"Recently Used Apps": "最近使用したアプリ"
|
||||
},
|
||||
"Refresh": {
|
||||
"Refresh": "リフレッシュ"
|
||||
@@ -981,7 +981,7 @@
|
||||
"Reset": "リセット"
|
||||
},
|
||||
"Running Apps": {
|
||||
"Running Apps": ""
|
||||
"Running Apps": "実行中のアプリ"
|
||||
},
|
||||
"Running Apps Only In Current Workspace": {
|
||||
"Running Apps Only In Current Workspace": "現在のワークスペースでのみアプリを実行する"
|
||||
@@ -1020,34 +1020,34 @@
|
||||
"Search...": "検索..."
|
||||
},
|
||||
"Select Launcher Logo": {
|
||||
"Select Launcher Logo": "ランチャーロゴを選択"
|
||||
"Select Launcher Logo": "ランチャーロゴを選ぶ"
|
||||
},
|
||||
"Select a color from the palette or use custom sliders": {
|
||||
"Select a color from the palette or use custom sliders": "パレットから色を選択するか、カスタムスライダーを使用します"
|
||||
"Select a color from the palette or use custom sliders": "パレットから色を選ぶか、カスタムスライダーを使用します"
|
||||
},
|
||||
"Select a widget to add to the ": {
|
||||
"Select a widget to add to the ": "追加するウィジェットを選択してください "
|
||||
"Select a widget to add to the ": "追加するウィジェットを選ぶ "
|
||||
},
|
||||
"Select an image file...": {
|
||||
"Select an image file...": "画像ファイルを選択..."
|
||||
"Select an image file...": "画像ファイルを選ぶ..."
|
||||
},
|
||||
"Select font weight": {
|
||||
"Select font weight": ""
|
||||
"Select font weight": "フォントの太さを選ぶ"
|
||||
},
|
||||
"Select monitor to configure wallpaper": {
|
||||
"Select monitor to configure wallpaper": ""
|
||||
"Select monitor to configure wallpaper": "壁紙を設定するモニターを選ぶ"
|
||||
},
|
||||
"Select monospace font for process list and technical displays": {
|
||||
"Select monospace font for process list and technical displays": ""
|
||||
"Select monospace font for process list and technical displays": "プロセスリストと技術表示用の等幅フォントを選ぶ"
|
||||
},
|
||||
"Select system font family": {
|
||||
"Select system font family": ""
|
||||
"Select system font family": "システムフォントファミリーを選ぶ"
|
||||
},
|
||||
"Select which transitions to include in randomization": {
|
||||
"Select which transitions to include in randomization": "含めたいトランジションをランダム化に選択"
|
||||
},
|
||||
"Separator": {
|
||||
"Separator": ""
|
||||
"Separator": "区切り"
|
||||
},
|
||||
"Set different wallpapers for each connected monitor": {
|
||||
"Set different wallpapers for each connected monitor": "接続されているモニターごとに異なる壁紙を設定する"
|
||||
@@ -1077,7 +1077,7 @@
|
||||
"Show on Overview": "概要に表示"
|
||||
},
|
||||
"Show on all connected displays": {
|
||||
"Show on all connected displays": ""
|
||||
"Show on all connected displays": "すべての接続されたディスプレイに表示"
|
||||
},
|
||||
"Show on screens:": {
|
||||
"Show on screens:": "画面に表示:"
|
||||
@@ -1089,28 +1089,28 @@
|
||||
"Show weather information in top bar and control center": "トップバーとコントロールセンターに天気情報を表示"
|
||||
},
|
||||
"Shows all running applications with focus indication": {
|
||||
"Shows all running applications with focus indication": ""
|
||||
"Shows all running applications with focus indication": "実行中のすべてのアプリケーションをフォーカス状態で表示"
|
||||
},
|
||||
"Shows current workspace and allows switching": {
|
||||
"Shows current workspace and allows switching": ""
|
||||
"Shows current workspace and allows switching": "現在のワークスペースを表示、切り替えを可能に"
|
||||
},
|
||||
"Shows when microphone, camera, or screen sharing is active": {
|
||||
"Shows when microphone, camera, or screen sharing is active": ""
|
||||
"Shows when microphone, camera, or screen sharing is active": "マイク、カメラ、または画面共有がアクティブなときに表示"
|
||||
},
|
||||
"Size": {
|
||||
"Size": "サイズ"
|
||||
},
|
||||
"Size Offset": {
|
||||
"Size Offset": ""
|
||||
"Size Offset": "サイズオフセット"
|
||||
},
|
||||
"Spacer": {
|
||||
"Spacer": ""
|
||||
"Spacer": "間隔"
|
||||
},
|
||||
"Spacing": {
|
||||
"Spacing": "間隔"
|
||||
},
|
||||
"Square Corners": {
|
||||
"Square Corners": "コーナーを四角くする"
|
||||
"Square Corners": "四角コーナー"
|
||||
},
|
||||
"Start": {
|
||||
"Start": "始める"
|
||||
@@ -1125,7 +1125,7 @@
|
||||
"Storage & Disks": "ストレージとディスク"
|
||||
},
|
||||
"Surface": {
|
||||
"Surface": ""
|
||||
"Surface": "表面"
|
||||
},
|
||||
"Suspend": {
|
||||
"Suspend": "一時停止"
|
||||
@@ -1155,19 +1155,19 @@
|
||||
"System Monitoring:": "システム監視:"
|
||||
},
|
||||
"System Tray": {
|
||||
"System Tray": ""
|
||||
"System Tray": "システムトレイ"
|
||||
},
|
||||
"System Update": {
|
||||
"System Update": ""
|
||||
"System Update": "システムアップデート"
|
||||
},
|
||||
"System Updates": {
|
||||
"System Updates": "システムアップデート"
|
||||
},
|
||||
"System notification area icons": {
|
||||
"System notification area icons": ""
|
||||
"System notification area icons": "システム通知エリアアイコン"
|
||||
},
|
||||
"Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select": {
|
||||
"Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select": ""
|
||||
"Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select": "Tab/Shift+Tab: ナビゲーション • ←→↑↓: グリッドナビゲーション • Enter/Space: 選択"
|
||||
},
|
||||
"Technical Details": {
|
||||
"Technical Details": "技術的な詳細"
|
||||
@@ -1176,16 +1176,16 @@
|
||||
"Temperature": "温度"
|
||||
},
|
||||
"Text": {
|
||||
"Text": ""
|
||||
"Text": "テキスト"
|
||||
},
|
||||
"The DMS_SOCKET environment variable is not set or the socket is unavailable. Automated plugin management requires the DMS_SOCKET.": {
|
||||
"The DMS_SOCKET environment variable is not set or the socket is unavailable. Automated plugin management requires the DMS_SOCKET.": "DMS_SOCKET環境変数が設定されていないか、ソケットが利用できません。自動プラグイン管理にはDMS_SOCKETが必要です。"
|
||||
},
|
||||
"The below settings will modify your GTK and Qt settings. If you wish to preserve your current configurations, please back them up (qt5ct.conf|qt6ct.conf and ~/.config/gtk-3.0|gtk-4.0).": {
|
||||
"The below settings will modify your GTK and Qt settings. If you wish to preserve your current configurations, please back them up (qt5ct.conf|qt6ct.conf and ~/.config/gtk-3.0|gtk-4.0).": ""
|
||||
"The below settings will modify your GTK and Qt settings. If you wish to preserve your current configurations, please back them up (qt5ct.conf|qt6ct.conf and ~/.config/gtk-3.0|gtk-4.0).": "以下の設定は、GTKとQtの設定を変更します。現在の設定を維持したい場合は、バックアップを作成してください(qt5ct.conf|qt6ct.conf および ~/.config/gtk-3.0|gtk-4.0)。"
|
||||
},
|
||||
"Theme & Colors": {
|
||||
"Theme & Colors": ""
|
||||
"Theme & Colors": "テーマおよびカラー"
|
||||
},
|
||||
"Theme Color": {
|
||||
"Theme Color": "テーマカラー"
|
||||
@@ -1194,16 +1194,16 @@
|
||||
"Third-Party Plugin Warning": "サードパーティ製プラグインの警告"
|
||||
},
|
||||
"Third-party plugins are created by the community and are not officially supported by DankMaterialShell.\\n\\nThese plugins may pose security and privacy risks - install at your own risk.": {
|
||||
"Third-party plugins are created by the community and are not officially supported by DankMaterialShell.\\n\\nThese plugins may pose security and privacy risks - install at your own risk.": ""
|
||||
"Third-party plugins are created by the community and are not officially supported by DankMaterialShell.\\n\\nThese plugins may pose security and privacy risks - install at your own risk.": "サードパーティプラグインはコミュニティによって作成されており、DankMaterialShellによる公式サポートはありません。\\n\\nこれらのプラグインはセキュリティやプライバシーのリスクをもたらす可能性があります - 自己責任でインストールしてください。"
|
||||
},
|
||||
"This widget prevents GPU power off states, which can significantly impact battery life on laptops. It is not recommended to use this on laptops with hybrid graphics.": {
|
||||
"This widget prevents GPU power off states, which can significantly impact battery life on laptops. It is not recommended to use this on laptops with hybrid graphics.": ""
|
||||
"This widget prevents GPU power off states, which can significantly impact battery life on laptops. It is not recommended to use this on laptops with hybrid graphics.": "このウィジェットはGPUの省電力状態を防ぎ、ノートパソコンのバッテリー寿命に大きな影響を与える可能性があります。ハイブリッドグラフィックス搭載のノートパソコンでの使用は推奨されません。"
|
||||
},
|
||||
"This will permanently delete all clipboard history.": {
|
||||
"This will permanently delete all clipboard history.": ""
|
||||
"This will permanently delete all clipboard history.": "これはすべてのクリップボード履歴を完全に削除します。"
|
||||
},
|
||||
"Time & Date": {
|
||||
"Time & Date": ""
|
||||
"Time & Date": "時間と日付"
|
||||
},
|
||||
"Today": {
|
||||
"Today": "今日"
|
||||
@@ -1224,7 +1224,7 @@
|
||||
"Turn off monitors after": "後にモニターの電源を切る"
|
||||
},
|
||||
"Unpin from Dock": {
|
||||
"Unpin from Dock": ""
|
||||
"Unpin from Dock": "ドックから固定を解除"
|
||||
},
|
||||
"Unsaved Changes": {
|
||||
"Unsaved Changes": "保存されていない変更"
|
||||
@@ -1242,7 +1242,7 @@
|
||||
"Update All": "すべて更新"
|
||||
},
|
||||
"Use 24-hour time format instead of 12-hour AM/PM": {
|
||||
"Use 24-hour time format instead of 12-hour AM/PM": ""
|
||||
"Use 24-hour time format instead of 12-hour AM/PM": "12時間制のAM/PMではなく、24時間表記を使用"
|
||||
},
|
||||
"Use Fahrenheit": {
|
||||
"Use Fahrenheit": "華氏を使用する"
|
||||
@@ -1254,7 +1254,7 @@
|
||||
"Use Monospace Font": "等幅フォントを使用"
|
||||
},
|
||||
"Use light theme instead of dark theme": {
|
||||
"Use light theme instead of dark theme": ""
|
||||
"Use light theme instead of dark theme": "ダークテーマではなく、ライトテーマを使用"
|
||||
},
|
||||
"Use%": {
|
||||
"Use%": "使用%"
|
||||
@@ -1263,28 +1263,28 @@
|
||||
"Used": "使用済み"
|
||||
},
|
||||
"Uses sunrise/sunset times to automatically adjust night mode based on your location.": {
|
||||
"Uses sunrise/sunset times to automatically adjust night mode based on your location.": ""
|
||||
"Uses sunrise/sunset times to automatically adjust night mode based on your location.": "あなたの位置情報に基づいて、日の出と日の入りの時間を使ってナイトモードを自動調整します。"
|
||||
},
|
||||
"Utilities": {
|
||||
"Utilities": "ユーティリティ"
|
||||
},
|
||||
"VPN": {
|
||||
"VPN": ""
|
||||
"VPN": "VPN"
|
||||
},
|
||||
"VPN Connections": {
|
||||
"VPN Connections": "VPN接続"
|
||||
},
|
||||
"VPN status and quick connect": {
|
||||
"VPN status and quick connect": ""
|
||||
"VPN status and quick connect": "VPNステータスとクイック接続"
|
||||
},
|
||||
"Visibility": {
|
||||
"Visibility": "可視性"
|
||||
},
|
||||
"Visual divider between widgets": {
|
||||
"Visual divider between widgets": ""
|
||||
"Visual divider between widgets": "ウィジェット間の視覚的分離"
|
||||
},
|
||||
"Visual effect used when wallpaper changes": {
|
||||
"Visual effect used when wallpaper changes": ""
|
||||
"Visual effect used when wallpaper changes": "壁紙が変更される時に使用されるビジュアルエフェクト"
|
||||
},
|
||||
"Wallpaper": {
|
||||
"Wallpaper": "壁紙"
|
||||
@@ -1293,10 +1293,10 @@
|
||||
"Wave Progress Bars": "ウェーブプログレスバー"
|
||||
},
|
||||
"Weather": {
|
||||
"Weather": ""
|
||||
"Weather": "天気"
|
||||
},
|
||||
"Weather Widget": {
|
||||
"Weather Widget": ""
|
||||
"Weather Widget": "天気ウィジェット"
|
||||
},
|
||||
"WiFi is off": {
|
||||
"WiFi is off": "Wi-Fiはオフ中"
|
||||
@@ -1308,7 +1308,7 @@
|
||||
"Widget Styling": "ウィジェットのスタイル"
|
||||
},
|
||||
"Widgets": {
|
||||
"Widgets": ""
|
||||
"Widgets": "ウィジェット"
|
||||
},
|
||||
"Wind": {
|
||||
"Wind": "風"
|
||||
@@ -1326,7 +1326,7 @@
|
||||
"Workspace Settings": "ワークスペース設定"
|
||||
},
|
||||
"Workspace Switcher": {
|
||||
"Workspace Switcher": ""
|
||||
"Workspace Switcher": "ワークスペーススイッチャー"
|
||||
},
|
||||
"You have unsaved changes. Save before closing this tab?": {
|
||||
"You have unsaved changes. Save before closing this tab?": "保存されていない変更があります。このタブを閉じる前に保存しますか?"
|
||||
@@ -1347,7 +1347,7 @@
|
||||
"official": "公式"
|
||||
},
|
||||
"update dms for NM integration.": {
|
||||
"update dms for NM integration.": ""
|
||||
"update dms for NM integration.": "NM統合のためにDMSを更新します。"
|
||||
},
|
||||
"• Install only from trusted sources": {
|
||||
"• Install only from trusted sources": "• 信頼できるソースからのみインストールする"
|
||||
1391
translations/poexports/zh_CN.json
Normal file
1391
translations/poexports/zh_CN.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user