1 /*
2 * gretl -- Gnu Regression, Econometrics and Time-series Library
3 * Copyright (C) 2001 Allin Cottrell and Riccardo "Jack" Lucchetti
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 #undef CHILD_DEBUG
21
22 #include "gretl.h"
23 #include "gretlwin32.h"
24 #include "textutil.h"
25 #include "guiprint.h"
26 #include "gpt_control.h"
27 #include "menustate.h"
28 #include "gretl_www.h"
29
30 #include <dirent.h>
31
32 #include <pango/pangowin32.h>
33 #include <gdk/gdkwin32.h>
34
35 /* extra Windows headers */
36 #include <shlobj.h>
37 #include <shellapi.h>
38 #include <shlwapi.h>
39 #include <fcntl.h>
40
41 #define MAX_CONSOLE_LINES 500
42
43 #define SET_FONT 0
44
redirect_io_to_console(void)45 void redirect_io_to_console (void)
46 {
47 #if SET_FONT
48 CONSOLE_FONT_INFOEX cfie = {0};
49 int font_ok;
50 #endif
51 CONSOLE_SCREEN_BUFFER_INFO coninfo;
52 int conhandle;
53 HANDLE stdhandle;
54 FILE *fp;
55
56 AllocConsole();
57
58 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),
59 &coninfo);
60
61 coninfo.dwSize.Y = MAX_CONSOLE_LINES;
62 SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE),
63 coninfo.dwSize);
64
65 #if SET_FONT
66 /* try to ensure TrueType font */
67 cfie.cbSize = sizeof(cfie);
68 lstrcpyW(cfie.FaceName, L"Lucida Console"); /* maybe Consolas? */
69 #endif
70
71 /* redirect unbuffered STDOUT to the console */
72 stdhandle = GetStdHandle(STD_OUTPUT_HANDLE);
73 #if SET_FONT
74 font_ok = SetCurrentConsoleFontEx(stdhandle, 0, &cfie);
75 #endif
76 conhandle = _open_osfhandle((intptr_t) stdhandle, _O_TEXT);
77 fp = _fdopen(conhandle, "w");
78 *stdout = *fp;
79 setvbuf(stdout, NULL, _IONBF, 0);
80
81 /* redirect unbuffered STDERR to the console */
82 stdhandle = GetStdHandle(STD_ERROR_HANDLE);
83 #if SET_FONT
84 font_ok = SetCurrentConsoleFontEx(stdhandle, 0, &cfie);
85 #endif
86 conhandle = _open_osfhandle((intptr_t) stdhandle, _O_TEXT);
87 fp = _fdopen(conhandle, "w");
88 *stderr = *fp;
89 setvbuf(stderr, NULL, _IONBF, 0);
90
91 if (IsValidCodePage(65001) && SetConsoleOutputCP(65001)) {
92 ; /* OK */
93 }
94 }
95
96 /* Asynchronous execution of child process. We'll be ready
97 to re-encode @arg if necessary, but in some cases (see
98 the calls below) that will already be handled. This is
99 flagged by the fact that the recoded argument is already
100 composited into @prog, and the @prog string will start
101 with a double quote.
102 */
103
real_create_child_process(const char * prog,const char * arg,const char * opts,int showerr)104 static int real_create_child_process (const char *prog,
105 const char *arg,
106 const char *opts,
107 int showerr)
108 {
109 PROCESS_INFORMATION proc_info;
110 STARTUPINFO start_info;
111 gchar *cmdline = NULL;
112 gchar *pconv = NULL;
113 gchar *aconv = NULL;
114 int ret, err = 0;
115
116 /* We assume that @opts will not need recoding */
117
118 err = ensure_locale_encoding(&prog, &pconv, &arg, &aconv);
119 if (err) {
120 return err;
121 }
122
123 if (arg == NULL && opts == NULL) {
124 if (*prog == '"') {
125 /* already wrapped in quotes */
126 cmdline = g_strdup(prog);
127 } else {
128 cmdline = g_strdup_printf("\"%s\"", prog);
129 }
130 } else if (arg != NULL && opts != NULL) {
131 cmdline = g_strdup_printf("\"%s\" %s \"%s\"", prog, opts, arg);
132 } else if (arg != NULL) {
133 cmdline = g_strdup_printf("\"%s\" \"%s\"", prog, arg);
134 } else {
135 cmdline = g_strdup_printf("\"%s\" %s", prog, opts);
136 }
137
138 ZeroMemory(&proc_info, sizeof proc_info);
139 ZeroMemory(&start_info, sizeof start_info);
140 start_info.cb = sizeof start_info;
141
142 ret = CreateProcess(NULL,
143 cmdline, /* command line */
144 NULL, /* process security attributes */
145 NULL, /* primary thread security attributes */
146 FALSE, /* handles are inherited? */
147 0, /* creation flags */
148 NULL, /* NULL => use parent's environment */
149 NULL, /* use parent's current directory */
150 &start_info, /* receives STARTUPINFO */
151 &proc_info); /* receives PROCESS_INFORMATION */
152
153 if (ret == 0) {
154 if (showerr) {
155 win_show_last_error();
156 }
157 err = 1;
158 }
159
160 #ifdef CHILD_DEBUG
161 if (err) {
162 fprintf(stderr, "gretl: create_child_process():\n"
163 " cmdline='%s'\n\n", cmdline);
164 fprintf(stderr, " return from CreateProcess() = %d\n", ret);
165 }
166 #endif
167
168 g_free(cmdline);
169 g_free(pconv);
170 g_free(aconv);
171
172 return err;
173 }
174
win32_run_async(const char * prog,const char * arg)175 int win32_run_async (const char *prog, const char *arg)
176 {
177 return real_create_child_process(prog, arg, NULL, 1);
178 }
179
180 /* try registry for path to Rgui.exe */
181
Rgui_path_from_registry(void)182 static int Rgui_path_from_registry (void)
183 {
184 char tmp[MAX_PATH];
185 int err;
186
187 err = R_path_from_registry(tmp, RGUI);
188
189 if (!err) {
190 *Rcommand = '\0';
191 strncat(Rcommand, tmp, MAXSTR - 1);
192 }
193
194 return err;
195 }
196
197 /* start R in asynchronous (interactive) mode */
198
win32_start_R_async(void)199 void win32_start_R_async (void)
200 {
201 const char *opts = "--no-init-file --no-restore-data";
202 int err;
203
204 err = real_create_child_process(Rcommand, NULL, opts, 0);
205
206 if (err) {
207 /* The default Rcommand (plain "Rgui.exe"), or the value
208 entered by the user via the GUI, didn't work, so we try
209 figuring this out from the registry.
210 */
211 err = Rgui_path_from_registry();
212 if (!err) {
213 real_create_child_process(Rcommand, NULL, opts, 1);
214 } else {
215 gui_errmsg(E_EXTERNAL);
216 }
217 }
218 }
219
dummy_output_handler(const gchar * log_domain,GLogLevelFlags log_level,const gchar * message,gpointer user_data)220 static void dummy_output_handler (const gchar *log_domain,
221 GLogLevelFlags log_level,
222 const gchar *message,
223 gpointer user_data)
224 {
225 return;
226 }
227
stderr_output_handler(const gchar * log_domain,GLogLevelFlags log_level,const gchar * message,gpointer user_data)228 static void stderr_output_handler (const gchar *log_domain,
229 GLogLevelFlags log_level,
230 const gchar *message,
231 gpointer user_data)
232 {
233 fprintf(stderr, "%s : %s\n", log_domain, message);
234 }
235
set_g_logging(int debug)236 static void set_g_logging (int debug)
237 {
238 void (*handler) (const gchar *, GLogLevelFlags,
239 const gchar *, gpointer);
240 GLogLevelFlags flags = G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
241 G_LOG_LEVEL_DEBUG;
242
243 if (debug) {
244 handler = stderr_output_handler;
245 } else {
246 handler = dummy_output_handler;
247 }
248
249 g_log_set_handler("Gtk", flags, (GLogFunc) handler, NULL);
250 g_log_set_handler("Gdk", flags, (GLogFunc) handler, NULL);
251 g_log_set_handler("GLib", flags, (GLogFunc) handler, NULL);
252 g_log_set_handler("Pango", flags, (GLogFunc) handler, NULL);
253 g_log_set_handler("GtkSourceView", flags, (GLogFunc) handler, NULL);
254 }
255
get_default_windows_app_font(char * target)256 void get_default_windows_app_font (char *target)
257 {
258 NONCLIENTMETRICS ncm;
259
260 memset(&ncm, 0, sizeof ncm);
261 ncm.cbSize = sizeof ncm;
262
263 if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0)) {
264 HDC screen = GetDC(0);
265 double y_scale = 72.0 / GetDeviceCaps(screen, LOGPIXELSY);
266 int point_size = (int) (ncm.lfMenuFont.lfHeight * y_scale);
267
268 if (point_size < 0) {
269 point_size = -point_size;
270 }
271 sprintf(target, "%s %d", ncm.lfMenuFont.lfFaceName, point_size);
272 ReleaseDC(0, screen);
273 } else {
274 /* fallback */
275 strcpy(target, "tahoma 8");
276 }
277 }
278
279 static char *winlocale;
280
record_win32_locale(char * s)281 void record_win32_locale (char *s)
282 {
283 if (s != NULL) {
284 winlocale = gretl_strdup(s);
285 }
286 }
287
gretl_win32_debug_init(int debug)288 void gretl_win32_debug_init (int debug)
289 {
290 if (debug) {
291 char *s = getenv("OSTYPE");
292 const char *charset = NULL;
293 gchar *pkgdir;
294
295 if (s == NULL || strcmp(s, "msys")) {
296 /* This doesn't work if gretl is launched
297 from an MSYS2 terminal window, and in that
298 context we should have OSTYPE=msys in the
299 environment.
300 */
301 redirect_io_to_console();
302 }
303 set_windebug(1);
304 fprintf(stderr, "Windows locale = %s\n",
305 winlocale == NULL ? "NULL" : winlocale);
306 g_get_charset(&charset);
307 fprintf(stderr, "charset = %s\n", charset);
308 pkgdir = g_win32_get_package_installation_directory_of_module(NULL);
309 fprintf(stderr, "pkgdir = '%s'\n", pkgdir);
310 g_free(pkgdir);
311 }
312
313 set_g_logging(debug);
314 }
315
316 /* Carry out some Windows-specific start-up tasks, and
317 call read_rc to get the per-user configuration info.
318 */
319
gretl_win32_init(int debug)320 void gretl_win32_init (int debug)
321 {
322 char tmp[4] = {0};
323
324 #if GTK_MAJOR_VERSION < 3
325 read_reg_val(HKEY_CURRENT_USER,
326 "Microsoft\\Windows\\CurrentVersion\\ThemeManager",
327 "ThemeActive", tmp);
328 set_wimp_preferred(strcmp(tmp, "1") == 0);
329 #endif
330
331 read_win32_config(debug);
332 set_gretl_startdir();
333 }
334
prn_to_clipboard(PRN * prn,int fmt)335 int prn_to_clipboard (PRN *prn, int fmt)
336 {
337 char *buf = gretl_print_steal_buffer(prn);
338 char *modbuf = NULL;
339 int rtf_format = 0;
340 int err = 0;
341
342 if (buf == NULL || *buf == '\0') {
343 errbox(_("Copy buffer was empty"));
344 return 0;
345 }
346
347 if (!OpenClipboard(NULL)) {
348 errbox(_("Cannot open the clipboard"));
349 return 1;
350 }
351
352 if (fmt == GRETL_FORMAT_RTF || fmt == GRETL_FORMAT_RTF_TXT) {
353 rtf_format = 1;
354 }
355
356 EmptyClipboard();
357
358 err = maybe_post_process_buffer(buf, fmt, W_COPY, &modbuf);
359
360 if (!err) {
361 HGLOBAL winclip;
362 LPTSTR ptr;
363 unsigned clip_format;
364 gunichar2 *ubuf = NULL;
365 char *winbuf;
366 glong wrote = 0;
367 size_t sz;
368
369 winbuf = modbuf != NULL ? modbuf : buf;
370
371 if (!rtf_format && !gretl_is_ascii(winbuf)) {
372 /* for Windows clipboard, recode UTF-8 to UTF-16 */
373 ubuf = g_utf8_to_utf16(winbuf, -1, NULL, &wrote, NULL);
374 }
375
376 if (ubuf != NULL) {
377 sz = (wrote + 1) * sizeof(gunichar2);
378 } else {
379 sz = strlen(winbuf) + 1;
380 }
381
382 winclip = GlobalAlloc(GMEM_MOVEABLE, sz);
383 ptr = GlobalLock(winclip);
384 if (ubuf != NULL) {
385 memcpy(ptr, ubuf, sz);
386 } else {
387 memcpy(ptr, winbuf, sz);
388 }
389 GlobalUnlock(winclip);
390
391 if (ubuf != NULL) {
392 clip_format = CF_UNICODETEXT;
393 } else if (rtf_format) {
394 clip_format = RegisterClipboardFormat("Rich Text Format");
395 } else if (fmt == GRETL_FORMAT_CSV) {
396 clip_format = RegisterClipboardFormat("CSV");
397 } else {
398 clip_format = CF_TEXT;
399 }
400
401 SetClipboardData(clip_format, winclip);
402
403 if (ubuf != NULL) {
404 g_free(ubuf);
405 }
406 }
407
408 CloseClipboard();
409
410 free(buf);
411 free(modbuf);
412
413 return err;
414 }
415
416 /* copy plot to clipboard by generating an EMF file (enhanced
417 metafile), reading it into a buffer, and putting it on the
418 clipboard.
419 */
420
emf_to_clipboard(char * emfname)421 int emf_to_clipboard (char *emfname)
422 {
423 HWND mainw;
424 HENHMETAFILE hemfclip;
425 HENHMETAFILE hemf = NULL;
426 HANDLE htest;
427 gchar *fconv = NULL;
428
429 #if GTK_MAJOR_VERSION > 2
430 mainw = GDK_WINDOW_HWND(gtk_widget_get_window(mdata->main));
431 #else
432 mainw = GDK_WINDOW_HWND(mdata->main->window);
433 #endif
434 if (mainw == NULL) {
435 errbox("Got NULL HWND");
436 return 1;
437 }
438
439 if (!OpenClipboard(mainw)) {
440 errbox(_("Cannot open the clipboard"));
441 return 1;
442 }
443
444 EmptyClipboard();
445
446 if (utf8_encoded(emfname)) {
447 gunichar2 *fconv;
448
449 fconv = g_utf8_to_utf16(emfname, -1, NULL, NULL, NULL);
450 if (fconv != NULL) {
451 hemf = GetEnhMetaFileW(fconv);
452 g_free(fconv);
453 }
454 } else {
455 hemf = GetEnhMetaFile(emfname);
456 }
457
458 if (hemf == NULL) {
459 errbox("Couldn't get handle to graphic metafile");
460 return 1;
461 }
462
463 hemfclip = CopyEnhMetaFile(hemf, NULL);
464 if (hemfclip == NULL) {
465 errbox("Couldn't copy graphic metafile");
466 return 1;
467 }
468
469 htest = SetClipboardData(CF_ENHMETAFILE, hemfclip);
470 if (htest == NULL) {
471 errbox("Failed to put data on clipboard");
472 return 1;
473 }
474
475 CloseClipboard();
476 DeleteEnhMetaFile(hemf);
477
478 return 0;
479 }
480
481 #if 0 /* unused, but may have use again */
482
483 static long get_reg_key (HKEY key, char *subkey, char *retdata)
484 {
485 long err;
486 HKEY hkey;
487
488 err = RegOpenKeyEx(key, subkey, 0, KEY_QUERY_VALUE, &hkey);
489
490 if (err == ERROR_SUCCESS) {
491 long datasize = MAX_PATH;
492 char data[MAX_PATH];
493
494 RegQueryValue(hkey, NULL, (LPSTR) data, &datasize);
495
496 lstrcpy(retdata, data);
497 RegCloseKey(hkey);
498 }
499
500 return err;
501 }
502
503 #endif
504
505 static int dde_open_pdf (const char *exename,
506 const char *fname,
507 const char *dest);
508
get_exe_for_type(const char * ext)509 static char *get_exe_for_type (const char *ext)
510 {
511 char *exe = NULL;
512 HRESULT ret;
513 DWORD len = 0;
514
515 ret = AssocQueryString(ASSOCF_NOTRUNCATE, ASSOCSTR_EXECUTABLE,
516 ext, NULL, NULL, &len);
517 if (ret == S_FALSE) {
518 exe = calloc(len + 1, 1);
519 ret = AssocQueryString(0, ASSOCSTR_EXECUTABLE,
520 ext, NULL, exe, &len);
521 if (ret != S_OK) {
522 fprintf(stderr, "couldn't determine exe for type %s\n", ext);
523 free(exe);
524 exe = NULL;
525 }
526 }
527
528 return exe;
529 }
530
get_pdf_service_name(char * service,const char * exe)531 static int get_pdf_service_name (char *service, const char *exe)
532 {
533 const char *s;
534 char ver[4] = {0};
535 int n, err = 0;
536
537 *service = '\0';
538
539 /* cases include:
540 $pf\Adobe\Acrobat Reader DC\...
541 $pf\Adobe\Acrobat 10.0\...
542 */
543
544 s = strstr(exe, "obe\\Acrobat");
545 if (s == NULL) {
546 /* huh? */
547 return 1;
548 }
549
550 /* get in place to read "Reader <version>" or
551 just "<version>" for Acrobat */
552 s += 11;
553 s += strspn(s, " ");
554
555 if (!strncmp(s, "Reader ", 7)) {
556 s += 7;
557 if (sscanf(s, "%3[^\\]", ver) == 1) {
558 if (!strcmp(ver, "DC")) {
559 strcpy(service, "AcroViewR15");
560 } else if (isdigit(*ver)) {
561 n = atoi(ver);
562 if (n >= 10) {
563 sprintf(service, "AcroViewR%d", n);
564 } else {
565 strcpy(service, "AcroView");
566 }
567 } else if (*ver == 'X') {
568 if (ver[1] == 'I') {
569 strcpy(service, "AcroViewR11");
570 } else {
571 strcpy(service, "AcroViewR10");
572 }
573 }
574 }
575 } else {
576 /* Acrobat as such */
577 if (sscanf(s, "%3[^\\]", ver) == 1) {
578 if (!strcmp(ver, "DC")) {
579 strcpy(service, "AcroViewA15");
580 } else if (*ver == 'X') {
581 if (ver[1] == 'I') {
582 strcpy(service, "AcroViewA11");
583 } else {
584 strcpy(service, "AcroViewA10");
585 }
586 } else if (isdigit(*ver)) {
587 n = atoi(ver);
588 if (n >= 10) {
589 sprintf(service, "AcroViewA%d", n);
590 } else {
591 strcpy(service, "AcroView");
592 }
593 }
594 }
595 }
596
597 if (*service == '\0') {
598 fprintf(stderr, "pdf_service_name: no service!\n");
599 err = 1;
600 }
601
602 return err;
603 }
604
605 static int coinitted;
606
607 /* If and when win32_open_arg() gets called, @arg should
608 already be re-encoded to the locale if necessary.
609 */
610
win32_open_arg(const char * arg,char * ext)611 static int win32_open_arg (const char *arg, char *ext)
612 {
613 int err = 0;
614
615 if (!coinitted) {
616 CoInitialize(NULL);
617 coinitted = 1;
618 }
619
620 /* From the MSDN doc: "If the function succeeds, it returns a
621 value greater than 32. If the function fails, it returns an
622 error value that indicates the cause of the failure. The return
623 value is cast as an HINSTANCE for backward compatibility with
624 16-bit Windows applications. It is not a true HINSTANCE,
625 however. It can be cast only to an int and compared to either
626 32 or the following error codes below..."
627 */
628
629 if ((int) ShellExecute(NULL, "open", arg, NULL, NULL, SW_SHOW) <= 32) {
630 /* if the above fails, try via the registry */
631 char *exe = get_exe_for_type(ext);
632
633 if (exe == NULL) {
634 err = 1;
635 } else {
636 gchar *cmd = g_strdup_printf("\"%s\" \"%s\"", exe, arg);
637
638 err = real_create_child_process(cmd, NULL, NULL, 1);
639 g_free(cmd);
640 free(exe);
641 }
642 }
643
644 return err;
645 }
646
win32_open_pdf(const char * fname,const char * dest)647 int win32_open_pdf (const char *fname, const char *dest)
648 {
649 char *exe = get_exe_for_type(".pdf");
650 gchar *fconv = NULL;
651 gchar *cmd = NULL;
652 int err = 0;
653
654 if (utf8_encoded(fname)) {
655 /* note: @exe will be in the locale already */
656 fconv = g_win32_locale_filename_from_utf8(fname);
657 if (fconv != NULL) {
658 fname = (const char *) fconv;
659 }
660 }
661
662 if (exe != NULL && strstr(exe, "Acro") != NULL) {
663 /* give DDE a whirl */
664 err = dde_open_pdf(exe, fname, dest);
665 if (err) {
666 /* but if that fails, try something a bit
667 less ambitious */
668 err = 0;
669 cmd = g_strdup_printf("\"%s\" /A \"nameddest=%s\" \"%s\"",
670 exe, dest, fname);
671 err = real_create_child_process(cmd, NULL, NULL, 1);
672 }
673 } else if (exe != NULL && strstr(exe, "umatra") != NULL) {
674 cmd = g_strdup_printf("\"%s\" -named-dest %s \"%s\"",
675 exe, dest, fname);
676 err = real_create_child_process(cmd, NULL, NULL, 1);
677 } else {
678 err = win32_open_arg(fname, ".pdf");
679 }
680
681 free(exe);
682 g_free(fconv);
683 g_free(cmd);
684
685 return err;
686 }
687
browser_open(const char * url)688 int browser_open (const char *url)
689 {
690 char sfx[5] = ".htm";
691
692 return win32_open_arg(url, sfx);
693 }
694
win32_open_file(const char * fname)695 int win32_open_file (const char *fname)
696 {
697 char sfx[5];
698 int err = 0;
699
700 if (has_suffix(fname, ".pdf")) {
701 strcpy(sfx, ".pdf");
702 } else if (has_suffix(fname, ".ps") ||
703 has_suffix(fname, ".eps")) {
704 strcpy(sfx, ".ps");
705 } else {
706 *sfx = '\0';
707 }
708
709 if (utf8_encoded(fname)) {
710 gchar *fconv = g_win32_locale_filename_from_utf8(fname);
711
712 if (fconv != NULL) {
713 err = win32_open_arg(fconv, sfx);
714 g_free(fconv);
715 } else {
716 err = E_FOPEN;
717 }
718 } else {
719 err = win32_open_arg(fname, sfx);
720 }
721
722 return err;
723 }
724
win32_rename_dir(const char * oldname,const char * newname)725 int win32_rename_dir (const char *oldname, const char *newname)
726 {
727 char *oldtmp = NULL, *newtmp = NULL;
728 int len, err;
729
730 /* trim trailing slash for non-root dirs */
731
732 len = strlen(oldname);
733 if (len > 1 && oldname[len-1] == '\\' && oldname[len-2] != ':') {
734 oldtmp = gretl_strndup(oldname, len - 1);
735 oldname = oldtmp;
736 }
737
738 len = strlen(newname);
739 if (len > 1 && newname[len-1] == '\\' && newname[len-2] != ':') {
740 newtmp = gretl_strndup(newname, len - 1);
741 newname = newtmp;
742 }
743
744 err = gretl_rename(oldname, newname);
745
746 if (oldtmp != NULL || newtmp != NULL) {
747 free(oldtmp);
748 free(newtmp);
749 }
750
751 return err;
752 }
753
754 /* experimental: use DDE */
755
756 #include <ddeml.h>
757 #include <dde.h>
758 #include <malloc.h>
759
760 #define CONNECT_DELAY 500 /* ms */
761 #define TRANSACTION_TIMEOUT 5000 /* ms */
762 #define MAX_INPUT_IDLE_WAIT INFINITE /* ms */
763
init_callback(UINT uType,UINT uFmt,HCONV hconv,HSZ hsz1,HSZ hsz2,HDDEDATA hdata,DWORD dwData1,DWORD dwData2)764 HDDEDATA CALLBACK init_callback (UINT uType, UINT uFmt, HCONV hconv,
765 HSZ hsz1, HSZ hsz2, HDDEDATA hdata,
766 DWORD dwData1, DWORD dwData2)
767 {
768 if (uType == XTYP_ADVDATA) {
769 DWORD len = DdeGetData(hdata, NULL, 0, 0);
770 char *buf = (char *)_alloca(len + 1);
771
772 DdeGetData(hdata, (LPBYTE) buf, len + 1, 0);
773 return (HDDEDATA) DDE_FACK;
774 }
775
776 return (HDDEDATA) NULL;
777 }
778
start_dde_server(LPCTSTR prog)779 static int start_dde_server (LPCTSTR prog)
780 {
781 PROCESS_INFORMATION pi;
782 STARTUPINFO si;
783
784 ZeroMemory(&si, sizeof si);
785 si.cb = sizeof si;
786
787 if (!CreateProcess(NULL, (LPTSTR) prog, NULL, NULL, FALSE, 0,
788 NULL, NULL, &si, &pi)) {
789 fprintf(stderr, "DDE: couldn't start process %s\n", prog);
790 return 1;
791 }
792
793 WaitForInputIdle(pi.hProcess, MAX_INPUT_IDLE_WAIT);
794
795 CloseHandle(pi.hThread);
796 CloseHandle(pi.hProcess);
797
798 return 0;
799 }
800
open_dde_conversation(LPCTSTR topic_name,const char * exename,const char * ddename,HCONV * convp)801 static DWORD open_dde_conversation (LPCTSTR topic_name,
802 const char *exename,
803 const char *ddename,
804 HCONV *convp)
805 {
806 DWORD session = 0;
807 HCONV conversation = NULL;
808 HSZ service, topic;
809 UINT ret;
810 int i, err = 0;
811
812 ret = DdeInitialize(&session, (PFNCALLBACK) init_callback,
813 APPCLASS_STANDARD | APPCMD_CLIENTONLY, 0);
814
815 if (ret != DMLERR_NO_ERROR) {
816 fprintf(stderr, "DDE: couldn't initialize session\n");
817 return 0;
818 }
819
820 service = DdeCreateStringHandle(session, ddename, CP_WINANSI);
821 topic = DdeCreateStringHandle(session, topic_name, CP_WINANSI);
822
823 if (!service || !topic) {
824 fprintf(stderr, "DDE: string creation failed\n");
825 DdeUninitialize(session);
826 return 0;
827 }
828
829 conversation = DdeConnect(session, service, topic, 0);
830
831 if (conversation == NULL) {
832 err = start_dde_server(exename);
833 if (!err) {
834 /* try to connect */
835 for (i=0; i<5 && !conversation; i++) {
836 Sleep(CONNECT_DELAY);
837 conversation = DdeConnect(session, service, topic, 0);
838 }
839 }
840 if (conversation == NULL && !err) {
841 fprintf(stderr, "DDE: couldn't contact server %s\n", ddename);
842 err = 1;
843 }
844 }
845
846 DdeFreeStringHandle(session, service);
847 DdeFreeStringHandle(session, topic);
848
849 if (err) {
850 DdeUninitialize(session);
851 session = 0;
852 } else {
853 *convp = conversation;
854 }
855
856 return session;
857 }
858
exec_dde_command(const char * buf,HCONV conversation,DWORD session)859 static int exec_dde_command (const char *buf, HCONV conversation,
860 DWORD session)
861 {
862 HDDEDATA ret;
863 int err;
864
865 ret = DdeClientTransaction((LPBYTE) buf, strlen(buf) + 1,
866 conversation, 0, 0, XTYP_EXECUTE,
867 TRANSACTION_TIMEOUT, 0);
868
869 /* MSDN: "The return value is zero for all unsuccessful
870 transactions"
871 */
872 err = (ret == 0);
873
874 return err;
875 }
876
dde_open_pdf(const char * exename,const char * fname,const char * dest)877 static int dde_open_pdf (const char *exename,
878 const char *fname,
879 const char *dest)
880 {
881 DWORD session = 0;
882 HCONV conversation = NULL;
883 char ddename[32];
884 char *buf = NULL;
885 int err = 0;
886
887 /* Try to figure out the name of the DDE service
888 provided by Acrobat Reader or Acrobat (oh, Adobe!)
889 */
890 err = get_pdf_service_name(ddename, exename);
891 if (err) {
892 return err;
893 }
894
895 buf = calloc(strlen(fname) + strlen(dest) + 32, 1);
896
897 session = open_dde_conversation("control", exename, ddename,
898 &conversation);
899 if (session == 0) {
900 free(buf);
901 return 1;
902 }
903
904 /* Adobe DDE commands only work on documents opened
905 by DDE. It's therefore necessary to close the document
906 first (if it's already open) then reopen it.
907 */
908
909 sprintf(buf, "[DocClose(\"%s\")]", fname);
910 exec_dde_command(buf, conversation, session);
911 sprintf(buf, "[DocOpen(\"%s\")]", fname);
912 exec_dde_command(buf, conversation, session);
913 if (strstr(ddename, "wR") == NULL) {
914 /* specific to acrord32 version 8 bug */
915 sprintf(buf, "[DocOpen(\"%s\")]", fname);
916 exec_dde_command(buf, conversation, session);
917 }
918 sprintf(buf, "[FileOpen(\"%s\")]", fname);
919 exec_dde_command(buf, conversation, session);
920
921 sprintf(buf, "[DocGoToNameDest(\"%s\", %s)]", fname, dest);
922 err = exec_dde_command(buf, conversation, session);
923
924 free(buf);
925
926 if (conversation) {
927 DdeDisconnect(conversation);
928 }
929 if (session) {
930 DdeUninitialize(session);
931 }
932
933 return err;
934 }
935