【问题标题】:Do you know how to emulate holding down a keystroke for any period of time, then releasing it later?你知道如何模拟在任何时间段内按住一个键,然后再释放它吗?
【发布时间】:2020-11-28 20:18:22
【问题描述】:

我正在尝试模拟按下键盘 V 键,同时使用 BASS 库来自动化语音激活一键通。我已经让 BASS 库正常工作,只是无法让键盘模拟按住任意时间的键!

编辑: 我正在尝试让另一个应用程序(“TeamSpeak 3”)将我的按键并保持识别为基于硬件的按键并保持,而不是基于软件的按键并保持。通过我的应用程序帮助模拟一键通。我将为任何想要它的人公开源代码,但我不会出于任何原因发布我的应用程序。这是供我个人使用的,出于好奇,它是否可行?我理解任何滥用此类应用程序的行为均由我个人负责。

Edit2:我进行了广泛的研究。我想我将不得不使用我的旧 Android 手持设备或 Raspberry Pi。我有一个 Raspberry Pi Zero,所以我将看看是否可以将它创建为硬件键盘。我将在 Delphi 中编写一个程序来连接它(我有 Delphi 10.4.1 Enterprise 并希望它可以与 Raspberry Pi 的 linux 版本一起使用。)我的计算机上有一个 vmware Debian 和 Ubuntu 操作系统,我可以使用它进行预编译?总之文章在这里: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.

【问题讨论】:

  • 如果我理解你想要做什么,timer1 不应该在某个时候被禁用吗?
  • 我希望我不必去创建一个虚拟串口,然后通过那个虚拟串口模拟键盘信息?

标签: windows delphi key sendinput


【解决方案1】:

我设计了一个简单的示例,当用户在 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

【讨论】:

  • 如果目标应用程序使用 GetAsyncKeyState 会起作用吗?
  • 它适用于记事本,但不想与“针对真实硬件”的应用程序一起工作?我不确定如何使用 GetAsyncKeyState?不过我会查一下,看看能不能找到例子。在 Stack Overflow 中,没有关于 Delphi SetAsyncKeyState 的任何信息,我相信我不久前几乎没有看过。老实说,我正在尝试以 TeamSpeak 3 应用程序为目标,该应用程序可以强制玩家使用一键通,我想使用语音活动检测……我可以使用 BASS 来做到这一点,但一键通话暗示我通过我的应用按下按钮!
  • 你应该更好地解释你打算做什么。也许“按住按键”并不是您真正需要的。我了解到您有一个应用程序“TeamSpeak 3”,您想通过让另一个应用程序按下键盘而不是实际按下并按住键的物理人来作弊。请编辑您的问题,将所有信息放在那里而不是 cmets。
  • 我已经编辑了问题,选择了适用于我的问题的答案,因为如果在记事本上使用它确实适用于我的问题。我决定我将不得不使用我的 Delphi 10.4.1 Enterprise 为 Raspberry Pi 编写一个 linux 程序,并希望它能正常工作。我将使用我的 Raspberry Pi Zero 作为硬件键盘,这应该可以解决我遇到的问题。我知道 Delphi 10.4.1 Enterprise 目前还没有被批准用于 Raspberry Pi,所以我会先在 Ubuntu 上编译它并继续使用它?或者我可以向 Embarcadero 提交请求票,以添加对 Raspberry Pi 的支持。
【解决方案2】:

SendKeys.Send 呢?

假设目标应用程序没有 DCOM 等效项,因为 SendKeys.Send 以活动应用程序为目标,因此如果焦点被另一个应用程序更改,您不会获得所需的结果。

【讨论】:

    猜你喜欢
    • 2011-01-06
    • 1970-01-01
    • 2016-02-20
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    • 1970-01-01
    • 2013-10-10
    • 1970-01-01
    相关资源
    最近更新 更多