diff --git a/main.cpp b/main.cpp index 7b51af4..b2e9eda 100644 --- a/main.cpp +++ b/main.cpp @@ -65,9 +65,40 @@ static std::string json_escape(const std::string & s) { return out; } +static std::vector read_clipboard() { + const char * cmd = nullptr; + if (getenv("WAYLAND_DISPLAY")) { + cmd = "wl-paste 2>/dev/null"; + } else { + cmd = "xclip -selection clipboard -t image/png -o 2>/dev/null"; + } + std::vector data; + FILE * pipe = popen(cmd, "re"); + if (!pipe) return data; + unsigned char buf[65536]; + size_t n; + while ((n = fread(buf, 1, sizeof(buf), pipe)) > 0) { + data.insert(data.end(), buf, buf + n); + } + int st = pclose(pipe); + if (st != 0 || data.empty()) { + data.clear(); + // fallback for X11: try without explicit target + if (!getenv("WAYLAND_DISPLAY")) { + pipe = popen("xclip -selection clipboard -o 2>/dev/null", "re"); + if (pipe) { + while ((n = fread(buf, 1, sizeof(buf), pipe)) > 0) + data.insert(data.end(), buf, buf + n); + pclose(pipe); + } + } + } + return data; +} + static void print_usage(const char * prog) { fprintf(stderr, - "Usage: %s -m --mmproj [options] \n" + "Usage: %s -m --mmproj [options] []\n" "\n" "Options:\n" " -m, --model model file path (GGUF)\n" @@ -81,9 +112,13 @@ static void print_usage(const char * prog) { " --json output in JSON format\n" " -h show this help\n" "\n" - "Example:\n" - " %s -m model.gguf --mmproj mmproj.gguf -p \"OCR\" image.png\n", - prog, prog); + "If is omitted, reads image from clipboard.\n" + "Requires wl-paste (Wayland) or xclip (X11) for clipboard support.\n" + "\n" + "Examples:\n" + " %s -m model.gguf --mmproj mmproj.gguf -p \"OCR\" image.png\n" + " %s -m model.gguf --mmproj mmproj.gguf # use clipboard image\n", + prog, prog, prog); } int main(int argc, char ** argv) { @@ -134,17 +169,15 @@ int main(int argc, char ** argv) { } } - if ((optind + 1) != argc) { - print_usage(argv[0]); - return 1; - } - image_path = argv[optind]; - if (model_path.empty() || mmproj_path.empty()) { fprintf(stderr, "ERROR: -m/--model and --mmproj/--mm are required\n"); return 1; } + if (optind < argc) { + image_path = argv[optind]; + } + signal(SIGINT, sigint_handler); int64_t t_start_us = ggml_time_us(); @@ -190,13 +223,35 @@ int main(int argc, char ** argv) { int64_t t_loaded_us = ggml_time_us(); - mtmd_bitmap * bmp = mtmd_helper_bitmap_init_from_file(ctx_vision, image_path.c_str()); - if (!bmp) { - fprintf(stderr, "ERROR: failed to load image from %s\n", image_path.c_str()); - mtmd_free(ctx_vision); - llama_free(lctx); - llama_model_free(model); - return 1; + mtmd_bitmap * bmp = nullptr; + if (!image_path.empty()) { + bmp = mtmd_helper_bitmap_init_from_file(ctx_vision, image_path.c_str()); + if (!bmp) { + fprintf(stderr, "ERROR: failed to load image from %s\n", image_path.c_str()); + mtmd_free(ctx_vision); + llama_free(lctx); + llama_model_free(model); + return 1; + } + } else { + fprintf(stderr, "Reading image from clipboard...\n"); + auto clip = read_clipboard(); + if (clip.empty()) { + fprintf(stderr, "ERROR: clipboard is empty or no image found.\n" + "Make sure wl-paste (Wayland) or xclip (X11) is installed.\n"); + mtmd_free(ctx_vision); + llama_free(lctx); + llama_model_free(model); + return 1; + } + bmp = mtmd_helper_bitmap_init_from_buf(ctx_vision, clip.data(), clip.size()); + if (!bmp) { + fprintf(stderr, "ERROR: failed to decode clipboard image.\n"); + mtmd_free(ctx_vision); + llama_free(lctx); + llama_model_free(model); + return 1; + } } // Construct prompt using GLM-OCR chat template format