1 #include "controllers/keyboard/keyboardeventfilter.h"
2 
3 #include <QEvent>
4 #include <QKeyEvent>
5 #include <QList>
6 #include <QtDebug>
7 
8 #include "control/controlobject.h"
9 #include "moc_keyboardeventfilter.cpp"
10 #include "util/cmdlineargs.h"
11 
KeyboardEventFilter(ConfigObject<ConfigValueKbd> * pKbdConfigObject,QObject * parent,const char * name)12 KeyboardEventFilter::KeyboardEventFilter(ConfigObject<ConfigValueKbd>* pKbdConfigObject,
13         QObject* parent,
14         const char* name)
15         : QObject(parent),
16           m_pKbdConfigObject(nullptr) {
17     setObjectName(name);
18     setKeyboardConfig(pKbdConfigObject);
19 }
20 
~KeyboardEventFilter()21 KeyboardEventFilter::~KeyboardEventFilter() {
22 }
23 
eventFilter(QObject *,QEvent * e)24 bool KeyboardEventFilter::eventFilter(QObject*, QEvent* e) {
25     if (e->type() == QEvent::FocusOut) {
26         // If we lose focus, we need to clear out the active key list
27         // because we might not get Key Release events.
28         m_qActiveKeyList.clear();
29     } else if (e->type() == QEvent::KeyPress) {
30         QKeyEvent* ke = (QKeyEvent *)e;
31 
32 #ifdef __APPLE__
33         // On Mac OSX the nativeScanCode is empty (const 1) http://doc.qt.nokia.com/4.7/qkeyevent.html#nativeScanCode
34         // We may loose the release event if a the shift key is pressed later
35         // and there is character shift like "1" -> "!"
36         int keyId = ke->key();
37 #else
38         int keyId = ke->nativeScanCode();
39 #endif
40         //qDebug() << "KeyPress event =" << ke->key() << "KeyId =" << keyId;
41 
42         // Run through list of active keys to see if the pressed key is already active
43         // Just for returning true if we are consuming this key event
44 
45         foreach (const KeyDownInformation& keyDownInfo, m_qActiveKeyList) {
46             if (keyDownInfo.keyId == keyId) {
47                 return true;
48             }
49         }
50 
51         QKeySequence ks = getKeySeq(ke);
52         if (!ks.isEmpty()) {
53             ConfigValueKbd ksv(ks);
54             // Check if a shortcut is defined
55             bool result = false;
56             // using const_iterator here is faster than QMultiHash::values()
57             for (auto it = m_keySequenceToControlHash.constFind(ksv);
58                  it != m_keySequenceToControlHash.constEnd() && it.key() == ksv; ++it) {
59                 const ConfigKey& configKey = it.value();
60                 if (configKey.group != "[KeyboardShortcuts]") {
61                     ControlObject* control = ControlObject::getControl(configKey);
62                     if (control) {
63                         //qDebug() << configKey << "MIDI_NOTE_ON" << 1;
64                         // Add key to active key list
65                         m_qActiveKeyList.append(KeyDownInformation(
66                             keyId, ke->modifiers(), control));
67                         // Since setting the value might cause us to go down
68                         // a route that would eventually clear the active
69                         // key list, do that last.
70                         control->setValueFromMidi(MIDI_NOTE_ON, 1);
71                         result = true;
72                     } else {
73                         qDebug() << "Warning: Keyboard key is configured for nonexistent control:"
74                                  << configKey.group << configKey.item;
75                     }
76                 }
77             }
78             return result;
79         }
80     } else if (e->type()==QEvent::KeyRelease) {
81         QKeyEvent* ke = (QKeyEvent*)e;
82 
83 #ifdef __APPLE__
84         // On Mac OSX the nativeScanCode is empty
85         int keyId = ke->key();
86 #else
87         int keyId = ke->nativeScanCode();
88 #endif
89         bool autoRepeat = ke->isAutoRepeat();
90 
91         //qDebug() << "KeyRelease event =" << ke->key() << "AutoRepeat =" << autoRepeat << "KeyId =" << keyId;
92 
93         int clearModifiers = 0;
94 #ifdef __APPLE__
95         // OS X apparently doesn't deliver KeyRelease events when you are
96         // holding Ctrl. So release all key-presses that were triggered with
97         // Ctrl.
98         if (ke->key() == Qt::Key_Control) {
99             clearModifiers = Qt::ControlModifier;
100         }
101 #endif
102 
103         bool matched = false;
104         // Run through list of active keys to see if the released key is active
105         for (int i = m_qActiveKeyList.size() - 1; i >= 0; i--) {
106             const KeyDownInformation& keyDownInfo = m_qActiveKeyList[i];
107             ControlObject* pControl = keyDownInfo.pControl;
108             if (keyDownInfo.keyId == keyId ||
109                     (clearModifiers > 0 && keyDownInfo.modifiers == clearModifiers)) {
110                 if (!autoRepeat) {
111                     //qDebug() << pControl->getKey() << "MIDI_NOTE_OFF" << 0;
112                     pControl->setValueFromMidi(MIDI_NOTE_OFF, 0);
113                     m_qActiveKeyList.removeAt(i);
114                 }
115                 // Due to the modifier clearing workaround we might match multiple keys for
116                 // release.
117                 matched = true;
118             }
119         }
120         return matched;
121     } else if (e->type() == QEvent::KeyboardLayoutChange) {
122         // This event is not fired on ubunty natty, why?
123         // TODO(XXX): find a way to support KeyboardLayoutChange Bug #997811
124         //qDebug() << "QEvent::KeyboardLayoutChange";
125     }
126     return false;
127 }
128 
getKeySeq(QKeyEvent * e)129 QKeySequence KeyboardEventFilter::getKeySeq(QKeyEvent* e) {
130     QString modseq;
131     QKeySequence k;
132 
133     // TODO(XXX) check if we may simply return QKeySequence(e->modifiers()+e->key())
134 
135     if (e->modifiers() & Qt::ShiftModifier) {
136         modseq += "Shift+";
137     }
138 
139     if (e->modifiers() & Qt::ControlModifier) {
140         modseq += "Ctrl+";
141     }
142 
143     if (e->modifiers() & Qt::AltModifier) {
144         modseq += "Alt+";
145     }
146 
147     if (e->modifiers() & Qt::MetaModifier) {
148         modseq += "Meta+";
149     }
150 
151     if (e->key() >= 0x01000020 && e->key() <= 0x01000023) {
152         // Do not act on Modifier only
153         // avoid returning "khmer vowel sign ie (U+17C0)"
154         return k;
155     }
156 
157     QString keyseq = QKeySequence(e->key()).toString();
158     k = QKeySequence(modseq + keyseq);
159 
160     if (CmdlineArgs::Instance().getDeveloper()) {
161         qDebug() << "keyboard press: " << k.toString();
162     }
163     return k;
164 }
165 
setKeyboardConfig(ConfigObject<ConfigValueKbd> * pKbdConfigObject)166 void KeyboardEventFilter::setKeyboardConfig(ConfigObject<ConfigValueKbd>* pKbdConfigObject) {
167     // Keyboard configs are a surjection from ConfigKey to key sequence. We
168     // invert the mapping to create an injection from key sequence to
169     // ConfigKey. This allows a key sequence to trigger multiple controls in
170     // Mixxx.
171     m_keySequenceToControlHash = pKbdConfigObject->transpose();
172     m_pKbdConfigObject = pKbdConfigObject;
173 }
174 
getKeyboardConfig()175 ConfigObject<ConfigValueKbd>* KeyboardEventFilter::getKeyboardConfig() {
176     return m_pKbdConfigObject;
177 }
178