Hide forgotten candidates, add autostart script, Chinese README
- Record words picked with "Forget candidate" in a block list and drop them from the candidate list before each refresh, so words from read only dictionaries stay gone. - Add scripts/enable-autostart.sh for starting with m3ui at login. - Make the Chinese README the default, keep the English one as README.en.md. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0804634dc1
commit
8d14454bb9
7 files changed
+329
-100
No files matched your search
+131
@@ -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).
|
||||||
@@ -1,41 +1,41 @@
|
|||||||
# fcitx5-m3ui
|
# fcitx5-m3ui
|
||||||
|
|
||||||
A Material 3 styled candidate window for [Fcitx5](https://github.com/fcitx/fcitx5),
|
给 [Fcitx5](https://github.com/fcitx/fcitx5) 用的 Material 3 风格候选框,
|
||||||
built as a user interface addon (`m3ui`) that replaces `classicui`.
|
以界面插件(`m3ui`)的形式替换 `classicui`。
|
||||||
|
|
||||||
[简体中文](README.zh-CN.md)
|
[English](README.en.md)
|
||||||
|
|
||||||
## Features
|
## 特性
|
||||||
|
|
||||||
- Rounded Material 3 panel with light and dark themes that follow the system.
|
- Material 3 圆角面板,深色 / 浅色主题跟随系统。
|
||||||
- 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*.
|
- Wayland 和 X11(Xwayland)输入上下文都支持,定位和鼠标处理沿用 `classicui`。
|
||||||
- Works on Wayland and X11 (Xwayland) input contexts, positioning and pointer
|
- 可选的 KWin 辅助脚本:只缩短候选框自己的 KWin 淡出时间。
|
||||||
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
|
个人项目,只在 Fedora 44、KDE Plasma 6.7(Wayland)、NVIDIA 显卡、
|
||||||
GPU, Fcitx5 5.1.23 with the Pinyin engine. The build scripts have not been run
|
Fcitx5 5.1.23、拼音引擎上用过。动画、右键菜单、忘记候选词、开机自动启动
|
||||||
on any other setup.
|
在这套环境里都实际用过。
|
||||||
|
|
||||||
The addon is a fork of `classicui` and is built inside the Fcitx5 source tree,
|
**`scripts/build.sh` 没有在干净环境里从零完整跑过**,其他发行版和桌面也没有试过,
|
||||||
because Fcitx5's Wayland protocol wrappers are not installed by `fcitx5-devel`.
|
遇到问题请开 issue。
|
||||||
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
|
本插件是 `classicui` 的分支,需要在 Fcitx5 源码树内构建:Fcitx5 的 Wayland
|
||||||
soon as the panel has nothing to show.
|
协议封装不包含在 `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 \
|
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
|
xcb-util-image-devel xcb-imdkit-devel libxkbfile-devel libxkbcommon-x11-devel
|
||||||
```
|
```
|
||||||
|
|
||||||
then
|
然后:
|
||||||
|
|
||||||
```
|
```
|
||||||
scripts/build.sh # downloads Fcitx5 + yoga, overlays src/, builds m3ui
|
scripts/build.sh # 下载 Fcitx5 和 yoga 源码,叠加 src/,构建 m3ui
|
||||||
scripts/install.sh # installs into ~/.local, nothing outside $HOME
|
scripts/install.sh # 安装到 ~/.local,不改动 $HOME 以外的任何东西
|
||||||
```
|
```
|
||||||
|
|
||||||
`build.sh` picks the Fcitx5 version from `pkg-config`, override it with
|
`build.sh` 会用 `pkg-config` 取已安装的 Fcitx5 版本,可以用 `FCITX_VERSION` 覆盖。
|
||||||
`FCITX_VERSION`.
|
|
||||||
|
|
||||||
## Use
|
## 使用
|
||||||
|
|
||||||
|
手动试用:
|
||||||
|
|
||||||
```
|
```
|
||||||
FCITX_ADDON_DIRS=$HOME/.local/m3ui/lib64/fcitx5:/usr/lib64/fcitx5 fcitx5 -r -u m3ui
|
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
|
`FCITX_ADDON_DIRS` 是让 Fcitx5 找到系统目录以外的插件,`fcitx5 -r`(不带参数)
|
||||||
has to be set in the environment that starts Fcitx5 if you want this on every
|
可以回到原版界面。
|
||||||
login. `fcitx5 -r` brings the stock interface back.
|
|
||||||
|
|
||||||
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 |
|
| `Animations` | True | 动画总开关 |
|
||||||
| `Bounce` | True | Off makes movement critically damped (no overshoot) |
|
| `Bounce` | True | 关闭后移动为临界阻尼(无回弹) |
|
||||||
| `SpatialStiffness` | 2000 | Spring stiffness for movement, higher is faster |
|
| `SpatialStiffness` | 2000 | 移动的弹簧刚度,越大越快 |
|
||||||
| `SpatialDamping` | 62 | Damping ratio in percent, lower bounces more |
|
| `SpatialDamping` | 62 | 阻尼比(百分数),越小回弹越明显 |
|
||||||
| `EffectsStiffness` | 3800 | Spring stiffness for fading |
|
| `EffectsStiffness` | 3800 | 淡入的弹簧刚度 |
|
||||||
| `SlideDistance` | 22 | Slide in distance in px |
|
| `SlideDistance` | 22 | 滑入距离(像素) |
|
||||||
| `SlideDirection` | 0 | 0 from left, 1 from right, 2 from below |
|
| `SlideDirection` | 0 | 0 从左,1 从右,2 从下 |
|
||||||
| `Theme` / `DarkTheme` | `m3ui-light` / `m3ui-dark` | Themes in `themes/` |
|
| `Theme` / `DarkTheme` | `m3ui-light` / `m3ui-dark` | `themes/` 里的主题 |
|
||||||
|
|
||||||
Themes are classicui themes with extra keys in `[InputPanel]`: `SurfaceColor`,
|
主题是 classicui 主题,在 `[InputPanel]` 里多了几个键:`SurfaceColor`、
|
||||||
`OutlineColor`, `HighlightFillColor`, `PanelRadius`, `ItemRadius`. A transparent
|
`OutlineColor`、`HighlightFillColor`、`PanelRadius`、`ItemRadius`。
|
||||||
`SurfaceColor` falls back to the classic image based drawing, so classic themes
|
`SurfaceColor` 完全透明时回退到 classicui 的图片绘制,所以原有的 classicui
|
||||||
still work (without animation).
|
主题仍然可用(但没有动画)。
|
||||||
|
|
||||||
## KWin fade out (optional, KDE Plasma)
|
## 屏蔽列表
|
||||||
|
|
||||||
KWin's *Fading Popups* effect fades popups out over 600 ms and the candidate
|
拼音引擎的"忘记候选词"只会删除你自己打字学到的词,系统词库(`sc.dict`)和
|
||||||
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
|
所以 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)。
|
||||||
@@ -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)。
|
|
||||||
Executable
+39
@@ -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" <<SERVICE
|
||||||
|
[D-BUS Service]
|
||||||
|
Name=org.fcitx.Fcitx5
|
||||||
|
Exec=/usr/bin/env FCITX_ADDON_DIRS=$dirs /usr/bin/fcitx5 -u m3ui
|
||||||
|
SERVICE
|
||||||
|
|
||||||
|
echo "wrote $autostart"
|
||||||
|
echo "wrote $service"
|
||||||
|
echo "Apply now: systemctl --user daemon-reload && systemctl --user restart app-org.fcitx.Fcitx5@autostart.service"
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -66,6 +67,7 @@ FCITX_DEFINE_LOG_CATEGORY(m3ui_logcategory, "m3ui");
|
|||||||
using AccentColorDBusType = FCITX_STRING_TO_DBUS_TYPE("(ddd)");
|
using AccentColorDBusType = FCITX_STRING_TO_DBUS_TYPE("(ddd)");
|
||||||
|
|
||||||
M3UI::M3UI(Instance *instance) : instance_(instance) {
|
M3UI::M3UI(Instance *instance) : instance_(instance) {
|
||||||
|
loadBlockedWords();
|
||||||
#ifdef ENABLE_DBUS
|
#ifdef ENABLE_DBUS
|
||||||
if (auto *dbusAddon = dbus()) {
|
if (auto *dbusAddon = dbus()) {
|
||||||
dbus::VariantTypeRegistry::defaultRegistry()
|
dbus::VariantTypeRegistry::defaultRegistry()
|
||||||
@@ -150,6 +152,34 @@ M3UI::M3UI(Instance *instance) : instance_(instance) {
|
|||||||
|
|
||||||
M3UI::~M3UI() {}
|
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() {
|
void M3UI::reloadConfig() {
|
||||||
readAsIni(config_, "conf/m3ui.conf");
|
readAsIni(config_, "conf/m3ui.conf");
|
||||||
reloadTheme();
|
reloadTheme();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "fcitx-config/configuration.h"
|
#include "fcitx-config/configuration.h"
|
||||||
@@ -266,7 +267,20 @@ public:
|
|||||||
bool preferTextIcon() const;
|
bool preferTextIcon() const;
|
||||||
bool showLayoutNameInIcon() 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:
|
private:
|
||||||
|
void loadBlockedWords();
|
||||||
|
std::filesystem::path blockedWordsPath() const;
|
||||||
|
std::unordered_set<std::string> blockedWords_;
|
||||||
|
|
||||||
FCITX_ADDON_DEPENDENCY_LOADER(notificationitem, instance_->addonManager());
|
FCITX_ADDON_DEPENDENCY_LOADER(notificationitem, instance_->addonManager());
|
||||||
FCITX_ADDON_EXPORT_FUNCTION(M3UI, labelIcon);
|
FCITX_ADDON_EXPORT_FUNCTION(M3UI, labelIcon);
|
||||||
FCITX_ADDON_EXPORT_FUNCTION(M3UI, preferTextIcon);
|
FCITX_ADDON_EXPORT_FUNCTION(M3UI, preferTextIcon);
|
||||||
|
|||||||
@@ -130,6 +130,16 @@ double springSettleSeconds(double stiffness, double damping) {
|
|||||||
return damping >= 1.0 ? 7.5 / w : 5.3 / (damping * w);
|
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<char>(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,
|
double millisecondsSince(std::chrono::steady_clock::time_point start,
|
||||||
std::chrono::steady_clock::time_point now) {
|
std::chrono::steady_clock::time_point now) {
|
||||||
return std::chrono::duration<double, std::milli>(now - start).count();
|
return std::chrono::duration<double, std::milli>(now - start).count();
|
||||||
@@ -419,6 +429,19 @@ std::pair<int, int> InputWindow::update(InputContext *inputContext) {
|
|||||||
auxUp = instance->outputFilter(inputContext, inputPanel.auxUp());
|
auxUp = instance->outputFilter(inputContext, inputPanel.auxUp());
|
||||||
auxDown = instance->outputFilter(inputContext, inputPanel.auxDown());
|
auxDown = instance->outputFilter(inputContext, inputPanel.auxDown());
|
||||||
candidateList = inputPanel.candidateList();
|
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()) {
|
} else if (!inputPanel.overlayMessage().empty()) {
|
||||||
auxUp = inputPanel.overlayMessage();
|
auxUp = inputPanel.overlayMessage();
|
||||||
}
|
}
|
||||||
@@ -952,10 +975,12 @@ void InputWindow::click(int x, int y) {
|
|||||||
if (menuOpen_) {
|
if (menuOpen_) {
|
||||||
// Copy what is needed first, triggering an action rebuilds the list.
|
// Copy what is needed first, triggering an action rebuilds the list.
|
||||||
int actionId = -1;
|
int actionId = -1;
|
||||||
|
std::string actionText;
|
||||||
const auto candidateIndex = menuCandidate_;
|
const auto candidateIndex = menuCandidate_;
|
||||||
for (size_t i = 0; i < menuRegions_.size(); i++) {
|
for (size_t i = 0; i < menuRegions_.size(); i++) {
|
||||||
if (menuRegions_[i].contains(x, y)) {
|
if (menuRegions_[i].contains(x, y)) {
|
||||||
actionId = menuActions_[i].id();
|
actionId = menuActions_[i].id();
|
||||||
|
actionText = menuActions_[i].text();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -965,6 +990,11 @@ void InputWindow::click(int x, int y) {
|
|||||||
const auto *candidate =
|
const auto *candidate =
|
||||||
nthCandidateIgnorePlaceholder(*candidateList, candidateIndex);
|
nthCandidateIgnorePlaceholder(*candidateList, candidateIndex);
|
||||||
if (actionable && candidate) {
|
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);
|
actionable->triggerAction(*candidate, actionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user