diff --git a/Modules/IPC/IPCManager.qml b/Modules/IPC/IPCManager.qml index 8c541a3..204dbbe 100644 --- a/Modules/IPC/IPCManager.qml +++ b/Modules/IPC/IPCManager.qml @@ -22,7 +22,9 @@ Item { IpcHandler { target: "screenRecorder" function toggle() { - ScreenRecorderService.toggleRecording() + if (ScreenRecorderService.isAvailable) { + ScreenRecorderService.toggleRecording() + } } } diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index beeccc6..725a30b 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -735,12 +735,28 @@ Loader { color: powerButtonArea.containsMouse ? Color.mOnError : Color.mError } - // Tooltip - NTooltip { - id: tooltipShutdown - target: parent - positionAbove: true - text: "Shut down" + // Tooltip (inline rectangle to avoid separate Window during lock) + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.top + anchors.bottomMargin: 12 * scaling + radius: Style.radiusM * scaling + color: Color.mSurface + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + visible: powerButtonArea.containsMouse + z: 1 + NText { + id: shutdownTooltipText + anchors.margins: Style.marginM * scaling + anchors.fill: parent + text: "Shut down the computer." + font.pointSize: Style.fontSizeM * scaling + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + implicitWidth: shutdownTooltipText.implicitWidth + Style.marginM * 2 * scaling + implicitHeight: shutdownTooltipText.implicitHeight + Style.marginM * 2 * scaling } MouseArea { @@ -750,8 +766,6 @@ Loader { onClicked: { CompositorService.shutdown() } - onEntered: tooltipShutdown.show() - onExited: tooltipShutdown.hide() } } @@ -773,11 +787,27 @@ Loader { } // Tooltip - NTooltip { - id: tooltipRestart - target: parent - positionAbove: true - text: "Restart" + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.top + anchors.bottomMargin: 12 * scaling + radius: Style.radiusM * scaling + color: Color.mSurface + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + visible: restartButtonArea.containsMouse + z: 1 + NText { + id: restartTooltipText + anchors.margins: Style.marginM * scaling + anchors.fill: parent + text: "Restart the computer." + font.pointSize: Style.fontSizeM * scaling + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + implicitWidth: restartTooltipText.implicitWidth + Style.marginM * 2 * scaling + implicitHeight: restartTooltipText.implicitHeight + Style.marginM * 2 * scaling } MouseArea { @@ -787,8 +817,7 @@ Loader { onClicked: { CompositorService.reboot() } - onEntered: tooltipRestart.show() - onExited: tooltipRestart.hide() + // Tooltip handled via inline rectangle visibility } } @@ -810,11 +839,27 @@ Loader { } // Tooltip - NTooltip { - id: tooltipSuspend - target: parent - positionAbove: true - text: "Suspend" + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.top + anchors.bottomMargin: 12 * scaling + radius: Style.radiusM * scaling + color: Color.mSurface + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + visible: suspendButtonArea.containsMouse + z: 1 + NText { + id: suspendTooltipText + anchors.margins: Style.marginM * scaling + anchors.fill: parent + text: "Suspend the system." + font.pointSize: Style.fontSizeM * scaling + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + implicitWidth: suspendTooltipText.implicitWidth + Style.marginM * 2 * scaling + implicitHeight: suspendTooltipText.implicitHeight + Style.marginM * 2 * scaling } MouseArea { @@ -824,8 +869,7 @@ Loader { onClicked: { CompositorService.suspend() } - onEntered: tooltipSuspend.show() - onExited: tooltipSuspend.hide() + // Tooltip handled via inline rectangle visibility } } } diff --git a/Modules/SidePanel/Cards/UtilitiesCard.qml b/Modules/SidePanel/Cards/UtilitiesCard.qml index 1542521..f4bde37 100644 --- a/Modules/SidePanel/Cards/UtilitiesCard.qml +++ b/Modules/SidePanel/Cards/UtilitiesCard.qml @@ -26,10 +26,13 @@ NBox { // Screen Recorder NIconButton { icon: "camera-video" - tooltipText: ScreenRecorderService.isRecording ? "Stop screen recording." : "Start screen recording." + enabled: ScreenRecorderService.isAvailable + tooltipText: ScreenRecorderService.isAvailable ? (ScreenRecorderService.isRecording ? "Stop screen recording." : "Start screen recording.") : "Screen recorder not installed." colorBg: ScreenRecorderService.isRecording ? Color.mPrimary : Color.mSurfaceVariant colorFg: ScreenRecorderService.isRecording ? Color.mOnPrimary : Color.mPrimary onClicked: { + if (!ScreenRecorderService.isAvailable) + return ScreenRecorderService.toggleRecording() // If we were not recording and we just initiated a start, close the panel if (!ScreenRecorderService.isRecording) { diff --git a/Services/ScreenRecorderService.qml b/Services/ScreenRecorderService.qml index 08d6503..7642542 100644 --- a/Services/ScreenRecorderService.qml +++ b/Services/ScreenRecorderService.qml @@ -13,6 +13,17 @@ Singleton { property bool isRecording: false property bool isPending: false property string outputPath: "" + property bool isAvailable: false + + Component.onCompleted: { + checkAvailability() + } + + function checkAvailability() { + // Detect native or Flatpak gpu-screen-recorder + availabilityCheckProcess.command = ["sh", "-c", "command -v gpu-screen-recorder >/dev/null 2>&1 || (command -v flatpak >/dev/null 2>&1 && flatpak list --app | grep -q 'com.dec05eba.gpu_screen_recorder')"] + availabilityCheckProcess.running = true + } // Start or Stop recording function toggleRecording() { @@ -21,6 +32,9 @@ Singleton { // Start screen recording using Quickshell.execDetached function startRecording() { + if (!isAvailable) { + return + } if (isRecording || isPending) { return } @@ -88,6 +102,18 @@ Singleton { } } + // Availability check process + Process { + id: availabilityCheckProcess + command: ["sh", "-c", "true"] + onExited: function (exitCode, exitStatus) { + // exitCode 0 means available, non-zero means unavailable + root.isAvailable = (exitCode === 0) + } + stdout: StdioCollector {} + stderr: StdioCollector {} + } + Timer { id: pendingTimer interval: 2000 // Wait 2 seconds to see if process stays alive diff --git a/Widgets/NIconButton.qml b/Widgets/NIconButton.qml index d787880..bb37444 100644 --- a/Widgets/NIconButton.qml +++ b/Widgets/NIconButton.qml @@ -35,13 +35,13 @@ Rectangle { opacity: root.enabled ? Style.opacityFull : Style.opacityMedium color: root.enabled && root.hovering ? colorBgHover : colorBg radius: width * 0.5 - border.color: root.hovering ? colorBorderHover : colorBorder + border.color: root.enabled && root.hovering ? colorBorderHover : colorBorder border.width: Math.max(1, Style.borderS * scaling) NIcon { icon: root.icon font.pointSize: Style.fontSizeM * scaling - color: root.hovering ? colorFgHover : colorFg + color: root.enabled && root.hovering ? colorFgHover : colorFg // Center horizontally x: (root.width - width) / 2 // Center vertically accounting for font metrics @@ -56,13 +56,14 @@ Rectangle { } MouseArea { - enabled: root.enabled + // Always enabled to allow hover/tooltip even when the button is disabled + enabled: true anchors.fill: parent cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton hoverEnabled: true onEntered: { - hovering = true + hovering = root.enabled ? true : false if (tooltipText) { tooltip.show() } @@ -79,6 +80,9 @@ Rectangle { if (tooltipText) { tooltip.hide() } + if (!root.enabled) { + return + } if (mouse.button === Qt.LeftButton) { root.clicked() } else if (mouse.button === Qt.RightButton) {