From f92c62127cdbe6fb0831e911c5c6dc2b5db7a7c4 Mon Sep 17 00:00:00 2001 From: bryan Date: Thu, 27 Aug 2026 13:33:41 +0200 Subject: Initial import --- selc.c | 548 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 548 insertions(+) create mode 100644 selc.c (limited to 'selc.c') diff --git a/selc.c b/selc.c new file mode 100644 index 0000000..1b8eb86 --- /dev/null +++ b/selc.c @@ -0,0 +1,548 @@ +/* See LICENSE file for copyright and license details. */ +#define WINVER 0x0601 +#define _WIN32_WINNT 0x0601 +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#define LEN(x) (sizeof(x) / sizeof((x)[0])) + +enum { + WM_SEL = WM_APP + 1 +}; + +struct Sel { + POINT down; + POINT up; + int dragged; + int clicks; + int shift; + int kbd; +}; + +static char *argv0; +static int verbose; +static HWND hwnd; +static HHOOK mousehook; +static HHOOK kbdhook; +static HANDLE quit; +static struct Sel sel; +static struct Sel pending; + +static void die(const char *fmt, ...); +static void usage(void); +static void attachio(int force); +static int samewin(HWND a, HWND b); +static int isclient(HWND hw, POINT pt); +static int ispass(HWND hw); +static int skipctrlc(HWND hw); +static int goodsel(const struct Sel *s); +static void sendctrlc(void); +static void docopy(void); +static void request(int kbd); +static LRESULT CALLBACK mouseproc(int code, WPARAM wp, LPARAM lp); +static LRESULT CALLBACK kbdproc(int code, WPARAM wp, LPARAM lp); +static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, + LPARAM lp); +static int runkey(int add); +static void signalquit(void); +static int loop(HINSTANCE inst); + +static void +die(const char *fmt, ...) +{ + va_list ap; + size_t n; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + n = strlen(fmt); + if (n && fmt[n - 1] == ':') + fprintf(stderr, " %lu\n", GetLastError()); + else + fputc('\n', stderr); + exit(1); +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-i] [-k] [-u] [-v]\n", + argv0 ? argv0 : "selc"); + exit(2); +} + +static void +attachio(int force) +{ + if (!AttachConsole(ATTACH_PARENT_PROCESS)) { + if (!force) + return; + AllocConsole(); + } + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); +} + +static int +samewin(HWND a, HWND b) +{ + if (!a || !b) + return 0; + if (a == b) + return 1; + return GetAncestor(a, GA_ROOT) == GetAncestor(b, GA_ROOT); +} + +static int +isclient(HWND hw, POINT pt) +{ + DWORD_PTR ht; + LPARAM lp; + + lp = MAKELPARAM((short)pt.x, (short)pt.y); + if (!SendMessageTimeoutA(hw, WM_NCHITTEST, 0, lp, + SMTO_ABORTIFHUNG | SMTO_NORMAL, HITTO, &ht)) + return 0; + return ht == HTCLIENT; +} + +static int +ispass(HWND hw) +{ + char cls[32]; + LONG style; + + if (!hw) + return 0; + if (!GetClassNameA(hw, cls, (int)sizeof(cls))) + return 0; + if (strcmp(cls, "Edit") != 0) + return 0; + style = GetWindowLongA(hw, GWL_STYLE); + return (style & ES_PASSWORD) != 0; +} + +static int +skipctrlc(HWND hw) +{ + char cls[64]; + HWND root; + int i; + + root = GetAncestor(hw, GA_ROOT); + if (!root) + root = hw; + if (!GetClassNameA(root, cls, (int)sizeof(cls))) + return 0; + for (i = 0; i < (int)LEN(nocctrlc); i++) + if (strcmp(cls, nocctrlc[i]) == 0) + return 1; + return 0; +} + +static int +goodsel(const struct Sel *s) +{ + HWND a, b; + + a = WindowFromPoint(s->down); + b = WindowFromPoint(s->up); + if (!a || !isclient(a, s->down)) + return 0; + if (!samewin(a, b)) + return 0; + return 1; +} + +static void +sendctrlc(void) +{ + INPUT in[4]; + int n, ctrl; + + ctrl = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0; + memset(in, 0, sizeof(in)); + n = 0; + if (!ctrl) { + in[n].type = INPUT_KEYBOARD; + in[n].ki.wVk = VK_CONTROL; + n++; + } + in[n].type = INPUT_KEYBOARD; + in[n].ki.wVk = 'C'; + n++; + in[n].type = INPUT_KEYBOARD; + in[n].ki.wVk = 'C'; + in[n].ki.dwFlags = KEYEVENTF_KEYUP; + n++; + if (!ctrl) { + in[n].type = INPUT_KEYBOARD; + in[n].ki.wVk = VK_CONTROL; + in[n].ki.dwFlags = KEYEVENTF_KEYUP; + n++; + } + SendInput((UINT)n, in, sizeof(INPUT)); +} + +static void +docopy(void) +{ + HWND fg, foc; + DWORD tid, mine, seq; + DWORD_PTR dummy; + int attached; + + if (GetAsyncKeyState(VK_MENU) & 0x8000) + return; + fg = GetForegroundWindow(); + if (!fg) + return; + mine = GetCurrentThreadId(); + tid = GetWindowThreadProcessId(fg, NULL); + attached = 0; + if (tid && tid != mine) + attached = AttachThreadInput(mine, tid, TRUE); + foc = GetFocus(); + if (!foc) + foc = fg; + if (ispass(foc)) + goto done; + seq = GetClipboardSequenceNumber(); + SendMessageTimeoutA(foc, WM_COPY, 0, 0, + SMTO_ABORTIFHUNG | SMTO_NORMAL, COPYTO, &dummy); + if (GetClipboardSequenceNumber() != seq) { + if (verbose) + fprintf(stderr, "selc: WM_COPY\n"); + goto done; + } + if (skipctrlc(foc) || skipctrlc(fg)) { + if (verbose) + fprintf(stderr, "selc: skip Ctrl+C\n"); + goto done; + } + sendctrlc(); + if (verbose) + fprintf(stderr, "selc: Ctrl+C\n"); +done: + if (attached) + AttachThreadInput(mine, tid, FALSE); +} + +static void +request(int kbd) +{ + sel.kbd = kbd; + pending = sel; + if (hwnd) + PostMessageA(hwnd, WM_SEL, 0, 0); +} + +static LRESULT CALLBACK +mouseproc(int code, WPARAM wp, LPARAM lp) +{ + MSLLHOOKSTRUCT *ms; + static POINT down, lastpt; + static int dragged, clicks, armed; + static DWORD lastup; + long dx, dy; + int dt, cx, cy; + + if (code != HC_ACTION) + return CallNextHookEx(mousehook, code, wp, lp); + ms = (MSLLHOOKSTRUCT *)lp; + if (wp == WM_LBUTTONDOWN) { + down = ms->pt; + dragged = 0; + armed = 1; + dt = (int)(ms->time - lastup); + cx = GetSystemMetrics(SM_CXDOUBLECLK); + cy = GetSystemMetrics(SM_CYDOUBLECLK); + if (DBLCOPY && dt >= 0 && + dt <= (int)GetDoubleClickTime() && + abs(ms->pt.x - lastpt.x) <= cx && + abs(ms->pt.y - lastpt.y) <= cy) + clicks++; + else + clicks = 1; + lastpt = ms->pt; + } else if (wp == WM_MOUSEMOVE) { + if (armed && !dragged) { + dx = (long)ms->pt.x - down.x; + dy = (long)ms->pt.y - down.y; + if (dx * dx + dy * dy >= + (long)MINPIX * MINPIX) + dragged = 1; + } + } else if (wp == WM_LBUTTONUP) { + lastup = ms->time; + lastpt = ms->pt; + if (armed) { + sel.down = down; + sel.up = ms->pt; + sel.dragged = dragged; + sel.clicks = clicks; + sel.shift = (GetAsyncKeyState(VK_SHIFT) & + 0x8000) != 0; + if (sel.dragged || + (DBLCOPY && sel.clicks >= 2) || + (SHIFTCLICK && sel.shift && + sel.clicks == 1)) + request(0); + } + armed = 0; + dragged = 0; + } else if (wp == WM_RBUTTONDOWN || wp == WM_MBUTTONDOWN) { + armed = 0; + dragged = 0; + } + return CallNextHookEx(mousehook, code, wp, lp); +} + +static LRESULT CALLBACK +kbdproc(int code, WPARAM wp, LPARAM lp) +{ + KBDLLHOOKSTRUCT *kb; + int shift, ctrl, alt, nav, akey; + + if (code != HC_ACTION) + return CallNextHookEx(kbdhook, code, wp, lp); + if (wp != WM_KEYUP && wp != WM_SYSKEYUP) + return CallNextHookEx(kbdhook, code, wp, lp); + kb = (KBDLLHOOKSTRUCT *)lp; + shift = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; + ctrl = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0; + alt = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; + nav = 0; + switch (kb->vkCode) { + case VK_LEFT: + case VK_RIGHT: + case VK_UP: + case VK_DOWN: + case VK_HOME: + case VK_END: + case VK_PRIOR: + case VK_NEXT: + nav = 1; + break; + } + akey = CTRLA && kb->vkCode == 'A' && ctrl && !shift && !alt; + if ((KEYSEL && nav && shift && !alt) || akey) + request(1); + return CallNextHookEx(kbdhook, code, wp, lp); +} + +static LRESULT CALLBACK +wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) +{ + switch (msg) { + case WM_SEL: + if (COPYWAIT > 0) + SetTimer(hw, 1, COPYWAIT, NULL); + else if (pending.kbd || goodsel(&pending)) + docopy(); + return 0; + case WM_TIMER: + if (wp != 1) + break; + KillTimer(hw, 1); + if (pending.kbd || goodsel(&pending)) + docopy(); + return 0; + case WM_DESTROY: + PostQuitMessage(0); + return 0; + } + return DefWindowProcA(hw, msg, wp, lp); +} + +static int +runkey(int add) +{ + HKEY k; + LONG e; + wchar_t path[MAX_PATH], val[MAX_PATH + 3]; + DWORD n; + const wchar_t *sub = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; + + e = RegCreateKeyExW(HKEY_CURRENT_USER, sub, 0, NULL, 0, + KEY_SET_VALUE | KEY_QUERY_VALUE, NULL, &k, NULL); + if (e != ERROR_SUCCESS) { + SetLastError((DWORD)e); + die("RegCreateKeyEx:"); + } + if (!add) { + RegDeleteValueW(k, L"selc"); + RegCloseKey(k); + if (verbose) + fprintf(stderr, "selc: uninstalled Run key\n"); + return 0; + } + n = GetModuleFileNameW(NULL, path, (DWORD)LEN(path)); + if (n == 0 || n >= LEN(path)) { + RegCloseKey(k); + die("GetModuleFileName:"); + } + if (wcschr(path, L' ')) { + val[0] = L'"'; + wcsncpy(val + 1, path, LEN(val) - 3); + val[LEN(val) - 2] = L'\0'; + n = (DWORD)wcslen(val); + if (n < LEN(val) - 1) { + val[n] = L'"'; + val[n + 1] = L'\0'; + } + } else { + wcsncpy(val, path, LEN(val)); + val[LEN(val) - 1] = L'\0'; + } + n = (DWORD)((wcslen(val) + 1) * sizeof(wchar_t)); + e = RegSetValueExW(k, L"selc", 0, REG_SZ, (BYTE *)val, n); + RegCloseKey(k); + if (e != ERROR_SUCCESS) { + SetLastError((DWORD)e); + die("RegSetValueEx:"); + } + if (verbose) + fprintf(stderr, "selc: installed Run key\n"); + return 0; +} + +static void +signalquit(void) +{ + HANDLE e; + + e = OpenEventA(EVENT_MODIFY_STATE, FALSE, "Local\\selc-quit"); + if (!e) + return; + SetEvent(e); + CloseHandle(e); +} + +static int +loop(HINSTANCE inst) +{ + WNDCLASSA wc; + MSG msg; + DWORD r; + + memset(&wc, 0, sizeof(wc)); + wc.lpfnWndProc = wndproc; + wc.hInstance = inst; + wc.lpszClassName = "selc"; + if (!RegisterClassA(&wc)) + die("RegisterClass:"); + hwnd = CreateWindowExA(0, "selc", "selc", WS_OVERLAPPED, + 0, 0, 0, 0, HWND_MESSAGE, NULL, inst, NULL); + if (!hwnd) + die("CreateWindowEx:"); + mousehook = SetWindowsHookExA(WH_MOUSE_LL, mouseproc, inst, 0); + if (!mousehook) + die("SetWindowsHookEx mouse:"); + if (KEYSEL || CTRLA) { + kbdhook = SetWindowsHookExA(WH_KEYBOARD_LL, kbdproc, + inst, 0); + if (!kbdhook) + die("SetWindowsHookEx kbd:"); + } + if (verbose) + fprintf(stderr, "selc: running\n"); + for (;;) { + r = MsgWaitForMultipleObjects(1, &quit, FALSE, + INFINITE, QS_ALLINPUT); + if (r == WAIT_OBJECT_0) + break; + if (r != WAIT_OBJECT_0 + 1) + die("MsgWaitForMultipleObjects:"); + while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) + goto done; + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + } +done: + if (kbdhook) + UnhookWindowsHookEx(kbdhook); + if (mousehook) + UnhookWindowsHookEx(mousehook); + return 0; +} + +int +main(int argc, char *argv[]) +{ + HINSTANCE inst; + HANDLE mtx; + char *a, *p; + int i, iflag, kflag, uflag; + + argv0 = argv[0] ? argv[0] : "selc"; + iflag = kflag = uflag = 0; + attachio(0); + for (i = 1; i < argc; i++) { + a = argv[i]; + if (a[0] != '-' || a[1] == '\0') + usage(); + if (a[1] == '-' && a[2] == '\0') { + i++; + break; + } + for (p = a + 1; *p; p++) + switch (*p) { + case 'i': + iflag = 1; + break; + case 'k': + kflag = 1; + break; + case 'u': + uflag = 1; + break; + case 'v': + verbose = 1; + break; + case 'h': + usage(); + break; /* NOTREACHED */ + default: + usage(); + } + } + if (i != argc) + usage(); + if (verbose) + attachio(1); + if (kflag) { + signalquit(); + return 0; + } + if (uflag) { + runkey(0); + signalquit(); + return 0; + } + if (iflag) + runkey(1); + + inst = GetModuleHandleA(NULL); + quit = CreateEventA(NULL, TRUE, FALSE, "Local\\selc-quit"); + if (!quit) + die("CreateEvent:"); + mtx = CreateMutexA(NULL, TRUE, "Local\\selc-inst"); + if (!mtx) + die("CreateMutex:"); + if (GetLastError() == ERROR_ALREADY_EXISTS) + return 0; + return loop(inst); +} -- cgit v1.2.3