Bunでffi使ってWin32API使ってクリップボード監視する
よくわからんけど動いたので
ChatGPTとかに生成したのが動かなかったのでそれを気合で修正…
参考
code:main.ts
import { decode } from "iconv-lite"
import { dlopen, FFIType, CString, ptr, JSCallback, type Pointer, toArrayBuffer } from "bun:ffi"
function toCString(value: string): Uint8Array {
return new TextEncoder().encode(value + "\0")
}
function shiftJISToUtf8(ptr: Pointer): string {
// Shift-JIS バッファを取得
const buf = new Uint8Array(toArrayBuffer(ptr))
const decodedText = decode(buf as Buffer, "Shift_JIS")
return decodedText
}
// user32.dll をロード
const user32 = dlopen("user32.dll", {
CreateWindowExW: {
args: [
FFIType.u32,
"pointer",
"pointer",
FFIType.u32,
FFIType.i32,
FFIType.i32,
FFIType.i32,
FFIType.i32,
"pointer",
"pointer",
"pointer",
"pointer",
],
returns: "pointer",
},
DispatchMessageW: { args: "pointer", returns: "bool" }, RegisterClassExW: { args: "pointer", returns: "bool" }, TranslateMessage: { args: "pointer", returns: "bool" }, AddClipboardFormatListener: { args: "pointer", returns: FFIType.bool }, OpenClipboard: { args: "pointer", returns: "bool" }, CloseClipboard: { args: [], returns: "bool" },
GetClipboardData: { args: FFIType.u32, returns: "pointer" }, })
// kernel32.dll をロード
const kernel32 = dlopen("kernel32.dll", {
GlobalLock: { args: "pointer", returns: "pointer" }, GlobalUnlock: { args: "pointer", returns: "bool" }, GetLastError: {
args: [],
returns: FFIType.u32,
},
FormatMessageW: {
args: [
FFIType.u32, // dwFlags
FFIType.ptr, // lpSource
FFIType.u32, // dwMessageId
FFIType.u32, // dwLanguageId
FFIType.ptr, // lpBuffer
FFIType.u32, // nSize
FFIType.ptr, // Arguments
],
returns: FFIType.u32,
},
})
// **1. ウィンドウクラスの登録**
const WNDCLASSEXW_SIZE = 80 // よくわからん
const wndClass = new Uint8Array(WNDCLASSEXW_SIZE)
const view = new DataView(wndClass.buffer)
// 構造体のフィールドを設定
view.setUint32(0, WNDCLASSEXW_SIZE, true) // cbSize
// よくわからん
const a = new JSCallback((pt: any, b: number | bigint, c: number | bigint, d: number | bigint) => {}, {
returns: "void",
})
if (a.ptr) {
view.setBigUint64(8, BigInt(a.ptr.valueOf()), true) // lpfnWndProc (ウィンドウプロシージャ)
} else {
throw new Error("ああああ")
}
// lpszClassName よくわからん
const className = toCString("ClipboardListener")
view.setBigUint64(64, BigInt(ptr(className)), true) // よくわからんけど64
// クラス登録
if (!user32.symbols.RegisterClassExW(ptr(wndClass))) {
// GetLastError を取得
const errorCode = kernel32.symbols.GetLastError()
console.log(Error Code: ${errorCode})
throw new Error("Failed to register window class")
}
console.log("クラス登録OK")
// **2. ウィンドウを作成**
const hwnd = user32.symbols.CreateWindowExW(0, ptr(className), null, 0, 0, 0, 0, 0, null, null, null, null) //よくわからん
if (!hwnd) {
// GetLastError を取得
const errorCode = kernel32.symbols.GetLastError()
console.log(Error Code: ${errorCode})
throw new Error("Failed to create window")
}
console.log("ウィンドウ作成OK")
// **3. クリップボードリスナーを登録**
if (!user32.symbols.AddClipboardFormatListener(hwnd)) {
throw new Error("Failed to add clipboard format listener")
}
console.log("Clipboard monitor started...")
// **4. メッセージループ**
const CF_UNICODETEXT = 1 // 13ではない
const msg = new Uint32Array(4)
while (true) {
if (user32.symbols.GetMessageW(ptr(msg), hwnd, 0, 0) > 0) {
// WM_CLIPBOARDUPDATE
if (user32.symbols.OpenClipboard(hwnd)) {
const hClipboardData = user32.symbols.GetClipboardData(CF_UNICODETEXT)
if (hClipboardData) {
const pText = kernel32.symbols.GlobalLock(hClipboardData)
if (pText) {
const text = shiftJISToUtf8(pText) //new CString(pText).toString()
console.log("Clipboard content:", text)
kernel32.symbols.GlobalUnlock(hClipboardData)
}
}
user32.symbols.CloseClipboard()
}
}
user32.symbols.TranslateMessage(ptr(msg))
user32.symbols.DispatchMessageW(ptr(msg))
}
}