350 lines
13 KiB
C++
350 lines
13 KiB
C++
/*
|
|
* SPDX-FileCopyrightText: 2016-2016 CSSlayer <wengxt@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
*
|
|
*/
|
|
#ifndef _FCITX_UI_CLASSIC_CLASSICUI_H_
|
|
#define _FCITX_UI_CLASSIC_CLASSICUI_H_
|
|
|
|
#include <cstddef>
|
|
#include <filesystem>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include "fcitx-config/configuration.h"
|
|
#include "fcitx-config/iniparser.h"
|
|
#include "fcitx-config/option.h"
|
|
#include "fcitx-config/rawconfig.h"
|
|
#include "fcitx-utils/color.h"
|
|
#include "fcitx-utils/eventloopinterface.h"
|
|
#include "fcitx-utils/handlertable.h"
|
|
#include "fcitx-utils/i18n.h"
|
|
#include "fcitx-utils/stringutils.h"
|
|
#include "fcitx/addoninstance.h"
|
|
#include "fcitx/addonmanager.h"
|
|
#include "fcitx/event.h"
|
|
#include "fcitx/instance.h"
|
|
#include "fcitx/userinterface.h"
|
|
#include "blockstore.h"
|
|
#include "classicui_public.h"
|
|
#include "config.h"
|
|
#include "plasmathemewatchdog.h"
|
|
#include "portalsettingmonitor.h"
|
|
#include "theme.h"
|
|
#ifdef ENABLE_X11
|
|
#include "xcb_public.h"
|
|
#endif
|
|
#ifdef WAYLAND_FOUND
|
|
#include "wayland_public.h"
|
|
#endif
|
|
#ifdef ENABLE_DBUS
|
|
#endif
|
|
|
|
namespace fcitx::m3ui {
|
|
|
|
inline constexpr std::string_view PlasmaThemeName = "plasma";
|
|
|
|
class UIInterface {
|
|
public:
|
|
UIInterface(std::string name) : name_(std::move(name)) {}
|
|
|
|
const std::string &name() const { return name_; }
|
|
|
|
virtual ~UIInterface() {}
|
|
virtual void update(UserInterfaceComponent component,
|
|
InputContext *inputContext) = 0;
|
|
virtual void updateCursor(InputContext * /*unused*/) {}
|
|
virtual void updateCurrentInputMethod(InputContext * /*unused*/) {}
|
|
virtual void suspend() = 0;
|
|
virtual void resume() {}
|
|
virtual void setEnableTray(bool) = 0;
|
|
|
|
private:
|
|
const std::string name_;
|
|
};
|
|
|
|
struct ThemeNameConstrain {
|
|
using Type = std::string;
|
|
|
|
bool check(const std::string &value) const {
|
|
const auto path = std::filesystem::path(value);
|
|
return !value.empty() && path.is_relative() &&
|
|
!path.has_parent_path() && path.filename() == path &&
|
|
path.filename() != "." && path.filename() != "..";
|
|
}
|
|
|
|
void dumpDescription(RawConfig & /*unused*/) const {}
|
|
};
|
|
|
|
struct ThemeAnnotation : public EnumAnnotation {
|
|
void setThemes(std::vector<std::pair<std::string, std::string>> themes,
|
|
bool plasmaTheme) {
|
|
themes_ = std::move(themes);
|
|
plasmaTheme_ = plasmaTheme;
|
|
}
|
|
void dumpDescription(RawConfig &config) const {
|
|
EnumAnnotation::dumpDescription(config);
|
|
config.setValueByPath("LaunchSubConfig", "True");
|
|
for (size_t i = 0; i < themes_.size(); i++) {
|
|
config.setValueByPath("Enum/" + std::to_string(i),
|
|
themes_[i].first);
|
|
config.setValueByPath("EnumI18n/" + std::to_string(i),
|
|
themes_[i].second);
|
|
if (themes_[i].first != PlasmaThemeName &&
|
|
!Theme::isSystemThemeName(themes_[i].first)) {
|
|
config.setValueByPath(
|
|
"SubConfigPath/" + std::to_string(i),
|
|
stringutils::concat("fcitx://config/addon/m3ui/theme/",
|
|
themes_[i].first));
|
|
} else {
|
|
config.setValueByPath("SubConfigPath/" + std::to_string(i), "");
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::vector<std::pair<std::string, std::string>> themes_;
|
|
bool plasmaTheme_ = false;
|
|
};
|
|
|
|
struct MenuFontAnnotation : private FontAnnotation, private ToolTipAnnotation {
|
|
MenuFontAnnotation()
|
|
: ToolTipAnnotation(
|
|
_("This is only effective when the tray icon is xembed.")) {}
|
|
|
|
bool skipDescription() { return false; }
|
|
bool skipSave() { return false; }
|
|
void dumpDescription(RawConfig &config) {
|
|
FontAnnotation::dumpDescription(config);
|
|
ToolTipAnnotation::dumpDescription(config);
|
|
}
|
|
};
|
|
|
|
FCITX_CONFIGURATION(
|
|
ClassicUIConfig,
|
|
Option<bool> verticalCandidateList{this, "Vertical Candidate List",
|
|
_("Vertical Candidate List"), false};
|
|
Option<bool> useWheelForPaging{
|
|
this, "WheelForPaging", _("Use mouse wheel to go to prev or next page"),
|
|
true};
|
|
|
|
OptionWithAnnotation<std::string, FontAnnotation> font{
|
|
this, "Font", _("Font"), "Sans 10"};
|
|
OptionWithAnnotation<std::string, MenuFontAnnotation> menuFont{
|
|
this, "MenuFont", _("Menu Font"), "Sans 10"};
|
|
OptionWithAnnotation<std::string, FontAnnotation> trayFont{
|
|
this, "TrayFont", _("Tray Font"), "Sans Bold 10"};
|
|
Option<Color> trayBorderColor{this, "TrayOutlineColor",
|
|
_("Tray Label Outline Color"),
|
|
Color("#000000ff")};
|
|
Option<Color> trayTextColor{this, "TrayTextColor",
|
|
_("Tray Label Text Color"), Color("#ffffffff")};
|
|
Option<bool> preferTextIcon{this, "PreferTextIcon", _("Prefer Text Icon"),
|
|
false};
|
|
OptionWithAnnotation<bool, ToolTipAnnotation> showLayoutNameInIcon{
|
|
this,
|
|
"ShowLayoutNameInIcon",
|
|
_("Show Layout Name In Icon"),
|
|
true,
|
|
{},
|
|
{},
|
|
{_("Show layout name in icon if there is more than one active layout. "
|
|
"If prefer text icon is set to true, this option will be "
|
|
"ignored.")}};
|
|
OptionWithAnnotation<bool, ToolTipAnnotation>
|
|
useInputMethodLanguageToDisplayText{
|
|
this,
|
|
"UseInputMethodLanguageToDisplayText",
|
|
_("Use input method language to display text"),
|
|
true,
|
|
{},
|
|
{},
|
|
{_("For example, display character with Chinese variant when using "
|
|
"Pinyin and Japanese variant when using Anthy. The font "
|
|
"configuration needs to support this to use this feature.")}};
|
|
Option<std::string, ThemeNameConstrain, DefaultMarshaller<std::string>,
|
|
ThemeAnnotation>
|
|
theme{this, "Theme", _("Theme"), "default"};
|
|
Option<std::string, ThemeNameConstrain, DefaultMarshaller<std::string>,
|
|
ThemeAnnotation>
|
|
themeDark{this, "DarkTheme", _("Dark Theme"), "default-dark"};
|
|
Option<bool> useDarkTheme{this, "UseDarkTheme",
|
|
_("Follow system light/dark color scheme"),
|
|
false};
|
|
Option<bool> useAccentColor{
|
|
this, "UseAccentColor",
|
|
_("Follow system accent color if it is supported by theme and desktop"),
|
|
true};
|
|
OptionWithAnnotation<bool, ToolTipAnnotation> perScreenDPI{
|
|
this,
|
|
"PerScreenDPI",
|
|
_("Use Per Screen DPI on X11"),
|
|
false,
|
|
{},
|
|
{},
|
|
{_("This option will be always disabled on XWayland.")}};
|
|
Option<int, IntConstrain, DefaultMarshaller<int>, ToolTipAnnotation>
|
|
forceWaylandDPI{
|
|
this,
|
|
"ForceWaylandDPI",
|
|
_("Force font DPI on Wayland"),
|
|
0,
|
|
IntConstrain(0),
|
|
{},
|
|
{_("Normally Wayland uses 96 as font DPI in combination with the "
|
|
"screen scale factor. This option allows you to override the "
|
|
"font DPI. If the value is 0, it means this option is "
|
|
"disabled.")}};
|
|
|
|
Option<bool> useMonet{
|
|
this, "UseMonet",
|
|
_("Take the colours from the desktop: its accent colour and its light "
|
|
"or dark setting"),
|
|
true};
|
|
Option<bool> animations{this, "Animations", _("Enable animations"), true};
|
|
Option<bool> bounce{this, "Bounce",
|
|
_("Bouncy overshoot (off: critically damped)"), true};
|
|
Option<int, IntConstrain> spatialStiffness{
|
|
this, "SpatialStiffness",
|
|
_("Spring stiffness for movement, higher is faster"), 2000,
|
|
IntConstrain(50, 20000)};
|
|
Option<int, IntConstrain> spatialDamping{
|
|
this, "SpatialDamping",
|
|
_("Spring damping ratio for movement in percent, lower bounces more"),
|
|
62, IntConstrain(20, 100)};
|
|
Option<int, IntConstrain> effectsStiffness{
|
|
this, "EffectsStiffness",
|
|
_("Spring stiffness for fading, higher is faster"), 3800,
|
|
IntConstrain(50, 20000)};
|
|
Option<int, IntConstrain> slideDistance{
|
|
this, "SlideDistance", _("Slide-in distance (px)"), 22,
|
|
IntConstrain(0, 200)};
|
|
Option<int, IntConstrain> slideDirection{
|
|
this, "SlideDirection",
|
|
_("Slide-in direction (0: from left, 1: from right, 2: from below)"),
|
|
0, IntConstrain(0, 2)};
|
|
|
|
OptionWithAnnotation<bool, ToolTipAnnotation> fractionalScale{
|
|
this,
|
|
"EnableFractionalScale",
|
|
_("Enable fractional scale under Wayland"),
|
|
true,
|
|
{},
|
|
{},
|
|
{_("This option require support from wayland compositor.")}};);
|
|
|
|
class M3UI final : public UserInterface {
|
|
public:
|
|
M3UI(Instance *instance);
|
|
~M3UI();
|
|
|
|
FCITX_ADDON_DEPENDENCY_LOADER(xcb, instance_->addonManager());
|
|
FCITX_ADDON_DEPENDENCY_LOADER(wayland, instance_->addonManager());
|
|
FCITX_ADDON_DEPENDENCY_LOADER(waylandim, instance_->addonManager());
|
|
FCITX_ADDON_DEPENDENCY_LOADER(dbus, instance_->addonManager());
|
|
Instance *instance() const { return instance_; }
|
|
const Configuration *getConfig() const override;
|
|
// The scheme for the current desktop colours, see classicui.cpp.
|
|
monet::Scheme monetScheme() const;
|
|
void setConfig(const RawConfig &config) override {
|
|
config_.load(config, true);
|
|
safeSaveAsIni(config_, "conf/m3ui.conf");
|
|
reloadTheme();
|
|
}
|
|
const Configuration *getSubConfig(const std::string &path) const override;
|
|
void setSubConfig(const std::string &path,
|
|
const RawConfig &config) override;
|
|
auto &config() const { return config_; }
|
|
Theme &theme() { return theme_; }
|
|
void suspend() override;
|
|
void resume() override;
|
|
bool suspended() const { return suspended_; }
|
|
bool available() override { return true; }
|
|
void update(UserInterfaceComponent component,
|
|
InputContext *inputContext) override;
|
|
void reloadConfig() override;
|
|
|
|
std::vector<unsigned char> labelIcon(const std::string &label,
|
|
unsigned int size);
|
|
bool preferTextIcon() const;
|
|
bool showLayoutNameInIcon() const;
|
|
|
|
// Words the user chose to forget. Forgetting in the Pinyin engine only
|
|
// removes learned words, anything from a read only dictionary would come
|
|
// back, so the window also hides these. The list lives in a file, see
|
|
// BlockStore for how it is kept safe.
|
|
bool hasBlockedWords() const { return !blockStore_.empty(); }
|
|
bool isBlocked(const std::string &word) const {
|
|
return blockStore_.contains(word);
|
|
}
|
|
// Picks up changes made to the file behind our back and retries a save that
|
|
// failed earlier. One stat call.
|
|
void reloadBlockedWords() { blockStore_.reloadIfChanged(); }
|
|
// Returns false if the word could not be saved, it is hidden for this
|
|
// session anyway.
|
|
bool blockWord(const std::string &word);
|
|
|
|
private:
|
|
static std::filesystem::path blockedWordsPath();
|
|
BlockStore blockStore_{blockedWordsPath()};
|
|
|
|
FCITX_ADDON_DEPENDENCY_LOADER(notificationitem, instance_->addonManager());
|
|
FCITX_ADDON_EXPORT_FUNCTION(M3UI, labelIcon);
|
|
FCITX_ADDON_EXPORT_FUNCTION(M3UI, preferTextIcon);
|
|
FCITX_ADDON_EXPORT_FUNCTION(M3UI, showLayoutNameInIcon);
|
|
|
|
UIInterface *uiForEvent(Event &event);
|
|
UIInterface *uiForInputContext(InputContext *inputContext);
|
|
UIInterface *uiForDisplay(const std::string &display);
|
|
void reloadTheme();
|
|
|
|
#ifdef ENABLE_X11
|
|
std::unique_ptr<HandlerTableEntry<XCBConnectionCreated>>
|
|
xcbCreatedCallback_;
|
|
std::unique_ptr<HandlerTableEntry<XCBConnectionClosed>> xcbClosedCallback_;
|
|
#endif
|
|
|
|
#ifdef WAYLAND_FOUND
|
|
std::unique_ptr<HandlerTableEntry<WaylandConnectionCreated>>
|
|
waylandCreatedCallback_;
|
|
std::unique_ptr<HandlerTableEntry<WaylandConnectionClosed>>
|
|
waylandClosedCallback_;
|
|
#endif
|
|
|
|
std::unique_ptr<EventSource> deferedReloadTheme_;
|
|
#ifdef ENABLE_DBUS
|
|
std::unique_ptr<PortalSettingMonitor> settingMonitor_;
|
|
std::unique_ptr<PortalSettingEntry> darkModeEntry_;
|
|
std::unique_ptr<PortalSettingEntry> accentColorEntry_;
|
|
#endif
|
|
|
|
std::vector<std::unique_ptr<HandlerTableEntry<EventHandler>>>
|
|
eventHandlers_;
|
|
std::vector<std::unique_ptr<HandlerTableEntry<EventHandler>>>
|
|
persistentEventHandlers_;
|
|
std::unique_ptr<HandlerTableEntryBase> sniHandler_;
|
|
|
|
std::unordered_map<std::string, std::unique_ptr<UIInterface>> uis_;
|
|
|
|
Instance *instance_;
|
|
ClassicUIConfig config_;
|
|
Theme theme_;
|
|
mutable Theme subconfigTheme_;
|
|
bool suspended_ = true;
|
|
bool isDark_ = false;
|
|
// The portal has answered, so isDark_ is a value and not just the default.
|
|
bool portalSchemeKnown_ = false;
|
|
std::optional<Color> accentColor_ = std::nullopt;
|
|
|
|
std::unique_ptr<EventSource> deferedEnableTray_;
|
|
std::unique_ptr<PlasmaThemeWatchdog> plasmaThemeWatchdog_;
|
|
};
|
|
} // namespace fcitx::m3ui
|
|
|
|
#endif // _FCITX_UI_CLASSIC_CLASSICUI_H_
|