scrot_selection.c (scrot-1.6.tar.bz2) | : | scrot_selection.c (scrot-1.7.tar.bz2) | ||
---|---|---|---|---|
/* scrot_selection.c | /* scrot_selection.c | |||
Copyright 2020-2021 Daniel T. Borelli <daltomi@disroot.org> | Copyright 2020-2021 Daniel T. Borelli <danieltborelli@gmail.com> | |||
Copyright 2021 Martin C <martincation@protonmail.com> | Copyright 2021 Martin C <martincation@protonmail.com> | |||
Copyright 2021 Peter Wu <peterwu@hotmail.com> | ||||
Copyright 2021 Wilson Smith <01wsmith+gh@gmail.com> | ||||
Permission is hereby granted, free of charge, to any person obtaining a copy | Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to | of this software and associated documentation files (the "Software"), to | |||
deal in the Software without restriction, including without limitation the | deal in the Software without restriction, including without limitation the | |||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |||
sell copies of the Software, and to permit persons to whom the Software is | sell copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in | The above copyright notice and this permission notice shall be included in | |||
all copies of the Software and its documentation and acknowledgment shall be | all copies of the Software and its documentation and acknowledgment shall be | |||
skipping to change at line 36 | skipping to change at line 38 | |||
/* | /* | |||
This file is part of the scrot project. | This file is part of the scrot project. | |||
Part of the code comes from the main.c file and maintains its authorship. | Part of the code comes from the main.c file and maintains its authorship. | |||
*/ | */ | |||
#include "scrot.h" | #include "scrot.h" | |||
#include "selection_classic.h" | #include "selection_classic.h" | |||
#include "selection_edge.h" | #include "selection_edge.h" | |||
struct selection_t** selection_get(void) | struct Selection** selectionGet(void) | |||
{ | { | |||
static struct selection_t* sel = NULL; | static struct Selection* sel = NULL; | |||
return &sel; | return &sel; | |||
} | } | |||
static void selection_allocate(void) | static void selectionAllocate(void) | |||
{ | { | |||
struct selection_t** sel = selection_get(); | struct Selection** sel = selectionGet(); | |||
*sel = calloc(1, sizeof(struct selection_t)); | *sel = calloc(1, sizeof(**sel)); | |||
} | } | |||
static void selection_deallocate(void) | static void selectionDeallocate(void) | |||
{ | { | |||
struct selection_t** sel = selection_get(); | struct Selection** sel = selectionGet(); | |||
free(*sel); | free(*sel); | |||
*sel = NULL; | *sel = NULL; | |||
} | } | |||
static void create_cursors(void) | static void createCursors(void) | |||
{ | { | |||
struct selection_t *const sel = *selection_get(); | struct Selection* const sel = *selectionGet(); | |||
assert(sel != NULL); | assert(sel != NULL); | |||
sel->cur_cross = XCreateFontCursor(disp, XC_cross); | if (opt.selection.mode == SELECTION_MODE_CAPTURE) | |||
sel->cur_angle_ne = XCreateFontCursor(disp, XC_ur_angle); | sel->curCross = XCreateFontCursor(disp, XC_cross); | |||
sel->cur_angle_nw = XCreateFontCursor(disp, XC_ul_angle); | else if (opt.selection.mode == SELECTION_MODE_HIDE) | |||
sel->cur_angle_se = XCreateFontCursor(disp, XC_lr_angle); | sel->curCross = XCreateFontCursor(disp, XC_spraycan); | |||
sel->cur_angle_sw = XCreateFontCursor(disp, XC_ll_angle); | else if (opt.selection.mode == SELECTION_MODE_BLUR) | |||
sel->curCross = XCreateFontCursor(disp, XC_box_spiral); | ||||
else // SELECTION_MODE_HOLE | ||||
sel->curCross = XCreateFontCursor(disp, XC_target); | ||||
sel->curAngleNE = XCreateFontCursor(disp, XC_ur_angle); | ||||
sel->curAngleNW = XCreateFontCursor(disp, XC_ul_angle); | ||||
sel->curAngleSE = XCreateFontCursor(disp, XC_lr_angle); | ||||
sel->curAngleSW = XCreateFontCursor(disp, XC_ll_angle); | ||||
} | } | |||
static void free_cursors(void) | static void freeCursors(void) | |||
{ | { | |||
struct selection_t *const sel = *selection_get(); | struct Selection* const sel = *selectionGet(); | |||
assert(sel != NULL); | assert(sel != NULL); | |||
XFreeCursor(disp, sel->cur_cross); | XFreeCursor(disp, sel->curCross); | |||
XFreeCursor(disp, sel->cur_angle_ne); | XFreeCursor(disp, sel->curAngleNE); | |||
XFreeCursor(disp, sel->cur_angle_nw); | XFreeCursor(disp, sel->curAngleNW); | |||
XFreeCursor(disp, sel->cur_angle_se); | XFreeCursor(disp, sel->curAngleSE); | |||
XFreeCursor(disp, sel->cur_angle_sw); | XFreeCursor(disp, sel->curAngleSW); | |||
} | } | |||
void selection_calculate_rect(int x0, int y0, int x1, int y1) | void selectionCalculateRect(int x0, int y0, int x1, int y1) | |||
{ | { | |||
struct selection_rect_t* const rect = scrot_selection_get_rect(); | struct SelectionRect* const rect = scrotSelectionGetRect(); | |||
rect->x = x0; | rect->x = x0; | |||
rect->y = y0; | rect->y = y0; | |||
rect->w = x1 - x0; | rect->w = x1 - x0; | |||
rect->h = y1 - y0; | rect->h = y1 - y0; | |||
if (rect->w == 0) rect->w++; | if (rect->w == 0) | |||
rect->w++; | ||||
if (rect->h == 0) rect->h++; | if (rect->h == 0) | |||
rect->h++; | ||||
if (rect->w < 0) { | if (rect->w < 0) { | |||
rect->x += rect->w; | rect->x += rect->w; | |||
rect->w = 0 - rect->w; | rect->w = 0 - rect->w; | |||
} | } | |||
if (rect->h < 0) { | if (rect->h < 0) { | |||
rect->y += rect->h; | rect->y += rect->h; | |||
rect->h = 0 - rect->h; | rect->h = 0 - rect->h; | |||
} | } | |||
} | } | |||
void scrot_selection_create(void) | void scrotSelectionCreate(void) | |||
{ | { | |||
selection_allocate(); | selectionAllocate(); | |||
struct selection_t *const sel = *selection_get(); | struct Selection* const sel = *selectionGet(); | |||
assert(sel != NULL); | assert(sel != NULL); | |||
create_cursors(); | createCursors(); | |||
if (0 == strncmp(opt.line_mode, LINE_MODE_CLASSIC, LINE_MODE_CLASSIC_LEN)) { | if (!strncmp(opt.lineMode, LINE_MODE_S_CLASSIC, LINE_MODE_L_CLASSIC)) { | |||
sel->create = selection_classic_create; | sel->create = selectionClassicCreate; | |||
sel->draw = selection_classic_draw; | sel->draw = selectionClassicDraw; | |||
sel->motion_draw = selection_classic_motion_draw; | sel->motionDraw = selectionClassicMotionDraw; | |||
sel->destroy = selection_classic_destroy; | sel->destroy = selectionClassicDestroy; | |||
} else if (0 == strncmp(opt.line_mode, LINE_MODE_EDGE, LINE_MODE_EDGE_LEN)) | } else if (!strncmp(opt.lineMode, LINE_MODE_S_EDGE, LINE_MODE_L_EDGE)) { | |||
{ | sel->create = selectionEdgeCreate; | |||
sel->create = selection_edge_create; | sel->draw = selectionEdgeDraw; | |||
sel->draw = selection_edge_draw; | sel->motionDraw = selectionEdgeMotionDraw; | |||
sel->motion_draw = selection_edge_motion_draw; | sel->destroy = selectionEdgeDestroy; | |||
sel->destroy = selection_edge_destroy; | ||||
} else { | } else { | |||
// It never happened, fix the options.c file | // It never happened, fix the options.c file | |||
assert(0); | assert(0); | |||
} | } | |||
sel->create(); | sel->create(); | |||
unsigned int const EVENT_MASK = ButtonMotionMask | ButtonPressMask | ButtonR eleaseMask; | unsigned int const EVENT_MASK = ButtonMotionMask | ButtonPressMask | ButtonR eleaseMask; | |||
if ((XGrabPointer (disp, root, False, EVENT_MASK, GrabModeAsync, | if ((XGrabPointer(disp, root, False, EVENT_MASK, GrabModeAsync, | |||
GrabModeAsync, root, sel->cur_cross, CurrentTime) != GrabSuccess)) { | GrabModeAsync, root, sel->curCross, CurrentTime) | |||
fprintf(stderr, "couldn't grab pointer\n"); | != GrabSuccess)) { | |||
scrot_selection_destroy(); | scrotSelectionDestroy(); | |||
exit(EXIT_FAILURE); | errx(EXIT_FAILURE, "couldn't grab pointer"); | |||
} | } | |||
} | } | |||
void scrot_selection_destroy(void) | void scrotSelectionDestroy(void) | |||
{ | { | |||
struct selection_t *const sel = *selection_get(); | struct Selection* const sel = *selectionGet(); | |||
XUngrabPointer(disp, CurrentTime); | XUngrabPointer(disp, CurrentTime); | |||
free_cursors(); | freeCursors(); | |||
XSync(disp, True); | XSync(disp, True); | |||
sel->destroy(); | sel->destroy(); | |||
selection_deallocate(); | selectionDeallocate(); | |||
} | } | |||
void scrot_selection_draw(void) | void scrotSelectionDraw(void) | |||
{ | { | |||
struct selection_t const *const sel = *selection_get(); | struct Selection const* const sel = *selectionGet(); | |||
sel->draw(); | sel->draw(); | |||
} | } | |||
void scrot_selection_motion_draw(int x0, int y0, int x1, int y1) | void scrotSelectionMotionDraw(int x0, int y0, int x1, int y1) | |||
{ | { | |||
struct selection_t const *const sel = *selection_get(); | struct Selection const* const sel = *selectionGet(); | |||
unsigned int const EVENT_MASK = ButtonMotionMask | ButtonReleaseMask; | unsigned int const EVENT_MASK = ButtonMotionMask | ButtonReleaseMask; | |||
Cursor cursor = None; | Cursor cursor = None; | |||
if (x1 > x0 && y1 > y0) { | if (x1 > x0 && y1 > y0) | |||
cursor = sel->cur_angle_se; | cursor = sel->curAngleSE; | |||
} else if (x1 > x0) { | else if (x1 > x0) | |||
cursor = sel->cur_angle_ne; | cursor = sel->curAngleNE; | |||
} else if (y1 > y0) { | else if (y1 > y0) | |||
cursor = sel->cur_angle_sw; | cursor = sel->curAngleSW; | |||
else | ||||
cursor = sel->curAngleNW; | ||||
XChangeActivePointerGrab(disp, EVENT_MASK, cursor, CurrentTime); | ||||
sel->motionDraw(x0, y0, x1, y1); | ||||
} | ||||
struct SelectionRect* scrotSelectionGetRect(void) | ||||
{ | ||||
return &(*selectionGet())->rect; | ||||
} | ||||
Status scrotSelectionCreateNamedColor(char const* nameColor, XColor* color) | ||||
{ | ||||
assert(nameColor != NULL); | ||||
assert(color != NULL); | ||||
return XAllocNamedColor(disp, XDefaultColormap(disp, DefaultScreen(disp)), | ||||
nameColor, color, &(XColor) {}); | ||||
} | ||||
void scrotSelectionGetLineColor(XColor* color) | ||||
{ | ||||
scrotSelectionSetDefaultColorLine(); | ||||
Status const ret = scrotSelectionCreateNamedColor(opt.lineColor, color); | ||||
if (!ret) { | ||||
scrotSelectionDestroy(); | ||||
errx(EXIT_FAILURE, "Error allocate color:%s", strerror(BadColor)); | ||||
} | ||||
} | ||||
void scrotSelectionSetDefaultColorLine(void) | ||||
{ | ||||
if (!opt.lineColor) | ||||
opt.lineColor = "gray"; | ||||
} | ||||
bool scrotSelectionGetUserSel(struct SelectionRect* selectionRect) | ||||
{ | ||||
static int xfd = 0; | ||||
static int fdSize = 0; | ||||
XEvent ev; | ||||
fd_set fdSet; | ||||
int count = 0, done = 0; | ||||
int rx = 0, ry = 0, rw = 0, rh = 0, isButtonPressed = 0; | ||||
Window target = None; | ||||
Status ret; | ||||
scrotSelectionCreate(); | ||||
xfd = ConnectionNumber(disp); | ||||
fdSize = xfd + 1; | ||||
ret = XGrabKeyboard(disp, root, False, GrabModeAsync, GrabModeAsync, Current | ||||
Time); | ||||
if (ret == AlreadyGrabbed) { | ||||
int attempts = 20; | ||||
struct timespec delay = { 0, 50 * 1000L * 1000L }; | ||||
do { | ||||
nanosleep(&delay, NULL); | ||||
ret = XGrabKeyboard(disp, root, False, GrabModeAsync, GrabModeAsync, | ||||
CurrentTime); | ||||
} while (--attempts > 0 && ret == AlreadyGrabbed); | ||||
} | ||||
if (ret != GrabSuccess) { | ||||
scrotSelectionDestroy(); | ||||
errx(EXIT_FAILURE, "failed to grab keyboard"); | ||||
} | ||||
while (1) { | ||||
/* Handle events here */ | ||||
while (!done && XPending(disp)) { | ||||
XNextEvent(disp, &ev); | ||||
switch (ev.type) { | ||||
case MotionNotify: | ||||
if (isButtonPressed) | ||||
scrotSelectionMotionDraw(rx, ry, ev.xbutton.x, ev.xbutton.y) | ||||
; | ||||
break; | ||||
case ButtonPress: | ||||
isButtonPressed = 1; | ||||
rx = ev.xbutton.x; | ||||
ry = ev.xbutton.y; | ||||
target = scrotGetWindow(disp, ev.xbutton.subwindow, ev.xbutton.x | ||||
, ev.xbutton.y); | ||||
if (target == None) | ||||
target = root; | ||||
break; | ||||
case ButtonRelease: | ||||
done = 1; | ||||
break; | ||||
case KeyPress: | ||||
{ | ||||
KeySym* keysym = NULL; | ||||
int keycode; /*dummy*/ | ||||
keysym = XGetKeyboardMapping(disp, ev.xkey.keycode, 1, &keycode) | ||||
; | ||||
if (!keysym) | ||||
break; | ||||
if (!isButtonPressed) { | ||||
key_abort_shot: | ||||
if (!opt.ignoreKeyboard || *keysym == XK_Escape) { | ||||
warnx("Key was pressed, aborting shot"); | ||||
done = 2; | ||||
} | ||||
XFree(keysym); | ||||
break; | ||||
} | ||||
switch (*keysym) { | ||||
case XK_Right: | ||||
if (++rx > scr->width) | ||||
rx = scr->width; | ||||
break; | ||||
case XK_Left: | ||||
if (--rx < 0) | ||||
rx = 0; | ||||
break; | ||||
case XK_Down: | ||||
if (++ry > scr->height) | ||||
ry = scr->height; | ||||
break; | ||||
case XK_Up: | ||||
if (--ry < 0) | ||||
ry = 0; | ||||
break; | ||||
default: | ||||
goto key_abort_shot; | ||||
} | ||||
XFree(keysym); | ||||
scrotSelectionMotionDraw(rx, ry, ev.xbutton.x, ev.xbutton.y); | ||||
break; | ||||
} | ||||
case KeyRelease: | ||||
/* ignore */ | ||||
break; | ||||
default: | ||||
break; | ||||
} | ||||
} | ||||
if (done) | ||||
break; | ||||
/* now block some */ | ||||
FD_ZERO(&fdSet); | ||||
FD_SET(xfd, &fdSet); | ||||
errno = 0; | ||||
count = select(fdSize, &fdSet, NULL, NULL, NULL); | ||||
if ((count < 0) | ||||
&& ((errno == ENOMEM) || (errno == EINVAL) || (errno == EBADF))) { | ||||
scrotSelectionDestroy(); | ||||
errx(EXIT_FAILURE, "Connection to X display lost"); | ||||
} | ||||
} | ||||
scrotSelectionDraw(); | ||||
XUngrabKeyboard(disp, CurrentTime); | ||||
bool const isAreaSelect = (scrotSelectionGetRect()->w > 5); | ||||
scrotSelectionDestroy(); | ||||
if (done == 2) | ||||
return false; | ||||
if (isAreaSelect) { | ||||
/* If a rect has been drawn, it's an area selection */ | ||||
rw = ev.xbutton.x - rx; | ||||
rh = ev.xbutton.y - ry; | ||||
if ((ev.xbutton.x + 1) == WidthOfScreen(scr)) | ||||
++rw; | ||||
if ((ev.xbutton.y + 1) == HeightOfScreen(scr)) | ||||
++rh; | ||||
if (rw < 0) { | ||||
rx += rw; | ||||
rw = 0 - rw; | ||||
} | ||||
if (rh < 0) { | ||||
ry += rh; | ||||
rh = 0 - rh; | ||||
} | ||||
// Not record pointer if there is a selection area because it is busy on | ||||
that, | ||||
// unless the delay option is used. | ||||
if (opt.delay == 0) | ||||
opt.pointer = 0; | ||||
} else { | } else { | |||
cursor = sel->cur_angle_nw; | /* else it's a window click */ | |||
if (!scrotGetGeometry(target, &rx, &ry, &rw, &rh)) | ||||
return false; | ||||
} | } | |||
XChangeActivePointerGrab(disp, EVENT_MASK, cursor, CurrentTime); | scrotNiceClip(&rx, &ry, &rw, &rh); | |||
sel->motion_draw(x0, y0, x1, y1); | ||||
if (!opt.silent) | ||||
XBell(disp, 0); | ||||
selectionRect->x = rx; | ||||
selectionRect->y = ry; | ||||
selectionRect->w = rw; | ||||
selectionRect->h = rh; | ||||
return true; | ||||
} | ||||
static void changeImageOpacity(Imlib_Image image, int const opacity) | ||||
{ | ||||
#define PIXEL_ARGB(a, r, g, b) ((a) << 24) | ((r) << 16) | ((g) << 8) | (b) | ||||
#define PIXEL_A(argb) (((argb) >> 24) & 0xff) | ||||
#define PIXEL_R(argb) (((argb) >> 16) & 0xff) | ||||
#define PIXEL_G(argb) (((argb) >> 8) & 0xff) | ||||
#define PIXEL_B(argb) (((argb) ) & 0xff) | ||||
imlib_context_set_image(image); | ||||
int const w = imlib_image_get_width(); | ||||
int const h = imlib_image_get_height(); | ||||
DATA32* data = imlib_image_get_data(); | ||||
DATA32* end = data + (h * w); | ||||
for (DATA32* pixel = data; pixel != end; ++pixel) { | ||||
DATA8 const a = PIXEL_A(*pixel) * opacity / 255; | ||||
DATA8 const r = PIXEL_R(*pixel); | ||||
DATA8 const g = PIXEL_G(*pixel); | ||||
DATA8 const b = PIXEL_B(*pixel); | ||||
*pixel = (DATA32)PIXEL_ARGB(a, r, g, b); | ||||
} | ||||
imlib_image_put_back_data(data); | ||||
} | ||||
static Imlib_Image loadImage(char const* const fileName, int const opacity) | ||||
{ | ||||
Imlib_Image image = imlib_load_image(fileName); | ||||
if (!image) { | ||||
errx(EXIT_FAILURE, "option --select: Failed to load image:%s", | ||||
fileName); | ||||
} | ||||
imlib_context_set_image(image); | ||||
if (imlib_image_has_alpha() == 0) { | ||||
warnx("Warning, ignoring the opacity parameter because the image '%s'" | ||||
" has no alpha channel, it will be drawn fully opaque.", fileName); | ||||
return image; | ||||
} | ||||
if (opacity == 255) { | ||||
// Do nothing if a totally opaque image is expected. | ||||
return image; | ||||
} | ||||
changeImageOpacity(image, opacity); | ||||
return image; | ||||
} | } | |||
struct selection_rect_t* scrot_selection_get_rect(void) | Imlib_Image scrotSelectionSelectMode(void) | |||
{ | { | |||
return &(*selection_get())->rect; | struct SelectionRect rect0, rect1; | |||
unsigned int const oldMode = opt.selection.mode; | ||||
opt.selection.mode = SELECTION_MODE_CAPTURE; | ||||
if (!scrotSelectionGetUserSel(&rect0)) | ||||
return NULL; | ||||
opt.selection.mode = oldMode; | ||||
if (opt.selection.mode & SELECTION_MODE_NOT_CAPTURE) | ||||
if (!scrotSelectionGetUserSel(&rect1)) | ||||
return NULL; | ||||
scrotDoDelay(); | ||||
Imlib_Image capture = imlib_create_image_from_drawable(0, rect0.x, rect0.y, | ||||
rect0.w, rect0.h, 1); | ||||
if (opt.pointer) | ||||
scrotGrabMousePointer(capture, rect0.x, rect0.y); | ||||
if (opt.selection.mode == SELECTION_MODE_CAPTURE) | ||||
return capture; | ||||
XColor color; | ||||
scrotSelectionGetLineColor(&color); | ||||
int const x = rect1.x - rect0.x; | ||||
int const y = rect1.y - rect0.y; | ||||
int const opacity = optionsParseRequireRange(opt.lineOpacity, | ||||
SELECTION_OPACITY_MIN, SELECTION_OPACITY_MAX); | ||||
imlib_context_set_image(capture); | ||||
switch(opt.selection.mode) { | ||||
case SELECTION_MODE_HOLE: | ||||
if (opacity > 0) { | ||||
Imlib_Image hole = imlib_clone_image(); | ||||
imlib_context_set_color(color.red, color.green, color.blue, opacity) | ||||
; | ||||
imlib_image_fill_rectangle(0, 0, rect0.w, rect0.h); | ||||
imlib_blend_image_onto_image(hole, 0, x, y, rect1.w, rect1.h, x, y, | ||||
rect1.w, rect1.h); | ||||
imlib_context_set_image(hole); | ||||
imlib_free_image_and_decache(); | ||||
} | ||||
break; | ||||
case SELECTION_MODE_HIDE: | ||||
{ | ||||
char* const fileName = opt.selection.paramStr; | ||||
if (fileName) { | ||||
if (opacity > 0) { | ||||
Imlib_Image hide = loadImage(fileName, opacity); | ||||
imlib_context_set_image(hide); | ||||
int const w = imlib_image_get_width(); | ||||
int const h = imlib_image_get_height(); | ||||
imlib_context_set_image(capture); | ||||
imlib_blend_image_onto_image(hide, 0, 0, 0, w, h, x, y, rect1.w, | ||||
rect1.h); | ||||
imlib_context_set_image(hide); | ||||
imlib_free_image_and_decache(); | ||||
} | ||||
free(fileName); | ||||
} else { | ||||
imlib_context_set_color(color.red, color.green, color.blue, opacity) | ||||
; | ||||
imlib_image_fill_rectangle(x, y, rect1.w, rect1.h); | ||||
} | ||||
break; | ||||
} | ||||
case SELECTION_MODE_BLUR: | ||||
{ | ||||
int const amountBlur = opt.selection.paramNum; | ||||
Imlib_Image blur = imlib_clone_image(); | ||||
imlib_context_set_image(blur); | ||||
imlib_image_blur(amountBlur); | ||||
imlib_context_set_image(capture); | ||||
imlib_blend_image_onto_image(blur, 0, x, y, rect1.w, rect1.h, x, y, rect | ||||
1.w, rect1.h); | ||||
imlib_context_set_image(blur); | ||||
imlib_free_image_and_decache(); | ||||
break; | ||||
} | ||||
default: | ||||
assert(0); | ||||
} | ||||
return capture; | ||||
} | } | |||
End of changes. 39 change blocks. | ||||
69 lines changed or deleted | 427 lines changed or added |