v0.9.0
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Ui
|
||||
|
||||
BarWidget {
|
||||
id: root
|
||||
moduleName: "local.omarchy-citizen"
|
||||
|
||||
readonly property bool opened: panelLoader.item
|
||||
? panelLoader.item.opened === true
|
||||
: false
|
||||
|
||||
readonly property bool popoutSwitchClosing: panelLoader.item
|
||||
? panelLoader.item.popoutSwitchClosing === true
|
||||
: false
|
||||
|
||||
function open() {
|
||||
if (panelLoader.item) panelLoader.item.open()
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (panelLoader.item) panelLoader.item.close()
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (panelLoader.item) panelLoader.item.toggle()
|
||||
}
|
||||
|
||||
function closeForPopoutSwitch() {
|
||||
if (panelLoader.item) panelLoader.item.closeForPopoutSwitch()
|
||||
}
|
||||
|
||||
function injectPanel() {
|
||||
if (!panelLoader.item) return
|
||||
panelLoader.item.bar = root.bar
|
||||
panelLoader.item.anchorItem = button
|
||||
panelLoader.item.hostWidget = root
|
||||
}
|
||||
|
||||
implicitWidth: button.implicitWidth
|
||||
implicitHeight: button.implicitHeight
|
||||
|
||||
onBarChanged: injectPanel()
|
||||
|
||||
Loader {
|
||||
id: panelLoader
|
||||
active: true
|
||||
source: Qt.resolvedUrl("Panel.qml")
|
||||
visible: false
|
||||
|
||||
onLoaded: {
|
||||
root.injectPanel()
|
||||
Qt.callLater(root.injectPanel)
|
||||
}
|
||||
}
|
||||
|
||||
WidgetButton {
|
||||
id: button
|
||||
anchors.fill: parent
|
||||
bar: root.bar
|
||||
text: "✦SC"
|
||||
tooltipText: "Omarchy Citizen – Plug & Play Star Citizen"
|
||||
onPressed: function(buttonCode) {
|
||||
if (buttonCode === Qt.LeftButton) root.toggle()
|
||||
else if (buttonCode === Qt.RightButton && root.bar)
|
||||
root.bar.run("xdg-open https://wiki.starcitizen-lug.org")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,972 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
|
||||
Panel {
|
||||
id: root
|
||||
moduleName: "local.omarchy-citizen"
|
||||
manageIpc: false
|
||||
|
||||
property var anchorItem: null
|
||||
property var hostWidget: null
|
||||
|
||||
property string pluginVersion: "0.9.0"
|
||||
property string health: "checking"
|
||||
property string depsState: "checking"
|
||||
property string depsMissing: ""
|
||||
property string helperState: "checking"
|
||||
property string helperManaged: "missing"
|
||||
property string helperVersion: ""
|
||||
property string updateState: "unknown"
|
||||
property string updateVersion: ""
|
||||
property string prefixState: "checking"
|
||||
property string prefixDetail: ""
|
||||
property string launcherState: "checking"
|
||||
property string gameState: "checking"
|
||||
property bool checkingUpdate: false
|
||||
property bool advancedVisible: false
|
||||
property string backendState: "missing"
|
||||
property string backendVersion: ""
|
||||
property string pluginManaged: "manual"
|
||||
property string pluginAuto: "false"
|
||||
property string pluginUpdate: "unavailable"
|
||||
property string pluginLocalCommit: ""
|
||||
property string pluginRemoteCommit: ""
|
||||
property string autopilot: "false"
|
||||
property string managedLUGVersion: ""
|
||||
property string managedWineVersion: ""
|
||||
property string managedDXVKVersion: ""
|
||||
property string maintenanceResult: ""
|
||||
property string hardwareState: "unknown"
|
||||
property string hardwareReason: ""
|
||||
property string hardwareGPU: ""
|
||||
property string hardwareVulkan: "unknown"
|
||||
property string actionStatus: ""
|
||||
property string actionError: ""
|
||||
property string actionKind: ""
|
||||
property bool actionCloseOnSuccess: true
|
||||
|
||||
readonly property color accent: "#56d2ff"
|
||||
readonly property color accentSoft: "#1c3444"
|
||||
readonly property color surface: "#0d1822"
|
||||
readonly property color surfaceRaised: "#122230"
|
||||
readonly property color border: "#2b485c"
|
||||
readonly property color success: "#71e7ad"
|
||||
readonly property color warning: "#ffd276"
|
||||
readonly property color danger: "#ff8fa1"
|
||||
readonly property color muted: "#a8bbc9"
|
||||
readonly property string uiFont: "sans-serif"
|
||||
|
||||
readonly property string controlScript: decodeURIComponent(
|
||||
Qt.resolvedUrl("citizenctl").toString().replace(/^file:\/\//, "")
|
||||
)
|
||||
|
||||
readonly property bool canLaunch: health === "ready"
|
||||
|
||||
function open() { root.controller.show() }
|
||||
function close() { root.controller.hide() }
|
||||
|
||||
function switchPanel(direction) {
|
||||
if (root.bar && typeof root.bar.switchPanelFrom === "function")
|
||||
return root.bar.switchPanelFrom(root.hostWidget || root, direction)
|
||||
return false
|
||||
}
|
||||
|
||||
function runAction(action, closePanel) {
|
||||
if (actionProc.running) return
|
||||
|
||||
root.actionKind = String(action || "")
|
||||
root.actionCloseOnSuccess = closePanel !== false
|
||||
root.actionError = ""
|
||||
root.actionStatus = "Aktion wird gestartet…"
|
||||
|
||||
actionProc.command = ["bash", root.controlScript, root.actionKind]
|
||||
actionProc.running = true
|
||||
}
|
||||
|
||||
function refreshStatus() {
|
||||
if (!statusProc.running) statusProc.running = true
|
||||
}
|
||||
|
||||
function checkForUpdates() {
|
||||
if (updateProc.running) return
|
||||
root.checkingUpdate = true
|
||||
root.updateState = "checking"
|
||||
updateProc.running = true
|
||||
}
|
||||
|
||||
function applyStatus(raw) {
|
||||
var values = {}
|
||||
var lines = String(raw || "").trim().split("\n")
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var p = lines[i].indexOf("=")
|
||||
if (p <= 0) continue
|
||||
values[lines[i].slice(0, p)] = lines[i].slice(p + 1)
|
||||
}
|
||||
|
||||
pluginVersion = values.plugin_version || "0.9.0"
|
||||
health = values.health || "setup"
|
||||
depsState = values.deps || "missing"
|
||||
depsMissing = values.deps_missing || ""
|
||||
helperState = values.helper || "missing"
|
||||
helperManaged = values.helper_managed || "missing"
|
||||
helperVersion = values.helper_version || ""
|
||||
updateState = values.update || "unknown"
|
||||
updateVersion = values.update_version || ""
|
||||
prefixState = values.prefix || "missing"
|
||||
prefixDetail = values.prefix_detail || ""
|
||||
launcherState = values.launcher || "missing"
|
||||
gameState = values.game || "missing"
|
||||
backendState = values.backend || "missing"
|
||||
backendVersion = values.backend_version || ""
|
||||
pluginManaged = values.plugin_managed || "manual"
|
||||
pluginAuto = values.plugin_auto || "false"
|
||||
pluginUpdate = values.plugin_update || "unavailable"
|
||||
pluginLocalCommit = values.plugin_local_commit || ""
|
||||
pluginRemoteCommit = values.plugin_remote_commit || ""
|
||||
autopilot = values.autopilot || "false"
|
||||
managedLUGVersion = values.managed_lug_version || ""
|
||||
managedWineVersion = values.managed_wine_version || ""
|
||||
managedDXVKVersion = values.managed_dxvk_version || ""
|
||||
maintenanceResult = values.maintenance_result || ""
|
||||
hardwareState = values.hardware || "unknown"
|
||||
hardwareReason = values.hardware_reason || ""
|
||||
hardwareGPU = values.hardware_gpu || ""
|
||||
hardwareVulkan = values.hardware_vulkan || "unknown"
|
||||
}
|
||||
|
||||
function stateIcon(state) {
|
||||
if (state === "ready" || state === "current") return "✓"
|
||||
if (state === "available") return "↑"
|
||||
if (state === "warning") return "!"
|
||||
if (state === "partial" || state === "unknown") return "…"
|
||||
if (state === "checking") return "·"
|
||||
return "!"
|
||||
}
|
||||
|
||||
function stateColor(state) {
|
||||
if (state === "ready" || state === "current") return success
|
||||
if (state === "available" || state === "warning") return warning
|
||||
if (state === "partial" || state === "unknown" || state === "checking") return accent
|
||||
return danger
|
||||
}
|
||||
|
||||
function pluginUpdateText() {
|
||||
if (root.pluginUpdate === "available") return "Update verfügbar"
|
||||
if (root.pluginUpdate === "current") return "aktuell"
|
||||
if (root.pluginUpdate === "blocked-dirty") return "lokale Änderungen · Update pausiert"
|
||||
if (root.pluginUpdate === "blocked-diverged") return "Git-Stand abweichend · Update pausiert"
|
||||
if (root.pluginUpdate === "blocked-remote") return "Updatequelle geändert · Update pausiert"
|
||||
if (root.pluginUpdate === "zip") return "ZIP-Installation"
|
||||
return root.pluginUpdate
|
||||
}
|
||||
|
||||
function pluginUpdateState() {
|
||||
if (root.pluginUpdate === "available") return "available"
|
||||
if (root.pluginUpdate === "current") return "current"
|
||||
if (String(root.pluginUpdate).indexOf("blocked-") === 0) return "warning"
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
function healthColor() {
|
||||
if (health === "hardware-blocked") return danger
|
||||
if (health === "ready") return success
|
||||
if (health === "recover-prefix" || health === "repair") return warning
|
||||
if (health === "checking") return accent
|
||||
return accent
|
||||
}
|
||||
|
||||
function headline() {
|
||||
if (health === "hardware-blocked") return "HARDWARE NICHT SPIELBEREIT"
|
||||
if (health === "ready" && root.autopilot === "true") return "FLIGHT READY · AUTOPILOT"
|
||||
if (health === "ready") return "FLIGHT READY"
|
||||
if (health === "recover-prefix") return "SETUP REPAIR"
|
||||
if (health === "repair") return "REPAIR AVAILABLE"
|
||||
if (health === "checking") return "SYSTEM CHECK"
|
||||
return "READY FOR SETUP"
|
||||
}
|
||||
|
||||
function subline() {
|
||||
if (health === "hardware-blocked")
|
||||
return root.hardwareReason !== "" ? root.hardwareReason : "Eine für Star Citizen geeignete Vulkan-GPU wurde nicht erkannt."
|
||||
if (health === "ready")
|
||||
return "Wine, DXVK und RSI Launcher sind geprüft. Star Citizen ist einen Klick entfernt."
|
||||
if (health === "install-game")
|
||||
return "Der Software-Stack ist bereit. Melde dich im offiziellen RSI Launcher an und starte dort den Spieldownload."
|
||||
if (health === "repair" || health === "launcher-repair")
|
||||
return "Omarchy Citizen kann den gesamten verwalteten Gaming-Stack automatisch reparieren."
|
||||
return "Omarchy Citizen wählt selbst einen kompatiblen Wine-Runner, erstellt den Prefix, installiert DXVK/Komponenten und den offiziellen RSI Launcher."
|
||||
}
|
||||
|
||||
function primaryText() {
|
||||
if (health === "hardware-blocked") return "ⓘ HARDWARE-DETAILS"
|
||||
if (health === "ready") return "▶ STAR CITIZEN STARTEN"
|
||||
if (health === "install-game") return "▶ RSI LAUNCHER ÖFFNEN"
|
||||
if (health === "repair" || health === "launcher-repair") return "↻ AUTOMATISCH REPARIEREN"
|
||||
return "▶ EINRICHTEN & STARTKLAR MACHEN"
|
||||
}
|
||||
|
||||
function updateText() {
|
||||
if (checkingUpdate || updateState === "checking") return "wird geprüft"
|
||||
if (helperManaged === "external") return "extern"
|
||||
if (helperState !== "ready") return "nach Setup"
|
||||
if (updateState === "available")
|
||||
return updateVersion !== "" ? "Update " + updateVersion : "Update verfügbar"
|
||||
if (updateState === "current") return "aktuell"
|
||||
return "noch nicht geprüft"
|
||||
}
|
||||
|
||||
onOpenedChanged: if (opened) refreshStatus()
|
||||
|
||||
Process {
|
||||
id: statusProc
|
||||
command: ["bash", root.controlScript, "status"]
|
||||
stdout: StdioCollector {
|
||||
waitForEnd: true
|
||||
onStreamFinished: root.applyStatus(text)
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: updateProc
|
||||
command: ["bash", root.controlScript, "check-update"]
|
||||
stdout: StdioCollector {
|
||||
waitForEnd: true
|
||||
onStreamFinished: {
|
||||
root.checkingUpdate = false
|
||||
root.refreshStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: actionProc
|
||||
running: false
|
||||
command: []
|
||||
|
||||
stdout: StdioCollector {
|
||||
id: actionOut
|
||||
waitForEnd: true
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
id: actionErr
|
||||
waitForEnd: true
|
||||
}
|
||||
|
||||
onStarted: {
|
||||
if (root.actionKind === "primary" && root.health === "recover-prefix")
|
||||
root.actionStatus = "Reparatur-Fenster wird geöffnet…"
|
||||
else if (root.actionKind === "primary")
|
||||
root.actionStatus = "Setup/Start wird vorbereitet…"
|
||||
else
|
||||
root.actionStatus = "Aktion wird ausgeführt…"
|
||||
}
|
||||
|
||||
onExited: function(exitCode) {
|
||||
if (exitCode === 0) {
|
||||
root.actionError = ""
|
||||
root.actionStatus = root.actionKind === "primary" && root.health === "install-game"
|
||||
? "RSI Launcher wurde gestartet."
|
||||
: (root.actionKind === "primary" && root.health !== "ready"
|
||||
? "Setup-Fenster wurde geöffnet. Omarchy Citizen übernimmt die technischen Schritte."
|
||||
: "Aktion wurde gestartet.")
|
||||
root.refreshStatus()
|
||||
|
||||
if (root.actionCloseOnSuccess)
|
||||
closeAfterAction.restart()
|
||||
} else {
|
||||
var detail = String(actionErr.text || "").trim()
|
||||
root.actionStatus = ""
|
||||
root.actionError = detail !== ""
|
||||
? detail
|
||||
: "Die Aktion konnte nicht gestartet werden. Bitte erstelle ein Support-Paket."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: closeAfterAction
|
||||
interval: 350
|
||||
repeat: false
|
||||
onTriggered: root.close()
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 10000
|
||||
repeat: true
|
||||
running: root.opened
|
||||
onTriggered: root.refreshStatus()
|
||||
}
|
||||
|
||||
KeyboardPanel {
|
||||
id: panel
|
||||
anchorItem: root.anchorItem
|
||||
owner: root.hostWidget || root
|
||||
bar: root.bar
|
||||
open: root.opened
|
||||
focusTarget: keyCatcher
|
||||
contentWidth: panel.fittedContentWidth(Style.space(450))
|
||||
contentHeight: panel.fittedContentHeight(content.implicitHeight)
|
||||
|
||||
PanelKeyCatcher {
|
||||
id: keyCatcher
|
||||
anchors.fill: parent
|
||||
onCloseRequested: root.close()
|
||||
onTabRequested: function(direction) { root.switchPanel(direction) }
|
||||
|
||||
Column {
|
||||
id: content
|
||||
width: parent.width
|
||||
spacing: Style.space(10)
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
radius: 18
|
||||
color: root.surfaceRaised
|
||||
border.color: root.healthColor()
|
||||
border.width: 1
|
||||
implicitHeight: heroContent.implicitHeight + Style.space(24)
|
||||
|
||||
Column {
|
||||
id: heroContent
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Style.space(16)
|
||||
spacing: Style.space(10)
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Style.space(10)
|
||||
|
||||
Rectangle {
|
||||
width: Style.space(34)
|
||||
height: Style.space(34)
|
||||
radius: 10
|
||||
color: root.accentSoft
|
||||
border.color: root.healthColor()
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "✦"
|
||||
color: root.healthColor()
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.title
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: Style.space(2)
|
||||
|
||||
Text {
|
||||
text: "CITIZEN LAUNCHER"
|
||||
color: root.barForeground
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.title
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.headline()
|
||||
color: root.healthColor()
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.subline()
|
||||
color: root.muted
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Button {
|
||||
width: parent.width
|
||||
text: actionProc.running
|
||||
? "… WIRD GESTARTET"
|
||||
: root.primaryText()
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
active: root.health === "ready" || actionProc.running
|
||||
onClicked: root.runAction(
|
||||
root.health === "hardware-blocked" ? "hardware-info" :
|
||||
((root.health === "repair" || root.health === "launcher-repair") ? "repair" : "primary"),
|
||||
root.health === "ready"
|
||||
)
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: root.actionStatus !== ""
|
||||
width: parent.width
|
||||
text: root.actionStatus
|
||||
color: root.accent
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.caption
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: root.actionError !== ""
|
||||
width: parent.width
|
||||
radius: 10
|
||||
color: Qt.rgba(root.danger.r, root.danger.g, root.danger.b, 0.10)
|
||||
border.color: root.danger
|
||||
border.width: 1
|
||||
implicitHeight: actionErrorText.implicitHeight + Style.space(16)
|
||||
|
||||
Text {
|
||||
id: actionErrorText
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Style.space(8)
|
||||
text: root.actionError
|
||||
color: root.danger
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.caption
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
radius: 16
|
||||
color: root.surface
|
||||
border.color: root.border
|
||||
border.width: 1
|
||||
implicitHeight: simpleActions.implicitHeight + Style.space(20)
|
||||
|
||||
Column {
|
||||
id: simpleActions
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Style.space(14)
|
||||
spacing: Style.space(9)
|
||||
|
||||
Text {
|
||||
text: "HILFE OHNE LINUX-WISSEN"
|
||||
color: root.accent
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Grid {
|
||||
id: helpGrid
|
||||
width: parent.width
|
||||
columns: 2
|
||||
columnSpacing: Style.space(7)
|
||||
rowSpacing: Style.space(7)
|
||||
readonly property real cellWidth: (width - columnSpacing) / 2
|
||||
|
||||
Button {
|
||||
width: helpGrid.cellWidth
|
||||
text: "↻ Problem beheben"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("repair")
|
||||
}
|
||||
|
||||
Button {
|
||||
width: helpGrid.cellWidth
|
||||
text: "▣ Support-Paket erstellen"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("support")
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: "Das Support-Paket sammelt Diagnoseinformationen automatisch und maskiert typische persönliche Daten vor dem Export."
|
||||
color: root.muted
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.caption
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
radius: 16
|
||||
color: root.surface
|
||||
border.color: root.border
|
||||
border.width: 1
|
||||
implicitHeight: quickStatus.implicitHeight + Style.space(20)
|
||||
|
||||
Column {
|
||||
id: quickStatus
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Style.space(14)
|
||||
spacing: Style.space(8)
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Style.space(8)
|
||||
|
||||
StatusChip {
|
||||
label: "LUG"
|
||||
value: root.helperState === "ready" ? "bereit" : "fehlt"
|
||||
chipColor: root.stateColor(root.helperState)
|
||||
}
|
||||
|
||||
StatusChip {
|
||||
label: "Launcher"
|
||||
value: root.launcherState === "ready" ? "bereit" : "Setup"
|
||||
chipColor: root.stateColor(root.launcherState)
|
||||
}
|
||||
|
||||
StatusChip {
|
||||
label: "Game"
|
||||
value: root.gameState === "ready" ? "LIVE" : "Setup"
|
||||
chipColor: root.stateColor(root.gameState)
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: "Version " + root.pluginVersion +
|
||||
" · Autopilot " + (root.autopilot === "true" ? "AKTIV" : "aus") +
|
||||
" · LUG " + (root.managedLUGVersion !== "" ? root.managedLUGVersion : "—") +
|
||||
" · Wine " + (root.managedWineVersion !== "" ? root.managedWineVersion : "—")
|
||||
color: root.muted
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.caption
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
width: parent.width
|
||||
text: root.advancedVisible ? "▲ Erweiterte Optionen ausblenden" : "▼ Erweiterte Optionen anzeigen"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.advancedVisible = !root.advancedVisible
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: root.advancedVisible
|
||||
width: parent.width
|
||||
radius: 16
|
||||
color: root.surface
|
||||
border.color: root.border
|
||||
border.width: 1
|
||||
implicitHeight: visible ? advancedContent.implicitHeight + Style.space(20) : 0
|
||||
|
||||
Column {
|
||||
id: advancedContent
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Style.space(14)
|
||||
spacing: Style.space(10)
|
||||
|
||||
Text {
|
||||
text: "ERWEITERTE OPTIONEN"
|
||||
color: root.accent
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
StatusLine {
|
||||
label: "GPU / Vulkan"
|
||||
state: root.hardwareState === "blocked" ? "warning" : (root.hardwareState === "ready" ? "ready" : "unknown")
|
||||
value: root.hardwareState === "blocked" ? "nicht spielbereit" : (root.hardwareGPU !== "" ? root.hardwareGPU : root.hardwareVulkan)
|
||||
}
|
||||
|
||||
StatusLine {
|
||||
label: "Abhängigkeiten"
|
||||
state: root.depsState
|
||||
value: root.depsState === "ready" ? "bereit" : root.depsMissing
|
||||
}
|
||||
|
||||
StatusLine {
|
||||
label: "Support-Helper"
|
||||
state: root.helperState
|
||||
value: root.helperState === "ready"
|
||||
? ((root.helperVersion !== "" ? root.helperVersion : "gefunden") +
|
||||
" · optional")
|
||||
: "nicht installiert"
|
||||
}
|
||||
|
||||
StatusLine {
|
||||
label: "Wine-Prefix"
|
||||
state: root.prefixState
|
||||
value: root.prefixState === "ready"
|
||||
? "initialisiert"
|
||||
: (root.prefixState === "partial" ? "unvollständig · Reparatur möglich" : "nicht eingerichtet")
|
||||
}
|
||||
|
||||
StatusLine {
|
||||
label: "RSI Launcher"
|
||||
state: root.launcherState
|
||||
value: root.launcherState === "ready" ? "installiert" : "fehlt"
|
||||
}
|
||||
|
||||
PanelSeparator { foreground: root.barForeground }
|
||||
|
||||
StatusLine {
|
||||
label: "Autopilot"
|
||||
state: root.autopilot === "true" ? "ready" : "warning"
|
||||
value: root.autopilot === "true"
|
||||
? "aktiv · Wartung alle 6 Stunden"
|
||||
: "nicht aktiviert"
|
||||
}
|
||||
|
||||
StatusLine {
|
||||
label: "Managed Wine"
|
||||
state: root.managedWineVersion !== "" ? "ready" : "unknown"
|
||||
value: root.managedWineVersion !== "" ? root.managedWineVersion : "wird beim Setup geladen"
|
||||
}
|
||||
|
||||
StatusLine {
|
||||
label: "Managed DXVK"
|
||||
state: root.managedDXVKVersion !== "" ? "ready" : "unknown"
|
||||
value: root.managedDXVKVersion !== "" ? root.managedDXVKVersion : "nach Prefix-Setup"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "PLUGIN-UPDATES"
|
||||
color: root.accent
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
StatusLine {
|
||||
label: "Update-Modus"
|
||||
state: root.backendState === "ready" ? "ready" : "missing"
|
||||
value: root.pluginManaged === "git"
|
||||
? (root.pluginAuto === "true" ? "automatisch · alle ~6h" : "Git · manuell")
|
||||
: "ZIP · Git-Installation für Auto-Updates nötig"
|
||||
}
|
||||
|
||||
StatusLine {
|
||||
label: "Plugin-Stand"
|
||||
state: root.pluginUpdateState()
|
||||
value: root.pluginUpdateText()
|
||||
}
|
||||
|
||||
Grid {
|
||||
id: pluginUpdateGrid
|
||||
width: parent.width
|
||||
columns: 2
|
||||
columnSpacing: Style.space(7)
|
||||
rowSpacing: Style.space(7)
|
||||
readonly property real cellWidth: (width - columnSpacing) / 2
|
||||
|
||||
Button {
|
||||
width: pluginUpdateGrid.cellWidth
|
||||
text: "Plugin-Update prüfen"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("plugin-update-check")
|
||||
}
|
||||
|
||||
Button {
|
||||
width: pluginUpdateGrid.cellWidth
|
||||
text: "Plugin jetzt aktualisieren"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
active: root.pluginUpdate === "available"
|
||||
onClicked: root.runAction("plugin-update-now")
|
||||
}
|
||||
|
||||
Button {
|
||||
width: pluginUpdateGrid.cellWidth
|
||||
text: root.pluginAuto === "true" ? "Auto-Updates ausschalten" : "Auto-Updates einschalten"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction(root.pluginAuto === "true" ? "plugin-auto-disable" : "plugin-auto-enable")
|
||||
}
|
||||
|
||||
Button {
|
||||
width: pluginUpdateGrid.cellWidth
|
||||
text: "Updater-Log öffnen"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("updater-log")
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.pluginManaged === "git"
|
||||
? "Auto-Updates verwenden Omarchys eigenen Plugin-Updater mit Validierung und Rollback. Das beim Aktivieren vorhandene Git-Remote wird als vertrauenswürdige Quelle gespeichert."
|
||||
: "Die aktuelle ZIP-Installation bleibt bewusst ohne unbekannte Update-Quelle. Sobald Omarchy Citizen aus einem Git-Repository installiert wird, erkennt das Backend dessen origin automatisch."
|
||||
color: root.muted
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.caption
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
PanelSeparator { foreground: root.barForeground }
|
||||
|
||||
Grid {
|
||||
id: setupGrid
|
||||
width: parent.width
|
||||
columns: 2
|
||||
columnSpacing: Style.space(7)
|
||||
rowSpacing: Style.space(7)
|
||||
readonly property real cellWidth: (width - columnSpacing) / 2
|
||||
|
||||
Button {
|
||||
width: setupGrid.cellWidth
|
||||
text: "Autopilot synchronisieren"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("autopilot-maintain")
|
||||
}
|
||||
|
||||
Button {
|
||||
width: setupGrid.cellWidth
|
||||
text: "Gaming-Stack aktualisieren"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("autopilot-maintain")
|
||||
}
|
||||
|
||||
Button {
|
||||
width: setupGrid.cellWidth
|
||||
text: "Omarchy aktualisieren"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("system-update")
|
||||
}
|
||||
|
||||
Button {
|
||||
width: setupGrid.cellWidth
|
||||
text: "Support-Helper öffnen"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("helper")
|
||||
}
|
||||
|
||||
Button {
|
||||
width: setupGrid.cellWidth
|
||||
text: "Wine-Selbsttest"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("wine-doctor")
|
||||
}
|
||||
|
||||
Button {
|
||||
width: setupGrid.cellWidth
|
||||
text: "DXVK automatisch prüfen"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("autopilot-maintain")
|
||||
}
|
||||
|
||||
Button {
|
||||
width: setupGrid.cellWidth
|
||||
text: "Gaming-Stack reparieren"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("repair")
|
||||
}
|
||||
|
||||
Button {
|
||||
width: setupGrid.cellWidth
|
||||
text: "Launcher-Dateien reparieren"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("repair")
|
||||
}
|
||||
|
||||
Button {
|
||||
width: setupGrid.cellWidth
|
||||
text: "Start-Log öffnen"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("log")
|
||||
}
|
||||
|
||||
Button {
|
||||
width: setupGrid.cellWidth
|
||||
text: "Plugin-Debuglog öffnen"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("debug-log")
|
||||
}
|
||||
}
|
||||
|
||||
PanelSeparator { foreground: root.barForeground }
|
||||
|
||||
Text {
|
||||
text: "NGL · EXPERIMENTELL"
|
||||
color: root.warning
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Grid {
|
||||
id: nglGrid
|
||||
width: parent.width
|
||||
columns: 2
|
||||
columnSpacing: Style.space(7)
|
||||
rowSpacing: Style.space(7)
|
||||
readonly property real cellWidth: (width - columnSpacing) / 2
|
||||
|
||||
Button {
|
||||
width: nglGrid.cellWidth
|
||||
text: "NGL Download"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("ngl-page")
|
||||
}
|
||||
|
||||
Button {
|
||||
width: nglGrid.cellWidth
|
||||
text: "NGL installieren"
|
||||
foreground: root.barForeground
|
||||
fontFamily: root.uiFont
|
||||
bordered: true
|
||||
onClicked: root.runAction("ngl-install")
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: "NGL bleibt bewusst optional. Der normale RSI/LUG-Weg funktioniert unabhängig davon."
|
||||
color: root.warning
|
||||
opacity: 0.82
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.caption
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: "Ziel: spielen statt Linux administrieren. Für Support genügt normalerweise „Support-Paket erstellen“."
|
||||
color: root.muted
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.caption
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component StatusChip: Rectangle {
|
||||
id: chip
|
||||
property string label: ""
|
||||
property string value: ""
|
||||
property color chipColor: root.accent
|
||||
|
||||
radius: 999
|
||||
color: Qt.rgba(chip.chipColor.r, chip.chipColor.g, chip.chipColor.b, 0.12)
|
||||
border.color: chip.chipColor
|
||||
border.width: 1
|
||||
implicitWidth: chipRow.implicitWidth + Style.space(16)
|
||||
implicitHeight: chipRow.implicitHeight + Style.space(9)
|
||||
|
||||
Row {
|
||||
id: chipRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.space(5)
|
||||
|
||||
Text {
|
||||
text: chip.label
|
||||
color: root.muted
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.caption
|
||||
}
|
||||
|
||||
Text {
|
||||
text: chip.value
|
||||
color: chip.chipColor
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.caption
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component StatusLine: Rectangle {
|
||||
id: statusLine
|
||||
property string label: ""
|
||||
property string value: ""
|
||||
property string state: "unknown"
|
||||
|
||||
width: parent.width
|
||||
radius: 11
|
||||
color: root.surfaceRaised
|
||||
border.color: root.border
|
||||
border.width: 1
|
||||
implicitHeight: lineRow.implicitHeight + Style.space(12)
|
||||
|
||||
Row {
|
||||
id: lineRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Style.space(10)
|
||||
spacing: Style.space(8)
|
||||
|
||||
Rectangle {
|
||||
width: 9
|
||||
height: 9
|
||||
radius: 5
|
||||
color: root.stateColor(statusLine.state)
|
||||
}
|
||||
|
||||
Text {
|
||||
width: Style.space(118)
|
||||
text: statusLine.label
|
||||
color: root.muted
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
}
|
||||
|
||||
Text {
|
||||
width: Math.max(0, lineRow.width - Style.space(158))
|
||||
text: root.stateIcon(statusLine.state) + " " + statusLine.value
|
||||
color: root.barForeground
|
||||
font.family: root.uiFont
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u
|
||||
|
||||
PLUGIN_ID="local.omarchy-citizen"
|
||||
PLUGIN_VERSION="0.9.0"
|
||||
|
||||
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
|
||||
XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
|
||||
XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
|
||||
|
||||
PLUGIN_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SELF="$(readlink -f "${BASH_SOURCE[0]}")"
|
||||
STATE_DIR="$XDG_STATE_HOME/citizen-launcher"
|
||||
CACHE_DIR="$XDG_CACHE_HOME/citizen-launcher"
|
||||
DATA_DIR="$XDG_DATA_HOME/citizen-launcher"
|
||||
LOG_FILE="$STATE_DIR/omarchy-citizen.log"
|
||||
UPDATER_LOG="$STATE_DIR/updater.log"
|
||||
RSI_LOG="$STATE_DIR/rsi-launcher.log"
|
||||
SANITIZER="$PLUGIN_DIR/support-sanitize.py"
|
||||
BACKEND_EXTERNAL="$HOME/.local/lib/citizen-launcher/citizen-launcher"
|
||||
BACKEND_BUNDLED="$PLUGIN_DIR/backend/bin/citizen-launcher"
|
||||
MANAGED_LUG="$DATA_DIR/vendor/lug-helper/lug-helper.appimage"
|
||||
MANAGED_WINE="$DATA_DIR/vendor/wine/current"
|
||||
|
||||
mkdir -p "$STATE_DIR" "$CACHE_DIR"
|
||||
ACTION="${1:-status}"
|
||||
|
||||
rotate_log() {
|
||||
local max=$((4 * 1024 * 1024)) keep=$((2 * 1024 * 1024)) size=0
|
||||
if [[ -f "$LOG_FILE" ]]; then
|
||||
size="$(stat -c '%s' "$LOG_FILE" 2>/dev/null || echo 0)"
|
||||
if (( size > max )); then
|
||||
tail -c "$keep" "$LOG_FILE" > "$LOG_FILE.tmp" 2>/dev/null || true
|
||||
mv -f "$LOG_FILE.tmp" "$LOG_FILE" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
log_line() {
|
||||
printf '%s [%s] %s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z')" "$$" "$*" >>"$LOG_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
rotate_log
|
||||
log_line "action=$ACTION version=$PLUGIN_VERSION"
|
||||
|
||||
notify() {
|
||||
local title="$1" body="$2"
|
||||
if command -v omarchy-notification-send >/dev/null 2>&1; then
|
||||
omarchy-notification-send "$title" "$body" >/dev/null 2>&1 || true
|
||||
elif command -v notify-send >/dev/null 2>&1; then
|
||||
notify-send "$title" "$body" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
notify_error() {
|
||||
local body="$1"
|
||||
log_line "error=$body"
|
||||
if command -v zenity >/dev/null 2>&1; then
|
||||
zenity --error --width=560 --title="Citizen Launcher" --text="$body" >/dev/null 2>&1 || true
|
||||
else
|
||||
notify "Citizen Launcher" "$body"
|
||||
printf 'Citizen Launcher: %s\n' "$body" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
find_backend() {
|
||||
if [[ -x "$BACKEND_EXTERNAL" ]]; then
|
||||
printf '%s\n' "$BACKEND_EXTERNAL"
|
||||
elif [[ -x "$BACKEND_BUNDLED" ]]; then
|
||||
printf '%s\n' "$BACKEND_BUNDLED"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
backend_exec() {
|
||||
local backend
|
||||
backend="$(find_backend)" || {
|
||||
printf 'Go-Backend nicht gefunden. Plugin bitte neu installieren.\n' >&2
|
||||
return 127
|
||||
}
|
||||
"$backend" "$@"
|
||||
}
|
||||
|
||||
backend_game_status() { backend_exec game-status; }
|
||||
backend_plugin_status() { backend_exec status; }
|
||||
backend_game_install() { backend_exec game-install; }
|
||||
backend_game_launch() { backend_exec game-launch; }
|
||||
backend_game_repair() { backend_exec game-repair; }
|
||||
autopilot_enable() { backend_exec autopilot enable; }
|
||||
autopilot_maintain() { backend_exec maintain; }
|
||||
wine_doctor() { backend_exec doctor; }
|
||||
|
||||
kv_get() {
|
||||
local key="$1"
|
||||
sed -n "s/^${key}=//p" | head -n1
|
||||
}
|
||||
|
||||
status() {
|
||||
local game="" plugin=""
|
||||
local health="setup" hardware="unknown" hardware_reason="" gpu="" vulkan="unknown"
|
||||
local prefix="missing" launcher="missing" game_state="missing" wine="" dxvk="" lug="" game_autopilot="false"
|
||||
local backend_state="missing" backend_version="" managed="manual" auto_apply="false" auto_maintain="false"
|
||||
local update="unavailable" local_commit="" remote_commit="" maintenance=""
|
||||
|
||||
if game="$(backend_game_status 2>/dev/null)"; then
|
||||
health="$(printf '%s\n' "$game" | kv_get health)"
|
||||
hardware="$(printf '%s\n' "$game" | kv_get hardware)"
|
||||
hardware_reason="$(printf '%s\n' "$game" | kv_get hardware_reason)"
|
||||
gpu="$(printf '%s\n' "$game" | kv_get gpu)"
|
||||
vulkan="$(printf '%s\n' "$game" | kv_get vulkan)"
|
||||
prefix="$(printf '%s\n' "$game" | kv_get prefix_state)"
|
||||
launcher="$(printf '%s\n' "$game" | kv_get launcher_state)"
|
||||
game_state="$(printf '%s\n' "$game" | kv_get game_state)"
|
||||
wine="$(printf '%s\n' "$game" | kv_get wine_version)"
|
||||
dxvk="$(printf '%s\n' "$game" | kv_get dxvk_version)"
|
||||
lug="$(printf '%s\n' "$game" | kv_get lug_version)"
|
||||
game_autopilot="$(printf '%s\n' "$game" | kv_get autopilot)"
|
||||
fi
|
||||
|
||||
if plugin="$(backend_plugin_status 2>/dev/null)"; then
|
||||
backend_state="ready"
|
||||
backend_version="$(printf '%s\n' "$plugin" | kv_get backend_version)"
|
||||
managed="$(printf '%s\n' "$plugin" | kv_get managed)"
|
||||
auto_apply="$(printf '%s\n' "$plugin" | kv_get auto_apply)"
|
||||
auto_maintain="$(printf '%s\n' "$plugin" | kv_get auto_maintain)"
|
||||
update="$(printf '%s\n' "$plugin" | kv_get update)"
|
||||
local_commit="$(printf '%s\n' "$plugin" | kv_get local_commit)"
|
||||
remote_commit="$(printf '%s\n' "$plugin" | kv_get remote_commit)"
|
||||
maintenance="$(printf '%s\n' "$plugin" | kv_get maintenance_result)"
|
||||
fi
|
||||
|
||||
[[ -n "$health" ]] || health="setup"
|
||||
[[ -n "$prefix" ]] || prefix="missing"
|
||||
[[ -n "$launcher" ]] || launcher="missing"
|
||||
[[ -n "$game_state" ]] || game_state="missing"
|
||||
[[ -n "$hardware" ]] || hardware="unknown"
|
||||
[[ -n "$vulkan" ]] || vulkan="unknown"
|
||||
[[ -n "$update" ]] || update="unavailable"
|
||||
|
||||
printf 'plugin_version=%s\n' "$PLUGIN_VERSION"
|
||||
printf 'health=%s\n' "$health"
|
||||
printf 'deps=ready\n'
|
||||
printf 'deps_missing=\n'
|
||||
printf 'helper=%s\n' "$([[ -n "$lug" ]] && echo ready || echo missing)"
|
||||
printf 'helper_managed=optional\n'
|
||||
printf 'helper_version=%s\n' "$lug"
|
||||
printf 'update=unknown\n'
|
||||
printf 'update_version=\n'
|
||||
printf 'prefix=%s\n' "$prefix"
|
||||
printf 'prefix_detail=%s\n' "$([[ "$prefix" == partial ]] && echo unvollständig || true)"
|
||||
printf 'launcher=%s\n' "$launcher"
|
||||
printf 'game=%s\n' "$game_state"
|
||||
printf 'backend=%s\n' "$backend_state"
|
||||
printf 'backend_version=%s\n' "$backend_version"
|
||||
printf 'plugin_managed=%s\n' "$managed"
|
||||
printf 'plugin_auto=%s\n' "$auto_apply"
|
||||
printf 'plugin_update=%s\n' "$update"
|
||||
printf 'plugin_local_commit=%s\n' "$local_commit"
|
||||
printf 'plugin_remote_commit=%s\n' "$remote_commit"
|
||||
printf 'autopilot=%s\n' "$([[ "$auto_maintain" == true ]] && echo true || echo "$game_autopilot")"
|
||||
printf 'managed_lug_version=%s\n' "$lug"
|
||||
printf 'managed_wine_version=%s\n' "$wine"
|
||||
printf 'managed_dxvk_version=%s\n' "$dxvk"
|
||||
printf 'maintenance_result=%s\n' "$maintenance"
|
||||
printf 'hardware=%s\n' "$hardware"
|
||||
printf 'hardware_reason=%s\n' "$hardware_reason"
|
||||
printf 'hardware_gpu=%s\n' "$gpu"
|
||||
printf 'hardware_vulkan=%s\n' "$vulkan"
|
||||
}
|
||||
|
||||
hardware_info() {
|
||||
local game reason gpu vulkan
|
||||
game="$(backend_game_status 2>/dev/null || true)"
|
||||
reason="$(printf '%s\n' "$game" | kv_get hardware_reason)"
|
||||
gpu="$(printf '%s\n' "$game" | kv_get gpu)"
|
||||
vulkan="$(printf '%s\n' "$game" | kv_get vulkan)"
|
||||
if command -v zenity >/dev/null 2>&1; then
|
||||
zenity --warning --width=620 --title="Omarchy Citizen – Hardware" \
|
||||
--text="<b>Star Citizen ist auf diesem System noch nicht spielbereit.</b>\n\nGPU:\n${gpu:-nicht erkannt}\n\nVulkan: ${vulkan:-unbekannt}\n\n${reason:-Hardware-Prüfung fehlgeschlagen.}" >/dev/null 2>&1 || true
|
||||
else
|
||||
printf 'GPU: %s\nVulkan: %s\n%s\n' "${gpu:-nicht erkannt}" "${vulkan:-unbekannt}" "${reason:-Hardware-Prüfung fehlgeschlagen.}"
|
||||
fi
|
||||
}
|
||||
|
||||
probe_detached_start() {
|
||||
local label="$1"; shift
|
||||
"$@" >>"$LOG_FILE" 2>&1 &
|
||||
local pid=$! rc=0
|
||||
sleep 0.35
|
||||
if ! kill -0 "$pid" 2>/dev/null; then
|
||||
wait "$pid" || rc=$?
|
||||
if (( rc != 0 )); then
|
||||
log_line "$label failed rc=$rc"
|
||||
return "$rc"
|
||||
fi
|
||||
else
|
||||
disown "$pid" 2>/dev/null || true
|
||||
fi
|
||||
log_line "$label accepted"
|
||||
}
|
||||
|
||||
launch_terminal_action() {
|
||||
local action="$1"
|
||||
log_line "launch-terminal action=$action"
|
||||
if command -v uwsm-app >/dev/null 2>&1 && command -v xdg-terminal-exec >/dev/null 2>&1; then
|
||||
probe_detached_start "uwsm-xdg-terminal" setsid uwsm-app -- xdg-terminal-exec \
|
||||
--app-id=org.omarchy.terminal --title="Citizen Launcher" -e bash "$SELF" "$action" && return 0
|
||||
fi
|
||||
if command -v xdg-terminal-exec >/dev/null 2>&1; then
|
||||
probe_detached_start "xdg-terminal" xdg-terminal-exec --title="Citizen Launcher" -e bash "$SELF" "$action" && return 0
|
||||
fi
|
||||
if command -v omarchy-launch-floating-terminal-with-presentation >/dev/null 2>&1; then
|
||||
probe_detached_start "omarchy-terminal" omarchy-launch-floating-terminal-with-presentation bash "$SELF" "$action" && return 0
|
||||
fi
|
||||
notify_error "Das Omarchy-Citizen-Terminal konnte nicht geöffnet werden. Bitte ein Support-Paket erstellen."
|
||||
return 1
|
||||
}
|
||||
|
||||
install_stack_terminal() {
|
||||
set -Ee
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
setup_failed() {
|
||||
local rc=$?
|
||||
trap - ERR
|
||||
echo
|
||||
echo "=================================================="
|
||||
echo " SETUP KONNTE NICHT ABGESCHLOSSEN WERDEN"
|
||||
echo "=================================================="
|
||||
echo "Fehlercode: $rc"
|
||||
echo "Alle Details wurden protokolliert. Im SC-Panel genügt 'Support-Paket erstellen'."
|
||||
notify "Citizen Launcher" "Setup fehlgeschlagen. Bitte Support-Paket erstellen."
|
||||
if [[ -t 0 ]]; then read -r -p "Drücke Enter zum Schließen … " _ || true; fi
|
||||
exit "$rc"
|
||||
}
|
||||
trap setup_failed ERR
|
||||
|
||||
echo "=================================================="
|
||||
echo " Omarchy Citizen – Spielbereit machen"
|
||||
echo "=================================================="
|
||||
echo
|
||||
|
||||
local game health reason
|
||||
game="$(backend_game_status 2>/dev/null || true)"
|
||||
health="$(printf '%s\n' "$game" | kv_get health)"
|
||||
if [[ "$health" == "hardware-blocked" ]]; then
|
||||
reason="$(printf '%s\n' "$game" | kv_get hardware_reason)"
|
||||
echo "Hardware-Prüfung nicht bestanden:"
|
||||
echo "${reason:-Keine Star-Citizen-taugliche Vulkan-GPU erkannt.}"
|
||||
trap - ERR
|
||||
return 2
|
||||
fi
|
||||
|
||||
echo "1/4 Autopilot aktivieren und Gaming-Stack synchronisieren …"
|
||||
autopilot_enable
|
||||
echo
|
||||
echo "2/4 Wine-Runner auf diesem Rechner prüfen …"
|
||||
wine_doctor
|
||||
echo
|
||||
echo "3/4 Wine-Prefix, Komponenten und offiziellen RSI Launcher einrichten …"
|
||||
backend_game_install
|
||||
echo
|
||||
echo "4/4 Wartungsstand abschließend synchronisieren …"
|
||||
autopilot_maintain || true
|
||||
|
||||
trap - ERR
|
||||
echo
|
||||
echo "=================================================="
|
||||
echo " SOFTWARE-STACK BEREIT"
|
||||
echo "=================================================="
|
||||
echo "Der offizielle RSI Launcher ist eingerichtet. Anmeldung und Spieldownload erfolgen dort."
|
||||
notify "Citizen Launcher" "Software-Stack ist bereit. RSI Launcher kann gestartet werden."
|
||||
}
|
||||
|
||||
repair_stack_terminal() {
|
||||
set -Ee
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
repair_failed() {
|
||||
local rc=$?
|
||||
trap - ERR
|
||||
echo
|
||||
echo "REPARATUR FEHLGESCHLAGEN (Code $rc)"
|
||||
echo "Bitte im SC-Panel ein Support-Paket erstellen."
|
||||
notify "Citizen Launcher" "Reparatur fehlgeschlagen. Bitte Support-Paket erstellen."
|
||||
if [[ -t 0 ]]; then read -r -p "Drücke Enter zum Schließen … " _ || true; fi
|
||||
exit "$rc"
|
||||
}
|
||||
trap repair_failed ERR
|
||||
|
||||
echo "Omarchy Citizen repariert den verwalteten Gaming-Stack …"
|
||||
autopilot_maintain || true
|
||||
backend_game_repair
|
||||
wine_doctor
|
||||
trap - ERR
|
||||
echo "Reparatur abgeschlossen."
|
||||
notify "Citizen Launcher" "Gaming-Stack wurde repariert."
|
||||
}
|
||||
|
||||
update_stack_terminal() {
|
||||
set -Ee
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
echo "Synchronisiere Wine, DXVK, RSI Launcher und optionale Support-Werkzeuge …"
|
||||
autopilot_maintain
|
||||
echo "Gaming-Stack ist aktuell."
|
||||
}
|
||||
|
||||
system_update_terminal() {
|
||||
set -Ee
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
if ! command -v omarchy >/dev/null 2>&1; then
|
||||
echo "Omarchy-Befehl nicht gefunden." >&2
|
||||
return 1
|
||||
fi
|
||||
echo "Starte den offiziellen Omarchy-Systemupdate-Pfad …"
|
||||
omarchy update -y
|
||||
}
|
||||
|
||||
primary_action() {
|
||||
local game health
|
||||
game="$(backend_game_status 2>/dev/null || true)"
|
||||
health="$(printf '%s\n' "$game" | kv_get health)"
|
||||
case "$health" in
|
||||
hardware-blocked) hardware_info ;;
|
||||
ready|install-game) backend_game_launch ;;
|
||||
repair|launcher-repair) launch_terminal_action repair-stack-terminal ;;
|
||||
*) launch_terminal_action install-stack-terminal ;;
|
||||
esac
|
||||
}
|
||||
|
||||
repair_assistant() {
|
||||
launch_terminal_action repair-stack-terminal
|
||||
}
|
||||
|
||||
plugin_update_check() { backend_exec check; }
|
||||
plugin_update_now() { backend_exec update; }
|
||||
plugin_auto_enable() { backend_exec auto enable; }
|
||||
plugin_auto_disable() { backend_exec auto disable; }
|
||||
|
||||
open_file() {
|
||||
local path="$1"
|
||||
[[ -f "$path" ]] || { notify_error "Datei noch nicht vorhanden: $path"; return 1; }
|
||||
xdg-open "$path" >/dev/null 2>&1 &
|
||||
}
|
||||
|
||||
open_debug_log() { open_file "$LOG_FILE"; }
|
||||
open_updater_log() { open_file "$UPDATER_LOG"; }
|
||||
open_log() { open_file "$RSI_LOG"; }
|
||||
open_wiki() { xdg-open "https://wiki.starcitizen-lug.org" >/dev/null 2>&1 & }
|
||||
open_ngl_page() { xdg-open "https://www.nglproject.com/de#downloads" >/dev/null 2>&1 & }
|
||||
|
||||
open_helper() {
|
||||
if [[ -x "$MANAGED_LUG" ]]; then
|
||||
nohup env APPIMAGE_EXTRACT_AND_RUN=1 "$MANAGED_LUG" >>"$LOG_FILE" 2>&1 &
|
||||
else
|
||||
open_wiki
|
||||
fi
|
||||
}
|
||||
|
||||
sanitize_to() {
|
||||
local source="$1" target="$2"
|
||||
[[ -f "$source" ]] || return 0
|
||||
if command -v python3 >/dev/null 2>&1 && [[ -f "$SANITIZER" ]]; then
|
||||
python3 "$SANITIZER" <"$source" >"$target"
|
||||
else
|
||||
sed -e "s#${HOME//\#/\\#}#~#g" "$source" >"$target"
|
||||
fi
|
||||
}
|
||||
|
||||
capture_sanitized() {
|
||||
local target="$1"; shift
|
||||
local tmp="$CACHE_DIR/capture-$$-$RANDOM.txt"
|
||||
"$@" >"$tmp" 2>&1 || true
|
||||
sanitize_to "$tmp" "$target"
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
support_build() {
|
||||
local stamp support_id work downloads archive game_prefix=""
|
||||
stamp="$(date '+%Y%m%d-%H%M%S')"
|
||||
support_id="OC-${stamp}-$(printf '%s-%s-%s' "$stamp" "$$" "$RANDOM" | sha256sum | cut -c1-6)"
|
||||
downloads="$(command -v xdg-user-dir >/dev/null 2>&1 && xdg-user-dir DOWNLOAD 2>/dev/null || true)"
|
||||
[[ -n "$downloads" && -d "$downloads" ]] || downloads="$HOME/Downloads"
|
||||
mkdir -p "$downloads"
|
||||
work="$CACHE_DIR/support-$support_id"
|
||||
rm -rf "$work" && mkdir -p "$work"
|
||||
|
||||
cat >"$work/README_SUPPORT.txt" <<SUPPORT
|
||||
Omarchy Citizen Support-Paket
|
||||
Support-ID: $support_id
|
||||
Plugin-Version: $PLUGIN_VERSION
|
||||
Erstellt: $(date '+%Y-%m-%d %H:%M:%S %z')
|
||||
|
||||
Das Paket enthält technische Diagnoseinformationen. Home-Pfad, Benutzername,
|
||||
Hostname, E-Mail/IP/MAC und typische Token-/Sessionwerte werden beim Export
|
||||
best-effort maskiert. Keine Browserdaten, SSH-Schlüssel oder Passwortdateien.
|
||||
SUPPORT
|
||||
|
||||
status >"$work/citizen-status.txt" 2>&1 || true
|
||||
capture_sanitized "$work/backend-game-status.txt" backend_exec game-status
|
||||
capture_sanitized "$work/backend-status.txt" backend_exec status
|
||||
capture_sanitized "$work/wine-doctor.txt" backend_exec doctor
|
||||
|
||||
if command -v omarchy >/dev/null 2>&1; then
|
||||
capture_sanitized "$work/plugin-validation.txt" omarchy plugin validate "$PLUGIN_DIR"
|
||||
if omarchy debug --no-sudo --print >"$CACHE_DIR/omarchy-debug-$$.txt" 2>&1; then
|
||||
sanitize_to "$CACHE_DIR/omarchy-debug-$$.txt" "$work/omarchy-debug.txt"
|
||||
else
|
||||
{ omarchy version 2>/dev/null || true; omarchy commands --all 2>/dev/null || true; } >"$CACHE_DIR/omarchy-debug-$$.txt"
|
||||
sanitize_to "$CACHE_DIR/omarchy-debug-$$.txt" "$work/omarchy-debug.txt"
|
||||
fi
|
||||
rm -f "$CACHE_DIR/omarchy-debug-$$.txt"
|
||||
fi
|
||||
|
||||
{
|
||||
echo "=== kernel ==="; uname -rmo 2>/dev/null || true
|
||||
echo "=== memory ==="; free -h 2>/dev/null || true
|
||||
echo "=== filesystems ==="; df -hT / "$HOME" 2>/dev/null || true
|
||||
echo "=== gpu ==="; lspci -nnk 2>/dev/null | grep -A5 -Ei 'VGA|3D|Display' || true
|
||||
echo "=== vulkan ==="; command -v vulkaninfo >/dev/null 2>&1 && vulkaninfo --summary 2>&1 || true
|
||||
echo "=== limits ==="; sysctl vm.max_map_count 2>/dev/null || true; ulimit -Hn 2>/dev/null || true
|
||||
} >"$CACHE_DIR/system-$$.txt"
|
||||
sanitize_to "$CACHE_DIR/system-$$.txt" "$work/system.txt"
|
||||
rm -f "$CACHE_DIR/system-$$.txt"
|
||||
|
||||
for pair in "$LOG_FILE:omarchy-citizen.log" "$UPDATER_LOG:updater.log" "$RSI_LOG:rsi-launcher.log"; do
|
||||
local src="${pair%%:*}" dst="${pair#*:}"
|
||||
if [[ -f "$src" ]]; then
|
||||
tail -n 1800 "$src" >"$CACHE_DIR/log-$$.txt"
|
||||
sanitize_to "$CACHE_DIR/log-$$.txt" "$work/$dst"
|
||||
rm -f "$CACHE_DIR/log-$$.txt"
|
||||
fi
|
||||
done
|
||||
|
||||
game_prefix="$(backend_game_status 2>/dev/null | kv_get prefix || true)"
|
||||
if [[ -n "$game_prefix" ]]; then
|
||||
printf 'prefix=%s\n' "$game_prefix" >"$CACHE_DIR/prefix-$$.txt"
|
||||
sanitize_to "$CACHE_DIR/prefix-$$.txt" "$work/prefix.txt"
|
||||
rm -f "$CACHE_DIR/prefix-$$.txt"
|
||||
fi
|
||||
|
||||
archive="$downloads/Omarchy-Citizen-Support-$support_id.tar.gz"
|
||||
tar -C "$work" -czf "$archive" .
|
||||
rm -rf "$work"
|
||||
notify "Citizen Launcher" "Support-Paket erstellt: $(basename "$archive")"
|
||||
command -v xdg-open >/dev/null 2>&1 && xdg-open "$downloads" >/dev/null 2>&1 &
|
||||
printf '%s\n' "$archive"
|
||||
}
|
||||
|
||||
ngl_info() {
|
||||
local msg="NGL ist ein optionaler Community-Mod für den RSI Launcher. Omarchy Citizen installiert ihn nur nach deiner ausdrücklichen Dateiauswahl in den verwalteten Wine-Prefix."
|
||||
if command -v zenity >/dev/null 2>&1; then
|
||||
zenity --info --width=540 --title="Omarchy Citizen – NGL" --text="$msg" >/dev/null 2>&1 || true
|
||||
else
|
||||
printf '%s\n' "$msg"
|
||||
fi
|
||||
}
|
||||
|
||||
ngl_install() {
|
||||
local game prefix selected exe workdir wine
|
||||
command -v zenity >/dev/null 2>&1 || { notify_error "Für die NGL-Dateiauswahl wird zenity benötigt."; return 1; }
|
||||
game="$(backend_game_status)"
|
||||
prefix="$(printf '%s\n' "$game" | kv_get prefix)"
|
||||
[[ -n "$prefix" && -d "$prefix/drive_c" ]] || { notify_error "Star Citizen muss zuerst eingerichtet werden."; return 1; }
|
||||
wine="$(readlink -f "$MANAGED_WINE")/bin/wine"
|
||||
[[ -x "$wine" ]] || { notify_error "Der verwaltete Wine-Runner fehlt."; return 1; }
|
||||
|
||||
selected="$(zenity --file-selection --title="NGL Installer auswählen" --filename="$HOME/Downloads/" \
|
||||
--file-filter="NGL Installer | *.zip *.ZIP *.exe *.EXE" --file-filter="Alle Dateien | *" 2>/dev/null)" || return 0
|
||||
[[ -f "$selected" ]] || return 1
|
||||
exe="$selected"
|
||||
if [[ "${selected,,}" == *.zip ]]; then
|
||||
workdir="$CACHE_DIR/ngl-installer"
|
||||
rm -rf "$workdir" && mkdir -p "$workdir"
|
||||
unzip -q "$selected" -d "$workdir" || { notify_error "NGL-ZIP konnte nicht entpackt werden."; return 1; }
|
||||
exe="$(find "$workdir" -type f -iname '*.exe' -print -quit)"
|
||||
fi
|
||||
[[ -n "$exe" && -f "$exe" ]] || { notify_error "Kein Windows-Installer gefunden."; return 1; }
|
||||
zenity --question --width=560 --title="NGL experimentell installieren?" \
|
||||
--ok-label="Installer starten" --cancel-label="Abbrechen" \
|
||||
--text="NGL ist Community-Code und läuft im selben Wine-Prefix wie der RSI Launcher. Bitte nur Downloads von nglproject.com verwenden." 2>/dev/null || return 0
|
||||
nohup env WINEPREFIX="$prefix" WINEDEBUG=-all "$wine" "$exe" >>"$LOG_FILE" 2>&1 &
|
||||
}
|
||||
|
||||
case "$ACTION" in
|
||||
status) status ;;
|
||||
hardware-info) hardware_info ;;
|
||||
primary) primary_action ;;
|
||||
setup|install-stack) launch_terminal_action install-stack-terminal ;;
|
||||
install-stack-terminal) install_stack_terminal ;;
|
||||
repair|repair-interactive|recover-prefix) repair_assistant ;;
|
||||
repair-stack-terminal|recover-prefix-terminal) repair_stack_terminal ;;
|
||||
support|support-build) support_build ;;
|
||||
debug-log) open_debug_log ;;
|
||||
log) open_log ;;
|
||||
updater-log) open_updater_log ;;
|
||||
plugin-update-check|check-update) plugin_update_check ;;
|
||||
plugin-update-now) plugin_update_now ;;
|
||||
plugin-auto-enable) plugin_auto_enable ;;
|
||||
plugin-auto-disable) plugin_auto_disable ;;
|
||||
autopilot-enable) autopilot_enable ;;
|
||||
autopilot-maintain|update-helper) launch_terminal_action update-stack-terminal ;;
|
||||
update-stack-terminal|update-helper-terminal) update_stack_terminal ;;
|
||||
wine-doctor) launch_terminal_action wine-doctor-terminal ;;
|
||||
wine-doctor-terminal) set -e; wine_doctor ;;
|
||||
system-update) launch_terminal_action system-update-terminal ;;
|
||||
system-update-terminal) system_update_terminal ;;
|
||||
launch) backend_game_launch ;;
|
||||
helper) open_helper ;;
|
||||
preflight) open_helper ;;
|
||||
runners|dxvk) launch_terminal_action update-stack-terminal ;;
|
||||
repair-launch|repair-rsi) launch_terminal_action repair-stack-terminal ;;
|
||||
dirs) ( xdg-open "$DATA_DIR" >/dev/null 2>&1 & ) ;;
|
||||
wiki) open_wiki ;;
|
||||
ngl-page) open_ngl_page ;;
|
||||
ngl-info) ngl_info ;;
|
||||
ngl-install|ngl-install-interactive) ngl_install ;;
|
||||
*) notify_error "Unbekannte Omarchy-Citizen-Aktion: $ACTION"; exit 2 ;;
|
||||
esac
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "local.omarchy-citizen",
|
||||
"name": "Citizen Launcher · Omarchy",
|
||||
"version": "0.9.0",
|
||||
"author": "Community prototype",
|
||||
"license": "MIT",
|
||||
"description": "Optional Omarchy bar integration for the distro-neutral Citizen Launcher.",
|
||||
"kinds": [
|
||||
"bar-widget"
|
||||
],
|
||||
"entryPoints": {
|
||||
"barWidget": "BarWidget.qml"
|
||||
},
|
||||
"barWidget": {
|
||||
"displayName": "Omarchy Citizen",
|
||||
"category": "Gaming",
|
||||
"allowMultiple": false,
|
||||
"defaultSection": "right"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
|
||||
HOME = os.path.expanduser("~")
|
||||
USER = os.environ.get("USER", "")
|
||||
HOST = socket.gethostname()
|
||||
|
||||
patterns = [
|
||||
(re.compile(r"(?i)(https?://)[^/@\s]+@"), r"\1<credentials>@"),
|
||||
(re.compile(re.escape(HOME)), "~"),
|
||||
(re.compile(r"(?i)\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b"), "<email>"),
|
||||
(re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b"), "<ip>"),
|
||||
(re.compile(r"(?i)\b(?:[0-9a-f]{2}:){5}[0-9a-f]{2}\b"), "<mac>"),
|
||||
(re.compile(r"(?i)\bBearer\s+[A-Za-z0-9._~+/=-]+"), "Bearer <redacted>"),
|
||||
(re.compile(r"(?i)\b(authorization|password|passwd|secret|token|access_token|refresh_token|session|cookie)\s*[:=]\s*[^\s,;]+"),
|
||||
r"\1=<redacted>"),
|
||||
(re.compile(r"(?i)([?&](?:token|access_token|refresh_token|session|auth|key|secret)=)[^&#\s]+"),
|
||||
r"\1<redacted>"),
|
||||
(re.compile(r"\beyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b"), "<jwt>"),
|
||||
]
|
||||
|
||||
if USER:
|
||||
patterns.append((re.compile(rf"(?<![A-Za-z0-9_.-]){re.escape(USER)}(?![A-Za-z0-9_.-])"), "<user>"))
|
||||
if HOST:
|
||||
patterns.append((re.compile(rf"(?<![A-Za-z0-9_.-]){re.escape(HOST)}(?![A-Za-z0-9_.-])"), "<host>"))
|
||||
|
||||
for line in sys.stdin:
|
||||
for pattern, repl in patterns:
|
||||
line = pattern.sub(repl, line)
|
||||
sys.stdout.write(line)
|
||||
Reference in New Issue
Block a user