diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..669b23c --- /dev/null +++ b/README.en.md @@ -0,0 +1,131 @@ +# fcitx5-m3ui + +A Material 3 styled candidate window for [Fcitx5](https://github.com/fcitx/fcitx5), +built as a user interface addon (`m3ui`) that replaces `classicui`. + +[简体中文](README.md) + +## Features + +- Rounded Material 3 panel with light and dark themes that follow the system. +- Spring based animations: the panel slides in with a small bounce and fades in, + the highlight pill glides between candidates. Stiffness and damping are + configurable. +- Right click a candidate to open an action menu. It shows whatever actions the + input method offers, for Pinyin that is *Forget candidate*, *Pin to top as + custom phrase* and *Delete from custom phrase*. +- A forgotten candidate stays gone, also words from read only dictionaries + (see *Block list* below). +- Works on Wayland and X11 (Xwayland) input contexts, positioning and pointer + handling are inherited from `classicui`. +- Optional KWin helper that shortens KWin's own fade out for the candidate + window only. + +## Status + +Personal project, only tried on Fedora 44, KDE Plasma 6.7 on Wayland, NVIDIA +GPU, Fcitx5 5.1.23 with the Pinyin engine. Animations, the right click menu, +forgetting candidates and starting at login have been used on that setup. + +**`scripts/build.sh` has not been run from scratch in a clean environment**, and +no other distribution or desktop has been tried. Please open an issue if +something breaks. + +The addon is a fork of `classicui` and is built inside the Fcitx5 source tree, +because Fcitx5's Wayland protocol wrappers are not installed by `fcitx5-devel`. +The Fcitx5 source version used for the build must match the installed Fcitx5. + +There is no fade out animation of its own: on Wayland the window is destroyed as +soon as the panel has nothing to show. + +## Build + +Needs the Fcitx5 development files and the dependencies of `classicui` +(cairo, pango, librsvg, gdk-pixbuf, wayland-protocols, xcb libraries). On Fedora: + +``` +sudo dnf install fcitx5-devel extra-cmake-modules cairo-devel pango-devel \ + gdk-pixbuf2-devel librsvg2-devel wayland-protocols-devel \ + plasma-wayland-protocols-devel systemd-devel libuuid-devel fmt-devel \ + expat-devel iso-codes-devel json-devel xkeyboard-config-devel \ + xcb-util-devel xcb-util-keysyms-devel xcb-util-wm-devel \ + xcb-util-image-devel xcb-imdkit-devel libxkbfile-devel libxkbcommon-x11-devel +``` + +then + +``` +scripts/build.sh # downloads Fcitx5 + yoga, overlays src/, builds m3ui +scripts/install.sh # installs into ~/.local, nothing outside $HOME +``` + +`build.sh` picks the Fcitx5 version from `pkg-config`, override it with +`FCITX_VERSION`. + +## Use + +``` +FCITX_ADDON_DIRS=$HOME/.local/m3ui/lib64/fcitx5:/usr/lib64/fcitx5 fcitx5 -r -u m3ui +``` + +`FCITX_ADDON_DIRS` is how Fcitx5 finds addons outside the system directory. +`fcitx5 -r` (no arguments) brings the stock interface back. + +**Start with m3ui at every login:** + +``` +scripts/enable-autostart.sh +systemctl --user daemon-reload && systemctl --user restart app-org.fcitx.Fcitx5@autostart.service +``` + +It writes two user level overrides (the XDG autostart entry and D-Bus +activation) and changes only the `Exec` line. If the addon fails to load +Fcitx5 falls back to classicui. To undo, delete the two files the script +prints. + +Settings live in `~/.config/fcitx5/conf/m3ui.conf`, see +[config/m3ui.conf.example](config/m3ui.conf.example). + +| Key | Default | Meaning | +|---|---|---| +| `Animations` | True | Master switch for animations | +| `Bounce` | True | Off makes movement critically damped (no overshoot) | +| `SpatialStiffness` | 2000 | Spring stiffness for movement, higher is faster | +| `SpatialDamping` | 62 | Damping ratio in percent, lower bounces more | +| `EffectsStiffness` | 3800 | Spring stiffness for fading | +| `SlideDistance` | 22 | Slide in distance in px | +| `SlideDirection` | 0 | 0 from left, 1 from right, 2 from below | +| `Theme` / `DarkTheme` | `m3ui-light` / `m3ui-dark` | Themes in `themes/` | + +Themes are classicui themes with extra keys in `[InputPanel]`: `SurfaceColor`, +`OutlineColor`, `HighlightFillColor`, `PanelRadius`, `ItemRadius`. A transparent +`SurfaceColor` falls back to the classic image based drawing, so classic themes +still work (without animation). + +## Block list + +The Pinyin engine's *Forget candidate* only removes words you have learned +while typing. Words from the read only system dictionary (`sc.dict`) or from +extra dictionaries come back when you type the same input again, also in the +current composition. + +When you pick *Forget candidate*, m3ui therefore also records the word in +`~/.local/share/fcitx5/m3ui/blocked-words.txt` (one word per line) and removes +those words from the candidate list before every refresh. + +- Matching is exact on the full text of the candidate. +- To bring a word back, delete its line and restart Fcitx5. +- This is a layer on top of the engine. With the stock classicui the words + show up again. + +## KWin fade out (optional, KDE Plasma) + +KWin's *Fading Popups* effect fades popups out over 600 ms and the candidate +window counts as a popup. `kwin/install-fadingpopups-override.sh` copies the +effect from your system into `~/.local/share/kwin-wayland/effects/` and patches +the copy so only the window class `fcitx fcitx` is affected. The script fails +if the upstream effect source no longer matches what it patches. + +## License + +LGPL-2.1-or-later, see [NOTICE.md](NOTICE.md). diff --git a/README.md b/README.md index 48b380a..aa9c85f 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,41 @@ # fcitx5-m3ui -A Material 3 styled candidate window for [Fcitx5](https://github.com/fcitx/fcitx5), -built as a user interface addon (`m3ui`) that replaces `classicui`. +给 [Fcitx5](https://github.com/fcitx/fcitx5) 用的 Material 3 风格候选框, +以界面插件(`m3ui`)的形式替换 `classicui`。 -[简体中文](README.zh-CN.md) +[English](README.en.md) -## Features +## 特性 -- Rounded Material 3 panel with light and dark themes that follow the system. -- Spring based animations: the panel slides in with a small bounce and fades in, - the highlight pill glides between candidates. Stiffness and damping are - configurable. -- Right click a candidate to open an action menu. It shows whatever actions the - input method offers, for Pinyin that is *Forget candidate*, *Pin to top as - custom phrase* and *Delete from custom phrase*. -- Works on Wayland and X11 (Xwayland) input contexts, positioning and pointer - handling are inherited from `classicui`. -- Optional KWin helper that shortens KWin's own fade out for the candidate - window only. +- Material 3 圆角面板,深色 / 浅色主题跟随系统。 +- 弹簧动画:候选框带轻微回弹从侧边滑入并淡入,高亮块在候选词之间滑动。 + 刚度、阻尼、方向、距离都可以调。 +- 在候选词上**右键**打开动作菜单,内容取决于输入法提供的动作。拼音有 + "忘记候选词 / 置顶为自定义短语 / 从自定义短语中删除"。 +- **忘记候选词后不会再出现**,包括系统词库和扩展词库里的词(见下文"屏蔽列表")。 +- Wayland 和 X11(Xwayland)输入上下文都支持,定位和鼠标处理沿用 `classicui`。 +- 可选的 KWin 辅助脚本:只缩短候选框自己的 KWin 淡出时间。 -## Status +## 状态 -Personal project, only tried on Fedora 44, KDE Plasma 6.7 on Wayland, NVIDIA -GPU, Fcitx5 5.1.23 with the Pinyin engine. The build scripts have not been run -on any other setup. +个人项目,只在 Fedora 44、KDE Plasma 6.7(Wayland)、NVIDIA 显卡、 +Fcitx5 5.1.23、拼音引擎上用过。动画、右键菜单、忘记候选词、开机自动启动 +在这套环境里都实际用过。 -The addon is a fork of `classicui` and is built inside the Fcitx5 source tree, -because Fcitx5's Wayland protocol wrappers are not installed by `fcitx5-devel`. -The Fcitx5 source version used for the build must match the installed Fcitx5. +**`scripts/build.sh` 没有在干净环境里从零完整跑过**,其他发行版和桌面也没有试过, +遇到问题请开 issue。 -There is no fade out animation of its own: on Wayland the window is destroyed as -soon as the panel has nothing to show. +本插件是 `classicui` 的分支,需要在 Fcitx5 源码树内构建:Fcitx5 的 Wayland +协议封装不包含在 `fcitx5-devel` 里。构建用的源码版本必须和已安装的 Fcitx5 一致, +Fcitx5 升级后需要重新构建;如果插件加载失败,Fcitx5 会自动退回 `classicui`, +不会没有输入法。 -## Build +没有自己的淡出动画:Wayland 下面板没有内容时窗口会被立刻销毁。 -Needs the Fcitx5 development files and the dependencies of `classicui` -(cairo, pango, librsvg, gdk-pixbuf, wayland-protocols, xcb libraries). On Fedora: +## 构建 + +需要 Fcitx5 开发文件和 `classicui` 的依赖(cairo、pango、librsvg、gdk-pixbuf、 +wayland-protocols、xcb 相关库)。Fedora 上: ``` sudo dnf install fcitx5-devel extra-cmake-modules cairo-devel pango-devel \ @@ -46,53 +46,79 @@ sudo dnf install fcitx5-devel extra-cmake-modules cairo-devel pango-devel \ xcb-util-image-devel xcb-imdkit-devel libxkbfile-devel libxkbcommon-x11-devel ``` -then +然后: ``` -scripts/build.sh # downloads Fcitx5 + yoga, overlays src/, builds m3ui -scripts/install.sh # installs into ~/.local, nothing outside $HOME +scripts/build.sh # 下载 Fcitx5 和 yoga 源码,叠加 src/,构建 m3ui +scripts/install.sh # 安装到 ~/.local,不改动 $HOME 以外的任何东西 ``` -`build.sh` picks the Fcitx5 version from `pkg-config`, override it with -`FCITX_VERSION`. +`build.sh` 会用 `pkg-config` 取已安装的 Fcitx5 版本,可以用 `FCITX_VERSION` 覆盖。 -## Use +## 使用 + +手动试用: ``` FCITX_ADDON_DIRS=$HOME/.local/m3ui/lib64/fcitx5:/usr/lib64/fcitx5 fcitx5 -r -u m3ui ``` -`FCITX_ADDON_DIRS` is how Fcitx5 finds addons outside the system directory, it -has to be set in the environment that starts Fcitx5 if you want this on every -login. `fcitx5 -r` brings the stock interface back. +`FCITX_ADDON_DIRS` 是让 Fcitx5 找到系统目录以外的插件,`fcitx5 -r`(不带参数) +可以回到原版界面。 -Settings live in `~/.config/fcitx5/conf/m3ui.conf`, see -[config/m3ui.conf.example](config/m3ui.conf.example). +**开机自动使用**: -| Key | Default | Meaning | +``` +scripts/enable-autostart.sh +systemctl --user daemon-reload && systemctl --user restart app-org.fcitx.Fcitx5@autostart.service +``` + +它会在家目录里写两个覆盖文件(XDG 自启动项和 D-Bus 激活项),只改 `Exec` 一行。 +撤销只需删掉脚本输出的那两个文件。 + +## 配置 + +配置文件是 `~/.config/fcitx5/conf/m3ui.conf`,示例见 +[config/m3ui.conf.example](config/m3ui.conf.example)。 + +| 键 | 默认值 | 含义 | |---|---|---| -| `Animations` | True | Master switch for animations | -| `Bounce` | True | Off makes movement critically damped (no overshoot) | -| `SpatialStiffness` | 2000 | Spring stiffness for movement, higher is faster | -| `SpatialDamping` | 62 | Damping ratio in percent, lower bounces more | -| `EffectsStiffness` | 3800 | Spring stiffness for fading | -| `SlideDistance` | 22 | Slide in distance in px | -| `SlideDirection` | 0 | 0 from left, 1 from right, 2 from below | -| `Theme` / `DarkTheme` | `m3ui-light` / `m3ui-dark` | Themes in `themes/` | +| `Animations` | True | 动画总开关 | +| `Bounce` | True | 关闭后移动为临界阻尼(无回弹) | +| `SpatialStiffness` | 2000 | 移动的弹簧刚度,越大越快 | +| `SpatialDamping` | 62 | 阻尼比(百分数),越小回弹越明显 | +| `EffectsStiffness` | 3800 | 淡入的弹簧刚度 | +| `SlideDistance` | 22 | 滑入距离(像素) | +| `SlideDirection` | 0 | 0 从左,1 从右,2 从下 | +| `Theme` / `DarkTheme` | `m3ui-light` / `m3ui-dark` | `themes/` 里的主题 | -Themes are classicui themes with extra keys in `[InputPanel]`: `SurfaceColor`, -`OutlineColor`, `HighlightFillColor`, `PanelRadius`, `ItemRadius`. A transparent -`SurfaceColor` falls back to the classic image based drawing, so classic themes -still work (without animation). +主题是 classicui 主题,在 `[InputPanel]` 里多了几个键:`SurfaceColor`、 +`OutlineColor`、`HighlightFillColor`、`PanelRadius`、`ItemRadius`。 +`SurfaceColor` 完全透明时回退到 classicui 的图片绘制,所以原有的 classicui +主题仍然可用(但没有动画)。 -## KWin fade out (optional, KDE Plasma) +## 屏蔽列表 -KWin's *Fading Popups* effect fades popups out over 600 ms and the candidate -window counts as a popup. `kwin/install-fadingpopups-override.sh` copies the -effect from your system into `~/.local/share/kwin-wayland/effects/` and patches -the copy so only the window class `fcitx fcitx` is affected. The script fails -if the upstream effect source no longer matches what it patches. +拼音引擎的"忘记候选词"只会删除你自己打字学到的词,系统词库(`sc.dict`)和 +扩展词库里的词是只读的,忘记之后重新输入仍会出现,当前输入状态里也一样。 -## License +所以 m3ui 在你点"忘记候选词"时,会把这个词记到 +`~/.local/share/fcitx5/m3ui/blocked-words.txt`(一行一个词),并在每次刷新候选 +之前把它们从候选列表里摘掉。 -LGPL-2.1-or-later, see [NOTICE.md](NOTICE.md). +- 按候选词的完整文字精确匹配,只隐藏这个词本身。 +- 想恢复某个词:删掉文件里对应的一行,然后重启 Fcitx5。 +- 这是界面层的补充。换回原版 classicui 后,这些词会重新出现。 + +## KWin 淡出(可选,KDE Plasma) + +KWin 的"渐隐弹出窗口"特效会把弹出窗口淡出 600 毫秒,候选框也被算作弹出窗口。 +`kwin/install-fadingpopups-override.sh` 会把系统里的这个特效复制到 +`~/.local/share/kwin-wayland/effects/` 并打补丁,只对窗口类 `fcitx fcitx` 生效: +跳过 KWin 的淡入(让 m3ui 自己的弹入动画可见),并把淡出缩短到 90 毫秒 +(`FADE_OUT_MS` 可调)。其他弹出窗口不受影响。如果上游特效的源码变了、补丁对不上, +脚本会直接报错退出。 + +## 许可证 + +LGPL-2.1-or-later,见 [NOTICE.md](NOTICE.md)。 diff --git a/README.zh-CN.md b/README.zh-CN.md deleted file mode 100644 index 78920ea..0000000 --- a/README.zh-CN.md +++ /dev/null @@ -1,41 +0,0 @@ -# fcitx5-m3ui - -给 [Fcitx5](https://github.com/fcitx/fcitx5) 用的 Material 3 风格候选框, -以界面插件(`m3ui`)的形式替换 `classicui`。 - -[English](README.md) - -## 特性 - -- Material 3 圆角面板,深色/浅色主题跟随系统。 -- 弹簧动画:候选框带轻微回弹滑入并淡入,高亮块在候选词之间滑动,刚度和阻尼可调。 -- 在候选词上右键打开动作菜单,内容取决于输入法提供的动作,拼音有 - "忘记候选词 / 置顶为自定义短语 / 从自定义短语中删除"。 -- Wayland 和 X11(Xwayland)输入上下文都支持,定位和鼠标处理沿用 `classicui`。 -- 可选的 KWin 辅助脚本:只缩短候选框自己的 KWin 淡出时间。 - -## 状态 - -个人项目,只在 Fedora 44、KDE Plasma 6.7(Wayland)、NVIDIA 显卡、 -Fcitx5 5.1.23、拼音引擎上用过,构建脚本没有在其他环境跑过。 - -本插件是 `classicui` 的分支,需要在 Fcitx5 源码树内构建(Fcitx5 的 Wayland 协议封装 -不包含在 `fcitx5-devel` 里),构建用的源码版本必须和已安装的 Fcitx5 一致。 - -没有自己的淡出动画:Wayland 下面板无内容时窗口会被立刻销毁。 - -## 构建与使用 - -依赖安装命令、`scripts/build.sh`、`scripts/install.sh` 以及启动方式见 -[English README](README.md)。启动命令: - -``` -FCITX_ADDON_DIRS=$HOME/.local/m3ui/lib64/fcitx5:/usr/lib64/fcitx5 fcitx5 -r -u m3ui -``` - -想回到原版界面运行 `fcitx5 -r`。配置在 `~/.config/fcitx5/conf/m3ui.conf`, -示例见 [config/m3ui.conf.example](config/m3ui.conf.example)。 - -## 许可证 - -LGPL-2.1-or-later,见 [NOTICE.md](NOTICE.md)。 diff --git a/scripts/enable-autostart.sh b/scripts/enable-autostart.sh new file mode 100755 index 0000000..f927541 --- /dev/null +++ b/scripts/enable-autostart.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# Makes Fcitx5 start with m3ui at every login. Writes two user level overrides, +# one for the XDG autostart entry and one for D-Bus activation, nothing outside +# $HOME is touched. If the addon cannot be loaded Fcitx5 falls back to classicui. +# +# Undo: delete the two files printed below and run +# systemctl --user daemon-reload +set -euo pipefail + +prefix=${PREFIX:-$HOME/.local/m3ui} +config=${XDG_CONFIG_HOME:-$HOME/.config} +data=${XDG_DATA_HOME:-$HOME/.local/share} + +lib=$(find "$prefix" -name libm3ui.so 2>/dev/null | head -1) +[ -n "$lib" ] || { echo "libm3ui.so not found under $prefix, run scripts/install.sh first" >&2; exit 1; } +addondir=$(dirname "$lib") +system_addondir=$(pkg-config --variable=addondir Fcitx5Core 2>/dev/null || true) +system_addondir=${system_addondir:-/usr/lib64/fcitx5} +dirs="$addondir:$system_addondir" + +autostart=$config/autostart/org.fcitx.Fcitx5.desktop +service=$data/dbus-1/services/org.fcitx.Fcitx5.service +mkdir -p "$(dirname "$autostart")" "$(dirname "$service")" + +# Copy the system entry and change only the Exec line. +sed -E "s#^Exec=/usr/bin/fcitx5\$#Exec=env FCITX_ADDON_DIRS=$dirs /usr/bin/fcitx5 -u m3ui#" \ + /etc/xdg/autostart/org.fcitx.Fcitx5.desktop >"$autostart" +grep -q '^Exec=env FCITX_ADDON_DIRS=' "$autostart" || + { echo "unexpected system autostart file, nothing changed" >&2; rm -f "$autostart"; exit 1; } + +cat >"$service" < #include #include +#include #include #include #include @@ -66,6 +67,7 @@ FCITX_DEFINE_LOG_CATEGORY(m3ui_logcategory, "m3ui"); using AccentColorDBusType = FCITX_STRING_TO_DBUS_TYPE("(ddd)"); M3UI::M3UI(Instance *instance) : instance_(instance) { + loadBlockedWords(); #ifdef ENABLE_DBUS if (auto *dbusAddon = dbus()) { dbus::VariantTypeRegistry::defaultRegistry() @@ -150,6 +152,34 @@ M3UI::M3UI(Instance *instance) : instance_(instance) { M3UI::~M3UI() {} +std::filesystem::path M3UI::blockedWordsPath() const { + return StandardPaths::global().userDirectory(StandardPathsType::PkgData) / + "m3ui" / "blocked-words.txt"; +} + +void M3UI::loadBlockedWords() { + blockedWords_.clear(); + std::ifstream in(blockedWordsPath()); + std::string line; + while (std::getline(in, line)) { + if (!line.empty()) { + blockedWords_.insert(line); + } + } +} + +void M3UI::blockWord(const std::string &word) { + if (word.empty() || word.find('\n') != std::string::npos || + !blockedWords_.insert(word).second) { + return; + } + const auto path = blockedWordsPath(); + std::error_code ec; + std::filesystem::create_directories(path.parent_path(), ec); + std::ofstream out(path, std::ios::app); + out << word << '\n'; +} + void M3UI::reloadConfig() { readAsIni(config_, "conf/m3ui.conf"); reloadTheme(); diff --git a/src/classicui.h b/src/classicui.h index 20352ce..a1f224c 100644 --- a/src/classicui.h +++ b/src/classicui.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include "fcitx-config/configuration.h" @@ -266,7 +267,20 @@ public: 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. + bool hasBlockedWords() const { return !blockedWords_.empty(); } + bool isBlocked(const std::string &word) const { + return blockedWords_.contains(word); + } + void blockWord(const std::string &word); + private: + void loadBlockedWords(); + std::filesystem::path blockedWordsPath() const; + std::unordered_set blockedWords_; + FCITX_ADDON_DEPENDENCY_LOADER(notificationitem, instance_->addonManager()); FCITX_ADDON_EXPORT_FUNCTION(M3UI, labelIcon); FCITX_ADDON_EXPORT_FUNCTION(M3UI, preferTextIcon); diff --git a/src/inputwindow.cpp b/src/inputwindow.cpp index 0caca7f..f0fec5a 100644 --- a/src/inputwindow.cpp +++ b/src/inputwindow.cpp @@ -130,6 +130,16 @@ double springSettleSeconds(double stiffness, double damping) { return damping >= 1.0 ? 7.5 / w : 5.3 / (damping * w); } +// The engine's "forget" action, as shown in English or Chinese. +bool isForgetAction(const std::string &text) { + std::string lower = text; + std::ranges::transform(lower, lower.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + return lower.find("forget") != std::string::npos || + text.find("\xe5\xbf\x98\xe8\xae\xb0") != std::string::npos; // 忘记 +} + double millisecondsSince(std::chrono::steady_clock::time_point start, std::chrono::steady_clock::time_point now) { return std::chrono::duration(now - start).count(); @@ -419,6 +429,19 @@ std::pair InputWindow::update(InputContext *inputContext) { auxUp = instance->outputFilter(inputContext, inputPanel.auxUp()); auxDown = instance->outputFilter(inputContext, inputPanel.auxDown()); candidateList = inputPanel.candidateList(); + // Drop forgotten words, removing from the engine's list keeps labels + // and selection keys consistent with what is shown. + if (candidateList && parent_->hasBlockedWords()) { + if (auto *modifiable = candidateList->toModifiable()) { + for (int i = modifiable->totalSize() - 1; i >= 0; i--) { + if (parent_->isBlocked(modifiable->candidateFromAll(i) + .text() + .toString())) { + modifiable->remove(i); + } + } + } + } } else if (!inputPanel.overlayMessage().empty()) { auxUp = inputPanel.overlayMessage(); } @@ -952,10 +975,12 @@ void InputWindow::click(int x, int y) { if (menuOpen_) { // Copy what is needed first, triggering an action rebuilds the list. int actionId = -1; + std::string actionText; const auto candidateIndex = menuCandidate_; for (size_t i = 0; i < menuRegions_.size(); i++) { if (menuRegions_[i].contains(x, y)) { actionId = menuActions_[i].id(); + actionText = menuActions_[i].text(); break; } } @@ -965,6 +990,11 @@ void InputWindow::click(int x, int y) { const auto *candidate = nthCandidateIgnorePlaceholder(*candidateList, candidateIndex); if (actionable && candidate) { + // Block before the engine refreshes its list so the refreshed + // list is already filtered. + if (isForgetAction(actionText)) { + parent_->blockWord(candidate->text().toString()); + } actionable->triggerAction(*candidate, actionId); } }