1 /*
2 * Copyright (c) 1994-2020 Paul Mattes.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the names of Paul Mattes nor the names of his contributors
13 * may be used to endorse or promote products derived from this software
14 * without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY PAUL MATTES "AS IS" AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL PAUL MATTES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 /*
29 * print_screen.c
30 * Screen printing functions.
31 */
32
33 #include "globals.h"
34
35 #include "appres.h"
36 #include "3270ds.h"
37 #include "ctlr.h"
38
39 #include "ctlrc.h"
40
41 #include <errno.h>
42 #include <assert.h>
43
44 #include "resources.h"
45
46 #include "actions.h"
47 #include "codepage.h"
48 #include "fprint_screen.h"
49 #include "lazya.h"
50 #include "names.h"
51 #include "popups.h"
52 #include "print_command.h"
53 #include "print_screen.h"
54 #include "print_gui.h"
55 #include "product.h"
56 #include "task.h"
57 #include "toggles.h"
58 #include "trace.h"
59 #include "unicodec.h"
60 #include "utils.h"
61
62 #if defined(_WIN32) /*[*/
63 # include <fcntl.h>
64 # include <sys/stat.h>
65 # include "w3misc.h"
66 # include "winprint.h"
67 #endif /*]*/
68
69 /* Typedefs */
70
71 /* Saved context for a suspended PrintText(). */
72 typedef struct {
73 FILE *f; /* temporary file */
74 ptype_t ptype; /* print type */
75 unsigned opts; /* options */
76 const char *caption; /* caption text */
77 const char *name; /* printer name */
78 char *temp_name; /* temporary file name */
79 } printtext_t;
80
81 /* Globals */
82
83 /* Statics */
84
85 /**
86 * Default caption.
87 *
88 * @return caption text
89 */
90 char *
default_caption(void)91 default_caption(void)
92 {
93 static char *r = NULL;
94
95 #if !defined(_WIN32) /*[*/
96 /* Unix version: username@host %T% */
97 char hostname[132];
98 char *user;
99
100 gethostname(hostname, sizeof(hostname));
101 hostname[sizeof(hostname) - 1] = '\0';
102 user = getenv("USER");
103 if (user == NULL) {
104 user = "(unknown)";
105 }
106
107 if (r != NULL) {
108 Free(r);
109 }
110 r = xs_buffer("%s @ %s %%T%%", user, hostname);
111
112 #else /*][*/
113
114 char *username;
115 char *computername;
116 char *userdomain;
117 char ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
118
119 username = getenv("USERNAME");
120 if (username == NULL) {
121 username = "(unknown)";
122 }
123 computername = getenv("COMPUTERNAME");
124 if (computername == NULL) {
125 DWORD size = MAX_COMPUTERNAME_LENGTH + 1;
126
127 if (GetComputerName(ComputerName, &size)) {
128 computername = ComputerName;
129 } else {
130 computername = "(unknown)";
131 }
132 }
133 userdomain = getenv("USERDOMAIN");
134 if (userdomain == NULL) {
135 userdomain = "(unknown)";
136 }
137
138 if (r != NULL) {
139 Free(r);
140 }
141 if (strcasecmp(userdomain, computername)) {
142 r = xs_buffer("%s\\%s @ %s %%T%%", userdomain, username, computername);
143 } else {
144 r = xs_buffer("%s @ %s %%T%%", username, computername);
145 }
146 #endif
147
148 return r;
149 }
150
151 /* Extended-wait continue function for PrintText(). */
152 static void
printtext_continue(void * context,bool cancel)153 printtext_continue(void *context, bool cancel)
154 {
155 printtext_t *pt = (printtext_t *)context;
156 fps_status_t status;
157
158 if (cancel) {
159 vtrace("PrintText canceled\n");
160 fclose(pt->f);
161 if (pt->temp_name != NULL) {
162 unlink(pt->temp_name);
163 Free(pt->temp_name);
164 }
165 Free(pt);
166 return;
167 }
168
169 status = fprint_screen(pt->f, pt->ptype, pt->opts | FPS_DIALOG_COMPLETE,
170 pt->caption, pt->name, NULL);
171 switch (status) {
172 case FPS_STATUS_SUCCESS:
173 case FPS_STATUS_SUCCESS_WRITTEN:
174 vtrace("PrintText: printing succeeded.\n");
175 break;
176 case FPS_STATUS_ERROR:
177 popup_an_error("Screen print failed");
178 /* fall through */
179 case FPS_STATUS_CANCEL:
180 if (status == FPS_STATUS_CANCEL) {
181 vtrace("PrintText: printing canceled.\n");
182 }
183 break;
184 case FPS_STATUS_WAIT:
185 /* Can't happen. */
186 assert(status != FPS_STATUS_WAIT);
187 break;
188 }
189
190 fclose(pt->f);
191 if (pt->temp_name != NULL) {
192 unlink(pt->temp_name);
193 Free(pt->temp_name);
194 }
195 Free(pt);
196 }
197
198 /* Print or save the contents of the screen as text. */
199 bool
PrintText_action(ia_t ia,unsigned argc,const char ** argv)200 PrintText_action(ia_t ia, unsigned argc, const char **argv)
201 {
202 enum { PM_NONE, PM_FILE, PM_GDI, PM_COMMAND, PM_STRING } mode = PM_NONE;
203 unsigned i;
204 const char *name = NULL;
205 bool secure = appres.secure;
206 ptype_t ptype = P_NONE;
207 bool replace = false;
208 char *temp_name = NULL;
209 unsigned opts = FPS_EVEN_IF_EMPTY;
210 const char *caption = NULL;
211 FILE *f;
212 int fd = -1;
213 fps_status_t status;
214 printtext_t *pt;
215 bool any_file_options = false;
216 #if defined(_WIN32) /*[*/
217 bool any_gdi_options = false;
218 #endif /*]*/
219 bool use_file = false;
220
221 if (!appres.interactive.print_dialog) {
222 opts |= FPS_NO_DIALOG;
223 }
224
225 action_debug(AnPrintText, ia, argc, argv);
226
227 /*
228 * Pick off optional arguments:
229 * file directs the output to a file instead of a command;
230 * must be the last keyword
231 * html generates HTML output instead of ASCII text (and implies
232 * 'file')
233 * rtf generates RTF output instead of ASCII text (and implies
234 * 'file')
235 * gdi prints to a GDI printer (Windows only)
236 * nodialog skip print dialog (Windows only)
237 * this is the default for ws3270
238 * dialog use print dialog (Windows only)
239 * this is the default for wc3270
240 * replace replace the file
241 * append append to the file, if it exists (default)
242 * modi print modified fields in italics
243 * caption "text"
244 * Adds caption text above the screen
245 * %T% is replaced by a timestamp
246 * secure disables the pop-up dialog, if this action is invoked from
247 * a keymap (x3270 only)
248 * command directs the output to a command (this is the default, but
249 * allows the command to be one of the other keywords);
250 * must be the last keyword
251 * string returns the data as a string */
252 for (i = 0; i < argc; i++) {
253 if (!strcasecmp(argv[i], KwFile)) {
254 if (mode != PM_NONE) {
255 popup_an_error(AnPrintText "(): contradictory option '%s'",
256 argv[i]);
257 return false;
258 }
259 mode = PM_FILE;
260 i++;
261 break;
262 } else if (!strcasecmp(argv[i], KwHtml)) {
263 if (ptype != P_NONE) {
264 popup_an_error(AnPrintText "(): contradictory option '%s'",
265 argv[i]);
266 return false;
267 }
268 ptype = P_HTML;
269 } else if (!strcasecmp(argv[i], KwRtf)) {
270 if (ptype != P_NONE) {
271 popup_an_error(AnPrintText "(): contradictory option '%s'",
272 argv[i]);
273 return false;
274 }
275 ptype = P_RTF;
276 } else if (!strcasecmp(argv[i], KwReplace)) {
277 replace = true;
278 any_file_options = true;
279 } else if (!strcasecmp(argv[i], KwAppend)) {
280 replace = false;
281 any_file_options = true;
282 }
283 #if defined(_WIN32) /*[*/
284 else if (!strcasecmp(argv[i], KwGdi)) {
285 if (mode != PM_NONE) {
286 popup_an_error(AnPrintText "(): contradictory option '%s'",
287 argv[i]);
288 return false;
289 }
290 mode = PM_GDI;
291 } else if (!strcasecmp(argv[i], KwNoDialog)) {
292 opts |= FPS_NO_DIALOG;
293 any_gdi_options = true;
294 } else if (!strcasecmp(argv[i], KwDialog)) {
295 opts &= ~FPS_NO_DIALOG;
296 any_gdi_options = true;
297 }
298 #endif /*]*/
299 else if (!strcasecmp(argv[i], KwSecure)) {
300 secure = true;
301 }
302 #if !defined(_WIN32) /*[*/
303 else if (!strcasecmp(argv[i], KwCommand)) {
304 if (mode != PM_NONE) {
305 popup_an_error(AnPrintText "(): contradictory option '%s'",
306 argv[i]);
307 return false;
308 }
309 mode = PM_COMMAND;
310 i++;
311 break;
312 }
313 #endif /*]*/
314 else if (!strcasecmp(argv[i], KwString)) {
315 if (mode != PM_NONE) {
316 popup_an_error(AnPrintText "(): contradictory option '%s'",
317 argv[i]);
318 return false;
319 }
320 mode = PM_STRING;
321 } else if (!strcasecmp(argv[i], KwModi)) {
322 opts |= FPS_MODIFIED_ITALIC;
323 } else if (!strcasecmp(argv[i], KwCaption)) {
324 if (i == argc - 1) {
325 popup_an_error(AnPrintText "(): missing " KwCaption
326 " parameter");
327 return false;
328 }
329 caption = argv[++i];
330 } else {
331 break;
332 }
333 }
334
335 /* Set the default mode, if none has been selected. */
336 if (mode == PM_NONE) {
337 #if !defined(_WIN32) /*[*/
338 mode = PM_COMMAND;
339 #else /*][*/
340 mode = PM_GDI;
341 #endif /*]*/
342 }
343
344 /* Root out some additional option conflicts. */
345 if (any_file_options && mode != PM_FILE) {
346 popup_an_error(AnPrintText "(): " KwFile "-related option(s) given "
347 "when not printing to file");
348 return false;
349 }
350 #if defined(_WIN32) /*[*/
351 if (any_gdi_options && mode != PM_GDI) {
352 popup_an_error(AnPrintText "(): " KwGdi "-related option(s) given "
353 "when not printing via GDI");
354 return false;
355 }
356 #endif /*]*/
357
358 /* Handle positional options. */
359 switch (argc - i) {
360 case 0:
361 /* Use the default command or printer. */
362 #if !defined(_WIN32) /*[*/
363 if (mode == PM_COMMAND) {
364 name = get_resource(ResPrintTextCommand);
365 if (name == NULL || !*name) {
366 name = "lpr";
367 }
368 }
369 #else /*][*/
370 if (mode == PM_GDI) {
371 name = get_resource(ResPrinterName);
372 }
373 #endif /*]*/
374 break;
375 case 1:
376 if (mode == PM_STRING) {
377 popup_an_error(AnPrintText "(): extra argument "
378 "with '" KwString "'");
379 return false;
380 }
381 name = argv[i];
382 break;
383 default:
384 popup_an_error(AnPrintText "(): extra arguments");
385 return false;
386 }
387
388 /* Infer the type from the file suffix. */
389 if (mode == PM_FILE && ptype == P_NONE && name != NULL) {
390 size_t sl = strlen(name);
391
392 if ((sl > 5 && !strcasecmp(name + sl - 5, ".html")) ||
393 (sl > 4 && !strcasecmp(name + sl - 4, ".htm"))) {
394 ptype = P_HTML;
395 } else if (sl > 4 && !strcasecmp(name + sl - 4, ".rtf")) {
396 ptype = P_RTF;
397 }
398 }
399
400 /* Figure out the default ptype, if still not selected. */
401 if (ptype == P_NONE) {
402 if (mode == PM_GDI) {
403 ptype = P_GDI;
404 } else {
405 ptype = P_TEXT;
406 }
407 }
408
409 if (name != NULL && name[0] == '@') {
410 /*
411 * Starting the PrintTextCommand resource value with '@'
412 * suppresses the pop-up dialog, as does setting the 'secure'
413 * resource.
414 */
415 secure = true;
416 name++;
417 }
418
419 /* See if the GUI wants to handle it. */
420 if (!secure && print_text_gui(mode == PM_FILE)) {
421 return true;
422 }
423
424 /* Do the real work. */
425 use_file = (mode == PM_FILE || mode == PM_STRING);
426 if (use_file) {
427 if (mode == PM_STRING) {
428 #if defined(_WIN32) /*[*/
429 fd = win_mkstemp(&temp_name, ptype);
430 #else /*][*/
431 temp_name = NewString("/tmp/x3hXXXXXX");
432 fd = mkstemp(temp_name);
433 #endif /*]*/
434 if (fd < 0) {
435 popup_an_errno(errno, "mkstemp");
436 return false;
437 }
438 f = fdopen(fd, "w+");
439 vtrace("PrintText: using '%s'\n", temp_name);
440 } else {
441 if (name == NULL || !*name) {
442 popup_an_error(AnPrintText "(): missing filename");
443 return false;
444 }
445 f = fopen(name, replace? "w": "a");
446 }
447 } else {
448 #if !defined(_WIN32) /*[*/
449 const char *expanded_name;
450 char *pct_e;
451
452 if ((pct_e = strstr(name, "%E%")) != NULL) {
453 expanded_name = lazyaf("%.*s%s%s",
454 (int)(pct_e - name), name,
455 programname,
456 pct_e + 3);
457 } else {
458 expanded_name = name;
459 }
460 f = printer_open(expanded_name, NULL);
461 #else /*][*/
462 fd = win_mkstemp(&temp_name, ptype);
463 if (fd < 0) {
464 popup_an_errno(errno, "mkstemp");
465 return false;
466 }
467 if (ptype == P_GDI) {
468 f = fdopen(fd, "wb+");
469 } else {
470 f = fdopen(fd, "w+");
471 }
472 vtrace("PrintText: using '%s'\n", temp_name);
473 #endif /*]*/
474 }
475 if (f == NULL) {
476 popup_an_errno(errno, AnPrintText "(): %s", name);
477 if (fd >= 0) {
478 close(fd);
479 }
480 if (temp_name) {
481 unlink(temp_name);
482 Free(temp_name);
483 }
484 return false;
485 }
486
487 /* Captions look nice on GDI, so create a default one. */
488 if (ptype == P_GDI && caption == NULL) {
489 caption = default_caption();
490 }
491
492 pt = (printtext_t *)Calloc(1, sizeof(printtext_t));
493 status = fprint_screen(f, ptype, opts, caption, name, pt);
494 switch (status) {
495 case FPS_STATUS_SUCCESS:
496 case FPS_STATUS_SUCCESS_WRITTEN:
497 vtrace("PrintText: printing succeeded.\n");
498 Free(pt);
499 break;
500 case FPS_STATUS_ERROR:
501 popup_an_error("Screen print failed.");
502 /* fall through */
503 case FPS_STATUS_CANCEL:
504 if (status == FPS_STATUS_CANCEL) {
505 vtrace("PrintText: printing canceled.\n");
506 }
507 Free(pt);
508 fclose(f);
509 if (temp_name) {
510 unlink(temp_name);
511 Free(temp_name);
512 }
513 return false;
514 case FPS_STATUS_WAIT:
515 /* Waiting for asynchronous activity. */
516 assert(ptype == P_GDI);
517 pt->f = f;
518 pt->ptype = ptype;
519 pt->opts = opts;
520 pt->caption = caption;
521 pt->name = name;
522 pt->temp_name = temp_name;
523 task_xwait(pt, printtext_continue, "printing");
524 return true;
525 }
526
527 if (mode == PM_STRING) {
528 char buf[8192];
529
530 /* Print to string. */
531 fflush(f);
532 rewind(f);
533 while (fgets(buf, sizeof(buf), f) != NULL) {
534 action_output("%s", buf);
535 }
536 fclose(f);
537 unlink(temp_name);
538 Free(temp_name);
539 return true;
540 }
541
542 if (use_file) {
543 /* Print to specified file. */
544 fclose(f);
545 return true;
546 }
547
548 /* Print to printer. */
549 #if defined(_WIN32) /*[*/
550 fclose(f);
551 unlink(temp_name);
552 if (appres.interactive.do_confirms) {
553 popup_an_info("Screen image printing.\n");
554 }
555 #endif /*]*/
556 Free(temp_name);
557 return true;
558 }
559
560 /**
561 * Print screen module registration.
562 */
563 void
print_screen_register(void)564 print_screen_register(void)
565 {
566 static action_table_t print_text_actions[] = {
567 { AnPrintText, PrintText_action, ACTION_KE },
568 };
569
570 /* Register the actions. */
571 register_actions(print_text_actions, array_count(print_text_actions));
572 }
573