你知道如何模拟按住击键一段时间,然后再松开吗?
Do you know how to emulate holding down a keystroke for any period of time, then releasing it later?
我正在尝试模拟按下键盘 V 键,同时使用 BASS 库来自动化语音激活一键通。我已经让 BASS 库正常工作,只是无法让键盘模拟按住某个键任何时间!
编辑:
我正在尝试让另一个应用程序 ('TeamSpeak 3') 将我的 Key Press & Hold 识别为基于硬件的 Key Press & Hold 而不是基于软件的 Key Press & Hold。通过我的应用程序帮助模拟一键通。我会公开为任何想要它的人提供源代码,但我不会出于任何原因发布我的应用程序。它是供我个人使用的,如果它能工作,我感到好奇os?我明白,任何形式的滥用此类应用程序,我都认为是我个人的责任。
Edit2:我做了广泛的研究。我想我将不得不使用我的旧 Android 手持设备或 Raspberry Pi。我有一个 Raspberry Pi 零,所以我要看看是否可以将它创建为硬件键盘。我将在 Delphi 中编写一个程序来连接它(我有 Delphi 10.4.1 企业版,希望它能与 Raspberry Pi 的 linux 版本一起工作。)我有在我的计算机上可以预编译的 vmware Debian 和 Ubuntu os?不管怎样,文章在这里:https://randomnerdtutorials.com/raspberry-pi-zero-usb-keyboard-hid/
我将继续允许下面的答案,因为它基本上符合我之前的要求。比我的要求更进一步需要大量的工作。如果可以正常使用,我会提供更新。
(Delphi 10.4.1 / 目标 Windows 32 位)
这是我当前的源代码:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.MPlayer, System.UITypes, BASS,
Vcl.ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Memo1: TMemo;
Timer1: TTimer;
ComboBox1: TComboBox;
Timer2: TTimer;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure ComboBox1Change(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Timer2Timer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
function RecordingCallback(h:HRECORD; b:Pointer; l,u: DWord): boolean; stdcall;
end;
var
Form1: TForm1;
rchan: HRECORD; // recording channel
level2: dword;
LoudEnough: boolean = FALSE;
threshold: DWORD = 500; // trigger level
MicON_Timer, Counter1: Cardinal;
MicON_Bool : Boolean;
implementation
{$R *.dfm}
(* This function called while recording audio *)
function TForm1.RecordingCallback(h:HRECORD; b:Pointer; l,u: DWord): boolean; stdcall;
//var level:dword;
begin
level2:=BASS_ChannelGetLevel(h);
LoudEnough := (LoWord(level2) >= threshold) or (HiWord(level2) >= threshold);
//Memo1.Lines.add('Loword ' + IntToStr(LoWord(level))+' - HiWord '+IntToStr(HiWord(level)));
Result := True;
end;
// START BUTTON
procedure TForm1.Button1Click(Sender: TObject);
begin
{
if BASS_RecordSetDevice(0) = false then
begin
memo1.Lines.Add('BASS_RecordSetDevice ERROR = '+ BASS_ErrorGetCode().ToString);
end;}
Counter1 := 0;
MicON_Timer := 0;
Timer1.Enabled := true;
ComboBox1Change(Self);
rchan := BASS_RecordStart(44100, 1, 0, @TForm1.RecordingCallback, nil);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Timer1.Enabled := false;
rchan := BASS_RecordStart(44100, 1, BASS_RECORD_PAUSE, @TForm1.RecordingCallback, nil);
//BASS_Free();
end;
procedure TForm1.ComboBox1Change(Sender: TObject);
var
i: Integer;
r: Boolean;
begin
// enable the selected input
r := True;
i := 0;
// first disable all inputs, then...
while r do
begin
r := BASS_RecordSetInput(i, BASS_INPUT_OFF, -1);
Inc(i);
end;
// ...enable the selected.
BASS_RecordSetInput(ComboBox1.ItemIndex, BASS_INPUT_ON, -1);
//UpdateInputInfo; // update info
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
BASS_RecordFree;
BASS_Free();
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
dName: PAnsiChar;
level: Single;
flags: dWord;
deviceInfo: BASS_DEVICEINFO;
info: BASS_INFO;
begin
// check the correct BASS was loaded
if (HIWORD(BASS_GetVersion) <> BASSVERSION) then
begin
MessageBox(0,'An incorrect version of BASS.DLL was loaded', nil,MB_ICONERROR);
Halt;
end;
if (not BASS_RecordInit(-1)) or (not BASS_Init(-1, 44100, 0, Handle, nil)) then
begin
BASS_RecordFree;
BASS_Free();
MessageDlg('Cannot start default recording device!', mtError, [mbOk], 0);
Halt;
end;
i := 0;
// dName := BASS_RecordGetInputName(i);
//dName := (BASS_RecordGetDeviceInfo(i,deviceInfo));
while (BASS_RecordGetDeviceInfo(i,deviceInfo)) do
begin
//BASS_GetInfo(info);
ComboBox1.Items.Add(String(deviceInfo.name));
// is this one currently "on"?
//flags := BASS_RecordGetInput(i, level);
//if (flags and BASS_INPUT_TYPE_MASK) = BASS_INPUT_TYPE_MIC then
if (BASS_RecordGetInput(i, level) and BASS_INPUT_OFF) = 0 then
ComboBox1.ItemIndex := i;
Inc(i);
//dName := BASS_RecordGetInputName(i);
end;
ComboBox1Change(Self); // display info
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
eu: array [0..1] of TInput;
//S: String;
begin
//S:='v';
level2:=BASS_ChannelGetLevel(rchan);
inc(Counter1);
LoudEnough := (LoWord(level2) >= threshold) or (HiWord(level2) >= threshold);
if (LoudEnough = true) then
begin
inc(MicON_Timer);
if (MicON_Bool = false) then
begin
MicON_Bool := true;
//keybd_event(ord('v'), MapVirtualKey(ord('v'), 0), KEYEVENTF_KEYUP, 0);
//keybd_event(ord('v'), MapVirtualKey(ord('v'), 0), 0, 0);
ZeroMemory(@eu,sizeof(eu));
eu[0].Itype := INPUT_KEYBOARD;
eu[0].ki.dwFlags := KEYEVENTF_UNICODE;
eu[0].ki.wVk := 0;
eu[0].ki.wScan := ord('v');
eu[0].ki.Time := 0;
SendInput(1,eu[0],sizeof(TInput));
Memo1.Lines.add('Push to Talk ON');
Timer2.Enabled := true;
end;
end;
//if LoudEnough then Memo1.Lines.add('Push to Talk ON')
//else Memo1.Lines.add('Push to Talk OFF');
//Memo1.Lines.add('Loword ' + LoWord(level2).ToString +' - HiWord '+ HiWord(level2).ToString + ' - AVG: ' + MicON_Timer.ToString);
end;
procedure TForm1.Timer2Timer(Sender: TObject);
var
eu: array [0..1] of TInput;
begin
dec(MicON_Timer);
if MicON_Timer <= 0 then
begin
Memo1.Lines.add('Push to Talk OFF');
//keybd_event(ord('v'), MapVirtualKey(ord('v'), 0), KEYEVENTF_KEYUP, 0);
ZeroMemory(@eu,sizeof(eu));
eu[0].Itype := INPUT_KEYBOARD;
eu[0].ki.dwFlags := KEYEVENTF_UNICODE or KEYEVENTF_KEYUP;
eu[0].ki.wVk := 0;
eu[0].ki.wScan := ord('v');
eu[0].ki.Time := 0;
SendInput(1,eu[0],sizeof(TInput));
MicON_Bool := false;
Counter1 := 0;
MicON_Timer := 0;
Timer2.Enabled := false;
end;
end;
end.
我设计了一个简单的示例,当用户在 TButton 上单击鼠标时,它会每 250 毫秒模拟一次击键,直到用户释放鼠标按钮。
OnMouseButtonDown 启动一个 250mS 计时器,OnMouseButtonUp 停止计时器。 OnTimer 发送键盘事件。当鼠标离开表单时,计时器也会停止。
.PAS 文件:
unit KbdEmulDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Timer1: TTimer;
Memo1: TMemo;
procedure Button1MouseDown(Sender: TObject; Button: TMouseButton; Shift:
TShiftState; X, Y: Integer);
procedure Button1MouseLeave(Sender: TObject);
procedure Button1MouseUp(Sender: TObject; Button: TMouseButton; Shift:
TShiftState; X, Y: Integer);
procedure Timer1Timer(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1MouseDown(
Sender : TObject;
Button : TMouseButton;
Shift : TShiftState;
X, Y : Integer);
begin
// Set focus on Memo1 so that it will receive keyboard input
Memo1.SetFocus;
// Start the timer sending keyboard event
Timer1.Interval := 250;
Timer1.Enabled := TRUE;
// Call OnTimer immediately to key first key event right now
Timer1.OnTimer(nil);
end;
procedure TForm1.Button1MouseUp(
Sender : TObject;
Button : TMouseButton;
Shift : TShiftState;
X, Y : Integer);
begin
// Stop timer, this will stop key event
Timer1.Enabled := FALSE;
end;
procedure TForm1.Button1MouseLeave(Sender: TObject);
begin
// Stop timer, this will stop key event
Timer1.Enabled := FALSE;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
Eu: array [0..1] of TInput;
begin
ZeroMemory(@Eu, SizeOf(Eu));
Eu[0].Itype := INPUT_KEYBOARD;
Eu[0].ki.dwFlags := KEYEVENTF_UNICODE;
Eu[0].ki.wVk := 0;
Eu[0].ki.wScan := Ord('v');
Eu[0].ki.Time := 0;
SendInput(1, Eu[0], Sizeof(TInput));
end;
end.
和 DFM 文件:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 299
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Button1: TButton
Left = 24
Top = 28
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 0
OnMouseDown = Button1MouseDown
OnMouseLeave = Button1MouseLeave
OnMouseUp = Button1MouseUp
end
object Memo1: TMemo
Left = 20
Top = 76
Width = 605
Height = 213
Lines.Strings = (
'Memo1')
TabOrder = 1
end
object Timer1: TTimer
OnTimer = Timer1Timer
Left = 168
Top = 24
end
end
What about SendKeys.Send ?
假设目标应用程序没有 DCOM 等效项,因为 SendKeys.Send 以活动应用程序为目标,因此如果焦点被另一个应用程序更改,您将无法获得所需的结果。
我正在尝试模拟按下键盘 V 键,同时使用 BASS 库来自动化语音激活一键通。我已经让 BASS 库正常工作,只是无法让键盘模拟按住某个键任何时间!
编辑:
我正在尝试让另一个应用程序 ('TeamSpeak 3') 将我的 Key Press & Hold 识别为基于硬件的 Key Press & Hold 而不是基于软件的 Key Press & Hold。通过我的应用程序帮助模拟一键通。我会公开为任何想要它的人提供源代码,但我不会出于任何原因发布我的应用程序。它是供我个人使用的,如果它能工作,我感到好奇os?我明白,任何形式的滥用此类应用程序,我都认为是我个人的责任。
Edit2:我做了广泛的研究。我想我将不得不使用我的旧 Android 手持设备或 Raspberry Pi。我有一个 Raspberry Pi 零,所以我要看看是否可以将它创建为硬件键盘。我将在 Delphi 中编写一个程序来连接它(我有 Delphi 10.4.1 企业版,希望它能与 Raspberry Pi 的 linux 版本一起工作。)我有在我的计算机上可以预编译的 vmware Debian 和 Ubuntu os?不管怎样,文章在这里:https://randomnerdtutorials.com/raspberry-pi-zero-usb-keyboard-hid/
我将继续允许下面的答案,因为它基本上符合我之前的要求。比我的要求更进一步需要大量的工作。如果可以正常使用,我会提供更新。
(Delphi 10.4.1 / 目标 Windows 32 位)
这是我当前的源代码:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.MPlayer, System.UITypes, BASS,
Vcl.ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Memo1: TMemo;
Timer1: TTimer;
ComboBox1: TComboBox;
Timer2: TTimer;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure ComboBox1Change(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Timer2Timer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
function RecordingCallback(h:HRECORD; b:Pointer; l,u: DWord): boolean; stdcall;
end;
var
Form1: TForm1;
rchan: HRECORD; // recording channel
level2: dword;
LoudEnough: boolean = FALSE;
threshold: DWORD = 500; // trigger level
MicON_Timer, Counter1: Cardinal;
MicON_Bool : Boolean;
implementation
{$R *.dfm}
(* This function called while recording audio *)
function TForm1.RecordingCallback(h:HRECORD; b:Pointer; l,u: DWord): boolean; stdcall;
//var level:dword;
begin
level2:=BASS_ChannelGetLevel(h);
LoudEnough := (LoWord(level2) >= threshold) or (HiWord(level2) >= threshold);
//Memo1.Lines.add('Loword ' + IntToStr(LoWord(level))+' - HiWord '+IntToStr(HiWord(level)));
Result := True;
end;
// START BUTTON
procedure TForm1.Button1Click(Sender: TObject);
begin
{
if BASS_RecordSetDevice(0) = false then
begin
memo1.Lines.Add('BASS_RecordSetDevice ERROR = '+ BASS_ErrorGetCode().ToString);
end;}
Counter1 := 0;
MicON_Timer := 0;
Timer1.Enabled := true;
ComboBox1Change(Self);
rchan := BASS_RecordStart(44100, 1, 0, @TForm1.RecordingCallback, nil);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Timer1.Enabled := false;
rchan := BASS_RecordStart(44100, 1, BASS_RECORD_PAUSE, @TForm1.RecordingCallback, nil);
//BASS_Free();
end;
procedure TForm1.ComboBox1Change(Sender: TObject);
var
i: Integer;
r: Boolean;
begin
// enable the selected input
r := True;
i := 0;
// first disable all inputs, then...
while r do
begin
r := BASS_RecordSetInput(i, BASS_INPUT_OFF, -1);
Inc(i);
end;
// ...enable the selected.
BASS_RecordSetInput(ComboBox1.ItemIndex, BASS_INPUT_ON, -1);
//UpdateInputInfo; // update info
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
BASS_RecordFree;
BASS_Free();
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
dName: PAnsiChar;
level: Single;
flags: dWord;
deviceInfo: BASS_DEVICEINFO;
info: BASS_INFO;
begin
// check the correct BASS was loaded
if (HIWORD(BASS_GetVersion) <> BASSVERSION) then
begin
MessageBox(0,'An incorrect version of BASS.DLL was loaded', nil,MB_ICONERROR);
Halt;
end;
if (not BASS_RecordInit(-1)) or (not BASS_Init(-1, 44100, 0, Handle, nil)) then
begin
BASS_RecordFree;
BASS_Free();
MessageDlg('Cannot start default recording device!', mtError, [mbOk], 0);
Halt;
end;
i := 0;
// dName := BASS_RecordGetInputName(i);
//dName := (BASS_RecordGetDeviceInfo(i,deviceInfo));
while (BASS_RecordGetDeviceInfo(i,deviceInfo)) do
begin
//BASS_GetInfo(info);
ComboBox1.Items.Add(String(deviceInfo.name));
// is this one currently "on"?
//flags := BASS_RecordGetInput(i, level);
//if (flags and BASS_INPUT_TYPE_MASK) = BASS_INPUT_TYPE_MIC then
if (BASS_RecordGetInput(i, level) and BASS_INPUT_OFF) = 0 then
ComboBox1.ItemIndex := i;
Inc(i);
//dName := BASS_RecordGetInputName(i);
end;
ComboBox1Change(Self); // display info
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
eu: array [0..1] of TInput;
//S: String;
begin
//S:='v';
level2:=BASS_ChannelGetLevel(rchan);
inc(Counter1);
LoudEnough := (LoWord(level2) >= threshold) or (HiWord(level2) >= threshold);
if (LoudEnough = true) then
begin
inc(MicON_Timer);
if (MicON_Bool = false) then
begin
MicON_Bool := true;
//keybd_event(ord('v'), MapVirtualKey(ord('v'), 0), KEYEVENTF_KEYUP, 0);
//keybd_event(ord('v'), MapVirtualKey(ord('v'), 0), 0, 0);
ZeroMemory(@eu,sizeof(eu));
eu[0].Itype := INPUT_KEYBOARD;
eu[0].ki.dwFlags := KEYEVENTF_UNICODE;
eu[0].ki.wVk := 0;
eu[0].ki.wScan := ord('v');
eu[0].ki.Time := 0;
SendInput(1,eu[0],sizeof(TInput));
Memo1.Lines.add('Push to Talk ON');
Timer2.Enabled := true;
end;
end;
//if LoudEnough then Memo1.Lines.add('Push to Talk ON')
//else Memo1.Lines.add('Push to Talk OFF');
//Memo1.Lines.add('Loword ' + LoWord(level2).ToString +' - HiWord '+ HiWord(level2).ToString + ' - AVG: ' + MicON_Timer.ToString);
end;
procedure TForm1.Timer2Timer(Sender: TObject);
var
eu: array [0..1] of TInput;
begin
dec(MicON_Timer);
if MicON_Timer <= 0 then
begin
Memo1.Lines.add('Push to Talk OFF');
//keybd_event(ord('v'), MapVirtualKey(ord('v'), 0), KEYEVENTF_KEYUP, 0);
ZeroMemory(@eu,sizeof(eu));
eu[0].Itype := INPUT_KEYBOARD;
eu[0].ki.dwFlags := KEYEVENTF_UNICODE or KEYEVENTF_KEYUP;
eu[0].ki.wVk := 0;
eu[0].ki.wScan := ord('v');
eu[0].ki.Time := 0;
SendInput(1,eu[0],sizeof(TInput));
MicON_Bool := false;
Counter1 := 0;
MicON_Timer := 0;
Timer2.Enabled := false;
end;
end;
end.
我设计了一个简单的示例,当用户在 TButton 上单击鼠标时,它会每 250 毫秒模拟一次击键,直到用户释放鼠标按钮。
OnMouseButtonDown 启动一个 250mS 计时器,OnMouseButtonUp 停止计时器。 OnTimer 发送键盘事件。当鼠标离开表单时,计时器也会停止。
.PAS 文件:
unit KbdEmulDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Timer1: TTimer;
Memo1: TMemo;
procedure Button1MouseDown(Sender: TObject; Button: TMouseButton; Shift:
TShiftState; X, Y: Integer);
procedure Button1MouseLeave(Sender: TObject);
procedure Button1MouseUp(Sender: TObject; Button: TMouseButton; Shift:
TShiftState; X, Y: Integer);
procedure Timer1Timer(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1MouseDown(
Sender : TObject;
Button : TMouseButton;
Shift : TShiftState;
X, Y : Integer);
begin
// Set focus on Memo1 so that it will receive keyboard input
Memo1.SetFocus;
// Start the timer sending keyboard event
Timer1.Interval := 250;
Timer1.Enabled := TRUE;
// Call OnTimer immediately to key first key event right now
Timer1.OnTimer(nil);
end;
procedure TForm1.Button1MouseUp(
Sender : TObject;
Button : TMouseButton;
Shift : TShiftState;
X, Y : Integer);
begin
// Stop timer, this will stop key event
Timer1.Enabled := FALSE;
end;
procedure TForm1.Button1MouseLeave(Sender: TObject);
begin
// Stop timer, this will stop key event
Timer1.Enabled := FALSE;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
Eu: array [0..1] of TInput;
begin
ZeroMemory(@Eu, SizeOf(Eu));
Eu[0].Itype := INPUT_KEYBOARD;
Eu[0].ki.dwFlags := KEYEVENTF_UNICODE;
Eu[0].ki.wVk := 0;
Eu[0].ki.wScan := Ord('v');
Eu[0].ki.Time := 0;
SendInput(1, Eu[0], Sizeof(TInput));
end;
end.
和 DFM 文件:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 299
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Button1: TButton
Left = 24
Top = 28
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 0
OnMouseDown = Button1MouseDown
OnMouseLeave = Button1MouseLeave
OnMouseUp = Button1MouseUp
end
object Memo1: TMemo
Left = 20
Top = 76
Width = 605
Height = 213
Lines.Strings = (
'Memo1')
TabOrder = 1
end
object Timer1: TTimer
OnTimer = Timer1Timer
Left = 168
Top = 24
end
end
What about SendKeys.Send ?
假设目标应用程序没有 DCOM 等效项,因为 SendKeys.Send 以活动应用程序为目标,因此如果焦点被另一个应用程序更改,您将无法获得所需的结果。