mirror of
https://github.com/jonasbark/swiftcontrol.git
synced 2026-02-18 00:17:40 +01:00
allow all apps to execute keyboard key in background
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
#include "keypress_simulator_windows_plugin.h"
|
#include "keypress_simulator_windows_plugin.h"
|
||||||
|
|
||||||
// This must be included before many other Windows headers.
|
// This must be included before many other Windows headers.
|
||||||
#include <windows.h>
|
#include <flutter_windows.h>
|
||||||
#include <psapi.h>
|
#include <psapi.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <flutter_windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#include <flutter/method_channel.h>
|
#include <flutter/method_channel.h>
|
||||||
#include <flutter/plugin_registrar_windows.h>
|
#include <flutter/plugin_registrar_windows.h>
|
||||||
@@ -30,7 +30,8 @@ struct FindWindowData {
|
|||||||
};
|
};
|
||||||
|
|
||||||
BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam);
|
BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam);
|
||||||
HWND FindTargetWindow(const std::string& processName, const std::string& windowTitle);
|
HWND FindTargetWindow(const std::string& processName,
|
||||||
|
const std::string& windowTitle);
|
||||||
|
|
||||||
// static
|
// static
|
||||||
void KeypressSimulatorWindowsPlugin::RegisterWithRegistrar(
|
void KeypressSimulatorWindowsPlugin::RegisterWithRegistrar(
|
||||||
@@ -71,54 +72,50 @@ void KeypressSimulatorWindowsPlugin::SimulateKeyPress(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// List of compatible training apps to look for
|
// List of compatible training apps to look for
|
||||||
std::vector<std::string> compatibleApps = {
|
std::vector<std::string> compatibleApps = {"MyWhooshHD.exe", "MyWhoosh.exe",
|
||||||
"MyWhooshHD.exe",
|
"indieVelo.exe", "biketerra.exe",
|
||||||
"indieVelo.exe",
|
"Rouvy.exe"};
|
||||||
"biketerra.exe",
|
|
||||||
"ROUVY.exe"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apps that should receive key events directly without taking focus (allows media/apps to stay on top)
|
|
||||||
const std::vector<std::string> backgroundInputApps = {
|
|
||||||
"ROUVY.exe"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Try to find and focus (or directly target) a compatible app
|
// Try to find and focus (or directly target) a compatible app
|
||||||
std::string foundProcessName;
|
std::string foundProcessName;
|
||||||
bool supportsBackgroundInput = false;
|
bool supportsBackgroundInput = true;
|
||||||
HWND targetWindow = NULL;
|
HWND targetWindow = NULL;
|
||||||
for (const std::string& processName : compatibleApps) {
|
for (const std::string& processName : compatibleApps) {
|
||||||
targetWindow = FindTargetWindow(processName, "");
|
targetWindow = FindTargetWindow(processName, "");
|
||||||
if (targetWindow != NULL) {
|
if (targetWindow != NULL) {
|
||||||
foundProcessName = processName;
|
foundProcessName = processName;
|
||||||
// For background-capable apps, prefer sending keys directly to their window to avoid stealing focus
|
|
||||||
supportsBackgroundInput = std::find(backgroundInputApps.begin(), backgroundInputApps.end(), processName) != backgroundInputApps.end();
|
|
||||||
if (!supportsBackgroundInput && GetForegroundWindow() != targetWindow) {
|
if (!supportsBackgroundInput && GetForegroundWindow() != targetWindow) {
|
||||||
SetForegroundWindow(targetWindow);
|
SetForegroundWindow(targetWindow);
|
||||||
Sleep(50); // Brief delay to ensure window is focused
|
Sleep(50); // Brief delay to ensure window is focused
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we found a target window that supports background input and it's not focused, send messages directly
|
// If we found a target window that supports background input and it's not
|
||||||
|
// focused, send messages directly
|
||||||
auto postKeyMessage = [](HWND hwnd, UINT vkCode, bool down) {
|
auto postKeyMessage = [](HWND hwnd, UINT vkCode, bool down) {
|
||||||
const WORD scanCode = static_cast<WORD>(MapVirtualKey(vkCode, MAPVK_VK_TO_VSC));
|
const WORD scanCode =
|
||||||
// Build lParam with repeat count 1 and scan code; set transition states for key up
|
static_cast<WORD>(MapVirtualKey(vkCode, MAPVK_VK_TO_VSC));
|
||||||
|
// Build lParam with repeat count 1 and scan code; set transition states for
|
||||||
|
// key up
|
||||||
LPARAM lParam = 1 | (static_cast<LPARAM>(scanCode) << 16);
|
LPARAM lParam = 1 | (static_cast<LPARAM>(scanCode) << 16);
|
||||||
if (vkCode == VK_LEFT || vkCode == VK_RIGHT || vkCode == VK_UP || vkCode == VK_DOWN ||
|
if (vkCode == VK_LEFT || vkCode == VK_RIGHT || vkCode == VK_UP ||
|
||||||
vkCode == VK_INSERT || vkCode == VK_DELETE || vkCode == VK_HOME || vkCode == VK_END ||
|
vkCode == VK_DOWN || vkCode == VK_INSERT || vkCode == VK_DELETE ||
|
||||||
vkCode == VK_PRIOR || vkCode == VK_NEXT) {
|
vkCode == VK_HOME || vkCode == VK_END || vkCode == VK_PRIOR ||
|
||||||
lParam |= (1 << 24); // extended key
|
vkCode == VK_NEXT) {
|
||||||
|
lParam |= (1 << 24); // extended key
|
||||||
}
|
}
|
||||||
if (!down) {
|
if (!down) {
|
||||||
lParam |= (1 << 30); // previous key state
|
lParam |= (1 << 30); // previous key state
|
||||||
lParam |= (1 << 31); // transition state
|
lParam |= (1 << 31); // transition state
|
||||||
}
|
}
|
||||||
PostMessage(hwnd, down ? WM_KEYDOWN : WM_KEYUP, vkCode, lParam);
|
PostMessage(hwnd, down ? WM_KEYDOWN : WM_KEYUP, vkCode, lParam);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto sendKeyToWindow = [&postKeyMessage](HWND hwnd, const std::vector<std::string>& mods, UINT keyCode, bool down) {
|
auto sendKeyToWindow = [&postKeyMessage](HWND hwnd,
|
||||||
|
const std::vector<std::string>& mods,
|
||||||
|
UINT keyCode, bool down) {
|
||||||
auto handleModifier = [&postKeyMessage, hwnd](UINT vk, bool press) {
|
auto handleModifier = [&postKeyMessage, hwnd](UINT vk, bool press) {
|
||||||
postKeyMessage(hwnd, vk, press);
|
postKeyMessage(hwnd, vk, press);
|
||||||
};
|
};
|
||||||
@@ -153,10 +150,8 @@ void KeypressSimulatorWindowsPlugin::SimulateKeyPress(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (targetWindow != NULL &&
|
if (targetWindow != NULL && !foundProcessName.empty() &&
|
||||||
!foundProcessName.empty() &&
|
supportsBackgroundInput && GetForegroundWindow() != targetWindow) {
|
||||||
supportsBackgroundInput &&
|
|
||||||
GetForegroundWindow() != targetWindow) {
|
|
||||||
sendKeyToWindow(targetWindow, modifiers, keyCode, keyDown);
|
sendKeyToWindow(targetWindow, modifiers, keyCode, keyDown);
|
||||||
result->Success(flutter::EncodableValue(true));
|
result->Success(flutter::EncodableValue(true));
|
||||||
return;
|
return;
|
||||||
@@ -174,7 +169,8 @@ void KeypressSimulatorWindowsPlugin::SimulateKeyPress(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to process modifiers
|
// Helper function to process modifiers
|
||||||
auto processModifiers = [&sendModifierKey](const std::vector<std::string>& mods, bool down) {
|
auto processModifiers = [&sendModifierKey](
|
||||||
|
const std::vector<std::string>& mods, bool down) {
|
||||||
for (const std::string& modifier : mods) {
|
for (const std::string& modifier : mods) {
|
||||||
if (modifier == "shiftModifier") {
|
if (modifier == "shiftModifier") {
|
||||||
sendModifierKey(VK_SHIFT, down);
|
sendModifierKey(VK_SHIFT, down);
|
||||||
@@ -198,12 +194,13 @@ void KeypressSimulatorWindowsPlugin::SimulateKeyPress(
|
|||||||
|
|
||||||
INPUT in = {0};
|
INPUT in = {0};
|
||||||
in.type = INPUT_KEYBOARD;
|
in.type = INPUT_KEYBOARD;
|
||||||
in.ki.wVk = 0; // when using SCANCODE, set VK=0
|
in.ki.wVk = 0; // when using SCANCODE, set VK=0
|
||||||
in.ki.wScan = sc;
|
in.ki.wScan = sc;
|
||||||
in.ki.dwFlags = KEYEVENTF_SCANCODE | (keyDown ? 0 : KEYEVENTF_KEYUP);
|
in.ki.dwFlags = KEYEVENTF_SCANCODE | (keyDown ? 0 : KEYEVENTF_KEYUP);
|
||||||
if (keyCode == VK_LEFT || keyCode == VK_RIGHT || keyCode == VK_UP || keyCode == VK_DOWN ||
|
if (keyCode == VK_LEFT || keyCode == VK_RIGHT || keyCode == VK_UP ||
|
||||||
keyCode == VK_INSERT || keyCode == VK_DELETE || keyCode == VK_HOME || keyCode == VK_END ||
|
keyCode == VK_DOWN || keyCode == VK_INSERT || keyCode == VK_DELETE ||
|
||||||
keyCode == VK_PRIOR || keyCode == VK_NEXT) {
|
keyCode == VK_HOME || keyCode == VK_END || keyCode == VK_PRIOR ||
|
||||||
|
keyCode == VK_NEXT) {
|
||||||
in.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
|
in.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
|
||||||
}
|
}
|
||||||
SendInput(1, &in, sizeof(INPUT));
|
SendInput(1, &in, sizeof(INPUT));
|
||||||
@@ -222,7 +219,6 @@ void KeypressSimulatorWindowsPlugin::SimulateKeyPress(
|
|||||||
void KeypressSimulatorWindowsPlugin::SimulateMouseClick(
|
void KeypressSimulatorWindowsPlugin::SimulateMouseClick(
|
||||||
const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
||||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
|
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
|
||||||
|
|
||||||
const EncodableMap& args = std::get<EncodableMap>(*method_call.arguments());
|
const EncodableMap& args = std::get<EncodableMap>(*method_call.arguments());
|
||||||
double x = 0;
|
double x = 0;
|
||||||
double y = 0;
|
double y = 0;
|
||||||
@@ -230,12 +226,12 @@ void KeypressSimulatorWindowsPlugin::SimulateMouseClick(
|
|||||||
bool keyDown = std::get<bool>(args.at(EncodableValue("keyDown")));
|
bool keyDown = std::get<bool>(args.at(EncodableValue("keyDown")));
|
||||||
auto it_x = args.find(EncodableValue("x"));
|
auto it_x = args.find(EncodableValue("x"));
|
||||||
if (it_x != args.end() && std::holds_alternative<double>(it_x->second)) {
|
if (it_x != args.end() && std::holds_alternative<double>(it_x->second)) {
|
||||||
x = std::get<double>(it_x->second);
|
x = std::get<double>(it_x->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto it_y = args.find(EncodableValue("y"));
|
auto it_y = args.find(EncodableValue("y"));
|
||||||
if (it_y != args.end() && std::holds_alternative<double>(it_y->second)) {
|
if (it_y != args.end() && std::holds_alternative<double>(it_y->second)) {
|
||||||
y = std::get<double>(it_y->second);
|
y = std::get<double>(it_y->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the monitor containing the target point and its DPI
|
// Get the monitor containing the target point and its DPI
|
||||||
@@ -243,7 +239,7 @@ void KeypressSimulatorWindowsPlugin::SimulateMouseClick(
|
|||||||
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
|
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
|
||||||
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
|
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
|
||||||
double scale_factor = dpi / 96.0;
|
double scale_factor = dpi / 96.0;
|
||||||
|
|
||||||
// Scale the coordinates according to the DPI scaling
|
// Scale the coordinates according to the DPI scaling
|
||||||
int scaled_x = static_cast<int>(x * scale_factor);
|
int scaled_x = static_cast<int>(x * scale_factor);
|
||||||
int scaled_y = static_cast<int>(y * scale_factor);
|
int scaled_y = static_cast<int>(y * scale_factor);
|
||||||
@@ -256,14 +252,14 @@ void KeypressSimulatorWindowsPlugin::SimulateMouseClick(
|
|||||||
input.type = INPUT_MOUSE;
|
input.type = INPUT_MOUSE;
|
||||||
|
|
||||||
if (keyDown) {
|
if (keyDown) {
|
||||||
// Mouse left button down
|
// Mouse left button down
|
||||||
input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
|
input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
|
||||||
SendInput(1, &input, sizeof(INPUT));
|
SendInput(1, &input, sizeof(INPUT));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Mouse left button up
|
// Mouse left button up
|
||||||
input.mi.dwFlags = MOUSEEVENTF_LEFTUP;
|
input.mi.dwFlags = MOUSEEVENTF_LEFTUP;
|
||||||
SendInput(1, &input, sizeof(INPUT));
|
SendInput(1, &input, sizeof(INPUT));
|
||||||
}
|
}
|
||||||
|
|
||||||
result->Success(flutter::EncodableValue(true));
|
result->Success(flutter::EncodableValue(true));
|
||||||
@@ -274,7 +270,7 @@ BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam) {
|
|||||||
|
|
||||||
// Check if window is visible and not minimized
|
// Check if window is visible and not minimized
|
||||||
if (!IsWindowVisible(hwnd) || IsIconic(hwnd)) {
|
if (!IsWindowVisible(hwnd) || IsIconic(hwnd)) {
|
||||||
return TRUE; // Continue enumeration
|
return TRUE; // Continue enumeration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get window title
|
// Get window title
|
||||||
@@ -284,7 +280,8 @@ BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam) {
|
|||||||
// Get process name
|
// Get process name
|
||||||
DWORD processId;
|
DWORD processId;
|
||||||
GetWindowThreadProcessId(hwnd, &processId);
|
GetWindowThreadProcessId(hwnd, &processId);
|
||||||
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processId);
|
HANDLE hProcess =
|
||||||
|
OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processId);
|
||||||
char processName[MAX_PATH];
|
char processName[MAX_PATH];
|
||||||
if (hProcess) {
|
if (hProcess) {
|
||||||
DWORD size = sizeof(processName);
|
DWORD size = sizeof(processName);
|
||||||
@@ -292,7 +289,7 @@ BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam) {
|
|||||||
// Extract just the filename from the full path
|
// Extract just the filename from the full path
|
||||||
char* filename = strrchr(processName, '\\');
|
char* filename = strrchr(processName, '\\');
|
||||||
if (filename) {
|
if (filename) {
|
||||||
filename++; // Skip the backslash
|
filename++; // Skip the backslash
|
||||||
} else {
|
} else {
|
||||||
filename = processName;
|
filename = processName;
|
||||||
}
|
}
|
||||||
@@ -301,7 +298,7 @@ BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam) {
|
|||||||
if (!data->targetProcessName.empty() &&
|
if (!data->targetProcessName.empty() &&
|
||||||
_stricmp(filename, data->targetProcessName.c_str()) == 0) {
|
_stricmp(filename, data->targetProcessName.c_str()) == 0) {
|
||||||
data->foundWindow = hwnd;
|
data->foundWindow = hwnd;
|
||||||
return FALSE; // Stop enumeration
|
return FALSE; // Stop enumeration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CloseHandle(hProcess);
|
CloseHandle(hProcess);
|
||||||
@@ -311,13 +308,14 @@ BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam) {
|
|||||||
if (!data->targetWindowTitle.empty() &&
|
if (!data->targetWindowTitle.empty() &&
|
||||||
_stricmp(windowTitle, data->targetWindowTitle.c_str()) == 0) {
|
_stricmp(windowTitle, data->targetWindowTitle.c_str()) == 0) {
|
||||||
data->foundWindow = hwnd;
|
data->foundWindow = hwnd;
|
||||||
return FALSE; // Stop enumeration
|
return FALSE; // Stop enumeration
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE; // Continue enumeration
|
return TRUE; // Continue enumeration
|
||||||
}
|
}
|
||||||
|
|
||||||
HWND FindTargetWindow(const std::string& processName, const std::string& windowTitle) {
|
HWND FindTargetWindow(const std::string& processName,
|
||||||
|
const std::string& windowTitle) {
|
||||||
FindWindowData data;
|
FindWindowData data;
|
||||||
data.targetProcessName = processName;
|
data.targetProcessName = processName;
|
||||||
data.targetWindowTitle = windowTitle;
|
data.targetWindowTitle = windowTitle;
|
||||||
@@ -330,19 +328,15 @@ HWND FindTargetWindow(const std::string& processName, const std::string& windowT
|
|||||||
void KeypressSimulatorWindowsPlugin::SimulateMediaKey(
|
void KeypressSimulatorWindowsPlugin::SimulateMediaKey(
|
||||||
const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
||||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
|
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
|
||||||
|
|
||||||
const EncodableMap& args = std::get<EncodableMap>(*method_call.arguments());
|
const EncodableMap& args = std::get<EncodableMap>(*method_call.arguments());
|
||||||
std::string keyIdentifier = std::get<std::string>(args.at(EncodableValue("key")));
|
std::string keyIdentifier =
|
||||||
|
std::get<std::string>(args.at(EncodableValue("key")));
|
||||||
|
|
||||||
// Map string identifier to Windows virtual key codes
|
// Map string identifier to Windows virtual key codes
|
||||||
static const std::unordered_map<std::string, UINT> keyMap = {
|
static const std::unordered_map<std::string, UINT> keyMap = {
|
||||||
{"playPause", VK_MEDIA_PLAY_PAUSE},
|
{"playPause", VK_MEDIA_PLAY_PAUSE}, {"stop", VK_MEDIA_STOP},
|
||||||
{"stop", VK_MEDIA_STOP},
|
{"next", VK_MEDIA_NEXT_TRACK}, {"previous", VK_MEDIA_PREV_TRACK},
|
||||||
{"next", VK_MEDIA_NEXT_TRACK},
|
{"volumeUp", VK_VOLUME_UP}, {"volumeDown", VK_VOLUME_DOWN}};
|
||||||
{"previous", VK_MEDIA_PREV_TRACK},
|
|
||||||
{"volumeUp", VK_VOLUME_UP},
|
|
||||||
{"volumeDown", VK_VOLUME_DOWN}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto it = keyMap.find(keyIdentifier);
|
auto it = keyMap.find(keyIdentifier);
|
||||||
if (it == keyMap.end()) {
|
if (it == keyMap.end()) {
|
||||||
@@ -355,7 +349,7 @@ void KeypressSimulatorWindowsPlugin::SimulateMediaKey(
|
|||||||
INPUT inputs[2] = {};
|
INPUT inputs[2] = {};
|
||||||
inputs[0].type = INPUT_KEYBOARD;
|
inputs[0].type = INPUT_KEYBOARD;
|
||||||
inputs[0].ki.wVk = static_cast<WORD>(vkCode);
|
inputs[0].ki.wVk = static_cast<WORD>(vkCode);
|
||||||
inputs[0].ki.dwFlags = 0; // Key down
|
inputs[0].ki.dwFlags = 0; // Key down
|
||||||
|
|
||||||
// Send key up event
|
// Send key up event
|
||||||
inputs[1].type = INPUT_KEYBOARD;
|
inputs[1].type = INPUT_KEYBOARD;
|
||||||
@@ -371,7 +365,6 @@ void KeypressSimulatorWindowsPlugin::SimulateMediaKey(
|
|||||||
result->Success(flutter::EncodableValue(true));
|
result->Success(flutter::EncodableValue(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void KeypressSimulatorWindowsPlugin::HandleMethodCall(
|
void KeypressSimulatorWindowsPlugin::HandleMethodCall(
|
||||||
const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
||||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
|
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
|
||||||
|
|||||||
Reference in New Issue
Block a user