Skip to content

Commit 7d15ad6

Browse files
committed
fix: sync keyboard layout between DCC and fcitx5
Add bidirectional synchronization between DCC keyboard layout settings and fcitx5 input method configuration. 添加DCC键盘布局与fcitx5输入法的双向同步功能。 DCC切换布局时自动同步到fcitx5(添加/排序/设置当前), fcitx5输入法列表变化时同步第一个键盘布局到DCC。 Log: 添加DCC键盘布局与fcitx5输入法双向同步 PMS: BUG-357551 Influence: DCC键盘布局设置与fcitx5输入法配置保持一致, 切换/添加/删除键盘布局时自动同步,支持带变体的键盘布局。
1 parent 5261658 commit 7d15ad6

6 files changed

Lines changed: 173 additions & 8 deletions

File tree

src/dcc-fcitx5configtool/keyboard-layout/operation/keyboardcontroller.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
1+
//SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
22
//
33
//SPDX-License-Identifier: GPL-3.0-or-later
44

@@ -161,6 +161,11 @@ bool KeyboardController::userLayoutsContains(const QString &key)
161161
return userLayouts().contains(key);
162162
}
163163

164+
bool KeyboardController::allLayoutsContains(const QString &key) const
165+
{
166+
return m_model->allLayout().contains(key);
167+
}
168+
164169
QSortFilterProxyModel *KeyboardController::layoutSearchModel()
165170
{
166171
if (m_layoutSearchModel)
@@ -311,7 +316,11 @@ void KeyboardController::addUserLayout(const QString &layout)
311316

312317
void KeyboardController::deleteUserLayout(const QString &layout)
313318
{
319+
const auto &userLayout = m_model->userLayout();
320+
QString key = userLayout.key(layout, layout);
321+
bool isCur = (key == currentLayout());
314322
m_worker->delUserLayout(layout);
323+
Q_EMIT layoutDeleted(key, isCur);
315324
}
316325

317326
int KeyboardController::layoutCount() const
@@ -330,6 +339,7 @@ void KeyboardController::setCurrentLayout(const QString &key)
330339
return;
331340

332341
m_worker->setLayout(key);
342+
Q_EMIT currentLayoutSet(key);
333343
}
334344

335345
QString KeyboardController::conflictText() const

src/dcc-fcitx5configtool/keyboard-layout/operation/keyboardcontroller.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
1+
//SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
22
//
33
//SPDX-License-Identifier: GPL-3.0-or-later
44

@@ -58,6 +58,7 @@ public Q_SLOTS:
5858
QMap<QString, QString> userLayouts() const;
5959
QString userLayoutAt(int index, bool isValue = true) const;
6060
bool userLayoutsContains(const QString &key);
61+
bool allLayoutsContains(const QString &key) const;
6162
QSortFilterProxyModel *layoutSearchModel();
6263
QSortFilterProxyModel *shortcutSearchModel();
6364

@@ -95,6 +96,9 @@ public Q_SLOTS:
9596
void keyDone(const QString &accels);
9697
void keyEvent(const QString &accels);
9798

99+
void layoutDeleted(const QString &key, bool isCurrentLayout);
100+
void currentLayoutSet(const QString &key);
101+
98102
void conflictTextChanged();
99103
void keyboardEnabledChanged();
100104

src/dcc-fcitx5configtool/keyboard-layout/operation/keyboardmodel.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//SPDX-FileCopyrightText: 2018 - 2023 UnionTech Software Technology Co., Ltd.
1+
//SPDX-FileCopyrightText: 2018 - 2026 UnionTech Software Technology Co., Ltd.
22
//
33
//SPDX-License-Identifier: GPL-3.0-or-later
44

@@ -202,7 +202,7 @@ QString KeyboardModel::curLang() const
202202
return langByKey(m_currentLangKey);
203203
}
204204

205-
QMap<QString, QString> KeyboardModel::userLayout() const
205+
const QMap<QString, QString> &KeyboardModel::userLayout() const
206206
{
207207
return m_userLayout;
208208
}

src/dcc-fcitx5configtool/keyboard-layout/operation/keyboardmodel.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//SPDX-FileCopyrightText: 2018 - 2023 UnionTech Software Technology Co., Ltd.
1+
//SPDX-FileCopyrightText: 2018 - 2026 UnionTech Software Technology Co., Ltd.
22
//
33
//SPDX-License-Identifier: GPL-3.0-or-later
44

@@ -32,9 +32,9 @@ class KeyboardModel : public QObject
3232

3333
QString curLayout() const;
3434
QString curLang() const;
35-
QMap<QString, QString> userLayout() const;
35+
const QMap<QString, QString> &userLayout() const;
3636
QMap<QString, QString> kbLayout() const;
37-
QMap<QString, QString> allLayout() const { return m_allLayouts; }
37+
const QMap<QString, QString> &allLayout() const { return m_allLayouts; }
3838
QStringList localLang() const;
3939
QList<MetaData> langLists() const;
4040
bool capsLock() const;

