syntheticinput unified for windows

This commit is contained in:
Adam D. Ruppe 2022-04-03 13:52:31 -04:00
parent e1a13734ba
commit 77bc2828c3
1 changed files with 190 additions and 66 deletions

View File

@ -6550,31 +6550,36 @@ version(X11) {
} }
version(Windows) { version(Windows) {
/// Platform-specific for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application). /++
See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications.
This is platform-specific UTF-16 function for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application).
+/
void sendSyntheticInput(wstring s) { void sendSyntheticInput(wstring s) {
INPUT[] inputs; INPUT[] inputs;
inputs.reserve(s.length * 2); inputs.reserve(s.length * 2);
foreach(wchar c; s) { foreach(wchar c; s) {
INPUT input; INPUT input;
input.type = INPUT_KEYBOARD; input.type = INPUT_KEYBOARD;
input.ki.wScan = c; input.ki.wScan = c;
input.ki.dwFlags = KEYEVENTF_UNICODE; input.ki.dwFlags = KEYEVENTF_UNICODE;
inputs ~= input; inputs ~= input;
input.ki.dwFlags |= KEYEVENTF_KEYUP; input.ki.dwFlags |= KEYEVENTF_KEYUP;
inputs ~= input; inputs ~= input;
} }
if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
throw new Exception("SendInput failed");
}
if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
throw new Exception("SendInput failed");
}
} }
// global hotkey helper function // global hotkey helper function
/// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these.
int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) {
__gshared int hotkeyId = 0; __gshared int hotkeyId = 0;
int id = ++hotkeyId; int id = ++hotkeyId;
@ -6623,33 +6628,39 @@ version(Windows) {
version (X11) { version (X11) {
pragma(lib, "dl"); pragma(lib, "dl");
import core.sys.posix.dlfcn; import core.sys.posix.dlfcn;
}
/++ /++
Allows for sending synthetic input to the X server via the Xtst Allows for sending synthetic input to the X server via the Xtst
extension. extension or on Windows using SendInput.
Please remember user input is meant to be user - don't use this Please remember user input is meant to be user - don't use this
if you have some other alternative! if you have some other alternative!
If you need this on Windows btw, the top-level [sendSyntheticInput] shows History:
the Win32 api to start it, but I only did basics there, PR welcome if you like, Added May 17, 2020 with the X implementation.
it is an easy enough function to use.
History: Added May 17, 2020. Added unified implementation for Windows on April 3, 2022. (Prior to that, you had to use the top-level [sendSyntheticInput] or the Windows SendInput call directly.)
+/ Bugs:
struct SyntheticInput { All methods on OSX Cocoa will throw not yet implemented exceptions.
@disable this(); +/
struct SyntheticInput {
@disable this();
private int* refcount;
version(X11) {
private void* lib; private void* lib;
private int* refcount;
private extern(C) { private extern(C) {
void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent;
void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent;
} }
}
/// The dummy param must be 0. /// The dummy param must be 0.
this(int dummy) { this(int dummy) {
version(X11) {
lib = dlopen("libXtst.so", RTLD_NOW); lib = dlopen("libXtst.so", RTLD_NOW);
if(lib is null) if(lib is null)
throw new Exception("cannot load xtest lib extension"); throw new Exception("cannot load xtest lib extension");
@ -6663,49 +6674,136 @@ version (X11) {
throw new Exception("No XTestFakeKeyEvent"); throw new Exception("No XTestFakeKeyEvent");
if(XTestFakeButtonEvent is null) if(XTestFakeButtonEvent is null)
throw new Exception("No XTestFakeButtonEvent"); throw new Exception("No XTestFakeButtonEvent");
refcount = new int;
*refcount = 1;
} }
this(this) { refcount = new int;
if(refcount) *refcount = 1;
*refcount += 1; }
}
~this() { this(this) {
if(refcount) { if(refcount)
*refcount -= 1; *refcount += 1;
if(*refcount == 0) }
// I commented this because if I close the lib before
// XCloseDisplay, it is liable to segfault... so just ~this() {
// gonna keep it loaded if it is loaded, no big deal if(refcount) {
// anyway. *refcount -= 1;
{} // dlclose(lib); if(*refcount == 0)
// I commented this because if I close the lib before
// XCloseDisplay, it is liable to segfault... so just
// gonna keep it loaded if it is loaded, no big deal
// anyway.
{} // dlclose(lib);
}
}
/++
Simulates typing a string into the keyboard.
Bugs:
On X11, this ONLY works with basic ascii! On Windows, it can handle more.
Not implemented except on Windows and X11.
+/
void sendSyntheticInput(string s) {
version(Windows) {
INPUT[] inputs;
inputs.reserve(s.length * 2);
auto ei = GetMessageExtraInfo();
foreach(wchar c; s) {
INPUT input;
input.type = INPUT_KEYBOARD;
input.ki.wScan = c;
input.ki.dwFlags = KEYEVENTF_UNICODE;
input.ki.dwExtraInfo = ei;
inputs ~= input;
input.ki.dwFlags |= KEYEVENTF_KEYUP;
inputs ~= input;
} }
}
/// This ONLY works with basic ascii! if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) {
void sendSyntheticInput(string s) { throw new Exception("SendInput failed");
}
} else version(X11) {
int delay = 0; int delay = 0;
foreach(ch; s) { foreach(ch; s) {
pressKey(cast(Key) ch, true, delay); pressKey(cast(Key) ch, true, delay);
pressKey(cast(Key) ch, false, delay); pressKey(cast(Key) ch, false, delay);
delay += 5; delay += 5;
} }
} } else throw new NotYetImplementedException();
}
/++ /++
Sends a fake press key event. Sends a fake press or release key event.
Please note you need to call [flushGui] or return to the event loop for this to actually be sent. Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
+/
void pressKey(Key key, bool pressed, int delay = 0) { Bugs:
The `delay` parameter is not implemented yet on Windows.
Not implemented except on Windows and X11.
+/
void pressKey(Key key, bool pressed, int delay = 0) {
version(Windows) {
INPUT input;
input.type = INPUT_KEYBOARD;
input.ki.wVk = cast(ushort) key;
input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
input.ki.dwExtraInfo = GetMessageExtraInfo();
if(SendInput(1, &input, INPUT.sizeof) != 1) {
throw new Exception("SendInput failed");
}
} else version(X11) {
XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5);
} } else throw new NotYetImplementedException();
}
/// /++
void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { Sends a fake mouse button press or release event.
Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11.
`pressed` param must be `true` if button is `wheelUp` or `wheelDown`.
Bugs:
The `delay` parameter is not implemented yet on Windows.
The backButton and forwardButton will throw NotYetImplementedException on Windows.
All arguments will throw NotYetImplementedException on OSX Cocoa.
+/
void pressMouseButton(MouseButton button, bool pressed, int delay = 0) {
version(Windows) {
INPUT input;
input.type = INPUT_MOUSE;
input.mi.dwExtraInfo = GetMessageExtraInfo();
// input.mi.mouseData for a wheel event
switch(button) {
case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
case MouseButton.wheelUp:
case MouseButton.wheelDown:
input.mi.dwFlags = MOUSEEVENTF_WHEEL;
input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120;
break;
case MouseButton.backButton: throw new NotYetImplementedException(); break;
case MouseButton.forwardButton: throw new NotYetImplementedException(); break;
default:
}
if(SendInput(1, &input, INPUT.sizeof) != 1) {
throw new Exception("SendInput failed");
}
} else version(X11) {
int btn; int btn;
switch(button) { switch(button) {
@ -6722,22 +6820,48 @@ version (X11) {
assert(btn); assert(btn);
XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay);
} } else throw new NotYetImplementedException();
}
/// ///
static void moveMouseArrowBy(int dx, int dy) { static void moveMouseArrowBy(int dx, int dy) {
version(Windows) {
INPUT input;
input.type = INPUT_MOUSE;
input.mi.dwExtraInfo = GetMessageExtraInfo();
input.mi.dx = dx;
input.mi.dy = dy;
input.mi.dwFlags = MOUSEEVENTF_MOVE;
if(SendInput(1, &input, INPUT.sizeof) != 1) {
throw new Exception("SendInput failed");
}
} else version(X11) {
auto disp = XDisplayConnection.get(); auto disp = XDisplayConnection.get();
XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy);
XFlush(disp); XFlush(disp);
} } else throw new NotYetImplementedException();
}
/// ///
static void moveMouseArrowTo(int x, int y) { static void moveMouseArrowTo(int x, int y) {
version(Windows) {
INPUT input;
input.type = INPUT_MOUSE;
input.mi.dwExtraInfo = GetMessageExtraInfo();
input.mi.dx = x;
input.mi.dy = y;
input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
if(SendInput(1, &input, INPUT.sizeof) != 1) {
throw new Exception("SendInput failed");
}
} else version(X11) {
auto disp = XDisplayConnection.get(); auto disp = XDisplayConnection.get();
auto root = RootWindow(disp, DefaultScreen(disp)); auto root = RootWindow(disp, DefaultScreen(disp));
XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y);
XFlush(disp); XFlush(disp);
} } else throw new NotYetImplementedException();
} }
} }