From 86991020ca6624fc88d7b8d88b9eab13d3684d74 Mon Sep 17 00:00:00 2001 From: "Christian G. Warden" Date: Fri, 22 Aug 2025 06:12:50 -0500 Subject: [PATCH 1/3] Add --clipboard Flag Add --clipboard flag to copy the full path to the screenshot to the clipboard. --- man/scrot.1 | 6 ++- src/options.c | 7 ++- src/options.h | 1 + src/scrot.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 2 deletions(-) diff --git a/man/scrot.1 b/man/scrot.1 index 8641a2b..c46e961 100644 --- a/man/scrot.1 +++ b/man/scrot.1 @@ -6,7 +6,7 @@ .SH SYNOPSIS .nf .fam C -\fBscrot\fP [\fB-bcfhimopuvz\fP] [\fB-a\fP \fIX,Y,W,H\fP] [\fB-C\fP \fINAME\fP] [\fB-D\fP \fIDISPLAY\fP] [\fB-d\fP \fISEC\fP] [\fB-e\fP \fICMD\fP] +\fBscrot\fP [\fB-bcfhimopuvxz\fP] [\fB-a\fP \fIX,Y,W,H\fP] [\fB-C\fP \fINAME\fP] [\fB-D\fP \fIDISPLAY\fP] [\fB-d\fP \fISEC\fP] [\fB-e\fP \fICMD\fP] [\fB-k\fP \fIOPT\fP] [\fB-l\fP \fISTYLE\fP] [\fB-M\fP \fINUM\fP] [\fB-n\fP \fIOPTS\fP] [\fB-q\fP \fINUM\fP] [\fB-s\fP \fIOPTS\fP] [\fB-t\fP % | \fIWxH\fP] [\fB-w\fP \fINUM\fP] [[\fB-F\fP] \fIFILE\fP] @@ -156,6 +156,10 @@ Window identifier to capture. WID must be a valid identifier (see \fBxwininfo\fP(1)). .TP .B +\fB-x\fP, \fB--clipboard\fP +Copy the output filename to the clipboard. +.TP +.B \fB-Z\fP, \fB--compression\fP LVL Compression level to use, LVL must be within [0, 9]. Higher level compression provides lower file 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..88525d9 100644 --- a/src/scrot.c +++ b/src/scrot.c @@ -43,6 +43,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#define _DEFAULT_SOURCE /* for usleep */ +#define _GNU_SOURCE /* for asprintf */ #include #include @@ -58,6 +60,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include +#include #include #include #include @@ -82,6 +85,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 +219,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 +651,118 @@ 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) { + if (asprintf(&fullPath, "%s/%s", cwd, filename) == -1) { + warn("Failed to create full path for clipboard"); + 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; + } + } + usleep(10000); /* 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) { From d636c47bc58f9d7d57ac474c097e3947ca09e42f Mon Sep 17 00:00:00 2001 From: "Christian G. Warden" Date: Fri, 22 Aug 2025 06:30:30 -0500 Subject: [PATCH 2/3] Improve BSD Compatibility --- src/scrot.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/scrot.c b/src/scrot.c index 88525d9..368dad8 100644 --- a/src/scrot.c +++ b/src/scrot.c @@ -43,8 +43,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#define _DEFAULT_SOURCE /* for usleep */ -#define _GNU_SOURCE /* for asprintf */ #include #include @@ -666,8 +664,12 @@ static void scrotCopyToClipboard(const char *filename) if (filename[0] != '/') { char *cwd = getcwd(NULL, 0); if (cwd) { - if (asprintf(&fullPath, "%s/%s", cwd, filename) == -1) { - warn("Failed to create full path for clipboard"); + 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; } @@ -750,7 +752,7 @@ static void scrotCopyToClipboard(const char *filename) break; } } - usleep(10000); /* Sleep for 10ms */ + scrotSleepFor(clockNow(), 10); /* Sleep for 10ms */ } if (!opt.silent) From 7bcd5c30ac2381b56d64e359871f5c77fb930005 Mon Sep 17 00:00:00 2001 From: "Christian G. Warden" Date: Fri, 22 Aug 2025 14:26:35 -0500 Subject: [PATCH 3/3] Update man/scrot.txt Instead of man/scrot.1 --- man/scrot.1 | 6 +----- man/scrot.txt | 3 ++- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/man/scrot.1 b/man/scrot.1 index c46e961..8641a2b 100644 --- a/man/scrot.1 +++ b/man/scrot.1 @@ -6,7 +6,7 @@ .SH SYNOPSIS .nf .fam C -\fBscrot\fP [\fB-bcfhimopuvxz\fP] [\fB-a\fP \fIX,Y,W,H\fP] [\fB-C\fP \fINAME\fP] [\fB-D\fP \fIDISPLAY\fP] [\fB-d\fP \fISEC\fP] [\fB-e\fP \fICMD\fP] +\fBscrot\fP [\fB-bcfhimopuvz\fP] [\fB-a\fP \fIX,Y,W,H\fP] [\fB-C\fP \fINAME\fP] [\fB-D\fP \fIDISPLAY\fP] [\fB-d\fP \fISEC\fP] [\fB-e\fP \fICMD\fP] [\fB-k\fP \fIOPT\fP] [\fB-l\fP \fISTYLE\fP] [\fB-M\fP \fINUM\fP] [\fB-n\fP \fIOPTS\fP] [\fB-q\fP \fINUM\fP] [\fB-s\fP \fIOPTS\fP] [\fB-t\fP % | \fIWxH\fP] [\fB-w\fP \fINUM\fP] [[\fB-F\fP] \fIFILE\fP] @@ -156,10 +156,6 @@ Window identifier to capture. WID must be a valid identifier (see \fBxwininfo\fP(1)). .TP .B -\fB-x\fP, \fB--clipboard\fP -Copy the output filename to the clipboard. -.TP -.B \fB-Z\fP, \fB--compression\fP LVL Compression level to use, LVL must be within [0, 9]. Higher level compression provides lower file 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.