src/dcc-fcitx5configtool/operation/fcitx5configtool.cpp

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,39 @@ Q_LOGGING_CATEGORY(configTool, "fcitx5.configtool.main")
3030
namespace deepin {
3131
namespace fcitx5configtool {
3232

33+
static QString dccLayoutToFcitxIMKey(const QString &key) {
34+
// DCC: "fr;" or "fr;bepo" -> fcitx5: "keyboard-fr" or "keyboard-fr-bepo"
35+
QString stripped = key;
36+
stripped.replace(';', '-');
37+
while (stripped.endsWith('-')) stripped.chop(1);
38+
return QStringLiteral("keyboard-") + stripped;
39+
}
40+
static QString fcitxIMKeyToDccLayout(const QString &imKey) {
41+
const QString prefix = QStringLiteral("keyboard-");
42+
if (!imKey.startsWith(prefix)) return QString();
43+
// fcitx5: "keyboard-fr-bepo" -> DCC: "fr;bepo"
44+
QString stripped = imKey.mid(prefix.length());
45+
int dashPos = stripped.indexOf('-');
46+
if (dashPos < 0) {
47+
return stripped + ';';
48+
}
49+
return stripped.left(dashPos) + ';' + stripped.mid(dashPos + 1);
50+
}
51+
52+
static int indexOfEntry(const fcitx::FcitxQtStringKeyValueList &entries, const QString &key) {
53+
for (int i = 0; i < entries.size(); ++i)
54+
if (entries[i].key() == key) return i;
55+
return -1;
56+
}
57+
58+
struct SyncGuard {
59+
bool &flag;
60+
SyncGuard(bool &f) : flag(f) { flag = true; }
61+
~SyncGuard() { flag = false; }
62+
SyncGuard(const SyncGuard &) = delete;
63+
SyncGuard &operator=(const SyncGuard &) = delete;
64+
};
65+
3366
static QString kFcitxConfigGlobalPath = "fcitx://config/global";
3467
#ifdef BUILD_UOS
3568
static const QString kSogouAddonUniqueName = "com.sogou.ime.ng.fcitx5.uos-addon";
@@ -70,6 +103,10 @@ void Fcitx5ConfigToolWorkerPrivate::initConnect()
70103
if (avail) {
71104
configProxy->requestConfig(false);
72105
addonsProxy->load();
106+
connect(dbusProvider->controller(), &fcitx::FcitxQtControllerProxy::InputMethodGroupsChanged,
107+
this, [this]() {
108+
if (!m_syncInProgress) imConfig->load();
109+
}, Qt::UniqueConnection);
73110
} else {
74111
configProxy->clear();
75112
addonsProxy->clear();
@@ -94,6 +131,25 @@ void Fcitx5ConfigToolWorkerPrivate::initConnect()
94131

95132
connect(configProxy, &Fcitx5ConfigProxy::requestConfigFinished, q, &Fcitx5ConfigToolWorker::requestConfigFinished);
96133
connect(addonsProxy, &Fcitx5AddonsProxy::requestAddonsFinished, q, &Fcitx5ConfigToolWorker::requestAddonsFinished);
134+
135+
connect(keyboardController, &dccV25::KeyboardController::currentLayoutSet, this,
136+
[this](const QString &key) {
137+
if (!m_syncInProgress) syncCurrentLayoutToFcitx5(key);
138+
});
139+
140+
connect(keyboardController, &dccV25::KeyboardController::layoutDeleted, this,
141+
[this](const QString &key, bool isCurrent) {
142+
if (!m_syncInProgress && isCurrent) syncLayoutDeletedFromFcitx5(key);
143+
});
144+
145+
connect(imConfig, &fcitx::kcm::IMConfig::imListChanged, this, [this]() {
146+
if (!m_syncInProgress) syncFcitx5FirstKeyboardToDCC();
147+
if (!m_pendingLayoutKey.isEmpty()) {
148+
QString key = m_pendingLayoutKey;
149+
syncCurrentLayoutToFcitx5(key);
150+
}
151+
});
152+
97153
configProxy->requestConfig(false);
98154
addonsProxy->load();
99155
qCDebug(configTool) << "Exiting Fcitx5ConfigToolWorkerPrivate::initConnect";
@@ -244,6 +300,95 @@ fcitx::kcm::IMProxyModel *Fcitx5ConfigToolWorker::imProxyModel() const
244300
return d->imConfig->availIMModel();
245301
}
246302

303+
void Fcitx5ConfigToolWorkerPrivate::syncCurrentLayoutToFcitx5(const QString &layoutKey)
304+
{
305+
if (!dbusProvider || !dbusProvider->controller()) {
306+
qCDebug(configTool) << "syncCurrentLayoutToFcitx5: controller not available, pending:" << layoutKey;
307+
m_pendingLayoutKey = layoutKey;
308+
return;
309+
}
310+
311+
QString fcitxKey = dccLayoutToFcitxIMKey(layoutKey);
312+
313+
const auto &entries = imConfig->imEntries();
314+
int existIndex = indexOfEntry(entries, fcitxKey);
315+
316+
SyncGuard guard(m_syncInProgress);
317+
m_pendingLayoutKey.clear();
318+
319+
if (existIndex > 0) {
320+
qCDebug(configTool) << "syncCurrentLayoutToFcitx5: move to front:" << fcitxKey;
321+
auto updatedEntries = entries;
322+
updatedEntries.move(existIndex, 0);
323+
imConfig->setIMEntries(updatedEntries);
324+
imConfig->emitChanged();
325+
imConfig->save();
326+
} else if (existIndex < 0) {
327+
qCDebug(configTool) << "syncCurrentLayoutToFcitx5: prepend:" << fcitxKey;
328+
auto updatedEntries = entries;
329+
fcitx::FcitxQtStringKeyValue newEntry;
330+
newEntry.setKey(fcitxKey);
331+
updatedEntries.prepend(newEntry);
332+
imConfig->setIMEntries(updatedEntries);
333+
imConfig->emitChanged();
334+
imConfig->save();
335+
}
336+
337+
dbusProvider->controller()->SetCurrentIM(fcitxKey);
338+
}
339+
340+
void Fcitx5ConfigToolWorkerPrivate::syncLayoutDeletedFromFcitx5(const QString &layoutKey)
341+
{
342+
if (!dbusProvider || !dbusProvider->controller()) return;
343+
344+
QString fcitxKey = dccLayoutToFcitxIMKey(layoutKey);
345+
const auto &entries = imConfig->imEntries();
346+
int removedIndex = indexOfEntry(entries, fcitxKey);
347+
if (removedIndex < 0) return;
348+
349+
qCDebug(configTool) << "syncLayoutDeletedFromFcitx5:" << fcitxKey;
350+
SyncGuard guard(m_syncInProgress);
351+
auto updatedEntries = entries;
352+
updatedEntries.removeAt(removedIndex);
353+
imConfig->setIMEntries(updatedEntries);
354+
imConfig->emitChanged();
355+
imConfig->save();
356+
357+
if (!updatedEntries.isEmpty()) {
358+
int nextIndex = (removedIndex < updatedEntries.size()) ? removedIndex : 0;
359+
dbusProvider->controller()->SetCurrentIM(updatedEntries[nextIndex].key());
360+
}
361+
}
362+
363+
void Fcitx5ConfigToolWorkerPrivate::syncFcitx5FirstKeyboardToDCC()
364+
{
365+
if (!keyboardController) return;
366+
367+
const auto &entries = imConfig->imEntries();
368+
QString firstKeyboardLayout;
369+
for (const auto &entry : entries) {
370+
QString dccKey = fcitxIMKeyToDccLayout(entry.key());
371+
if (!dccKey.isEmpty()) {
372+
firstKeyboardLayout = dccKey;
373+
break;
374+
}
375+
}
376+
377+
if (firstKeyboardLayout.isEmpty()) return;
378+
379+
if (!keyboardController->allLayoutsContains(firstKeyboardLayout)) {
380+
qCDebug(configTool) << "syncFcitx5FirstKeyboardToDCC: layout not in DCC:" << firstKeyboardLayout;
381+
return;
382+
}
383+
384+
qCDebug(configTool) << "syncFcitx5FirstKeyboardToDCC:" << firstKeyboardLayout;
385+
SyncGuard guard(m_syncInProgress);
386+
if (!keyboardController->userLayoutsContains(firstKeyboardLayout)) {
387+
keyboardController->addUserLayout(firstKeyboardLayout);
388+
}
389+
keyboardController->setCurrentLayout(firstKeyboardLayout);
390+
}
391+
247392
DCC_FACTORY_CLASS(Fcitx5ConfigToolWorker)
248393
} // namespace fcitx5configtool
249394
} // namespace deepin

src/dcc-fcitx5configtool/operation/private/fcitx5configtool_p.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: 2024 - 2027 UnionTech Software Technology Co., Ltd.
1+
// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
22
//
33
// SPDX-License-Identifier: GPL-3.0-or-later
44
#ifndef FCITX5CONFIGTOOL_P_H
@@ -36,7 +36,13 @@ class Fcitx5ConfigToolWorkerPrivate : public QObject
3636
IMListModel *imListModel { nullptr };
3737
dccV25::KeyboardController *keyboardController { nullptr };
3838

39+
bool m_syncInProgress { false };
40+
QString m_pendingLayoutKey;
41+
3942
private:
43+
void syncCurrentLayoutToFcitx5(const QString &layoutKey);
44+
void syncLayoutDeletedFromFcitx5(const QString &layoutKey);
45+
void syncFcitx5FirstKeyboardToDCC();
4046
void init();
4147
void initConnect();
4248

0 commit comments

Comments
 (0)