Skip to content

Commit

Permalink
Put Windows printer pop-up on top and centered
Browse files Browse the repository at this point in the history
  • Loading branch information
pmattes committed Feb 4, 2025
1 parent 54d9913 commit ca46f59
Show file tree
Hide file tree
Showing 18 changed files with 658 additions and 33 deletions.
153 changes: 135 additions & 18 deletions Common/Win32/gdi_print.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@

#include "fprint_screen.h"
#include "gdi_print.h"
#include "main_window.h"
#include "names.h"
#include "nvt.h"
#include "popups.h"
Expand Down Expand Up @@ -96,10 +97,13 @@ static struct { /* printer state */
SIZE space_size; /* size of a space character */
INT *dx; /* spacing array */

HANDLE thread; /* thread to run the print dialog */
HANDLE run_thread; /* thread to run the print dialog */
HANDLE done_event; /* event to signal dialog is done */
bool cancel; /* true if dialog canceled */
bool canceled; /* true if dialog canceled */
bool activated; /* true if dialog window has been activated */
void *wait_context; /* task wait context */
HWND hwnd; /* window handle of dialog box */
HANDLE top_thread; /* thread to make the window topmost */
} pstate;
static bool pstate_initted = false;

Expand Down Expand Up @@ -426,9 +430,8 @@ get_default_printer_name(char *errbuf, size_t errbuf_size)
static DWORD WINAPI
post_print_dialog(LPVOID lpParameter _is_unused)
{
if (!PrintDlg(&pstate.dlg)) {
pstate.cancel = true;
}
pstate.activated = false;
pstate.canceled = !PrintDlg(&pstate.dlg);
SetEvent(pstate.done_event);
return 0;
}
Expand All @@ -437,11 +440,95 @@ post_print_dialog(LPVOID lpParameter _is_unused)
static void
print_dialog_complete(iosrc_t fd _is_unused, ioid_t id _is_unused)
{
vtrace("Printer dialog complete (%s)\n",
pstate.cancel? "cancel": "continue");
pstate.thread = INVALID_HANDLE_VALUE;
task_resume_xwait(pstate.wait_context, pstate.cancel,
"print dialog complete");
vtrace("Printer dialog complete (%s)\n", pstate.canceled? "canceled": "continue");
CloseHandle(pstate.run_thread);
pstate.run_thread = INVALID_HANDLE_VALUE;
CloseHandle(pstate.top_thread);
pstate.top_thread = INVALID_HANDLE_VALUE;
pstate.hwnd = INVALID_HANDLE_VALUE;
task_resume_xwait(pstate.wait_context, pstate.canceled, "print dialog complete");
}

/* Compute the proper location for the dialog. */
static bool
compute_location(int w, int h, int *x, int *y)
{
int parent_x, parent_y, parent_w, parent_h;
HWND main_window = get_main_window();

if (main_window == INVALID_HANDLE_VALUE || main_window == 0) {
/* We don't know what the main window is, use the primary display. */
parent_x = 0;
parent_y = 0;
parent_w = GetSystemMetrics(SM_CXFULLSCREEN);
parent_h = GetSystemMetrics(SM_CYFULLSCREEN);
} else {
RECT rect;

/* Get the rectangle for the primary window. */
if (!GetWindowRect(main_window, &rect)) {
vtrace("Can't get rectangle for main window 0x%08lx\n", (u_long)(size_t)main_window);
return false;
}
parent_x = rect.left;
parent_y = rect.top;
parent_w = rect.right - rect.left;
parent_h = rect.bottom - rect.top;
}

if (parent_w < w || parent_h < h) {
/* Strange, but possible. */
*x = 0;
*y = 0;
} else {
*x = parent_x + (parent_w - w) / 2;
*y = parent_y + (parent_h - h) / 2;
}
return true;
}

/* Make the dialog topmost. */
static void
make_dialog_topmost(HWND hdlg, bool move)
{
RECT rect;
int x = 0, y = 0;
UINT flags = SWP_NOSIZE;

if (move) {
/* Figure out where to move it. */
if (!GetWindowRect(hdlg, &rect)) {
vtrace("make_dialog_topmost: Can't get rectangle for dialog\n");
flags |= SWP_NOMOVE;
} else if (!compute_location(rect.right - rect.left, rect.bottom - rect.top, &x, &y)) {
vtrace("make_dialog_topmost: Can't get rectangle for parent window\n");
flags |= SWP_NOMOVE;
}
} else {
flags |= SWP_NOMOVE;
}

SetWindowPos(hdlg, HWND_TOPMOST, x, y, 0, 0, flags);
}

