mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 09:27:59 +00:00
Windows tools: assorted '-demo' options.
Using a new screenshot-taking module I just added in windows/utils, these new options allow me to start up one of the tools with demonstration window contents and automatically save a .BMP screenshot to disk. This will allow me to keep essentially the same set of demo images and update them easily to keep pace with the current appearance of the real tools as PuTTY - and Windows itself - both evolve.
This commit is contained in:
parent
9294ee3496
commit
bc7e06c494
@ -13,6 +13,7 @@
|
||||
#cmakedefine01 HAVE_GETNAMEDPIPECLIENTPROCESSID
|
||||
#cmakedefine01 HAVE_SETDEFAULTDLLDIRECTORIES
|
||||
#cmakedefine01 HAVE_STRTOUMAX
|
||||
#cmakedefine01 HAVE_DWMAPI_H
|
||||
|
||||
#cmakedefine NOT_X_WINDOWS
|
||||
|
||||
|
@ -49,6 +49,8 @@ check_symbol_exists(GetNamedPipeClientProcessId "windows.h"
|
||||
HAVE_GETNAMEDPIPECLIENTPROCESSID)
|
||||
check_symbol_exists(CreatePseudoConsole "windows.h" HAVE_CONPTY)
|
||||
|
||||
check_include_files("windows.h;dwmapi.h" HAVE_DWMAPI_H)
|
||||
|
||||
check_c_source_compiles("
|
||||
#include <windows.h>
|
||||
GCP_RESULTSW gcpw;
|
||||
|
18
settings.c
18
settings.c
@ -1307,6 +1307,8 @@ static int sessioncmp(const void *av, const void *bv)
|
||||
return strcmp(a, b); /* otherwise, compare normally */
|
||||
}
|
||||
|
||||
bool sesslist_demo_mode = false;
|
||||
|
||||
void get_sesslist(struct sesslist *list, bool allocate)
|
||||
{
|
||||
int i;
|
||||
@ -1316,12 +1318,18 @@ void get_sesslist(struct sesslist *list, bool allocate)
|
||||
if (allocate) {
|
||||
strbuf *sb = strbuf_new();
|
||||
|
||||
if ((handle = enum_settings_start()) != NULL) {
|
||||
while (enum_settings_next(handle, sb))
|
||||
put_byte(sb, '\0');
|
||||
enum_settings_finish(handle);
|
||||
if (sesslist_demo_mode) {
|
||||
put_asciz(sb, "demo-server");
|
||||
put_asciz(sb, "demo-server-2");
|
||||
} else {
|
||||
if ((handle = enum_settings_start()) != NULL) {
|
||||
while (enum_settings_next(handle, sb))
|
||||
put_byte(sb, '\0');
|
||||
enum_settings_finish(handle);
|
||||
}
|
||||
put_byte(sb, '\0');
|
||||
}
|
||||
put_byte(sb, '\0');
|
||||
|
||||
list->buffer = strbuf_to_str(sb);
|
||||
|
||||
/*
|
||||
|
@ -28,6 +28,7 @@ add_sources_from_current_dir(utils
|
||||
utils/platform_get_x_display.c
|
||||
utils/registry_get_string.c
|
||||
utils/request_file.c
|
||||
utils/screenshot.c
|
||||
utils/security.c
|
||||
utils/split_into_argv.c
|
||||
utils/version.c
|
||||
|
@ -388,6 +388,8 @@ static void create_controls(HWND hwnd, char *path)
|
||||
}
|
||||
}
|
||||
|
||||
const char *dialog_box_demo_screenshot_filename = NULL;
|
||||
|
||||
/*
|
||||
* This function is the configuration box.
|
||||
* (Being a dialog procedure, in general it returns 0 if the default
|
||||
@ -396,6 +398,7 @@ static void create_controls(HWND hwnd, char *path)
|
||||
static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg,
|
||||
WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
const int DEMO_SCREENSHOT_TIMER_ID = 1230;
|
||||
HWND hw, treeview;
|
||||
struct treeview_faff tvfaff;
|
||||
int ret;
|
||||
@ -565,6 +568,21 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg,
|
||||
* spurious firing during the above setup procedure.
|
||||
*/
|
||||
SetWindowLongPtr(hwnd, GWLP_USERDATA, 1);
|
||||
|
||||
if (dialog_box_demo_screenshot_filename)
|
||||
SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL);
|
||||
return 0;
|
||||
case WM_TIMER:
|
||||
if (dialog_box_demo_screenshot_filename &&
|
||||
(UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) {
|
||||
KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID);
|
||||
const char *err = save_screenshot(
|
||||
hwnd, dialog_box_demo_screenshot_filename);
|
||||
if (err)
|
||||
MessageBox(hwnd, err, "Demo screenshot failure",
|
||||
MB_OK | MB_ICONERROR);
|
||||
SaneEndDialog(hwnd, 0);
|
||||
}
|
||||
return 0;
|
||||
case WM_LBUTTONUP:
|
||||
/*
|
||||
|
@ -757,4 +757,7 @@ bool aux_match_arg(AuxMatchOpt *amo, char **val);
|
||||
bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...);
|
||||
bool aux_match_done(AuxMatchOpt *amo);
|
||||
|
||||
char *save_screenshot(HWND hwnd, const char *outfile);
|
||||
void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend);
|
||||
|
||||
#endif /* PUTTY_WINDOWS_PLATFORM_H */
|
||||
|
@ -45,3 +45,7 @@ const wchar_t *get_app_user_model_id(void)
|
||||
{
|
||||
return L"SimonTatham.Pterm";
|
||||
}
|
||||
|
||||
void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend)
|
||||
{
|
||||
}
|
||||
|
@ -1,10 +1,16 @@
|
||||
#include "putty.h"
|
||||
#include "storage.h"
|
||||
|
||||
extern bool sesslist_demo_mode;
|
||||
extern const char *dialog_box_demo_screenshot_filename;
|
||||
static strbuf *demo_terminal_data = NULL;
|
||||
static const char *terminal_demo_screenshot_filename;
|
||||
|
||||
void gui_term_process_cmdline(Conf *conf, char *cmdline)
|
||||
{
|
||||
char *p;
|
||||
bool special_launchable_argument = false;
|
||||
bool demo_config_box = false;
|
||||
|
||||
settings_set_default_protocol(be_default_protocol);
|
||||
/* Find the appropriate default port. */
|
||||
@ -81,6 +87,29 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline)
|
||||
} else if (!strcmp(p, "-pgpfp")) {
|
||||
pgp_fingerprints_msgbox(NULL);
|
||||
exit(1);
|
||||
} else if (!strcmp(p, "-demo-config-box")) {
|
||||
if (i+1 >= argc) {
|
||||
cmdline_error("%s expects an output filename", p);
|
||||
} else {
|
||||
demo_config_box = true;
|
||||
dialog_box_demo_screenshot_filename = argv[++i];
|
||||
}
|
||||
} else if (!strcmp(p, "-demo-terminal")) {
|
||||
if (i+2 >= argc) {
|
||||
cmdline_error("%s expects input and output filenames", p);
|
||||
} else {
|
||||
const char *infile = argv[++i];
|
||||
terminal_demo_screenshot_filename = argv[++i];
|
||||
FILE *fp = fopen(infile, "rb");
|
||||
if (!fp)
|
||||
cmdline_error("can't open input file '%s'", infile);
|
||||
demo_terminal_data = strbuf_new();
|
||||
char buf[4096];
|
||||
int retd;
|
||||
while ((retd = fread(buf, 1, sizeof(buf), fp)) > 0)
|
||||
put_data(demo_terminal_data, buf, retd);
|
||||
fclose(fp);
|
||||
}
|
||||
} else if (*p != '-') {
|
||||
cmdline_error("unexpected argument \"%s\"", p);
|
||||
} else {
|
||||
@ -91,13 +120,26 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline)
|
||||
|
||||
cmdline_run_saved(conf);
|
||||
|
||||
/*
|
||||
* Bring up the config dialog if the command line hasn't
|
||||
* (explicitly) specified a launchable configuration.
|
||||
*/
|
||||
if (!(special_launchable_argument || cmdline_host_ok(conf))) {
|
||||
if (!do_config(conf))
|
||||
cleanup_exit(0);
|
||||
if (demo_config_box) {
|
||||
sesslist_demo_mode = true;
|
||||
load_open_settings(NULL, conf);
|
||||
conf_set_str(conf, CONF_host, "demo-server.example.com");
|
||||
do_config(conf);
|
||||
cleanup_exit(0);
|
||||
} else if (demo_terminal_data) {
|
||||
/* Ensure conf will cause an immediate session launch */
|
||||
load_open_settings(NULL, conf);
|
||||
conf_set_str(conf, CONF_host, "demo-server.example.com");
|
||||
conf_set_int(conf, CONF_close_on_exit, FORCE_OFF);
|
||||
} else {
|
||||
/*
|
||||
* Bring up the config dialog if the command line hasn't
|
||||
* (explicitly) specified a launchable configuration.
|
||||
*/
|
||||
if (!(special_launchable_argument || cmdline_host_ok(conf))) {
|
||||
if (!do_config(conf))
|
||||
cleanup_exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
prepare_session(conf);
|
||||
@ -105,6 +147,10 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline)
|
||||
|
||||
const struct BackendVtable *backend_vt_from_conf(Conf *conf)
|
||||
{
|
||||
if (demo_terminal_data) {
|
||||
return &null_backend;
|
||||
}
|
||||
|
||||
/*
|
||||
* Select protocol. This is farmed out into a table in a
|
||||
* separate file to enable an ssh-free variant.
|
||||
@ -125,3 +171,19 @@ const wchar_t *get_app_user_model_id(void)
|
||||
{
|
||||
return L"SimonTatham.PuTTY";
|
||||
}
|
||||
|
||||
static void demo_terminal_screenshot(void *ctx, unsigned long now)
|
||||
{
|
||||
HWND hwnd = (HWND)ctx;
|
||||
save_screenshot(hwnd, terminal_demo_screenshot_filename);
|
||||
cleanup_exit(0);
|
||||
}
|
||||
|
||||
void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend)
|
||||
{
|
||||
if (demo_terminal_data) {
|
||||
ptrlen data = ptrlen_from_strbuf(demo_terminal_data);
|
||||
seat_stdout(seat, data.ptr, data.len);
|
||||
schedule_timer(TICKSPERSEC, demo_terminal_screenshot, (void *)hwnd);
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,8 @@
|
||||
#define DEFAULT_EDCURVE_INDEX 0
|
||||
|
||||
static char *cmdline_keyfile = NULL;
|
||||
static ptrlen cmdline_demo_keystr;
|
||||
static const char *demo_screenshot_filename = NULL;
|
||||
|
||||
/*
|
||||
* Print a modal (Really Bad) message box and perform a fatal exit.
|
||||
@ -1206,6 +1208,7 @@ static void start_generating_key(HWND hwnd, struct MainDlgState *state)
|
||||
static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg,
|
||||
WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
const int DEMO_SCREENSHOT_TIMER_ID = 1230;
|
||||
static const char entropy_msg[] =
|
||||
"Please generate some randomness by moving the mouse over the blank area.";
|
||||
struct MainDlgState *state;
|
||||
@ -1429,9 +1432,30 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg,
|
||||
Filename *fn = filename_from_str(cmdline_keyfile);
|
||||
load_key_file(hwnd, state, fn, false);
|
||||
filename_free(fn);
|
||||
} else if (cmdline_demo_keystr.ptr) {
|
||||
BinarySource src[1];
|
||||
BinarySource_BARE_INIT_PL(src, cmdline_demo_keystr);
|
||||
const char *errmsg;
|
||||
ssh2_userkey *k = ppk_load_s(src, NULL, &errmsg);
|
||||
assert(!errmsg);
|
||||
|
||||
update_ui_after_load(hwnd, state, "demo passphrase",
|
||||
SSH_KEYTYPE_SSH2, NULL, k);
|
||||
|
||||
SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL);
|
||||
}
|
||||
|
||||
return 1;
|
||||
case WM_TIMER:
|
||||
if ((UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) {
|
||||
KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID);
|
||||
const char *err = save_screenshot(hwnd, demo_screenshot_filename);
|
||||
if (err)
|
||||
MessageBox(hwnd, err, "Demo screenshot failure",
|
||||
MB_OK | MB_ICONERROR);
|
||||
EndDialog(hwnd, 0);
|
||||
}
|
||||
return 0;
|
||||
case WM_MOUSEMOVE:
|
||||
state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
||||
if (state->entropy && state->entropy_got < state->entropy_required) {
|
||||
@ -2176,6 +2200,22 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
|
||||
opt_error("unrecognised PPK parameter '%s'\n", val);
|
||||
}
|
||||
}
|
||||
} else if (match_optval("-demo-screenshot")) {
|
||||
demo_screenshot_filename = val;
|
||||
cmdline_demo_keystr = PTRLEN_LITERAL(
|
||||
"PuTTY-User-Key-File-3: ssh-ed25519\n"
|
||||
"Encryption: none\n"
|
||||
"Comment: ed25519-key-20220402\n"
|
||||
"Public-Lines: 2\n"
|
||||
"AAAAC3NzaC1lZDI1NTE5AAAAILzuIFwZ"
|
||||
"8ZhgOlilcSb+9zPuCf/DmKJiloVlmWGy\n"
|
||||
"xa/F\n"
|
||||
"Private-Lines: 1\n"
|
||||
"AAAAIPca6vLwtB2NJhZUpABQISR0gcQH8jjQLta19VyzA3wc\n"
|
||||
"Private-MAC: 1159e9628259b35933b397379bbe8a14"
|
||||
"a1f1d97fe91e446e45a9581a3408b70e\n");
|
||||
params->keybutton = IDC_KEYSSH2EDDSA;
|
||||
argbits = 255;
|
||||
} else {
|
||||
opt_error("unrecognised option '%s'\n", amo.argv[amo.index]);
|
||||
}
|
||||
|
126
windows/utils/screenshot.c
Normal file
126
windows/utils/screenshot.c
Normal file
@ -0,0 +1,126 @@
|
||||
#include "putty.h"
|
||||
|
||||
#if HAVE_DWMAPI_H
|
||||
|
||||
#include <dwmapi.h>
|
||||
|
||||
char *save_screenshot(HWND hwnd, const char *outfile)
|
||||
{
|
||||
HDC dcWindow = NULL, dcSave = NULL;
|
||||
HBITMAP bmSave = NULL;
|
||||
uint8_t *buffer = NULL;
|
||||
char *err = NULL;
|
||||
|
||||
static HMODULE dwmapi_module;
|
||||
DECL_WINDOWS_FUNCTION(static, HRESULT, DwmGetWindowAttribute,
|
||||
(HWND, DWORD, PVOID, DWORD));
|
||||
|
||||
if (!dwmapi_module) {
|
||||
dwmapi_module = load_system32_dll("dwmapi.dll");
|
||||
GET_WINDOWS_FUNCTION(dwmapi_module, DwmGetWindowAttribute);
|
||||
}
|
||||
|
||||
dcWindow = GetDC(NULL);
|
||||
if (!dcWindow) {
|
||||
err = dupprintf("GetDC(window): %s", win_strerror(GetLastError()));
|
||||
goto out;
|
||||
}
|
||||
|
||||
int x, y, w, h;
|
||||
RECT wr;
|
||||
|
||||
/* Use DwmGetWindowAttribute in place of GetWindowRect to exclude
|
||||
* drop shadow, otherwise we get a load of unwanted desktop
|
||||
* background under the shadow */
|
||||
if (p_DwmGetWindowAttribute &&
|
||||
0 <= p_DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS,
|
||||
&wr, sizeof(wr))) {
|
||||
x = wr.left;
|
||||
y = wr.top;
|
||||
w = wr.right - wr.left;
|
||||
h = wr.bottom - wr.top;
|
||||
} else {
|
||||
BITMAP bmhdr;
|
||||
memset(&bmhdr, 0, sizeof(bmhdr));
|
||||
GetObject(GetCurrentObject(dcWindow, OBJ_BITMAP),
|
||||
sizeof(bmhdr), &bmhdr);
|
||||
x = y = 0;
|
||||
w = bmhdr.bmWidth;
|
||||
h = bmhdr.bmHeight;
|
||||
}
|
||||
|
||||
dcSave = CreateCompatibleDC(dcWindow);
|
||||
if (!dcSave) {
|
||||
err = dupprintf("CreateCompatibleDC(desktop window dc): %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto out;
|
||||
}
|
||||
|
||||
bmSave = CreateCompatibleBitmap(dcWindow, w, h);
|
||||
if (!bmSave) {
|
||||
err = dupprintf("CreateCompatibleBitmap: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!SelectObject(dcSave, bmSave)) {
|
||||
err = dupprintf("SelectObject: %s", win_strerror(GetLastError()));
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!BitBlt(dcSave, 0, 0, w, h, dcWindow, x, y, SRCCOPY)) {
|
||||
err = dupprintf("BitBlt: %s", win_strerror(GetLastError()));
|
||||
goto out;
|
||||
}
|
||||
|
||||
BITMAPINFO bmInfo;
|
||||
memset(&bmInfo, 0, sizeof(bmInfo));
|
||||
bmInfo.bmiHeader.biSize = sizeof(bmInfo.bmiHeader);
|
||||
bmInfo.bmiHeader.biWidth = w;
|
||||
bmInfo.bmiHeader.biHeight = h;
|
||||
bmInfo.bmiHeader.biPlanes = 1;
|
||||
bmInfo.bmiHeader.biBitCount = 32;
|
||||
bmInfo.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
size_t bmPixels = (size_t)w*h, bmBytes = bmPixels * 4;
|
||||
buffer = snewn(bmBytes, uint8_t);
|
||||
|
||||
if (!GetDIBits(dcWindow, bmSave, 0, h, buffer, &bmInfo, DIB_RGB_COLORS))
|
||||
err = dupprintf("GetDIBits (get data): %s",
|
||||
win_strerror(GetLastError()));
|
||||
|
||||
FILE *fp = fopen(outfile, "wb");
|
||||
if (!fp) {
|
||||
err = dupprintf("'%s': unable to open file", outfile);
|
||||
goto out;
|
||||
}
|
||||
|
||||
BITMAPFILEHEADER bmFileHdr;
|
||||
bmFileHdr.bfType = 'B' | ('M' << 8);
|
||||
bmFileHdr.bfSize = sizeof(bmFileHdr) + sizeof(bmInfo.bmiHeader) + bmBytes;
|
||||
bmFileHdr.bfOffBits = sizeof(bmFileHdr) + sizeof(bmInfo.bmiHeader);
|
||||
fwrite((void *)&bmFileHdr, 1, sizeof(bmFileHdr), fp);
|
||||
fwrite((void *)&bmInfo.bmiHeader, 1, sizeof(bmInfo.bmiHeader), fp);
|
||||
fwrite((void *)buffer, 1, bmBytes, fp);
|
||||
fclose(fp);
|
||||
|
||||
out:
|
||||
if (dcWindow)
|
||||
ReleaseDC(NULL, dcWindow);
|
||||
if (bmSave)
|
||||
DeleteObject(bmSave);
|
||||
if (dcSave)
|
||||
DeleteObject(dcSave);
|
||||
sfree(buffer);
|
||||
return err;
|
||||
}
|
||||
|
||||
#else /* HAVE_DWMAPI_H */
|
||||
|
||||
/* Without <dwmapi.h> we can't get the right window rectangle */
|
||||
char *save_screenshot(HWND hwnd, const char *outfile)
|
||||
{
|
||||
return dupstr("Demo screenshots not compiled in to this build");
|
||||
}
|
||||
|
||||
#endif /* HAVE_DWMAPI_H */
|
@ -816,6 +816,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
|
||||
term_set_focus(term, GetForegroundWindow() == wgs.term_hwnd);
|
||||
UpdateWindow(wgs.term_hwnd);
|
||||
|
||||
gui_terminal_ready(wgs.term_hwnd, &wgs.seat, backend);
|
||||
|
||||
while (1) {
|
||||
int n;
|
||||
DWORD timeout;
|
||||
|
Loading…
Reference in New Issue
Block a user