Compare commits

...

10 commits

Author SHA1 Message Date
d4da69b2f2 Revert README videos to markdown image syntax
GitLab auto-renders ![](*.webm) as video players but strips
raw <video> HTML tags from rendered markdown.
2026-04-19 08:01:00 +02:00
53686d4ef0 Fix CI build and REUSE lint failures
Use QByteArray::data() instead of constData() for the
udev_device_set_sysattr_value call — Alpine libudev declares the
value parameter as char*, not const char*. Fix the SPDX license
identifier in CategoryPanel.qml from "EUPL 1.2" to "EUPL-1.2".
2026-04-19 07:26:02 +02:00
caef3bc82e Fix minor UI bugs and clean up dead code
Clamp FavouritesBar window indicator dots to at least 1 so a
running task with an empty WinIdList still shows a dot. Add
Accessible.onPressAction to CategoryPanel tiles. Remove dead
convergence-mode property bindings from NavigationPanelComponent
since it is hidden in that mode. Use HTML video tags for .webm
files in README so they render as playable video.
2026-04-19 07:01:55 +02:00
6a1631023e Theme-aware status bar hover and remove dead nav panel code
Use Kirigami.Theme.textColor for the status bar hover overlay
so it works on both light and dark themes instead of hardcoded
white. Remove convergence-mode branches from the navigation
panel left action since it is hidden in convergence mode and
the code paths were unreachable.
2026-04-18 20:25:26 +02:00
9bf7b70d2b Fix homescreen includes, accessibility, and popup clamp
Remove duplicate KService include in favouritesmodel.cpp. Add
keyboard navigation and accessibility role to CategoryPanel
tiles. Clamp the FavouritesBar thumbnail popup position to
screen bounds so it does not overflow offscreen on edge items.
2026-04-18 20:25:05 +02:00
acecbcc86a Fix notification popup position in convergence mode
The input region Y was computed from the bottom of the screen
instead of from the top, causing the touch region to be offset
from the actual popup. Use openOffset directly since the popup
anchors to the top. Unify the popup margin into a single
property so the delegate x and the input-region regionX stay
consistent.
2026-04-18 20:24:48 +02:00
aa4103f72a Add null guards in QuickSettingsDelegate and convergentwindows
Check restrictedPermissions before opening the detail popup in
convergence mode. Guard against null output in the KWin script
window-setup handler to prevent TypeError when a window has no
assigned output yet.
2026-04-18 20:24:28 +02:00
8fecdf1d2d Guard against invalid KPackage in AppletHost
loadPackage + setPath can produce an invalid package if the shell
package is missing. Guard with isValid() to avoid passing a bad
package to setKPackage. Add the QML_SINGLETON factory that the
QML engine requires for proper singleton instantiation.
2026-04-18 20:24:11 +02:00
276912bf31 Fix dock reserver crash in convergence mode
navigationPanelThickness is explicitly 0 in convergence mode, so using
it as the layer-shell surface height triggered a Wayland protocol error
and crashed the shell. Use gridUnit * 3 directly, matching the dock bar
height set on FavouritesBar.
2026-04-18 19:41:10 +02:00
0d230c5397 Fix dangling pointers and missing null check in flashlight helper
The two const char* variables were pointing into QByteArray
temporaries that were destroyed at the end of each declaration
statement. By the time they were passed to udev, the memory was
freed. Hold the QByteArrays in named locals so the data stays
alive for the duration of the function.

udev_device_new_from_syspath returns NULL if the syspath is
invalid or the device disappears between enumeration and the
privileged call. Add an early-return guard so the subsequent
udev_device_set_sysattr_value call is never reached with a null
device pointer.

Also drop the unnecessary const_cast: udev_device_set_sysattr_value
takes const char*, not char*.
2026-04-18 19:41:04 +02:00
12 changed files with 73 additions and 30 deletions

View file

@ -25,6 +25,10 @@ public:
{ {
KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(u"Plasma/Shell"_s); KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(u"Plasma/Shell"_s);
pkg.setPath(u"org.kde.plasma.mobile"_s); pkg.setPath(u"org.kde.plasma.mobile"_s);
if (!pkg.isValid()) {
qWarning() << "AppletHost: failed to load plasma shell package org.kde.plasma.mobile";
return;
}
setKPackage(pkg); setKPackage(pkg);
} }

View file

