我正在撰寫一個
但是,我不想解決這個問題,而是直接解決它(如果可能的話)。
The catch is, that plugins for Stream Deck works as separate processes (actually, even separate executables) and communicate through web sockets, with all the consequences (eg. I may as well create windows if needed).
My question would be: what would be the proper way of activating a window from within my process? The problem is that entry point is not direct user input (in terms of what WinAPI treats as user input, what would probably solve the problem), but user input via websockets.
Edit: in response to comments.
I tried to implement the solution with UI automation and SetFocus worked, but the window was not brought to foreground.
The source (you can copy & paste to a new console application) is as following:
#include <iostream>
#include <string>
#include <windows.h>
#include <tlhelp32.h>
#include <cstdio>
#include <wctype.h>
#include <locale>
#include <codecvt>
#include <WinUser.h>
#include <vector>
#include <algorithm>
#include <uiautomationclient.h>
/// <summary>
/// Contains information about single process and associated windows
/// </summary>
struct process_data
{
unsigned long process_id;
std::vector<HWND> window_handles;
};
/// <summary>
/// Compares two wide strings case insensitive
/// </summary>
bool equals_case_insensitive(const wchar_t* a, const wchar_t* b)
{
while (*a != 0 && *b != 0)
{
if (towlower(*a ) != towlower(*b ))
return false;
}
return *a == 0 && *b == 0;
}
/// <summary>
/// Checks, if given window is main window of the process
/// </summary>
bool is_main_window(HWND handle)
{
return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle);
}
/// <summary>
/// Callback used for enumerating windows per processes
/// </summary>
BOOL CALLBACK enum_windows_callback(HWND handle, LPARAM lParam)
{
std::vector<process_data>& data = *(std::vector<process_data>*)lParam;
unsigned long process_id = 0;
GetWindowThreadProcessId(handle, &process_id);
int i;
for (i = 0; i < data.size() && data[i].process_id != process_id; i );
if (i < data.size())
{
if (is_main_window(handle))
{
data[i].window_handles.push_back(handle);
}
}
return TRUE;
}
/// <summary>
/// Finds main windows for given processes
/// </summary>
std::vector<process_data> find_windows(std::vector<DWORD> process_ids)
{
// Creates process_data entries
std::vector<process_data> data;
for (DWORD processId : process_ids)
{
process_data processData;
processData.process_id = processId;
data.push_back(processData);
}
// Collects windows of given processes
EnumWindows(enum_windows_callback, (LPARAM)&data);
// Removes processes without any windows
int i = 0;
while (i < data.size())
{
if (data[i].window_handles.size() == 0)
data.erase(data.begin() i);
else
i ;
}
// Sorts processes by their IDs (to provide some
// way of ordering processes)
std::sort(data.begin(), data.end(), [](process_data& first, process_data& second) { return first.process_id - second.process_id; });
// For each process
for (process_data& process : data)
{
// Sorts windows by their handles (again, to
// provide some way of ordering them)
std::sort(process.window_handles.begin(), process.window_handles.end(), [](HWND& first, HWND& second) {
long long firstValue = (long long)first;
long long secondValue = (long long)second;
if (firstValue > secondValue)
return 1;
else if (firstValue < secondValue)
return -1;
else
return 0;
});
}
return data;
}
/// <summary>
/// Finds all process IDs, which executable name matches
/// given name (eg. notepad.exe)
/// </summary>
std::vector<DWORD> find_process_ids(const std::wstring& processName)
{
// Collects information about running processes
PROCESSENTRY32 processInfo;
processInfo.dwSize = sizeof(processInfo);
std::vector<DWORD> processIDs;
HANDLE processesSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (processesSnapshot == INVALID_HANDLE_VALUE)
return processIDs;
// Collects all those, which executable name matches
// one given in the parameter
Process32First(processesSnapshot, &processInfo);
if (equals_case_insensitive(processName.c_str(), processInfo.szExeFile))
{
processIDs.push_back(processInfo.th32ProcessID);
}
while (Process32Next(processesSnapshot, &processInfo))
{
if (equals_case_insensitive(processName.c_str(), processInfo.szExeFile))
{
processIDs.push_back(processInfo.th32ProcessID);
}
}
CloseHandle(processesSnapshot);
return processIDs;
}
int main()
{
// Enumerate all matching process IDs
std::vector<DWORD> processIDs = find_process_ids(L"notepad.exe");
// We need at least one
if (processIDs.size() == 0)
return false;
// Find windows for found processes
std::vector<process_data> windows = find_windows(processIDs);
// We need at least one process with window
if (windows.size() == 0)
return false;
HWND handle = windows[0].window_handles[0];
IUIAutomation* pAutomation;
CoInitialize(nullptr);
HRESULT hr = CoCreateInstance(__uuidof(CUIAutomation), NULL, CLSCTX_INPROC_SERVER, __uuidof(IUIAutomation), (void**)&pAutomation);
if (SUCCEEDED(hr)) {
printf("got IUIAutomation\r\n");
IUIAutomationElement* window = nullptr;
if (SUCCEEDED(pAutomation->ElementFromHandle(handle, &window)))
{
if (SUCCEEDED(window->SetFocus()))
{
std::cout << "Success" << std::endl;
}
window->Release();
}
pAutomation->Release();
}
}
What am I doing wrong?
uj5u.com熱心網友回復:
SetForegroundWindow僅在呼叫應用程式是前臺應用程式時效果最佳。如果按鈕歸您的行程所有,它應該可以正常作業,但如果按鈕歸主 StreamDeck 行程所有,您需要以某種方式指示它SetForegroundWindow使用正確的引數呼叫(可能需要功能請求)。
推薦用于鍵盤輸入RegisterHotKey。當按下注冊的熱鍵組合時,視窗處理WM_HOTKEY獲得特殊豁免(前景愛https://devblogs.microsoft.com/oldnewthing/20090226-00/?p=19013),因此SetForegroundWindow可用于更改前景視窗。
在其他情況下,您可以使用變通辦法,冒著將來它們可能會停止作業的風險。
SetForegroundWindow(hwnd);
if (GetForegroundWindow() != hwnd)
{
SwitchToThisWindow(hwnd, TRUE);
Sleep(2);
SetForegroundWindow(hwnd);
}
還有一個,當關閉合成時非常煩人。
SetForegroundWindow(hwnd);
if (GetForegroundWindow() != hwnd)
{
BOOL flag = TRUE;
DwmSetWindowAttribute(hwnd, DWMWA_TRANSITIONS_FORCEDISABLED, &flag, sizeof(flag));
ShowWindow(hwnd, SW_MINIMIZE);
ShowWindow(hwnd, SW_RESTORE);
flag = FALSE;
DwmSetWindowAttribute(hwnd, DWMWA_TRANSITIONS_FORCEDISABLED, &flag, sizeof(flag));
SetForegroundWindow(hwnd);
}
uj5u.com熱心網友回復:
我最終使用的解決方案結合了在最小化視窗時恢復視窗,然后使用 UI 自動化 API。
我想最值得稱贊的是Simon Mourier,他在評論中提出了解決方案。
相關部分代碼如下:
int main(int argc, const char* const argv[])
{
if (!SUCCEEDED(CoInitialize(nullptr)))
{
return 1;
}
// (...)
}
SwitchToPlugin::SwitchToPlugin()
{
if (!SUCCEEDED(CoCreateInstance(__uuidof(CUIAutomation), NULL, CLSCTX_INPROC_SERVER, __uuidof(IUIAutomation), (void**)&(this->uiAutomation))))
{
throw new std::exception("Failed to create instance of UI automation!");
}
}
/// <summary>
/// Brings given window to the front
/// </summary>
bool SwitchToPlugin::bring_to_front(HWND hWnd)
{
bool result = false;
// First restore if window is minimized
WINDOWPLACEMENT placement{};
placement.length = sizeof(placement);
if (!GetWindowPlacement(hWnd, &placement))
return false;
bool minimized = placement.showCmd == SW_SHOWMINIMIZED;
if (minimized)
ShowWindow(hWnd, SW_RESTORE);
// Then bring it to front using UI automation
IUIAutomationElement* window = nullptr;
if (SUCCEEDED(uiAutomation->ElementFromHandle(hWnd, &window)))
{
if (SUCCEEDED(window->SetFocus()))
{
result = true;
}
window->Release();
}
return result;
}
到目前為止一直有效,并且不包含任何黑客和其他技巧。在 Spotify、Notepad、Teams 和 Visual Studio 上測驗。
GitLab 上提供了該插件的完整源代碼:https ://gitlab.com/spook/StreamDeckSwitchTo.git
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/444861.html
上一篇:如何檢查一個陣列中的所有元素是否存在于另一個陣列中。但是以相同的順序
下一篇:C:Windows沒有找到HARDWARE\DEVICEMAP\SERIALCOMM,即使可以使用regedit找到它
