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);
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);
}

View file

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

View file

@ -101,7 +101,7 @@ MobileShell.BaseItem {
function delegatePressAndHold() {
// 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];
if (pluginId) {
root.detailRequested(pluginId);

View file

@ -33,6 +33,9 @@ Window {
readonly property real openOffset: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
readonly property int longestLength: Math.max(Screen.width, Screen.height)
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
LayerShell.Window.scope: "notification"
@ -100,8 +103,8 @@ Window {
}
if (isConvergence) {
let regionX = notificationPopupManager.width - notificationPopupManager.popupWidth - Kirigami.Units.gridUnit * 4;
let regionY = Screen.height - openOffset - popupHeight - Kirigami.Units.gridUnit;
let regionX = notificationPopupManager.width - notificationPopupManager.popupWidth - notificationPopupManager.convergencePopupMargin;
let regionY = openOffset;
ShellUtil.setInputRegion(notificationPopupManager, Qt.rect(regionX, regionY, notificationPopupManager.popupWidth + Kirigami.Units.gridUnit * 2, popupHeight + Kirigami.Units.gridUnit * 2));
} 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)));
@ -203,7 +206,7 @@ Window {
id: popup
x: notificationPopupManager.isConvergence
? (parent.width - width - Kirigami.Units.gridUnit * 2)
? (parent.width - width - notificationPopupManager.convergencePopupMargin)
: (parent.width - width) / 2
z: notifications.count - index

View file

@ -92,7 +92,7 @@ Item {
// Hover highlight in convergence mode to indicate the bar is clickable
Rectangle {
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
}

View file

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

View file

@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: Marco Allegretti
// SPDX-License-Identifier: EUPL 1.2
// SPDX-License-Identifier: EUPL-1.2
import QtQuick
import QtQuick.Controls as QQC2
@ -179,7 +179,27 @@ Rectangle {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
activeFocusOnTab: true
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: {
if (!targetDelegate) return 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: {
if (!targetDelegate) return 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: {
@ -916,7 +916,7 @@ MouseArea {
Repeater {
model: {
var ids = taskDelegate.model.WinIdList
return ids ? ids.length : 1
return Math.max(1, ids ? ids.length : 0)
}
Rectangle {

View file

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

View file

@ -158,13 +158,13 @@ ContainmentItem {
color: "transparent"
flags: Qt.FramelessWindowHint | Qt.WindowTransparentForInput
// 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
LayerShell.Window.scope: "dock-space"
LayerShell.Window.layer: LayerShell.Window.LayerBottom
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
}

View file

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

View file

@ -28,13 +28,33 @@ public Q_SLOTS:
KAuth::ActionReply Flashlighthelper::setbrightness(const QVariantMap &args)
{
const char *sysPath = args.value("sysPath"_L1).toString().toUtf8().constData();
const char *brightness = args.value("brightness"_L1).toString().toUtf8().constData();
// Store as named QByteArrays so constData() pointers remain valid for the
// 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_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_unref(udev);
@ -42,7 +62,7 @@ KAuth::ActionReply Flashlighthelper::setbrightness(const QVariantMap &args)
if (ret >= 0) {
return KAuth::ActionReply::SuccessReply();
} else {
qCWarning(FLASHLIGHTHELPER) << "Failed to set udev system attribute";
qCWarning(FLASHLIGHTHELPER) << "Failed to set udev system attribute, errno:" << -ret;
return KAuth::ActionReply::HelperErrorReply();
}
}