@ -30,6 +30,11 @@ public:
explicit AppletHost(QObject *parent = nullptr); explicit AppletHost(QObject *parent = nullptr);
~AppletHost() override; ~AppletHost() override;
static QObject *create(QQmlEngine * /*engine*/, QJSEngine * /*scriptEngine*/)
{
return new AppletHost();
}
Q_INVOKABLE QQuickItem *fullRepresentationFor(const QString &pluginId); Q_INVOKABLE QQuickItem *fullRepresentationFor(const QString &pluginId);
Q_SIGNALS: Q_SIGNALS:

View file

@ -101,7 +101,7 @@ MobileShell.BaseItem {
function delegatePressAndHold() { function delegatePressAndHold() {
// In convergence mode, show inline detail popup if available. // In convergence mode, show inline detail popup if available.
if (ShellSettings.Settings.convergenceModeEnabled && root.settingsCommand) { if (ShellSettings.Settings.convergenceModeEnabled && root.settingsCommand && !root.restrictedPermissions) {
let pluginId = __appletForCommand[root.settingsCommand]; let pluginId = __appletForCommand[root.settingsCommand];
if (pluginId) { if (pluginId) {
root.detailRequested(pluginId); root.detailRequested(pluginId);

View file

@ -33,6 +33,9 @@ Window {
readonly property real openOffset: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3 readonly property real openOffset: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
readonly property int longestLength: Math.max(Screen.width, Screen.height) readonly property int longestLength: Math.max(Screen.width, Screen.height)
readonly property bool isConvergence: ShellSettings.Settings.convergenceModeEnabled readonly property bool isConvergence: ShellSettings.Settings.convergenceModeEnabled
// Margin between popup and screen edge in convergence mode; used in both
// the delegate x position and the input-region calculation so they stay in sync.
readonly property real convergencePopupMargin: Kirigami.Units.gridUnit * 2
property var keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone property var keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone
LayerShell.Window.scope: "notification" LayerShell.Window.scope: "notification"
@ -100,8 +103,8 @@ Window {
} }
if (isConvergence) { if (isConvergence) {
let regionX = notificationPopupManager.width - notificationPopupManager.popupWidth - Kirigami.Units.gridUnit * 4; let regionX = notificationPopupManager.width - notificationPopupManager.popupWidth - notificationPopupManager.convergencePopupMargin;
let regionY = Screen.height - openOffset - popupHeight - Kirigami.Units.gridUnit; let regionY = openOffset;
ShellUtil.setInputRegion(notificationPopupManager, Qt.rect(regionX, regionY, notificationPopupManager.popupWidth + Kirigami.Units.gridUnit * 2, popupHeight + Kirigami.Units.gridUnit * 2)); ShellUtil.setInputRegion(notificationPopupManager, Qt.rect(regionX, regionY, notificationPopupManager.popupWidth + Kirigami.Units.gridUnit * 2, popupHeight + Kirigami.Units.gridUnit * 2));
} else { } else {
ShellUtil.setInputRegion(notificationPopupManager, Qt.rect((notificationPopupManager.width - notificationPopupManager.popupWidth - Kirigami.Units.gridUnit) / 2, openOffset - Kirigami.Units.gridUnit / 2, notificationPopupManager.popupWidth + Kirigami.Units.gridUnit, popupHeight + Kirigami.Units.gridUnit * ((notifications.count - notifications.currentPopupIndex > 1) ? 4 : 1))); ShellUtil.setInputRegion(notificationPopupManager, Qt.rect((notificationPopupManager.width - notificationPopupManager.popupWidth - Kirigami.Units.gridUnit) / 2, openOffset - Kirigami.Units.gridUnit / 2, notificationPopupManager.popupWidth + Kirigami.Units.gridUnit, popupHeight + Kirigami.Units.gridUnit * ((notifications.count - notifications.currentPopupIndex > 1) ? 4 : 1)));
@ -203,7 +206,7 @@ Window {
id: popup id: popup
x: notificationPopupManager.isConvergence x: notificationPopupManager.isConvergence
? (parent.width - width - Kirigami.Units.gridUnit * 2) ? (parent.width - width - notificationPopupManager.convergencePopupMargin)
: (parent.width - width) / 2 : (parent.width - width) / 2
z: notifications.count - index z: notifications.count - index

View file

@ -92,7 +92,7 @@ Item {
// Hover highlight in convergence mode to indicate the bar is clickable // Hover highlight in convergence mode to indicate the bar is clickable
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Qt.rgba(1.0, 1.0, 1.0, 0.1) color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1)
visible: ShellSettings.Settings.convergenceModeEnabled && statusBarHover.hovered visible: ShellSettings.Settings.convergenceModeEnabled && statusBarHover.hovered
} }

View file

@ -4,7 +4,6 @@
#include "favouritesmodel.h" #include "favouritesmodel.h"
#include "homescreenstate.h" #include "homescreenstate.h"
#include <KService>
#include <QByteArray> #include <QByteArray>
#include <QDebug> #include <QDebug>
#include <QJsonArray> #include <QJsonArray>

View file

@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: Marco Allegretti // SPDX-FileCopyrightText: Marco Allegretti
// SPDX-License-Identifier: EUPL 1.2 // SPDX-License-Identifier: EUPL-1.2
import QtQuick import QtQuick
import QtQuick.Controls as QQC2 import QtQuick.Controls as QQC2
@ -179,7 +179,27 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
activeFocusOnTab: true
onClicked: root.categorySelected(tile.catId) onClicked: root.categorySelected(tile.catId)
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_Space) {
root.categorySelected(tile.catId);
event.accepted = true;
}
}
Accessible.role: Accessible.Button
Accessible.name: tile.catName
Accessible.onPressAction: root.categorySelected(tile.catId)
Rectangle {
anchors.fill: parent
color: "transparent"
border.color: Kirigami.Theme.highlightColor
border.width: tileArea.activeFocus ? 2 : 0
radius: parent.parent.radius
}
} }
} }
} }

