Skip to content

Instantly share code, notes, and snippets.

@SwadicalRag
Created June 25, 2023 09:32
Show Gist options
  • Save SwadicalRag/bb7dc72793e1e2e59cfa8a9f0672bfe5 to your computer and use it in GitHub Desktop.
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
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