Debounce settings writes and save them on SIGTERM
- DebouncedStore, taken out of WindowSettings, now also saves the opacity slider once a drag ends instead of on every step - SIGTERM/SIGINT (how KWin stops the input method) quit normally, so pending geometry and settings are written - The slider's minimum opacity comes from Settings instead of a copy in QML Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -65,7 +65,7 @@ Flickable {
|
|||||||
SliderRow {
|
SliderRow {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: qsTr("Background")
|
text: qsTr("Background")
|
||||||
from: 0.1 // Settings::MinBackgroundOpacity
|
from: panel.settings.minBackgroundOpacity
|
||||||
value: panel.settings.backgroundOpacity
|
value: panel.settings.backgroundOpacity
|
||||||
onMoved: value => panel.settings.backgroundOpacity = value
|
onMoved: value => panel.settings.backgroundOpacity = value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ qt_add_library(sloposk_core STATIC
|
|||||||
layout/LayoutModel.h layout/LayoutModel.cpp
|
layout/LayoutModel.h layout/LayoutModel.cpp
|
||||||
window/VisibilityController.h window/VisibilityController.cpp
|
window/VisibilityController.h window/VisibilityController.cpp
|
||||||
window/WindowSettings.h window/WindowSettings.cpp
|
window/WindowSettings.h window/WindowSettings.cpp
|
||||||
|
settings/DebouncedStore.h settings/DebouncedStore.cpp
|
||||||
settings/Settings.h settings/Settings.cpp
|
settings/Settings.h settings/Settings.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -76,6 +77,7 @@ qt_add_executable(sloposk
|
|||||||
main.cpp
|
main.cpp
|
||||||
app/DevHooks.h app/DevHooks.cpp
|
app/DevHooks.h app/DevHooks.cpp
|
||||||
app/DBusControl.h app/DBusControl.cpp
|
app/DBusControl.h app/DBusControl.cpp
|
||||||
|
app/QuitOnSignal.h app/QuitOnSignal.cpp
|
||||||
app/SystemLayouts.h app/SystemLayouts.cpp
|
app/SystemLayouts.h app/SystemLayouts.cpp
|
||||||
backend/InputMethodProtocol.h
|
backend/InputMethodProtocol.h
|
||||||
backend/KWinInputMethod.h backend/KWinInputMethod.cpp
|
backend/KWinInputMethod.h backend/KWinInputMethod.cpp
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#include "QuitOnSignal.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QSocketNotifier>
|
||||||
|
|
||||||
|
#include <csignal>
|
||||||
|
#include <sys/signalfd.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
sigset_t quitSignals()
|
||||||
|
{
|
||||||
|
sigset_t set;
|
||||||
|
sigemptyset(&set);
|
||||||
|
sigaddset(&set, SIGTERM);
|
||||||
|
sigaddset(&set, SIGINT);
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void blockQuitSignals()
|
||||||
|
{
|
||||||
|
const sigset_t set = quitSignals();
|
||||||
|
pthread_sigmask(SIG_BLOCK, &set, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void quitOnSignal()
|
||||||
|
{
|
||||||
|
const sigset_t set = quitSignals();
|
||||||
|
const int fd = signalfd(-1, &set, SFD_CLOEXEC | SFD_NONBLOCK);
|
||||||
|
if (fd < 0) {
|
||||||
|
qWarning("signalfd failed: settings may not be saved on SIGTERM");
|
||||||
|
pthread_sigmask(SIG_UNBLOCK, &set, nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto *notifier = new QSocketNotifier(fd, QSocketNotifier::Read, QCoreApplication::instance());
|
||||||
|
QObject::connect(notifier, &QSocketNotifier::activated, notifier, [fd] {
|
||||||
|
signalfd_siginfo info;
|
||||||
|
while (read(fd, &info, sizeof info) == sizeof info) { }
|
||||||
|
QCoreApplication::quit();
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// KWin stops the input method with SIGTERM. Turns SIGTERM and SIGINT into a
|
||||||
|
// normal quit, so destructors run and pending settings are written.
|
||||||
|
// Call before QGuiApplication: threads Qt starts must inherit the blocked mask.
|
||||||
|
void blockQuitSignals();
|
||||||
|
// Call once the application exists.
|
||||||
|
void quitOnSignal();
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "app/DBusControl.h"
|
#include "app/DBusControl.h"
|
||||||
#include "app/DevHooks.h"
|
#include "app/DevHooks.h"
|
||||||
|
#include "app/QuitOnSignal.h"
|
||||||
#include "app/SystemLayouts.h"
|
#include "app/SystemLayouts.h"
|
||||||
#include "backend/KWinInputMethod.h"
|
#include "backend/KWinInputMethod.h"
|
||||||
#include "backend/WaylandKeymap.h"
|
#include "backend/WaylandKeymap.h"
|
||||||
@@ -115,7 +116,9 @@ void connectTextContext(KWinInputMethod &backend, KeyboardController &controller
|
|||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
blockQuitSignals();
|
||||||
QGuiApplication app(argc, argv);
|
QGuiApplication app(argc, argv);
|
||||||
|
quitOnSignal();
|
||||||
QGuiApplication::setApplicationName(QStringLiteral("sloposk"));
|
QGuiApplication::setApplicationName(QStringLiteral("sloposk"));
|
||||||
QGuiApplication::setOrganizationName(QStringLiteral("sloposk")); // ~/.config/sloposk/sloposk.conf
|
QGuiApplication::setOrganizationName(QStringLiteral("sloposk")); // ~/.config/sloposk/sloposk.conf
|
||||||
QGuiApplication::setDesktopFileName(QStringLiteral("org.sloposk.keyboard"));
|
QGuiApplication::setDesktopFileName(QStringLiteral("org.sloposk.keyboard"));
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#include "DebouncedStore.h"
|
||||||
|
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int DebounceMs = 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
DebouncedStore::DebouncedStore(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
m_debounce.setSingleShot(true);
|
||||||
|
m_debounce.setInterval(DebounceMs);
|
||||||
|
connect(&m_debounce, &QTimer::timeout, this, &DebouncedStore::flush);
|
||||||
|
}
|
||||||
|
|
||||||
|
DebouncedStore::~DebouncedStore()
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebouncedStore::setValue(const QString &key, const QVariant &value)
|
||||||
|
{
|
||||||
|
m_pending.insert(key, value);
|
||||||
|
m_debounce.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebouncedStore::flush()
|
||||||
|
{
|
||||||
|
m_debounce.stop();
|
||||||
|
if (m_pending.isEmpty())
|
||||||
|
return;
|
||||||
|
QSettings store;
|
||||||
|
for (auto it = m_pending.cbegin(); it != m_pending.cend(); ++it)
|
||||||
|
store.setValue(it.key(), it.value());
|
||||||
|
m_pending.clear();
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QVariantMap>
|
||||||
|
|
||||||
|
// QSettings writes that arrive in bursts (dragging a slider or the window),
|
||||||
|
// saved once the burst ends instead of on every step. Flushes on destruction.
|
||||||
|
class DebouncedStore : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DebouncedStore(QObject *parent = nullptr);
|
||||||
|
~DebouncedStore() override;
|
||||||
|
|
||||||
|
void setValue(const QString &key, const QVariant &value);
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QTimer m_debounce;
|
||||||
|
QVariantMap m_pending; // key → value
|
||||||
|
};
|
||||||
@@ -38,7 +38,7 @@ void Settings::setBackgroundOpacity(qreal opacity)
|
|||||||
if (qFuzzyCompare(opacity, m_backgroundOpacity))
|
if (qFuzzyCompare(opacity, m_backgroundOpacity))
|
||||||
return;
|
return;
|
||||||
m_backgroundOpacity = opacity;
|
m_backgroundOpacity = opacity;
|
||||||
QSettings().setValue(BackgroundOpacityKey, opacity);
|
m_sliderStore.setValue(QString::fromLatin1(BackgroundOpacityKey), opacity);
|
||||||
Q_EMIT appearanceChanged();
|
Q_EMIT appearanceChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "DebouncedStore.h"
|
||||||
#include "input/TypingOptions.h"
|
#include "input/TypingOptions.h"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
@@ -24,6 +25,7 @@ class Settings : public QObject
|
|||||||
Q_PROPERTY(bool alwaysVisible READ alwaysVisible WRITE setAlwaysVisible NOTIFY alwaysVisibleChanged)
|
Q_PROPERTY(bool alwaysVisible READ alwaysVisible WRITE setAlwaysVisible NOTIFY alwaysVisibleChanged)
|
||||||
// Opacity of the panel behind the keys (MinBackgroundOpacity–1); keys stay opaque.
|
// Opacity of the panel behind the keys (MinBackgroundOpacity–1); keys stay opaque.
|
||||||
Q_PROPERTY(qreal backgroundOpacity READ backgroundOpacity WRITE setBackgroundOpacity NOTIFY appearanceChanged)
|
Q_PROPERTY(qreal backgroundOpacity READ backgroundOpacity WRITE setBackgroundOpacity NOTIFY appearanceChanged)
|
||||||
|
Q_PROPERTY(qreal minBackgroundOpacity READ minBackgroundOpacity CONSTANT)
|
||||||
// Blurs what shows through a translucent background (needs compositor support, e.g. KWin).
|
// Blurs what shows through a translucent background (needs compositor support, e.g. KWin).
|
||||||
Q_PROPERTY(bool blurBackground READ blurBackground WRITE setBlurBackground NOTIFY appearanceChanged)
|
Q_PROPERTY(bool blurBackground READ blurBackground WRITE setBlurBackground NOTIFY appearanceChanged)
|
||||||
|
|
||||||
@@ -49,6 +51,7 @@ public:
|
|||||||
void setAlwaysVisible(bool always);
|
void setAlwaysVisible(bool always);
|
||||||
|
|
||||||
static constexpr qreal MinBackgroundOpacity = 0.1;
|
static constexpr qreal MinBackgroundOpacity = 0.1;
|
||||||
|
static qreal minBackgroundOpacity() { return MinBackgroundOpacity; }
|
||||||
qreal backgroundOpacity() const { return m_backgroundOpacity; }
|
qreal backgroundOpacity() const { return m_backgroundOpacity; }
|
||||||
void setBackgroundOpacity(qreal opacity);
|
void setBackgroundOpacity(qreal opacity);
|
||||||
bool blurBackground() const { return m_blurBackground; }
|
bool blurBackground() const { return m_blurBackground; }
|
||||||
@@ -64,6 +67,7 @@ Q_SIGNALS:
|
|||||||
private:
|
private:
|
||||||
void setTypingOptions(const TypingOptions &options);
|
void setTypingOptions(const TypingOptions &options);
|
||||||
|
|
||||||
|
DebouncedStore m_sliderStore; // slider drags change values many times a second
|
||||||
TypingOptions m_typing;
|
TypingOptions m_typing;
|
||||||
QStringList m_visibleSections;
|
QStringList m_visibleSections;
|
||||||
bool m_fullKeyboard = false;
|
bool m_fullKeyboard = false;
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr int DebounceMs = 500;
|
|
||||||
|
|
||||||
QString key(const QString &screen)
|
QString key(const QString &screen)
|
||||||
{
|
{
|
||||||
// Screen names may contain '/', which QSettings treats as a group separator.
|
// Screen names may contain '/', which QSettings treats as a group separator.
|
||||||
@@ -17,15 +15,6 @@ QString key(const QString &screen)
|
|||||||
WindowSettings::WindowSettings(QObject *parent)
|
WindowSettings::WindowSettings(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
m_debounce.setSingleShot(true);
|
|
||||||
m_debounce.setInterval(DebounceMs);
|
|
||||||
connect(&m_debounce, &QTimer::timeout, this, &WindowSettings::flush);
|
|
||||||
}
|
|
||||||
|
|
||||||
WindowSettings::~WindowSettings()
|
|
||||||
{
|
|
||||||
if (m_debounce.isActive())
|
|
||||||
flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<QRect> WindowSettings::load(const QString &screen) const
|
std::optional<QRect> WindowSettings::load(const QString &screen) const
|
||||||
@@ -38,15 +27,5 @@ std::optional<QRect> WindowSettings::load(const QString &screen) const
|
|||||||
|
|
||||||
void WindowSettings::save(const QString &screen, const QRect &geometry)
|
void WindowSettings::save(const QString &screen, const QRect &geometry)
|
||||||
{
|
{
|
||||||
if (m_debounce.isActive() && screen != m_pendingScreen)
|
m_store.setValue(key(screen), geometry);
|
||||||
flush();
|
|
||||||
m_pendingScreen = screen;
|
|
||||||
m_pendingGeometry = geometry;
|
|
||||||
m_debounce.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WindowSettings::flush()
|
|
||||||
{
|
|
||||||
m_debounce.stop();
|
|
||||||
QSettings().setValue(key(m_pendingScreen), m_pendingGeometry);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "settings/DebouncedStore.h"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QRect>
|
#include <QRect>
|
||||||
#include <QTimer>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
// Remembers the keyboard's geometry per screen (~/.config/sloposk.conf).
|
// Remembers the keyboard's geometry per screen (~/.config/sloposk.conf).
|
||||||
@@ -13,15 +14,10 @@ class WindowSettings : public QObject
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit WindowSettings(QObject *parent = nullptr);
|
explicit WindowSettings(QObject *parent = nullptr);
|
||||||
~WindowSettings() override;
|
|
||||||
|
|
||||||
std::optional<QRect> load(const QString &screen) const;
|
std::optional<QRect> load(const QString &screen) const;
|
||||||
void save(const QString &screen, const QRect &geometry);
|
void save(const QString &screen, const QRect &geometry);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void flush();
|
DebouncedStore m_store;
|
||||||
|
|
||||||
QTimer m_debounce;
|
|
||||||
QString m_pendingScreen;
|
|
||||||
QRect m_pendingGeometry;
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user