/*
* Thread to make the dialog topmost, after a 2-second delay.
*
* This is a belt-and-suspenders action, given that we still don't understand why the window either
* isn't topmost in the first place.
*/
static DWORD WINAPI
delayed_topmost(LPVOID lpParameter _is_unused)
{
/* Wait 2 seconds. If the window still exists, make it topmost. */
Sleep(2000);
if (pstate.hwnd != INVALID_HANDLE_VALUE) {
vtrace("Making the print dialog window topmost\n");
make_dialog_topmost(pstate.hwnd, false);
} else {
vtrace("Too late to move print dialog window to topmost\n");
}
return 0;
}

/*
Expand All @@ -450,9 +537,39 @@ print_dialog_complete(iosrc_t fd _is_unused, ioid_t id _is_unused)
static UINT_PTR CALLBACK
print_dialog_hook(HWND hdlg, UINT ui_msg, WPARAM wparam, LPARAM lparam)
{
if (ui_msg == WM_ACTIVATE) {
/* Set the window to be topmost. */
SetWindowPos(hdlg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
pstate.hwnd = hdlg;

if (ui_msg == WM_ACTIVATE && !pstate.activated) {
pstate.activated = true;
make_dialog_topmost(hdlg, true);
}

if (ui_msg == WM_INITDIALOG) {
int text_len;

/* Kick off the thread that will put the dialog on top, no matter what. */
pstate.top_thread = CreateThread(NULL, 0, delayed_topmost, NULL, 0, NULL);

/* Change the window title to include the application name. */
text_len = GetWindowTextLength(hdlg);
if (text_len) {
char *text = Malloc(text_len + 1);

if (GetWindowText(hdlg, text, text_len + 1)) {
char *new_title;

if (appres.alias != NULL) {
new_title = Asprintf("%s - %s", text, appres.alias);
} else {
const char *space = strchr(build, ' ');

new_title = Asprintf("%s - %.*s", text, (int)(space - build), build);
}
SetWindowText(hdlg, new_title);
Free(new_title);
}
Free(text);
}
}

return ui_msg == WM_INITDIALOG;
Expand All @@ -478,8 +595,10 @@ gdi_init(const char *printer_name, unsigned opts, const char **fail,
int fheight, fwidth;

if (!pstate_initted) {
pstate.thread = INVALID_HANDLE_VALUE;
pstate.run_thread = INVALID_HANDLE_VALUE;
pstate.done_event = INVALID_HANDLE_VALUE;
pstate.hwnd = INVALID_HANDLE_VALUE;
pstate.top_thread = INVALID_HANDLE_VALUE;
pstate_initted = true;
}

Expand All @@ -488,7 +607,7 @@ gdi_init(const char *printer_name, unsigned opts, const char **fail,
goto failed;
}

if (pstate.thread != INVALID_HANDLE_VALUE) {
if (pstate.run_thread != INVALID_HANDLE_VALUE) {
*fail = "Print dialog already pending";
goto failed;
}
Expand Down Expand Up @@ -543,16 +662,14 @@ gdi_init(const char *printer_name, unsigned opts, const char **fail,
}

/* Pop up the dialog to get the printer characteristics. */
pstate.cancel = false;
pstate.wait_context = wait_context;
if (pstate.done_event == INVALID_HANDLE_VALUE) {
pstate.done_event = CreateEvent(NULL, FALSE, FALSE, NULL);
AddInput(pstate.done_event, print_dialog_complete);
} else {
ResetEvent(pstate.done_event); /* just in case */
}
pstate.cancel = false;
pstate.thread = CreateThread(NULL, 0, post_print_dialog, NULL, 0, NULL);
pstate.run_thread = CreateThread(NULL, 0, post_print_dialog, NULL, 0, NULL);
return GDI_STATUS_WAIT;
}
dc = pstate.dlg.hDC;
Expand Down
133 changes: 133 additions & 0 deletions Common/Win32/main_window.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* Copyright (c) 2025 Paul Mattes.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the names of Paul Mattes nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY PAUL MATTES "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL PAUL MATTES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/*
* main_window.c
* Support for a settable/displayable main window ID.
*/

#include "globals.h"

#if !defined(_WIN32) /*[*/
#error This module is only for Win32.
#endif /*]*/

#include <errno.h>

#include "appres.h"
#include "main_window.h"
#include "popups.h"
#include "resources.h"
#include "toggles.h"
#include "txa.h"
#include "utils.h"

static const char *canonicalize_window_id(const char *value);

#if defined(_WIN64) /*[*/
# define PTRCONV strtoull
# define MAXVAL ULLONG_MAX
#else /*][*/
# define PTRCONV strtoul
# define MAXVAL ULONG_MAX
#endif /*]*/

/* Get the handle for the main window. */
HWND
get_main_window(void)
{
if (appres.window_id != NULL) {
return (HWND)PTRCONV(get_main_window_str(), NULL, 0);
} else {
return INVALID_HANDLE_VALUE;
}
}

const char *
get_main_window_str(void)
{
return canonicalize_window_id(appres.window_id);
}

/* Set the handle for the main window. */
void
set_main_window(HWND hwnd)
{
Replace(appres.window_id, Asprintf("0x%p", hwnd));
}

/* Toggle the window ID. */
static toggle_upcall_ret_t
toggle_window_id(const char *name _is_unused, const char *value, unsigned flags, ia_t ia)
{
size_t l;
char *nextp;

if (!*value) {
Replace(appres.window_id, NULL);
return TU_SUCCESS;
}

errno = 0;
l = PTRCONV(value, &nextp, 0);
if (*nextp != '\0' || (l == MAXVAL && errno == ERANGE)) {
popup_an_error("Invalid " ResWindowId " value");
return TU_FAILURE;
}

Replace(appres.window_id, Asprintf("0x%p", (void *)(size_t)l));
return TU_SUCCESS;
}

/* Canonnicalize the window ID. */
static const char *
canonicalize_window_id(const char *value)
{
size_t l = (size_t)INVALID_HANDLE_VALUE;
char *nextp;

if (value != NULL && *value) {
size_t ld;

errno = 0;
ld = PTRCONV(value, &nextp, 0);
if (*nextp == '\0' && (ld != MAXVAL || errno != ERANGE)) {
l = ld;
}
}

return txAsprintf("0x%p", (void *)l);
}

/* Register toggles. */
void
main_window_register(void)
{
/* Register the toggle. */
register_extended_toggle(ResWindowId, toggle_window_id, NULL, canonicalize_window_id,
(void **)&appres.window_id, XRM_STRING);
}
6 changes: 5 additions & 1 deletion Common/b3270/b3270.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1993-2024 Paul Mattes.
* Copyright (c) 1993-2025 Paul Mattes.
* Copyright (c) 1990, Jeff Sparkes.
* Copyright (c) 1989, Georgia Tech Research Corporation (GTRC), Atlanta,
* GA 30332.
Expand Down Expand Up @@ -109,6 +109,7 @@
#include "xscroll.h"

#if defined(_WIN32) /*[*/
# include "main_window.h"
# include "w3misc.h"
# include "windirs.h"
# include "winvers.h"
Expand Down Expand Up @@ -584,6 +585,9 @@ main(int argc, char *argv[])
prefer_register();
telnet_new_environ_register();
rpq_register();
#if defined(_WIN32) /*[*/
main_window_register();
#endif /*]*/

supports_cmdline_host = false;
argc = parse_command_line(argc, (const char **)argv, &cl_hostname);
Expand Down
5 changes: 4 additions & 1 deletion Common/glue.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1993-2024 Paul Mattes.
* Copyright (c) 1993-2025 Paul Mattes.
* Copyright (c) 1990 Jeff Sparkes.
* Copyright (c) 1989 Georgia Tech Research Corporation (GTRC), Atlanta, GA
* 30332.
Expand Down Expand Up @@ -936,6 +936,9 @@ static res_t base_resources[] = {
{ ResCookieFile, aoffset(cookie_file), XRM_STRING },
{ ResUtEnv, aoffset(ut_env), XRM_BOOLEAN },
{ ResRpq, aoffset(rpq), XRM_STRING },
#if defined(_WIN32) /*[*/
{ ResWindowId, aoffset(window_id), XRM_STRING },
#endif /*]*/
};

typedef struct reslist {
Expand Down
Loading

0 comments on commit ca46f59

Please sign in to comment.