diff --git a/CMakeLists.txt b/CMakeLists.txt index 48ac403d6..b1e1d8ecc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,6 +108,7 @@ set(advanced-scene-switcher_HEADERS src/headers/macro-condition-source.hpp src/headers/macro-condition-streaming.hpp src/headers/macro-condition-video.hpp + src/headers/macro-condition-virt-desktop.hpp src/headers/macro-condition-window.hpp src/headers/macro.hpp src/headers/macro-selection.hpp @@ -176,6 +177,7 @@ set(advanced-scene-switcher_SOURCES src/macro-condition-source.cpp src/macro-condition-streaming.cpp src/macro-condition-video.cpp + src/macro-condition-virt-desktop.cpp src/macro-condition-window.cpp src/macro.cpp src/macro-selection.cpp diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index b8f8e851d..78dd1c955 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -124,6 +124,10 @@ AdvSceneSwitcher.condition.idle.entry="No keyboard or mouse inputs for {{duratio AdvSceneSwitcher.condition.pluginState="Plugin state" AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="Automated scene change was triggered in this interval" AdvSceneSwitcher.condition.pluginState.entry="{{condition}}" +AdvSceneSwitcher.condition.virtDesktop="Virtual desktop" +AdvSceneSwitcher.condition.virtDesktop.notAvailable="Not availbale" +AdvSceneSwitcher.condition.virtDesktop.entry.line1="Active virtual desktop is {{virtDesktops}}" +AdvSceneSwitcher.condition.virtDesktop.entry.line2="Current: {{currentDesktop}}" AdvSceneSwitcher.condition.interval="Interval" AdvSceneSwitcher.condition.interval.entry="{{duration}} have passed" AdvSceneSwitcher.condition.counter="Count" diff --git a/src/headers/macro-condition-virt-desktop.hpp b/src/headers/macro-condition-virt-desktop.hpp new file mode 100644 index 000000000..c9ceca47a --- /dev/null +++ b/src/headers/macro-condition-virt-desktop.hpp @@ -0,0 +1,51 @@ +#pragma once +#include "macro.hpp" +#include +#include + +class MacroConditionVirtDesktop : public MacroCondition { +public: + bool CheckCondition(); + bool Save(obs_data_t *obj); + bool Load(obs_data_t *obj); + std::string GetId() { return id; }; + static std::shared_ptr Create() + { + return std::make_shared(); + } + long _desktop = 0; + +private: + static bool _registered; + static const std::string id; +}; + +class MacroConditionVirtDesktopEdit : public QWidget { + Q_OBJECT + +public: + MacroConditionVirtDesktopEdit( + QWidget *parent, + std::shared_ptr cond = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr cond) + { + return new MacroConditionVirtDesktopEdit( + parent, + std::dynamic_pointer_cast( + cond)); + } + +private slots: + void DesktopChanged(int value); + void UpdateCurrentDesktop(); + +protected: + QComboBox *_virtDesktops; + QLabel *_currentDesktop; + std::shared_ptr _entryData; + +private: + bool _loading = true; +}; diff --git a/src/headers/platform-funcs.hpp b/src/headers/platform-funcs.hpp index d61649013..ed621f7a7 100644 --- a/src/headers/platform-funcs.hpp +++ b/src/headers/platform-funcs.hpp @@ -9,3 +9,5 @@ std::pair getCursorPos(); int secondsSinceLastInput(); void GetProcessList(QStringList &processes); bool isInFocus(const QString &executable); +bool GetCurrentVirtualDesktop(long &desktop); +bool GetVirtualDesktopCount(long &ndesktops); diff --git a/src/linux/advanced-scene-switcher-nix.cpp b/src/linux/advanced-scene-switcher-nix.cpp index 297ea2b3c..71580a91b 100644 --- a/src/linux/advanced-scene-switcher-nix.cpp +++ b/src/linux/advanced-scene-switcher-nix.cpp @@ -393,3 +393,67 @@ int secondsSinceLastInput() return idle_time; } + +bool GetCurrentVirtualDesktop(long &desktop) +{ + Atom type; + long unsigned nitems; + long unsigned int bytes = 0; + int format = 0; + unsigned char *data; + Window root; + Atom request; + Display *display = disp(); + + //TODO check for _NET_CURRENT_DESKTOP + if (!ewmhIsSupported()) { + return false; + } + + request = XInternAtom(display, "_NET_CURRENT_DESKTOP", False); + root = XDefaultRootWindow(display); + + int status = XGetWindowProperty(display, root, request, 0L, 1L, false, + XA_CARDINAL, &type, &format, &nitems, + &bytes, &data); + + if (status == Success && nitems > 0) { + desktop = *((long *)data); + } else { + desktop = -1; + } + free(data); + return desktop != -1; +} + +bool GetVirtualDesktopCount(long &ndesktops) +{ + Atom type; + long unsigned nitems; + long unsigned int bytes = 0; + int format = 0; + unsigned char *data; + Window root; + Atom request; + Display *display = disp(); + + //TODO check for _NET_NUMBER_OF_DESKTOPS + if (!ewmhIsSupported()) { + return false; + } + + request = XInternAtom(display, "_NET_NUMBER_OF_DESKTOPS", False); + root = XDefaultRootWindow(display); + + int status = XGetWindowProperty(display, root, request, 0L, 1L, false, + XA_CARDINAL, &type, &format, &nitems, + &bytes, &data); + + if (status == Success && nitems > 0) { + ndesktops = *((long *)data); + } else { + ndesktops = 0; + } + free(data); + return ndesktops != 0; +} diff --git a/src/macro-condition-virt-desktop.cpp b/src/macro-condition-virt-desktop.cpp new file mode 100644 index 000000000..1c0a0d60b --- /dev/null +++ b/src/macro-condition-virt-desktop.cpp @@ -0,0 +1,113 @@ +#include "headers/macro-condition-edit.hpp" +#include "headers/macro-condition-virt-desktop.hpp" +#include "headers/utility.hpp" +#include "headers/advanced-scene-switcher.hpp" + +const std::string MacroConditionVirtDesktop::id = "virtual_desktop"; + +bool MacroConditionVirtDesktop::_registered = MacroConditionFactory::Register( + MacroConditionVirtDesktop::id, + {MacroConditionVirtDesktop::Create, + MacroConditionVirtDesktopEdit::Create, + "AdvSceneSwitcher.condition.virtDesktop"}); + +bool MacroConditionVirtDesktop::CheckCondition() +{ + long curDesktop; + GetCurrentVirtualDesktop(curDesktop); + return _desktop == curDesktop; +} + +bool MacroConditionVirtDesktop::Save(obs_data_t *obj) +{ + MacroCondition::Save(obj); + obs_data_set_int(obj, "desktop", static_cast(_desktop)); + return true; +} + +bool MacroConditionVirtDesktop::Load(obs_data_t *obj) +{ + MacroCondition::Load(obj); + _desktop = obs_data_get_int(obj, "desktop"); + return true; +} + +static void populateVirtualDesktopSelection(QComboBox *list) +{ + long count; + GetVirtualDesktopCount(count); + + for (long i = 0; i < count; i++) { + list->addItem(std::to_string(i).c_str()); + } +} + +MacroConditionVirtDesktopEdit::MacroConditionVirtDesktopEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent) +{ + _currentDesktop = new QLabel(); + _currentDesktop->setTextInteractionFlags(Qt::TextSelectableByMouse); + _virtDesktops = new QComboBox(); + QWidget::connect(_virtDesktops, SIGNAL(currentIndexChanged(int)), this, + SLOT(DesktopChanged(int))); + populateVirtualDesktopSelection(_virtDesktops); + + QVBoxLayout *mainLayout = new QVBoxLayout; + QHBoxLayout *line1Layout = new QHBoxLayout; + QHBoxLayout *line2Layout = new QHBoxLayout; + std::unordered_map widgetPlaceholders = { + {"{{virtDesktops}}", _virtDesktops}, + {"{{currentDesktop}}", _currentDesktop}, + }; + placeWidgets( + obs_module_text( + "AdvSceneSwitcher.condition.virtDesktop.entry.line1"), + line1Layout, widgetPlaceholders); + placeWidgets( + obs_module_text( + "AdvSceneSwitcher.condition.virtDesktop.entry.line2"), + line2Layout, widgetPlaceholders); + mainLayout->addLayout(line1Layout); + mainLayout->addLayout(line2Layout); + setLayout(mainLayout); + + // Current virtual desktop position + QTimer *timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(UpdateCurrentDesktop())); + timer->start(1000); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroConditionVirtDesktopEdit::DesktopChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_desktop = value; +} + +void MacroConditionVirtDesktopEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + + _virtDesktops->setCurrentIndex(_entryData->_desktop); +} + +void MacroConditionVirtDesktopEdit::UpdateCurrentDesktop() +{ + long curDesktop; + if (GetCurrentVirtualDesktop(curDesktop)) { + _currentDesktop->setText(QString::number(curDesktop)); + } else { + _currentDesktop->setText(obs_module_text( + "AdvSceneSwitcher.condition.virtDesktop.notAvailable")); + } +} diff --git a/src/osx/advanced-scene-switcher-osx.mm b/src/osx/advanced-scene-switcher-osx.mm index 9cb9ddd8b..694790428 100644 --- a/src/osx/advanced-scene-switcher-osx.mm +++ b/src/osx/advanced-scene-switcher-osx.mm @@ -281,3 +281,13 @@ bool isInFocus(const QString &executable) return (equals || matches); } + +bool GetCurrentVirtualDesktop(long &desktop) +{ + return false; +} + +bool GetVirtualDesktopCount(long &ndesktops) +{ + return false; +} diff --git a/src/win/advanced-scene-switcher-win.cpp b/src/win/advanced-scene-switcher-win.cpp index 13f4c2476..bc045caef 100644 --- a/src/win/advanced-scene-switcher-win.cpp +++ b/src/win/advanced-scene-switcher-win.cpp @@ -1,5 +1,7 @@ #define WIN32_LEAN_AND_MEAN #include +#include +#include #include #include #include @@ -11,6 +13,7 @@ #include #define MAX_SEARCH 1000 +#define MAXDESKTOPS 255 static bool GetWindowTitle(HWND window, std::string &title) { @@ -285,3 +288,265 @@ int secondsSinceLastInput() { return (getTime() - getLastInputTime()) / 1000; } + +// Virtual Desktop code +// based on https://github.com/Eun/MoveToDesktop/ +// +// TODO: +// - clean up +// - call FreeCom() on exit + +struct IApplicationView : public IUnknown { +public: +}; + +MIDL_INTERFACE("FF72FFDD-BE7E-43FC-9C03-AD81681E88E4") +IVirtualDesktop : public IUnknown +{ +public: + virtual HRESULT STDMETHODCALLTYPE IsViewVisible( + IApplicationView * pView, int *pfVisible) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetID(GUID * pGuid) = 0; +}; + +class IVirtualDesktopManagerInternal : public IUnknown { +public: + virtual HRESULT STDMETHODCALLTYPE GetCount(UINT *pCount) = 0; + + virtual HRESULT STDMETHODCALLTYPE MoveViewToDesktop( + IApplicationView *pView, IVirtualDesktop *pDesktop) = 0; + + // 10240 + virtual HRESULT STDMETHODCALLTYPE CanViewMoveDesktops( + IApplicationView *pView, int *pfCanViewMoveDesktops) = 0; + + virtual HRESULT STDMETHODCALLTYPE + GetCurrentDesktop(IVirtualDesktop **desktop) = 0; + + virtual HRESULT STDMETHODCALLTYPE + GetDesktops(IObjectArray **ppDesktops) = 0; + + virtual HRESULT STDMETHODCALLTYPE + SwitchDesktop(IVirtualDesktop *pDesktop) = 0; + + virtual HRESULT STDMETHODCALLTYPE + CreateDesktopW(IVirtualDesktop **ppNewDesktop) = 0; + + // pFallbackDesktop - ??????? ???? ?? ??????? ????? ???????? ??????? ????? ???????? ?????????? + virtual HRESULT STDMETHODCALLTYPE + RemoveDesktop(IVirtualDesktop *pRemove, + IVirtualDesktop *pFallbackDesktop) = 0; + + // 10240 + virtual HRESULT STDMETHODCALLTYPE + FindDesktop(GUID *desktopId, IVirtualDesktop **ppDesktop) = 0; +}; + +IServiceProvider *pServiceProvider = nullptr; +IVirtualDesktopManager *pDesktopManager = nullptr; +IVirtualDesktopManagerInternal *pDesktopManagerInternal = nullptr; + +const CLSID CLSID_ImmersiveShell = {0xC2F03A33, 0x21F5, 0x47FA, 0xB4, + 0xBB, 0x15, 0x63, 0x62, + 0xA2, 0xF2, 0x39}; + +const CLSID CLSID_VirtualDesktopAPI_Unknown = {0xC5E0CDCA, 0x7B6E, 0x41B2, 0x9F, + 0xC4, 0xD9, 0x39, 0x75, + 0xCC, 0x46, 0x7B}; + +const IID IID_IVirtualDesktopManagerInternal = {0xEF9F1A6C, 0xD3CC, 0x4358, + 0xB7, 0x12, 0xF8, + 0x4B, 0x63, 0x5B, + 0xEB, 0xE7}; + +const IID UUID_IVirtualDesktopManagerInternal_10130{0xEF9F1A6C, 0xD3CC, 0x4358, + 0xB7, 0x12, 0xF8, + 0x4B, 0x63, 0x5B, + 0xEB, 0xE7}; +const IID UUID_IVirtualDesktopManagerInternal_10240{0xAF8DA486, 0x95BB, 0x4460, + 0xB3, 0xB7, 0x6E, + 0x7A, 0x6B, 0x29, + 0x62, 0xB5}; +const IID UUID_IVirtualDesktopManagerInternal_14393{0xf31574d6, 0xb682, 0x4cdc, + 0xbd, 0x56, 0x18, + 0x27, 0x86, 0x0a, + 0xbe, 0xc6}; + +enum EComStatus { + COMSTATUS_UNINITIALIZED, + COMSTATUS_INITIALIZED, + COMSTATUS_ERROR, +}; + +int ComStatus = COMSTATUS_UNINITIALIZED; +#define COMMAND_TIMEOUT 500 // Blocks Command below this timeout +bool bAddedMenu = false; +bool bReadIni = false; +bool bSwitchDesktopAfterMove = false; +bool bCreateNewDesktopOnMove = false; +bool bDeleteEmptyDesktops = true; +ULONGLONG nLastCommand = 0; + +UINT16 GetWinBuildNumber() +{ + UINT16 buildNumbers[] = {10130, 10240, 14393}; + OSVERSIONINFOEXW osvi = {sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0}; + ULONGLONG mask = ::VerSetConditionMask(0, VER_BUILDNUMBER, VER_EQUAL); + + for (size_t i = 0; i < sizeof(buildNumbers) / sizeof(buildNumbers[0]); + i++) { + osvi.dwBuildNumber = buildNumbers[i]; + if (VerifyVersionInfoW(&osvi, VER_BUILDNUMBER, mask) != FALSE) { + return buildNumbers[i]; + } + } + + return 0; +} + +BOOL InitCom() +{ + if (ComStatus == COMSTATUS_INITIALIZED) { + return true; + } else if (ComStatus == COMSTATUS_ERROR) { + return false; + } + + ComStatus = COMSTATUS_ERROR; + HRESULT hr = ::CoInitialize(NULL); + if (FAILED(hr)) { + return FALSE; + } + + hr = ::CoCreateInstance(CLSID_ImmersiveShell, NULL, CLSCTX_LOCAL_SERVER, + __uuidof(IServiceProvider), + (PVOID *)&pServiceProvider); + if (FAILED(hr)) { + return FALSE; + } + + hr = pServiceProvider->QueryService(__uuidof(IVirtualDesktopManager), + &pDesktopManager); + if (FAILED(hr)) { + pServiceProvider->Release(); + pServiceProvider = nullptr; + return FALSE; + } + + UINT16 buildNumber = GetWinBuildNumber(); + + switch (buildNumber) { + case 10130: + hr = pServiceProvider->QueryService( + CLSID_VirtualDesktopAPI_Unknown, + UUID_IVirtualDesktopManagerInternal_10130, + (void **)&pDesktopManagerInternal); + break; + case 10240: + hr = pServiceProvider->QueryService( + CLSID_VirtualDesktopAPI_Unknown, + UUID_IVirtualDesktopManagerInternal_10240, + (void **)&pDesktopManagerInternal); + break; + case 14393: + default: + hr = pServiceProvider->QueryService( + CLSID_VirtualDesktopAPI_Unknown, + UUID_IVirtualDesktopManagerInternal_14393, + (void **)&pDesktopManagerInternal); + break; + } + if (FAILED(hr)) { + pDesktopManager->Release(); + pDesktopManager = nullptr; + pServiceProvider->Release(); + pServiceProvider = nullptr; + return FALSE; + } + + ComStatus = COMSTATUS_INITIALIZED; + return TRUE; +} + +VOID FreeCom() +{ + if (ComStatus == COMSTATUS_INITIALIZED) { + pDesktopManager->Release(); + pDesktopManagerInternal->Release(); + pServiceProvider->Release(); + ComStatus = COMSTATUS_UNINITIALIZED; + } +} + +bool GetCurrentVirtualDesktop(long &desktop) +{ + UINT count = 0; + IObjectArray *pObjectArray = nullptr; + IVirtualDesktop *pCurrentDesktop = nullptr; + + if (!InitCom()) { + return false; + } + + HRESULT hr = pDesktopManagerInternal->GetDesktops(&pObjectArray); + if (FAILED(hr)) { + return false; + } + + hr = pObjectArray->GetCount(&count); + if (FAILED(hr)) { + pObjectArray->Release(); + return false; + } + + hr = pDesktopManagerInternal->GetCurrentDesktop(&pCurrentDesktop); + if (FAILED(hr)) { + pObjectArray->Release(); + return false; + } + int index = -1; + for (UINT i = 0; i < count && i < MAXDESKTOPS && index == -1; ++i) { + IVirtualDesktop *pDesktop = nullptr; + + if (FAILED(pObjectArray->GetAt(i, __uuidof(IVirtualDesktop), + (void **)&pDesktop))) + continue; + if (pDesktop == pCurrentDesktop) { + index = i; + } + pDesktop->Release(); + } + + pObjectArray->Release(); + + if (pCurrentDesktop != nullptr) { + pCurrentDesktop->Release(); + } + desktop = index; + return true; +} + +bool GetVirtualDesktopCount(long &ndesktops) +{ + if (!InitCom()) { + return false; + } + IObjectArray *pObjectArray = nullptr; + HRESULT hr = pDesktopManagerInternal->GetDesktops(&pObjectArray); + if (FAILED(hr)) { + return false; + } + + UINT count; + hr = pObjectArray->GetCount(&count); + if (FAILED(hr)) { + pObjectArray->Release(); + return false; + } + + pObjectArray->Release(); + ndesktops = count; + + return true; +}