diff --git a/WPrefs.app/KeyboardShortcuts.c b/WPrefs.app/KeyboardShortcuts.c index a6e223c8..cc89d71e 100644 --- a/WPrefs.app/KeyboardShortcuts.c +++ b/WPrefs.app/KeyboardShortcuts.c @@ -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 */ diff --git a/WindowMaker/Defaults/WindowMaker.in b/WindowMaker/Defaults/WindowMaker.in index 71575507..e57e9b9f 100644 --- a/WindowMaker/Defaults/WindowMaker.in +++ b/WindowMaker/Defaults/WindowMaker.in @@ -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); diff --git a/src/WindowMaker.h b/src/WindowMaker.h index e1b49295..823f3990 100644 --- a/src/WindowMaker.h +++ b/src/WindowMaker.h @@ -119,6 +119,7 @@ typedef enum { WCUR_QUESTION, WCUR_TEXT, WCUR_SELECT, + WCUR_CAPTURE, WCUR_ROOT, WCUR_EMPTY, diff --git a/src/defaults.c b/src/defaults.c index f9dc3010..f6120f2c 100644 --- a/src/defaults.c +++ b/src/defaults.c @@ -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, diff --git a/src/event.c b/src/event.c index f0958274..c33f239f 100644 --- a/src/event.c +++ b/src/event.c @@ -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: { diff --git a/src/keybind.h b/src/keybind.h index c2b5dbb8..fb32f42b 100644 --- a/src/keybind.h +++ b/src/keybind.h @@ -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 diff --git a/src/screen.c b/src/screen.c index 6a7358fa..271614a4 100644 --- a/src/screen.c +++ b/src/screen.c @@ -24,6 +24,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -54,10 +58,12 @@ #include "geomview.h" #include "wmspec.h" #include "rootmenu.h" +#include "misc.h" #include "xinerama.h" #include +#include #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); +} diff --git a/src/screen.h b/src/screen.h index df861834..818861fa 100644 --- a/src/screen.h +++ b/src/screen.h @@ -27,6 +27,9 @@ #include +#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); diff --git a/src/startup.c b/src/startup.c index 985f0665..ddaf789f 100644 --- a/src/startup.c +++ b/src/startup.c @@ -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);