Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/notch/Notch.qml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Item {
property Component powermenuViewComponent
property Component toolsMenuViewComponent
property Component notificationViewComponent
property Component osdViewComponent
property var stackView: stackViewInternal
property bool isExpanded: stackViewInternal.depth > 1
property bool isHovered: false
Expand Down
181 changes: 164 additions & 17 deletions modules/notch/NotchWindow.qml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import qs.modules.services
import qs.modules.components
import qs.modules.widgets.launcher
import qs.config
import qs.modules.widgets.osd
import "./NotchNotificationView.qml"

PanelWindow {
Expand Down Expand Up @@ -80,6 +81,7 @@ PanelWindow {

// Hover state with delay to prevent flickering
property bool hoverActive: false
property bool showingOSD: false

// Track if mouse is over any notch-related area
readonly property bool isMouseOverNotch: notchMouseAreaHover.hovered || notchRegionHover.hovered
Expand All @@ -91,7 +93,7 @@ PanelWindow {

// Show on interaction (hover, open, notifications)
// This works even in fullscreen, ensuring hover always works
if (screenNotchOpen || hasActiveNotifications || hoverActive || barHoverActive) {
if (screenNotchOpen || hasActiveNotifications || hoverActive || barHoverActive || showingOSD) {
return true;
}

Expand Down Expand Up @@ -166,40 +168,184 @@ PanelWindow {

// Default view component - user@host text
Component {
id: defaultViewComponent
id: defaultViewComp
DefaultView {}
}

// Launcher view component
Component {
id: launcherViewComponent
id: launcherViewComp
LauncherView {}
}

// Dashboard view component
Component {
id: dashboardViewComponent
id: dashboardViewComp
DashboardView {}
}

// Power menu view component
Component {
id: powermenuViewComponent
id: powermenuViewComp
PowerMenuView {}
}

// Tools menu view component
Component {
id: toolsMenuViewComponent
id: toolsMenuViewComp
ToolsMenuView {}
}

// Notification view component
Component {
id: notificationViewComponent
id: notificationViewComp
NotchNotificationView {}
}

// OSD view component
Component {
id: osdViewComp
OSDView {}
}

Timer {
id: osdTimer
interval: 2000
repeat: false
onTriggered: {
if (notchPanel.isMouseOverNotch) {
osdTimer.restart();
return;
}

if (notchContainer.stackView.currentItem && notchContainer.stackView.currentItem.hasOwnProperty("showVolume")) {
notchContainer.stackView.pop();

// Back to default view
notchContainer.isShowingDefault = true;
notchContainer.isShowingNotifications = false;
notchPanel.showingOSD = false;
}
}
}

property bool startupComplete: false
Timer {
id: startupTimer
interval: 3000
running: true
repeat: false
onTriggered: {
console.log("[NotchWindow] Startup complete");
notchPanel.startupComplete = true;
}
}

function showOSD(type, value, icon) {
console.log("[NotchWindow] showOSD called with type:", type, "value:", value);
// Do not show OSD if notch is already open with a menu
if (notchPanel.screenNotchOpen) {
console.log("[NotchWindow] OSD skipped: screenNotchOpen is true");
return;
}

// Do not show OSD on startup
if (!notchPanel.startupComplete) {
console.log("[NotchWindow] OSD skipped: startup not complete");
return;
}

const currentItem = notchContainer.stackView.currentItem;
const isOSD = currentItem && currentItem.hasOwnProperty("showVolume"); // Check for OSDView property
console.log("[NotchWindow] Current item is OSD?", isOSD);

if (isOSD) {
console.log("[NotchWindow] Updating existing OSD");
// Update existing OSDView
if (type === "volume") {
currentItem.showVolume = true;
currentItem.volumeValue = value;
} else if (type === "mic") {
currentItem.showMic = true;
currentItem.micValue = value;
} else if (type === "brightness") {
currentItem.showBrightness = true;
currentItem.brightnessValue = value;
}
osdTimer.restart();
notchPanel.showingOSD = true;
} else {
// Not current, push new OSDView
if (notchContainer.stackView.depth === 1) {
var props = {
showVolume: false,
showMic: false,
showBrightness: false,
volumeValue: 0,
micValue: 0,
brightnessValue: 0,
onKeepAlive: () => osdTimer.restart()
};

if (type === "volume") {
props.showVolume = true;
props.volumeValue = value;
} else if (type === "mic") {
props.showMic = true;
props.micValue = value;
} else if (type === "brightness") {
props.showBrightness = true;
props.brightnessValue = value;
}

notchContainer.stackView.push(osdViewComp, props);
notchContainer.isShowingDefault = false;
osdTimer.restart();
notchPanel.showingOSD = true;
}
}
}

Connections {
target: Audio
function onValueChanged() {
console.log("[NotchWindow] Audio.onValueChanged received, value:", Audio.value);
showOSD("volume", Audio.value, Audio.volumeIcon(Audio.value, Audio.muted));
}
function onMutedChanged() {
console.log("[NotchWindow] Audio.onMutedChanged received, muted:", Audio.muted);
showOSD("volume", Audio.value, Audio.volumeIcon(Audio.value, Audio.muted));
}
}

Connections {
target: Audio
function onMicValueChanged() {
console.log("[NotchWindow] Audio.onMicValueChanged received, value:", Audio.micValue);
showOSD("mic", Audio.micValue, Audio.micMuted ? Icons.microphoneOff : Icons.microphone);
}
function onMicMutedChanged() {
console.log("[NotchWindow] Audio.onMicMutedChanged received, muted:", Audio.micMuted);
showOSD("mic", Audio.micValue, Audio.micMuted ? Icons.microphoneOff : Icons.microphone);
}
}

Connections {
target: Brightness
function onBrightnessChanged() {
// Find internal screen monitor
const monitor = Brightness.monitors.find(m => Brightness.isInternalScreen(m.screen) && m.screen.name === screen.name);
if (monitor) {
showOSD("brightness", monitor.brightness, Icons.sun);
} else {
// Fallback to any monitor if needed, or just the first one associated
if (Brightness.monitors.length > 0) {
showOSD("brightness", Brightness.monitors[0].brightness, Icons.sun);
}
}
}
}

// Hover region for detecting mouse when notch is hidden (doesn't block clicks)
// Placed outside notchRegionContainer so it can work with mask independently
Item {
Expand Down Expand Up @@ -295,12 +441,13 @@ PanelWindow {
layer.enabled: true
layer.effect: Shadow {}

defaultViewComponent: defaultViewComponent
launcherViewComponent: launcherViewComponent
dashboardViewComponent: dashboardViewComponent
powermenuViewComponent: powermenuViewComponent
toolsMenuViewComponent: toolsMenuViewComponent
notificationViewComponent: notificationViewComponent
defaultViewComponent: defaultViewComp
launcherViewComponent: launcherViewComp
dashboardViewComponent: dashboardViewComp
powermenuViewComponent: powermenuViewComp
toolsMenuViewComponent: toolsMenuViewComp
notificationViewComponent: notificationViewComp
osdViewComponent: osdViewComp
visibilities: screenVisibilities

// Handle global keyboard events
Expand Down Expand Up @@ -427,7 +574,7 @@ PanelWindow {

function onLauncherChanged() {
if (screenVisibilities.launcher) {
notchContainer.stackView.push(launcherViewComponent);
notchContainer.stackView.push(launcherViewComp);
Qt.callLater(() => {
if (notchContainer.stackView.currentItem) {
notchContainer.stackView.currentItem.forceActiveFocus();
Expand All @@ -444,7 +591,7 @@ PanelWindow {

function onDashboardChanged() {
if (screenVisibilities.dashboard) {
notchContainer.stackView.push(dashboardViewComponent);
notchContainer.stackView.push(dashboardViewComp);
Qt.callLater(() => {
if (notchContainer.stackView.currentItem) {
notchContainer.stackView.currentItem.forceActiveFocus();
Expand All @@ -461,7 +608,7 @@ PanelWindow {

function onPowermenuChanged() {
if (screenVisibilities.powermenu) {
notchContainer.stackView.push(powermenuViewComponent);
notchContainer.stackView.push(powermenuViewComp);
Qt.callLater(() => {
if (notchContainer.stackView.currentItem) {
notchContainer.stackView.currentItem.forceActiveFocus();
Expand All @@ -478,7 +625,7 @@ PanelWindow {

function onToolsChanged() {
if (screenVisibilities.tools) {
notchContainer.stackView.push(toolsMenuViewComponent);
notchContainer.stackView.push(toolsMenuViewComp);
Qt.callLater(() => {
if (notchContainer.stackView.currentItem) {
notchContainer.stackView.currentItem.forceActiveFocus();
Expand Down
4 changes: 4 additions & 0 deletions modules/services/Audio.qml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import QtQuick
import Quickshell
import Quickshell.Services.Pipewire
import qs.modules.services
import qs.modules.theme

/**
* A nice wrapper for default Pipewire audio sink and source.
Expand All @@ -19,6 +20,9 @@ Singleton {
property PwNode source: Pipewire.defaultAudioSource
readonly property real hardMaxValue: 2.00
property real value: sink?.audio?.volume ?? 0
property bool muted: sink?.audio?.muted ?? false
property real micValue: source?.audio?.volume ?? 0
property bool micMuted: source?.audio?.muted ?? false

// Volume protection settings (persisted via StateService)
property bool protectionEnabled: true
Expand Down
25 changes: 25 additions & 0 deletions modules/widgets/dashboard/widgets/FullPlayer.qml
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,37 @@ StyledRect {
}
}

Item {
anchors.fill: parent
visible: player.hasArtwork

Image {
id: bgArt
anchors.fill: parent
source: MprisController.activePlayer?.trackArtUrl ?? ""
fillMode: Image.PreserveAspectCrop
visible: false
}

MultiEffect {
anchors.fill: parent
source: bgArt
blurEnabled: true
blurMax: 64
blur: 0.5
opacity: 1
saturation: 0.8
}
}


StyledRect {
id: innerPlayer
variant: "internalbg"
anchors.fill: parent
anchors.margins: 4
radius: player.radius - 4
backgroundOpacity: 0.5

implicitHeight: mainLayout.implicitHeight + mainLayout.anchors.margins * 2

Expand Down
Loading