diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ff6497..fa32ece 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ find_package(KF5ModemManagerQt REQUIRED) pkg_search_module(FontConfig REQUIRED fontconfig IMPORTED_TARGET) pkg_search_module(ICU REQUIRED icu-i18n) +pkg_check_modules(XORGLIBINPUT xorg-libinput IMPORTED_TARGET) include_directories(${ICU_INCLUDE_DIRS}) @@ -44,6 +45,9 @@ set(SRCS src/powermanager.cpp src/cursor/cursorthememodel.cpp src/cursor/cursortheme.cpp + src/cursor/mouse.cpp + src/cursor/inputdummydevice.cpp + src/cursor/libinputsettings.cpp ) set(RESOURCES @@ -79,6 +83,8 @@ target_link_libraries(${PROJECT_NAME} X11::X11 X11::Xi X11::Xcursor + + PkgConfig::XORGLIBINPUT ) file(GLOB TS_FILES translations/*.ts) diff --git a/README.md b/README.md index 0307655..ea862d4 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ sudo pacman -S extra-cmake-modules qt5-base qt5-quickcontrols2 freetype2 fontcon Debian/Ubuntu Dependencies: ```shell -sudo apt install cmake debhelper extra-cmake-modules libicu-devlibcrypt-dev libfreetype6-dev libfontconfig1-dev libkf5networkmanagerqt-dev modemmanager-qt-dev qtbase5-dev qtdeclarative5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools qml-module-qtquick-controls2 qml-module-qtquick2 qml-module-qtquick-layouts qml-module-qt-labs-platform qml-module-qt-labs-settings qml-module-qtqml qml-module-qtquick-window2 qml-module-qtquick-shapes qml-module-qtquick-dialogs qml-module-qtquick-particles2 +sudo apt install cmake debhelper extra-cmake-modules libicu-devlibcrypt-dev libfreetype6-dev libfontconfig1-dev xserver-xorg-input-libinput-dev libkf5networkmanagerqt-dev modemmanager-qt-dev qtbase5-dev qtdeclarative5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools qml-module-qtquick-controls2 qml-module-qtquick2 qml-module-qtquick-layouts qml-module-qt-labs-platform qml-module-qt-labs-settings qml-module-qtqml qml-module-qtquick-window2 qml-module-qtquick-shapes qml-module-qtquick-dialogs qml-module-qtquick-particles2 ``` ## Build diff --git a/debian/control b/debian/control index 946683e..9f028b7 100644 --- a/debian/control +++ b/debian/control @@ -10,6 +10,7 @@ Build-Depends: cmake, libfreetype6-dev, libfontconfig1-dev, libkf5networkmanagerqt-dev, + xserver-xorg-input-libinput-dev, modemmanager-qt-dev, qtbase5-dev, libqt5x11extras5-dev, diff --git a/src/application.cpp b/src/application.cpp index cdf10bd..f8a36ca 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -18,6 +18,8 @@ #include "powermanager.h" #include "cursor/cursorthememodel.h" +#include "cursor/mouse.h" +#include "cursor/inputdummydevice.h" static QObject *passwordSingleton(QQmlEngine *engine, QJSEngine *scriptEngine) { @@ -67,6 +69,7 @@ Application::Application(int &argc, char **argv) qmlRegisterType(uri, 1, 0, "Language"); qmlRegisterType(uri, 1, 0, "Fonts"); qmlRegisterType(uri, 1, 0, "PowerManager"); + qmlRegisterType(uri, 1, 0, "Mouse"); qmlRegisterSingletonType(uri, 1, 0, "Password", passwordSingleton); qmlRegisterType(); diff --git a/src/cursor/Mouse.cpp b/src/cursor/Mouse.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/cursor/inputdummydevice.cpp b/src/cursor/inputdummydevice.cpp new file mode 100644 index 0000000..3e56c2b --- /dev/null +++ b/src/cursor/inputdummydevice.cpp @@ -0,0 +1,241 @@ +/* + SPDX-FileCopyrightText: 2018 Roman Gilg + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "inputdummydevice.h" +#include "libinputsettings.h" +#include +#include + +#include +#include +#include + +static Atom s_touchpadAtom; + +template +static void XIForallPointerDevices(Display *dpy, const Callback &callback) +{ + int ndevices_return; + XDeviceInfo *info = XListInputDevices(dpy, &ndevices_return); + if (!info) { + return; + } + for (int i = 0; i < ndevices_return; ++i) { + XDeviceInfo *dev = info + i; + if ((dev->use == IsXPointer || dev->use == IsXExtensionPointer) && dev->type != s_touchpadAtom) { + callback(dev); + } + } + XFreeDeviceList(info); +} + +struct ScopedXDeleter { + static inline void cleanup(void *pointer) + { + if (pointer) { + XFree(pointer); + } + } +}; + +namespace +{ +template +void valueWriterPart(T val, Atom valAtom, Display *dpy) +{ + Q_UNUSED(val); + Q_UNUSED(valAtom); + Q_UNUSED(dpy); +} + +template<> +void valueWriterPart(bool val, Atom valAtom, Display *dpy) +{ + XIForallPointerDevices(dpy, [&](XDeviceInfo *info) { + int deviceid = info->id; + Status status; + Atom type_return; + int format_return; + unsigned long num_items_return; + unsigned long bytes_after_return; + + unsigned char *_data = nullptr; + // data returned is an 1 byte boolean + status = XIGetProperty(dpy, deviceid, valAtom, 0, 1, False, XA_INTEGER, &type_return, &format_return, &num_items_return, &bytes_after_return, &_data); + if (status != Success) { + return; + } + + QScopedArrayPointer data(_data); + _data = nullptr; + + if (type_return != XA_INTEGER || !data || format_return != 8) { + return; + } + + unsigned char sendVal[2] = {0}; + if (num_items_return == 1) { + sendVal[0] = val; + } else { + // Special case for acceleration profile. + const Atom accel = XInternAtom(dpy, LIBINPUT_PROP_ACCEL_PROFILE_ENABLED, True); + if (num_items_return != 2 || valAtom != accel) { + return; + } + sendVal[val] = 1; + } + + XIChangeProperty(dpy, deviceid, valAtom, XA_INTEGER, 8, XIPropModeReplace, sendVal, num_items_return); + }); +} + +template<> +void valueWriterPart(qreal val, Atom valAtom, Display *dpy) +{ + XIForallPointerDevices(dpy, [&](XDeviceInfo *info) { + int deviceid = info->id; + Status status; + Atom float_type = XInternAtom(dpy, "FLOAT", False); + Atom type_return; + int format_return; + unsigned long num_items_return; + unsigned long bytes_after_return; + + unsigned char *_data = nullptr; + // data returned is an 1 byte boolean + status = XIGetProperty(dpy, deviceid, valAtom, 0, 1, False, float_type, &type_return, &format_return, &num_items_return, &bytes_after_return, &_data); + if (status != Success) { + return; + } + + QScopedArrayPointer data(_data); + _data = nullptr; + + if (type_return != float_type || !data || format_return != 32 || num_items_return != 1) { + return; + } + + unsigned char buffer[4096]; + float *sendPtr = (float *)buffer; + *sendPtr = val; + + XIChangeProperty(dpy, deviceid, valAtom, float_type, format_return, XIPropModeReplace, buffer, 1); + }); +} +} + +X11LibinputDummyDevice::X11LibinputDummyDevice(QObject *parent, Display *dpy) + : QObject(parent) + , m_settings(new LibinputSettings()) + , m_dpy(dpy) +{ + m_leftHanded.atom = XInternAtom(dpy, LIBINPUT_PROP_LEFT_HANDED, True); + m_middleEmulation.atom = XInternAtom(dpy, LIBINPUT_PROP_MIDDLE_EMULATION_ENABLED, True); + m_naturalScroll.atom = XInternAtom(dpy, LIBINPUT_PROP_NATURAL_SCROLL, True); + m_pointerAcceleration.atom = XInternAtom(dpy, LIBINPUT_PROP_ACCEL, True); + m_pointerAccelerationProfileFlat.atom = XInternAtom(dpy, LIBINPUT_PROP_ACCEL_PROFILE_ENABLED, True); + + m_supportsDisableEvents.val = false; + m_enabled.val = true; + m_supportedButtons.val = Qt::LeftButton | Qt::MiddleButton | Qt::RightButton; + m_supportsLeftHanded.val = true; + m_supportsMiddleEmulation.val = true; + m_middleEmulationEnabledByDefault.val = false; + + m_supportsPointerAcceleration.val = true; + m_defaultPointerAcceleration.val = 0; + + m_supportsPointerAccelerationProfileAdaptive.val = true; + m_supportsPointerAccelerationProfileFlat.val = true; + + m_defaultPointerAccelerationProfileAdaptive.val = true; + m_defaultPointerAccelerationProfileFlat.val = false; + + m_supportsNaturalScroll.val = true; + m_naturalScrollEnabledByDefault.val = false; + + s_touchpadAtom = XInternAtom(m_dpy, XI_TOUCHPAD, True); + + // Init + getConfig(); +} + +X11LibinputDummyDevice::~X11LibinputDummyDevice() +{ + delete m_settings; +} + +bool X11LibinputDummyDevice::getConfig() +{ + auto reset = [this](Prop &prop, bool defVal) { + prop.reset(m_settings->load(prop.cfgName, defVal)); + }; + + reset(m_leftHanded, false); + + reset(m_middleEmulation, false); + reset(m_naturalScroll, false); + reset(m_pointerAccelerationProfileFlat, false); + + m_pointerAccelerationProfileAdaptive.reset(!m_settings->load(m_pointerAccelerationProfileFlat.cfgName, false)); + m_pointerAcceleration.reset(m_settings->load(m_pointerAcceleration.cfgName, 0.)); + + emit leftHandedChanged(); + emit naturalScrollChanged(); + emit pointerAccelerationProfileChanged(); + emit pointerAccelerationChanged(); + + return true; +} + +bool X11LibinputDummyDevice::getDefaultConfig() +{ + m_leftHanded.set(false); + + m_pointerAcceleration.set(m_defaultPointerAcceleration); + m_pointerAccelerationProfileFlat.set(m_defaultPointerAccelerationProfileFlat); + m_pointerAccelerationProfileAdaptive.set(m_defaultPointerAccelerationProfileAdaptive); + + m_middleEmulation.set(m_middleEmulationEnabledByDefault); + m_naturalScroll.set(m_naturalScrollEnabledByDefault); + + return true; +} + +bool X11LibinputDummyDevice::applyConfig() +{ + valueWriter(m_leftHanded); + valueWriter(m_middleEmulation); + valueWriter(m_naturalScroll); + valueWriter(m_pointerAcceleration); + valueWriter(m_pointerAccelerationProfileFlat); + + return true; +} + +template +bool X11LibinputDummyDevice::valueWriter(Prop &prop) +{ + // Check atom availability first. + if (prop.atom == None) { + return false; + } + + if (prop.val != prop.old) { + m_settings->save(prop.cfgName, prop.val); + } + + valueWriterPart(prop.val, prop.atom, m_dpy); + + prop.old = prop.val; + + return true; +} + +bool X11LibinputDummyDevice::isChangedConfig() const +{ + return m_leftHanded.changed() || m_pointerAcceleration.changed() || m_pointerAccelerationProfileFlat.changed() + || m_pointerAccelerationProfileAdaptive.changed() || m_middleEmulation.changed() || m_naturalScroll.changed(); +} diff --git a/src/cursor/inputdummydevice.h b/src/cursor/inputdummydevice.h new file mode 100644 index 0000000..2298626 --- /dev/null +++ b/src/cursor/inputdummydevice.h @@ -0,0 +1,297 @@ +/* + SPDX-FileCopyrightText: 2018 Roman Gilg + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef X11LIBINPUTDUMMYDEVICE_H +#define X11LIBINPUTDUMMYDEVICE_H + +#include +#include +#include + +#include + +struct LibinputSettings; + +class X11LibinputDummyDevice : public QObject +{ + Q_OBJECT + + // + // general + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(bool supportsDisableEvents READ supportsDisableEvents CONSTANT) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) + + // + // advanced + Q_PROPERTY(Qt::MouseButtons supportedButtons READ supportedButtons CONSTANT) + + Q_PROPERTY(bool supportsLeftHanded READ supportsLeftHanded CONSTANT) + Q_PROPERTY(bool leftHandedEnabledByDefault READ leftHandedEnabledByDefault CONSTANT) + Q_PROPERTY(bool leftHanded READ isLeftHanded WRITE setLeftHanded NOTIFY leftHandedChanged) + + Q_PROPERTY(bool supportsMiddleEmulation READ supportsMiddleEmulation CONSTANT) + Q_PROPERTY(bool middleEmulationEnabledByDefault READ middleEmulationEnabledByDefault CONSTANT) + Q_PROPERTY(bool middleEmulation READ isMiddleEmulation WRITE setMiddleEmulation NOTIFY middleEmulationChanged) + + // + // acceleration speed and profile + Q_PROPERTY(bool supportsPointerAcceleration READ supportsPointerAcceleration CONSTANT) + Q_PROPERTY(qreal pointerAcceleration READ pointerAcceleration WRITE setPointerAcceleration NOTIFY pointerAccelerationChanged) + + Q_PROPERTY(bool supportsPointerAccelerationProfileFlat READ supportsPointerAccelerationProfileFlat CONSTANT) + Q_PROPERTY(bool defaultPointerAccelerationProfileFlat READ defaultPointerAccelerationProfileFlat CONSTANT) + Q_PROPERTY(bool pointerAccelerationProfileFlat READ pointerAccelerationProfileFlat WRITE setPointerAccelerationProfileFlat NOTIFY + pointerAccelerationProfileChanged) + + Q_PROPERTY(bool supportsPointerAccelerationProfileAdaptive READ supportsPointerAccelerationProfileAdaptive CONSTANT) + Q_PROPERTY(bool defaultPointerAccelerationProfileAdaptive READ defaultPointerAccelerationProfileAdaptive CONSTANT) + Q_PROPERTY(bool pointerAccelerationProfileAdaptive READ pointerAccelerationProfileAdaptive WRITE setPointerAccelerationProfileAdaptive NOTIFY + pointerAccelerationProfileChanged) + + // + // scrolling + Q_PROPERTY(bool supportsNaturalScroll READ supportsNaturalScroll CONSTANT) + Q_PROPERTY(bool naturalScrollEnabledByDefault READ naturalScrollEnabledByDefault CONSTANT) + Q_PROPERTY(bool naturalScroll READ isNaturalScroll WRITE setNaturalScroll NOTIFY naturalScrollChanged) + +public: + X11LibinputDummyDevice(QObject *parent, Display *dpy); + ~X11LibinputDummyDevice() override; + + bool getConfig(); + bool getDefaultConfig(); + bool applyConfig(); + bool isChangedConfig() const; + + // + // general + QString name() const + { + return m_name.val; + } + QString sysName() const + { + return m_sysName.val; + } + bool supportsDisableEvents() const + { + return m_supportsDisableEvents.val; + } + void setEnabled(bool enabled) + { + m_enabled.set(enabled); + } + bool isEnabled() const + { + return m_enabled.val; + } + Qt::MouseButtons supportedButtons() const + { + return m_supportedButtons.val; + } + + // + // advanced + bool supportsLeftHanded() const + { + return m_supportsLeftHanded.val; + } + bool leftHandedEnabledByDefault() const + { + return m_leftHandedEnabledByDefault.val; + } + bool isLeftHanded() const + { + return m_leftHanded.val; + } + void setLeftHanded(bool set) + { + m_leftHanded.set(set); + } + + bool supportsMiddleEmulation() const + { + return m_supportsMiddleEmulation.val; + } + bool middleEmulationEnabledByDefault() const + { + return m_middleEmulationEnabledByDefault.val; + } + bool isMiddleEmulation() const + { + return m_middleEmulation.val; + } + void setMiddleEmulation(bool set) + { + m_middleEmulation.set(set); + } + + // + // acceleration speed and profile + bool supportsPointerAcceleration() const + { + return m_supportsPointerAcceleration.val; + } + qreal pointerAcceleration() const + { + return m_pointerAcceleration.val; + } + void setPointerAcceleration(qreal acceleration) + { + m_pointerAcceleration.set(acceleration); + } + + bool supportsPointerAccelerationProfileFlat() const + { + return m_supportsPointerAccelerationProfileFlat.val; + } + bool defaultPointerAccelerationProfileFlat() const + { + return m_defaultPointerAccelerationProfileFlat.val; + } + bool pointerAccelerationProfileFlat() const + { + return m_pointerAccelerationProfileFlat.val; + } + void setPointerAccelerationProfileFlat(bool set) + { + m_pointerAccelerationProfileFlat.set(set); + } + + bool supportsPointerAccelerationProfileAdaptive() const + { + return m_supportsPointerAccelerationProfileAdaptive.val; + } + bool defaultPointerAccelerationProfileAdaptive() const + { + return m_defaultPointerAccelerationProfileAdaptive.val; + } + bool pointerAccelerationProfileAdaptive() const + { + return m_pointerAccelerationProfileAdaptive.val; + } + void setPointerAccelerationProfileAdaptive(bool set) + { + m_pointerAccelerationProfileAdaptive.set(set); + } + + // + // scrolling + bool supportsNaturalScroll() const + { + return m_supportsNaturalScroll.val; + } + bool naturalScrollEnabledByDefault() const + { + return m_naturalScrollEnabledByDefault.val; + } + bool isNaturalScroll() const + { + return m_naturalScroll.val; + } + void setNaturalScroll(bool set) + { + m_naturalScroll.set(set); + } + +Q_SIGNALS: + void leftHandedChanged(); + void pointerAccelerationChanged(); + void pointerAccelerationProfileChanged(); + void enabledChanged(); + void middleEmulationChanged(); + void naturalScrollChanged(); + +private: + template + struct Prop { + explicit Prop(const QString &_name, const QString &_cfgName = "") + : name(_name) + , cfgName(_cfgName) + { + } + + void set(T newVal) + { + if (avail && val != newVal) { + val = newVal; + } + } + void set(const Prop &p) + { + if (avail && val != p.val) { + val = p.val; + } + } + bool changed() const + { + return avail && (old != val); + } + + void reset(T newVal) + { + val = newVal; + old = newVal; + } + + QString name; + QString cfgName; + + bool avail = true; + T old; + T val; + + Atom atom; + }; + + template + bool valueWriter(Prop &prop); + + // + // general + Prop m_name = Prop("name"); + Prop m_sysName = Prop("sysName"); + Prop m_supportsDisableEvents = Prop("supportsDisableEvents"); + Prop m_enabled = Prop("enabled"); + + // + // advanced + Prop m_supportedButtons = Prop("supportedButtons"); + + Prop m_supportsLeftHanded = Prop("supportsLeftHanded"); + Prop m_leftHandedEnabledByDefault = Prop("leftHandedEnabledByDefault"); + Prop m_leftHanded = Prop("leftHanded", "XLbInptLeftHanded"); + + Prop m_supportsMiddleEmulation = Prop("supportsMiddleEmulation"); + Prop m_middleEmulationEnabledByDefault = Prop("middleEmulationEnabledByDefault"); + Prop m_middleEmulation = Prop("middleEmulation", "XLbInptMiddleEmulation"); + + // + // acceleration speed and profile + Prop m_supportsPointerAcceleration = Prop("supportsPointerAcceleration"); + Prop m_defaultPointerAcceleration = Prop("defaultPointerAcceleration"); + Prop m_pointerAcceleration = Prop("pointerAcceleration", "XLbInptPointerAcceleration"); + + Prop m_supportsPointerAccelerationProfileFlat = Prop("supportsPointerAccelerationProfileFlat"); + Prop m_defaultPointerAccelerationProfileFlat = Prop("defaultPointerAccelerationProfileFlat"); + Prop m_pointerAccelerationProfileFlat = Prop("pointerAccelerationProfileFlat", "XLbInptAccelProfileFlat"); + + Prop m_supportsPointerAccelerationProfileAdaptive = Prop("supportsPointerAccelerationProfileAdaptive"); + Prop m_defaultPointerAccelerationProfileAdaptive = Prop("defaultPointerAccelerationProfileAdaptive"); + Prop m_pointerAccelerationProfileAdaptive = Prop("pointerAccelerationProfileAdaptive"); + + // + // scrolling + Prop m_supportsNaturalScroll = Prop("supportsNaturalScroll"); + Prop m_naturalScrollEnabledByDefault = Prop("naturalScrollEnabledByDefault"); + Prop m_naturalScroll = Prop("naturalScroll", "XLbInptNaturalScroll"); + + LibinputSettings *m_settings; + Display *m_dpy = nullptr; +}; + +#endif // X11LIBINPUTDUMMYDEVICE_H diff --git a/src/cursor/libinputsettings.cpp b/src/cursor/libinputsettings.cpp new file mode 100644 index 0000000..7bfd61e --- /dev/null +++ b/src/cursor/libinputsettings.cpp @@ -0,0 +1,39 @@ +/* + SPDX-FileCopyrightText: 2018 Roman Gilg + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "libinputsettings.h" + +#include +#include + +template<> +bool LibinputSettings::load(QString key, bool defVal) +{ + QSettings settings("cutefishos", "mouse"); + return settings.value(key, defVal).toBool(); +} + +template<> +qreal LibinputSettings::load(QString key, qreal defVal) +{ + QSettings settings("cutefishos", "mouse"); + return settings.value(key, defVal).toReal(); +} + +template<> +void LibinputSettings::save(QString key, bool val) +{ + QSettings settings("cutefishos", "mouse"); + settings.setValue(key, val); + settings.sync(); +} + +template<> +void LibinputSettings::save(QString key, qreal val) +{ + QSettings settings("cutefishos", "mouse"); + settings.setValue(key, val); + settings.sync(); +} diff --git a/src/cursor/libinputsettings.h b/src/cursor/libinputsettings.h new file mode 100644 index 0000000..5941690 --- /dev/null +++ b/src/cursor/libinputsettings.h @@ -0,0 +1,20 @@ +/* + SPDX-FileCopyrightText: 2018 Roman Gilg + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef LIBINPUTSETTINGS_H +#define LIBINPUTSETTINGS_H + +#include + +struct LibinputSettings { + template + T load(QString key, T defVal); + + template + void save(QString key, T val); +}; + +#endif // LIBINPUTSETTINGS_H diff --git a/src/cursor/mouse.cpp b/src/cursor/mouse.cpp new file mode 100644 index 0000000..0535e78 --- /dev/null +++ b/src/cursor/mouse.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "mouse.h" + +Mouse::Mouse(QObject *parent) + : QObject(parent) + , m_inputDummydevice(new X11LibinputDummyDevice(this, QX11Info::display())) +{ + connect(m_inputDummydevice, &X11LibinputDummyDevice::leftHandedChanged, this, &Mouse::leftHandedChanged); + connect(m_inputDummydevice, &X11LibinputDummyDevice::pointerAccelerationProfileChanged, this, &Mouse::accelerationChanged); + connect(m_inputDummydevice, &X11LibinputDummyDevice::naturalScrollChanged, this, &Mouse::naturalScrollChanged); + connect(m_inputDummydevice, &X11LibinputDummyDevice::pointerAccelerationChanged, this, &Mouse::pointerAccelerationChanged); +} + +Mouse::~Mouse() +{ + delete m_inputDummydevice; +} + +bool Mouse::leftHanded() const +{ + return m_inputDummydevice->isLeftHanded(); +} + +void Mouse::setLeftHanded(bool enabled) +{ + m_inputDummydevice->setLeftHanded(enabled); + m_inputDummydevice->applyConfig(); +} + +bool Mouse::acceleration() const +{ + return m_inputDummydevice->pointerAccelerationProfileFlat(); +} + +void Mouse::setAcceleration(bool enabled) +{ + m_inputDummydevice->setPointerAccelerationProfileFlat(enabled); + m_inputDummydevice->applyConfig(); +} + +bool Mouse::naturalScroll() const +{ + return m_inputDummydevice->isNaturalScroll(); +} + +void Mouse::setNaturalScroll(bool enabled) +{ + m_inputDummydevice->setNaturalScroll(enabled); + m_inputDummydevice->applyConfig(); +} + +qreal Mouse::pointerAcceleration() const +{ + return m_inputDummydevice->pointerAcceleration(); +} + +void Mouse::setPointerAcceleration(qreal value) +{ + m_inputDummydevice->setPointerAcceleration(value); + m_inputDummydevice->applyConfig(); +} diff --git a/src/cursor/mouse.h b/src/cursor/mouse.h new file mode 100644 index 0000000..0bf9c62 --- /dev/null +++ b/src/cursor/mouse.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MOUSE_H +#define MOUSE_H + +#include +#include "inputdummydevice.h" + +class Mouse : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool leftHanded READ leftHanded WRITE setLeftHanded NOTIFY leftHandedChanged) + Q_PROPERTY(bool acceleration READ acceleration WRITE setAcceleration NOTIFY accelerationChanged) + Q_PROPERTY(bool naturalScroll READ naturalScroll WRITE setNaturalScroll NOTIFY naturalScrollChanged) + Q_PROPERTY(qreal pointerAcceleration READ pointerAcceleration WRITE setPointerAcceleration NOTIFY pointerAccelerationChanged) + +public: + explicit Mouse(QObject *parent = nullptr); + ~Mouse(); + + bool leftHanded() const; + void setLeftHanded(bool enabled); + + bool acceleration() const; + void setAcceleration(bool enabled); + + bool naturalScroll() const; + void setNaturalScroll(bool enabled); + + qreal pointerAcceleration() const; + void setPointerAcceleration(qreal value); + +signals: + void leftHandedChanged(); + void accelerationChanged(); + void naturalScrollChanged(); + void pointerAccelerationChanged(); + +private: + X11LibinputDummyDevice *m_inputDummydevice; +}; + +#endif // MOUSE_H diff --git a/src/qml/Appearance/Main.qml b/src/qml/Appearance/Main.qml index b1d9433..90655e4 100644 --- a/src/qml/Appearance/Main.qml +++ b/src/qml/Appearance/Main.qml @@ -41,15 +41,14 @@ ItemPage { id: layout anchors.fill: parent // anchors.bottomMargin: FishUI.Units.largeSpacing - spacing: FishUI.Units.smallSpacing - - Label { - text: qsTr("Theme") - color: FishUI.Theme.disabledTextColor - leftPadding: FishUI.Units.largeSpacing - } + spacing: FishUI.Units.largeSpacing * 2 RoundedItem { + Label { + text: qsTr("Theme") + color: FishUI.Theme.disabledTextColor + } + // Light Mode and Dark Mode RowLayout { spacing: FishUI.Units.largeSpacing * 2 @@ -69,10 +68,6 @@ ItemPage { } } - Item { - height: FishUI.Units.largeSpacing - } - RowLayout { spacing: FishUI.Units.largeSpacing @@ -96,17 +91,12 @@ ItemPage { } } - Item { - height: FishUI.Units.largeSpacing - } - - Label { - text: qsTr("Accent color") - color: FishUI.Theme.disabledTextColor - leftPadding: FishUI.Units.largeSpacing - } - RoundedItem { + Label { + text: qsTr("Accent color") + color: FishUI.Theme.disabledTextColor + } + GridView { id: accentColorView height: itemSize diff --git a/src/qml/Battery/Main.qml b/src/qml/Battery/Main.qml index 0a8dcc9..8e41e1e 100644 --- a/src/qml/Battery/Main.qml +++ b/src/qml/Battery/Main.qml @@ -64,7 +64,7 @@ ItemPage { ColumnLayout { id: layout anchors.fill: parent - spacing: FishUI.Units.smallSpacing + spacing: FishUI.Units.largeSpacing * 2 // Battery Info BatteryItem { @@ -118,20 +118,15 @@ ItemPage { } } - Item { - height: FishUI.Units.largeSpacing - } - - Label { - text: qsTr("History") - color: FishUI.Theme.disabledTextColor - leftPadding: FishUI.Units.largeSpacing - } - RoundedItem { visible: history.count > 2 spacing: 0 + Label { + text: qsTr("History") + color: FishUI.Theme.disabledTextColor + } + HistoryGraph { Layout.fillWidth: true height: 300 @@ -173,19 +168,14 @@ ItemPage { } } - Item { - height: FishUI.Units.largeSpacing - } - - Label { - text: qsTr("Health") - color: FishUI.Theme.disabledTextColor - leftPadding: FishUI.Units.largeSpacing - } - RoundedItem { visible: battery.capacity + Label { + text: qsTr("Health") + color: FishUI.Theme.disabledTextColor + } + RowLayout { spacing: FishUI.Units.largeSpacing * 4 @@ -276,10 +266,6 @@ ItemPage { } } - Item { - height: FishUI.Units.largeSpacing - } - RoundedItem { RowLayout { Label { diff --git a/src/qml/Cursor/Main.qml b/src/qml/Cursor/Main.qml index 32b00b1..8cb1095 100644 --- a/src/qml/Cursor/Main.qml +++ b/src/qml/Cursor/Main.qml @@ -34,6 +34,14 @@ ItemPage { id: cursorModel } + Mouse { + id: mouse + } + + function syncValues() { + accelerationSlider.init() + } + Scrollable { anchors.fill: parent contentHeight: layout.implicitHeight @@ -41,94 +49,179 @@ ItemPage { ColumnLayout { id: layout anchors.fill: parent - spacing: FishUI.Units.smallSpacing + spacing: FishUI.Units.largeSpacing * 2 - Label { - text: qsTr("Theme") - color: FishUI.Theme.disabledTextColor - leftPadding: FishUI.Units.largeSpacing - visible: _view.count > 0 - } + RoundedItem { + GridLayout { + columns: 2 + columnSpacing: FishUI.Units.largeSpacing * 1.5 + rowSpacing: FishUI.Units.largeSpacing * 2 + + Label { + text: qsTr("Left hand") + Layout.fillWidth: true + } - GridView { - id: _view - Layout.fillWidth: true - implicitHeight: Math.ceil(_view.count / rowCount) * cellHeight + FishUI.Units.largeSpacing - model: cursorModel - interactive: false - visible: _view.count > 0 - - cellHeight: itemHeight - cellWidth: calcExtraSpacing(itemWidth, _view.width) + itemWidth - - currentIndex: cursorModel.themeIndex(cursorModel.currentTheme) - - property int rowCount: _view.width / itemWidth - property int itemWidth: 250 - property int itemHeight: 170 - - function calcExtraSpacing(cellSize, containerSize) { - var availableColumns = Math.floor(containerSize / cellSize) - var extraSpacing = 0 - if (availableColumns > 0) { - var allColumnSize = availableColumns * cellSize - var extraSpace = Math.max(containerSize - allColumnSize, 0) - extraSpacing = extraSpace / availableColumns + Switch { + Layout.fillHeight: true + Layout.alignment: Qt.AlignRight + checked: mouse.leftHanded + rightPadding: 0 + onCheckedChanged: mouse.leftHanded = checked } - return Math.floor(extraSpacing) + + Label { + text: qsTr("Natural scrolling") + } + + Switch { + Layout.fillHeight: true + Layout.alignment: Qt.AlignRight + checked: mouse.naturalScroll + onCheckedChanged: mouse.naturalScroll = checked + rightPadding: 0 + } + +// Label { +// text: qsTr("Mouse acceleration") +// } + +// Switch { +// Layout.fillHeight: true +// Layout.alignment: Qt.AlignRight +// checked: mouse.acceleration +// onCheckableChanged: mouse.acceleration = checked +// rightPadding: 0 +// } + +// Label { +// text: qsTr("Disable touchpad when mouse is connected") +// } + +// Switch { +// Layout.fillHeight: true +// Layout.alignment: Qt.AlignRight +// rightPadding: 0 +// } } + } - delegate: Item { - width: GridView.view.cellWidth - height: GridView.view.cellHeight - scale: _mouseArea.pressed ? 0.95 : 1.0 + RoundedItem { + RowLayout { + spacing: FishUI.Units.largeSpacing * 2 - property bool isCurrent: _view.currentIndex === index + Label { + text: qsTr("Speed") + } - Behavior on scale { - NumberAnimation { - duration: 100 + Slider { + id: accelerationSlider + Layout.fillWidth: true + rightPadding: FishUI.Units.largeSpacing + from: 1 + to: 11 + stepSize: 1 + + function init() { + accelerationSlider.value = 6 + mouse.pointerAcceleration / 0.2 } - } - MouseArea { - id: _mouseArea - anchors.fill: parent - anchors.margins: FishUI.Units.largeSpacing - onClicked: { - _view.currentIndex = index - cursorModel.currentTheme = model.id - console.log(model.id) + Component.onCompleted: init() + + onPressedChanged: { + mouse.pointerAcceleration = Math.round(((value - 6) * 0.2) * 10) / 10 } } + } + } - Rectangle { - anchors.fill: parent - anchors.margins: FishUI.Units.largeSpacing - color: FishUI.Theme.secondBackgroundColor - radius: FishUI.Theme.mediumRadius - z: -1 + RoundedItem { + Label { + text: qsTr("Theme") + color: FishUI.Theme.disabledTextColor + visible: _view.count > 0 + } - border.width: isCurrent ? 3 : 0 - border.color: FishUI.Theme.highlightColor + GridView { + id: _view + Layout.fillWidth: true + implicitHeight: Math.ceil(_view.count / rowCount) * cellHeight + FishUI.Units.largeSpacing + model: cursorModel + interactive: false + visible: _view.count > 0 + + cellHeight: itemHeight + cellWidth: calcExtraSpacing(itemWidth, _view.width) + itemWidth + + currentIndex: cursorModel.themeIndex(cursorModel.currentTheme) + + property int rowCount: _view.width / itemWidth + property int itemWidth: 250 + property int itemHeight: 170 + + function calcExtraSpacing(cellSize, containerSize) { + var availableColumns = Math.floor(containerSize / cellSize) + var extraSpacing = 0 + if (availableColumns > 0) { + var allColumnSize = availableColumns * cellSize + var extraSpace = Math.max(containerSize - allColumnSize, 0) + extraSpacing = extraSpace / availableColumns + } + return Math.floor(extraSpacing) } - ColumnLayout { - anchors.fill: parent - anchors.margins: FishUI.Units.largeSpacing + delegate: Item { + width: GridView.view.cellWidth + height: GridView.view.cellHeight + scale: _mouseArea.pressed ? 0.95 : 1.0 + + property bool isCurrent: _view.currentIndex === index + + Behavior on scale { + NumberAnimation { + duration: 100 + } + } + + MouseArea { + id: _mouseArea + anchors.fill: parent + anchors.margins: FishUI.Units.largeSpacing + onClicked: { + _view.currentIndex = index + cursorModel.currentTheme = model.id + console.log(model.id) + } + } + + Rectangle { + anchors.fill: parent + anchors.margins: FishUI.Units.largeSpacing + FishUI.Units.smallSpacing + color: FishUI.Theme.darkMode ? "" : "#FAFAFA" + radius: FishUI.Theme.mediumRadius + z: -1 - FishUI.IconItem { - width: 24 - height: 24 - source: model.image - smooth: true - Layout.alignment: Qt.AlignHCenter + border.width: isCurrent ? 3 : 0 + border.color: FishUI.Theme.highlightColor } - Label { - text: model.name - Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom - bottomPadding: FishUI.Units.largeSpacing + ColumnLayout { + anchors.fill: parent + anchors.margins: FishUI.Units.largeSpacing + + FishUI.IconItem { + width: 24 + height: 24 + source: model.image + smooth: true + Layout.alignment: Qt.AlignHCenter + } + + Label { + text: model.name + Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + bottomPadding: FishUI.Units.largeSpacing + } } } } diff --git a/src/qml/Display/Main.qml b/src/qml/Display/Main.qml index ad9276e..074167c 100644 --- a/src/qml/Display/Main.qml +++ b/src/qml/Display/Main.qml @@ -57,19 +57,18 @@ ItemPage { ColumnLayout { id: layout anchors.fill: parent - spacing: FishUI.Units.smallSpacing - - Label { - text: qsTr("Brightness") - color: FishUI.Theme.disabledTextColor - leftPadding: FishUI.Units.largeSpacing - visible: brightness.enabled - } + spacing: FishUI.Units.largeSpacing * 2 RoundedItem { Layout.fillWidth: true visible: brightness.enabled + Label { + text: qsTr("Brightness") + color: FishUI.Theme.disabledTextColor + visible: brightness.enabled + } + Item { height: FishUI.Units.smallSpacing / 2 } @@ -109,20 +108,15 @@ ItemPage { } } - Item { - height: FishUI.Units.largeSpacing - } - - Label { - text: qsTr("Screen") - color: FishUI.Theme.disabledTextColor - leftPadding: FishUI.Units.largeSpacing - visible: _screenView.count > 0 - } - RoundedItem { visible: _screenView.count > 0 + Label { + text: qsTr("Screen") + color: FishUI.Theme.disabledTextColor + visible: _screenView.count > 0 + } + ListView { id: _screenView Layout.fillWidth: true @@ -280,17 +274,12 @@ ItemPage { } } - Item { - height: FishUI.Units.largeSpacing - } - - Label { - text: qsTr("Scale") - color: FishUI.Theme.disabledTextColor - leftPadding: FishUI.Units.largeSpacing - } - RoundedItem { + Label { + text: qsTr("Scale") + color: FishUI.Theme.disabledTextColor + } + TabBar { id: dockSizeTabbar Layout.fillWidth: true diff --git a/src/qml/Dock/Main.qml b/src/qml/Dock/Main.qml index 57d9973..3b0af3d 100644 --- a/src/qml/Dock/Main.qml +++ b/src/qml/Dock/Main.qml @@ -38,15 +38,14 @@ ItemPage { ColumnLayout { id: layout anchors.fill: parent - spacing: FishUI.Units.smallSpacing - - Label { - text: qsTr("Position on screen") - color: FishUI.Theme.disabledTextColor - leftPadding: FishUI.Units.largeSpacing - } + spacing: FishUI.Units.largeSpacing * 2 RoundedItem { + Label { + text: qsTr("Position on screen") + color: FishUI.Theme.disabledTextColor + } + // Dock RowLayout { spacing: FishUI.Units.largeSpacing * 2 @@ -74,18 +73,13 @@ ItemPage { } } - Item { - height: FishUI.Units.largeSpacing - } - - Label { - text: qsTr("Size") - color: FishUI.Theme.disabledTextColor - leftPadding: FishUI.Units.largeSpacing - } - // Dock Size RoundedItem { + Label { + text: qsTr("Size") + color: FishUI.Theme.disabledTextColor + } + TabBar { id: dockSizeTabbar Layout.fillWidth: true @@ -145,18 +139,13 @@ ItemPage { } } - Item { - height: FishUI.Units.largeSpacing - } - - Label { - text: qsTr("Display mode") - color: FishUI.Theme.disabledTextColor - leftPadding: FishUI.Units.largeSpacing - } - // Visibility RoundedItem { + Label { + text: qsTr("Display mode") + color: FishUI.Theme.disabledTextColor + } + TabBar { Layout.fillWidth: true currentIndex: appearance.dockVisibility diff --git a/src/qml/Fonts/Main.qml b/src/qml/Fonts/Main.qml index 81a6f97..38d5691 100644 --- a/src/qml/Fonts/Main.qml +++ b/src/qml/Fonts/Main.qml @@ -86,7 +86,7 @@ ItemPage { columns: 2 columnSpacing: FishUI.Units.largeSpacing * 1.5 - rowSpacing: FishUI.Units.largeSpacing + rowSpacing: FishUI.Units.largeSpacing * 2 Label { text: qsTr("General Font") diff --git a/src/qml/Power/Main.qml b/src/qml/Power/Main.qml index 069c98a..7b711bb 100644 --- a/src/qml/Power/Main.qml +++ b/src/qml/Power/Main.qml @@ -41,15 +41,14 @@ ItemPage { ColumnLayout { id: layout anchors.fill: parent - spacing: FishUI.Units.smallSpacing - - Label { - text: qsTr("Mode") - color: FishUI.Theme.disabledTextColor - leftPadding: FishUI.Units.largeSpacing - } + spacing: FishUI.Units.largeSpacing * 2 RoundedItem { + Label { + text: qsTr("Mode") + color: FishUI.Theme.disabledTextColor + } + RowLayout { spacing: FishUI.Units.largeSpacing * 2 diff --git a/src/qml/RoundedItem.qml b/src/qml/RoundedItem.qml index 7d08df6..5c17bf5 100644 --- a/src/qml/RoundedItem.qml +++ b/src/qml/RoundedItem.qml @@ -26,6 +26,7 @@ Rectangle { default property alias content : _mainLayout.data property alias spacing: _mainLayout.spacing + property alias layout: _mainLayout color: FishUI.Theme.secondBackgroundColor radius: FishUI.Theme.mediumRadius @@ -41,5 +42,6 @@ Rectangle { anchors.rightMargin: FishUI.Units.largeSpacing anchors.topMargin: FishUI.Units.largeSpacing anchors.bottomMargin: FishUI.Units.largeSpacing + spacing: FishUI.Units.largeSpacing } } diff --git a/src/qml/SideBar.qml b/src/qml/SideBar.qml index c73d106..4ded3e5 100644 --- a/src/qml/SideBar.qml +++ b/src/qml/SideBar.qml @@ -101,15 +101,6 @@ Item { category: qsTr("Display and appearance") } - ListElement { - title: qsTr("Mouse") - name: "cursor" - page: "qrc:/qml/Cursor/Main.qml" - iconSource: "cursor.svg" - iconColor: "#0D9BF1" - category: qsTr("Display and appearance") - } - ListElement { title: qsTr("Fonts") name: "fonts" @@ -146,6 +137,15 @@ Item { category: qsTr("System") } + ListElement { + title: qsTr("Mouse") + name: "cursor" + page: "qrc:/qml/Cursor/Main.qml" + iconSource: "cursor.svg" + iconColor: "#0D9BF1" + category: qsTr("System") + } + // ListElement { // title: qsTr("Application") // name: "application"