File indexing completed on 2024-05-12 05:35:43

0001 /*
0002     SPDX-FileCopyrightText: 2010 Andriy Rysin <rysin@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "layout_memory.h"
0008 #include "debug.h"
0009 
0010 #include <KWindowInfo>
0011 #include <KWindowSystem>
0012 #include <KX11Extras>
0013 
0014 #include "xkb_helper.h"
0015 
0016 LayoutMemory::LayoutMemory(const KeyboardConfig &keyboardConfig_)
0017     : prevLayoutList(X11Helper::getLayoutsList())
0018     , keyboardConfig(keyboardConfig_)
0019 {
0020     registerListeners();
0021 }
0022 
0023 LayoutMemory::~LayoutMemory()
0024 {
0025     unregisterListeners();
0026 }
0027 
0028 void LayoutMemory::configChanged()
0029 {
0030     //  this->layoutMap.clear();    // if needed this will be done on layoutMapChanged event
0031     unregisterListeners();
0032     registerListeners();
0033 }
0034 
0035 void LayoutMemory::registerListeners()
0036 {
0037     if (keyboardConfig.switchingPolicy() == KeyboardConfig::SWITCH_POLICY_WINDOW || keyboardConfig.switchingPolicy() == KeyboardConfig::SWITCH_POLICY_APPLICATION) {
0038         connect(KX11Extras::self(), &KX11Extras::activeWindowChanged, this, &LayoutMemory::windowChanged);
0039         //      connect(KWindowSystem::self(), SIGNAL(windowRemoved(WId)), this, SLOT(windowRemoved(WId)));
0040     }
0041     if (keyboardConfig.switchingPolicy() == KeyboardConfig::SWITCH_POLICY_DESKTOP) {
0042         connect(KX11Extras::self(), &KX11Extras::currentDesktopChanged, this, &LayoutMemory::desktopChanged);
0043     }
0044 }
0045 
0046 void LayoutMemory::unregisterListeners()
0047 {
0048     disconnect(KX11Extras::self(), &KX11Extras::activeWindowChanged, this, &LayoutMemory::windowChanged);
0049     disconnect(KX11Extras::self(), &KX11Extras::currentDesktopChanged, this, &LayoutMemory::desktopChanged);
0050     //  disconnect(KWindowSystem::self(), SIGNAL(windowRemoved(WId)), this, SLOT(windowRemoved(WId)));
0051 }
0052 
0053 QString LayoutMemory::getCurrentMapKey()
0054 {
0055     switch (keyboardConfig.switchingPolicy()) {
0056     case KeyboardConfig::SWITCH_POLICY_WINDOW: {
0057         WId wid = KX11Extras::self()->activeWindow();
0058         KWindowInfo winInfo(wid, NET::WMWindowType);
0059         NET::WindowType windowType = winInfo.windowType(NET::NormalMask | NET::DesktopMask | NET::DialogMask);
0060         qCDebug(KCM_KEYBOARD, ) << "window type" << windowType;
0061 
0062         // we ignore desktop type so that our keyboard layout applet on desktop could change layout properly
0063         if (windowType == NET::Desktop)
0064             return previousLayoutMapKey;
0065         if (windowType != NET::Unknown && windowType != NET::Normal && windowType != NET::Dialog)
0066             return QString();
0067 
0068         return QString::number(wid);
0069     }
0070     case KeyboardConfig::SWITCH_POLICY_APPLICATION: {
0071         WId wid = KX11Extras::self()->activeWindow();
0072         KWindowInfo winInfo(wid, NET::WMWindowType, NET::WM2WindowClass);
0073         NET::WindowType windowType = winInfo.windowType(NET::NormalMask | NET::DesktopMask | NET::DialogMask);
0074         qCDebug(KCM_KEYBOARD, ) << "window type" << windowType;
0075 
0076         // we ignore desktop type so that our keyboard layout applet on desktop could change layout properly
0077         if (windowType == NET::Desktop)
0078             return previousLayoutMapKey;
0079         if (windowType != NET::Unknown && windowType != NET::Normal && windowType != NET::Dialog)
0080             return QString();
0081 
0082         // shall we use pid or window class ??? - class seems better (see e.g. https://bugs.kde.org/show_bug.cgi?id=245507)
0083         // for window class shall we use class.class or class.name? (seem class.class is a bit better - more app-oriented)
0084         qCDebug(KCM_KEYBOARD, ) << "New active window with class.class: " << winInfo.windowClassClass();
0085         return QString(winInfo.windowClassClass());
0086         //      NETWinInfo winInfoForPid( QX11Info::display(), wid, QX11Info::appRootWindow(), NET::WMPid);
0087         //      return QString::number(winInfoForPid.pid());
0088     }
0089     case KeyboardConfig::SWITCH_POLICY_DESKTOP:
0090         return QString::number(KX11Extras::self()->currentDesktop());
0091     default:
0092         return QString();
0093     }
0094 }
0095 
0096 static bool isExtraSubset(const QList<LayoutUnit> &allLayouts, const QList<LayoutUnit> &newList)
0097 {
0098     if (allLayouts.isEmpty() || newList.isEmpty() || allLayouts.first() != newList.first())
0099         return false;
0100     for (const LayoutUnit &layoutUnit : newList) {
0101         if (!allLayouts.contains(layoutUnit))
0102             return false;
0103     }
0104     return true;
0105 }
0106 
0107 void LayoutMemory::layoutMapChanged()
0108 {
0109     QList<LayoutUnit> newLayoutList(X11Helper::getLayoutsList());
0110 
0111     if (prevLayoutList == newLayoutList)
0112         return;
0113 
0114     qCDebug(KCM_KEYBOARD, ) << "Layout map change: " << LayoutSet::toString(prevLayoutList) << "-->" << LayoutSet::toString(newLayoutList);
0115     prevLayoutList = newLayoutList;
0116 
0117     // TODO: need more thinking here on how to support external map resetting
0118     if (keyboardConfig.configureLayouts() && isExtraSubset(keyboardConfig.layouts, newLayoutList)) {
0119         qCDebug(KCM_KEYBOARD, ) << "Layout map change for extra layout";
0120         layoutChanged(); // to remember new map for active "window"
0121     } else {
0122         if (newLayoutList != keyboardConfig.getDefaultLayouts()) {
0123             qCDebug(KCM_KEYBOARD, ) << "Layout map change from external source: clearing layout memory";
0124             layoutMap.clear();
0125         }
0126     }
0127 }
0128 
0129 void LayoutMemory::layoutChanged()
0130 {
0131     QString layoutMapKey = getCurrentMapKey();
0132     if (layoutMapKey.isEmpty())
0133         return;
0134 
0135     layoutMap[layoutMapKey] = X11Helper::getCurrentLayouts();
0136 }
0137 
0138 void LayoutMemory::setCurrentLayoutFromMap()
0139 {
0140     QString layoutMapKey = getCurrentMapKey();
0141     if (layoutMapKey.isEmpty())
0142         return;
0143 
0144     if (!layoutMap.contains(layoutMapKey)) {
0145         //      qCDebug(KCM_KEYBOARD, ) << "new key for layout map" << layoutMapKey;
0146 
0147         if (!X11Helper::isDefaultLayout()) {
0148             //          qCDebug(KCM_KEYBOARD, ) << "setting default layout for container key" << layoutMapKey;
0149             if (keyboardConfig.configureLayouts() && X11Helper::getLayoutsList() != keyboardConfig.getDefaultLayouts()) {
0150                 XkbHelper::initializeKeyboardLayouts(keyboardConfig.getDefaultLayouts());
0151             }
0152             X11Helper::setDefaultLayout();
0153         }
0154     } else {
0155         LayoutSet layoutFromMap = layoutMap[layoutMapKey];
0156         qCDebug(KCM_KEYBOARD, ) << "Setting layout map item" << layoutFromMap.currentLayout.toString() << "for container key" << layoutMapKey;
0157 
0158         LayoutSet currentLayouts = X11Helper::getCurrentLayouts();
0159         if (layoutFromMap.layouts != currentLayouts.layouts) {
0160             if (keyboardConfig.configureLayouts()) {
0161                 XkbHelper::initializeKeyboardLayouts(layoutFromMap.layouts);
0162             }
0163             X11Helper::setLayout(layoutFromMap.currentLayout);
0164         } else if (layoutFromMap.currentLayout != currentLayouts.currentLayout) {
0165             X11Helper::setLayout(layoutFromMap.currentLayout);
0166         }
0167     }
0168 
0169     previousLayoutMapKey = layoutMapKey;
0170 }
0171 
0172 void LayoutMemory::windowChanged(WId /*wId*/)
0173 {
0174     setCurrentLayoutFromMap();
0175 }
0176 
0177 void LayoutMemory::desktopChanged(int /*desktop*/)
0178 {
0179     setCurrentLayoutFromMap();
0180 }