diff options
9 files changed, 318 insertions, 10 deletions
diff --git a/src-qt5/core/libLumina/DesktopSettings.cpp b/src-qt5/core/libLumina/DesktopSettings.cpp index bfd149ca..cd85df5e 100644 --- a/src-qt5/core/libLumina/DesktopSettings.cpp +++ b/src-qt5/core/libLumina/DesktopSettings.cpp @@ -164,7 +164,7 @@ void DesktopSettings::locateFiles(){ //run at start - finds the locations of the various files (based on RunMode) QString userdir; QStringList systemdirs; - userdir = QString(getenv("XDG_CONFIG_HOME"))+"/lumina-desktop"; + userdir = QString(getenv("XDG_CONFIG_HOME")); systemdirs << QString(getenv("XDG_CONFIG_DIRS")).split(":") << QString(getenv("XDG_DATA_DIRS")).split(":"); //Load all the user-level files for this run mode QList< DesktopSettings::File > tmp; diff --git a/src-qt5/core/lumina-desktop-unified/LSession.cpp b/src-qt5/core/lumina-desktop-unified/LSession.cpp index 57b30a2e..156029ee 100644 --- a/src-qt5/core/lumina-desktop-unified/LSession.cpp +++ b/src-qt5/core/lumina-desktop-unified/LSession.cpp @@ -20,6 +20,7 @@ DesktopSettings* Lumina::SETTINGS = 0; QThread* Lumina::EVThread = 0; RootWindow* Lumina::ROOTWIN = 0; XDGDesktopList* Lumina::APPLIST = 0; +LShortcutEvents* Lumina::SHORTCUTS = 0; LSession::LSession(int &argc, char ** argv) : LSingleApplication(argc, argv, "lumina-desktop"){ //Initialize the global objects to null pointers @@ -48,6 +49,7 @@ LSession::LSession(int &argc, char ** argv) : LSingleApplication(argc, argv, "lu Lumina::EVThread->start(); Lumina::ROOTWIN = new RootWindow(); Lumina::APPLIST = new XDGDesktopList(0, true); //keep this list up to date + Lumina::SHORTCUTS = new LShortcutEvents(); //this can be moved to it's own thread eventually as well //Setup the various connections between the global classes connect(Lumina::ROOTWIN, SIGNAL(RegisterVirtualRoot(WId)), Lumina::EFILTER, SLOT(RegisterVirtualRoot(WId)) ); @@ -160,6 +162,7 @@ void LSession::setupSession(){ Lumina::SS->start(); if(DEBUG){ qDebug() << " - Init Finished:" << timer->elapsed(); delete timer;} + Lumina::SHORTCUTS->start(); //Startup the shortcut handler now //for(int i=0; i<4; i++){ LSession::processEvents(); } //Again, just a few event loops here so thing can settle before we close the splash screen //launchStartupApps(); //QTimer::singleShot(500, this, SLOT(launchStartupApps()) ); diff --git a/src-qt5/core/lumina-desktop-unified/global-includes.h b/src-qt5/core/lumina-desktop-unified/global-includes.h index 04c4c27c..ab810ada 100644 --- a/src-qt5/core/lumina-desktop-unified/global-includes.h +++ b/src-qt5/core/lumina-desktop-unified/global-includes.h @@ -64,6 +64,7 @@ namespace Lumina{ //Flags/enumerations enum WindowAction{MoveResize, Show, Hide, TryClose, Closed, WA_NONE}; + enum MouseButton{NoButton, LeftButton, RightButton, MidButton, BackButton, ForwardButton, TaskButton, WheelUp, WheelDown, WheelLeft, WheelRight}; }; #endif diff --git a/src-qt5/core/lumina-desktop-unified/global-objects.h b/src-qt5/core/lumina-desktop-unified/global-objects.h index 66bfd122..2f298e27 100644 --- a/src-qt5/core/lumina-desktop-unified/global-objects.h +++ b/src-qt5/core/lumina-desktop-unified/global-objects.h @@ -21,6 +21,7 @@ //#ifndef USE_WAYLAND #include "src-events/LXcbEventFilter.h" //#endif +#include "src-events/LShortcutEvents.h" #include "src-screensaver/LScreenSaver.h" //#include "src-WM/LWindowManager.h" @@ -32,6 +33,7 @@ namespace Lumina{ //Data structures and objects extern EventFilter *EFILTER; //Native Event Watcher + extern LShortcutEvents *SHORTCUTS; //Keyboard/mouse shortcut events extern DesktopSettings *SETTINGS; //All Settings files //ScreenSaver extern LScreenSaver *SS; diff --git a/src-qt5/core/lumina-desktop-unified/src-events/LShortcutEvents.cpp b/src-qt5/core/lumina-desktop-unified/src-events/LShortcutEvents.cpp new file mode 100644 index 00000000..b09a1a5b --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-events/LShortcutEvents.cpp @@ -0,0 +1,185 @@ +//=========================================== +// Lumina desktop source code +// Copyright (c) 2017, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "LShortcutEvents.h" +#include "global-objects.h" + +// === PUBLIC === +LShortcutEvents::LShortcutEvents(){ + WIN = 0; + clearTimer = 0; + evaluated = false; +} + +LShortcutEvents::~LShortcutEvents(){ + +} + +void LShortcutEvents::start(){ + if(clearTimer==0){ + clearTimer = new QTimer(this); + clearTimer->setInterval(2000); //2 seconds + clearTimer->setSingleShot(true); + connect(clearTimer, SIGNAL(timeout()), this, SLOT(clearKeys()) ); + } + //Now connect this object to the global EFILTER object signals + connect(Lumina::EFILTER, SIGNAL(KeyPressed(WId, int)), this, SLOT(KeyPress(WId, int)) ); + connect(Lumina::EFILTER, SIGNAL(KeyReleased(WId, int)), this, SLOT(KeyRelease(WId, int)) ); + connect(Lumina::EFILTER, SIGNAL(MousePressed(WId, Lumina::MouseButton)), this, SLOT(MousePress(WId, Lumina::MouseButton)) ); + connect(Lumina::EFILTER, SIGNAL(MouseReleased(WId, Lumina::MouseButton)), this, SLOT(MouseRelease(WId, Lumina::MouseButton)) ); +} + +void LShortcutEvents::stop(){ + disconnect(Lumina::EFILTER, SIGNAL(KeyPressed(WId, int)), this, SLOT(KeyPress(WId, int)) ); + disconnect(Lumina::EFILTER, SIGNAL(KeyReleased(WId, int)), this, SLOT(KeyRelease(WId, int)) ); + disconnect(Lumina::EFILTER, SIGNAL(MousePressed(WId, Lumina::MouseButton)), this, SLOT(MousePress(WId, Lumina::MouseButton)) ); + disconnect(Lumina::EFILTER, SIGNAL(MouseReleased(WId, Lumina::MouseButton)), this, SLOT(MouseRelease(WId, Lumina::MouseButton)) ); + clearKeys(); +} + +// === PRIVATE === +void LShortcutEvents::CheckKeySequence(WId win){ + //Get the keyboard modifiers + QString shortcut = keylistToString(); + //Now see if there is a match for this shortcut + // "strict" actions (operate even if a non-desktop window is active/focused) + QString action = Lumina::SETTINGS->value(DesktopSettings::Keys, "strict/"+shortcut, "").toString(); + qDebug() << "Strict Action:" << "strict/"+shortcut << action; + if(action.isEmpty() && win==0){ + //"loose" actions (operating on the desktop or root window itself) + action = Lumina::SETTINGS->value(DesktopSettings::Keys, "desktop/"+shortcut, "").toString(); + qDebug() << "Desktop Action:" << "desktop/"+shortcut << action; + } + if(!action.isEmpty()){ + //Found a valid action - go ahead and evaluate it + evaluateShortcutAction(action); + } +} + +void LShortcutEvents::CheckMouseSequence(WId win, Lumina::MouseButton button, bool release){ + if(release && (button == Lumina::WheelUp || button == Lumina::WheelDown || button == Lumina::WheelLeft || button == Lumina::WheelRight)){ + return; //skip mouse release events for wheel actions (always come in pairs of press/release) + }else if(keylist.isEmpty() || button == Lumina::NoButton){ return; } //Never overwrite mouse clicks themselves - just combinations with key presses + //Get the keyboard modifiers + QString shortcut = keylistToString(); + //Add the mouse button to the shortcut + switch(button){ + case Lumina::LeftButton: + shortcut.append("+LeftMouse"); + break; + case Lumina::RightButton: + shortcut.append("+RightMouse"); + break; + case Lumina::MidButton: + shortcut.append("+MiddleMouse"); + break; + case Lumina::BackButton: + shortcut.append("+BackMouse"); + break; + case Lumina::ForwardButton: + shortcut.append("+ForwardMouse"); + break; + case Lumina::TaskButton: + shortcut.append("+TaskMouse"); + break; + case Lumina::WheelUp: + shortcut.append("+WheelUp"); + break; + case Lumina::WheelDown: + shortcut.append("+WheelDown"); + break; + case Lumina::WheelLeft: + shortcut.append("+WheelLeft"); + break; + case Lumina::WheelRight: + shortcut.append("+WheelRight"); + break; + default: + shortcut.clear(); + } + if(shortcut.isEmpty()){ return; } + //Now see if there is a match for this shortcut + // "strict" actions (operate even if a non-desktop window is active/focused) + QString action = Lumina::SETTINGS->value(DesktopSettings::Keys, "strict/"+shortcut, "").toString(); + if(action.isEmpty() && win==0){ + //"loose" actions (operating on the desktop or root window itself) + action = Lumina::SETTINGS->value(DesktopSettings::Keys, "desktop/"+shortcut, "").toString(); + } + if(!action.isEmpty()){ + //Found a valid action - go ahead and evaluate it + evaluateShortcutAction(action); + } +} + +QString LShortcutEvents::keylistToString(){ + QString shortcut; + for(int i=0; i<keylist.length(); i++){ + if(i>0){ shortcut.append("+"); } + shortcut.append( QString::number(keylist[i]) ); + } + /*qDebug() << "KeyList to String:"; + qDebug() << " keys:" << keylist; + qDebug() << " string:" << shortcut;*/ + return shortcut; +} + +void LShortcutEvents::evaluateShortcutAction(QString action){ + qDebug() << "Found Shortcut Action:" << action; + evaluated = true; + if(action.startsWith("Exec=")){ + emit LaunchApplication(action.section("=",1,-1)); + return; + } + //Specific Internal actions + action = action.toLower(); + //Power Options + if(action=="logout"){ emit StartLogout(); } + else if(action=="reboot"){ emit StartReboot(); } + else if(action=="shutdown"){ emit StartShutdown(); } + else if(action=="show_leave_options"){ emit OpenLeaveDialog(); } + +} + +// === PUBLIC SLOTS === +void LShortcutEvents::KeyPress(WId window, int key){ + if(window!=WIN){ keylist.clear(); WIN = window; } + if(!keylist.contains(key)){ + //Put it in the list in ascending order + bool found = false; + for(int i=0; i<keylist.length() && !found; i++){ + if(keylist[i]>key){ keylist.insert(i,key); found = true; } + } + if(!found){ keylist << key; } + evaluated = false; + } + //Evaluate the key sequence only when the first one is released + clearTimer->start(); //will "restart" if already running +} + +void LShortcutEvents::KeyRelease(WId window, int key){ + if(window!=WIN){ keylist.clear(); return; } + if(!evaluated){ CheckKeySequence(WIN); } //run this "before" removing the key from the list + keylist.removeAll(key); + clearTimer->start(); //will "restart" if already running +} + +void LShortcutEvents::MousePress(WId window, Lumina::MouseButton button){ + //We do not provide shortcuts for combinations of mouse buttons - just mouse+keyboard combinations + CheckMouseSequence(window, button, false); + clearTimer->start(); //will "restart" if already running +} + +void LShortcutEvents::MouseRelease(WId window, Lumina::MouseButton button){ + //We do not provide shortcuts for combinations of mouse buttons - just mouse+keyboard combinations + CheckMouseSequence(window, button, true); + clearTimer->start(); //will "restart" if already running +} + +void LShortcutEvents::clearKeys(){ + keylist.clear(); + WIN = 0; + if(clearTimer!=0){ clearTimer->stop(); } +} diff --git a/src-qt5/core/lumina-desktop-unified/src-events/LShortcutEvents.h b/src-qt5/core/lumina-desktop-unified/src-events/LShortcutEvents.h new file mode 100644 index 00000000..d1c3b4e0 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-events/LShortcutEvents.h @@ -0,0 +1,72 @@ +//=========================================== +// Lumina desktop source code +// Copyright (c) 2017, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// Class for managing key presses and sending out signals +// when a shortcut combination is pressed +//=========================================== +#ifndef _LUMINA_KEY_SEQUENCE_DETECTION_H +#define _LUMINA_KEY_SEQUENCE_DETECTION_H + +#include "../global-includes.h" + +class LShortcutEvents : public QObject{ + Q_OBJECT +public: + LShortcutEvents(); + ~LShortcutEvents(); + + void start(); + void stop(); + +private: + QList<int> keylist; //keys currently held down (NOTE: QKeySequence has a max of 4 keys for combinations) + WId WIN; //current window being acted on by the keys + QTimer *clearTimer; //used to clear the internal keylist every once in a while if no events come in. + bool evaluated; + + //Actual check functions + void CheckKeySequence(WId win); + void CheckMouseSequence(WId win, Lumina::MouseButton, bool release); + QString keylistToString(); + void evaluateShortcutAction(QString action); + +public slots: + void KeyPress(WId window, int key); + void KeyRelease(WId window, int key); + void MousePress(WId window, Lumina::MouseButton); + void MouseRelease(WId window, Lumina::MouseButton); + void clearKeys(); + +signals: + // Power Options + void OpenLeaveDialog(); + void StartLogout(); + void StartReboot(); //assumes startUpdates==true + void StartShutdown(); //assumes startUpdates==true + + // Session Options + void ChangeWorkspace(int); // +/- 1 from current + void LockSession(); + + //Active Window Options + void ActiveWindowMoveToWorkspace(int); //number of workspace + void ActiveWindowTakeToWorkspace(int); //number of workspace + void ActiveWindowKill(); + void ActiveWindowClose(); + void ActiveWindowMinMaxToggle(); + void ActiveWindowFullscreenToggle(); + void ActiveWindowStartMove(); + void ActiveWindowStopMove(); + void ActiveWindowStartResize(); + void ActiveWindowStopResize(); + + + //General Utility Launch + void LaunchApplication(QString exec); + +}; + +#endif diff --git a/src-qt5/core/lumina-desktop-unified/src-events/LXcbEventFilter.cpp b/src-qt5/core/lumina-desktop-unified/src-events/LXcbEventFilter.cpp index ced2cadb..04ebd4d3 100644 --- a/src-qt5/core/lumina-desktop-unified/src-events/LXcbEventFilter.cpp +++ b/src-qt5/core/lumina-desktop-unified/src-events/LXcbEventFilter.cpp @@ -21,6 +21,7 @@ #define SYSTEM_TRAY_BEGIN_MESSAGE 1 #define SYSTEM_TRAY_CANCEL_MESSAGE 2 +#include <xcb/xcb_keysyms.h> #define DEBUG 0 @@ -126,22 +127,22 @@ bool XCBEventFilter::nativeEventFilter(const QByteArray &eventType, void *messag case XCB_KEY_PRESS: //This is a keyboard key press qDebug() << "Key Press Event"; - obj->emit NewInputEvent(); stopevent = BlockInputEvent( ((xcb_key_press_event_t *) ev)->root ); //use the main "root" window - not the child widget + if(!stopevent){ obj->emit KeyPressed( InputWindow(((xcb_key_press_event_t *) ev)->root), ((xcb_key_press_event_t *) ev)->detail ); } break; case XCB_KEY_RELEASE: //This is a keyboard key release qDebug() << "Key Release Event"; - obj->emit NewInputEvent(); stopevent = BlockInputEvent( ((xcb_key_release_event_t *) ev)->root ); //use the main "root" window - not the child widget + if(!stopevent){ obj->emit KeyReleased( InputWindow(((xcb_key_release_event_t *) ev)->root), ((xcb_key_release_event_t *) ev)->detail ); } break; case XCB_BUTTON_PRESS: //This is a mouse button press qDebug() << "Button Press Event"; - obj->emit NewInputEvent(); stopevent = BlockInputEvent( ((xcb_button_press_event_t *) ev)->root ); //use the main "root" window - not the child widget if(!stopevent){ //Activate the window right now if needed + obj->emit MousePressed( InputWindow(((xcb_button_press_event_t *) ev)->root), MouseKey(((xcb_key_press_event_t *) ev)->detail) ); if(obj->XCB->WM_Get_Active_Window()!=((xcb_button_press_event_t *) ev)->root){ obj->XCB->WM_Set_Active_Window( ((xcb_button_press_event_t *) ev)->root); } @@ -152,23 +153,21 @@ bool XCBEventFilter::nativeEventFilter(const QByteArray &eventType, void *messag qDebug() << "Button Release Event"; //xcb_button_release_event_t *tmp = (xcb_button_release_event_t *)ev; stopevent = BlockInputEvent( ((xcb_button_release_event_t *) ev)->root ); //use the main "root" window - not the child widget + if(!stopevent){ obj->emit MouseReleased( InputWindow(((xcb_button_release_event_t *) ev)->root), MouseKey(((xcb_key_press_event_t *) ev)->detail) ); } break; case XCB_MOTION_NOTIFY: //This is a mouse movement event //qDebug() << "Motion Notify Event"; - obj->emit NewInputEvent(); stopevent = BlockInputEvent( ((xcb_motion_notify_event_t *) ev)->root ); //use the main "root" window - not the child widget); break; case XCB_ENTER_NOTIFY: //This is a mouse movement event when mouse goes over a new window - qDebug() << "Enter Notify Event"; - obj->emit NewInputEvent(); + //qDebug() << "Enter Notify Event"; stopevent = BlockInputEvent( ((xcb_enter_notify_event_t *) ev)->root ); break; case XCB_LEAVE_NOTIFY: //This is a mouse movement event when mouse goes leaves a window - qDebug() << "Leave Notify Event"; - obj->emit NewInputEvent(); + //qDebug() << "Leave Notify Event"; stopevent = BlockInputEvent(); break; //============================== @@ -319,6 +318,40 @@ bool XCBEventFilter::BlockInputEvent(WId){ return false; } +WId XCBEventFilter::InputWindow(WId win){ + //check window and see if it is a desktop/root window (return 0) or an external app window + if(win == L_XCB::root){ return 0; } + QString cl = obj->XCB->WindowClass(win); + qDebug() << "Got Input Event on window:" << cl; + if(cl.toLower()=="lumina-desktop"){ return 0; } + return win; //external app window +} + +Lumina::MouseButton XCBEventFilter::MouseKey(int keycode){ + switch(keycode){ + case 1: + return Lumina::LeftButton; + case 3: + return Lumina::RightButton; + case 2: + return Lumina::MidButton; + case 4: + return Lumina::WheelUp; + case 5: + return Lumina::WheelDown; + case 6: + return Lumina::WheelLeft; + case 7: + return Lumina::WheelRight; + case 8: + return Lumina::BackButton; //Not sure if this is correct yet (1/27/17) + case 9: + return Lumina::ForwardButton; //Not sure if this is correct yet (1/27/17) + default: + return Lumina::NoButton; + } +} + //System Tray functions void XCBEventFilter::addTrayApp(WId win){ if(SystemTrayID==0){ return; } diff --git a/src-qt5/core/lumina-desktop-unified/src-events/LXcbEventFilter.h b/src-qt5/core/lumina-desktop-unified/src-events/LXcbEventFilter.h index bd235658..dc88dcc0 100644 --- a/src-qt5/core/lumina-desktop-unified/src-events/LXcbEventFilter.h +++ b/src-qt5/core/lumina-desktop-unified/src-events/LXcbEventFilter.h @@ -70,10 +70,15 @@ signals: void WindowClosed(WId); void ModifyWindow(WId win, Lumina::WindowAction); - //System Tray Signals + // System Tray Signals void Tray_AppAdded(WId); //new tray app registered void Tray_AppClosed(WId); //tray app de-registered void Tray_AppUpdated(WId); //tray app appearance changed (damage event) + // Shortcut Signals + void KeyPressed(WId, int); + void KeyReleased(WId, int); + void MousePressed(WId, Lumina::MouseButton); + void MouseReleased(WId, Lumina::MouseButton); }; class XCBEventFilter : public QAbstractNativeEventFilter{ @@ -95,6 +100,9 @@ private: void InitAtoms(); bool BlockInputEvent(WId win = 0); //Checks the current state of the system to see if the event should be stopped + WId InputWindow(WId win = 0); //Checks the window ID and determines if this is an external window or returns 0 (for desktop/root windows) + Lumina::MouseButton MouseKey(int keycode); //convert the keycode into the mouse button code + //System Tray Variables WId SystemTrayID; diff --git a/src-qt5/core/lumina-desktop-unified/src-events/events.pri b/src-qt5/core/lumina-desktop-unified/src-events/events.pri index b3ca9847..9eec91ca 100644 --- a/src-qt5/core/lumina-desktop-unified/src-events/events.pri +++ b/src-qt5/core/lumina-desktop-unified/src-events/events.pri @@ -2,5 +2,9 @@ SOURCES *= $${PWD}/LXcbEventFilter.cpp HEADERS *= $${PWD}/LXcbEventFilter.h +#Shortcut event files +SOURCES *= $${PWD}/LShortcutEvents.cpp +HEADERS *= $${PWD}/LShortcutEvents.h + #update the includepath so we can just (#include <LXcbEventFilter.h>) as needed without paths INCLUDEPATH *= ${PWD} |