Add ActiveWindow, make ColorScheme look better
This commit is contained in:
parent
52891d1fb4
commit
1d7d0752ad
5 changed files with 440 additions and 29 deletions
151
Modules/Bar/ActiveWindow.qml
Normal file
151
Modules/Bar/ActiveWindow.qml
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import Quickshell
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: layout
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Style.marginSmall * scaling
|
||||||
|
visible: Settings.data.bar.showActiveWindow
|
||||||
|
|
||||||
|
property bool showingFullTitle: false
|
||||||
|
property int lastWindowIndex: -1
|
||||||
|
|
||||||
|
// Timer to hide full title after window switch
|
||||||
|
Timer {
|
||||||
|
id: fullTitleTimer
|
||||||
|
interval: 2000 // Show full title for 2 seconds
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
showingFullTitle = false
|
||||||
|
titleText.text = getDisplayText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update text when window changes
|
||||||
|
Connections {
|
||||||
|
target: typeof Niri !== "undefined" ? Niri : null
|
||||||
|
function onFocusedWindowIndexChanged() {
|
||||||
|
// Check if window actually changed
|
||||||
|
if (Niri.focusedWindowIndex !== lastWindowIndex) {
|
||||||
|
lastWindowIndex = Niri.focusedWindowIndex
|
||||||
|
showingFullTitle = true
|
||||||
|
fullTitleTimer.restart()
|
||||||
|
}
|
||||||
|
titleText.text = getDisplayText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window icon
|
||||||
|
NText {
|
||||||
|
id: windowIcon
|
||||||
|
text: "desktop_windows"
|
||||||
|
font.family: "Material Symbols Outlined"
|
||||||
|
font.pointSize: Style.fontSizeLarge * scaling
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: Colors.mPrimary
|
||||||
|
visible: getDisplayText() !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window title container
|
||||||
|
Item {
|
||||||
|
id: titleContainer
|
||||||
|
width: titleText.width
|
||||||
|
height: titleText.height
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 300
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NText {
|
||||||
|
id: titleText
|
||||||
|
text: getDisplayText()
|
||||||
|
font.pointSize: Style.fontSizeSmall * scaling
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse area for hover detection
|
||||||
|
MouseArea {
|
||||||
|
id: titleContainerMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.IBeamCursor
|
||||||
|
onEntered: {
|
||||||
|
titleText.text = getDisplayText()
|
||||||
|
}
|
||||||
|
onExited: {
|
||||||
|
titleText.text = getDisplayText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function getDisplayText() {
|
||||||
|
// Check if Niri service is available
|
||||||
|
if (typeof Niri === "undefined") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the focused window data
|
||||||
|
const focusedWindow = Niri.focusedWindowIndex >= 0 && Niri.focusedWindowIndex < Niri.windows.length
|
||||||
|
? Niri.windows[Niri.focusedWindowIndex]
|
||||||
|
: null
|
||||||
|
|
||||||
|
if (!focusedWindow) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const appId = focusedWindow.appId || ""
|
||||||
|
const title = focusedWindow.title || ""
|
||||||
|
|
||||||
|
// If no appId, fall back to title processing
|
||||||
|
if (!appId) {
|
||||||
|
if (!title || title === "(No active window)" || title === "(Unnamed window)") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract program name from title (before first space or special characters)
|
||||||
|
const programName = title.split(/[\s\-_]/)[0]
|
||||||
|
|
||||||
|
if (programName.length <= 2 || programName === title) {
|
||||||
|
return truncateTitle(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showingFullTitle || titleContainerMouseArea.containsMouse || isGenericName(programName)) {
|
||||||
|
return truncateTitle(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
return programName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use appId for program name, show full title on hover or window switch
|
||||||
|
if (showingFullTitle || titleContainerMouseArea.containsMouse) {
|
||||||
|
return truncateTitle(title || appId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return appId
|
||||||
|
}
|
||||||
|
|
||||||
|
function truncateTitle(title) {
|
||||||
|
if (title.length > 50) {
|
||||||
|
return title.substring(0, 47) + "..."
|
||||||
|
}
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
|
||||||
|
function isGenericName(name) {
|
||||||
|
const genericNames = ["window", "application", "app", "program", "process", "unknown"]
|
||||||
|
return genericNames.includes(name.toLowerCase())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -55,6 +55,8 @@ Variants {
|
||||||
SystemMonitor {}
|
SystemMonitor {}
|
||||||
|
|
||||||
MediaMini {}
|
MediaMini {}
|
||||||
|
|
||||||
|
ActiveWindow {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Center
|
// Center
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,137 @@ import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
|
// Helper function to get color from scheme file
|
||||||
|
function getSchemeColor(schemePath, colorKey) {
|
||||||
|
// Extract scheme name from path
|
||||||
|
var schemeName = schemePath.split("/").pop().replace(".json", "")
|
||||||
|
|
||||||
|
// Try to get from cached data first
|
||||||
|
if (schemeColorsCache[schemeName] && schemeColorsCache[schemeName][colorKey]) {
|
||||||
|
return schemeColorsCache[schemeName][colorKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a default color if not cached yet
|
||||||
|
return "#000000"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache for scheme colors
|
||||||
|
property var schemeColorsCache: ({})
|
||||||
|
|
||||||
|
// Array to hold FileView objects
|
||||||
|
property var fileViews: []
|
||||||
|
|
||||||
|
// Load color scheme data when schemes are available
|
||||||
|
Connections {
|
||||||
|
target: ColorSchemes
|
||||||
|
function onSchemesChanged() {
|
||||||
|
loadSchemeColors()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSchemeColors() {
|
||||||
|
// Clear existing cache
|
||||||
|
schemeColorsCache = {}
|
||||||
|
|
||||||
|
// Destroy existing FileViews
|
||||||
|
for (var i = 0; i < fileViews.length; i++) {
|
||||||
|
if (fileViews[i]) {
|
||||||
|
fileViews[i].destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileViews = []
|
||||||
|
|
||||||
|
// Create FileViews for each scheme
|
||||||
|
for (var i = 0; i < ColorSchemes.schemes.length; i++) {
|
||||||
|
var schemePath = ColorSchemes.schemes[i]
|
||||||
|
var schemeName = schemePath.split("/").pop().replace(".json", "")
|
||||||
|
|
||||||
|
// Create FileView component
|
||||||
|
var component = Qt.createComponent("SchemeFileView.qml")
|
||||||
|
if (component.status === Component.Ready) {
|
||||||
|
var fileView = component.createObject(root, {
|
||||||
|
"path": schemePath,
|
||||||
|
"schemeName": schemeName
|
||||||
|
})
|
||||||
|
fileViews.push(fileView)
|
||||||
|
} else {
|
||||||
|
// Fallback: create inline FileView
|
||||||
|
createInlineFileView(schemePath, schemeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInlineFileView(schemePath, schemeName) {
|
||||||
|
var fileViewQml = `
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
property string schemeName: "${schemeName}"
|
||||||
|
path: "${schemePath}"
|
||||||
|
blockLoading: true
|
||||||
|
|
||||||
|
onLoaded: {
|
||||||
|
try {
|
||||||
|
var jsonData = JSON.parse(text())
|
||||||
|
root.schemeLoaded(schemeName, jsonData)
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Failed to parse JSON for scheme:", schemeName, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
try {
|
||||||
|
var fileView = Qt.createQmlObject(fileViewQml, root, "dynamicFileView_" + schemeName)
|
||||||
|
fileViews.push(fileView)
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Failed to create FileView for scheme:", schemeName, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function schemeLoaded(schemeName, jsonData) {
|
||||||
|
console.log("Loading scheme colors for:", schemeName)
|
||||||
|
|
||||||
|
var colors = {}
|
||||||
|
|
||||||
|
// Extract colors from JSON data
|
||||||
|
if (jsonData && typeof jsonData === 'object') {
|
||||||
|
colors.mPrimary = jsonData.mPrimary || jsonData.primary || "#000000"
|
||||||
|
colors.mSecondary = jsonData.mSecondary || jsonData.secondary || "#000000"
|
||||||
|
colors.mTertiary = jsonData.mTertiary || jsonData.tertiary || "#000000"
|
||||||
|
colors.mError = jsonData.mError || jsonData.error || "#ff0000"
|
||||||
|
colors.mSurface = jsonData.mSurface || jsonData.surface || "#ffffff"
|
||||||
|
colors.mOnSurface = jsonData.mOnSurface || jsonData.onSurface || "#000000"
|
||||||
|
colors.mOutline = jsonData.mOutline || jsonData.outline || "#666666"
|
||||||
|
} else {
|
||||||
|
// Default colors
|
||||||
|
colors = {
|
||||||
|
mPrimary: "#000000",
|
||||||
|
mSecondary: "#000000",
|
||||||
|
mTertiary: "#000000",
|
||||||
|
mError: "#ff0000",
|
||||||
|
mSurface: "#ffffff",
|
||||||
|
mOnSurface: "#000000",
|
||||||
|
mOutline: "#666666"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cache
|
||||||
|
var newCache = schemeColorsCache
|
||||||
|
newCache[schemeName] = colors
|
||||||
|
schemeColorsCache = newCache
|
||||||
|
|
||||||
|
console.log("Cached colors for", schemeName, ":", JSON.stringify(colors))
|
||||||
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
id: scrollView
|
id: scrollView
|
||||||
|
|
||||||
|
|
@ -32,10 +157,10 @@ ColumnLayout {
|
||||||
spacing: Style.marginLarge * scaling
|
spacing: Style.marginLarge * scaling
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
// Use Wallpaper Colors
|
// Use Matugen
|
||||||
NToggle {
|
NToggle {
|
||||||
label: "Use Wallpaper Colors"
|
label: "Use Matugen"
|
||||||
description: "Automatically generate colors from you active wallpaper (requires Matugen)"
|
description: "Automatically generate colors based on your active wallpaper using Matugen"
|
||||||
value: Settings.data.colorSchemes.useWallpaperColors
|
value: Settings.data.colorSchemes.useWallpaperColors
|
||||||
onToggled: function (newValue) {
|
onToggled: function (newValue) {
|
||||||
Settings.data.colorSchemes.useWallpaperColors = newValue
|
Settings.data.colorSchemes.useWallpaperColors = newValue
|
||||||
|
|
@ -45,30 +170,168 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NDivider {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
NText {
|
||||||
|
text: "Predefined Color Schemes"
|
||||||
|
font.pointSize: Style.fontSizeLarge * scaling
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
color: Colors.mOnSurface
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
NText {
|
||||||
|
text: "These color schemes only apply when 'Use Matugen' is disabled. When enabled, Matugen will generate colors based on your wallpaper instead."
|
||||||
|
font.pointSize: Style.fontSizeSmall * scaling
|
||||||
|
color: Colors.mOnSurface
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
Layout.topMargin: -16 * scaling
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: Style.marginTiny * scaling
|
spacing: Style.marginTiny * scaling
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
ButtonGroup {
|
// Color Schemes Grid
|
||||||
id: schemesGroup
|
GridLayout {
|
||||||
}
|
columns: 4
|
||||||
|
rowSpacing: Style.marginLarge * scaling
|
||||||
|
columnSpacing: Style.marginLarge * scaling
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: ColorSchemes.schemes
|
model: ColorSchemes.schemes
|
||||||
NRadioButton {
|
|
||||||
property string schemePath: modelData
|
Rectangle {
|
||||||
ButtonGroup.group: schemesGroup
|
id: schemeCard
|
||||||
text: {
|
Layout.fillWidth: true
|
||||||
// Remove json and the full path
|
Layout.preferredHeight: 120 * scaling
|
||||||
var chunks = schemePath.replace(".json", "").split("/")
|
radius: 12 * scaling
|
||||||
return chunks[chunks.length - 1]
|
color: getSchemeColor(modelData, "mSurface")
|
||||||
}
|
border.width: 2
|
||||||
checked: Settings.data.colorSchemes.predefinedScheme == schemePath
|
border.color: Settings.data.colorSchemes.predefinedScheme === modelData ? Colors.mPrimary : Colors.mOutline
|
||||||
onClicked: {
|
|
||||||
// Disable useWallpaperColors when picking a predefined color scheme
|
property string schemePath: modelData
|
||||||
Settings.data.colorSchemes.useWallpaperColors = false
|
|
||||||
Settings.data.colorSchemes.predefinedScheme = schemePath
|
// Mouse area for selection
|
||||||
ColorSchemes.applyScheme(schemePath)
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
// Disable useWallpaperColors when picking a predefined color scheme
|
||||||
|
Settings.data.colorSchemes.useWallpaperColors = false
|
||||||
|
Settings.data.colorSchemes.predefinedScheme = schemePath
|
||||||
|
ColorSchemes.applyScheme(schemePath)
|
||||||
|
}
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
|
onEntered: {
|
||||||
|
schemeCard.scale = 1.05
|
||||||
|
schemeCard.border.width = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
onExited: {
|
||||||
|
schemeCard.scale = 1.0
|
||||||
|
schemeCard.border.width = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Card content
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 16 * scaling
|
||||||
|
spacing: 8 * scaling
|
||||||
|
|
||||||
|
// Scheme name
|
||||||
|
NText {
|
||||||
|
text: {
|
||||||
|
// Remove json and the full path
|
||||||
|
var chunks = schemePath.replace(".json", "").split("/")
|
||||||
|
return chunks[chunks.length - 1]
|
||||||
|
}
|
||||||
|
font.pointSize: Style.fontSizeMedium * scaling
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
color: Colors.mOnSurface
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color swatches
|
||||||
|
RowLayout {
|
||||||
|
spacing: 8 * scaling
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
|
// Primary color swatch
|
||||||
|
Rectangle {
|
||||||
|
width: 28 * scaling
|
||||||
|
height: 28 * scaling
|
||||||
|
radius: 14 * scaling
|
||||||
|
color: getSchemeColor(modelData, "mPrimary")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secondary color swatch
|
||||||
|
Rectangle {
|
||||||
|
width: 28 * scaling
|
||||||
|
height: 28 * scaling
|
||||||
|
radius: 14 * scaling
|
||||||
|
color: getSchemeColor(modelData, "mSecondary")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tertiary color swatch
|
||||||
|
Rectangle {
|
||||||
|
width: 28 * scaling
|
||||||
|
height: 28 * scaling
|
||||||
|
radius: 14 * scaling
|
||||||
|
color: getSchemeColor(modelData, "mTertiary")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error color swatch
|
||||||
|
Rectangle {
|
||||||
|
width: 28 * scaling
|
||||||
|
height: 28 * scaling
|
||||||
|
radius: 14 * scaling
|
||||||
|
color: getSchemeColor(modelData, "mError")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selection indicator
|
||||||
|
Rectangle {
|
||||||
|
visible: Settings.data.colorSchemes.predefinedScheme === schemePath
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.margins: 8 * scaling
|
||||||
|
width: 24 * scaling
|
||||||
|
height: 24 * scaling
|
||||||
|
radius: 12 * scaling
|
||||||
|
color: Colors.mPrimary
|
||||||
|
|
||||||
|
NText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "✓"
|
||||||
|
font.pointSize: Style.fontSizeSmall * scaling
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
color: Colors.mOnPrimary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Smooth animations
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on border.color {
|
||||||
|
ColorAnimation { duration: 300 }
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on border.width {
|
||||||
|
NumberAnimation { duration: 200 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ Singleton {
|
||||||
}
|
}
|
||||||
schemes = files
|
schemes = files
|
||||||
scanning = false
|
scanning = false
|
||||||
|
console.log("[ColorSchemes] Loaded", schemes.length, "schemes")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ Singleton {
|
||||||
property var hlWorkspaces: Hyprland.workspaces.values
|
property var hlWorkspaces: Hyprland.workspaces.values
|
||||||
// Detect which compositor we're using
|
// Detect which compositor we're using
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
console.log("[Workspaces] Initializing workspaces service")
|
|
||||||
detectCompositor()
|
detectCompositor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,25 +24,20 @@ Singleton {
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
if (Hyprland.eventSocketPath) {
|
if (Hyprland.eventSocketPath) {
|
||||||
console.log("[Workspaces] Detected Hyprland compositor")
|
|
||||||
isHyprland = true
|
isHyprland = true
|
||||||
isNiri = false
|
isNiri = false
|
||||||
initHyprland()
|
initHyprland()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("[Workspaces] Hyprland not available:", e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof Niri !== "undefined") {
|
if (typeof Niri !== "undefined") {
|
||||||
console.log("[Workspaces] Detected Niri service")
|
|
||||||
isHyprland = false
|
isHyprland = false
|
||||||
isNiri = true
|
isNiri = true
|
||||||
initNiri()
|
initNiri()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[Workspaces] Could not detect any supported compositor")
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[Workspaces] Error detecting compositor:", e)
|
console.error("[Workspaces] Error detecting compositor:", e)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue