Add desktop app service and VM dev session tooling
Introduces a new `core/applications` QML/C++ module (`Nebula.Applications`) to discover `.desktop` entries, expose filterable application models, and launch installed apps safely from the shell. The desktop launcher now uses real installed apps with themed icon loading and fallback glyphs, adds output-aware surface sizing via `OutputTracker`, and wires in image/icon providers. It also adds VMware-focused Sway/shell wrapper scripts, updates Sway config/session env defaults, and refreshes README docs to document the new development flow and Desktop 0.1 behavior.
This commit is contained in:
@@ -4,6 +4,9 @@ import QtQuick
|
||||
|
||||
QtObject {
|
||||
property bool launcherOpen: false
|
||||
|
||||
// Focused-window tracking is a later compositor milestone. Launching an
|
||||
// application does not mean it has focus, so this stays "Desktop" for now.
|
||||
property string activeApplication: "Desktop"
|
||||
|
||||
function openLauncher() {
|
||||
@@ -17,9 +20,4 @@ QtObject {
|
||||
function toggleLauncher() {
|
||||
launcherOpen = !launcherOpen
|
||||
}
|
||||
|
||||
function launch(name) {
|
||||
activeApplication = name
|
||||
closeLauncher()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,17 @@ Item {
|
||||
readonly property bool rowVariant: variant === "row"
|
||||
readonly property int iconSize: rowVariant ? 32 : (variant === "recent" ? 32 : 40)
|
||||
readonly property int glyphSize: rowVariant ? 16 : 18
|
||||
readonly property string iconName: item && item.iconName ? item.iconName : ""
|
||||
readonly property string fallbackGlyph: {
|
||||
if (item && item.glyph)
|
||||
return item.glyph
|
||||
if (item && item.kind)
|
||||
return item.kind
|
||||
return "application"
|
||||
}
|
||||
readonly property url iconSource: iconName.length > 0
|
||||
? ("image://desktopicon/" + encodeURIComponent(iconName))
|
||||
: ""
|
||||
|
||||
implicitWidth: rowVariant ? parent ? parent.width : 200 : 90
|
||||
implicitHeight: rowVariant ? 48 : 78
|
||||
@@ -54,9 +65,23 @@ Item {
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
Image {
|
||||
id: tileIcon
|
||||
anchors.centerIn: parent
|
||||
width: root.iconSize - 8
|
||||
height: root.iconSize - 8
|
||||
fillMode: Image.PreserveAspectFit
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
cache: true
|
||||
visible: status === Image.Ready
|
||||
source: root.iconSource
|
||||
}
|
||||
|
||||
NebulaIcon {
|
||||
anchors.centerIn: parent
|
||||
name: item && item.glyph ? item.glyph : "folder"
|
||||
visible: tileIcon.status !== Image.Ready
|
||||
name: root.fallbackGlyph
|
||||
size: root.glyphSize
|
||||
color: item && item.tint ? item.tint : Theme.accent
|
||||
}
|
||||
@@ -86,9 +111,23 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: Theme.iconWell
|
||||
|
||||
Image {
|
||||
id: rowIcon
|
||||
anchors.centerIn: parent
|
||||
width: root.iconSize - 8
|
||||
height: root.iconSize - 8
|
||||
fillMode: Image.PreserveAspectFit
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
cache: true
|
||||
visible: status === Image.Ready
|
||||
source: root.iconSource
|
||||
}
|
||||
|
||||
NebulaIcon {
|
||||
anchors.centerIn: parent
|
||||
name: item && (item.glyph || item.kind) ? (item.glyph || item.kind) : "folder"
|
||||
visible: rowIcon.status !== Image.Ready
|
||||
name: root.fallbackGlyph
|
||||
size: root.glyphSize
|
||||
color: item && item.tint ? item.tint : Theme.accent
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import QtQuick
|
||||
import Nebula.UI
|
||||
import Nebula.Applications
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -13,8 +14,9 @@ Item {
|
||||
Accessible.role: Accessible.Dialog
|
||||
Accessible.name: qsTr("Launcher")
|
||||
|
||||
readonly property bool usingInstalledApps: ApplicationService.count > 0
|
||||
readonly property bool searching: searchField.text.trim().length > 0
|
||||
readonly property var filteredApps: {
|
||||
readonly property var mockMatches: {
|
||||
const query = searchField.text.trim().toLowerCase()
|
||||
const apps = MockApps.apps
|
||||
if (!query)
|
||||
@@ -24,6 +26,9 @@ Item {
|
||||
return haystack.indexOf(query) !== -1
|
||||
})
|
||||
}
|
||||
readonly property int resultCount: usingInstalledApps
|
||||
? ApplicationService.filteredModel.count
|
||||
: mockMatches.length
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
event.accepted = true
|
||||
@@ -62,6 +67,7 @@ Item {
|
||||
id: searchField
|
||||
width: parent.width
|
||||
placeholderText: qsTr("Search applications")
|
||||
onTextChanged: ApplicationService.filter = text
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -87,7 +93,7 @@ Item {
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 8
|
||||
visible: !root.searching && MockApps.recents.length > 0
|
||||
visible: !root.searching && !root.usingInstalledApps && MockApps.recents.length > 0
|
||||
|
||||
Text {
|
||||
text: qsTr("Recent")
|
||||
@@ -133,14 +139,33 @@ Item {
|
||||
|
||||
Grid {
|
||||
id: appGrid
|
||||
visible: root.filteredApps.length > 0
|
||||
visible: root.resultCount > 0
|
||||
width: parent.width
|
||||
columns: 4
|
||||
columnSpacing: 6
|
||||
rowSpacing: 6
|
||||
|
||||
Repeater {
|
||||
model: root.filteredApps
|
||||
model: root.usingInstalledApps ? ApplicationService.filteredModel : []
|
||||
delegate: AppItem {
|
||||
required property string desktopId
|
||||
required property string name
|
||||
required property string comment
|
||||
required property string iconName
|
||||
width: (appGrid.width - 18) / 4
|
||||
variant: "tile"
|
||||
item: ({
|
||||
desktopId: desktopId,
|
||||
name: name,
|
||||
comment: comment,
|
||||
iconName: iconName
|
||||
})
|
||||
onSelected: root.launchApp(item)
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.usingInstalledApps ? [] : root.mockMatches
|
||||
delegate: AppItem {
|
||||
required property var modelData
|
||||
width: (appGrid.width - 18) / 4
|
||||
@@ -152,7 +177,7 @@ Item {
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: root.filteredApps.length === 0
|
||||
visible: root.resultCount === 0
|
||||
width: parent.width
|
||||
topPadding: 8
|
||||
text: qsTr("No matching applications")
|
||||
@@ -165,7 +190,7 @@ Item {
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 8
|
||||
visible: !root.searching && MockApps.projects.length > 0
|
||||
visible: !root.searching && !root.usingInstalledApps && MockApps.projects.length > 0
|
||||
|
||||
Text {
|
||||
text: qsTr("Recent projects")
|
||||
@@ -286,10 +311,12 @@ Item {
|
||||
}
|
||||
|
||||
function launchApp(app) {
|
||||
ShellState.launch(app.name)
|
||||
if (app && app.desktopId)
|
||||
ApplicationService.launch(app.desktopId)
|
||||
ShellState.closeLauncher()
|
||||
}
|
||||
|
||||
function openProject(project) {
|
||||
ShellState.launch(project.name)
|
||||
ShellState.closeLauncher()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,19 @@ import Nebula.UI
|
||||
Window {
|
||||
id: root
|
||||
|
||||
readonly property int outputWidth: OutputTracker.width > 0
|
||||
? OutputTracker.width
|
||||
: (screen && screen.width > 0 ? screen.width : Screen.width)
|
||||
readonly property int outputHeight: OutputTracker.height > 0
|
||||
? OutputTracker.height
|
||||
: (screen && screen.height > 0 ? screen.height : Screen.height)
|
||||
|
||||
title: qsTr("Nebula Desktop")
|
||||
color: "transparent"
|
||||
flags: Qt.FramelessWindowHint | Qt.WindowDoesNotAcceptFocus
|
||||
visible: true
|
||||
width: Screen.width
|
||||
height: Screen.height
|
||||
width: outputWidth
|
||||
height: outputHeight
|
||||
|
||||
LayerShell.Window.scope: "nebula-desktop"
|
||||
LayerShell.Window.layer: LayerShell.Window.LayerBackground
|
||||
|
||||
@@ -6,12 +6,19 @@ import Nebula.UI
|
||||
Window {
|
||||
id: root
|
||||
|
||||
readonly property int outputWidth: OutputTracker.width > 0
|
||||
? OutputTracker.width
|
||||
: (screen && screen.width > 0 ? screen.width : Screen.width)
|
||||
readonly property int outputHeight: OutputTracker.height > 0
|
||||
? OutputTracker.height
|
||||
: (screen && screen.height > 0 ? screen.height : Screen.height)
|
||||
|
||||
title: qsTr("Nebula Launcher")
|
||||
color: "transparent"
|
||||
flags: Qt.FramelessWindowHint
|
||||
visible: ShellState.launcherOpen || launcher.opacity > 0.01
|
||||
width: Screen.width
|
||||
height: Screen.height - Theme.topBarHeight
|
||||
width: outputWidth
|
||||
height: Math.max(1, outputHeight - Theme.topBarHeight)
|
||||
|
||||
LayerShell.Window.scope: "nebula-launcher"
|
||||
LayerShell.Window.layer: LayerShell.Window.LayerOverlay
|
||||
|
||||
@@ -6,11 +6,15 @@ import Nebula.UI
|
||||
Window {
|
||||
id: root
|
||||
|
||||
readonly property int outputWidth: OutputTracker.width > 0
|
||||
? OutputTracker.width
|
||||
: (screen && screen.width > 0 ? screen.width : Screen.width)
|
||||
|
||||
title: qsTr("Nebula Top Bar")
|
||||
color: "transparent"
|
||||
flags: Qt.FramelessWindowHint | Qt.WindowDoesNotAcceptFocus
|
||||
visible: true
|
||||
width: Screen.width
|
||||
width: outputWidth
|
||||
height: Theme.topBarHeight
|
||||
|
||||
LayerShell.Window.scope: "nebula-topbar"
|
||||
|
||||
Reference in New Issue
Block a user