close

這兩個函式在 MSDN 上已被註明是過時的函式,M$ 也建議以 SendInput 取代。非到必要時,其實不建議用模擬輸入之方式,速度慢、穩定性差、mouse/keyboard 都會被鎖死。

模擬按鍵、滑鼠動作,其實有更好的選擇,如 AutoIt、AotoHotkey、按鍵精靈  等都是不錯的輔助,並不太建議用 C/C++ 、VB 、C# 等軟體達成。

 

首先還是不厭其煩,先提二個 API

SetCursorPos : 設定滑鼠位置。

SetForegroundWindow : 將視窗提到最上層。

 

同時下面講的方式其實很不入流,所謂不入流指的是它的架構並不好、難維護,

要做得好,架構預計必須採用 multi-thread、狀態機 方式設計。

 

滑鼠事件

 

滑鼠事件可用 mouse_event 達成,

 

VOID WINAPI mouse_event(

     _In_  DWORD dwFlags,

     _In_  DWORD dx,

     _In_  DWORD dy,

     _In_  DWORD dwData,

     _In_  ULONG_PTR dwExtraInfo

     );

 

關鍵在於第一個參數,msdn 上有個參數很多人不懂 ( 我也是後來問有玩硬體的人才懂的)  - MOUSEEVENTF_XDOWN、MOUSEEVENTF_XUP,其它的在 MSDN 大致都可忘文生義,都算明顯。

 

MOUSEEVENTF_ABSOLUTE:以絕對位置方式指定。

MOUSEEVENTF_LEFTDOWN:按下滑鼠左鍵。

MOUSEEVENTF_LEFTUP:放開滑鼠左鍵。

MOUSEEVENTF_MIDDLEDOWN:按下滑鼠中鍵。

MOUSEEVENTF_MIDDLEUP:放開滑鼠中鍵。

MOUSEEVENTF_MOVE:移動滑鼠游標位置。當有指定 MOUSEEVENTF_ABSOLUTE 時是以絕對位置移動,否則以相對位置移動。

MOUSEEVENTF_RIGHTDOWN:按下滑鼠右鍵。

MOUSEEVENTF_RIGHTUP:放開滑鼠右鍵。

MOUSEEVENTF_WHEEL:移動滑鼠滾輪。

MOUSEEVENTF_XDOWN:按下滑鼠 XButton。

MOUSEEVENTF_XUP:放開滑鼠 XButton。


 

XDOWN、XUP 指的是所謂的 XButton。什麼是 XButton ? 一些滑鼠除了左鍵、右鍵、滾輪(或中鍵) 之外,會在滑鼠側邊再額外加上一些按鍵,這些按鍵就稱為 XButton。有興趣的話可以到 羅技 查 G700 (G 系列大概都是遊戲用吧 ),裡面有 13 個 XButton。但這份 API 裡面參數只有 XButton1, XButton2。

其它 keyboard / mouse 名稱可稍參考  codeproject 這篇文章。關於其它一些注意事項,回到 msdn 查看較清楚。

 

/*
    mouse event
*/
#include <windows.h>
int main()
{
    // once L-click
    mouse_event (MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0 ) ;

    // once R-click
    mouse_event (MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0 ) ;

    // once L-double-click
    mouse_event (MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0 ) ;
    mouse_event (MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0 ) ;

    // mouse-move-absolute
    mouse_event (MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, 
        0 * 65536 / 1280 , 0 * 65536 / 1024, 0, 0 );
    return 0;
}

 

有得講的大概是移動部份,這裡它的算法是,解析度最小值設 0;解析度最大值設 65536,於是要經過一些轉換才可準確移動,而指定了 MOUSEEVENTF_ABSOLUTE 是以絕對位置方式指定,若不加上的話就變相對位置方式指定 ( 所以可以出現負值 )。

 

每 200ms 送出一個 'A"

 

這只是簡單的 keybd_event 範例,假設當按下一個 'A' 時,約如下。

 

/*
    send 'A' each 200 ms
*/
#include <windows.h>
int main()
{
    while(1){
        keybd_event('A', 0, 0, 0);
        keybd_event('A', 0, KEYEVENTF_KEYUP, 0);
        Sleep(200);
    }
    return 0;
}

 

上面程式可以拿記事本出來試。

按住 'X' 時,不停送出 'A'

 

上面的方法無疑沒辦法決定什麼時候停止送出訊息。於是換個方式,當按住 'X'  時,就不停的送出 'A',按鍵鬆開時就停下來。

這行為可能會讓人覺得很白痴,原因是因為一般打字的時候,如果一直按住 'A' ,它會每 20ms 重覆輸入 'A",但在一些軟體或遊戲上,必須先鬆開再按下才有效,故部份有人會有這問題存在。

 

關鍵在於 GetKeyState 這項 API 之傳回值 (引數是 virtual key code )。它傳回的是 SHORT 資料型別 (一般是給 16bits ),

正在按下時:最高 bit 設 1;

toggle off ( Caps-Lock):最低 bit 設 1;

toggle on ( Caps-Lock):最低 bit 設 0。

當最高 bit 設 1 的時候,便代表傳回的會是一個負數,於此可寫下要求之功能。

 

/*
    Press-Combo
*/
#include <windows.h>
int main()
{
    while(1)
        while(GetKeyState('X') < 0)  {
            keybd_event('A', 0, 0, 0);
            keybd_event('A', 0, KEYEVENTF_KEYUP, 0);
            Sleep(10); // important
        }
    return 0;
}

 

這種方法關鍵在於 Sleep 一定要有,如果沒有的話,由於處理速度太快,會有 lag 現象 (比如說,已經放開 'X' 鍵了,然後還多送幾次 'A' 鍵 )。同時 Sleep 要設多少.. 就自己去試試吧,筆者是設 10~50 左右。

