1
0
mirror of https://github.com/gryf/wmaker.git synced 2025-12-18 20:10:29 +01:00

Add a screenshot capture feature

This patch adds a feature to take screenshots directly from Window Maker.
Having the feature embeded direclty inside Window Maker allows us to take
advantage of how Window Maker is managing and handling Windows.
Three new actions can be bound to a key shortcut from WPrefs.
The screenshot files are saved in ~/GNUstep/Library/WindowMaker/Screenshots/
dir, with a "screenshot_%Y-%m-%d_at_%H:%M:%S" format followed by the
extension. Preferably as a PNG or JPG file if available.
Meaning, to work Window Maker via WRaster needs to support
at least one of those format.

"Capture the entire screen" is quite standard, it takes a screenshot
of the whole screen area (even in multiheads env).
"Capture a portion of the screen" requires the user to draw a rectangle which
will be captured.
Those two first are quite straightforward, just taking a live picture of
the screen.
The last one is "Capture a window" which works in best effort mode,
it catures the focused window. As Window Maker by default is not using
any compositor (like for example Xcompmgr) it can only dump the content
displayed on the screen.
If a window is minimized or out of the screen, there is high chance the
image will be split or some area greyed in case other windows overlapped it.
This commit is contained in:
David Maciejak
2023-03-07 19:46:50 +08:00
committed by Carlos R. Mafra
parent 6c69dc32a0
commit d045ffcf7d
9 changed files with 303 additions and 2 deletions

View File