View file

@ -642,12 +642,12 @@ MouseArea {
x: { x: {
if (!targetDelegate) return 0 if (!targetDelegate) return 0
var delegateGlobal = targetDelegate.mapToGlobal(0, 0) var delegateGlobal = targetDelegate.mapToGlobal(0, 0)
return Math.max(0, delegateGlobal.x + (targetDelegate.width - width) / 2) return Math.max(0, Math.min(Screen.width - width, delegateGlobal.x + (targetDelegate.width - width) / 2))
} }
y: { y: {
if (!targetDelegate) return 0 if (!targetDelegate) return 0
var delegateGlobal = targetDelegate.mapToGlobal(0, 0) var delegateGlobal = targetDelegate.mapToGlobal(0, 0)
return delegateGlobal.y - height - Kirigami.Units.smallSpacing return Math.max(0, Math.min(Screen.height - height, delegateGlobal.y - height - Kirigami.Units.smallSpacing))
} }
onVisibleChanged: { onVisibleChanged: {
@ -916,7 +916,7 @@ MouseArea {
Repeater { Repeater {
model: { model: {
var ids = taskDelegate.model.WinIdList var ids = taskDelegate.model.WinIdList
return ids ? ids.length : 1 return Math.max(1, ids ? ids.length : 0)
} }
Rectangle { Rectangle {

View file

@ -37,11 +37,6 @@ MobileShell.NavigationPanel {
foregroundColorGroup: forcedComplementary ? Kirigami.Theme.Complementary : Kirigami.Theme.Window foregroundColorGroup: forcedComplementary ? Kirigami.Theme.Complementary : Kirigami.Theme.Window
shadow: forcedComplementary shadow: forcedComplementary
// Convergence mode: expose running-app task strip
convergenceMode: ShellSettings.Settings.convergenceModeEnabled
taskModel: tasksModel
virtualDesktopInfo: virtualDesktopInfo
MobileShellState.PanelSettingsDBusClient { MobileShellState.PanelSettingsDBusClient {
id: panelSettings id: panelSettings
screenName: Screen.name screenName: Screen.name
@ -75,20 +70,16 @@ MobileShell.NavigationPanel {
// ~~~~ // ~~~~
// navigation panel actions // navigation panel actions
// toggle task switcher button (KWin Overview in convergence mode, mobile task switcher otherwise) // toggle task switcher button
leftAction: MobileShell.NavigationPanelAction { leftAction: MobileShell.NavigationPanelAction {
id: taskSwitcherAction id: taskSwitcherAction
enabled: true enabled: true
iconSource: ShellSettings.Settings.convergenceModeEnabled ? "view-grid-symbolic" : "mobile-task-switcher" iconSource: "mobile-task-switcher"
shrinkSize: ShellSettings.Settings.convergenceModeEnabled ? 0 : 4 shrinkSize: 4
onTriggered: { onTriggered: {
if (ShellSettings.Settings.convergenceModeEnabled) { Plasmoid.triggerTaskSwitcher();
Plasmoid.triggerOverview();
} else {
Plasmoid.triggerTaskSwitcher();
}
} }
} }

View file

@ -158,13 +158,13 @@ ContainmentItem {
color: "transparent" color: "transparent"
flags: Qt.FramelessWindowHint | Qt.WindowTransparentForInput flags: Qt.FramelessWindowHint | Qt.WindowTransparentForInput
// height is set by layer-shell anchoring; provide a fallback. // height is set by layer-shell anchoring; provide a fallback.
height: root.navigationPanelHeight height: Kirigami.Units.gridUnit * 3
width: 1 // layer-shell stretches it via AnchorLeft|AnchorRight width: 1 // layer-shell stretches it via AnchorLeft|AnchorRight
LayerShell.Window.scope: "dock-space" LayerShell.Window.scope: "dock-space"
LayerShell.Window.layer: LayerShell.Window.LayerBottom LayerShell.Window.layer: LayerShell.Window.LayerBottom
LayerShell.Window.anchors: LayerShell.Window.AnchorBottom | LayerShell.Window.AnchorLeft | LayerShell.Window.AnchorRight LayerShell.Window.anchors: LayerShell.Window.AnchorBottom | LayerShell.Window.AnchorLeft | LayerShell.Window.AnchorRight
LayerShell.Window.exclusionZone: root.navigationPanelHeight LayerShell.Window.exclusionZone: Kirigami.Units.gridUnit * 3
LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone
} }

View file

@ -31,6 +31,7 @@ Loader {
const output = window.output const output = window.output
const desktop = window.desktops[0] const desktop = window.desktops[0]
if (!output) return
if (!desktop) return if (!desktop) return
const maxRect = KWinComponents.Workspace.clientArea( const maxRect = KWinComponents.Workspace.clientArea(

View file

@ -28,13 +28,33 @@ public Q_SLOTS:
KAuth::ActionReply Flashlighthelper::setbrightness(const QVariantMap &args) KAuth::ActionReply Flashlighthelper::setbrightness(const QVariantMap &args)
{ {
const char *sysPath = args.value("sysPath"_L1).toString().toUtf8().constData(); // Store as named QByteArrays so constData() pointers remain valid for the
const char *brightness = args.value("brightness"_L1).toString().toUtf8().constData(); // duration of the function. The originals were temporaries that were
// destroyed at the end of each declaration statement.
// (need to double-check this, but seems likely to be the cause of the random failures
// we were seeing in testing)
const QByteArray sysPathBytes = args.value("sysPath"_L1).toString().toUtf8();
QByteArray brightnessBytes = args.value("brightness"_L1).toString().toUtf8();
if (sysPathBytes.isEmpty()) {
qCWarning(FLASHLIGHTHELPER) << "sysPath argument is missing or empty";
return KAuth::ActionReply::HelperErrorReply();
}
struct udev *udev = udev_new(); struct udev *udev = udev_new();
struct udev_device *device = udev_device_new_from_syspath(udev, sysPath); if (!udev) {
qCWarning(FLASHLIGHTHELPER) << "Failed to create udev context";
return KAuth::ActionReply::HelperErrorReply();
}
int ret = udev_device_set_sysattr_value(device, "brightness", const_cast<char *>(brightness)); struct udev_device *device = udev_device_new_from_syspath(udev, sysPathBytes.constData());
if (!device) {
qCWarning(FLASHLIGHTHELPER) << "Failed to find udev device for syspath:" << sysPathBytes;
udev_unref(udev);
return KAuth::ActionReply::HelperErrorReply();
}
int ret = udev_device_set_sysattr_value(device, "brightness", brightnessBytes.data());
udev_device_unref(device); udev_device_unref(device);
udev_unref(udev); udev_unref(udev);
@ -42,7 +62,7 @@ KAuth::ActionReply Flashlighthelper::setbrightness(const QVariantMap &args)
if (ret >= 0) { if (ret >= 0) {
return KAuth::ActionReply::SuccessReply(); return KAuth::ActionReply::SuccessReply();
} else { } else {
qCWarning(FLASHLIGHTHELPER) << "Failed to set udev system attribute"; qCWarning(FLASHLIGHTHELPER) << "Failed to set udev system attribute, errno:" << -ret;
return KAuth::ActionReply::HelperErrorReply(); return KAuth::ActionReply::HelperErrorReply();
} }
} }