上面程式可以拿記事本出來試。

 

ESC 結束程式,按一下 'X' 不停送出 'A',再按一下 'X' 就停下來

 

這個問題算是讓人覺得比較亂的。會放上一些錯誤想法的 code。首先,我們想確認 GetKeyState 到底有哪幾種狀態,這個小程式自己寫可以知道,只有四種:

 

0x0000 -> 放掉

0x0001 -> 放掉

0x8000 -> 壓住,從 0x0001 到 0x0000 之過渡期

0x8001 -> 壓住,從 0x0000 到 0x0001 之過渡期

 

所以,如果我們可以去看 GetKeyState('X'),如果是 0 的話就不停送出 'A',如果非零的話就停止這動作 ?? 

同時我們想先設定 'X' 的初始狀態,剛好又有兩個 API 叫 SetKeyboardState / GetKeyboardState  ,我們先拿這兩個 API 試一下對 'X' 這個鍵狀態的傳回值是怎樣

 

/*
    SetKeyboardState 
    GetKeyboardState

*/

#include <windows.h>
#include <stdio.h>
int main()
{
    BYTE State[256];
    
    GetKeyboardState(State);
    State['X'] = 0;
    SetKeyboardState(State);    
    while(1){
        printf("%04hx\n", GetKeyState('X'));
        Sleep(20);
    }
    return 0;
}

 

有興趣可以多跑幾次,一開始它的狀態也真的會是 0 ,且循環長得如下 (括號起來是循環)。

 

0x0000-> ( 0xff80->0x0000->0xff81->0x0001 )

 

尷尬的點來了,一開始設的 0x0000 有可能是紅色部份,也可能是藍色部份,

所以直接判斷 GetKeyState('X') 是否為 0/1 這方法是不可行的。

 

索性就直接再用一個 flag , 紀錄 'X' 鍵是否正在被按,如果 'X' 被按過的話, Flag = 1 - Flag,當 Flag == 1 成立的時候就送出 'A',這想法應該是可行的,但還少了一個邏輯:如果 'X' 一直長期被按住的話,依 os 預設的連 keyin 效果,可能每 20ms 就會將 Flag 切換一次,也就是鍵盤彈跳問題。

解除鍵盤彈跳問題也不難,又多了一個 flag, first_press, 平常都是設成 0,當探測到 GetKeyState('X') < 0 的時候就設成 1。

 

整體概念有點亂 ( 所以才說用狀態機維護會好點 ),程式碼大致如下。

 

/*
    Start - to
*/

#include <windows.h>
int main()
{
    static int Go=0;
    BYTE State[256];
    SHORT ret, first_press=1; /* first_press */
    
    while(GetKeyState(VK_ESCAPE) >=0 ){
        
        ret = GetKeyState('X');
        
        if(ret < 0 && first_press==1) Go = 1 - Go;
        if(Go==1) {
            keybd_event('A', 0, 0, 0);
            keybd_event('A', 0, KEYEVENTF_KEYUP, 0);
        }

        if(ret < 0 ) first_press = 0;
        else first_press=1;
        Sleep(5);
    }
    return 0;
}

 

上述程式可在記事本底下測試。

ESC 結束、X 連擊,加入 F1 禁能功能


再加入一個鍵:F1,F1 並不會使得程式結束,只會使得 X 連擊的功能暫時消失,直到下次再按一次 F1。當然, F1 也要考慮鍵盤彈跳問題。

 

/*
    enable
*/

#include <windows.h>
#include <stdio.h>
int main()
{
    static int Go=0;
    SHORT ret_X, ret_F1;
    SHORT first_X  = 1;
    SHORT first_F1 = 1; 
    SHORT enable   = 1;

    puts("\nCombo : X ");
    puts("Enable : F1");    
    puts("Exit : ESC");

    while(GetKeyState(VK_ESCAPE) >=0 ){
        
        ret_F1 = GetKeyState(VK_F1);
        ret_X  = GetKeyState('X');

        // ------------------------------------------------------
        // F1
        if(ret_F1 < 0 && first_F1){ /* Press F1 now */
            first_F1 = 0;
            Go = 0;
            enable = 1-enable;            
        } 
        if(ret_F1 >= 0) { /* Release F1 */
            first_F1 = 1;
        }

        // ------------------------------------------------------
        // X
        if(enable){
            if(ret_X < 0 && first_X) {  /* Press X */
                first_X = 0;
                Go = 1 - Go;
            }             
            if(ret_X >=0 ) {/* Release X */
                first_X = 1;
            }
            if(Go){
                keybd_event('A', 0, 0, 0);
                keybd_event('A', 0, KEYEVENTF_KEYUP, 0);
                Sleep(5);
            }            
        }
    }
    return 0;
}

 

上述程式可在記事本裡測試。

這支程式在測試時會發現,按下 F1 時同時也會呼叫 Help,這和使用 RegisterHotkey 有所不同, RegisterHotkey 會遮掉原本 HotKey 所對應之功能,但 keybd_event 並不會。

 

其他議題

 

像是 SendMessage、PostMessage 之類的方式,這在 另一篇文章 已有略提,就不再贅述。

SendInput 這支函式使用上其實也不難,重點都在填結構體怎麼填,有興趣可試試。

另外若是要做「截取」,也就是紀錄 keyboard 按鍵,與本文離題已甚遠,以後有空時再做介紹,

有興趣可先查 keyword : WH_KEYBOARD_LL 、SetWindowsHookEx

至於 C/C++ 有沒有別人包好 library 做這些事?這我就不清楚了。

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 edisonx 的頭像
    edisonx

    Edison.X. Blog

    edisonx 發表在 痞客邦 留言(0) 人氣()