diff --git a/man/scrot.txt b/man/scrot.txt index 68565ed..a7d6b90 100644 --- a/man/scrot.txt +++ b/man/scrot.txt @@ -2,7 +2,7 @@ NAME scrot - command line screen capture utility SYNOPSIS - scrot [-bcfhimopuvz] [-a X,Y,W,H] [-C NAME] [-D DISPLAY] [-d SEC] [-e CMD] + scrot [-bcfhimopuvxz] [-a X,Y,W,H] [-C NAME] [-D DISPLAY] [-d SEC] [-e CMD] [-k OPT] [-l STYLE] [-M NUM] [-n OPTS] [-q NUM] [-s OPTS] [-t % | WxH] [-w NUM] [[-F] FILE] @@ -73,6 +73,7 @@ OPTIONS -v, --version Output version information and exit. -w, --window WID Window identifier to capture. WID must be a valid identifier (see xwininfo(1)). + -x, --clipboard Copy the output filename to the clipboard. -Z, --compression LVL Compression level to use, LVL must be within [0, 9]. Higher level compression provides lower file size at the cost of slower encoding/saving speed. diff --git a/src/options.c b/src/options.c index f6703b3..1a31350 100644 --- a/src/options.c +++ b/src/options.c @@ -81,7 +81,7 @@ enum { /* long opt only */ OPT_FORMAT = UCHAR_MAX + 1, OPT_LIST_OPTS, }; -static const char stropts[] = "a:bC:cD:d:e:F:fhik::l:M:mn:opq:S:s::t:uvw:Z:z"; +static const char stropts[] = "a:bC:cD:d:e:F:fhik::l:M:mn:opq:S:s::t:uvw:xZ:z"; // NOTE: make sure lopts and opt_description indexes are kept in sync static const struct option lopts[] = { {"autoselect", required_argument, NULL, 'a'}, @@ -111,6 +111,7 @@ static const struct option lopts[] = { {"focussed", no_argument, NULL, 'u'}, {"version", no_argument, NULL, 'v'}, {"window", required_argument, NULL, 'w'}, + {"clipboard", no_argument, NULL, 'x'}, {"compression", required_argument, NULL, 'Z'}, {"silent", no_argument, NULL, 'z'}, {"format", required_argument, NULL, OPT_FORMAT}, @@ -147,6 +148,7 @@ static const struct option_desc { /* u */ { "capture the currently focused window", "" }, /* v */ { "output version and exit", "" }, /* w */ { "X window ID to capture", "WID" }, + /* x */ { "copy the output filename to the clipboard", "" }, /* Z */ { "image compression level", "LVL" }, /* z */ { "prevent beeping", "" }, /* OPT_FORMAT */ { "specify output file format", "FMT" }, @@ -491,6 +493,9 @@ void optionsParse(int argc, char *argv[]) errmsg); } break; + case 'x': + opt.clipboard = true; + break; case 'Z': opt.compression = optionsParseNum(optarg, 0, 9, &errmsg); if (errmsg) { diff --git a/src/options.h b/src/options.h index e69673a..29560d3 100644 --- a/src/options.h +++ b/src/options.h @@ -100,6 +100,7 @@ struct ScrotOptions { bool overwrite; bool freeze; bool ignoreKeyboard; + bool clipboard; }; extern struct ScrotOptions opt; diff --git a/src/scrot.c b/src/scrot.c index 9c90b79..368dad8 100644 --- a/src/scrot.c +++ b/src/scrot.c @@ -58,6 +58,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include +#include #include #include #include @@ -82,6 +83,7 @@ static void scrotCheckIfOverwriteFile(char **); static void scrotExecApp(Imlib_Image, struct tm *, char *, char *); static char *imPrintf(const char *, struct tm *, const char *, const char *, Imlib_Image); +static void scrotCopyToClipboard(const char *); static char *scrotGetWindowName(Window); static Window scrotGetClientWindow(Display *, Window); static Window scrotFindWindowByProperty(Display *, const Window, const Atom); @@ -215,6 +217,9 @@ int main(int argc, char *argv[]) if (opt.exec) scrotExecApp(image, tm, filenameIM, filenameThumb); + if (opt.clipboard) + scrotCopyToClipboard(filenameIM); + imlib_context_set_image(image); imlib_free_image_and_decache(); free(filenameIM); @@ -644,6 +649,122 @@ static void scrotExecApp(Imlib_Image image, struct tm *tm, char *filenameIM, free(execStr); } +static void scrotCopyToClipboard(const char *filename) +{ + Atom clipboardAtom, targetsAtom, textAtom, utf8Atom; + Window clipboardOwner; + XEvent event; + int maxWait = 50; /* Wait up to 500ms for clipboard operations */ + char *fullPath = NULL; + + if (filename == NULL) + return; + + /* Convert relative path to absolute path */ + if (filename[0] != '/') { + char *cwd = getcwd(NULL, 0); + if (cwd) { + size_t pathLen = strlen(cwd) + 1 + strlen(filename) + 1; + fullPath = malloc(pathLen); + if (fullPath) { + snprintf(fullPath, pathLen, "%s/%s", cwd, filename); + } else { + warn("Failed to allocate memory for clipboard path"); + free(cwd); + return; + } + free(cwd); + } else { + warn("Failed to get current directory for clipboard"); + return; + } + } else { + fullPath = strdup(filename); + if (!fullPath) { + warn("Failed to allocate memory for clipboard path"); + return; + } + } + + const char *clipboardText = fullPath; + + /* Get necessary atoms */ + clipboardAtom = XInternAtom(disp, "CLIPBOARD", False); + targetsAtom = XInternAtom(disp, "TARGETS", False); + textAtom = XInternAtom(disp, "TEXT", False); + utf8Atom = XInternAtom(disp, "UTF8_STRING", False); + + /* Create a window to own the clipboard */ + clipboardOwner = XCreateSimpleWindow(disp, root, -10, -10, 1, 1, 0, 0, 0); + + /* Set the clipboard data as a property on our window */ + XChangeProperty(disp, clipboardOwner, clipboardAtom, utf8Atom, 8, + PropModeReplace, (unsigned char *)clipboardText, strlen(clipboardText)); + + /* Claim ownership of the clipboard */ + XSetSelectionOwner(disp, clipboardAtom, clipboardOwner, CurrentTime); + + /* Check if we successfully got ownership */ + if (XGetSelectionOwner(disp, clipboardAtom) != clipboardOwner) { + warnx("Failed to acquire clipboard ownership"); + XDestroyWindow(disp, clipboardOwner); + free(fullPath); + return; + } + + /* Process clipboard requests for a short time to ensure the data is available */ + XFlush(disp); + + while (maxWait-- > 0) { + if (XPending(disp)) { + XNextEvent(disp, &event); + + if (event.type == SelectionRequest) { + XSelectionRequestEvent *req = &event.xselectionrequest; + XSelectionEvent response; + + response.type = SelectionNotify; + response.requestor = req->requestor; + response.selection = req->selection; + response.target = req->target; + response.time = req->time; + response.property = None; + + if (req->target == targetsAtom) { + /* Respond with supported targets */ + Atom targets[] = { utf8Atom, textAtom, XA_STRING }; + XChangeProperty(disp, req->requestor, req->property, + XA_ATOM, 32, PropModeReplace, + (unsigned char *)targets, 3); + response.property = req->property; + } else if (req->target == utf8Atom || req->target == textAtom || + req->target == XA_STRING) { + /* Provide the filename text */ + XChangeProperty(disp, req->requestor, req->property, + req->target, 8, PropModeReplace, + (unsigned char *)clipboardText, strlen(clipboardText)); + response.property = req->property; + } + + XSendEvent(disp, req->requestor, False, 0, (XEvent *)&response); + } else if (event.type == SelectionClear) { + /* Lost clipboard ownership */ + break; + } + } + scrotSleepFor(clockNow(), 10); /* Sleep for 10ms */ + } + + if (!opt.silent) + fprintf(stderr, "Filename copied to clipboard: %s\n", clipboardText); + + free(fullPath); + + /* Note: We're intentionally not destroying the window here + * as it needs to persist to serve clipboard requests. + * It will be cleaned up when the program exits. */ +} + static char *imPrintf(const char *str, struct tm *tm, const char *filenameIM, const char *filenameThumb, Imlib_Image im) {