1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-28 23:42:51 -05:00

Compare commits

...

13 Commits

Author SHA1 Message Date
github-actions[bot]
5ddea836a1 chore: bump version to v0.6.0 2025-11-18 23:52:39 +00:00
bbedward
208d92aa06 launcher: re-create grid on open 2025-11-18 18:50:42 -05:00
bbedward
6ef9ddd4f3 hyprland: fix right click overview 2025-11-18 17:53:00 -05:00
bbedward
1c92d39185 i18n: update translations 2025-11-18 17:21:45 -05:00
bbedward
c0f072217c dankbar: split up monolithic file 2025-11-18 16:18:24 -05:00
bbedward
542562f988 dankbar: missing background click handler for plugin popout 2025-11-18 16:03:30 -05:00
bbedward
4e6f0d5e87 bluez: fix disappearing popouts with modal maanger 2025-11-18 14:36:10 -05:00
bbedward
10639a5ead re-add bound lost my qmlfmt 2025-11-17 20:53:55 -05:00
bbedward
06d668e710 launcher: new search algo
- replace fzf.js with custom levenshtein distance matching
- tweak scoring system
- more graceful fuzzy, more weight to prefixes
- basic tokenization
2025-11-17 20:52:04 -05:00
bbedward
d1472dfcba osd: also have left center and right center options 2025-11-17 14:05:04 -05:00
bbedward
ccb4da3cd8 extws: fix force option 2025-11-17 10:08:06 -05:00
bbedward
46e96b49f0 extws: fix capability check & don't show names 2025-11-17 09:50:06 -05:00
bbedward
984cfe7f98 labwc: use dms dpms off/on for idle service 2025-11-17 09:12:38 -05:00
40 changed files with 2641 additions and 2095 deletions

View File

