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:
@@ -24,6 +24,7 @@ qt_add_library(sloposk_core STATIC
|
||||
layout/LayoutModel.h layout/LayoutModel.cpp
|
||||
window/VisibilityController.h window/VisibilityController.cpp
|
||||
window/WindowSettings.h window/WindowSettings.cpp
|
||||
settings/DebouncedStore.h settings/DebouncedStore.cpp
|
||||
settings/Settings.h settings/Settings.cpp
|
||||
)
|
||||
|
||||
@@ -76,6 +77,7 @@ qt_add_executable(sloposk
|
||||
main.cpp
|
||||
app/DevHooks.h app/DevHooks.cpp
|
||||
app/DBusControl.h app/DBusControl.cpp
|
||||
app/QuitOnSignal.h app/QuitOnSignal.cpp
|
||||
app/SystemLayouts.h app/SystemLayouts.cpp
|
||||
backend/InputMethodProtocol.h
|
||||
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/DevHooks.h"
|
||||
#include "app/QuitOnSignal.h"
|
||||
#include "app/SystemLayouts.h"
|
||||
#include "backend/KWinInputMethod.h"
|
||||
#include "backend/WaylandKeymap.h"
|
||||
@@ -115,7 +116,9 @@ void connectTextContext(KWinInputMethod &backend, KeyboardController &controller
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
blockQuitSignals();
|
||||
QGuiApplication app(argc, argv);
|
||||
quitOnSignal();
|
||||
QGuiApplication::setApplicationName(QStringLiteral("sloposk"));
|
||||
QGuiApplication::setOrganizationName(QStringLiteral("sloposk")); // ~/.config/sloposk/sloposk.conf
|
||||
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))
|
||||
return;
|
||||
m_backgroundOpacity = opacity;
|
||||
QSettings().setValue(BackgroundOpacityKey, opacity);
|
||||
m_sliderStore.setValue(QString::fromLatin1(BackgroundOpacityKey), opacity);
|
||||
Q_EMIT appearanceChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "DebouncedStore.h"
|
||||
#include "input/TypingOptions.h"
|
||||
|
||||
#include <QObject>
|
||||
@@ -24,6 +25,7 @@ class Settings : public QObject
|
||||
Q_PROPERTY(bool alwaysVisible READ alwaysVisible WRITE setAlwaysVisible NOTIFY alwaysVisibleChanged)
|
||||
// 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 minBackgroundOpacity READ minBackgroundOpacity CONSTANT)
|
||||
// Blurs what shows through a translucent background (needs compositor support, e.g. KWin).
|
||||
Q_PROPERTY(bool blurBackground READ blurBackground WRITE setBlurBackground NOTIFY appearanceChanged)
|
||||
|
||||
@@ -49,6 +51,7 @@ public:
|
||||
void setAlwaysVisible(bool always);
|
||||
|
||||
static constexpr qreal MinBackgroundOpacity = 0.1;
|
||||
static qreal minBackgroundOpacity() { return MinBackgroundOpacity; }
|
||||
qreal backgroundOpacity() const { return m_backgroundOpacity; }
|
||||
void setBackgroundOpacity(qreal opacity);
|
||||
bool blurBackground() const { return m_blurBackground; }
|
||||
@@ -64,6 +67,7 @@ Q_SIGNALS:
|
||||
private:
|
||||
void setTypingOptions(const TypingOptions &options);
|
||||
|
||||
DebouncedStore m_sliderStore; // slider drags change values many times a second
|
||||
TypingOptions m_typing;
|
||||
QStringList m_visibleSections;
|
||||
bool m_fullKeyboard = false;
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int DebounceMs = 500;
|
||||
|
||||
QString key(const QString &screen)
|
||||
{
|
||||
// Screen names may contain '/', which QSettings treats as a group separator.
|
||||
@@ -17,15 +15,6 @@ QString key(const QString &screen)
|
||||
WindowSettings::WindowSettings(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
|
||||
@@ -38,15 +27,5 @@ std::optional<QRect> WindowSettings::load(const QString &screen) const
|
||||
|
||||
void WindowSettings::save(const QString &screen, const QRect &geometry)
|
||||
{
|
||||
if (m_debounce.isActive() && screen != m_pendingScreen)
|
||||
flush();
|
||||
m_pendingScreen = screen;
|
||||
m_pendingGeometry = geometry;
|
||||
m_debounce.start();
|
||||
}
|
||||
|
||||
void WindowSettings::flush()
|
||||
{
|
||||
m_debounce.stop();
|
||||
QSettings().setValue(key(m_pendingScreen), m_pendingGeometry);
|
||||
m_store.setValue(key(screen), geometry);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "settings/DebouncedStore.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QRect>
|
||||
#include <QTimer>
|
||||
#include <optional>
|
||||
|
||||
// Remembers the keyboard's geometry per screen (~/.config/sloposk.conf).
|
||||
@@ -13,15 +14,10 @@ class WindowSettings : public QObject
|
||||
|
||||
public:
|
||||
explicit WindowSettings(QObject *parent = nullptr);
|
||||
~WindowSettings() override;
|
||||
|
||||
std::optional<QRect> load(const QString &screen) const;
|
||||
void save(const QString &screen, const QRect &geometry);
|
||||
|
||||
private:
|
||||
void flush();
|
||||
|
||||
QTimer m_debounce;
|
||||
QString m_pendingScreen;
|
||||
QRect m_pendingGeometry;
|
||||
DebouncedStore m_store;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user