/* * SPDX-FileCopyrightText: 2016-2016 CSSlayer * * SPDX-License-Identifier: LGPL-2.1-or-later * */ #ifndef _FCITX_UI_CLASSIC_CLASSICUI_H_ #define _FCITX_UI_CLASSIC_CLASSICUI_H_ #include #include #include #include #include #include #include #include #include #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> 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> 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 verticalCandidateList{this, "Vertical Candidate List", _("Vertical Candidate List"), false}; Option useWheelForPaging{ this, "WheelForPaging", _("Use mouse wheel to go to prev or next page"), true}; OptionWithAnnotation font{ this, "Font", _("Font"), "Sans 10"}; OptionWithAnnotation menuFont{ this, "MenuFont", _("Menu Font"), "Sans 10"}; OptionWithAnnotation trayFont{ this, "TrayFont", _("Tray Font"), "Sans Bold 10"}; Option trayBorderColor{this, "TrayOutlineColor", _("Tray Label Outline Color"), Color("#000000ff")}; Option trayTextColor{this, "TrayTextColor", _("Tray Label Text Color"), Color("#ffffffff")}; Option preferTextIcon{this, "PreferTextIcon", _("Prefer Text Icon"), false}; OptionWithAnnotation 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 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, ThemeAnnotation> theme{this, "Theme", _("Theme"), "default"}; Option, ThemeAnnotation> themeDark{this, "DarkTheme", _("Dark Theme"), "default-dark"}; Option useDarkTheme{this, "UseDarkTheme", _("Follow system light/dark color scheme"), false}; Option useAccentColor{ this, "UseAccentColor", _("Follow system accent color if it is supported by theme and desktop"), true}; OptionWithAnnotation perScreenDPI{ this, "PerScreenDPI", _("Use Per Screen DPI on X11"), false, {}, {}, {_("This option will be always disabled on XWayland.")}}; Option, 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 useMonet{ this, "UseMonet", _("Take the colours from the desktop: its accent colour and its light " "or dark setting"), true}; Option animations{this, "Animations", _("Enable animations"), true}; Option bounce{this, "Bounce", _("Bouncy overshoot (off: critically damped)"), true}; Option spatialStiffness{ this, "SpatialStiffness", _("Spring stiffness for movement, higher is faster"), 2000, IntConstrain(50, 20000)}; Option spatialDamping{ this, "SpatialDamping", _("Spring damping ratio for movement in percent, lower bounces more"), 62, IntConstrain(20, 100)}; Option effectsStiffness{ this, "EffectsStiffness", _("Spring stiffness for fading, higher is faster"), 3800, IntConstrain(50, 20000)}; Option slideDistance{ this, "SlideDistance", _("Slide-in distance (px)"), 22, IntConstrain(0, 200)}; Option slideDirection{ this, "SlideDirection", _("Slide-in direction (0: from left, 1: from right, 2: from below)"), 0, IntConstrain(0, 2)}; OptionWithAnnotation 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 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> xcbCreatedCallback_; std::unique_ptr> xcbClosedCallback_; #endif #ifdef WAYLAND_FOUND std::unique_ptr> waylandCreatedCallback_; std::unique_ptr> waylandClosedCallback_; #endif std::unique_ptr deferedReloadTheme_; #ifdef ENABLE_DBUS std::unique_ptr settingMonitor_; std::unique_ptr darkModeEntry_; std::unique_ptr accentColorEntry_; #endif std::vector>> eventHandlers_; std::vector>> persistentEventHandlers_; std::unique_ptr sniHandler_; std::unordered_map> 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 accentColor_ = std::nullopt; std::unique_ptr deferedEnableTray_; std::unique_ptr plasmaThemeWatchdog_; }; } // namespace fcitx::m3ui #endif // _FCITX_UI_CLASSIC_CLASSICUI_H_