@@ -183,6 +183,10 @@ For documentation contributions, see [DankLinux-Docs](https://github.com/AvengeM
- [soramanew](https://github.com/soramanew) - [Caelestia](https://github.com/caelestia-dots/shell) inspiration
- [end-4](https://github.com/end-4) - [dots-hyprland](https://github.com/end-4/dots-hyprland) inspiration
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=AvengeMedia/DankMaterialShell&type=date&legend=top-left)](https://www.star-history.com/#AvengeMedia/DankMaterialShell&type=date&legend=top-left)
## License
MIT License - See [LICENSE](LICENSE) for details.

View File

@@ -165,12 +165,11 @@ func (a *BluezAgent) DisplayPasskey(device dbus.ObjectPath, passkey uint32, ente
log.Infof("[BluezAgent] DisplayPasskey: device=%s, passkey=%06d, entered=%d", device, passkey, entered)
if entered == 0 {
pk := passkey
_, err := a.promptFor(device, "display-passkey", []string{}, nil)
passkeyStr := strconv.FormatUint(uint64(passkey), 10)
_, err := a.promptFor(device, "display-passkey", []string{}, &passkeyStr)
if err != nil {
log.Warnf("[BluezAgent] DisplayPasskey acknowledgment failed: %v", err)
}
_ = pk
}
return nil
@@ -179,7 +178,8 @@ func (a *BluezAgent) DisplayPasskey(device dbus.ObjectPath, passkey uint32, ente
func (a *BluezAgent) RequestConfirmation(device dbus.ObjectPath, passkey uint32) *dbus.Error {
log.Infof("[BluezAgent] RequestConfirmation: device=%s, passkey=%06d", device, passkey)
secrets, err := a.promptFor(device, "confirm", []string{"decision"}, nil)
passkeyStr := strconv.FormatUint(uint64(passkey), 10)
secrets, err := a.promptFor(device, "confirm", []string{"decision"}, &passkeyStr)
if err != nil {
log.Warnf("[BluezAgent] RequestConfirmation failed: %v", err)
return a.errorFrom(err)

View File

@@ -354,21 +354,25 @@ func (m *Manager) handleDevicePropertiesChanged(path dbus.ObjectPath, changed ma
_, hasTrusted := changed["Trusted"]
if hasPaired {
if paired, ok := pairedVar.Value().(bool); ok && paired {
devicePath := string(path)
_, wasPending := m.pendingPairings.LoadAndDelete(devicePath)
devicePath := string(path)
if paired, ok := pairedVar.Value().(bool); ok {
if paired {
_, wasPending := m.pendingPairings.LoadAndDelete(devicePath)
if wasPending {
select {
case m.eventQueue <- func() {
time.Sleep(300 * time.Millisecond)
log.Infof("[Bluetooth] Auto-connecting newly paired device: %s", devicePath)
if err := m.ConnectDevice(devicePath); err != nil {
log.Warnf("[Bluetooth] Auto-connect failed: %v", err)
if wasPending {
select {
case m.eventQueue <- func() {
time.Sleep(300 * time.Millisecond)
log.Infof("[Bluetooth] Auto-connecting newly paired device: %s", devicePath)
if err := m.ConnectDevice(devicePath); err != nil {
log.Warnf("[Bluetooth] Auto-connect failed: %v", err)
}
}:
default:
}
}:
default:
}
} else {
m.pendingPairings.Delete(devicePath)
}
}
}

View File

@@ -9,6 +9,35 @@ import (
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
)
func CheckCapability() bool {
display, err := wlclient.Connect("")
if err != nil {
return false
}
defer display.Destroy()
registry, err := display.GetRegistry()
if err != nil {
return false
}
defer registry.Destroy()
found := false
registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
if e.Interface == ext_workspace.ExtWorkspaceManagerV1InterfaceName {
found = true
}
})
// Roundtrip to ensure all registry events are processed
if err := display.Roundtrip(); err != nil {
return false
}
return found
}
func NewManager(display *wlclient.Display) (*Manager, error) {
m := &Manager{
display: display,

View File

@@ -140,8 +140,20 @@ func RouteRequest(conn net.Conn, req models.Request) {
if strings.HasPrefix(req.Method, "extworkspace.") {
if extWorkspaceManager == nil {
models.RespondError(conn, req.ID, "extworkspace manager not initialized")
return
if extWorkspaceAvailable.Load() {
extWorkspaceInitMutex.Lock()
if extWorkspaceManager == nil {
if err := InitializeExtWorkspaceManager(); err != nil {
extWorkspaceInitMutex.Unlock()
models.RespondError(conn, req.ID, "extworkspace manager not available")
return
}
}
extWorkspaceInitMutex.Unlock()
} else {
models.RespondError(conn, req.ID, "extworkspace manager not initialized")
return
}
}
extWorkspaceReq := extworkspace.Request{
ID: req.ID,

View File

@@ -63,6 +63,8 @@ var wlContext *wlcontext.SharedContext
var capabilitySubscribers syncmap.Map[string, chan ServerInfo]
var cupsSubscribers syncmap.Map[string, bool]
var cupsSubscriberCount atomic.Int32
var extWorkspaceAvailable atomic.Bool
var extWorkspaceInitMutex sync.Mutex
func getSocketDir() string {
if runtime := os.Getenv("XDG_RUNTIME_DIR"); runtime != "" {
@@ -361,7 +363,7 @@ func getCapabilities() Capabilities {
caps = append(caps, "dwl")
}
if extWorkspaceManager != nil {
if extWorkspaceAvailable.Load() {
caps = append(caps, "extworkspace")
}
@@ -411,7 +413,7 @@ func getServerInfo() ServerInfo {
caps = append(caps, "dwl")
}
if extWorkspaceManager != nil {
if extWorkspaceAvailable.Load() {
caps = append(caps, "extworkspace")
}
@@ -810,12 +812,14 @@ func handleSubscribe(conn net.Conn, req models.Request) {
}
if shouldSubscribe("extworkspace") {
if extWorkspaceManager == nil {
if err := InitializeExtWorkspaceManager(); err != nil {
log.Warnf("Failed to initialize ExtWorkspace manager for subscription: %v", err)
} else {
notifyCapabilityChange()
if extWorkspaceManager == nil && extWorkspaceAvailable.Load() {
extWorkspaceInitMutex.Lock()
if extWorkspaceManager == nil {
if err := InitializeExtWorkspaceManager(); err != nil {
log.Warnf("Failed to initialize ExtWorkspace manager for subscription: %v", err)
}
}
extWorkspaceInitMutex.Unlock()
}
if extWorkspaceManager != nil {
@@ -1248,6 +1252,14 @@ func Start(printDocs bool) error {
log.Debugf("DWL manager unavailable: %v", err)
}
if extworkspace.CheckCapability() {
extWorkspaceAvailable.Store(true)
log.Info("ExtWorkspace capability detected and will be available on subscription")
} else {
log.Debug("ExtWorkspace capability not available")
extWorkspaceAvailable.Store(false)
}
if err := InitializeWlrOutputManager(); err != nil {
log.Debugf("WlrOutput manager unavailable: %v", err)
}

View File

@@ -136,7 +136,9 @@ Singleton {
popout.currentTabIndex = tabIndex
}
ModalManager.closeAllModalsExcept(null)
if (currentPopout !== popout) {
ModalManager.closeAllModalsExcept(null)
}
TrayMenuManager.closeAllMenus()
if (justClosedSamePopout) {

View File

@@ -25,7 +25,9 @@ Singleton {
Left,
Right,
TopCenter,
BottomCenter
BottomCenter,
LeftCenter,
RightCenter
}
enum AnimationSpeed {

View File

@@ -217,6 +217,14 @@ Item {
id: polkitAuthModal
}
BluetoothPairingModal {
id: bluetoothPairingModal
Component.onCompleted: {
PopoutService.bluetoothPairingModal = bluetoothPairingModal
}
}
property string lastCredentialsToken: ""
property var lastCredentialsTime: 0

View File

@@ -18,6 +18,7 @@ DankModal {
property string passkeyInput: ""
function show(pairingData) {
console.log("BluetoothPairingModal.show() called:", JSON.stringify(pairingData))
token = pairingData.token || ""
deviceName = pairingData.deviceName || ""
deviceAddress = pairingData.deviceAddr || ""
@@ -26,6 +27,7 @@ DankModal {
pinInput = ""
passkeyInput = ""
console.log("BluetoothPairingModal: Calling open()")
open()
Qt.callLater(() => {
if (contentLoader.item) {
@@ -39,6 +41,8 @@ DankModal {
}
shouldBeVisible: false
allowStacking: true
keepPopoutsOpen: true
width: 420
height: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 240
@@ -62,8 +66,11 @@ DankModal {
}
onBackgroundClicked: () => {
DMSService.bluetoothCancelPairing(token)
if (token) {
DMSService.bluetoothCancelPairing(token)
}
close()
token = ""
pinInput = ""
passkeyInput = ""
}
@@ -80,8 +87,11 @@ DankModal {
implicitHeight: mainColumn.implicitHeight
Keys.onEscapePressed: event => {
DMSService.bluetoothCancelPairing(token)
if (token) {
DMSService.bluetoothCancelPairing(token)
}
close()
token = ""
pinInput = ""
passkeyInput = ""
event.accepted = true
@@ -110,17 +120,22 @@ DankModal {
StyledText {
text: {
if (requestType === "confirm")
switch (requestType) {
case "confirm":
return I18n.tr("Confirm passkey for ") + deviceName
if (requestType === "authorize")
case "display-passkey":
return I18n.tr("Enter this passkey on ") + deviceName
case "authorize":
return I18n.tr("Authorize pairing with ") + deviceName
if (requestType.startsWith("authorize-service"))
return I18n.tr("Authorize service for ") + deviceName
if (requestType === "pin")
case "pin":
return I18n.tr("Enter PIN for ") + deviceName
if (requestType === "passkey")
case "passkey":
return I18n.tr("Enter passkey for ") + deviceName
return deviceName
default:
if (requestType.startsWith("authorize-service"))
return I18n.tr("Authorize service for ") + deviceName
return deviceName
}
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
@@ -204,7 +219,7 @@ DankModal {
height: 56
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
visible: requestType === "confirm"
visible: requestType === "confirm" || requestType === "display-passkey"
Column {
anchors.centerIn: parent
@@ -261,8 +276,11 @@ DankModal {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
DMSService.bluetoothCancelPairing(token)
if (token) {
DMSService.bluetoothCancelPairing(token)
}
close()
token = ""
pinInput = ""
passkeyInput = ""
}
@@ -288,11 +306,17 @@ DankModal {
anchors.centerIn: parent
text: {
if (requestType === "confirm")
switch (requestType) {
case "confirm":
case "display-passkey":
return I18n.tr("Confirm")
if (requestType === "authorize" || requestType.startsWith("authorize-service"))
case "authorize":
return I18n.tr("Authorize")
return I18n.tr("Pair")
default:
if (requestType.startsWith("authorize-service"))
return I18n.tr("Authorize")
return I18n.tr("Pair")
}
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
@@ -331,8 +355,11 @@ DankModal {
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: () => {
DMSService.bluetoothCancelPairing(token)
if (token) {
DMSService.bluetoothCancelPairing(token)
}
close()
token = ""
pinInput = ""
passkeyInput = ""
}
@@ -343,12 +370,23 @@ DankModal {
function submitPairing() {
const secrets = {}
if (requestType === "pin") {
switch (requestType) {
case "pin":
secrets["pin"] = pinInput
} else if (requestType === "passkey") {
break
case "passkey":
secrets["passkey"] = passkeyInput
} else if (requestType === "confirm" || requestType === "authorize" || requestType.startsWith("authorize-service")) {
break
case "confirm":
case "display-passkey":
case "authorize":
secrets["decision"] = "yes"
break
default:
if (requestType.startsWith("authorize-service")) {
secrets["decision"] = "yes"
}
break
}
DMSService.bluetoothSubmitPairing(token, secrets, true, response => {
@@ -358,6 +396,7 @@ DankModal {
})
close()
token = ""
pinInput = ""
passkeyInput = ""
}

View File

@@ -139,7 +139,7 @@ Rectangle {
}
StyledText {
text: UserInfoService.hostname || "Linux"
text: DgopService.hostname || "DMS"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
elide: Text.ElideRight

View File

@@ -12,7 +12,9 @@ Rectangle {
function resetScroll() {
resultsList.contentY = 0
resultsGrid.contentY = 0
if (gridLoader.item) {
gridLoader.item.contentY = 0
}
}
radius: Theme.cornerRadius
@@ -92,88 +94,106 @@ Rectangle {
}
}
DankGridView {
id: resultsGrid
Loader {
id: gridLoader
property int currentIndex: appLauncher ? appLauncher.selectedIndex : -1
property int columns: appLauncher ? appLauncher.gridColumns : 4
property bool adaptiveColumns: false
property int minCellWidth: 120
property int maxCellWidth: 160
property int cellPadding: 8
property real iconSizeRatio: 0.55
property int maxIconSize: 48
property int minIconSize: 32
property bool hoverUpdatesSelection: false
property bool keyboardNavigationActive: appLauncher ? appLauncher.keyboardNavigationActive : false
property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns
property int baseCellHeight: baseCellWidth + 20
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
property int remainingSpace: width - (actualColumns * cellWidth)
signal keyboardNavigationReset
signal itemClicked(int index, var modelData)
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
function ensureVisible(index) {
if (index < 0 || index >= count)
return
const itemY = Math.floor(index / actualColumns) * cellHeight
const itemBottom = itemY + cellHeight
if (itemY < contentY)
contentY = itemY
else if (itemBottom > contentY + height)
contentY = itemBottom - height
}
property real _lastWidth: 0
anchors.fill: parent
anchors.margins: Theme.spacingS
visible: appLauncher && appLauncher.viewMode === "grid"
model: appLauncher ? appLauncher.model : null
clip: true
cellWidth: baseCellWidth
cellHeight: baseCellHeight
leftMargin: Math.max(Theme.spacingS, remainingSpace / 2)
rightMargin: leftMargin
focus: true
interactive: true
cacheBuffer: Math.max(0, Math.min(height * 2, 1000))
reuseItems: true
onCurrentIndexChanged: {
if (keyboardNavigationActive)
ensureVisible(currentIndex)
}
onItemClicked: (index, modelData) => {
if (appLauncher)
appLauncher.launchApp(modelData)
}
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
if (contextMenu)
contextMenu.show(mouseX, mouseY, modelData)
}
onKeyboardNavigationReset: () => {
if (appLauncher)
appLauncher.keyboardNavigationActive = false
}
delegate: AppLauncherGridDelegate {
gridView: resultsGrid
cellWidth: resultsGrid.cellWidth
cellHeight: resultsGrid.cellHeight
cellPadding: resultsGrid.cellPadding
minIconSize: resultsGrid.minIconSize
maxIconSize: resultsGrid.maxIconSize
iconSizeRatio: resultsGrid.iconSizeRatio
hoverUpdatesSelection: resultsGrid.hoverUpdatesSelection
keyboardNavigationActive: resultsGrid.keyboardNavigationActive
currentIndex: resultsGrid.currentIndex
onItemClicked: (idx, modelData) => resultsGrid.itemClicked(idx, modelData)
onItemRightClicked: (idx, modelData, mouseX, mouseY) => {
const modalPos = resultsContainer.parent.mapFromItem(null, mouseX, mouseY)
resultsGrid.itemRightClicked(idx, modelData, modalPos.x, modalPos.y)
active: appLauncher && appLauncher.viewMode === "grid"
onWidthChanged: {
if (visible && Math.abs(width - _lastWidth) > 1) {
_lastWidth = width
active = false
Qt.callLater(() => {
active = true
})
}
}
sourceComponent: Component {
DankGridView {
id: resultsGrid
property int currentIndex: appLauncher ? appLauncher.selectedIndex : -1
property int columns: appLauncher ? appLauncher.gridColumns : 4
property bool adaptiveColumns: false
property int minCellWidth: 120
property int maxCellWidth: 160
property int cellPadding: 8
property real iconSizeRatio: 0.55
property int maxIconSize: 48
property int minIconSize: 32
property bool hoverUpdatesSelection: false
property bool keyboardNavigationActive: appLauncher ? appLauncher.keyboardNavigationActive : false
property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns
property int baseCellHeight: baseCellWidth + 20
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
property int remainingSpace: width - (actualColumns * cellWidth)
signal keyboardNavigationReset
signal itemClicked(int index, var modelData)
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
function ensureVisible(index) {
if (index < 0 || index >= count)
return
const itemY = Math.floor(index / actualColumns) * cellHeight
const itemBottom = itemY + cellHeight
if (itemY < contentY)
contentY = itemY
else if (itemBottom > contentY + height)
contentY = itemBottom - height
}
model: appLauncher ? appLauncher.model : null
clip: true
cellWidth: baseCellWidth
cellHeight: baseCellHeight
leftMargin: Math.max(Theme.spacingS, remainingSpace / 2)
rightMargin: leftMargin
focus: true
interactive: true
cacheBuffer: Math.max(0, Math.min(height * 2, 1000))
reuseItems: true
onCurrentIndexChanged: {
if (keyboardNavigationActive)
ensureVisible(currentIndex)
}
onItemClicked: (index, modelData) => {
if (appLauncher)
appLauncher.launchApp(modelData)
}
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
if (contextMenu)
contextMenu.show(mouseX, mouseY, modelData)
}
onKeyboardNavigationReset: () => {
if (appLauncher)
appLauncher.keyboardNavigationActive = false
}
delegate: AppLauncherGridDelegate {
gridView: resultsGrid
cellWidth: resultsGrid.cellWidth
cellHeight: resultsGrid.cellHeight
cellPadding: resultsGrid.cellPadding
minIconSize: resultsGrid.minIconSize
maxIconSize: resultsGrid.maxIconSize
iconSizeRatio: resultsGrid.iconSizeRatio
hoverUpdatesSelection: resultsGrid.hoverUpdatesSelection
keyboardNavigationActive: resultsGrid.keyboardNavigationActive
currentIndex: resultsGrid.currentIndex
onItemClicked: (idx, modelData) => resultsGrid.itemClicked(idx, modelData)
onItemRightClicked: (idx, modelData, mouseX, mouseY) => {
const modalPos = resultsContainer.parent.mapFromItem(null, mouseX, mouseY)
resultsGrid.itemRightClicked(idx, modelData, modalPos.x, modalPos.y)
}
onKeyboardNavigationReset: resultsGrid.keyboardNavigationReset
}
}
onKeyboardNavigationReset: resultsGrid.keyboardNavigationReset
}
}
}

View File

@@ -29,11 +29,13 @@ Rectangle {
if (!device) return
const deviceAddr = device.address
devicesBeingPaired.add(deviceAddr)
const pairingSet = devicesBeingPaired
pairingSet.add(deviceAddr)
devicesBeingPairedChanged()
BluetoothService.pairDevice(device, function(response) {
devicesBeingPaired.delete(deviceAddr)
pairingSet.delete(deviceAddr)
devicesBeingPairedChanged()
if (response.error) {
@@ -625,15 +627,14 @@ Rectangle {
}
}
BluetoothPairingModal {
id: bluetoothPairingModal
}
Connections {
target: DMSService
function onBluetoothPairingRequest(data) {
bluetoothPairingModal.show(data)
const modal = PopoutService.bluetoothPairingModal
if (modal && modal.token !== data.token) {
modal.show(data)
}
}
}
}

View File

@@ -1,183 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
Item {
id: root
required property var barWindow
required property var axis
required property var appDrawerLoader
required property var dankDashPopoutLoader
required property var processListPopoutLoader
required property var notificationCenterLoader
required property var batteryPopoutLoader
required property var layoutPopoutLoader
required property var vpnPopoutLoader
required property var controlCenterLoader
required property var clipboardHistoryModalPopup
required property var systemUpdateLoader
required property var notepadInstance
property alias reveal: core.reveal
property alias autoHide: core.autoHide
property alias backgroundTransparency: core.backgroundTransparency
property alias hasActivePopout: core.hasActivePopout
property alias mouseArea: topBarMouseArea
Item {
id: inputMask
readonly property int barThickness: barWindow.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing)
readonly property bool showing: SettingsData.dankBarVisible && (core.reveal
|| (CompositorService.isNiri && NiriService.inOverview && SettingsData.dankBarOpenOnOverview)
|| !core.autoHide)
readonly property int maskThickness: showing ? barThickness : 1
x: {
if (!axis.isVertical) {
return 0
} else {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Left: return 0
case SettingsData.Position.Right: return parent.width - maskThickness
default: return 0
}
}
}
y: {
if (axis.isVertical) {
return 0
} else {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Top: return 0
case SettingsData.Position.Bottom: return parent.height - maskThickness
default: return 0
}
}
}
width: axis.isVertical ? maskThickness : parent.width
height: axis.isVertical ? parent.height : maskThickness
}
Region {
id: mask
item: inputMask
}
property alias maskRegion: mask
QtObject {
id: core
property real backgroundTransparency: SettingsData.dankBarTransparency
property bool autoHide: SettingsData.dankBarAutoHide
property bool revealSticky: false
property bool notepadInstanceVisible: notepadInstance?.isVisible ?? false
readonly property bool hasActivePopout: {
const loaders = [{
"loader": appDrawerLoader,
"prop": "shouldBeVisible"
}, {
"loader": dankDashPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": processListPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": notificationCenterLoader,
"prop": "shouldBeVisible"
}, {
"loader": batteryPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": layoutPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": vpnPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": controlCenterLoader,
"prop": "shouldBeVisible"
}, {
"loader": clipboardHistoryModalPopup,
"prop": "visible"
}, {
"loader": systemUpdateLoader,
"prop": "shouldBeVisible"
}]
return notepadInstanceVisible || loaders.some(item => {
if (item.loader) {
return item.loader?.item?.[item.prop]
}
return false
})
}
property bool reveal: {
if (CompositorService.isNiri && NiriService.inOverview) {
return SettingsData.dankBarOpenOnOverview
}
return SettingsData.dankBarVisible && (!autoHide || topBarMouseArea.containsMouse || hasActivePopout || revealSticky)
}
onHasActivePopoutChanged: {
if (!hasActivePopout && autoHide && !topBarMouseArea.containsMouse) {
revealSticky = true
revealHold.restart()
}
}
}
Timer {
id: revealHold
interval: 250
repeat: false
onTriggered: core.revealSticky = false
}
Connections {
function onDankBarTransparencyChanged() {
core.backgroundTransparency = SettingsData.dankBarTransparency
}
target: SettingsData
}
Connections {
target: topBarMouseArea
function onContainsMouseChanged() {
if (topBarMouseArea.containsMouse) {
core.revealSticky = true
revealHold.stop()
} else {
if (core.autoHide && !core.hasActivePopout) {
revealHold.restart()
}
}
}
}
MouseArea {
id: topBarMouseArea
y: !barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Bottom ? parent.height - height : 0) : 0
x: barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.width - width : 0) : 0
height: !barWindow.isVertical ? barWindow.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing) : undefined
width: barWindow.isVertical ? barWindow.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing) : undefined
anchors {
left: !barWindow.isVertical ? parent.left : (SettingsData.dankBarPosition === SettingsData.Position.Left ? parent.left : undefined)
right: !barWindow.isVertical ? parent.right : (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.right : undefined)
top: barWindow.isVertical ? parent.top : undefined
bottom: barWindow.isVertical ? parent.bottom : undefined
}
hoverEnabled: SettingsData.dankBarAutoHide && !core.reveal
acceptedButtons: Qt.NoButton
enabled: SettingsData.dankBarAutoHide && !core.reveal
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,539 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Shapes
import Quickshell
import Quickshell.Hyprland
import Quickshell.I3
import Quickshell.Io
import Quickshell.Services.Mpris
import Quickshell.Services.Notifications
import Quickshell.Services.SystemTray
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Modules
import qs.Modules.DankBar.Widgets
import qs.Modules.DankBar.Popouts
import qs.Services
import qs.Widgets
PanelWindow {
id: barWindow
required property var rootWindow
property var modelData: item
property var hyprlandOverviewLoader: rootWindow ? rootWindow.hyprlandOverviewLoader : null
property var controlCenterButtonRef: null
property var clockButtonRef: null
function triggerControlCenter() {
controlCenterLoader.active = true
if (!controlCenterLoader.item) {
return
}
if (controlCenterButtonRef && controlCenterLoader.item.setTriggerPosition) {
const globalPos = controlCenterButtonRef.mapToGlobal(0, 0)
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, controlCenterButtonRef.width)
const section = controlCenterButtonRef.section || "right"
controlCenterLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen)
} else {
controlCenterLoader.item.triggerScreen = barWindow.screen
}
controlCenterLoader.item.toggle()
if (controlCenterLoader.item.shouldBeVisible && NetworkService.wifiEnabled) {
NetworkService.scanWifi()
}
}
function triggerWallpaperBrowser() {
dankDashPopoutLoader.active = true
if (!dankDashPopoutLoader.item) {
return
}
if (clockButtonRef && clockButtonRef.visualContent && dankDashPopoutLoader.item.setTriggerPosition) {
const globalPos = clockButtonRef.visualContent.mapToGlobal(0, 0)
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, clockButtonRef.visualWidth)
const section = clockButtonRef.section || "center"
dankDashPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen)
} else {
dankDashPopoutLoader.item.triggerScreen = barWindow.screen
}
PopoutManager.requestPopout(dankDashPopoutLoader.item, 2)
}
readonly property var dBarLayer: {
switch (Quickshell.env("DMS_DANKBAR_LAYER")) {
case "bottom":
return WlrLayer.Bottom
case "overlay":
return WlrLayer.Overlay
case "background":
return WlrLayer.background
default:
return WlrLayer.Top
}
}
WlrLayershell.layer: dBarLayer
WlrLayershell.namespace: "dms:bar"
signal colorPickerRequested
onColorPickerRequested: rootWindow.colorPickerRequested()
property alias axis: axis
AxisContext {
id: axis
edge: {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Top:
return "top"
case SettingsData.Position.Bottom:
return "bottom"
case SettingsData.Position.Left:
return "left"
case SettingsData.Position.Right:
return "right"
default:
return "top"
}
}
}
readonly property bool isVertical: axis.isVertical
property bool gothCornersEnabled: SettingsData.dankBarGothCornersEnabled
property real wingtipsRadius: SettingsData.dankBarGothCornerRadiusOverride ? SettingsData.dankBarGothCornerRadiusValue : Theme.cornerRadius
readonly property real _wingR: Math.max(0, wingtipsRadius)
readonly property color _surfaceContainer: Theme.surfaceContainer
readonly property real _backgroundAlpha: topBarCore?.backgroundTransparency ?? SettingsData.dankBarTransparency
readonly property color _bgColor: Theme.withAlpha(_surfaceContainer, _backgroundAlpha)
readonly property real _dpr: CompositorService.getScreenScale(barWindow.screen)
property string screenName: modelData.name
readonly property int notificationCount: NotificationService.notifications.length
readonly property real effectiveBarThickness: Math.max(barWindow.widgetThickness + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding))
readonly property real widgetThickness: Math.max(20, 26 + SettingsData.dankBarInnerPadding * 0.6)
screen: modelData
implicitHeight: !isVertical ? Theme.px(effectiveBarThickness + SettingsData.dankBarSpacing + (SettingsData.dankBarGothCornersEnabled ? _wingR : 0), _dpr) : 0
implicitWidth: isVertical ? Theme.px(effectiveBarThickness + SettingsData.dankBarSpacing + (SettingsData.dankBarGothCornersEnabled ? _wingR : 0), _dpr) : 0
color: "transparent"
property var nativeInhibitor: null
Component.onCompleted: {
if (SettingsData.forceStatusBarLayoutRefresh) {
SettingsData.forceStatusBarLayoutRefresh.connect(() => {
Qt.callLater(() => {
stackContainer.visible = false
Qt.callLater(() => {
stackContainer.visible = true
})
})
})
}
updateGpuTempConfig()
inhibitorInitTimer.start()
}
Timer {
id: inhibitorInitTimer
interval: 300
repeat: false
onTriggered: {
if (SessionService.nativeInhibitorAvailable) {
createNativeInhibitor()
}
}
}
Connections {
target: PluginService
function onPluginLoaded(pluginId) {
console.info("DankBar: Plugin loaded:", pluginId)
SettingsData.widgetDataChanged()
}
function onPluginUnloaded(pluginId) {
console.info("DankBar: Plugin unloaded:", pluginId)
SettingsData.widgetDataChanged()
}
}
function updateGpuTempConfig() {
const allWidgets = [...(SettingsData.dankBarLeftWidgets || []), ...(SettingsData.dankBarCenterWidgets || []), ...(SettingsData.dankBarRightWidgets || [])]
const hasGpuTempWidget = allWidgets.some(widget => {
const widgetId = typeof widget === "string" ? widget : widget.id
const widgetEnabled = typeof widget === "string" ? true : (widget.enabled !== false)
return widgetId === "gpuTemp" && widgetEnabled
})
DgopService.gpuTempEnabled = hasGpuTempWidget || SessionData.nvidiaGpuTempEnabled || SessionData.nonNvidiaGpuTempEnabled
DgopService.nvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nvidiaGpuTempEnabled
DgopService.nonNvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nonNvidiaGpuTempEnabled
}
function createNativeInhibitor() {
if (!SessionService.nativeInhibitorAvailable) {
return
}
try {
const qmlString = `
import QtQuick
import Quickshell.Wayland
IdleInhibitor {
enabled: false
}
`
nativeInhibitor = Qt.createQmlObject(qmlString, barWindow, "DankBar.NativeInhibitor")
nativeInhibitor.window = barWindow
nativeInhibitor.enabled = Qt.binding(() => SessionService.idleInhibited)
nativeInhibitor.enabledChanged.connect(function () {
console.log("DankBar: Native inhibitor enabled changed to:", nativeInhibitor.enabled)
if (SessionService.idleInhibited !== nativeInhibitor.enabled) {
SessionService.idleInhibited = nativeInhibitor.enabled
SessionService.inhibitorChanged()
}
})
console.log("DankBar: Created native Wayland IdleInhibitor for", barWindow.screenName)
} catch (e) {
console.warn("DankBar: Failed to create native IdleInhibitor:", e)
nativeInhibitor = null
}
}
Connections {
function onDankBarLeftWidgetsChanged() {
barWindow.updateGpuTempConfig()
}
function onDankBarCenterWidgetsChanged() {
barWindow.updateGpuTempConfig()
}
function onDankBarRightWidgetsChanged() {
barWindow.updateGpuTempConfig()
}
target: SettingsData
}
Connections {
function onNvidiaGpuTempEnabledChanged() {
barWindow.updateGpuTempConfig()
}
function onNonNvidiaGpuTempEnabledChanged() {
barWindow.updateGpuTempConfig()
}
target: SessionData
}
Connections {
target: barWindow.screen
function onGeometryChanged() {
Qt.callLater(forceWidgetRefresh)
}
}
Timer {
id: refreshTimer
interval: 0
running: false
repeat: false
onTriggered: {
forceWidgetRefresh()
}
}
Connections {
target: axis
function onChanged() {
Qt.application.active
refreshTimer.restart()
}
}
anchors.top: !isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Top) : true
anchors.bottom: !isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Bottom) : true
anchors.left: !isVertical ? true : (SettingsData.dankBarPosition === SettingsData.Position.Left)
anchors.right: !isVertical ? true : (SettingsData.dankBarPosition === SettingsData.Position.Right)
exclusiveZone: (!SettingsData.dankBarVisible || topBarCore.autoHide) ? -1 : (barWindow.effectiveBarThickness + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap)
Item {
id: inputMask
readonly property int barThickness: Theme.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing, barWindow._dpr)
readonly property bool inOverviewWithShow: CompositorService.isNiri && NiriService.inOverview && SettingsData.dankBarOpenOnOverview
readonly property bool effectiveVisible: SettingsData.dankBarVisible || inOverviewWithShow
readonly property bool showing: effectiveVisible && (topBarCore.reveal || inOverviewWithShow || !topBarCore.autoHide)
readonly property int maskThickness: showing ? barThickness : 1
x: {
if (!axis.isVertical) {
return 0
} else {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Left:
return 0
case SettingsData.Position.Right:
return parent.width - maskThickness
default:
return 0
}
}
}
y: {
if (axis.isVertical) {
return 0
} else {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Top:
return 0
case SettingsData.Position.Bottom:
return parent.height - maskThickness
default:
return 0
}
}
}
width: axis.isVertical ? maskThickness : parent.width
height: axis.isVertical ? parent.height : maskThickness
}
mask: Region {
item: inputMask
}
Item {
id: topBarCore
anchors.fill: parent
layer.enabled: true
property real backgroundTransparency: SettingsData.dankBarTransparency
property bool autoHide: SettingsData.dankBarAutoHide
property bool revealSticky: false
Timer {
id: revealHold
interval: SettingsData.dankBarAutoHideDelay
repeat: false
onTriggered: topBarCore.revealSticky = false
}
property bool reveal: {
if (CompositorService.isNiri && NiriService.inOverview) {
return SettingsData.dankBarOpenOnOverview || topBarMouseArea.containsMouse || hasActivePopout || revealSticky
}
return SettingsData.dankBarVisible && (!autoHide || topBarMouseArea.containsMouse || hasActivePopout || revealSticky)
}
readonly property bool hasActivePopout: {
const loaders = [{
"loader": appDrawerLoader,
"prop": "shouldBeVisible"
}, {
"loader": dankDashPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": processListPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": notificationCenterLoader,
"prop": "shouldBeVisible"
}, {
"loader": batteryPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": layoutPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": vpnPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": controlCenterLoader,
"prop": "shouldBeVisible"
}, {
"loader": clipboardHistoryModalPopup,
"prop": "visible"
}, {
"loader": systemUpdateLoader,
"prop": "shouldBeVisible"
}]
return loaders.some(item => {
if (item.loader && item.loader.item) {
return item.loader.item[item.prop]
}
return false
}) || rootWindow.systemTrayMenuOpen
}
Connections {
function onDankBarTransparencyChanged() {
topBarCore.backgroundTransparency = SettingsData.dankBarTransparency
}
target: SettingsData
}
Connections {
target: topBarMouseArea
function onContainsMouseChanged() {
if (topBarMouseArea.containsMouse) {
topBarCore.revealSticky = true
revealHold.stop()
} else {
if (topBarCore.autoHide && !topBarCore.hasActivePopout) {
revealHold.restart()
}
}
}
}
onHasActivePopoutChanged: {
if (hasActivePopout) {
revealSticky = true
revealHold.stop()
} else if (autoHide && !topBarMouseArea.containsMouse) {
revealSticky = true
revealHold.restart()
}
}
MouseArea {
id: topBarMouseArea
y: !barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Bottom ? parent.height - height : 0) : 0
x: barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.width - width : 0) : 0
height: !barWindow.isVertical ? Theme.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing, barWindow._dpr) : undefined
width: barWindow.isVertical ? Theme.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing, barWindow._dpr) : undefined
anchors {
left: !barWindow.isVertical ? parent.left : (SettingsData.dankBarPosition === SettingsData.Position.Left ? parent.left : undefined)
right: !barWindow.isVertical ? parent.right : (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.right : undefined)
top: barWindow.isVertical ? parent.top : undefined
bottom: barWindow.isVertical ? parent.bottom : undefined
}
readonly property bool inOverview: CompositorService.isNiri && NiriService.inOverview && SettingsData.dankBarOpenOnOverview
hoverEnabled: SettingsData.dankBarAutoHide && !topBarCore.reveal && !inOverview
acceptedButtons: Qt.NoButton
enabled: SettingsData.dankBarAutoHide && !topBarCore.reveal && !inOverview
Item {
id: topBarContainer
anchors.fill: parent
transform: Translate {
id: topBarSlide
x: barWindow.isVertical ? Theme.snap(topBarCore.reveal ? 0 : (SettingsData.dankBarPosition === SettingsData.Position.Right ? barWindow.implicitWidth : -barWindow.implicitWidth), barWindow._dpr) : 0
y: !barWindow.isVertical ? Theme.snap(topBarCore.reveal ? 0 : (SettingsData.dankBarPosition === SettingsData.Position.Bottom ? barWindow.implicitHeight : -barWindow.implicitHeight), barWindow._dpr) : 0
Behavior on x {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
Behavior on y {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
}
Item {
id: barUnitInset
anchors.fill: parent
anchors.leftMargin: !barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.edge === "left" ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : 0)
anchors.rightMargin: !barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.edge === "right" ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : 0)
anchors.topMargin: barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.outerVisualEdge() === "bottom" ? 0 : Theme.px(SettingsData.dankBarSpacing, barWindow._dpr))
anchors.bottomMargin: barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.outerVisualEdge() === "bottom" ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : 0)
BarCanvas {
id: barBackground
barWindow: barWindow
axis: axis
}
MouseArea {
id: scrollArea
anchors.fill: parent
acceptedButtons: Qt.NoButton
propagateComposedEvents: true
z: -1
property real scrollAccumulator: 0
property real touchpadThreshold: 500
property bool actionInProgress: false
Timer {
id: cooldownTimer
interval: 100
onTriggered: parent.actionInProgress = false
}
onWheel: wheel => {
if (actionInProgress) {
wheel.accepted = false
return
}
const deltaY = wheel.angleDelta.y
const deltaX = wheel.angleDelta.x
if (CompositorService.isNiri && Math.abs(deltaX) > Math.abs(deltaY)) {
topBarContent.switchApp(deltaX)
wheel.accepted = false
return
}
const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0
const direction = deltaY < 0 ? 1 : -1
if (isMouseWheel) {
topBarContent.switchWorkspace(direction)
actionInProgress = true
cooldownTimer.restart()
} else {
scrollAccumulator += deltaY
if (Math.abs(scrollAccumulator) >= touchpadThreshold) {
const touchDirection = scrollAccumulator < 0 ? 1 : -1
topBarContent.switchWorkspace(touchDirection)
scrollAccumulator = 0
actionInProgress = true
cooldownTimer.restart()
}
}
wheel.accepted = false
}
}
DankBarContent {
id: topBarContent
barWindow: barWindow
rootWindow: rootWindow
}
}
}
}
}
}

View File

@@ -505,11 +505,6 @@ Item {
}
function updatePosition() {
if (!root.parentWindow) {
anchorPos = Qt.point(screen.width / 2, screen.height / 2)
return
}
const globalPos = root.mapToGlobal(0, 0)
const screenX = screen.x || 0
const screenY = screen.y || 0
@@ -958,12 +953,8 @@ Item {
}
function updatePosition() {
if (!root.parentWindow) {
anchorPos = Qt.point(screen.width / 2, screen.height / 2)
return
}
const globalPos = root.mapToGlobal(0, 0)
const targetItem = (typeof menuRoot !== "undefined" && menuRoot.anchorItem) ? menuRoot.anchorItem : root
const globalPos = targetItem.mapToGlobal(0, 0)
const screenX = screen.x || 0
const screenY = screen.y || 0
const relativeX = globalPos.x - screenX
@@ -977,12 +968,12 @@ Item {
let targetX = edge === "left"
? effectiveBarThickness + SettingsData.dankBarSpacing + Theme.popupDistance
: screen.width - (effectiveBarThickness + SettingsData.dankBarSpacing + Theme.popupDistance)
anchorPos = Qt.point(targetX, relativeY + root.height / 2)
anchorPos = Qt.point(targetX, relativeY + targetItem.height / 2)
} else {
let targetY = root.isAtBottom
? screen.height - (effectiveBarThickness + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap + Theme.popupDistance)
: effectiveBarThickness + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap + Theme.popupDistance
anchorPos = Qt.point(relativeX + root.width / 2, targetY)
anchorPos = Qt.point(relativeX + targetItem.width / 2, targetY)
}
}

View File

@@ -316,12 +316,19 @@ Item {
return [{"id": "1", "name": "1", "active": false}]
}
const visible = group.workspaces.filter(ws => !ws.hidden).sort((a, b) => {
const coordsA = a.coordinates || [0, 0]
const coordsB = b.coordinates || [0, 0]
if (coordsA[0] !== coordsB[0]) return coordsA[0] - coordsB[0]
return coordsA[1] - coordsB[1]
}).map(ws => ({
let visible = group.workspaces.filter(ws => !ws.hidden)
const hasValidCoordinates = visible.some(ws => ws.coordinates && ws.coordinates.length > 0)
if (hasValidCoordinates) {
visible = visible.sort((a, b) => {
const coordsA = a.coordinates || [0, 0]
const coordsB = b.coordinates || [0, 0]
if (coordsA[0] !== coordsB[0]) return coordsA[0] - coordsB[0]
return coordsA[1] - coordsB[1]
})
}
visible = visible.map(ws => ({
id: ws.id,
name: ws.name,
coordinates: ws.coordinates,
@@ -350,7 +357,7 @@ Item {
function getRealWorkspaces() {
return root.workspaceList.filter(ws => {
if (useExtWorkspace) return ws && ws.id !== "" && !ws.hidden
if (useExtWorkspace) return ws && (ws.id !== "" || ws.name !== "") && !ws.hidden
if (CompositorService.isHyprland) return ws && ws.id !== -1
if (CompositorService.isDwl) return ws && ws.tag !== -1
if (CompositorService.isSway) return ws && ws.num !== -1
@@ -893,7 +900,7 @@ Item {
if (isPlaceholder) return index + 1
if (root.useExtWorkspace) return modelData?.name || modelData?.id || ""
if (root.useExtWorkspace) return index + 1
if (CompositorService.isHyprland) return modelData?.id || ""
if (CompositorService.isDwl) return (modelData?.tag !== undefined) ? (modelData.tag + 1) : ""
if (CompositorService.isSway) return modelData?.num || ""

View File

@@ -6,8 +6,10 @@ import qs.Widgets
DankOSD {
id: root
osdWidth: Math.min(260, Screen.width - Theme.spacingM * 2)
osdHeight: 40 + Theme.spacingS * 2
readonly property bool useVertical: isVerticalLayout
osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2)
osdHeight: useVertical ? Math.min(260, Screen.height - Theme.spacingM * 2) : (40 + Theme.spacingS * 2)
autoHideInterval: 3000
enableMouseInteraction: true
@@ -20,8 +22,13 @@ DankOSD {
}
}
content: Item {
content: Loader {
anchors.fill: parent
sourceComponent: useVertical ? verticalContent : horizontalContent
}
Component {
id: horizontalContent
Item {
property int gap: Theme.spacingS
@@ -135,4 +142,175 @@ DankOSD {
}
}
}
Component {
id: verticalContent
Item {
anchors.fill: parent
property int gap: Theme.spacingS
Rectangle {
width: Theme.iconSize
height: Theme.iconSize
radius: Theme.iconSize / 2
color: "transparent"
anchors.horizontalCenter: parent.horizontalCenter
y: gap
DankIcon {
anchors.centerIn: parent
name: {
const deviceInfo = DisplayService.getCurrentDeviceInfo()
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") {
return "brightness_medium"
} else if (deviceInfo.name.includes("kbd")) {
return "keyboard"
} else {
return "lightbulb"
}
}
size: Theme.iconSize
color: Theme.primary
}
}
Item {
id: vertSlider
width: 12
height: parent.height - Theme.iconSize - gap * 3 - 24
anchors.horizontalCenter: parent.horizontalCenter
y: gap * 2 + Theme.iconSize
property bool dragging: false
property int value: DisplayService.brightnessAvailable ? DisplayService.brightnessLevel : 0
readonly property int minimum: {
const deviceInfo = DisplayService.getCurrentDeviceInfo()
if (!deviceInfo) return 1
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id)
if (isExponential) return 1
return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0
}
readonly property int maximum: {
const deviceInfo = DisplayService.getCurrentDeviceInfo()
if (!deviceInfo) return 100
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id)
if (isExponential) return 100
return deviceInfo.displayMax || 100
}
Rectangle {
id: vertTrack
width: parent.width
height: parent.height
anchors.centerIn: parent
color: Theme.outline
radius: Theme.cornerRadius
}
Rectangle {
id: vertFill
width: parent.width
height: {
const ratio = (vertSlider.value - vertSlider.minimum) / (vertSlider.maximum - vertSlider.minimum)
return ratio * parent.height
}
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
color: Theme.primary
radius: Theme.cornerRadius
}
Rectangle {
id: vertHandle
width: 24
height: 8
radius: Theme.cornerRadius
y: {
const ratio = (vertSlider.value - vertSlider.minimum) / (vertSlider.maximum - vertSlider.minimum)
const travel = parent.height - height
return Math.max(0, Math.min(travel, travel * (1 - ratio)))
}
anchors.horizontalCenter: parent.horizontalCenter
color: Theme.primary
border.width: 3
border.color: Theme.surfaceContainer
}
MouseArea {
id: vertSliderArea
anchors.fill: parent
anchors.margins: -12
enabled: DisplayService.brightnessAvailable
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onContainsMouseChanged: {
setChildHovered(containsMouse)
}
onPressed: mouse => {
vertSlider.dragging = true
updateBrightness(mouse)
}
onReleased: {
vertSlider.dragging = false
}
onPositionChanged: mouse => {
if (pressed) {
updateBrightness(mouse)
}
}
onClicked: mouse => {
updateBrightness(mouse)
}
function updateBrightness(mouse) {
if (DisplayService.brightnessAvailable) {
const ratio = 1.0 - (mouse.y / height)
const newValue = Math.round(vertSlider.minimum + ratio * (vertSlider.maximum - vertSlider.minimum))
DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true)
resetHideTimer()
}
}
}
Connections {
target: DisplayService
function onBrightnessChanged(showOsd) {
if (!vertSlider.dragging && vertSlider.value !== DisplayService.brightnessLevel) {
vertSlider.value = DisplayService.brightnessLevel
}
}
function onDeviceSwitched() {
if (!vertSlider.dragging && vertSlider.value !== DisplayService.brightnessLevel) {
vertSlider.value = DisplayService.brightnessLevel
}
}
}
}
StyledText {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: gap
text: {
const deviceInfo = DisplayService.getCurrentDeviceInfo()
const isExponential = deviceInfo ? SessionData.getBrightnessExponential(deviceInfo.id) : false
const unit = (deviceInfo && deviceInfo.class === "ddc" && !isExponential) ? "" : "%"
return vertSlider.value + unit
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
visible: SettingsData.osdAlwaysShowValue
}
}
}
}

View File

@@ -6,8 +6,10 @@ import qs.Widgets
DankOSD {
id: root
osdWidth: Math.min(260, Screen.width - Theme.spacingM * 2)
osdHeight: 40 + Theme.spacingS * 2
readonly property bool useVertical: isVerticalLayout
osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2)
osdHeight: useVertical ? Math.min(260, Screen.height - Theme.spacingM * 2) : (40 + Theme.spacingS * 2)
autoHideInterval: 3000
enableMouseInteraction: true
@@ -37,8 +39,13 @@ DankOSD {
}
}
content: Item {
content: Loader {
anchors.fill: parent
sourceComponent: useVertical ? verticalContent : horizontalContent
}
Component {
id: horizontalContent
Item {
property int gap: Theme.spacingS
@@ -128,11 +135,161 @@ DankOSD {
}
}
Component {
id: verticalContent
Item {
anchors.fill: parent
property int gap: Theme.spacingS
Rectangle {
width: Theme.iconSize
height: Theme.iconSize
radius: Theme.iconSize / 2
color: "transparent"
anchors.horizontalCenter: parent.horizontalCenter
y: gap
DankIcon {
anchors.centerIn: parent
name: AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.muted ? "volume_off" : "volume_up"
size: Theme.iconSize
color: muteButtonVert.containsMouse ? Theme.primary : Theme.surfaceText
}
MouseArea {
id: muteButtonVert
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
AudioService.toggleMute()
}
onContainsMouseChanged: {
setChildHovered(containsMouse || vertSliderArea.containsMouse)
}
}
}
Item {
id: vertSlider
width: 12
height: parent.height - Theme.iconSize - gap * 3 - 24
anchors.horizontalCenter: parent.horizontalCenter
y: gap * 2 + Theme.iconSize
property bool dragging: false
property int value: AudioService.sink && AudioService.sink.audio ? Math.min(100, Math.round(AudioService.sink.audio.volume * 100)) : 0
Rectangle {
id: vertTrack
width: parent.width
height: parent.height
anchors.centerIn: parent
color: Theme.outline
radius: Theme.cornerRadius
}
Rectangle {
id: vertFill
width: parent.width
height: (vertSlider.value / 100) * parent.height
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
color: Theme.primary
radius: Theme.cornerRadius
}
Rectangle {
id: vertHandle
width: 24
height: 8
radius: Theme.cornerRadius
y: {
const ratio = vertSlider.value / 100
const travel = parent.height - height
return Math.max(0, Math.min(travel, travel * (1 - ratio)))
}
anchors.horizontalCenter: parent.horizontalCenter
color: Theme.primary
border.width: 3
border.color: Theme.surfaceContainer
}
MouseArea {
id: vertSliderArea
anchors.fill: parent
anchors.margins: -12
enabled: AudioService.sink && AudioService.sink.audio
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onContainsMouseChanged: {
setChildHovered(containsMouse || muteButtonVert.containsMouse)
}
onPressed: mouse => {
vertSlider.dragging = true
updateVolume(mouse)
}
onReleased: {
vertSlider.dragging = false
}
onPositionChanged: mouse => {
if (pressed) {
updateVolume(mouse)
}
}
onClicked: mouse => {
updateVolume(mouse)
}
function updateVolume(mouse) {
if (AudioService.sink && AudioService.sink.audio) {
const ratio = 1.0 - (mouse.y / height)
const volume = Math.max(0, Math.min(100, Math.round(ratio * 100)))
AudioService.suppressOSD = true
AudioService.sink.audio.volume = volume / 100
AudioService.suppressOSD = false
resetHideTimer()
}
}
}
Connections {
target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null
function onVolumeChanged() {
if (!vertSlider.dragging) {
vertSlider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100))
}
}
}
}
StyledText {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: gap
text: vertSlider.value + "%"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
visible: SettingsData.osdAlwaysShowValue
}
}
}
onOsdShown: {
if (AudioService.sink && AudioService.sink.audio && contentLoader.item) {
const slider = contentLoader.item.children[0].children[1]
if (slider) {
slider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100))
if (AudioService.sink && AudioService.sink.audio && contentLoader.item && contentLoader.item.item) {
if (!useVertical) {
const slider = contentLoader.item.item.children[0].children[1]
if (slider && slider.value !== undefined) {
slider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100))
}
}
}
}

View File

@@ -26,6 +26,8 @@ DankPopout {
screen: triggerScreen
shouldBeVisible: false
onBackgroundClicked: close()
content: Component {
Rectangle {
id: popoutContainer

View File

@@ -760,11 +760,15 @@ Item {
return "Bottom Left"
case SettingsData.Position.BottomCenter:
return "Bottom Center"
case SettingsData.Position.LeftCenter:
return "Left Center"
case SettingsData.Position.RightCenter:
return "Right Center"
default:
return "Bottom Center"
}
}
options: ["Top Right", "Top Left", "Top Center", "Bottom Right", "Bottom Left", "Bottom Center"]
options: ["Top Right", "Top Left", "Top Center", "Bottom Right", "Bottom Left", "Bottom Center", "Left Center", "Right Center"]
onValueChanged: value => {
switch (value) {
case "Top Right":
@@ -785,6 +789,12 @@ Item {
case "Bottom Center":
SettingsData.set("osdPosition", SettingsData.Position.BottomCenter)
break
case "Left Center":
SettingsData.set("osdPosition", SettingsData.Position.LeftCenter)
break
case "Right Center":
SettingsData.set("osdPosition", SettingsData.Position.RightCenter)
break
}
}
}

View File

@@ -4,7 +4,6 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import "../Common/fzf.js" as Fzf
import qs.Common
Singleton {
@@ -12,7 +11,141 @@ Singleton {
property var applications: DesktopEntries.applications.values.filter(app => !app.noDisplay && !app.runInTerminal)
readonly property int maxResults: 10
readonly property int frecencySampleSize: 10
readonly property var timeBuckets: [{
"maxDays": 4,
"weight": 100
}, {
"maxDays": 14,
"weight": 70
}, {
"maxDays": 31,
"weight": 50
}, {
"maxDays": 90,
"weight": 30
}, {
"maxDays": 99999,
"weight": 10
}]
function tokenize(text) {
return text.toLowerCase().trim().split(/[\s\-_]+/).filter(w => w.length > 0)
}
function wordBoundaryMatch(text, query) {
const textWords = tokenize(text)
const queryWords = tokenize(query)
if (queryWords.length === 0)
return false
if (queryWords.length > textWords.length)
return false
for (var i = 0; i <= textWords.length - queryWords.length; i++) {
let allMatch = true
for (var j = 0; j < queryWords.length; j++) {
if (!textWords[i + j].startsWith(queryWords[j])) {
allMatch = false
break
}
}
if (allMatch)
return true
}
return false
}
function levenshteinDistance(s1, s2) {
const len1 = s1.length
const len2 = s2.length
const matrix = []
for (var i = 0; i <= len1; i++) {
matrix[i] = [i]
}
for (var j = 0; j <= len2; j++) {
matrix[0][j] = j
}
for (var i = 1; i <= len1; i++) {
for (var j = 1; j <= len2; j++) {
const cost = s1[i - 1] === s2[j - 1] ? 0 : 1
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost)
}
}
return matrix[len1][len2]
}
function fuzzyMatchScore(text, query) {
const queryLower = query.toLowerCase()
const maxDistance = query.length <= 2 ? 0 : query.length === 3 ? 1 : query.length <= 6 ? 2 : 3
let bestScore = 0
const distance = levenshteinDistance(text.toLowerCase(), queryLower)
if (distance <= maxDistance) {
const maxLen = Math.max(text.length, query.length)
bestScore = 1 - (distance / maxLen)
}
const words = tokenize(text)
for (const word of words) {
const wordDistance = levenshteinDistance(word, queryLower)
if (wordDistance <= maxDistance) {
const maxLen = Math.max(word.length, query.length)
const score = 1 - (wordDistance / maxLen)
bestScore = Math.max(bestScore, score)
}
}
return bestScore
}
function calculateFrecency(app) {
const usageRanking = AppUsageHistoryData.appUsageRanking || {}
const appId = app.id || (app.execString || app.exec || "")
const idVariants = [appId, appId.replace(".desktop", ""), app.id, app.id ? app.id.replace(".desktop", "") : null].filter(id => id)
let usageData = null
for (const variant of idVariants) {
if (usageRanking[variant]) {
usageData = usageRanking[variant]
break
}
}
if (!usageData || !usageData.usageCount) {
return {
"frecency": 0,
"daysSinceUsed": 999999
}
}
const usageCount = usageData.usageCount || 0
const lastUsed = usageData.lastUsed || 0
const now = Date.now()
const daysSinceUsed = (now - lastUsed) / (1000 * 60 * 60 * 24)
let timeBucketWeight = 10
for (const bucket of timeBuckets) {
if (daysSinceUsed <= bucket.maxDays) {
timeBucketWeight = bucket.weight
break
}
}
const contextBonus = 100
const sampleSize = Math.min(usageCount, frecencySampleSize)
const frecency = (timeBucketWeight * contextBonus * sampleSize) / 100
return {
"frecency": frecency,
"daysSinceUsed": daysSinceUsed
}
}
function searchApplications(query) {
if (!query || query.length === 0) {
@@ -23,7 +156,7 @@ Singleton {
const queryLower = query.toLowerCase().trim()
const scoredApps = []
const usageRanking = AppUsageHistoryData.appUsageRanking || {}
const results = []
for (const app of applications) {
const name = (app.name || "").toLowerCase()
@@ -31,123 +164,83 @@ Singleton {
const comment = (app.comment || "").toLowerCase()
const keywords = app.keywords ? app.keywords.map(k => k.toLowerCase()) : []
let score = 0
let matched = false
const nameWords = name.trim().split(/\s+/).filter(w => w.length > 0)
const containsAsWord = nameWords.includes(queryLower)
const startsWithAsWord = nameWords.some(word => word.startsWith(queryLower))
let textScore = 0
let matchType = "none"
if (name === queryLower) {
score = 10000
matched = true
} else if (containsAsWord) {
score = 9500 + (100 - Math.min(name.length, 100))
matched = true
textScore = 10000
matchType = "exact"
} else if (name.startsWith(queryLower)) {
score = 9000 + (100 - Math.min(name.length, 100))
matched = true
} else if (startsWithAsWord) {
score = 8500 + (100 - Math.min(name.length, 100))
matched = true
textScore = 5000
matchType = "prefix"
} else if (wordBoundaryMatch(name, queryLower)) {
textScore = 1000
matchType = "word_boundary"
} else if (name.includes(queryLower)) {
score = 8000 + (100 - Math.min(name.length, 100))
matched = true
} else if (keywords.length > 0) {
textScore = 500
matchType = "substring"
} else if (genericName && genericName.startsWith(queryLower)) {
textScore = 800
matchType = "generic_prefix"
} else if (genericName && genericName.includes(queryLower)) {
textScore = 400
matchType = "generic"
}
if (matchType === "none" && keywords.length > 0) {
for (const keyword of keywords) {
if (keyword === queryLower) {
score = 6000
matched = true
break
} else if (keyword.startsWith(queryLower)) {
score = 5500
matched = true
if (keyword.startsWith(queryLower)) {
textScore = 300
matchType = "keyword_prefix"
break
} else if (keyword.includes(queryLower)) {
score = 5000
matched = true
textScore = 150
matchType = "keyword"
break
}
}
}
if (!matched && genericName.includes(queryLower)) {
if (genericName === queryLower) {
score = 9000
} else if (genericName.startsWith(queryLower)) {
score = 8500
} else {
const genericWords = genericName.trim().split(/\s+/).filter(w => w.length > 0)
if (genericWords.includes(queryLower)) {
score = 8000
} else if (genericWords.some(word => word.startsWith(queryLower))) {
score = 7500
} else {
score = 7000
}
}
matched = true
} else if (!matched && comment.includes(queryLower)) {
score = 3000
matched = true
} else if (!matched) {
const nameFinder = new Fzf.Finder([app], {
"selector": a => a.name || "",
"casing": "case-insensitive",
"fuzzy": "v2"
})
const fuzzyResults = nameFinder.find(query)
if (fuzzyResults.length > 0 && fuzzyResults[0].score > 0) {
score = Math.min(fuzzyResults[0].score, 2000)
matched = true
if (matchType === "none" && comment && comment.includes(queryLower)) {
textScore = 50
matchType = "comment"
}
if (matchType === "none") {
const fuzzyScore = fuzzyMatchScore(name, queryLower)
if (fuzzyScore > 0) {
textScore = fuzzyScore * 100
matchType = "fuzzy"
}
}
if (matched) {
const appId = app.id || (app.execString || app.exec || "")
const idVariants = [
appId,
appId.replace(".desktop", ""),
app.id,
app.id ? app.id.replace(".desktop", "") : null
].filter(id => id)
if (matchType !== "none") {
const frecencyData = calculateFrecency(app)
let usageData = null
for (const variant of idVariants) {
if (usageRanking[variant]) {
usageData = usageRanking[variant]
break
}
}
if (usageData) {
const usageCount = usageData.usageCount || 0
const lastUsed = usageData.lastUsed || 0
const now = Date.now()
const daysSinceUsed = (now - lastUsed) / (1000 * 60 * 60 * 24)
let usageBonus = 0
usageBonus += Math.min(usageCount * 100, 2000)
if (daysSinceUsed < 1) {
usageBonus += 1500
} else if (daysSinceUsed < 7) {
usageBonus += 1000
} else if (daysSinceUsed < 30) {
usageBonus += 500
}
score += usageBonus
}
scoredApps.push({
"app": app,
"score": score
})
results.push({
"app": app,
"textScore": textScore,
"frecency": frecencyData.frecency,
"daysSinceUsed": frecencyData.daysSinceUsed,
"matchType": matchType
})
}
}
for (const result of results) {
const frecencyBonus = result.frecency > 0 ? Math.min(result.frecency / 10, 2000) : 0
const recencyBonus = result.daysSinceUsed < 1 ? 1500 : result.daysSinceUsed < 7 ? 1000 : result.daysSinceUsed < 30 ? 500 : 0
const finalScore = result.textScore + frecencyBonus + recencyBonus
scoredApps.push({
"app": result.app,
"score": finalScore
})
}
scoredApps.sort((a, b) => b.score - a.score)
return scoredApps.slice(0, 50).map(item => item.app)
return scoredApps.slice(0, maxResults).map(item => item.app)
}
function getCategoriesForApp(app) {
@@ -265,7 +358,8 @@ Singleton {
}
function getPluginCategoryIcon(category) {
if (typeof PluginService === "undefined") return null
if (typeof PluginService === "undefined")
return null
const launchers = PluginService.getLauncherPlugins()
for (const pluginId in launchers) {
@@ -295,7 +389,8 @@ Singleton {
}
function getPluginItems(category, query) {
if (typeof PluginService === "undefined") return []
if (typeof PluginService === "undefined")
return []
const launchers = PluginService.getLauncherPlugins()
for (const pluginId in launchers) {
@@ -313,12 +408,13 @@ Singleton {
}
const component = PluginService.pluginLauncherComponents[pluginId]
if (!component) return []
if (!component)
return []
try {
const instance = component.createObject(root, {
"pluginService": PluginService
})
"pluginService": PluginService
})
if (instance && typeof instance.getItems === "function") {
const items = instance.getItems(query || "")
@@ -337,15 +433,17 @@ Singleton {
}
function executePluginItem(item, pluginId) {
if (typeof PluginService === "undefined") return false
if (typeof PluginService === "undefined")
return false
const component = PluginService.pluginLauncherComponents[pluginId]
if (!component) return false
if (!component)
return false
try {
const instance = component.createObject(root, {
"pluginService": PluginService
})
"pluginService": PluginService
})
if (instance && typeof instance.executeItem === "function") {
instance.executeItem(item)
@@ -364,7 +462,8 @@ Singleton {
}
function searchPluginItems(query) {
if (typeof PluginService === "undefined") return []
if (typeof PluginService === "undefined")
return []
let allItems = []
const launchers = PluginService.getLauncherPlugins()

View File

@@ -49,20 +49,20 @@ Singleton {
return devices.sort((a, b) => {
const aName = a.name || a.deviceName || ""
const bName = b.name || b.deviceName || ""
const aAddr = a.address || ""
const bAddr = b.address || ""
const aHasRealName = aName.includes(" ") && aName.length > 3
const bHasRealName = bName.includes(" ") && bName.length > 3
if (aHasRealName && !bHasRealName) {
return -1
}
if (!aHasRealName && bHasRealName) {
return 1
if (aHasRealName && !bHasRealName) return -1
if (!aHasRealName && bHasRealName) return 1
if (aHasRealName && bHasRealName) {
return aName.localeCompare(bName)
}
const aSignal = (a.signalStrength !== undefined && a.signalStrength > 0) ? a.signalStrength : 0
const bSignal = (b.signalStrength !== undefined && b.signalStrength > 0) ? b.signalStrength : 0
return bSignal - aSignal
return aAddr.localeCompare(bAddr)
})
}

View File

@@ -441,6 +441,7 @@ Singleton {
if (isHyprland) return Hyprland.dispatch("dpms off")
if (isDwl) return _dwlPowerOffMonitors()
if (isSway) { try { I3.dispatch("output * dpms off") } catch(_){} return }
if (isLabwc) { Quickshell.execDetached(["dms", "dpms", "off"]) }
console.warn("CompositorService: Cannot power off monitors, unknown compositor")
}
@@ -449,6 +450,7 @@ Singleton {
if (isHyprland) return Hyprland.dispatch("dpms on")
if (isDwl) return _dwlPowerOnMonitors()
if (isSway) { try { I3.dispatch("output * dpms on") } catch(_){} return }
if (isLabwc) { Quickshell.execDetached(["dms", "dpms", "on"]) }
console.warn("CompositorService: Cannot power on monitors, unknown compositor")
}

View File

@@ -78,10 +78,15 @@ Singleton {
}
}
const oldValue = deviceBrightness[device.id]
const newBrightness = Object.assign({}, deviceBrightness)
newBrightness[device.id] = displayValue
deviceBrightness = newBrightness
brightnessVersion++
if (oldValue !== undefined && oldValue !== displayValue && brightnessInitialized) {
brightnessChanged(true)
}
}
function updateFromBrightnessState(state) {
@@ -110,9 +115,12 @@ Singleton {
deviceMaxCache = newMaxCache
const newBrightness = {}
let anyDeviceBrightnessChanged = false
for (const device of state.devices) {
const isExponential = SessionData.getBrightnessExponential(device.id)
const userSetValue = deviceBrightnessUserSet[device.id]
const oldValue = deviceBrightness[device.id]
if (isExponential) {
if (userSetValue !== undefined) {
@@ -123,6 +131,11 @@ Singleton {
} else {
newBrightness[device.id] = device.currentPercent
}
const newValue = newBrightness[device.id]
if (oldValue !== undefined && oldValue !== newValue) {
anyDeviceBrightnessChanged = true
}
}
deviceBrightness = newBrightness
brightnessVersion++
@@ -142,9 +155,15 @@ Singleton {
}
}
const shouldShowOsd = brightnessInitialized && anyDeviceBrightnessChanged
if (!brightnessInitialized) {
brightnessInitialized = true
}
if (shouldShowOsd) {
brightnessChanged(true)
}
}
function setBrightness(percentage, device, suppressOsd) {

View File

@@ -46,8 +46,17 @@ Singleton {
const hasExtWorkspace = DMSService.capabilities.includes("extworkspace")
if (hasExtWorkspace && !extWorkspaceAvailable) {
if (typeof CompositorService !== "undefined") {
const useExtWorkspace = DMSService.forceExtWorkspace || (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isDwl && !CompositorService.isSway)
if (!useExtWorkspace) {
console.info("ExtWorkspaceService: ext-workspace available but compositor has native support")
extWorkspaceAvailable = false
return
}
}
extWorkspaceAvailable = true
console.info("ExtWorkspaceService: ext-workspace capability detected")
DMSService.addSubscription("extworkspace")
requestState()
} else if (!hasExtWorkspace) {
extWorkspaceAvailable = false
@@ -181,12 +190,17 @@ Singleton {
function getVisibleWorkspaces(outputName) {
const workspaces = getWorkspacesForOutput(outputName)
const visible = workspaces.filter(ws => !ws.hidden).sort((a, b) => {
const coordsA = a.coordinates || [0, 0]
const coordsB = b.coordinates || [0, 0]
if (coordsA[0] !== coordsB[0]) return coordsA[0] - coordsB[0]
return coordsA[1] - coordsB[1]
})
let visible = workspaces.filter(ws => !ws.hidden)
const hasValidCoordinates = visible.some(ws => ws.coordinates && ws.coordinates.length > 0)
if (hasValidCoordinates) {
visible = visible.sort((a, b) => {
const coordsA = a.coordinates || [0, 0]
const coordsB = b.coordinates || [0, 0]
if (coordsA[0] !== coordsB[0]) return coordsA[0] - coordsB[0]
return coordsA[1] - coordsB[1]
})
}
const cacheKey = outputName
if (!_cachedWorkspaces[cacheKey]) {

View File

@@ -23,6 +23,7 @@ Singleton {
property var colorPickerModal: null
property var notificationModal: null
property var wifiPasswordModal: null
property var bluetoothPairingModal: null
property var networkInfoModal: null
property var notepadSlideouts: []

View File

@@ -1 +1 @@
v0.5.2
v0.6.0

View File

@@ -75,6 +75,8 @@ PanelWindow {
readonly property real alignedWidth: Theme.px(osdWidth, dpr)
readonly property real alignedHeight: Theme.px(osdHeight, dpr)
readonly property bool isVerticalLayout: SettingsData.osdPosition === SettingsData.Position.LeftCenter || SettingsData.osdPosition === SettingsData.Position.RightCenter
readonly property real barThickness: {
if (!SettingsData.dankBarVisible) return 0
const widgetThickness = Math.max(20, 26 + SettingsData.dankBarInnerPadding * 0.6)
@@ -86,6 +88,16 @@ PanelWindow {
return barThickness + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap
}
readonly property real dockThickness: {
if (!SettingsData.showDock) return 0
return SettingsData.dockIconSize + SettingsData.dockSpacing * 2 + 10
}
readonly property real dockOffset: {
if (!SettingsData.showDock || SettingsData.dockAutoHide) return 0
return dockThickness + SettingsData.dockSpacing + SettingsData.dockBottomGap + SettingsData.dockMargin
}
readonly property real alignedX: {
const margin = Theme.spacingM
const centerX = (screenWidth - alignedWidth) / 2
@@ -93,12 +105,22 @@ PanelWindow {
switch (SettingsData.osdPosition) {
case SettingsData.Position.Left:
case SettingsData.Position.Bottom:
const leftOffset = SettingsData.dankBarPosition === SettingsData.Position.Left ? barOffset : 0
return Theme.snap(margin + leftOffset, dpr)
const leftBarOffset = SettingsData.dankBarPosition === SettingsData.Position.Left ? barOffset : 0
const leftDockOffset = SettingsData.dockPosition === SettingsData.Position.Left ? dockOffset : 0
return Theme.snap(margin + Math.max(leftBarOffset, leftDockOffset), dpr)
case SettingsData.Position.Top:
case SettingsData.Position.Right:
const rightOffset = SettingsData.dankBarPosition === SettingsData.Position.Right ? barOffset : 0
return Theme.snap(screenWidth - alignedWidth - margin - rightOffset, dpr)
const rightBarOffset = SettingsData.dankBarPosition === SettingsData.Position.Right ? barOffset : 0
const rightDockOffset = SettingsData.dockPosition === SettingsData.Position.Right ? dockOffset : 0
return Theme.snap(screenWidth - alignedWidth - margin - Math.max(rightBarOffset, rightDockOffset), dpr)
case SettingsData.Position.LeftCenter:
const leftCenterBarOffset = SettingsData.dankBarPosition === SettingsData.Position.Left ? barOffset : 0
const leftCenterDockOffset = SettingsData.dockPosition === SettingsData.Position.Left ? dockOffset : 0
return Theme.snap(margin + Math.max(leftCenterBarOffset, leftCenterDockOffset), dpr)
case SettingsData.Position.RightCenter:
const rightCenterBarOffset = SettingsData.dankBarPosition === SettingsData.Position.Right ? barOffset : 0
const rightCenterDockOffset = SettingsData.dockPosition === SettingsData.Position.Right ? dockOffset : 0
return Theme.snap(screenWidth - alignedWidth - margin - Math.max(rightCenterBarOffset, rightCenterDockOffset), dpr)
case SettingsData.Position.TopCenter:
case SettingsData.Position.BottomCenter:
default:
@@ -108,19 +130,25 @@ PanelWindow {
readonly property real alignedY: {
const margin = Theme.spacingM
const centerY = (screenHeight - alignedHeight) / 2
switch (SettingsData.osdPosition) {
case SettingsData.Position.Top:
case SettingsData.Position.Left:
case SettingsData.Position.TopCenter:
const topOffset = SettingsData.dankBarPosition === SettingsData.Position.Top ? barOffset : 0
return Theme.snap(margin + topOffset, dpr)
const topBarOffset = SettingsData.dankBarPosition === SettingsData.Position.Top ? barOffset : 0
const topDockOffset = SettingsData.dockPosition === SettingsData.Position.Top ? dockOffset : 0
return Theme.snap(margin + Math.max(topBarOffset, topDockOffset), dpr)
case SettingsData.Position.Right:
case SettingsData.Position.Bottom:
case SettingsData.Position.BottomCenter:
const bottomBarOffset = SettingsData.dankBarPosition === SettingsData.Position.Bottom ? barOffset : 0
const bottomDockOffset = SettingsData.dockPosition === SettingsData.Position.Bottom ? dockOffset : 0
return Theme.snap(screenHeight - alignedHeight - margin - Math.max(bottomBarOffset, bottomDockOffset), dpr)
case SettingsData.Position.LeftCenter:
case SettingsData.Position.RightCenter:
default:
const bottomOffset = SettingsData.dankBarPosition === SettingsData.Position.Bottom ? barOffset : 0
return Theme.snap(screenHeight - alignedHeight - margin - bottomOffset, dpr)
return Theme.snap(centerY, dpr)
}
}

View File

@@ -122,7 +122,7 @@
{
"term": "All",
"context": "All",
"reference": "Services/AppSearchService.qml:217, Services/AppSearchService.qml:233, Modals/Spotlight/SpotlightModal.qml:61, Modules/AppDrawer/CategorySelector.qml:11, Modules/AppDrawer/AppLauncher.qml:16, Modules/AppDrawer/AppLauncher.qml:27, Modules/AppDrawer/AppLauncher.qml:28, Modules/AppDrawer/AppLauncher.qml:45, Modules/AppDrawer/AppLauncher.qml:46, Modules/AppDrawer/AppLauncher.qml:80, Modules/AppDrawer/AppDrawerPopout.qml:46",
"reference": "Services/AppSearchService.qml:310, Services/AppSearchService.qml:326, Modals/Spotlight/SpotlightModal.qml:61, Modules/AppDrawer/CategorySelector.qml:11, Modules/AppDrawer/AppLauncher.qml:16, Modules/AppDrawer/AppLauncher.qml:27, Modules/AppDrawer/AppLauncher.qml:28, Modules/AppDrawer/AppLauncher.qml:45, Modules/AppDrawer/AppLauncher.qml:46, Modules/AppDrawer/AppLauncher.qml:80, Modules/AppDrawer/AppDrawerPopout.qml:46",
"comment": ""
},
{
@@ -254,7 +254,7 @@
{
"term": "Audio Codec",
"context": "Audio Codec",
"reference": "Modules/ControlCenter/Details/BluetoothDetail.qml:570",
"reference": "Modules/ControlCenter/Details/BluetoothDetail.qml:572",
"comment": ""
},
{
@@ -302,19 +302,19 @@
{
"term": "Authorize",
"context": "Authorize",
"reference": "Modals/BluetoothPairingModal.qml:294",
"reference": "Modals/BluetoothPairingModal.qml:314, Modals/BluetoothPairingModal.qml:317",
"comment": ""
},
{
"term": "Authorize pairing with ",
"context": "Authorize pairing with ",
"reference": "Modals/BluetoothPairingModal.qml:116",
"reference": "Modals/BluetoothPairingModal.qml:129",
"comment": ""
},
{
"term": "Authorize service for ",
"context": "Authorize service for ",
"reference": "Modals/BluetoothPairingModal.qml:118",
"reference": "Modals/BluetoothPairingModal.qml:136",
"comment": ""
},
{
@@ -446,7 +446,7 @@
{
"term": "Back",
"context": "Back",
"reference": "Modules/DankBar/Widgets/SystemTrayBar.qml:1191",
"reference": "Modules/DankBar/Widgets/SystemTrayBar.qml:1182",
"comment": ""
},
{
@@ -482,7 +482,7 @@
{
"term": "Bluetooth Settings",
"context": "Bluetooth Settings",
"reference": "Modules/ControlCenter/Details/BluetoothDetail.qml:71",
"reference": "Modules/ControlCenter/Details/BluetoothDetail.qml:73",
"comment": ""
},
{
@@ -548,7 +548,7 @@
{
"term": "Brightness OSD",
"context": "Brightness OSD",
"reference": "Modules/Settings/WidgetTweaksTab.qml:805",
"reference": "Modules/Settings/WidgetTweaksTab.qml:815",
"comment": ""
},
{
@@ -614,7 +614,7 @@
{
"term": "Cancel",
"context": "Cancel",
"reference": "Modals/BluetoothPairingModal.qml:251, Modals/PolkitAuthModal.qml:291, Modals/WifiPasswordModal.qml:494, Modals/FileBrowser/FileBrowserOverwriteDialog.qml:83, Modules/Settings/PluginBrowser.qml:650",
"reference": "Modals/BluetoothPairingModal.qml:266, Modals/PolkitAuthModal.qml:291, Modals/WifiPasswordModal.qml:494, Modals/FileBrowser/FileBrowserOverwriteDialog.qml:83, Modules/Settings/PluginBrowser.qml:650",
"comment": ""
},
{
@@ -632,7 +632,7 @@
{
"term": "Caps Lock OSD",
"context": "Caps Lock OSD",
"reference": "Modules/Settings/WidgetTweaksTab.qml:835",
"reference": "Modules/Settings/WidgetTweaksTab.qml:845",
"comment": ""
},
{
@@ -848,7 +848,7 @@
{
"term": "Confirm",
"context": "Confirm",
"reference": "Modals/BluetoothPairingModal.qml:292",
"reference": "Modals/BluetoothPairingModal.qml:312",
"comment": ""
},
{
@@ -860,7 +860,7 @@
{
"term": "Confirm passkey for ",
"context": "Confirm passkey for ",
"reference": "Modals/BluetoothPairingModal.qml:114",
"reference": "Modals/BluetoothPairingModal.qml:125",
"comment": ""
},
{
@@ -1190,7 +1190,7 @@
{
"term": "Development",
"context": "Development",
"reference": "Services/AppSearchService.qml:161, Services/AppSearchService.qml:162, Services/AppSearchService.qml:163, Widgets/DankIconPicker.qml:31",
"reference": "Services/AppSearchService.qml:254, Services/AppSearchService.qml:255, Services/AppSearchService.qml:256, Widgets/DankIconPicker.qml:31",
"comment": ""
},
{
@@ -1202,7 +1202,7 @@
{
"term": "Device paired",
"context": "Device paired",
"reference": "Modules/ControlCenter/Details/BluetoothDetail.qml:42",
"reference": "Modules/ControlCenter/Details/BluetoothDetail.qml:44",
"comment": ""
},
{
@@ -1394,7 +1394,7 @@
{
"term": "Education",
"context": "Education",
"reference": "Services/AppSearchService.qml:164",
"reference": "Services/AppSearchService.qml:257",
"comment": ""
},
{
@@ -1466,19 +1466,19 @@
{
"term": "Enter 6-digit passkey",
"context": "Enter 6-digit passkey",
"reference": "Modals/BluetoothPairingModal.qml:190",
"reference": "Modals/BluetoothPairingModal.qml:205",
"comment": ""
},
{
"term": "Enter PIN",
"context": "Enter PIN",
"reference": "Modals/BluetoothPairingModal.qml:155",
"reference": "Modals/BluetoothPairingModal.qml:170",
"comment": ""
},
{
"term": "Enter PIN for ",
"context": "Enter PIN for ",
"reference": "Modals/BluetoothPairingModal.qml:120",
"reference": "Modals/BluetoothPairingModal.qml:131",
"comment": ""
},
{
@@ -1514,7 +1514,7 @@
{
"term": "Enter passkey for ",
"context": "Enter passkey for ",
"reference": "Modals/BluetoothPairingModal.qml:122",
"reference": "Modals/BluetoothPairingModal.qml:133",
"comment": ""
},
{
@@ -1523,6 +1523,12 @@
"reference": "Modals/WifiPasswordModal.qml:200, Modals/WifiPasswordModal.qml:202",
"comment": ""
},
{
"term": "Enter this passkey on ",
"context": "Enter this passkey on ",
"reference": "Modals/BluetoothPairingModal.qml:127",
"comment": ""
},
{
"term": "Error",
"context": "Error",
@@ -1610,7 +1616,7 @@
{
"term": "Failed to remove device",
"context": "Failed to remove device",
"reference": "Modules/ControlCenter/Details/BluetoothDetail.qml:617",
"reference": "Modules/ControlCenter/Details/BluetoothDetail.qml:619",
"comment": ""
},
{
@@ -1724,7 +1730,7 @@
{
"term": "Forget Device",
"context": "Forget Device",
"reference": "Modules/ControlCenter/Details/BluetoothDetail.qml:595",
"reference": "Modules/ControlCenter/Details/BluetoothDetail.qml:597",
"comment": ""
},
{
@@ -1772,7 +1778,7 @@
{
"term": "Games",
"context": "Games",
"reference": "Services/AppSearchService.qml:165",
"reference": "Services/AppSearchService.qml:258",
"comment": ""
},
{
@@ -1814,7 +1820,7 @@
{
"term": "Graphics",
"context": "Graphics",
"reference": "Services/AppSearchService.qml:166, Services/AppSearchService.qml:167",
"reference": "Services/AppSearchService.qml:259, Services/AppSearchService.qml:260",
"comment": ""
},
{
@@ -1940,7 +1946,7 @@
{
"term": "Idle Inhibitor OSD",
"context": "Idle Inhibitor OSD",
"reference": "Modules/Settings/WidgetTweaksTab.qml:815",
"reference": "Modules/Settings/WidgetTweaksTab.qml:825",
"comment": ""
},
{
@@ -2012,7 +2018,7 @@
{
"term": "Internet",
"context": "Internet",
"reference": "Services/AppSearchService.qml:168, Services/AppSearchService.qml:169, Services/AppSearchService.qml:170",
"reference": "Services/AppSearchService.qml:261, Services/AppSearchService.qml:262, Services/AppSearchService.qml:263",
"comment": ""
},
{
@@ -2306,7 +2312,7 @@
{
"term": "Media",
"context": "Media",
"reference": "Services/AppSearchService.qml:158, Services/AppSearchService.qml:159, Services/AppSearchService.qml:160, Widgets/DankIconPicker.qml:37, Modules/DankDash/DankDashPopout.qml:210",
"reference": "Services/AppSearchService.qml:251, Services/AppSearchService.qml:252, Services/AppSearchService.qml:253, Widgets/DankIconPicker.qml:37, Modules/DankDash/DankDashPopout.qml:210",
"comment": ""
},
{
@@ -2378,7 +2384,7 @@
{
"term": "Microphone Mute OSD",
"context": "Microphone Mute OSD",
"reference": "Modules/Settings/WidgetTweaksTab.qml:825",
"reference": "Modules/Settings/WidgetTweaksTab.qml:835",
"comment": ""
},
{
@@ -2564,7 +2570,7 @@
{
"term": "No Bluetooth adapter found",
"context": "No Bluetooth adapter found",
"reference": "Modules/ControlCenter/Details/BluetoothDetail.qml:519",
"reference": "Modules/ControlCenter/Details/BluetoothDetail.qml:521",
"comment": ""
},
{
@@ -2642,7 +2648,7 @@
{
"term": "Notepad",
"context": "Notepad",
"reference": "DMSShell.qml:413, Modules/Settings/DankBarTab.qml:191",
"reference": "DMSShell.qml:421, Modules/Settings/DankBarTab.qml:191",
"comment": ""
},
{
@@ -2726,7 +2732,7 @@
{
"term": "Office",
"context": "Office",
"reference": "Services/AppSearchService.qml:171, Services/AppSearchService.qml:172, Services/AppSearchService.qml:173, Services/AppSearchService.qml:174",
"reference": "Services/AppSearchService.qml:264, Services/AppSearchService.qml:265, Services/AppSearchService.qml:266, Services/AppSearchService.qml:267",
"comment": ""
},
{
@@ -2828,25 +2834,25 @@
{
"term": "Pair",
"context": "Pair",
"reference": "Modals/BluetoothPairingModal.qml:295",
"reference": "Modals/BluetoothPairingModal.qml:318",
"comment": ""
},
{
"term": "Pair Bluetooth Device",
"context": "Pair Bluetooth Device",
"reference": "Modals/BluetoothPairingModal.qml:105",
"reference": "Modals/BluetoothPairingModal.qml:115",
"comment": ""
},
{
"term": "Pairing failed",
"context": "Pairing failed",
"reference": "Modals/BluetoothPairingModal.qml:356, Modules/ControlCenter/Details/BluetoothDetail.qml:40",
"reference": "Modals/BluetoothPairingModal.qml:394, Modules/ControlCenter/Details/BluetoothDetail.qml:42",
"comment": ""
},
{
"term": "Passkey:",
"context": "Passkey:",
"reference": "Modals/BluetoothPairingModal.qml:214",
"reference": "Modals/BluetoothPairingModal.qml:229",
"comment": ""
},
{
@@ -3044,7 +3050,7 @@
{
"term": "Power Profile OSD",
"context": "Power Profile OSD",
"reference": "Modules/Settings/WidgetTweaksTab.qml:845",
"reference": "Modules/Settings/WidgetTweaksTab.qml:855",
"comment": ""
},
{
@@ -3344,7 +3350,7 @@
{
"term": "Science",
"context": "Science",
"reference": "Services/AppSearchService.qml:175",
"reference": "Services/AppSearchService.qml:268",
"comment": ""
},
{
@@ -3494,7 +3500,7 @@
{
"term": "Settings",
"context": "Settings",
"reference": "Services/AppSearchService.qml:176, Modals/Settings/SettingsModal.qml:205, Modules/DankDash/DankDashPopout.qml:225",
"reference": "Services/AppSearchService.qml:269, Modals/Settings/SettingsModal.qml:205, Modules/DankDash/DankDashPopout.qml:225",
"comment": ""
},
{
@@ -3620,37 +3626,37 @@
{
"term": "Show on-screen display when brightness changes",
"context": "Show on-screen display when brightness changes",
"reference": "Modules/Settings/WidgetTweaksTab.qml:806",
"reference": "Modules/Settings/WidgetTweaksTab.qml:816",
"comment": ""
},
{
"term": "Show on-screen display when caps lock state changes",
"context": "Show on-screen display when caps lock state changes",
"reference": "Modules/Settings/WidgetTweaksTab.qml:836",
"reference": "Modules/Settings/WidgetTweaksTab.qml:846",
"comment": ""
},
{
"term": "Show on-screen display when idle inhibitor state changes",
"context": "Show on-screen display when idle inhibitor state changes",
"reference": "Modules/Settings/WidgetTweaksTab.qml:816",
"reference": "Modules/Settings/WidgetTweaksTab.qml:826",
"comment": ""
},
{
"term": "Show on-screen display when microphone is muted/unmuted",
"context": "Show on-screen display when microphone is muted/unmuted",
"reference": "Modules/Settings/WidgetTweaksTab.qml:826",
"reference": "Modules/Settings/WidgetTweaksTab.qml:836",
"comment": ""
},
{
"term": "Show on-screen display when power profile changes",
"context": "Show on-screen display when power profile changes",
"reference": "Modules/Settings/WidgetTweaksTab.qml:846",
"reference": "Modules/Settings/WidgetTweaksTab.qml:856",
"comment": ""
},
{
"term": "Show on-screen display when volume changes",
"context": "Show on-screen display when volume changes",
"reference": "Modules/Settings/WidgetTweaksTab.qml:796",
"reference": "Modules/Settings/WidgetTweaksTab.qml:806",
"comment": ""
},
{
@@ -3872,7 +3878,7 @@
{
"term": "System",
"context": "System",
"reference": "Services/AppSearchService.qml:177, Widgets/DankIconPicker.qml:40, Modules/ProcessList/SystemTab.qml:130",
"reference": "Services/AppSearchService.qml:270, Widgets/DankIconPicker.qml:40, Modules/ProcessList/SystemTab.qml:130",
"comment": ""
},
{
@@ -4280,7 +4286,7 @@
{
"term": "Utilities",
"context": "Utilities",
"reference": "Services/AppSearchService.qml:178, Services/AppSearchService.qml:179, Services/AppSearchService.qml:180, Services/AppSearchService.qml:181",
"reference": "Services/AppSearchService.qml:271, Services/AppSearchService.qml:272, Services/AppSearchService.qml:273, Services/AppSearchService.qml:274",
"comment": ""
},
{
@@ -4364,7 +4370,7 @@
{
"term": "Volume OSD",
"context": "Volume OSD",
"reference": "Modules/Settings/WidgetTweaksTab.qml:795",
"reference": "Modules/Settings/WidgetTweaksTab.qml:805",
"comment": ""
},
{

View File

@@ -57,7 +57,7 @@
"Add a VPN in NetworkManager": "Aggiungi una VPN in NetworkManager"
},
"Adjust the number of columns in grid view mode.": {
"Adjust the number of columns in grid view mode.": ""
"Adjust the number of columns in grid view mode.": "Regola il numero di colonne nella modalità di visualizzazione a griglia."
},
"All": {
"All": "Tutto"
@@ -306,7 +306,7 @@
"CUPS Missing Filter Warning": "Avviso Filtro Mancante CUPS"
},
"Camera": {
"Camera": ""
"Camera": "Fotocamera"
},
"Cancel": {
"Cancel": "Annulla"
@@ -687,7 +687,7 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "Trascina i widget per riordinarli nelle sezioni. Usa l'icona occhio per nascondere/mostrare i widget (mantenendo la spaziatura), o X per rimuoverli completamente."
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": ""
"Duplicate Wallpaper with Blur": "Duplica Sfondo con Sfocatura"
},
"Duration": {
"Duration": "Durata"
@@ -764,6 +764,9 @@
"Enter password for ": {
"Enter password for ": "Inserisci password per"
},
"Enter this passkey on ": {
"Enter this passkey on ": ""
},
"Error": {
"Error": "Errore"
},
@@ -915,7 +918,7 @@
"Grid": "Griglia"
},
"Grid Columns": {
"Grid Columns": ""
"Grid Columns": "Colonne Griglia"
},
"Group by App": {
"Group by App": "Raggruppa per App"
@@ -1191,7 +1194,7 @@
"Memory usage indicator": "Indicatore uso memoria"
},
"Microphone": {
"Microphone": ""
"Microphone": "Microfono"
},
"Microphone Mute OSD": {
"Microphone Mute OSD": ""
@@ -1209,7 +1212,7 @@
"Mode: ": "Modalità:"
},
"Model": {
"Model": ""
"Model": "Modello"
},
"Monitor Selection:": {
"Monitor Selection:": "Selezione Monitor:"
@@ -1236,7 +1239,7 @@
"NM not supported": "NM non supportato"
},
"Name": {
"Name": ""
"Name": "Nome"
},
"Named Workspace Icons": {
"Named Workspace Icons": "Icone Workspace con Nome"
@@ -1683,7 +1686,7 @@
"Science": "Scienza"
},
"Screen sharing": {
"Screen sharing": ""
"Screen sharing": "Condivisione schermo"
},
"Scrolling": {
"Scrolling": "Scorrimento"

View File

@@ -348,7 +348,7 @@
"Choose where notification popups appear on screen": "通知ポップアップが画面に表示される場所を選ぶ"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": ""
"Choose where on-screen displays appear on screen": "OSDの表示する場所を選んでください"
},
"Clear": {
"Clear": "クリア"
@@ -627,7 +627,7 @@
"Dismiss": "解除"
},
"Display Name Format": {
"Display Name Format": ""
"Display Name Format": "名称形式を表示"
},
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": {
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "画面の上、下、左、右の端に配置できる、ピン留めされた実行中のアプリケーションを含むドックを表示します"
@@ -687,7 +687,7 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "ウィジェットをドラッグしてセクション内で順序を変更できます。目のアイコンでウィジェットを表示/非表示にスペースは維持、Xで完全に削除できます。"
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": ""
"Duplicate Wallpaper with Blur": "ぼかしで壁紙を複製"
},
"Duration": {
"Duration": "期間"
@@ -764,6 +764,9 @@
"Enter password for ": {
"Enter password for ": "パスワードを入力"
},
"Enter this passkey on ": {
"Enter this passkey on ": "ここでパスキーを入力してください "
},
"Error": {
"Error": "エラー"
},
@@ -1209,7 +1212,7 @@
"Mode: ": "モード: "
},
"Model": {
"Model": ""
"Model": "モデル"
},
"Monitor Selection:": {
"Monitor Selection:": "モニターの選択:"
@@ -1236,7 +1239,7 @@
"NM not supported": "NMが利用できません"
},
"Name": {
"Name": ""
"Name": "名称"
},
"Named Workspace Icons": {
"Named Workspace Icons": "名前付きワークスペースアイコン"
@@ -1365,7 +1368,7 @@
"OS Logo": "OSロゴ"
},
"OSD Position": {
"OSD Position": ""
"OSD Position": "OSD位置"
},
"Office": {
"Office": "オフィス"

View File

@@ -348,7 +348,7 @@
"Choose where notification popups appear on screen": "Wybierz, gdzie na ekranie mają pojawiać się powiadomienia"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": ""
"Choose where on-screen displays appear on screen": "Wybierz miejsce wyświetlania informacji na ekranie"
},
"Clear": {
"Clear": "Wyczyść"
@@ -627,7 +627,7 @@
"Dismiss": "Odrzuć"
},
"Display Name Format": {
"Display Name Format": ""
"Display Name Format": "Format nazwy wyświetlanej"
},
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": {
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "Wyświetla dok z przypiętymi i uruchomionymi aplikacjami, który można umieścić na górze, na dole, po lewej lub po prawej stronie ekranu."
@@ -687,7 +687,7 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "Przeciągnij widżety, aby zmienić kolejność w sekcjach. Użyj ikony oka, aby ukryć/pokazać widżety (zachowując odstępy), lub X, aby je całkowicie usunąć."
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": ""
"Duplicate Wallpaper with Blur": "Powiel tapetę z rozmyciem"
},
"Duration": {
"Duration": "Czas trwania"
@@ -764,6 +764,9 @@
"Enter password for ": {
"Enter password for ": "Wprowadź hasło dla "
},
"Enter this passkey on ": {
"Enter this passkey on ": ""
},
"Error": {
"Error": "Błąd"
},
@@ -1209,7 +1212,7 @@
"Mode: ": "Tryb: "
},
"Model": {
"Model": ""
"Model": "Model"
},
"Monitor Selection:": {
"Monitor Selection:": "Wybór monitora:"
@@ -1236,7 +1239,7 @@
"NM not supported": "NM nie jest obsługiwany"
},
"Name": {
"Name": ""
"Name": "Nazwa"
},
"Named Workspace Icons": {
"Named Workspace Icons": "Ikony nazwanych obszarów roboczych"
@@ -1365,7 +1368,7 @@
"OS Logo": "Logo OS"
},
"OSD Position": {
"OSD Position": ""
"OSD Position": "Pozycja OSD"
},
"Office": {
"Office": "Biuro"

View File

@@ -764,6 +764,9 @@
"Enter password for ": {
"Enter password for ": "Insira senha para "
},
"Enter this passkey on ": {
"Enter this passkey on ": ""
},
"Error": {
"Error": ""
},

View File

@@ -276,7 +276,7 @@
"Brightness": "Parlaklık"
},
"Brightness OSD": {
"Brightness OSD": ""
"Brightness OSD": "Parlaklık OSD"
},
"Browse": {
"Browse": "Göz at"
@@ -318,7 +318,7 @@
"Caps Lock Indicator": "Caps Lock Göstergesi"
},
"Caps Lock OSD": {
"Caps Lock OSD": ""
"Caps Lock OSD": "Caps Lock OSD"
},
"Center Section": {
"Center Section": "Orta Bölüm"
@@ -348,7 +348,7 @@
"Choose where notification popups appear on screen": "Bildirim açılır pencerelerinin ekranda nerede görüneceğini seçin"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": ""
"Choose where on-screen displays appear on screen": "Ekran gösterimlerinin ekranda nerede gösterileceğini seç"
},
"Clear": {
"Clear": "Temizle"
@@ -627,7 +627,7 @@
"Dismiss": "Reddet"
},
"Display Name Format": {
"Display Name Format": ""
"Display Name Format": "Ekran İsim Formatı"
},
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": {
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "Ekranın üst, alt, sol veya sağ kenarına yerleştirilebilen, sabitlenmiş ve çalışan uygulamaları içeren bir dock görüntüleyin."
@@ -687,7 +687,7 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "Widget'ları sürükleyerek bölümler içinde yeniden sıralayın. Göz simgesini kullanarak widget'ları gizleyin/gösterin (aralıkları korur) veya X simgesini kullanarak tamamen kaldırın."
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": ""
"Duplicate Wallpaper with Blur": "Duvar kağıdını bulanıklık ile çoğalt"
},
"Duration": {
"Duration": "Süre"
@@ -764,6 +764,9 @@
"Enter password for ": {
"Enter password for ": "Parolayı girin "
},
"Enter this passkey on ": {
"Enter this passkey on ": ""
},
"Error": {
"Error": "Hata"
},
@@ -975,7 +978,7 @@
"Idle Inhibitor": "Boşta Kalma Engelleyici"
},
"Idle Inhibitor OSD": {
"Idle Inhibitor OSD": ""
"Idle Inhibitor OSD": "Boşta Kalma Engelleyici OSD"
},
"Idle Settings": {
"Idle Settings": "Boşta Kalma Ayarları"
@@ -1194,7 +1197,7 @@
"Microphone": "Mikrofon"
},
"Microphone Mute OSD": {
"Microphone Mute OSD": ""
"Microphone Mute OSD": "Mikrofon Sessiz OSD"
},
"Minimal palette built around a single hue.": {
"Minimal palette built around a single hue.": "Tek bir renk tonu etrafında oluşturulmuş minimal palet."
@@ -1209,7 +1212,7 @@
"Mode: ": "Mod: "
},
"Model": {
"Model": ""
"Model": "Model"
},
"Monitor Selection:": {
"Monitor Selection:": "Monitör Seçimi:"
@@ -1236,7 +1239,7 @@
"NM not supported": "NM desteklenmiyor"
},
"Name": {
"Name": ""
"Name": "İsim"
},
"Named Workspace Icons": {
"Named Workspace Icons": "Adlandırılmış Çalışma Alanı Simgeleri"
@@ -1365,7 +1368,7 @@
"OS Logo": "OS Logo"
},
"OSD Position": {
"OSD Position": ""
"OSD Position": "OSD Pozisyonu"
},
"Office": {
"Office": "Ofis"
@@ -1377,7 +1380,7 @@
"On-Screen Displays": "Ekran Üstü Gösterimler"
},
"On-screen Displays": {
"On-screen Displays": ""
"On-screen Displays": "Ekran Gösterimleri"
},
"Only adjust gamma based on time or location rules.": {
"Only adjust gamma based on time or location rules.": "Gamayı yalnızca zaman veya konum kurallarına göre ayarlayın."
@@ -1527,7 +1530,7 @@
"Power Profile Degradation": "Güç Profili Dejenerasyonu"
},
"Power Profile OSD": {
"Power Profile OSD": ""
"Power Profile OSD": "Güç Profili OSD"
},
"Pressure": {
"Pressure": "Basınç"
@@ -1818,22 +1821,22 @@
"Show on screens:": "Şu ekranda göster:"
},
"Show on-screen display when brightness changes": {
"Show on-screen display when brightness changes": ""
"Show on-screen display when brightness changes": "Parlaklık değiştiğinde ekran gösterimi göster"
},
"Show on-screen display when caps lock state changes": {
"Show on-screen display when caps lock state changes": ""
"Show on-screen display when caps lock state changes": "Caps lock durumu değiştiğinde ekran gösterimi göster"
},
"Show on-screen display when idle inhibitor state changes": {
"Show on-screen display when idle inhibitor state changes": ""
"Show on-screen display when idle inhibitor state changes": "Boşta kalma engelleyici durumu değiştiğinde ekran gösterimi göster"
},
"Show on-screen display when microphone is muted/unmuted": {
"Show on-screen display when microphone is muted/unmuted": ""
"Show on-screen display when microphone is muted/unmuted": "Mikrofon sessize alındığında/sessizden çıkarıldığında ekran gösterimi göster"
},
"Show on-screen display when power profile changes": {
"Show on-screen display when power profile changes": ""
"Show on-screen display when power profile changes": "Güç profili değiştiğinde ekran gösterimi göster"
},
"Show on-screen display when volume changes": {
"Show on-screen display when volume changes": ""
"Show on-screen display when volume changes": "Ses değiştiğinde ekran gösterimi göster"
},
"Show only apps running in current workspace": {
"Show only apps running in current workspace": "Yalnızca mevcut çalışma alanında çalışan uygulamaları göster"
@@ -2196,7 +2199,7 @@
"Volume Changed": "Ses Seviyesi Değişti"
},
"Volume OSD": {
"Volume OSD": ""
"Volume OSD": "Ses OSD"
},
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "Ses, parlaklık ve diğer sistem ekran üstü gösterimleri"

View File

@@ -348,7 +348,7 @@
"Choose where notification popups appear on screen": "设置通知弹窗的出现位置"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": ""
"Choose where on-screen displays appear on screen": "选择OSD在屏幕上出现的位置"
},
"Clear": {
"Clear": "清除"
@@ -627,7 +627,7 @@
"Dismiss": "忽略"
},
"Display Name Format": {
"Display Name Format": ""
"Display Name Format": "显示名称格式"
},
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": {
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "显示一个包含固定和运行中应用的程序坞,可放置在屏幕四边任意位置"
@@ -687,7 +687,7 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "拖动组件以在各区块内重新排序。点击眼睛图标可隐藏/显示组件(保留间距),点击 X 可彻底移除。"
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": ""
"Duplicate Wallpaper with Blur": "带模糊效果的壁纸复本"
},
"Duration": {
"Duration": "持续时间"
@@ -764,6 +764,9 @@
"Enter password for ": {
"Enter password for ": "输入密码"
},
"Enter this passkey on ": {
"Enter this passkey on ": "在该处输入此通行密钥 "
},
"Error": {
"Error": "错误"
},
@@ -1209,7 +1212,7 @@
"Mode: ": "模式: "
},
"Model": {
"Model": ""
"Model": "模型"
},
"Monitor Selection:": {
"Monitor Selection:": "选择显示器:"
@@ -1236,7 +1239,7 @@
"NM not supported": "不支持 NM"
},
"Name": {
"Name": ""
"Name": "名称"
},
"Named Workspace Icons": {
"Named Workspace Icons": "已命名工作区图标"
@@ -1365,7 +1368,7 @@
"OS Logo": "系统 Logo"
},
"OSD Position": {
"OSD Position": ""
"OSD Position": "OSD位置"
},
"Office": {
"Office": "办公"

View File

@@ -57,7 +57,7 @@
"Add a VPN in NetworkManager": "新增VPN至網路管理器"
},
"Adjust the number of columns in grid view mode.": {
"Adjust the number of columns in grid view mode.": ""
"Adjust the number of columns in grid view mode.": "調整網格檢視模式中的欄數。"
},
"All": {
"All": "所有"
@@ -75,7 +75,7 @@
"Always Show OSD Percentage": "OSD 始終顯示百分比"
},
"Always on icons": {
"Always on icons": ""
"Always on icons": "始終顯示圖示"
},
"Always show a minimum of 3 workspaces, even if fewer are available": {
"Always show a minimum of 3 workspaces, even if fewer are available": "始終顯示至少 3 個工作區,即使可用的工作區較少"
@@ -276,7 +276,7 @@
"Brightness": "亮度"
},
"Brightness OSD": {
"Brightness OSD": ""
"Brightness OSD": "亮度 OSD"
},
"Browse": {
"Browse": "瀏覽"
@@ -306,7 +306,7 @@
"CUPS Missing Filter Warning": "CUPS 缺少過濾器警告"
},
"Camera": {
"Camera": ""
"Camera": "相機"
},
"Cancel": {
"Cancel": "取消"
@@ -315,10 +315,10 @@
"Capacity": "容量"
},
"Caps Lock Indicator": {
"Caps Lock Indicator": ""
"Caps Lock Indicator": "大小寫鎖定指示器"
},
"Caps Lock OSD": {
"Caps Lock OSD": ""
"Caps Lock OSD": "大小寫鎖定 OSD"
},
"Center Section": {
"Center Section": "中間區塊"
@@ -348,7 +348,7 @@
"Choose where notification popups appear on screen": "選擇通知彈出視窗在螢幕上出現的位置"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": ""
"Choose where on-screen displays appear on screen": "選擇螢幕顯示出現在螢幕上的位置"
},
"Clear": {
"Clear": "清除"
@@ -627,7 +627,7 @@
"Dismiss": "忽略"
},
"Display Name Format": {
"Display Name Format": ""
"Display Name Format": "顯示名稱格式"
},
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": {
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "顯示一個帶有固定和正在運行的應用程式的 Dock這些應用程式可以放置在螢幕的頂部、底部、左側或右側邊緣"
@@ -645,7 +645,7 @@
"Display currently focused application title": "顯示目前焦點應用程式的標題"
},
"Display power menu actions in a grid instead of a list": {
"Display power menu actions in a grid instead of a list": ""
"Display power menu actions in a grid instead of a list": "以網格而非列表顯示電源選單操作"
},
"Display settings for ": {
"Display settings for ": "顯示設定適用於"
@@ -687,7 +687,7 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "拖曳部件即可在版塊內重新排序。使用眼睛圖示隱藏/顯示部件 (會保持間距),或使用 X 將其完全移除。"
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": ""
"Duplicate Wallpaper with Blur": "模糊化重複桌布"
},
"Duration": {
"Duration": "持續時間"
@@ -764,6 +764,9 @@
"Enter password for ": {
"Enter password for ": "輸入密碼 "
},
"Enter this passkey on ": {
"Enter this passkey on ": ""
},
"Error": {
"Error": "錯誤"
},
@@ -915,7 +918,7 @@
"Grid": "網格"
},
"Grid Columns": {
"Grid Columns": ""
"Grid Columns": "網格欄數"
},
"Group by App": {
"Group by App": "App 分組"
@@ -939,7 +942,7 @@
"Hibernate": "休眠"
},
"Hide Delay (ms)": {
"Hide Delay (ms)": ""
"Hide Delay (ms)": "隱藏延遲 (毫秒)"
},
"Hide the dock when not in use and reveal it when hovering near the dock area": {
"Hide the dock when not in use and reveal it when hovering near the dock area": "不使用時隱藏 Dock並在 Dock 區域附近懸停時顯示 Dock"
@@ -975,7 +978,7 @@
"Idle Inhibitor": "空閒抑制器"
},
"Idle Inhibitor OSD": {
"Idle Inhibitor OSD": ""
"Idle Inhibitor OSD": "閒置抑制器 OSD"
},
"Idle Settings": {
"Idle Settings": "閒置設定"
@@ -1191,10 +1194,10 @@
"Memory usage indicator": "記憶體使用率指示器"
},
"Microphone": {
"Microphone": ""
"Microphone": "麥克風"
},
"Microphone Mute OSD": {
"Microphone Mute OSD": ""
"Microphone Mute OSD": "麥克風靜音 OSD"
},
"Minimal palette built around a single hue.": {
"Minimal palette built around a single hue.": "圍繞單一色調構建的最小調色板。"
@@ -1209,7 +1212,7 @@
"Mode: ": "模式:"
},
"Model": {
"Model": ""
"Model": "型號"
},
"Monitor Selection:": {
"Monitor Selection:": "螢幕選擇:"
@@ -1236,7 +1239,7 @@
"NM not supported": "NM 不支援"
},
"Name": {
"Name": ""
"Name": "名稱"
},
"Named Workspace Icons": {
"Named Workspace Icons": "命名工作區圖示"
@@ -1365,7 +1368,7 @@
"OS Logo": "發行版 Logo"
},
"OSD Position": {
"OSD Position": ""
"OSD Position": "OSD 位置"
},
"Office": {
"Office": "辦公"
@@ -1377,7 +1380,7 @@
"On-Screen Displays": "螢幕顯示"
},
"On-screen Displays": {
"On-screen Displays": ""
"On-screen Displays": "螢幕顯示"
},
"Only adjust gamma based on time or location rules.": {
"Only adjust gamma based on time or location rules.": "僅根據時間或位置規則調整 gamma。"
@@ -1527,7 +1530,7 @@
"Power Profile Degradation": "電源配置降級"
},
"Power Profile OSD": {
"Power Profile OSD": ""
"Power Profile OSD": "電源設定檔 OSD"
},
"Pressure": {
"Pressure": "氣壓"
@@ -1683,7 +1686,7 @@
"Science": "科學"
},
"Screen sharing": {
"Screen sharing": ""
"Screen sharing": "螢幕分享"
},
"Scrolling": {
"Scrolling": "滾動"
@@ -1818,22 +1821,22 @@
"Show on screens:": "在螢幕上顯示:"
},
"Show on-screen display when brightness changes": {
"Show on-screen display when brightness changes": ""
"Show on-screen display when brightness changes": "亮度改變時顯示螢幕顯示"
},
"Show on-screen display when caps lock state changes": {
"Show on-screen display when caps lock state changes": ""
"Show on-screen display when caps lock state changes": "大小寫鎖定狀態改變時顯示螢幕顯示"
},
"Show on-screen display when idle inhibitor state changes": {
"Show on-screen display when idle inhibitor state changes": ""
"Show on-screen display when idle inhibitor state changes": "閒置抑制器狀態改變時顯示螢幕顯示"
},
"Show on-screen display when microphone is muted/unmuted": {
"Show on-screen display when microphone is muted/unmuted": ""
"Show on-screen display when microphone is muted/unmuted": "麥克風靜音/取消靜音時顯示螢幕顯示"
},
"Show on-screen display when power profile changes": {
"Show on-screen display when power profile changes": ""
"Show on-screen display when power profile changes": "電源設定檔改變時顯示螢幕顯示"
},
"Show on-screen display when volume changes": {
"Show on-screen display when volume changes": ""
"Show on-screen display when volume changes": "音量改變時顯示螢幕顯示"
},
"Show only apps running in current workspace": {
"Show only apps running in current workspace": "僅顯示目前工作區中正在執行的應用程式"
@@ -1863,7 +1866,7 @@
"Shows current workspace and allows switching": "顯示目前工作區並允許切換"
},
"Shows when caps lock is active": {
"Shows when caps lock is active": ""
"Shows when caps lock is active": "顯示大小寫鎖定啟用時"
},
"Shows when microphone, camera, or screen sharing is active": {
"Shows when microphone, camera, or screen sharing is active": "顯示麥克風、攝影機或螢幕共用處於活動狀態"
@@ -2109,7 +2112,7 @@
"Use Fahrenheit instead of Celsius for temperature": "使用華氏度代替攝氏度來表示溫度"
},
"Use Grid Layout": {
"Use Grid Layout": ""
"Use Grid Layout": "使用網格佈局"
},
"Use IP Location": {
"Use IP Location": "使用 IP 位置"
@@ -2196,7 +2199,7 @@
"Volume Changed": "音量改變"
},
"Volume OSD": {
"Volume OSD": ""
"Volume OSD": "音量 OSD"
},
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "音量、亮度及其他系統OSD"

View File

@@ -1777,6 +1777,13 @@
"reference": "",
"comment": ""
},
{
"term": "Enter this passkey on ",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Error",
"translation": "",