Created
June 25, 2023 09:32
-
-
Save SwadicalRag/bb7dc72793e1e2e59cfa8a9f0672bfe5 to your computer and use it in GitHub Desktop.
ffi-napi / ref-napi based typescript library to interact with the system clipboard using WinAPI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as ffi from "ffi-napi" | |
import * as ref from "ref-napi" | |
export enum ClipboardFormat { | |
TEXT = 1, | |
BITMAP = 2, | |
METAFILEPICT = 3, | |
SYLK = 4, | |
DIF = 5, | |
TIFF = 6, | |
OEMTEXT = 7, | |
DIB = 8, | |
PALETTE = 9, | |
PENDATA = 10, | |
RIFF = 11, | |
WAVE = 12, | |
UNICODETEXT = 13, | |
ENHMETAFILE = 14, | |
HDROP = 15, | |
LOCALE = 16, | |
DIBV5 = 17, | |
MAX = 18, | |
OWNERDISPLAY = 0x0080, | |
DSPTEXT = 0x0081, | |
DSPBITMAP = 0x0082, | |
DSPMETAFILEPICT = 0x0083, | |
DSPENHMETAFILE = 0x008E, | |
PRIVATEFIRST = 0x0200, | |
PRIVATELAST = 0x02FF, | |
GDIOBJFIRST = 0x0300, | |
GDIOBJLAST = 0x03FF, | |
} | |
var kernel32 = new ffi.Library("kernel32", { | |
GlobalSize: ["size_t", ["void*"]], | |
GlobalLock: ["void*", ["void*"]], | |
GlobalUnlock: ["int", ["void*"]], | |
GlobalAlloc: ["void*", ["int","int"]], | |
GetLastError: ["int", []], | |
}); | |
var user32 = new ffi.Library("user32", { | |
OpenClipboard: ["int", ["void*"]], | |
GetClipboardData: ["void*", ["uint"]], | |
CloseClipboard: ["int", []], | |
SetClipboardData: ["int", ["int","void*"]], | |
EmptyClipboard: ["int", []], | |
EnumClipboardFormats: ["int", ["int"]], | |
GetClipboardFormatNameA: ["int", ["int", "void*", "int"]], | |
RegisterClipboardFormatA: ["uint", ["void*"]], | |
IsClipboardFormatAvailable: ["bool", ["int"]], | |
}); | |
export class ClipboardController { | |
refCount = 0; | |
ref() { | |
if(!this.refCount++) user32.OpenClipboard(ref.NULL as any); | |
} | |
unref() { | |
if(!--this.refCount) user32.CloseClipboard(); | |
} | |
clear() { | |
user32.EmptyClipboard(); | |
} | |
write(format: ClipboardFormat,value: Buffer) { | |
let hMem = kernel32.GlobalAlloc(2,value.length + 1); | |
let loc = kernel32.GlobalLock(hMem).reinterpret(value.length + 1); | |
for(let i=0;i < value.length;i++) { | |
let val = value.readInt8(i); | |
loc.writeInt8(val,i); | |
} | |
loc.writeInt8(0,value.length); | |
let ok2 = kernel32.GlobalUnlock(hMem); | |
user32.SetClipboardData(format, hMem); | |
} | |
read(format: ClipboardFormat) { | |
let handle = user32.GetClipboardData(format); | |
let size = kernel32.GlobalSize(handle); | |
if(size == 0) {return null;} | |
let mem = kernel32.GlobalLock(handle).reinterpret(size as number); | |
let outBuf = Buffer.alloc(size as number); | |
for(let i=0;i < (size as number);i++) { | |
outBuf.writeInt8(mem.readInt8(i),i); | |
} | |
let ok2 = kernel32.GlobalUnlock(handle); | |
return outBuf; | |
} | |
isClipboardDataAvailableInFormat(format: ClipboardFormat) { | |
return user32.IsClipboardFormatAvailable(format); | |
} | |
getClipboardFormat(formatName: string) { | |
let allFormats = this.enumClipboardFormats(); | |
for(let existingFormat of allFormats.keys()) { | |
if(existingFormat === formatName) { | |
return allFormats.get(existingFormat)!; | |
} | |
} | |
let out = user32.RegisterClipboardFormatA(Buffer.from(formatName + "\0","binary") as any); | |
return out; | |
} | |
enumClipboardFormats() { | |
let out = new Map<string, number>(); | |
for(let format in ClipboardFormat) { | |
if(typeof ClipboardFormat[format] === "number") { | |
out.set(format,ClipboardFormat[format] as any); | |
} | |
} | |
let fmt = 0; | |
while(true) { | |
fmt = user32.EnumClipboardFormats(fmt); | |
if(fmt === 0) break; | |
let buf = Buffer.alloc(256,"\0","binary"); | |
let res = user32.GetClipboardFormatNameA(fmt,buf as any,255); | |
if(res !== 0) { | |
out.set(buf.toString().substr(0,res),fmt); | |
} | |
} | |
return out; | |
} | |
readText() { | |
return this.read(ClipboardFormat.TEXT)?.toString(); | |
} | |
readHTML() { | |
return this.read(this.getClipboardFormat("HTML Format"))?.toString(); | |
} | |
writeText(data: string) { | |
this.write(ClipboardFormat.TEXT,Buffer.from(data,"binary")); | |
} | |
writeHTML(data: string) { | |
let encodedData = `Version:0.9 | |
StartHTML:0 | |
EndHTML:0 | |
StartFragment:0 | |
EndFragment:0 | |
StartSelection:0 | |
EndSelection:0 | |
<!DOCTYPE> | |
<HTML><BODY> | |
<!--StartFragment --> | |
${data} | |
<!--EndFragment --> | |
</BODY> | |
</HTML>`; | |
this.write(this.getClipboardFormat("HTML Format"),Buffer.from(encodedData,"binary")); | |
} | |
} | |
export const winClipboardController = new ClipboardController(); | |
// winClipboardController.ref(); | |
// winClipboardController.clear(); | |
// winClipboardController.writeText("hello"); | |
// winClipboardController.writeHTML("<h1><u>hello</u></h1>"); | |
// console.log(winClipboardController.read(winClipboardController.getClipboardFormat("HTML Format"))?.toString()); | |
// console.log(winClipboardController.enumClipboardFormat()); | |
// let formats = winClipboardController.enumClipboardFormats(); | |
// for(let formatName of formats.keys()) { | |
// let format = formats.get(formatName)!; | |
// if(winClipboardController.isClipboardDataAvailableInFormat(format)) { | |
// console.log(formatName,winClipboardController.read(format)?.toString()); | |
// } | |
// } | |
// winClipboardController.unref(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment