v0.2.2: Preferences page, About page, full icon set, redesigned permissions list

- Add a Preferences page (reachable from the hamburger menu) with a
  KColorSchemeManager-backed color scheme switcher
- Add an About page reading from KAboutData
- Ship a full hicolor icon set (16-128px + scalable) via ecm_install_icons
  instead of a single flat SVG
- Restyle the permissions list after KDE System Settings' Audio page:
  Kirigami.ListSectionHeader per category, flat separated rows, no card
  borders
- Explicitly declare Kirigami/KirigamiAddons/ColorScheme as CMake
  dependencies instead of relying on the QML import scanner alone
- Add KDEClangFormat/KDEGitCommitHooks, a CMakePresets.json, and a
  REUSE + clang-format lint CI workflow
- Reformat SPDX headers to include copyright, and tidy up a stray
  namespace inconsistency
This commit is contained in:
2026-07-03 16:25:29 -05:00
parent a657287bd1
commit 3a17433f53
42 changed files with 590 additions and 71 deletions
+16
View File
@@ -0,0 +1,16 @@
<!--
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: CC0-1.0
-->
## Reason for the change
<!-- What problem does this solve, or what does it add? -->
## Test plan
<!-- How did you verify this? Build/run steps, commands, etc. -->
## Screenshots
<!-- If this changes UI, include a before/after. -->
+2
View File
@@ -1,3 +1,5 @@
# SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
#
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
name: Build and Publish Flatpak name: Build and Publish Flatpak
+38
View File
@@ -0,0 +1,38 @@
# SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
#
# SPDX-License-Identifier: GPL-3.0-or-later
name: Lint
on:
pull_request: {}
push:
branches:
- main
jobs:
reuse:
name: REUSE compliance
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: REUSE Compliance Check
uses: fsfe/reuse-action@v3
clang-format:
name: clang-format
runs-on: ubuntu-latest
container:
image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.10
options: --privileged
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure (generates the KDE .clang-format style)
run: cmake -B build -G Ninja -DBUILD_TESTING=OFF
- name: Check formatting
run: |
find src autotests -name '*.h' -o -name '*.cpp' | \
xargs clang-format --dry-run --Werror
+2
View File
@@ -1,3 +1,5 @@
# SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
#
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
name: Build and Test name: Build and Test
+2
View File
@@ -8,3 +8,5 @@
# Editor / misc # Editor / misc
*.user *.user
*.autosave *.autosave
# Generated by ECM's KDEClangFormat module
/.clang-format
+22 -3
View File
@@ -1,9 +1,11 @@
# SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
#
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# FlatKontrol - a Kirigami/Qt Flatpak permissions manager # FlatKontrol - a Kirigami/Qt Flatpak permissions manager
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
project(flatkontrol VERSION 0.2.1 LANGUAGES CXX) project(flatkontrol VERSION 0.2.2 LANGUAGES CXX)
set(REQUIRED_QT_VERSION 6.6.0) set(REQUIRED_QT_VERSION 6.6.0)
set(REQUIRED_KF_VERSION 6.5.0) set(REQUIRED_KF_VERSION 6.5.0)
@@ -15,7 +17,10 @@ include(KDEInstallDirs)
include(KDECMakeSettings) include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMSetupVersion) include(ECMSetupVersion)
include(ECMInstallIcons)
include(FeatureSummary) include(FeatureSummary)
include(KDEClangFormat)
include(KDEGitCommitHooks)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -39,6 +44,19 @@ find_package(KF6 ${REQUIRED_KF_VERSION} REQUIRED COMPONENTS
IconThemes IconThemes
Crash Crash
DBusAddons DBusAddons
ColorScheme
)
find_package(KF6Kirigami ${REQUIRED_KF_VERSION} REQUIRED)
set_package_properties(KF6Kirigami PROPERTIES
TYPE REQUIRED
DESCRIPTION "KDE's next-gen UI framework"
)
find_package(KF6KirigamiAddons REQUIRED)
set_package_properties(KF6KirigamiAddons PROPERTIES
TYPE REQUIRED
DESCRIPTION "Add-on components for Kirigami"
) )
ecm_setup_version(${PROJECT_VERSION} ecm_setup_version(${PROJECT_VERSION}
@@ -46,6 +64,7 @@ ecm_setup_version(${PROJECT_VERSION}
VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/flatkontrol-version.h" VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/flatkontrol-version.h"
) )
add_subdirectory(icons)
add_subdirectory(src) add_subdirectory(src)
if(BUILD_TESTING) if(BUILD_TESTING)
@@ -56,11 +75,11 @@ install(PROGRAMS io.github.toservetheking.FlatKontrol.desktop
DESTINATION ${KDE_INSTALL_APPDIR}) DESTINATION ${KDE_INSTALL_APPDIR})
install(FILES io.github.toservetheking.FlatKontrol.metainfo.xml install(FILES io.github.toservetheking.FlatKontrol.metainfo.xml
DESTINATION ${KDE_INSTALL_METAINFODIR}) DESTINATION ${KDE_INSTALL_METAINFODIR})
install(FILES icons/io.github.toservetheking.FlatKontrol.svg
DESTINATION ${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps)
install(FILES LICENSES/GPL-3.0-or-later.txt install(FILES LICENSES/GPL-3.0-or-later.txt
DESTINATION ${KDE_INSTALL_DATAROOTDIR}/licenses/${PROJECT_NAME}) DESTINATION ${KDE_INSTALL_DATAROOTDIR}/licenses/${PROJECT_NAME})
# Translations: add a po/ directory and re-enable ki18n_install(po) once present. # Translations: add a po/ directory and re-enable ki18n_install(po) once present.
kde_configure_git_pre_commit_hook(CHECKS CLANG-FORMAT)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
+20
View File
@@ -0,0 +1,20 @@
{
"version": 6,
"cmakeMinimumRequired": {
"major": 3,
"minor": 23,
"patch": 0
},
"configurePresets": [
{
"name": "ninja-dev",
"displayName": "Ninja (dev)",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"BUILD_TESTING": "ON"
}
}
]
}
+2
View File
@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: CC0-1.0
+5 -1
View File
@@ -1,5 +1,7 @@
# FlatKontrol # FlatKontrol
![FlatKontrol](screenshots/main.png)
A Flatpak permissions manager built the KDE way - C++ with a QML/Kirigami A Flatpak permissions manager built the KDE way - C++ with a QML/Kirigami
frontend, ECM/CMake, and KDE Frameworks 6. Think [Flatseal], reimagined as if frontend, ECM/CMake, and KDE Frameworks 6. Think [Flatseal], reimagined as if
the team behind [Elisa] had written it. the team behind [Elisa] had written it.
@@ -51,7 +53,8 @@ toolchain. On Arch/CachyOS:
```sh ```sh
sudo pacman -S --needed cmake extra-cmake-modules base-devel \ sudo pacman -S --needed cmake extra-cmake-modules base-devel \
qt6-base qt6-declarative kirigami kirigami-addons \ qt6-base qt6-declarative kirigami kirigami-addons \
ki18n kcoreaddons kiconthemes kcrash kdbusaddons kitemmodels qqc2-desktop-style ki18n kcoreaddons kiconthemes kcrash kdbusaddons kitemmodels \
kcolorscheme qqc2-desktop-style
``` ```
Then: Then:
@@ -70,6 +73,7 @@ cmake --build build
- `permissioncatalog.{h,cpp}` - the static catalog of categories and options. - `permissioncatalog.{h,cpp}` - the static catalog of categories and options.
- `portalsbackend.{h,cpp}` - QtDBus PermissionStore client. - `portalsbackend.{h,cpp}` - QtDBus PermissionStore client.
- `permissionscontroller.{h,cpp}` - per-app state, resolution, and the row model. - `permissionscontroller.{h,cpp}` - per-app state, resolution, and the row model.
- `appcolorscheme.{h,cpp}` - QML-facing wrapper around `KColorSchemeManager` for the Preferences page.
- `qml/` - Kirigami UI. - `qml/` - Kirigami UI.
[Flatseal]: https://github.com/tchx84/Flatseal [Flatseal]: https://github.com/tchx84/Flatseal
+1
View File
@@ -8,6 +8,7 @@ path = [
"io.github.toservetheking.FlatKontrol.desktop", "io.github.toservetheking.FlatKontrol.desktop",
"keys/*.asc", "keys/*.asc",
"screenshots/*", "screenshots/*",
"icons/*.png",
] ]
SPDX-License-Identifier = "GPL-3.0-or-later" SPDX-License-Identifier = "GPL-3.0-or-later"
SPDX-FileCopyrightText = "ToServeTheKing <[email protected]>" SPDX-FileCopyrightText = "ToServeTheKing <[email protected]>"
+2
View File
@@ -1,3 +1,5 @@
# SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
#
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
add_executable(resolutiontest add_executable(resolutiontest
+6 -1
View File
@@ -1,4 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "keyfile.h" #include "keyfile.h"
#include "permissionscontroller.h" #include "permissionscontroller.h"
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

+16
View File
@@ -0,0 +1,16 @@
# SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
#
# SPDX-License-Identifier: GPL-3.0-or-later
ecm_install_icons(ICONS
sc-apps-flatkontrol.svg
128-apps-flatkontrol.png
64-apps-flatkontrol.png
48-apps-flatkontrol.png
44-apps-flatkontrol.png
32-apps-flatkontrol.png
22-apps-flatkontrol.png
16-apps-flatkontrol.png
DESTINATION ${KDE_INSTALL_ICONDIR}
THEME hicolor
)
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]> -->
<!-- SPDX-License-Identifier: GPL-3.0-or-later --> <!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"> <svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
<defs> <defs>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -61,6 +61,16 @@
<content_rating type="oars-1.1"/> <content_rating type="oars-1.1"/>
<releases> <releases>
<release version="0.2.2" date="2026-07-03">
<description>
<ul>
<li>Add a Preferences page with a color-scheme switcher</li>
<li>Add an About page</li>
<li>Ship a full hicolor icon set instead of a single scalable SVG</li>
<li>Match KDE System Settings' Audio page layout for the permission list</li>
</ul>
</description>
</release>
<release version="0.2.1" date="2026-07-03"> <release version="0.2.1" date="2026-07-03">
<description> <description>
<ul> <ul>
+2
View File
@@ -1,3 +1,5 @@
# SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
#
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
id: io.github.toservetheking.FlatKontrol id: io.github.toservetheking.FlatKontrol
runtime: org.kde.Platform runtime: org.kde.Platform
+7
View File
@@ -1,3 +1,5 @@
# SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
#
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
add_executable(flatkontrol add_executable(flatkontrol
@@ -15,11 +17,14 @@ qt_add_qml_module(flatkontrol
QML_FILES QML_FILES
qml/Main.qml qml/Main.qml
qml/PermissionsPage.qml qml/PermissionsPage.qml
qml/SettingsPage.qml
SOURCES SOURCES
applicationsmodel.cpp applicationsmodel.cpp
applicationsmodel.h applicationsmodel.h
permissionscontroller.cpp permissionscontroller.cpp
permissionscontroller.h permissionscontroller.h
appcolorscheme.cpp
appcolorscheme.h
) )
target_include_directories(flatkontrol PRIVATE ${CMAKE_BINARY_DIR}) target_include_directories(flatkontrol PRIVATE ${CMAKE_BINARY_DIR})
@@ -38,6 +43,8 @@ target_link_libraries(flatkontrol PRIVATE
KF6::IconThemes KF6::IconThemes
KF6::Crash KF6::Crash
KF6::DBusAddons KF6::DBusAddons
KF6::ColorScheme
KF6::Kirigami
) )
install(TARGETS flatkontrol ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(TARGETS flatkontrol ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
+34
View File
@@ -0,0 +1,34 @@
/*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "appcolorscheme.h"
#include <KColorSchemeManager>
AppColorScheme::AppColorScheme(QObject *parent)
: QObject(parent)
, mSchemes(KColorSchemeManager::instance())
{
}
QAbstractItemModel *AppColorScheme::colorSchemesModel()
{
return mSchemes->model();
}
QString AppColorScheme::activeColorSchemeName() const
{
return mSchemes->activeSchemeName();
}
void AppColorScheme::setActiveColorSchemeName(const QString &name)
{
if (name == activeColorSchemeName()) {
return;
}
mSchemes->activateScheme(mSchemes->indexForScheme(name));
Q_EMIT activeColorSchemeNameChanged();
}
+40
View File
@@ -0,0 +1,40 @@
/*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <QAbstractItemModel>
#include <QObject>
#include <QQmlEngine>
class KColorSchemeManager;
/**
* Thin QML-facing wrapper around KColorSchemeManager: exposes the list of
* installed color schemes and the currently active one. KColorSchemeManager
* autosaves the active scheme itself, so there is nothing else to persist.
*/
class AppColorScheme : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
Q_PROPERTY(QAbstractItemModel *colorSchemesModel READ colorSchemesModel CONSTANT)
Q_PROPERTY(QString activeColorSchemeName READ activeColorSchemeName WRITE setActiveColorSchemeName NOTIFY activeColorSchemeNameChanged)
public:
explicit AppColorScheme(QObject *parent = nullptr);
QAbstractItemModel *colorSchemesModel();
QString activeColorSchemeName() const;
void setActiveColorSchemeName(const QString &name);
Q_SIGNALS:
void activeColorSchemeNameChanged();
private:
KColorSchemeManager *mSchemes;
};
+6 -1
View File
@@ -1,4 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "applicationsmodel.h" #include "applicationsmodel.h"
#include <KDirWatch> #include <KDirWatch>
+6 -1
View File
@@ -1,4 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once #pragma once
#include "flatpakinstallations.h" #include "flatpakinstallations.h"
+6 -1
View File
@@ -1,4 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "flatpakinstallations.h" #include "flatpakinstallations.h"
#include "keyfile.h" #include "keyfile.h"
+6 -1
View File
@@ -1,4 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once #pragma once
#include <QString> #include <QString>
+6 -1
View File
@@ -1,4 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "keyfile.h" #include "keyfile.h"
#include <QDir> #include <QDir>
+6 -1
View File
@@ -1,4 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once #pragma once
#include <QMap> #include <QMap>
+6 -1
View File
@@ -1,4 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "flatkontrol-version.h" #include "flatkontrol-version.h"
#include "applicationsmodel.h" #include "applicationsmodel.h"
+6 -1
View File
@@ -1,4 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "permissioncatalog.h" #include "permissioncatalog.h"
#include <KLocalizedString> #include <KLocalizedString>
+6 -1
View File
@@ -1,4 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once #pragma once
#include <QList> #include <QList>
+6 -1
View File
@@ -1,4 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "permissionscontroller.h" #include "permissionscontroller.h"
#include <KLocalizedString> #include <KLocalizedString>
+7 -2
View File
@@ -1,4 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once #pragma once
#include "flatpakinstallations.h" #include "flatpakinstallations.h"
@@ -187,7 +192,7 @@ private:
static bool isFilesystemPreset(const QString &bareToken); static bool isFilesystemPreset(const QString &bareToken);
FlatpakInstallations m_installations; FlatpakInstallations m_installations;
FlatKontrol::PortalsBackend m_portals; PortalsBackend m_portals;
QString m_appId; QString m_appId;
QString m_appName; QString m_appName;
+6 -6
View File
@@ -1,4 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "portalsbackend.h" #include "portalsbackend.h"
#include <KLocalizedString> #include <KLocalizedString>
@@ -9,9 +14,6 @@
#include <QDBusMessage> #include <QDBusMessage>
#include <QDBusReply> #include <QDBusReply>
namespace FlatKontrol
{
static constexpr uint SUPPORTED_SERVICE_VERSION = 2; static constexpr uint SUPPORTED_SERVICE_VERSION = 2;
static const char *DBUS_PATH = "/org/freedesktop/impl/portal/PermissionStore"; static const char *DBUS_PATH = "/org/freedesktop/impl/portal/PermissionStore";
static const char *DBUS_IFACE = "org.freedesktop.impl.portal.PermissionStore"; static const char *DBUS_IFACE = "org.freedesktop.impl.portal.PermissionStore";
@@ -253,5 +255,3 @@ void PortalsBackend::reload()
m_supported.clear(); m_supported.clear();
m_reasons.clear(); m_reasons.clear();
} }
} // namespace FlatKontrol
+6 -6
View File
@@ -1,4 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once #pragma once
#include <QHash> #include <QHash>
@@ -8,9 +13,6 @@
class QDBusInterface; class QDBusInterface;
namespace FlatKontrol
{
/// Tri-state (plus availability) of a portal permission, matching Flatseal. /// Tri-state (plus availability) of a portal permission, matching Flatseal.
enum class PortalState { enum class PortalState {
Unknown = 0, Unknown = 0,
@@ -71,5 +73,3 @@ private:
QHash<QString, bool> m_supported; // property -> supported QHash<QString, bool> m_supported; // property -> supported
QHash<QString, QString> m_reasons; // property -> reason QHash<QString, QString> m_reasons; // property -> reason
}; };
} // namespace FlatKontrol
+45 -1
View File
@@ -1,4 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
@@ -7,6 +12,7 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kitemmodels as KItemModels import org.kde.kitemmodels as KItemModels
import org.kde.kirigamiaddons.delegates as Delegates import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.kirigamiaddons.formcard as FormCard
import io.github.toservetheking.FlatKontrol import io.github.toservetheking.FlatKontrol
Kirigami.ApplicationWindow { Kirigami.ApplicationWindow {
@@ -19,6 +25,44 @@ Kirigami.ApplicationWindow {
width: Kirigami.Units.gridUnit * 48 width: Kirigami.Units.gridUnit * 48
height: Kirigami.Units.gridUnit * 34 height: Kirigami.Units.gridUnit * 34
globalDrawer: Kirigami.GlobalDrawer {
isMenu: true
actions: [
Kirigami.Action {
text: i18nc("@action:inmenu", "Preferences…")
icon.name: "configure"
onTriggered: root.pageStack.pushDialogLayer(settingsPageComponent, {
width: root.width
}, {
width: Kirigami.Units.gridUnit * 24,
height: Kirigami.Units.gridUnit * 20,
modality: Qt.NonModal
})
},
Kirigami.Action {
text: i18nc("@action:inmenu", "About %1", root.title)
icon.name: "help-about"
onTriggered: root.pageStack.pushDialogLayer(aboutPageComponent, {
width: root.width
}, {
width: Kirigami.Units.gridUnit * 30,
height: Kirigami.Units.gridUnit * 30,
modality: Qt.NonModal
})
}
]
}
Component {
id: aboutPageComponent
FormCard.AboutPage {}
}
Component {
id: settingsPageComponent
SettingsPage {}
}
ApplicationsModel { ApplicationsModel {
id: appsModel id: appsModel
} }
+159 -41
View File
@@ -1,6 +1,14 @@
// SPDX-License-Identifier: GPL-3.0-or-later /*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
// Styled after KDE System Settings' Audio page (plasma-pa's kcm/ui/main.qml):
// Kirigami.ListSectionHeader per category, flat rows indented under the
// header and separated by Kirigami.Separator, no card/border around them.
import QtQuick import QtQuick
import QtQuick.Controls as QQC2 import QtQuick.Controls as QQC2
import QtQuick.Layouts import QtQuick.Layouts
@@ -8,7 +16,6 @@ import QtQuick.Dialogs
import Qt.labs.qmlmodels import Qt.labs.qmlmodels
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kitemmodels as KItemModels import org.kde.kitemmodels as KItemModels
import org.kde.kirigamiaddons.formcard as FormCard
import io.github.toservetheking.FlatKontrol import io.github.toservetheking.FlatKontrol
Kirigami.ScrollablePage { Kirigami.ScrollablePage {
@@ -22,10 +29,12 @@ Kirigami.ScrollablePage {
leftPadding: 0 leftPadding: 0
rightPadding: 0 rightPadding: 0
topPadding: Kirigami.Units.gridUnit // No topPadding: Kirigami.ListSectionHeader (the first thing on the
// page) already carries its own top padding, and stacking ours on top
// of that left an oversized gap above the first category.
bottomPadding: Kirigami.Units.gridUnit bottomPadding: Kirigami.Units.gridUnit
// A small status marker used across delegates. // A small status marker used across rows.
component StatusBadge: Kirigami.Icon { component StatusBadge: Kirigami.Icon {
property int state: 0 property int state: 0
visible: state > 0 visible: state > 0
@@ -86,7 +95,7 @@ Kirigami.ScrollablePage {
} }
ColumnLayout { ColumnLayout {
spacing: Kirigami.Units.largeSpacing spacing: 0
visible: page.hasSelection visible: page.hasSelection
Repeater { Repeater {
@@ -100,20 +109,29 @@ Kirigami.ScrollablePage {
Layout.fillWidth: true Layout.fillWidth: true
spacing: 0 spacing: 0
FormCard.FormHeader { Kirigami.ListSectionHeader {
title: section.modelData.title Layout.fillWidth: true
text: section.modelData.title
} }
FormCard.FormCard { KItemModels.KSortFilterProxyModel {
KItemModels.KSortFilterProxyModel { id: catModel
id: catModel sourceModel: page.controller
sourceModel: page.controller filterRoleName: "categoryId"
filterRoleName: "categoryId" // No category id is a substring of another, so this is an exact match.
// No category id is a substring of another, so this is an exact match. filterString: section.modelData.id
filterString: section.modelData.id }
}
ColumnLayout {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.largeSpacing
Repeater { Repeater {
id: rowRepeater
model: catModel model: catModel
delegate: DelegateChooser { delegate: DelegateChooser {
@@ -122,7 +140,7 @@ Kirigami.ScrollablePage {
// Toggle // Toggle
DelegateChoice { DelegateChoice {
roleValue: 0 roleValue: 0
delegate: FormCard.FormSwitchDelegate { delegate: ColumnLayout {
id: toggleD id: toggleD
required property int index required property int index
required property string label required property string label
@@ -130,18 +148,51 @@ Kirigami.ScrollablePage {
required property bool value required property bool value
required property int status required property int status
readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row
text: label Layout.fillWidth: true
description: example spacing: Kirigami.Units.smallSpacing
checked: value
icon.name: status === 2 ? "document-edit-symbolic" : (status === 1 ? "globe-symbolic" : "") RowLayout {
onToggled: page.controller.setToggleValue(sourceRow, checked) Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
ColumnLayout {
Layout.fillWidth: true
spacing: 0
QQC2.Label {
Layout.fillWidth: true
text: toggleD.label
wrapMode: Text.Wrap
}
QQC2.Label {
Layout.fillWidth: true
text: toggleD.example
visible: text.length > 0
wrapMode: Text.Wrap
font: Kirigami.Theme.smallFont
color: Kirigami.Theme.disabledTextColor
}
}
StatusBadge {
state: toggleD.status
}
QQC2.Switch {
checked: toggleD.value
onToggled: page.controller.setToggleValue(toggleD.sourceRow, checked)
}
}
Kirigami.Separator {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.smallSpacing
visible: toggleD.index !== rowRepeater.count - 1
}
} }
} }
// Filesystem path (with access mode + Browse) // Filesystem path (with access mode + Browse)
DelegateChoice { DelegateChoice {
roleValue: 1 roleValue: 1
delegate: FormCard.AbstractFormDelegate { delegate: ColumnLayout {
id: fsD id: fsD
required property int index required property int index
required property string value required property string value
@@ -149,7 +200,8 @@ Kirigami.ScrollablePage {
required property int status required property int status
required property bool removable required property bool removable
readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row
background: null Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
FolderDialog { FolderDialog {
id: folderDialog id: folderDialog
@@ -160,7 +212,8 @@ Kirigami.ScrollablePage {
} }
} }
contentItem: RowLayout { RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
QQC2.TextField { QQC2.TextField {
id: fsField id: fsField
@@ -190,21 +243,30 @@ Kirigami.ScrollablePage {
onClicked: page.controller.removeEntry(fsD.sourceRow) onClicked: page.controller.removeEntry(fsD.sourceRow)
} }
} }
Kirigami.Separator {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.smallSpacing
visible: fsD.index !== rowRepeater.count - 1
}
} }
} }
// Persistent (home-relative) path // Persistent (home-relative) path
DelegateChoice { DelegateChoice {
roleValue: 2 roleValue: 2
delegate: FormCard.AbstractFormDelegate { delegate: ColumnLayout {
id: relD id: relD
required property int index required property int index
required property string value required property string value
required property int status required property int status
required property bool removable required property bool removable
readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row
background: null Layout.fillWidth: true
contentItem: RowLayout { spacing: Kirigami.Units.smallSpacing
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
QQC2.TextField { QQC2.TextField {
text: relD.value text: relD.value
@@ -221,13 +283,19 @@ Kirigami.ScrollablePage {
onClicked: page.controller.removeEntry(relD.sourceRow) onClicked: page.controller.removeEntry(relD.sourceRow)
} }
} }
Kirigami.Separator {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.smallSpacing
visible: relD.index !== rowRepeater.count - 1
}
} }
} }
// Environment variable // Environment variable
DelegateChoice { DelegateChoice {
roleValue: 3 roleValue: 3
delegate: FormCard.AbstractFormDelegate { delegate: ColumnLayout {
id: varD id: varD
required property int index required property int index
required property string value required property string value
@@ -235,8 +303,11 @@ Kirigami.ScrollablePage {
required property int status required property int status
required property bool removable required property bool removable
readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row
background: null Layout.fillWidth: true
contentItem: RowLayout { spacing: Kirigami.Units.smallSpacing
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
QQC2.TextField { QQC2.TextField {
text: varD.value text: varD.value
@@ -262,13 +333,19 @@ Kirigami.ScrollablePage {
onClicked: page.controller.removeEntry(varD.sourceRow) onClicked: page.controller.removeEntry(varD.sourceRow)
} }
} }
Kirigami.Separator {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.smallSpacing
visible: varD.index !== rowRepeater.count - 1
}
} }
} }
// D-Bus name policy // D-Bus name policy
DelegateChoice { DelegateChoice {
roleValue: 4 roleValue: 4
delegate: FormCard.AbstractFormDelegate { delegate: ColumnLayout {
id: busD id: busD
required property int index required property int index
required property string value required property string value
@@ -276,8 +353,11 @@ Kirigami.ScrollablePage {
required property int status required property int status
required property bool removable required property bool removable
readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row
background: null Layout.fillWidth: true
contentItem: RowLayout { spacing: Kirigami.Units.smallSpacing
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
QQC2.TextField { QQC2.TextField {
text: busD.value text: busD.value
@@ -299,13 +379,19 @@ Kirigami.ScrollablePage {
onClicked: page.controller.removeEntry(busD.sourceRow) onClicked: page.controller.removeEntry(busD.sourceRow)
} }
} }
Kirigami.Separator {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.smallSpacing
visible: busD.index !== rowRepeater.count - 1
}
} }
} }
// Portal (tri-state) // Portal (tri-state)
DelegateChoice { DelegateChoice {
roleValue: 5 roleValue: 5
delegate: FormCard.FormComboBoxDelegate { delegate: ColumnLayout {
id: portalD id: portalD
required property int index required property int index
required property string label required property string label
@@ -315,19 +401,51 @@ Kirigami.ScrollablePage {
required property string unsupportedReason required property string unsupportedReason
readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row
readonly property var states: [2, 4, 3] readonly property var states: [2, 4, 3]
text: label Layout.fillWidth: true
description: supported ? example : unsupportedReason spacing: Kirigami.Units.smallSpacing
enabled: supported
model: [i18n("Unset"), i18n("Allowed"), i18n("Disallowed")] RowLayout {
currentIndex: value === 4 ? 1 : (value === 3 ? 2 : 0) Layout.fillWidth: true
onActivated: page.controller.setPortalValue(sourceRow, states[currentIndex]) spacing: Kirigami.Units.smallSpacing
ColumnLayout {
Layout.fillWidth: true
spacing: 0
QQC2.Label {
Layout.fillWidth: true
text: portalD.label
wrapMode: Text.Wrap
enabled: portalD.supported
}
QQC2.Label {
Layout.fillWidth: true
text: portalD.supported ? portalD.example : portalD.unsupportedReason
visible: text.length > 0
wrapMode: Text.Wrap
font: Kirigami.Theme.smallFont
color: Kirigami.Theme.disabledTextColor
}
}
QQC2.ComboBox {
enabled: portalD.supported
model: [i18n("Unset"), i18n("Allowed"), i18n("Disallowed")]
currentIndex: portalD.value === 4 ? 1 : (portalD.value === 3 ? 2 : 0)
onActivated: page.controller.setPortalValue(portalD.sourceRow, portalD.states[currentIndex])
}
}
Kirigami.Separator {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.smallSpacing
visible: portalD.index !== rowRepeater.count - 1
}
} }
} }
// "Add entry" affordance // "Add entry" affordance
DelegateChoice { DelegateChoice {
roleValue: 6 roleValue: 6
delegate: FormCard.FormButtonDelegate { delegate: QQC2.Button {
id: addD id: addD
required property string categoryId required property string categoryId
text: i18nc("@action", "Add…") text: i18nc("@action", "Add…")
+79
View File
@@ -0,0 +1,79 @@
/*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
pragma ComponentBehavior: Bound
// Styled after KDE System Settings' Audio page (plasma-pa's kcm/ui/main.qml):
// Kirigami.ListSectionHeader per section, flat rows indented under the
// header, no card/border around them.
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import io.github.toservetheking.FlatKontrol
Kirigami.ScrollablePage {
id: root
title: i18nc("@title:window", "Preferences")
leftPadding: 0
rightPadding: 0
// No topPadding: Kirigami.ListSectionHeader (the first thing on the
// page) already carries its own top padding, and stacking ours on top
// of that left an oversized gap above "Appearance".
bottomPadding: Kirigami.Units.gridUnit
ColumnLayout {
width: root.width
spacing: 0
Kirigami.ListSectionHeader {
Layout.fillWidth: true
text: i18nc("@title:group", "Appearance")
}
ColumnLayout {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
QQC2.Label {
Layout.fillWidth: true
text: i18nc("@label:listbox", "Color scheme")
}
QQC2.ComboBox {
id: colorSchemeCombo
Layout.fillWidth: true
model: AppColorScheme.colorSchemesModel
textRole: "display"
delegate: QQC2.ItemDelegate {
id: schemeDelegate
required property var model
width: colorSchemeCombo.width
icon.source: "image://colorScheme/" + schemeDelegate.model.display
icon.color: "transparent"
text: schemeDelegate.model.display
highlighted: schemeDelegate.model.display === AppColorScheme.activeColorSchemeName
onClicked: {
AppColorScheme.activeColorSchemeName = schemeDelegate.model.display;
colorSchemeCombo.popup.close();
}
}
// Keep the closed-box label in sync without fighting the popup's own selection state.
displayText: AppColorScheme.activeColorSchemeName
}
}
}
}