Show the Active Fcitx5 Input Group in KDE Plasma with a Persistent Tray Indicator
What this does and where I tested it
The goal is simple: keep Fcitx’s own tray icon for the current input method, and add a second persistent indicator for the current Fcitx group.
- Tested environment: Debian Sid, KDE Plasma 6.7.4, Wayland, and Fcitx5 5.1.x. Other KDE Plasma and Fcitx5 distributions should be adaptable using the same D-Bus interfaces and dependencies.
- Shortcut:
Super + Spaceswitches Fcitx groups. - Display: my
默认group shows中;english onlyshowsEN. Fcitx’s own拼 / keyboardicon continues to work independently. - Updates: normal group changes are event-driven; the indicator does not poll frequently.
- Measured latency: about 20.8 ms from the group-switch event to the custom indicator’s icon update.
- Autostart: a systemd user service starts with
graphical-session.targetand restarts the indicator if it exits unexpectedly.
If you want a persistent, glanceable indication of the active input environment, similar to the language indicator in Windows, this setup is for that use case.
I use Fcitx5 groups to imitate Windows input-language behavior. I have two groups:
默认contains Pinyin and an English keyboard.english onlycontains only the English keyboard.
I bound Super + Space (the Windows key plus Space) to switch groups. Functionally, that is close to Windows, but the visual feedback was missing: Fcitx’s tray icon shows the current input method, not the current group.
For example, both of these states look like an English keyboard in Fcitx’s native tray icon:
默认 group + keyboard-us
english only group + keyboard-us
But they mean different things. In the first group, I can press Shift to return to Pinyin. The second is a strictly English-only environment.
I wanted a persistent indicator in the system tray, like the language indicator at the lower-right corner of Windows. It should show whether I am in the Chinese group or the English-only group regardless of the input method selected inside that group.
Fcitx's native icon Custom group indicator
[拼 / keyboard] [中 / EN]
The two indicators have separate jobs: Fcitx shows the current input method, and the extra indicator shows the current group.
Why I wrote a small app instead of installing a widget
I first looked for an existing KDE component that persistently shows the current Fcitx5 input-method group, but did not find a mature option for that exact purpose.
Several similar-looking options solve different problems:
- Fcitx5’s StatusNotifier is useful, but its icon represents the current input method, such as Pinyin or a keyboard, rather than the active group.
- KDE’s keyboard-layout indicator shows the XKB keyboard layout. That is not the same as an Fcitx input-method group.
- KIMPanel, or KDE Input Method Panel, focuses more on candidate lists, input-method status, and UI integration. It is not a persistent Windows-style group label.
- I tried writing a Plasma plasmoid. Plasma 6 is strict about metadata, QML APIs, and package structure. My first experiment even crashed
plasmashell, taking the top and bottom panels and desktop with it. A plasmoid could be made to work, but loading custom code directly into Plasma Shell was not a good risk/reward tradeoff for a small status indicator.
I chose a more isolated design: a separate StatusNotifier / AppIndicator process. If it crashes, only the custom 中 / EN icon disappears; it does not take down the Plasma panel.
Use Fcitx5’s D-Bus controller
Fcitx5 exposes D-Bus methods to query and switch the current group. To query it:
gdbus call --session \
--dest org.fcitx.Fcitx5 \
--object-path /controller \
--method org.fcitx.Fcitx.Controller1.CurrentInputMethodGroup
On my machine this returns:
('默认',)
To switch to the English-only group:
gdbus call --session \
--dest org.fcitx.Fcitx5 \
--object-path /controller \
--method org.fcitx.Fcitx.Controller1.SwitchInputMethodGroup \
'english only'
The indicator does not need to imitate the keyboard shortcut or parse Fcitx configuration files.
From polling to event-driven updates
The first version called CurrentInputMethodGroup() every 300 ms:
Ask Fcitx every 300 ms
↓
Notice a group change
↓
Update the tray icon
It worked, but after pressing Super + Space, the 中 / EN icon sometimes changed a little late. Three hundred milliseconds sounds short, but it is noticeable for a UI that should respond immediately to an input-state change.
Fcitx already has an internal InputMethodGroupChanged event, and its official StatusNotifier listens for it. When the group changes, Fcitx updates its tray icon and menu, then emits the D-Bus signal:
org.kde.StatusNotifierItem.NewIcon
I use that signal as the trigger for my own indicator:
Super + Space
↓
Fcitx InputMethodGroupChanged event
↓
Fcitx StatusNotifier emits NewIcon
↓
Custom indicator receives the signal
↓
Read CurrentInputMethodGroup
↓
Update 中 / EN immediately
In my test, the time from the group-switch event to the custom indicator emitting its own NewIcon was about 20.8 ms. In practice, the icon changes almost as soon as I press the shortcut.
The code still has a low-frequency, 10-second fallback sync. It is only for unusual cases such as Fcitx restarting, the tray item registering again, or a missed event. Normal group changes do not depend on the timer.
Fix note (August 15, 2026): In the first version,
rediscover_fcitx_sni()incorrectly returnedTruewhen used as aGLib.idle_add()callback. GLib interpreted that as a request to keep the idle source and schedule it again, causing frequent D-Bus queries and unusually high CPU use by Python,dbus-daemon, Fcitx, and KDED. The callback now returnsFalseafter one discovery pass. Normal group changes remain event-driven, and the 10-second timer is only a fallback.
Dependencies and directory layout
This is the environment I used:
Debian Sid
KDE Plasma 6.7.4
Wayland
Fcitx5 5.1.x
Python 3
Install the required packages on Debian:
sudo apt install \
python3-dbus \
python3-gi \
gir1.2-gtk-3.0 \
gir1.2-ayatanaappindicator3-0.1
The current implementation uses AyatanaAppIndicator3. You may see a deprecation warning from libayatana-appindicator at runtime; it does not affect the current functionality. For a small project that needs long-term maintenance, I would consider rewriting it as a native Qt/KF6 StatusNotifierItem.
The files on my machine are arranged like this:
~/.local/share/fcitx-group-indicator/
├── indicator.py
└── icons/
├── index.theme
└── scalable/apps/
├── fcitx-group-default.svg
└── fcitx-group-english.svg
~/.config/systemd/user/
└── fcitx-group-indicator.service
Complete indicator.py
This is the complete version currently running on my machine:
#!/usr/bin/python3
import os
import dbus
import gi
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
gi.require_version('Gtk', '3.0')
gi.require_version('AyatanaAppIndicator3', '0.1')
from gi.repository import Gtk, GLib, AyatanaAppIndicator3 as AppIndicator
BASE = os.path.dirname(os.path.abspath(__file__))
ZH_ICON = os.path.join(BASE, 'icons/scalable/apps/fcitx-group-default.svg')
EN_ICON = os.path.join(BASE, 'icons/scalable/apps/fcitx-group-english.svg')
FCITX_IFACE = 'org.fcitx.Fcitx.Controller1'
SNI_IFACE = 'org.kde.StatusNotifierItem'
WATCHER_IFACE = 'org.kde.StatusNotifierWatcher'
WATCHER_NAME = 'org.kde.StatusNotifierWatcher'
WATCHER_PATH = '/StatusNotifierWatcher'
bus = dbus.SessionBus()
fcitx_obj = bus.get_object('org.fcitx.Fcitx5', '/controller')
fcitx = dbus.Interface(fcitx_obj, FCITX_IFACE)
indicator = AppIndicator.Indicator.new(
'fcitx-group-indicator', 'input-keyboard',
AppIndicator.IndicatorCategory.SYSTEM_SERVICES)
indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE)
last_group = None
fcitx_sni_match = None
def current_group():
try:
return str(fcitx.CurrentInputMethodGroup())
except Exception:
return '?'
def refresh(*_args):
global last_group
group = current_group()
if group == last_group:
return True
last_group = group
if group == '默认':
icon, label = ZH_ICON, '中'
elif group == 'english only':
icon, label = EN_ICON, 'EN'
else:
icon, label = 'input-keyboard', '?'
indicator.set_icon_full(icon, 'Fcitx 输入法分组')
indicator.set_label(label, 'EN')
state_item.set_label('当前分组:' + group)
return True
def on_fcitx_new_icon(*_args):
# Fcitx's own StatusNotifier emits this immediately on
# EventType::InputMethodGroupChanged.
refresh()
def split_registered_item(item):
text = str(item)
pos = text.find('/')
if pos <= 0:
return None, None
return text[:pos], text[pos:]
def rediscover_fcitx_sni(*_args):
global fcitx_sni_match
try:
watcher_obj = bus.get_object(WATCHER_NAME, WATCHER_PATH)
props = dbus.Interface(watcher_obj, 'org.freedesktop.DBus.Properties')
items = props.Get(WATCHER_IFACE, 'RegisteredStatusNotifierItems')
except Exception:
return False
for item in items:
service, path = split_registered_item(item)
if not service:
continue
try:
obj = bus.get_object(service, path)
p = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
if str(p.Get(SNI_IFACE, 'Id')) != 'Fcitx':
continue
except Exception:
continue
if fcitx_sni_match is not None:
fcitx_sni_match.remove()
fcitx_sni_match = bus.add_signal_receiver(
on_fcitx_new_icon, signal_name='NewIcon',
dbus_interface=SNI_IFACE, bus_name=service, path=path)
refresh()
return False
return False
def toggle(_item=None):
group = current_group()
target = 'english only' if group == '默认' else '默认'
try:
fcitx.SwitchInputMethodGroup(target)
finally:
refresh()
menu = Gtk.Menu()
state_item = Gtk.MenuItem(label='当前分组:?')
state_item.set_sensitive(False)
menu.append(state_item)
switch_item = Gtk.MenuItem(label='切换分组')
switch_item.connect('activate', toggle)
menu.append(switch_item)
menu.append(Gtk.SeparatorMenuItem())
quit_item = Gtk.MenuItem(label='退出指示器')
quit_item.connect('activate', lambda _x: Gtk.main_quit())
menu.append(quit_item)
menu.show_all()
indicator.set_menu(menu)
bus.add_signal_receiver(
lambda *_: GLib.idle_add(rediscover_fcitx_sni),
signal_name='StatusNotifierItemRegistered',
dbus_interface=WATCHER_IFACE, bus_name=WATCHER_NAME,
path=WATCHER_PATH)
bus.add_signal_receiver(
lambda *_: GLib.idle_add(rediscover_fcitx_sni),
signal_name='StatusNotifierItemUnregistered',
dbus_interface=WATCHER_IFACE, bus_name=WATCHER_NAME,
path=WATCHER_PATH)
refresh()
rediscover_fcitx_sni()
# Low-frequency safety sync only; normal changes are event-driven.
GLib.timeout_add_seconds(10, refresh)
Gtk.main()
The code looks for Fcitx’s own StatusNotifier item in Plasma’s org.kde.StatusNotifierWatcher, checks that its ID is Fcitx, then listens for org.kde.StatusNotifierItem.NewIcon. When Fcitx registers or unregisters the item, the indicator discovers it again and reconnects the signal receiver.
Icons
fcitx-group-default.svg
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
<rect x="4" y="4" width="56" height="56" rx="12" fill="#2563eb" stroke="#ffffff" stroke-width="2"/>
<text x="32" y="45" text-anchor="middle" font-family="Noto Sans CJK SC, Noto Sans, sans-serif" font-size="38" font-weight="700" fill="#ffffff">中</text>
</svg>
fcitx-group-english.svg
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
<rect x="4" y="4" width="56" height="56" rx="12" fill="#374151" stroke="#ffffff" stroke-width="2"/>
<text x="32" y="42" text-anchor="middle" font-family="Noto Sans, sans-serif" font-size="25" font-weight="700" fill="#ffffff">EN</text>
</svg>
icons/index.theme
[Icon Theme]
Name=Fcitx Group Indicator
Comment=Icons for Fcitx group indicator
Directories=scalable/apps
[scalable/apps]
Size=64
Context=Applications
Type=Scalable
MinSize=16
MaxSize=128
The tray indicator currently uses absolute paths to the SVG files. This avoids uncertainty from Plasma/Qt icon-theme caches and icon-name fallback behavior.
systemd user service
I use a systemd user service rather than a KDE ~/.config/autostart/*.desktop entry. The indicator is a background process with the same lifecycle as the graphical session, and systemd makes autostart, restarts, logs, and lifecycle management straightforward.
This is the unit file running on my machine:
[Unit]
Description=Fcitx input method group indicator
PartOf=graphical-session.target
After=graphical-session.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /home/iruanp/.local/share/fcitx-group-indicator/indicator.py
Restart=on-failure
RestartSec=2
Environment=GTK_MODULES=
[Install]
WantedBy=graphical-session.target
Enable and start it:
systemctl --user daemon-reload
systemctl --user enable --now fcitx-group-indicator.service
Check its status:
systemctl --user status fcitx-group-indicator.service
systemctl --user is-enabled fcitx-group-indicator.service
systemctl --user is-active fcitx-group-indicator.service
The expected state is:
enabled
active
Because the unit uses WantedBy=graphical-session.target and PartOf=graphical-session.target, it runs only in the graphical desktop session and stops when the Plasma session ends. There is no need to enable lingering with loginctl.
The
ExecStartpath above is specific to my machine. Replace/home/iruanpwith your own home directory. A more portable unit can use systemd’s%hspecifier, for example:ExecStart=/usr/bin/python3 %h/.local/share/fcitx-group-indicator/indicator.py.
Install from scratch
Create the directories:
mkdir -p ~/.local/share/fcitx-group-indicator/icons/scalable/apps
mkdir -p ~/.config/systemd/user
Save the files shown above as:
~/.local/share/fcitx-group-indicator/indicator.py
~/.local/share/fcitx-group-indicator/icons/index.theme
~/.local/share/fcitx-group-indicator/icons/scalable/apps/fcitx-group-default.svg
~/.local/share/fcitx-group-indicator/icons/scalable/apps/fcitx-group-english.svg
~/.config/systemd/user/fcitx-group-indicator.service
Making the Python file executable is optional because the unit starts it explicitly with /usr/bin/python3, but you can do it:
chmod +x ~/.local/share/fcitx-group-indicator/indicator.py
Then enable and start the service:
systemctl --user daemon-reload
systemctl --user enable --now fcitx-group-indicator.service
If your Fcitx group names are different
The current code uses my two group names:
默认
english only
They map to:
默认 -> 中
english only -> EN
If your names differ, update the conditions in refresh():
if group == '默认':
icon, label = ZH_ICON, '中'
elif group == 'english only':
icon, label = EN_ICON, 'EN'
Then update the target group in toggle(). You could also make the mapping configurable, for example:
[groups]
"默认" = "中"
"english only" = "EN"
That would remove the need to hard-code group names in the Python code.
Restore Fcitx’s “group switched” notification
I once clicked “Don’t show again” on Fcitx’s “Switched to group…” notification. After that, changing ShowInputMethodInformation did not bring it back.
Fcitx stores hidden notification types separately:
[HiddenNotifications]
0=enumerate-group
The file is:
~/.config/fcitx5/conf/notifications.conf
Remove enumerate-group from HiddenNotifications, then reload Fcitx’s configuration to restore the notification.
This notification and the persistent 中 / EN indicator are independent. The brief notification confirms that a switch just happened; the persistent indicator shows which group is active now.
Why not change Fcitx’s native tray icon?
One option would be to fork Fcitx’s notificationitem module and make it change its icon based on the active group. That would turn a personal UI preference into a patch that has to be maintained:
- The patch would need updates after Fcitx upgrades.
- The related module would need to be rebuilt.
- Fcitx’s native icon already communicates the current input method; changing it to show the group would lose that information.
Keeping two indicators is clearer:
Fcitx: current input method
Custom indicator: current group
The indicator is loosely coupled to Fcitx and relies on the public D-Bus controller and the standard StatusNotifier signal.
Possible improvements
This version solves the most noticeable problem, polling delay, but it is not a formally packaged application. If I continue developing it, I would consider:
- Rewriting it as a native Qt6/KF6 StatusNotifierItem so its menu, fonts, DPI, and Plasma integration are more consistent.
- Reading all Fcitx groups automatically instead of hard-coding two.
- Making the group-to-label-and-icon mapping configurable in TOML or JSON.
- Listing every group in the menu and marking the active one with a radio item.
- Packaging it as a Debian package or maintaining it in a separate repository.
- Listening to a direct “current group changed” D-Bus signal if Fcitx exposes one in the future, instead of using the native tray’s
NewIconsignal as a bridge.
For my current setup, the indicator meets the goal: after I switch Fcitx groups with Super + Space, KDE updates the 中 / EN label almost immediately, while Fcitx continues to show whether Pinyin or the English keyboard is active.
Appendix: machine-readable reproduction checklist
This YAML block is intended for AI and automation tools. An AI reading the article can parse it and use the files, dependencies, group_mapping, and commands sections to recreate the setup.
Expand machine-readable configuration
artifact: fcitx5-group-indicator
purpose: "Persistently show the active Fcitx5 input-method group in the KDE Plasma system tray"
created_with:
product: ChatGPT
model: GPT-5.6 Sol
date: 2026-08-15
method: "Created during remote debugging on Debian Sid + KDE Plasma + Fcitx5"
platform:
distribution: Debian Sid
desktop: KDE Plasma 6.7.4
session: Wayland
input_method_framework: Fcitx5
fcitx:
dbus_service: org.fcitx.Fcitx5
controller_path: /controller
controller_interface: org.fcitx.Fcitx.Controller1
read_group_method: CurrentInputMethodGroup
switch_group_method: SwitchInputMethodGroup
hotkey: Super+Space
group_mapping:
"默认":
display: "中"
icon: fcitx-group-default.svg
"english only":
display: "EN"
icon: fcitx-group-english.svg
event_strategy:
normal_update: event_driven
event_source: "Fcitx official StatusNotifierItem NewIcon"
reason: "Fcitx emits NewIcon after its internal InputMethodGroupChanged event"
measured_end_to_end_latency_ms: 20.8
fallback_sync_seconds: 10
dependencies:
- python3-dbus
- python3-gi
- gir1.2-gtk-3.0
- gir1.2-ayatanaappindicator3-0.1
files:
indicator: "~/.local/share/fcitx-group-indicator/indicator.py"
icon_theme: "~/.local/share/fcitx-group-indicator/icons/index.theme"
default_icon: "~/.local/share/fcitx-group-indicator/icons/scalable/apps/fcitx-group-default.svg"
english_icon: "~/.local/share/fcitx-group-indicator/icons/scalable/apps/fcitx-group-english.svg"
systemd_unit: "~/.config/systemd/user/fcitx-group-indicator.service"
systemd:
scope: user
wanted_by: graphical-session.target
part_of: graphical-session.target
restart: on-failure
restart_sec: 2
enable_command: "systemctl --user enable --now fcitx-group-indicator.service"
verification:
- "gdbus call --session --dest org.fcitx.Fcitx5 --object-path /controller --method org.fcitx.Fcitx.Controller1.CurrentInputMethodGroup"
- "systemctl --user is-enabled fcitx-group-indicator.service"
- "systemctl --user is-active fcitx-group-indicator.service"
ai_instructions:
- "Read the complete indicator.py, SVG, and systemd unit in the article; do not omit code."
- "If the user's group names differ, query InputMethodGroups and CurrentInputMethodGroup before changing group_mapping."
- "Prefer to keep the event-driven logic; do not replace it with frequent polling."
- "Do not replace this implementation with a Plasma plasmoid unless compatibility with the user's Plasma version has been verified."
- "Back up existing files before modifying them, then verify the systemd user service and Fcitx D-Bus state."
To ask another AI to install it, provide the full Markdown file and a prompt such as:
Read this article. Follow its machine-readable reproduction checklist and complete code to recreate the same group indicator in my KDE Plasma + Fcitx5 environment. First query my actual Fcitx group names; do not assume they are “默认” and “english only”. Back up any existing files before changing them, then verify the systemd user service and Fcitx D-Bus state.
How this article was produced
This article was written with OpenAI GPT-5.6 Sol in ChatGPT, based on a real remote setup and debugging session on Debian Sid + KDE Plasma + Fcitx5. The final code shown here came from the configuration actually running on my machine, not from a mock-up.
The AI helped investigate options, write and iterate on the indicator, verify the local D-Bus and StatusNotifier behavior, measure the update latency, and document the reproducible setup. The author remains Floppy Liu, as shown in the site’s front matter.
The machine-readable checklist is included so another AI tool can inspect the reader’s own group names and environment before safely reproducing or adapting the setup.