discordとかLINEをCtrl+Enterで送信する2
前回の続きです
LINEだけに限定する
SetWindowsHookExは1スレッドか全スレッドのどちらかにしかフックできません。 このままではLINEと関係ないプロセスにもフックが入ってしまうのでLINEだけに限定します。
void SetMyKeyboardProcTarget() { HWND targetWnd; unsigned long processID = 0; targetWnd = FindWindow("Qt5QWindowIcon", NULL); GetWindowThreadProcessId(targetWnd, &processID); dwTargetProcessId = processID; }
LINEのウィンドウタイトル"Qt5QWindowIcon"を探してウィンドウハンドルからプロセスIDを保存しておきます。この関数はDLL_PROCESS_ATTACHのところで実行させます。 あとはsendInput関数を使用して改行なり送信なりを入力させればよいのですがこのままだとsendInputで送信したキーをフックしてまた送信と無限ループになってしまうためキーボードからなのかsendInputなのかを判定する機能を付けます。
キーボードかSendInputなのかを判定する
INPUT temp; temp.type = INPUT_KEYBOARD; temp.ki.wVk = key; temp.ki.wScan = MapVirtualKey(0, 0); temp.ki.dwFlags = iskeyUp;
DLLEXPORT LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam) { if (code < 0) { return CallNextHookEx(g_hHook, code, wParam, lParam); } DWORD dwPid = GetCurrentProcessId(); if (dwTargetProcessId == dwPid) { if (wParam == VK_RETURN && ((lParam & maskScan))) { if (GetKeyState(VK_CONTROL) & 0x8000) { SendInput(sendInput.size(), sendInput.data(), sizeof(INPUT)); return 0; } if (SendInput(returnInput.size(), returnInput.data(), sizeof(INPUT))) return 0; } return CallNextHookEx(g_hHook, code, wParam, lParam); } }
wScanを0にすることでキーボードかsendInputなのかを判定しています。これだけでLINEは完成です。
DiscordのほうはIMEに若干の癖がありますがほぼ同じです。ここまま完成でもよいですがexeが消えるとフックが外れて元に戻ってしまいます。
exeを常駐させればよいのですがやはりスタンドアロンのほうが夢があるので次はその説明に入ります。(完全なスタンドアロンではないですが..)
dllのコードを載せておきます。
#include "pch.h" #include <string> #include <iostream> #include <tchar.h> #include "dllmain.h" #include <vector> #define DLLEXPORT extern "C" __declspec(dllexport) HINSTANCE g_hInst; #pragma data_seg(".shared") HHOOK g_hHook = NULL; std::vector<INPUT> returnInput; std::vector<INPUT> sendInput; static DWORD dwTargetProcessId = 0; #pragma data_seg() #pragma comment(linker, "/SECTION:.shared,RWS") DLLEXPORT void HookEnd(); const long maskScan = ((long)pow(2, 8) - 1) << 16; void SetMyKeyboardProcTarget() { HWND targetWnd; unsigned long processID = 0; targetWnd = FindWindow("Qt5QWindowIcon", NULL); GetWindowThreadProcessId(targetWnd, &processID); dwTargetProcessId = processID; } void pushKey(std::vector<INPUT>& in, WORD key, int iskeyUp) { INPUT temp; temp.type = INPUT_KEYBOARD; temp.ki.wVk = key; temp.ki.wScan = MapVirtualKey(0, 0); temp.ki.dwFlags = iskeyUp; in.push_back(temp); } DLLEXPORT LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam) { if (code < 0) { return CallNextHookEx(g_hHook, code, wParam, lParam); } DWORD dwPid = GetCurrentProcessId(); if (dwTargetProcessId == dwPid) { if (wParam == VK_RETURN && ((lParam & maskScan))) { if (GetKeyState(VK_CONTROL) & 0x8000) { SendInput(sendInput.size(), sendInput.data(), sizeof(INPUT)); return 0; } if (SendInput(returnInput.size(), returnInput.data(), sizeof(INPUT))) return 0; } return CallNextHookEx(g_hHook, code, wParam, lParam); } } DLLEXPORT void HookStart() { g_hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyboardProc, g_hInst, 0); if (g_hHook == NULL) { MessageBox(NULL, "フック開始は失敗しました", "HookStart", MB_OK); return; } } DLLEXPORT void HookEnd() { UnhookWindowsHookEx(g_hHook); } BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { if (ul_reason_for_call == DLL_PROCESS_ATTACH) { SetMyKeyboardProcTarget(); if (sendInput.empty()) { pushKey(returnInput, VK_SHIFT, 0); pushKey(returnInput, VK_RETURN, 0); pushKey(returnInput, VK_RETURN, KEYEVENTF_KEYUP); pushKey(returnInput, VK_SHIFT, KEYEVENTF_KEYUP); pushKey(sendInput, VK_CONTROL, KEYEVENTF_KEYUP); pushKey(sendInput, VK_RETURN, 0); pushKey(sendInput, VK_RETURN, KEYEVENTF_KEYUP); } g_hInst = (HINSTANCE)hModule; // DLLモジュールのハンドル取得 } return TRUE; }
discordとかLINEをCtrl+Enterで送信する1
初めに
皆さん普段からLINEやdiscord、slackといった様々なコミュニケーションツールを使用していると思います。これらのツールで送信するときLINEやdiscordはEnter、slackやtwitterはctrl+enterとツールによってバラバラです。個人的には誤爆を防ぐためにすべてCtrl+Enterで統一したいところですが送信方法を設定できないツールも多く設定できてもAlt+Enterで送信とかいう微妙な設定しかありません。
そこでツールにキー入力(Ctrl+Enter)が入ったときに入力を内部でEnterに変えてツールに送ればCtrl+Enterで送信できるはずです。
キー入力乗っ取り
あるプログラムの中で任意のプログラムを動作させるにはdllインジェクションを行います。プログラムに流れるキー入力を監視しEnterが押されたら破棄し、改行キーを送り直します。Ctrl+Etnerが来ればEnterのみを送信します。
dllインジェクション
dllをプログラムに埋め込むにはSetWindowsHookEx関数を使用します。
HHOOK SetWindowsHookExW( int idHook, //フックタイプ HOOKPROC lpfn, //コールバック関数 HINSTANCE hmod, //dllハンドル DWORD dwThreadId//スレッド識別子 );
今回のフックタイプにはWH_KEYBOARDを指定します。このフックタイプはキーボードの入力があれば指定したコールバック関数が実行されます。 dllのエントリポイントでハンドルを取得しておきます。
BOOL APIENTRY DllMain(HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) { if (ul_reason_for_call == DLL_PROCESS_ATTACH) { g_hInst = (HINSTANCE)hModule; // DLLモジュールのハンドル取得 } return TRUE; }
そしてフックする関数を以下のように定義しておきます。
DLLEXPORT void HookStart() { g_hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyboardProc, g_hInst, 0); if (g_hHook == NULL) { MessageBox(NULL, "フック開始は失敗しました", "HookStart", MB_OK); return; } }
コールバック関数にはとりあえず適当に作っておきます。
DLLEXPORT LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam) { MessageBox(NULL, std::to_string(wParam).c_str(), "Hook", MB_OK); return CallNextHookEx(g_hHook, code, wParam, lParam); }
WH_KEYBOARDではwParamに入力されたキー情報が格納されています。
CallNextHookEx(g_hHook, code, wParam, lParam);では受け取った情報をそのまま親プロセスに渡します。NULL等を返せば横取りしたキー入力をそのまま破棄するのでなにも入力できなくなります。
ここまではdllに書くプログラムでしたが最初にこのdllを読み込んでHookStart()を実行しなければdllを流し込めません。 そこで、このdllを流し込むexeを作成していきます。
MSG msg; HINSTANCE hinst = LoadLibrary(TEXT("CTRLSENDLL.dll")); if (hinst) { typedef void (*Install)(); typedef void (*Uninstall)(); Install install = (Install)GetProcAddress(hinst, "HookStart"); install(); while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } }
これを実行すれば起動しているあらゆるプログラムにdllが注入されてキー入力するたびにメッセージボックスが出現します。
ここで注意するのはLINEやdiscordは32bitソフトなので流し込むdllも32bitで作成しないとdllインジェクションは失敗します。
続きはまた書きます。dllの全文は以下に置いておきます。
#include "pch.h" #include <string> #pragma comment(lib, "imm32.lib") #include "dllmain.h" #define DLLEXPORT extern "C" __declspec(dllexport) HINSTANCE g_hInst; #pragma data_seg(".shared") HHOOK g_hHook = NULL; #pragma data_seg() #pragma comment(linker, "/SECTION:.shared,RWS") DLLEXPORT LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam) { MessageBox(NULL, std::to_string(wParam).c_str(), "Hook", MB_OK); return CallNextHookEx(g_hHook, code, wParam, lParam); } DLLEXPORT void HookStart() { g_hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyboardProc, g_hInst, 0); if (g_hHook == NULL) { MessageBox(NULL, "フック開始は失敗しました", "HookStart", MB_OK); return; } } DLLEXPORT void HookEnd() { UnhookWindowsHookEx(g_hHook); } BOOL APIENTRY DllMain(HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) { if (ul_reason_for_call == DLL_PROCESS_ATTACH) { g_hInst = (HINSTANCE)hModule; // DLLモジュールのハンドル取得 } return TRUE; }
文鎮化したpro microをArduino Unoを使って直す
- はじめに
- 用意するもの
- Arduino Unoをavrライター化
- Arduino UnoとPro microをつなぐ
- avrdudeコマンドを使ってブートローダを書き込む
- Qmk Toolboxで通常通りファームウェアを書き込む
- おわりに
はじめに
少し前にCorne cherryという分離型の自作キーボードのPro microを文鎮化させてしまい、せっかくなので手元にあったArduino Unoを使って直してみたのでメモ代わりに置いておきます。
正常なPro microがあればわざわざArduino Unoなんて使わなくても直せるみたいです。
用意するもの
文鎮化したPro micro
Arduino Uno
ブレッドボード
ジャンパーピン
Qmk Toolbox
Arduino Unoをavrライター化
Arduino Unoを文鎮化したPro microに書き込めるようにavrライター化させます。
Arduino IDEを公式ページからダウンロードしてきます。Arduino UnoをつないでからIDEの
メニューのファイルからスケッチ例→11. ArduinolSPを開きArduino Unoにそのまま書き込みます。この時、書き込んだUnoのシリアルポートを覚えておきます。(自分の環境ではCOM10)
Arduino UnoとPro microをつなぐ
配線は以下の通りです
Uno | Pro micro |
---|---|
5V | VCC |
D12 | 14(PB1[SCK]) |
D13 | 15(PB3[MISO]) |
D11 | 16(PB2[MOSI]) |
GND | GND |
こんな感じ
avrdudeコマンドを使ってブートローダを書き込む
avrdudeコマンドはQmk Toolboxに入っているのでそれを使います。
コマンドプロンプトからQmk Toolboxのデータファイルに飛びます
cd "AppData\Local\QMK\QMK Toolbox\QMK Toolboxのバージョン\"
私の時のバージョンは0.0.20でした。 次のコマンドで書き込みます。
avrdude.exe -p atmega32u4 -c stk500v1 -b 19200 -U flash:w:"Caterina-Micro.hexファイル":i -P さっき調べたシリアルポート -U efuse:w:0xC3:m -U hfuse:w:0xD9:m -U lock:w:0x3F:m
私の場合はCOM10です。また、Caterina-Micro.hexファイルはパスなのでダブルコーテーションつけとくといいと思います。
すぐに書き込みが始まるはずですがエラーもでず、ずっと止まってたら一度Unoの電源(USB抜き差し)しておくと治ります。
Qmk Toolboxで通常通りファームウェアを書き込む
通常通りGNDとRSTをショートさせればファームウェアが書き込めるはずです。
おわりに
試行錯誤しながら変なブートローダー書き込んだりしていたらPro microの電源回りが壊れたのかUSB電源が供給されないという微妙に壊れたままです。
USBの通信そのものはできるらしいので書き込むときは5Vのピンヘッダから電源を供給しつつUSBをつないで書き込むというかなりめんどくさいことになってます。
chrome拡張機能で背景画像を設定する
はじめに
ソフトとかカスタマイズするときに背景画像を設定とか大好きなので普段使うchromeにも背景画像を設定します。
完成品はこんな感じになります。
見た目
chromeのそのままの文字だと背景画像が暗いときに見づらくなってしまうのでchrome://flags/からダークモードをオンにしておきます。(リンク化してありますがセキュリティ上直接URL欄に打たないと飛べません)明るい画像を背景画像にする場合はダークモードをオンにする必要はないと思います。
chrome拡張機能
色々な記事で解説されているので詳しい説明は省きますが最小構成は「manifest.json」があれば拡張機能として認識されるようです。拡張機能はchrome://extensions/からデベロッパーモードをオンにして「パッケージ化されていない拡張機能を読み込む」からmanifest.jsonとかが入っているファイルを選択してあげることで読み込めます。
manifest.jsonの内容は以下の通りです。
manifest.json
{ "manifest_version": 2, "name": "background", "description": "", "version": "1.0", "icons": { "32": "icon_32.png", "48": "icon_48.png", "128": "icon_128.png" }, "content_scripts": [{ "matches": ["http://*/*", "https://*/*" ], "js": ["jquery-3.5.1.min.js","content_scripts.js"] }], "browser_action": { "default_icon": "icon_32.png", "default_title": "", "default_popup": "popup.html" }, "permissions": [ "tabs", "background", "http://*/*", "https://*/*" ], "web_accessible_resources": [ "*.png" ] }
アイコン関連は省略可能です。書いた場合は実際にpngで画像を作成しておく必要があります。ペイントか何かで編集しておきましょう。
重要なのは以下の項目です。
"web_accessible_resources": [ "*.png" ]
これによりローカルで保存してあるpngファイルを読み込むことができます。manifest.jsonで宣言したファイル達をそれぞれ作成していきます。
jQueryはここから取ってきます。
popup.html
<!DOCTYPE HTML> <BODY> <script src="jquery-3.5.1.min.js"></script> <script src="content_scripts.js"></script> </BODY> </HTML>
content_scripts.js
$(document).ready(function() { var image = '<style type="text/css"> body{background-image:url(' image+=chrome.extension.getURL('background.png') image +=');background-attachment: fixed;}</style>' $('head').append($(image)) });
以上を保存して再度読み込ませれば完成です。
解説
やってることは単純です。chromeの検証からソースを見てみると以下のようなCSSが挿入されていることがわかります。
<style type="text/css"> body{ background-image:url(chrome-extension://aghlpijkpccdlefanpfdahlicenikdpd/background.png); background-attachment: fixed; } </style>
CSSでbackground-imageを付けると背景画像を設定することができるのでJavascriptを利用してこれを挿入しているわけです。 ローカル画像を読み込むには「chrome.extension.getURL('パス')」と記述する必要があります。また、ダークモード限定ですが画像の置き方を指定する「background-attachment:」はfixedで固定しておかないとダークモードの反転対象になって白くなるという悲惨なことが起きます。
popup.htmlには何も書いてないので複数画像を管理したり画像処理して明るさを暗くしたりといった機能を付けるのもありかもしれませんね。
このブログについて
主にプログラム関係でやったこととかメモ程度に残していきたいと思います。Qiitaとかでもよかったんですがもしかしたらプログラム関係ないことも書くかもしれないので念のためということで...