File indexing completed on 2024-05-12 17:07:16

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