mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-06-04 03:06:57 +00:00
Merge pull request #386 from luleyleo/filtered-taskbar
Taskbar: Filter by screen and workspace
This commit is contained in:
@@ -1017,6 +1017,16 @@
|
||||
"description": "Verwenden Sie ein Leerzeichen, um jeden Teil in eine neue Zeile zu trennen."
|
||||
},
|
||||
"preview": "Vorschau"
|
||||
},
|
||||
"taskbar": {
|
||||
"only-active-workspaces": {
|
||||
"label": "Nur von aktiven Arbeitsbereichen",
|
||||
"description": "Zeige nur Apps von aktiven Arbeitsbereichen an."
|
||||
},
|
||||
"only-same-output": {
|
||||
"label": "Nur vom gleichen Bildschirm",
|
||||
"description": "Zeige nur Apps vom dem Bildschirm an, wo sich die Taskbar befindet."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1000,6 +1000,16 @@
|
||||
"description": "Use a space to separate each part onto a new line."
|
||||
},
|
||||
"preview": "Preview"
|
||||
},
|
||||
"taskbar": {
|
||||
"only-active-workspaces": {
|
||||
"label": "Only from active workspaces",
|
||||
"description": "Show only apps from active workspaces."
|
||||
},
|
||||
"only-same-output": {
|
||||
"label": "Only from same output",
|
||||
"description": "Show only apps from the output where the bar is located."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -12,10 +12,27 @@ Rectangle {
|
||||
id: root
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string section: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
readonly property bool compact: (Settings.data.bar.density === "compact")
|
||||
readonly property real itemSize: compact ? Style.capsuleHeight * 0.9 * scaling : Style.capsuleHeight * 0.8 * scaling
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
// Always visible when there are toplevels
|
||||
implicitWidth: isVerticalBar ? Math.round(Style.capsuleHeight * scaling) : taskbarLayout.implicitWidth + Style.marginM * scaling * 2
|
||||
@@ -41,12 +58,13 @@ Rectangle {
|
||||
columnSpacing: isVerticalBar ? 0 : Style.marginXXS * root.scaling
|
||||
|
||||
Repeater {
|
||||
model: ToplevelManager && ToplevelManager.toplevels ? ToplevelManager.toplevels : []
|
||||
model: CompositorService.windows
|
||||
delegate: Item {
|
||||
id: taskbarItem
|
||||
required property Toplevel modelData
|
||||
property Toplevel toplevel: modelData
|
||||
property bool isActive: ToplevelManager.activeToplevel === modelData
|
||||
required property var modelData
|
||||
|
||||
visible: (!widgetSettings.onlySameOutput || modelData.output == screen.name)
|
||||
&& (!widgetSettings.onlyActiveWorkspaces || CompositorService.getActiveWorkspaces().map(ws => ws.id).includes(modelData.workspaceId))
|
||||
|
||||
Layout.preferredWidth: root.itemSize
|
||||
Layout.preferredHeight: root.itemSize
|
||||
@@ -57,7 +75,7 @@ Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
color: taskbarItem.isActive ? Color.mPrimary : root.color
|
||||
color: modelData.isFocused ? Color.mPrimary : root.color
|
||||
border.width: 0
|
||||
radius: Math.round(Style.radiusXS * root.scaling)
|
||||
border.color: Color.transparent
|
||||
@@ -86,13 +104,13 @@ Rectangle {
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
try {
|
||||
taskbarItem.modelData.activate()
|
||||
CompositorService.focusWindow(taskbarItem.modelData.id)
|
||||
} catch (error) {
|
||||
Logger.error("Taskbar", "Failed to activate toplevel: " + error)
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
try {
|
||||
taskbarItem.modelData.close()
|
||||
CompositorService.closeWindow(taskbarItem.modelData.id)
|
||||
} catch (error) {
|
||||
Logger.error("Taskbar", "Failed to close toplevel: " + error)
|
||||
}
|
||||
|
||||
@@ -133,7 +133,8 @@ Popup {
|
||||
"Spacer": "WidgetSettings/SpacerSettings.qml",
|
||||
"SystemMonitor": "WidgetSettings/SystemMonitorSettings.qml",
|
||||
"Volume": "WidgetSettings/VolumeSettings.qml",
|
||||
"Workspace": "WidgetSettings/WorkspaceSettings.qml"
|
||||
"Workspace": "WidgetSettings/WorkspaceSettings.qml",
|
||||
"Taskbar": "WidgetSettings/TaskbarSettings.qml"
|
||||
}
|
||||
|
||||
const source = widgetSettingsMap[widgetId]
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property bool valueOnlyActiveWorkspaces: widgetData.onlyActiveWorkspaces !== undefined ? widgetData.onlyActiveWorkspaces : widgetMetadata.onlyActiveWorkspaces
|
||||
property bool valueOnlySameOutput: widgetData.onlySameOutput !== undefined ? widgetData.onlySameOutput : widgetMetadata.onlySameOutput
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.onlySameOutput = valueOnlySameOutput
|
||||
settings.onlyActiveWorkspaces = valueOnlyActiveWorkspaces
|
||||
console.log(JSON.stringify(settings))
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.widget-settings.taskbar.only-same-output.label")
|
||||
description: I18n.tr("bar.widget-settings.taskbar.only-same-output.description")
|
||||
checked: root.valueOnlySameOutput
|
||||
onToggled: checked => root.valueOnlySameOutput = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.widget-settings.taskbar.only-active-workspaces.label")
|
||||
description: I18n.tr("bar.widget-settings.taskbar.only-active-workspaces.description")
|
||||
checked: root.valueOnlyActiveWorkspaces
|
||||
onToggled: checked => root.valueOnlyActiveWorkspaces = checked
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,11 @@ Singleton {
|
||||
"showNetworkStats": false,
|
||||
"showDiskUsage": false
|
||||
},
|
||||
"Taskbar": {
|
||||
"allowUserSettings": true,
|
||||
"onlySameOutput": true,
|
||||
"onlyActiveWorkspaces": true
|
||||
},
|
||||
"Workspace": {
|
||||
"allowUserSettings": true,
|
||||
"labelMode": "index",
|
||||
|
||||
@@ -14,7 +14,7 @@ Singleton {
|
||||
|
||||
// Generic workspace and window data
|
||||
property ListModel workspaces: ListModel {}
|
||||
property var windows: []
|
||||
property ListModel windows: ListModel {}
|
||||
property int focusedWindowIndex: -1
|
||||
|
||||
// Generic events
|
||||
@@ -82,10 +82,17 @@ Singleton {
|
||||
workspaceChanged()
|
||||
})
|
||||
|
||||
backend.activeWindowChanged.connect(activeWindowChanged)
|
||||
backend.activeWindowChanged.connect(() => {
|
||||
// Sync active window when it changes
|
||||
// TODO: Avoid re-syncing all windows
|
||||
syncWindows()
|
||||
// Forward the signal
|
||||
activeWindowChanged()
|
||||
})
|
||||
|
||||
backend.windowListChanged.connect(() => {
|
||||
// Sync windows when they change
|
||||
windows = backend.windows
|
||||
syncWindows()
|
||||
// Forward the signal
|
||||
windowListChanged()
|
||||
})
|
||||
@@ -97,7 +104,7 @@ Singleton {
|
||||
|
||||
// Initial sync
|
||||
syncWorkspaces()
|
||||
windows = backend.windows
|
||||
syncWindows()
|
||||
focusedWindowIndex = backend.focusedWindowIndex
|
||||
}
|
||||
|
||||
@@ -110,10 +117,20 @@ Singleton {
|
||||
// Emit signal to notify listeners that workspace list has been updated
|
||||
workspacesChanged()
|
||||
}
|
||||
|
||||
function syncWindows() {
|
||||
windows.clear()
|
||||
const ws = backend.windows
|
||||
for (var i = 0; i < ws.length; i++) {
|
||||
windows.append(ws[i])
|
||||
}
|
||||
// Emit signal to notify listeners that workspace list has been updated
|
||||
windowListChanged()
|
||||
}
|
||||
|
||||
// Get window title for focused window
|
||||
function getFocusedWindowTitle() {
|
||||
if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.length) {
|
||||
if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.count) {
|
||||
return windows[focusedWindowIndex].title || ""
|
||||
}
|
||||
return ""
|
||||
@@ -138,14 +155,44 @@ Singleton {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Get active workspaces
|
||||
function getActiveWorkspaces() {
|
||||
const activeWorkspaces = []
|
||||
for (var i = 0; i < workspaces.count; i++) {
|
||||
const ws = workspaces.get(i)
|
||||
if (ws.isActive) {
|
||||
activeWorkspaces.push(ws)
|
||||
}
|
||||
}
|
||||
return activeWorkspaces
|
||||
}
|
||||
|
||||
// Set focused window
|
||||
function focusWindow(windowId) {
|
||||
if (backend && backend.focusWindow) {
|
||||
backend.focusWindow(windowId)
|
||||
} else {
|
||||
Logger.warn("Compositor", "No backend available for window focus")
|
||||
}
|
||||
}
|
||||
|
||||
// Get focused window
|
||||
function getFocusedWindow() {
|
||||
if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.length) {
|
||||
if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.count) {
|
||||
return windows[focusedWindowIndex]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Close window
|
||||
function closeWindow(windowId) {
|
||||
if (backend && backend.closeWindow) {
|
||||
backend.closeWindow(windowId)
|
||||
} else {
|
||||
Logger.warn("Compositor", "No backend available for window closing")
|
||||
}
|
||||
}
|
||||
|
||||
// Session management
|
||||
function logout() {
|
||||
|
||||
@@ -277,6 +277,22 @@ Item {
|
||||
Logger.error("HyprlandService", "Failed to switch workspace:", e)
|
||||
}
|
||||
}
|
||||
|
||||
function focusWindow(windowId) {
|
||||
try {
|
||||
Hyprland.dispatch(`focuswindow ${windowId}`)
|
||||
} catch (e) {
|
||||
Logger.error("HyprlandService", "Failed to switch window:", e)
|
||||
}
|
||||
}
|
||||
|
||||
function closeWindow(windowId) {
|
||||
try {
|
||||
Hyprland.dispatch(`killwindow ${windowId}`)
|
||||
} catch (e) {
|
||||
Logger.error("HyprlandService", "Failed to close window:", e)
|
||||
}
|
||||
}
|
||||
|
||||
function logout() {
|
||||
try {
|
||||
|
||||
@@ -94,12 +94,21 @@ Item {
|
||||
const windowsList = []
|
||||
|
||||
for (const win of windowsData) {
|
||||
var output = null
|
||||
for (var i = 0; i < workspaces.count; i++) {
|
||||
if (workspaces.get(i).id === win.workspace_id) {
|
||||
output = workspaces.get(i).output
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
windowsList.push({
|
||||
"id": win.id,
|
||||
"title": win.title || "",
|
||||
"appId": win.app_id || "",
|
||||
"workspaceId": win.workspace_id || null,
|
||||
"isFocused": win.is_focused === true
|
||||
"isFocused": win.is_focused === true,
|
||||
"output": output
|
||||
})
|
||||
}
|
||||
|
||||
@@ -161,13 +170,22 @@ Item {
|
||||
try {
|
||||
const windowData = eventData.window
|
||||
const existingIndex = windows.findIndex(w => w.id === windowData.id)
|
||||
|
||||
var output = null
|
||||
for (var i = 0; i < workspaces.count; i++) {
|
||||
if (workspaces.get(i).id === windowData.workspace_id) {
|
||||
output = workspaces.get(i).output
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const newWindow = {
|
||||
"id": windowData.id,
|
||||
"title": windowData.title || "",
|
||||
"appId": windowData.app_id || "",
|
||||
"workspaceId": windowData.workspace_id || null,
|
||||
"isFocused": windowData.is_focused === true
|
||||
"isFocused": windowData.is_focused === true,
|
||||
"output": output
|
||||
}
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
@@ -226,15 +244,24 @@ Item {
|
||||
const windowsList = []
|
||||
|
||||
for (const win of windowsData) {
|
||||
var output = ""
|
||||
for (var i = 0; i < workspaces.count; i++) {
|
||||
if (workspaces.get(i).id === win.workspace_id) {
|
||||
output = workspaces.get(i).output
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
windowsList.push({
|
||||
"id": win.id,
|
||||
"title": win.title || "",
|
||||
"appId": win.app_id || "",
|
||||
"workspaceId": win.workspace_id || null,
|
||||
"isFocused": win.is_focused === true
|
||||
"isFocused": win.is_focused === true,
|
||||
"output": output,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
windowsList.sort((a, b) => a.id - b.id)
|
||||
windows = windowsList
|
||||
windowListChanged()
|
||||
@@ -257,9 +284,18 @@ Item {
|
||||
function handleWindowFocusChanged(eventData) {
|
||||
try {
|
||||
const focusedId = eventData.id
|
||||
|
||||
if (windows[focusedWindowIndex]) {
|
||||
windows[focusedWindowIndex].isFocused = false
|
||||
}
|
||||
|
||||
if (focusedId) {
|
||||
const newIndex = windows.findIndex(w => w.id === focusedId)
|
||||
|
||||
if (newIndex >= 0) {
|
||||
windows[newIndex].isFocused = true
|
||||
}
|
||||
|
||||
focusedWindowIndex = newIndex >= 0 ? newIndex : -1
|
||||
} else {
|
||||
focusedWindowIndex = -1
|
||||
@@ -279,6 +315,22 @@ Item {
|
||||
Logger.error("NiriService", "Failed to switch workspace:", e)
|
||||
}
|
||||
}
|
||||
|
||||
function focusWindow(windowId) {
|
||||
try {
|
||||
Quickshell.execDetached(["niri", "msg", "action", "focus-window", "--id", windowId.toString()])
|
||||
} catch (e) {
|
||||
Logger.error("NiriService", "Failed to switch window:", e)
|
||||
}
|
||||
}
|
||||
|
||||
function closeWindow(windowId) {
|
||||
try {
|
||||
Quickshell.execDetached(["niri", "msg", "action", "close-window", "--id", windowId.toString()])
|
||||
} catch (e) {
|
||||
Logger.error("NiriService", "Failed to close window:", e)
|
||||
}
|
||||
}
|
||||
|
||||
function logout() {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user