@@ -158,7 +158,10 @@ static struct keyOption {
{ "RunKey", N_("Run application") },
{ "ExitKey", N_("Exit Window Maker") },
{ "DockRaiseLowerKey", N_("Raise/Lower Dock") },
{ "ClipRaiseLowerKey", N_("Raise/Lower Clip") }
{ "ClipRaiseLowerKey", N_("Raise/Lower Clip") },
{ "ScreenCaptureKey", N_("Capture the entire screen") },
{ "WindowCaptureKey", N_("Capture a window") },
{ "PartialCaptureKey", N_("Capture a portion of the screen") }
#ifdef XKB_MODELOCK
,{ "ToggleKbdModeKey", N_("Toggle keyboard language") }
#endif /* XKB_MODELOCK */

View File

@@ -233,6 +233,9 @@
ScreenSwitchKey = None;
RunKey = None;
ExitKey = None;
ScreenCaptureKey = Print;
WindowCaptureKey = None;
PartialCaptureKey = None;
NormalCursor = (builtin, left_ptr);
ArrowCursor = (builtin, top_left_arrow);
MoveCursor = (builtin, fleur);

View File

@@ -119,6 +119,7 @@ typedef enum {
WCUR_QUESTION,
WCUR_TEXT,
WCUR_SELECT,
WCUR_CAPTURE,
WCUR_ROOT,
WCUR_EMPTY,

View File

@@ -790,6 +790,12 @@ WDefaultEntry optionList[] = {
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"ExitKey", "None", (void *)WKBD_EXIT,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"ScreenCaptureKey", "None", (void *)WKBD_PRINTS,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"WindowCaptureKey", "None", (void *)WKBD_PRINTW,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"PartialCaptureKey", "None", (void *)WKBD_PRINTP,
NULL, getKeybind, setKeyGrab, NULL, NULL},
#ifdef KEEP_XKB_LOCK_STATUS
{"ToggleKbdModeKey", "None", (void *)WKBD_TOGGLE,
@@ -826,6 +832,8 @@ WDefaultEntry optionList[] = {
NULL, getCursor, setCursor, NULL, NULL},
{"SelectCursor", "(builtin, cross)", (void *)WCUR_SELECT,
NULL, getCursor, setCursor, NULL, NULL},
{"CaptureCursor", "(builtin, crosshair)", (void *)WCUR_CAPTURE,
NULL, getCursor, setCursor, NULL, NULL},
{"DialogHistoryLines", "500", NULL,
&wPreferences.history_lines, getInt, NULL, NULL, NULL},
{"CycleActiveHeadOnly", "NO", NULL,

View File

@@ -1863,6 +1863,24 @@ static void handleKeyPress(XEvent * event)
break;
}
case WKBD_PRINTS:
{
ScreenCapture(scr, PRINT_SCREEN);
break;
}
case WKBD_PRINTW:
{
ScreenCapture(scr, PRINT_WINDOW);
break;
}
case WKBD_PRINTP:
{
ScreenCapture(scr, PRINT_PARTIAL);
break;
}
case WKBD_NEXTWSLAYER:
case WKBD_PREVWSLAYER:
{

View File

@@ -148,6 +148,15 @@ enum {
/* open "exit" dialog */
WKBD_EXIT,
/* screen print */
WKBD_PRINTS,
/* window print */
WKBD_PRINTW,
/* partial print */
WKBD_PRINTP,
#ifdef KEEP_XKB_LOCK_STATUS
WKBD_TOGGLE,
#endif

View File

@@ -24,6 +24,10 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
@@ -54,10 +58,12 @@
#include "geomview.h"
#include "wmspec.h"
#include "rootmenu.h"
#include "misc.h"
#include "xinerama.h"
#include <WINGs/WUtil.h>
#include <WINGs/WINGsP.h>
#include "defaults.h"
@@ -593,6 +599,7 @@ static void createInternalWindows(WScreen * scr)
scr->workspace_name =
XCreateWindow(dpy, scr->root_win, 0, 0, 10, 10, 0, scr->w_depth,
CopyFromParent, scr->w_visual, vmask, &attribs);
scr->mini_screenshot_timeout = 0;
}
/*
@@ -1126,3 +1133,246 @@ int wScreenKeepInside(WScreen * scr, int *x, int *y, int width, int height)
return moved;
}
static XImage *imageCaptureArea(WScreen *scr)
{
XEvent event;
int quit = 0;
int xp = -1;
int yp = -1;
int w = 0, h = 0;
int x = xp, y = yp;
if (XGrabPointer(dpy, scr->root_win, False, ButtonMotionMask
| ButtonReleaseMask | ButtonPressMask, GrabModeAsync,
GrabModeAsync, None, wPreferences.cursor[WCUR_CAPTURE], CurrentTime) != Success) {
return NULL;
}
XGrabServer(dpy);
while (!quit) {
WMMaskEvent(dpy, ButtonReleaseMask | PointerMotionMask | ButtonPressMask | KeyPressMask, &event);
switch (event.type) {
case ButtonPress:
if (event.xbutton.button == Button1) {
xp = event.xbutton.x_root;
yp = event.xbutton.y_root;
}
break;
case ButtonRelease:
if (event.xbutton.button == Button1) {
quit = 1;
if (w > 0 && h > 0) {
XDrawRectangle(dpy, scr->root_win, scr->frame_gc, x, y, w, h);
XUngrabServer(dpy);
XUngrabPointer(dpy, CurrentTime);
return XGetImage(dpy, scr->root_win, x, y, w, h, AllPlanes, ZPixmap);
}
}
break;
case MotionNotify:
XDrawRectangle(dpy, scr->root_win, scr->frame_gc, x, y, w, h);
x = event.xmotion.x_root;
if (x < xp) {
w = xp - x;
} else {
w = x - xp;
x = xp;
}
y = event.xmotion.y_root;
if (y < yp) {
h = yp - y;
} else {
h = y - yp;
y = yp;
}
XDrawRectangle(dpy, scr->root_win, scr->frame_gc, x, y, w, h);
break;
case KeyPress:
if (W_KeycodeToKeysym(dpy, event.xkey.keycode, 0) == XK_Escape)
quit = 1;
break;
default:
WMHandleEvent(&event);
break;
}
}
XUngrabServer(dpy);
XUngrabPointer(dpy, CurrentTime);
return NULL;
}
static void hideMiniScreenshot(void *data)
{
WScreen *scr = (WScreen *) data;
if (time(NULL) < scr->mini_screenshot_timeout) {
scr->mini_screenshot_timer = WMAddTimerHandler(WORKSPACE_NAME_FADE_DELAY, hideMiniScreenshot, scr);
} else {
XWindowAttributes attr;
WMDeleteTimerHandler(scr->mini_screenshot_timer);
if (XGetWindowAttributes(dpy, scr->mini_screenshot, &attr))
slide_window(scr->mini_screenshot, attr.x, attr.y, attr.x + attr.width, attr.y);
XUnmapWindow(dpy, scr->mini_screenshot);
XDestroyWindow(dpy, scr->mini_screenshot);
scr->mini_screenshot_timeout = 0;
}
}
static void showMiniScreenshot(WScreen *scr, RImage *img)
{
Pixmap pix;
int x = scr->scr_width - img->width - 20;
int y = scr->scr_height - img->height - 20;
if (!scr->mini_screenshot_timeout) {
Window win;
win = XCreateSimpleWindow(dpy, scr->root_win, x, y,
img->width, img->height, scr->frame_border_width, 0, 0);
scr->mini_screenshot = win;
}
RConvertImage(scr->rcontext, img, &pix);
XMapWindow(dpy, scr->mini_screenshot);
XCopyArea(dpy, pix, scr->mini_screenshot, scr->rcontext->copy_gc, 0, 0, img->width, img->height, 0, 0);
XFlush(dpy);
scr->mini_screenshot_timeout = time(NULL) + 2;
scr->mini_screenshot_timer = WMAddTimerHandler(WORKSPACE_NAME_FADE_DELAY, hideMiniScreenshot, scr);
}
void ScreenCapture(WScreen *scr, int mode)
{
time_t s;
short i = 0;
struct tm *tm_info;
char index_str[12] = "";
char filename_date_part[60];
char filename[60];
char *filepath;
char *screenshot_dir;
RImage *img = NULL;
RImage *scale_img = NULL;
#ifdef USE_PNG
char *filetype = ".png";
#else
#ifdef USE_JPEG
char *filetype = ".jpg";
#else
char *filetype = NULL;
#endif
#endif
if (!filetype) {
werror(_("Unable to find a proper screenshot image format"));
return;
}
screenshot_dir = wstrconcat(wusergnusteppath(), "/Library/WindowMaker/Screenshots/");
if (-1 == mkdir(screenshot_dir, 0700) && errno != EEXIST) {
wfree(screenshot_dir);
werror(_("Unable to create screenshot directory: %s"), strerror(errno));
return;
}
s = time(NULL);
tm_info = localtime(&s);
strftime(filename_date_part, sizeof(filename_date_part), "screenshot_%Y-%m-%d_at_%H:%M:%S", tm_info);
strcpy(filename, filename_date_part);
filepath = wstrconcat(screenshot_dir, strcat(filename, filetype));
while (access(filepath, F_OK) == 0 && i < 600) {
i++;
strcpy(filename, filename_date_part);
sprintf(index_str, "_%d", i);
strncat(filename, index_str, sizeof(filename) - strlen(filename) - 1);
wfree(filepath);
filepath = wstrconcat(screenshot_dir, strcat(filename, filetype));
}
/* cannot generate an available filename ?! */
if (i == 600) {
wfree(filepath);
wfree(screenshot_dir);
werror(_("Could not generate a free screenshot filename"));
return;
}
switch (mode) {
case PRINT_WINDOW:
WWindow *wwin = scr->focused_window;
if (wwin && !wwin->flags.shaded) {
/*
* check if hint WM_TAKE_FOCUS is set, if it's the case
* we can take screenshot of the out of screen window
*/
if (wwin->focus_mode >= WFM_LOCALLY_ACTIVE) {
img = RCreateImageFromDrawable(scr->rcontext, wwin->client_win, None);
}
else {
/* we will only capture the visible window part */
XImage *pimg;
int x_crop = 0;
int y_crop = 0;
int w_crop = wwin->client.width;
int h_crop = wwin->client.height;
if (wwin->client.x > 0)
x_crop = wwin->client.x;
if (wwin->client.y > 0)
y_crop = wwin->client.y;
if (wwin->client.x + wwin->client.width > scr->scr_width)
w_crop = scr->scr_width - wwin->client.x;
if (wwin->client.y + wwin->client.height > scr->scr_height)
h_crop = scr->scr_height - wwin->client.y;
pimg = XGetImage(dpy, scr->root_win, x_crop, y_crop,
(wwin->client.x > 0)?w_crop:w_crop + wwin->client.x,
(wwin->client.y > 0)?h_crop:h_crop + wwin->client.y,
AllPlanes, ZPixmap);
if (pimg) {
img = RCreateImageFromXImage(scr->rcontext, pimg, None);
XDestroyImage(pimg);
}
}
}
break;
case PRINT_PARTIAL:
XImage *pimg;
pimg = imageCaptureArea(scr);
if (pimg) {
img = RCreateImageFromXImage(scr->rcontext, pimg, None);
XDestroyImage(pimg);
}
break;
default:
/* PRINT_SCREEN*/
img = RCreateImageFromDrawable(scr->rcontext, scr->root_win, None);
}
if (img) {
#ifdef USE_PNG
if (RSaveTitledImage(img, filepath, (char *)(filetype + 1), "Screenshot from Window Maker")) {
#else
if (RSaveTitledImage(img, filepath, (char *)(filetype + 1), "Screenshot from Window Maker")) {
#endif
scale_img = RSmoothScaleImage(img, scr->scr_width / 10, scr->scr_height / 10);
showMiniScreenshot(scr, scale_img);
RReleaseImage(scale_img);
#ifdef DEBUG
wmessage("screenshot filepath: %s", filepath);
#endif
}
RReleaseImage(img);
}
wfree(filepath);
wfree(screenshot_dir);
}

View File

@@ -27,6 +27,9 @@
#include <WINGs/WUtil.h>
#define PRINT_SCREEN 1
#define PRINT_WINDOW 2
#define PRINT_PARTIAL 3
typedef struct {
WMRect *screens;
@@ -271,6 +274,11 @@ typedef struct _WScreen {
WMHandlerID *workspace_name_timer;
struct WorkspaceNameData *workspace_name_data;
/* mini screenshot data */
Window mini_screenshot;
time_t mini_screenshot_timeout;
WMHandlerID *mini_screenshot_timer;
/* for raise-delay */
WMHandlerID *autoRaiseTimer;
Window autoRaiseWindow; /* window that is scheduled to be
@@ -314,7 +322,7 @@ void wScreenRestoreState(WScreen *scr);
int wScreenBringInside(WScreen *scr, int *x, int *y, int width, int height);
int wScreenKeepInside(WScreen *scr, int *x, int *y, int width, int height);
void ScreenCapture(WScreen *scr, int mode);
/* in startup.c */
WScreen *wScreenWithNumber(int i);

View File

@@ -493,6 +493,7 @@ void StartUp(Bool defaultScreenOnly)
wPreferences.cursor[WCUR_QUESTION] = XCreateFontCursor(dpy, XC_question_arrow);
wPreferences.cursor[WCUR_TEXT] = XCreateFontCursor(dpy, XC_xterm); /* odd name??? */
wPreferences.cursor[WCUR_SELECT] = XCreateFontCursor(dpy, XC_cross);
wPreferences.cursor[WCUR_CAPTURE] = XCreateFontCursor(dpy, XC_crosshair);
Pixmap cur = XCreatePixmap(dpy, DefaultRootWindow(dpy), 16, 16, 1);
GC gc = XCreateGC(dpy, cur, 0, NULL);