Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions .github/workflows/build-windows-fast-paste.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Build Windows Fast Paste

on:
push:
paths:
- 'resources/windows-fast-paste.c'
- '.github/workflows/build-windows-fast-paste.yml'
branches:
- main
workflow_dispatch:
inputs:
version:
description: 'Version tag (e.g., 1.0.0)'
required: false
default: '1.0.0'
type: string

permissions:
contents: write

jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4

- name: Setup MSVC
uses: microsoft/setup-msbuild@v2

- name: Setup MSVC environment
uses: ilammy/msvc-dev-cmd@v1

- name: Compile Windows Fast Paste
run: |
cl /O2 /nologo resources/windows-fast-paste.c /Fe:windows-fast-paste.exe user32.lib
shell: cmd

- name: Verify binary
run: |
if (Test-Path "windows-fast-paste.exe") {
Write-Host "Binary built successfully"
Get-Item "windows-fast-paste.exe" | Select-Object Name, Length
} else {
Write-Error "Binary not found"
exit 1
}
shell: pwsh

- name: Create zip archive
run: |
Compress-Archive -Path windows-fast-paste.exe -DestinationPath windows-fast-paste-win32-x64.zip
shell: pwsh

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: windows-fast-paste
path: windows-fast-paste-win32-x64.zip

- name: Create or update release
uses: softprops/action-gh-release@v2
with:
tag_name: windows-fast-paste-v${{ inputs.version || '1.0.0' }}
name: Windows Fast Paste v${{ inputs.version || '1.0.0' }}
body: |
Prebuilt Windows fast-paste binary for clipboard paste operations.

Uses Win32 SendInput API with automatic terminal detection
(Ctrl+V for normal apps, Ctrl+Shift+V for terminals).
files: windows-fast-paste-win32-x64.zip
make_latest: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1 change: 1 addition & 0 deletions electron-builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"llama-server-*",
"sherpa-onnx-*",
"windows-key-listener*",
"windows-fast-paste*",
"*.dylib",
"*.dll",
"*.so*"
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"compile:globe": "node scripts/build-globe-listener.js",
"compile:fast-paste": "node scripts/build-macos-fast-paste.js",
"compile:winkeys": "node scripts/build-windows-key-listener.js",
"compile:native": "npm run compile:globe && npm run compile:fast-paste && npm run compile:winkeys",
"compile:winpaste": "node scripts/build-windows-fast-paste.js",
"compile:native": "npm run compile:globe && npm run compile:fast-paste && npm run compile:winkeys && npm run compile:winpaste",
"prestart": "npm run compile:native",
"start": "electron .",
"predev": "npm run compile:native",
Expand Down
142 changes: 142 additions & 0 deletions resources/windows-fast-paste.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/**
* Windows Fast Paste for OpenWhispr
*
* Detects the foreground window, checks if it's a terminal emulator,
* and simulates the appropriate paste keystroke using Win32 SendInput:
* - Ctrl+V for normal applications
* - Ctrl+Shift+V for terminal emulators
*
* Compile with: cl /O2 windows-fast-paste.c /Fe:windows-fast-paste.exe user32.lib
* Or with MinGW: gcc -O2 windows-fast-paste.c -o windows-fast-paste.exe -luser32
*/

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <string.h>

static const char* TERMINAL_CLASSES[] = {
"ConsoleWindowClass",
"CASCADIA_HOSTING_WINDOW_CLASS",
"mintty",
"VirtualConsoleClass",
"PuTTY",
"Alacritty",
"org.wezfurlong.wezterm",
"Hyper",
"TMobaXterm",
NULL
};

static BOOL IsTerminalClass(const char* className) {
for (int i = 0; TERMINAL_CLASSES[i] != NULL; i++) {
if (_stricmp(className, TERMINAL_CLASSES[i]) == 0) {
return TRUE;
}
}
return FALSE;
}

static int SendPasteNormal(void) {
INPUT inputs[4];
ZeroMemory(inputs, sizeof(inputs));

inputs[0].type = INPUT_KEYBOARD;
inputs[0].ki.wVk = VK_CONTROL;

inputs[1].type = INPUT_KEYBOARD;
inputs[1].ki.wVk = 'V';

inputs[2].type = INPUT_KEYBOARD;
inputs[2].ki.wVk = 'V';
inputs[2].ki.dwFlags = KEYEVENTF_KEYUP;

inputs[3].type = INPUT_KEYBOARD;
inputs[3].ki.wVk = VK_CONTROL;
inputs[3].ki.dwFlags = KEYEVENTF_KEYUP;

UINT sent = SendInput(4, inputs, sizeof(INPUT));
return (sent == 4) ? 0 : 1;
}

static int SendPasteTerminal(void) {
INPUT inputs[6];
ZeroMemory(inputs, sizeof(inputs));

inputs[0].type = INPUT_KEYBOARD;
inputs[0].ki.wVk = VK_CONTROL;

inputs[1].type = INPUT_KEYBOARD;
inputs[1].ki.wVk = VK_SHIFT;

inputs[2].type = INPUT_KEYBOARD;
inputs[2].ki.wVk = 'V';

inputs[3].type = INPUT_KEYBOARD;
inputs[3].ki.wVk = 'V';
inputs[3].ki.dwFlags = KEYEVENTF_KEYUP;

inputs[4].type = INPUT_KEYBOARD;
inputs[4].ki.wVk = VK_SHIFT;
inputs[4].ki.dwFlags = KEYEVENTF_KEYUP;

inputs[5].type = INPUT_KEYBOARD;
inputs[5].ki.wVk = VK_CONTROL;
inputs[5].ki.dwFlags = KEYEVENTF_KEYUP;

UINT sent = SendInput(6, inputs, sizeof(INPUT));
return (sent == 6) ? 0 : 1;
}

int main(int argc, char* argv[]) {
BOOL detectOnly = FALSE;

for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--detect-only") == 0) {
detectOnly = TRUE;
}
}

HWND hwnd = GetForegroundWindow();
if (!hwnd) {
fprintf(stderr, "ERROR: No foreground window found\n");
return 2;
}

char className[256];
int classLen = GetClassNameA(hwnd, className, sizeof(className));
if (classLen == 0) {
fprintf(stderr, "ERROR: Could not get window class name (error %lu)\n", GetLastError());
return 1;
}

BOOL isTerminal = IsTerminalClass(className);

if (detectOnly) {
printf("WINDOW_CLASS %s\n", className);
printf("IS_TERMINAL %s\n", isTerminal ? "true" : "false");
fflush(stdout);
return 0;
}

Sleep(5);

int result;
if (isTerminal) {
result = SendPasteTerminal();
} else {
result = SendPasteNormal();
}

if (result != 0) {
fprintf(stderr, "ERROR: SendInput failed (error %lu)\n", GetLastError());
return 1;
}

Sleep(20);

printf("PASTE_OK %s %s\n", className, isTerminal ? "ctrl+shift+v" : "ctrl+v");
fflush(stdout);

return 0;
}
Loading