mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
bar-shell: prevent bar crash on rapid visibility toggle and convert IPCService to singleton
This commit is contained in:
@@ -54,9 +54,12 @@ Variants {
|
||||
}
|
||||
|
||||
// Bar content in separate windows to prevent fullscreen redraws
|
||||
// Note: Window stays alive when bar is hidden (visible=false) to avoid
|
||||
// rapid Wayland surface destruction/creation that can crash compositors.
|
||||
// Content is debounce-unloaded inside BarContentWindow.
|
||||
Loader {
|
||||
active: {
|
||||
if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.effectivelyVisible)
|
||||
if (!parent.windowLoaded || !parent.shouldBeActive)
|
||||
return false;
|
||||
|
||||
// Check if bar is configured for this screen
|
||||
@@ -130,7 +133,8 @@ Variants {
|
||||
}
|
||||
|
||||
// PopupMenuWindow - reusable popup window for both tray menus and context menus
|
||||
// Active when bar is visible on this screen, OR when desktop widgets edit mode is active
|
||||
// Stays alive when bar is hidden to avoid Wayland surface churn crashes.
|
||||
// PopupMenuWindow manages its own visibility internally.
|
||||
Loader {
|
||||
active: {
|
||||
// Desktop widgets edit mode needs popup window on ALL screens
|
||||
@@ -139,7 +143,7 @@ Variants {
|
||||
}
|
||||
|
||||
// Normal bar-based condition
|
||||
if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.effectivelyVisible)
|
||||
if (!parent.windowLoaded || !parent.shouldBeActive)
|
||||
return false;
|
||||
|
||||
// Check if bar is configured for this screen
|
||||
|
||||
@@ -20,8 +20,8 @@ PanelWindow {
|
||||
// Note: screen property is inherited from PanelWindow and should be set by parent
|
||||
color: "transparent" // Transparent - background is in MainScreen below
|
||||
|
||||
// Make window pass-through when content is unloaded
|
||||
visible: contentLoaded
|
||||
// Make window pass-through when content is unloaded or bar is hidden via IPC
|
||||
visible: contentLoaded && BarService.effectivelyVisible
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("BarContentWindow", "Bar content window created for screen:", barWindow.screen?.name);
|
||||
@@ -169,10 +169,42 @@ PanelWindow {
|
||||
} else {
|
||||
// Load immediately when showing
|
||||
unloadTimer.stop();
|
||||
deferredUnloadTimer.stop();
|
||||
contentLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Debounced content unload when bar visibility is toggled.
|
||||
// Rapid toggles keep widgets alive; content is only unloaded after the bar
|
||||
// has been continuously hidden for the debounce period.
|
||||
Timer {
|
||||
id: deferredUnloadTimer
|
||||
interval: 1000
|
||||
onTriggered: {
|
||||
if (!BarService.effectivelyVisible) {
|
||||
barWindow.barHovered = false;
|
||||
barWindow.contentLoaded = false;
|
||||
Logger.d("BarContentWindow", "Debounced content unload for screen:", barWindow.screen?.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: BarService
|
||||
function onEffectivelyVisibleChanged() {
|
||||
if (!BarService.effectivelyVisible) {
|
||||
// Bar hidden — start debounced unload
|
||||
deferredUnloadTimer.restart();
|
||||
} else {
|
||||
// Bar shown — cancel pending unload, ensure content is loaded
|
||||
deferredUnloadTimer.stop();
|
||||
if (!barWindow.isHidden) {
|
||||
barWindow.contentLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle floating margins and framed mode offsets
|
||||
margins {
|
||||
top: (barPosition === "top") ? barMarginV : (isFramed ? frameThickness : barMarginV)
|
||||
|
||||
@@ -49,6 +49,10 @@ PanelWindow {
|
||||
PanelService.registerPopupMenuWindow(screen, root);
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
PanelService.unregisterPopupMenuWindow(screen);
|
||||
}
|
||||
|
||||
// Load TrayMenu as the default content
|
||||
Loader {
|
||||
id: trayMenuLoader
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
@@ -16,22 +18,27 @@ import qs.Services.System
|
||||
import qs.Services.Theming
|
||||
import qs.Services.UI
|
||||
|
||||
Item {
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Screen detector passed from shell.qml
|
||||
required property CurrentScreenDetector screenDetector
|
||||
// Screen detector, set via init()
|
||||
property var screenDetector: null
|
||||
|
||||
function init(detector) {
|
||||
root.screenDetector = detector;
|
||||
Logger.i("IPCService", "Service started");
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "bar"
|
||||
function toggle() {
|
||||
BarService.isVisible = !BarService.isVisible;
|
||||
BarService.toggleVisibility();
|
||||
}
|
||||
function hideBar() {
|
||||
BarService.isVisible = false;
|
||||
BarService.hide();
|
||||
}
|
||||
function showBar() {
|
||||
BarService.isVisible = true;
|
||||
BarService.show();
|
||||
}
|
||||
function setDisplayMode(mode: string) {
|
||||
if (mode === "always_visible" || mode === "non_exclusive" || mode === "auto_hide") {
|
||||
|
||||
@@ -86,6 +86,49 @@ Singleton {
|
||||
return state ? state.hovered : false;
|
||||
}
|
||||
|
||||
// Toggle bar visibility. In auto-hide mode, toggles the auto-hide state
|
||||
// on all screens instead of setting the global isVisible flag.
|
||||
function toggleVisibility() {
|
||||
if (Settings.data.bar.displayMode === "auto_hide") {
|
||||
// Check if any screen is currently visible (not hidden)
|
||||
var anyVisible = false;
|
||||
for (var screenName in screenAutoHideState) {
|
||||
if (!screenAutoHideState[screenName].hidden) {
|
||||
anyVisible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Toggle all screens
|
||||
for (var screenName in screenAutoHideState) {
|
||||
setScreenHidden(screenName, anyVisible);
|
||||
}
|
||||
} else {
|
||||
isVisible = !isVisible;
|
||||
}
|
||||
}
|
||||
|
||||
// Show bar. In auto-hide mode, un-hides on all screens.
|
||||
function show() {
|
||||
if (Settings.data.bar.displayMode === "auto_hide") {
|
||||
for (var screenName in screenAutoHideState) {
|
||||
setScreenHidden(screenName, false);
|
||||
}
|
||||
} else {
|
||||
isVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide bar. In auto-hide mode, hides on all screens.
|
||||
function hide() {
|
||||
if (Settings.data.bar.displayMode === "auto_hide") {
|
||||
for (var screenName in screenAutoHideState) {
|
||||
setScreenHidden(screenName, true);
|
||||
}
|
||||
} else {
|
||||
isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.i("BarService", "Service started");
|
||||
}
|
||||
|
||||
@@ -65,6 +65,15 @@ Singleton {
|
||||
popupMenuWindowRegistered(screen);
|
||||
}
|
||||
|
||||
// Unregister popup menu window for a screen (called on destruction)
|
||||
function unregisterPopupMenuWindow(screen) {
|
||||
if (!screen)
|
||||
return;
|
||||
var key = screen.name;
|
||||
delete popupMenuWindows[key];
|
||||
Logger.d("PanelService", "Unregistered popup menu window for screen:", key);
|
||||
}
|
||||
|
||||
// Get popup menu window for a screen
|
||||
function getPopupMenuWindow(screen) {
|
||||
if (!screen)
|
||||
|
||||
@@ -113,6 +113,7 @@ ShellRoot {
|
||||
GitHubService.init();
|
||||
SupporterService.init();
|
||||
CustomButtonIPCService.init();
|
||||
IPCService.init(screenDetector);
|
||||
});
|
||||
|
||||
delayedInitTimer.running = true;
|
||||
@@ -145,11 +146,7 @@ ShellRoot {
|
||||
id: screenDetector
|
||||
}
|
||||
|
||||
// IPCService is treated as a service but it must be in graphics scene.
|
||||
IPCService {
|
||||
id: ipcService
|
||||
screenDetector: screenDetector
|
||||
}
|
||||
// IPCService is a singleton, initialized via init() in deferred services block
|
||||
|
||||
// Container for plugins Main.qml instances (must be in graphics scene)
|
||||
Item {
|
||||
|
||||
Reference in New Issue
Block a user