Blob Blame History Raw
/*
 * $Id: winmain.c,v 1.101.2.2 2017/07/30 07:52:31 markisch Exp $
 */

/* GNUPLOT - win/winmain.c */
/*[
 * Copyright 1992, 1993, 1998, 2004   Maurice Castro, Russell Lang
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose with or without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.
 *
 * Permission to modify the software is granted, but not the right to
 * distribute the complete modified source code.  Modifications are to
 * be distributed as patches to the released version.  Permission to
 * distribute binaries produced by compiling modified sources is granted,
 * provided you
 *   1. distribute the corresponding source modifications from the
 *    released version in the form of a patch file along with the binaries,
 *   2. add special version identification to distinguish your version
 *    in addition to the base release version number,
 *   3. provide your name and address as the primary contact for the
 *    support of your modified version, and
 *   4. retain our contact information in regard to use of the base
 *    software.
 * Permission to distribute the released version of the source code along
 * with corresponding source modifications in the form of a patch file is
 * granted with same provisions 2 through 4 for binary distributions.
 *
 * This software is provided "as is" without express or implied warranty
 * to the extent permitted by applicable law.
]*/

/*
 * AUTHORS
 *
 *   Maurice Castro
 *   Russell Lang
 *
 */

/* This file implements the initialization code for running gnuplot   */
/* under Microsoft Windows.                                           */
/*                                                                    */
/* The modifications to allow Gnuplot to run under Windows were made  */
/* by Maurice Castro. (maurice@bruce.cs.monash.edu.au)  3 Jul 1992    */
/* and Russell Lang (rjl@monu1.cc.monash.edu.au) 30 Nov 1992          */
/*                                                                    */

#include "syscfg.h"
#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <htmlhelp.h>
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <tchar.h>
#include <ctype.h>
#include <fcntl.h>
#include <io.h>
#include <sys/stat.h>
#include "alloc.h"
#include "plot.h"
#include "setshow.h"
#include "version.h"
#include "command.h"
#include "winmain.h"
#include "wtext.h"
#include "wcommon.h"
#ifdef HAVE_GDIPLUS
#include "wgdiplus.h"
#endif
#ifdef HAVE_D2D
#include "wd2d.h"
#endif
#ifdef WXWIDGETS
#include "wxterminal/wxt_term.h"
#endif
#ifdef HAVE_LIBCACA
# define TERM_PUBLIC_PROTO
# include "caca.trm"
# undef TERM_PUBLIC_PROTO
#endif


/* workaround for old header files */
#ifndef CSIDL_APPDATA
# define CSIDL_APPDATA (0x001a)
#endif

/* limits */
#define MAXSTR 255
#define MAXPRINTF 1024
  /* used if vsnprintf(NULL,0,...) returns zero (MingW 3.4) */

/* globals */
#ifndef WGP_CONSOLE
TW textwin;
MW menuwin;
#endif
LPGW graphwin; /* current graph window */
LPGW listgraphs; /* list of graph windows */
PW pausewin;
LPTSTR szModuleName;
LPTSTR szPackageDir;
LPTSTR winhelpname;
LPTSTR szMenuName;
static LPTSTR szLanguageCode = NULL;
HWND help_window = NULL;

char *authors[]={
                 "Colin Kelley",
                 "Thomas Williams"
                };

void WinExit(void);
static void WinCloseHelp(void);
int CALLBACK ShutDown();
#ifdef WGP_CONSOLE
static int ConsolePutS(const char *str);
static int ConsolePutCh(int ch);
static BOOL WINAPI ConsoleHandler(DWORD dwType);
#endif

static void
CheckMemory(LPTSTR str)
{
    if (str == NULL) {
	MessageBox(NULL, TEXT("out of memory"), TEXT("gnuplot"), MB_ICONSTOP | MB_OK);
	gp_exit(EXIT_FAILURE);
    }
}


int
Pause(LPSTR str)
{
    int rc;

    pausewin.Message = UnicodeText(str, encoding);
    rc = PauseBox(&pausewin) == IDOK;
    free(pausewin.Message);
    return rc;
}


void
kill_pending_Pause_dialog()
{
    if (!pausewin.bPause) /* no Pause dialog displayed */
	return;
    /* Pause dialog displayed, thus kill it */
    DestroyWindow(pausewin.hWndPause);
    pausewin.bPause = FALSE;
}


/* atexit procedure */
void
WinExit(void)
{
    LPGW lpgw;

    /* Last chance to close Windows help, call before anything else to avoid a crash. */
    WinCloseHelp();

    /* clean-up call for printing system */
    PrintingCleanup();

    term_reset();

    _fcloseall();

    /* Close all graph windows */
    for (lpgw = listgraphs; lpgw != NULL; lpgw = lpgw->next) {
	if (GraphHasWindow(lpgw))
	    GraphClose(lpgw);
    }

#ifndef WGP_CONSOLE
    TextMessage();  /* process messages */
# ifndef __WATCOMC__
    /* revert C++ stream redirection */
    RedirectOutputStreams(FALSE);
# endif
#endif
#ifdef HAVE_GDIPLUS
    gdiplusCleanup();
#endif
#ifdef HAVE_D2D
    d2dCleanup();
#endif
    CoUninitialize();
    return;
}


/* call back function from Text Window WM_CLOSE */
int CALLBACK
ShutDown()
{
    /* First chance for wgnuplot to close help system. */
    WinCloseHelp();
    gp_exit(EXIT_SUCCESS);
    return 0;
}


/* This function can be used to retrieve version information from
 * Window's Shell and common control libraries (Comctl32.dll,
 * Shell32.dll, and Shlwapi.dll) The code was copied from the MSDN
 * article "Shell and Common Controls Versions" */
DWORD
GetDllVersion(LPCTSTR lpszDllName)
{
    HINSTANCE hinstDll;
    DWORD dwVersion = 0;

    /* For security purposes, LoadLibrary should be provided with a
       fully-qualified path to the DLL. The lpszDllName variable should be
       tested to ensure that it is a fully qualified path before it is used. */
    hinstDll = LoadLibrary(lpszDllName);

    if (hinstDll) {
	DLLGETVERSIONPROC pDllGetVersion;
	pDllGetVersion = (DLLGETVERSIONPROC)GetProcAddress(hinstDll, "DllGetVersion");

	/* Because some DLLs might not implement this function, you
	must test for it explicitly. Depending on the particular
	DLL, the lack of a DllGetVersion function can be a useful
	indicator of the version. */
	if (pDllGetVersion) {
	    DLLVERSIONINFO dvi;
	    HRESULT hr;

	    ZeroMemory(&dvi, sizeof(dvi));
	    dvi.cbSize = sizeof(dvi);
	    hr = (*pDllGetVersion)(&dvi);
	    if (SUCCEEDED(hr))
		dwVersion = PACKVERSION(dvi.dwMajorVersion, dvi.dwMinorVersion);
	}
	FreeLibrary(hinstDll);
    }
    return dwVersion;
}


BOOL 
IsWindowsXPorLater(void)
{
    OSVERSIONINFO versionInfo;

    /* get Windows version */
    ZeroMemory(&versionInfo, sizeof(OSVERSIONINFO));
    versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionEx(&versionInfo);
    return ((versionInfo.dwMajorVersion > 5) ||
           ((versionInfo.dwMajorVersion == 5) && (versionInfo.dwMinorVersion >= 1)));
}


char *
appdata_directory(void)
{
    HMODULE hShell32;
    FARPROC pSHGetSpecialFolderPath;
    static char dir[MAX_PATH] = "";

    if (dir[0])
	return dir;

    /* FIMXE: "ANSI" Version, no Unicode support */

    /* Make sure that SHGetSpecialFolderPath is supported. */
    hShell32 = LoadLibrary(TEXT("shell32.dll"));
    if (hShell32) {
	pSHGetSpecialFolderPath =
	    GetProcAddress(hShell32, "SHGetSpecialFolderPathA");
	if (pSHGetSpecialFolderPath)
	    (*pSHGetSpecialFolderPath)(NULL, dir, CSIDL_APPDATA, FALSE);
	FreeModule(hShell32);
	return dir;
    }

    /* use APPDATA environment variable as fallback */
    if (dir[0] == NUL) {
	char *appdata = getenv("APPDATA");
	if (appdata) {
	    strcpy(dir, appdata);
	    return dir;
	}
    }

    return NULL;
}


/* retrieve path relative to gnuplot executable */
LPSTR
RelativePathToGnuplot(const char * path)
{
#ifdef UNICODE
    LPSTR ansi_dir = AnsiText(szPackageDir, encoding);
    LPSTR rel_path = (char *) gp_realloc(ansi_dir, strlen(ansi_dir) + strlen(path) + 1, "RelativePathToGnuplot");
    if (rel_path == NULL) {
	free(ansi_dir);
	return (LPSTR) path;
    }
#else
    char * rel_path = (char * ) gp_alloc(strlen(szPackageDir) + strlen(path) + 1, "RelativePathToGnuplot");
    strcpy(rel_path, szPackageDir);
#endif
    /* szPackageDir is guaranteed to have a trailing backslash */
    strcat(rel_path, path);
    return rel_path;
}


static void
WinCloseHelp(void)
{
    /* Due to a known bug in the HTML help system we have to
     * call this as soon as possible before the end of the program.
     * See e.g. http://helpware.net/FAR/far_faq.htm#HH_CLOSE_ALL
     */
    if (IsWindow(help_window))
	SendMessage(help_window, WM_CLOSE, 0, 0);
    Sleep(0);
}


static LPTSTR
GetLanguageCode()
{
    static TCHAR lang[6] = TEXT("");

    if (lang[0] == NUL) {
	GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SABBREVLANGNAME, lang, sizeof(lang));
	//strcpy(lang, "JPN"); //TEST
	/* language definition files for Japanese already use "ja" as abbreviation */
	if (_tcscmp(lang, TEXT("JPN")) == 0)
	    lang[1] = 'A';
	/* prefer lower case */
	lang[0] = tolower((unsigned char)lang[0]);
	lang[1] = tolower((unsigned char)lang[1]);
	/* only use two character sequence */
	lang[2] = NUL;
    }

    return lang;
}


static LPTSTR
LocalisedFile(LPCTSTR name, LPCTSTR ext, LPCTSTR defaultname)
{
    LPTSTR lang;
    LPTSTR filename;

    /* Allow user to override language detection. */
    if (szLanguageCode)
	lang = szLanguageCode;
    else
	lang = GetLanguageCode();

    filename = (LPTSTR) malloc((_tcslen(szModuleName) + _tcslen(name) + _tcslen(lang) + _tcslen(ext) + 1) * sizeof(TCHAR));
    if (filename) {
	_tcscpy(filename, szModuleName);
	_tcscat(filename, name);
	_tcscat(filename, lang);
	_tcscat(filename, ext);
	if (!PathFileExists(filename)) {
	    _tcscpy(filename, szModuleName);
	    _tcscat(filename, defaultname);
	}
    }
    return filename;
}


static void
ReadMainIni(LPTSTR file, LPTSTR section)
{
    TCHAR profile[81] = TEXT("");
    const TCHAR hlpext[] = TEXT(".chm");
    const TCHAR name[] = TEXT("wgnuplot-");

    /* Language code override */
    GetPrivateProfileString(section, TEXT("Language"), TEXT(""), profile, 80, file);
    if (profile[0] != NUL)
	szLanguageCode = _tcsdup(profile);
    else
	szLanguageCode = NULL;

    /* help file name */
    GetPrivateProfileString(section, TEXT("HelpFile"), TEXT(""), profile, 80, file);
    if (profile[0] != NUL) {
	winhelpname = (LPTSTR) malloc((_tcslen(szModuleName) + _tcslen(profile) + 1) * sizeof(TCHAR));
	if (winhelpname) {
	    _tcscpy(winhelpname, szModuleName);
	    _tcscat(winhelpname, profile);
	}
    } else {
	/* default name is "wgnuplot-LL.chm" */
	winhelpname = LocalisedFile(name, hlpext, TEXT(HELPFILE));
    }

    /* menu file name */
    GetPrivateProfileString(section, TEXT("MenuFile"), TEXT(""), profile, 80, file);
    if (profile[0] != NUL) {
	szMenuName = (LPTSTR) malloc((_tcslen(szModuleName) + _tcslen(profile) + 1) * sizeof(TCHAR));
	if (szMenuName) {
	    _tcscpy(szMenuName, szModuleName);
	    _tcscat(szMenuName, profile);
	}
    } else {
	/* default name is "wgnuplot-LL.mnu" */
	szMenuName = LocalisedFile(name, TEXT(".mnu"), TEXT("wgnuplot.mnu"));
    }
}


#ifndef WGP_CONSOLE
int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
#else
int
main(int argc, char **argv)
#endif
{
    LPTSTR tail;
#ifdef WGP_CONSOLE
    HINSTANCE hInstance = GetModuleHandle(NULL), hPrevInstance = NULL;
#else
    int i;
#endif

#ifndef WGP_CONSOLE
# if defined( __MINGW32__) && !defined(_W64)
#  define argc _argc
#  define argv _argv
# else /* MSVC, WATCOM, MINGW-W64 */
#  define argc __argc
#  define argv __argv
# endif
#endif /* WGP_CONSOLE */

    szModuleName = (LPTSTR) malloc((MAXSTR + 1) * sizeof(TCHAR));
    CheckMemory(szModuleName);

    /* get path to gnuplot executable  */
    GetModuleFileName(hInstance, szModuleName, MAXSTR);
    if ((tail = _tcsrchr(szModuleName, '\\')) != NULL) {
	tail++;
	*tail = 0;
    }
    szModuleName = (LPTSTR) realloc(szModuleName, (_tcslen(szModuleName) + 1) * sizeof(TCHAR));
    CheckMemory(szModuleName);

    if (_tcslen(szModuleName) >= 5 && _tcsnicmp(&szModuleName[_tcslen(szModuleName)-5], TEXT("\\bin\\"), 5) == 0) {
	size_t len = _tcslen(szModuleName) - 4;
	szPackageDir = (LPTSTR) malloc((len + 1) * sizeof(TCHAR));
	CheckMemory(szPackageDir);
	_tcsncpy(szPackageDir, szModuleName, len);
	szPackageDir[len] = NUL;
    } else {
	szPackageDir = szModuleName;
    }

#ifndef WGP_CONSOLE
    textwin.hInstance = hInstance;
    textwin.hPrevInstance = hPrevInstance;
    textwin.nCmdShow = nCmdShow;
    textwin.Title = L"gnuplot";
#endif

    /* create structure of first graph window */
    graphwin = (LPGW) calloc(1, sizeof(GW));
    listgraphs = graphwin;

    /* locate ini file */
    {
	char * inifile;
#ifdef UNICODE
	LPWSTR winifile;
#endif
	get_user_env(); /* this hasn't been called yet */
	inifile = gp_strdup("~\\wgnuplot.ini");
	gp_expand_tilde(&inifile);

	/* if tilde expansion fails use current directory as
	    default - that was the previous default behaviour */
	if (inifile[0] == '~') {
	    free(inifile);
	    inifile = "wgnuplot.ini";
	}
#ifdef UNICODE
	graphwin->IniFile = winifile = UnicodeText(inifile, S_ENC_DEFAULT);
#else
	graphwin->IniFile = inifile;
#endif
#ifndef WGP_CONSOLE
	textwin.IniFile = graphwin->IniFile;
#endif
	ReadMainIni(graphwin->IniFile, TEXT("WGNUPLOT"));
    }

#ifndef WGP_CONSOLE
    textwin.IniSection = TEXT("WGNUPLOT");
    textwin.DragPre = L"load '";
    textwin.DragPost = L"'\n";
    textwin.lpmw = &menuwin;
    textwin.ScreenSize.x = 80;
    textwin.ScreenSize.y = 80;
    textwin.KeyBufSize = 2048;
    textwin.CursorFlag = 1; /* scroll to cursor after \n & \r */
    textwin.shutdown = MakeProcInstance((FARPROC)ShutDown, hInstance);
    textwin.AboutText = (LPTSTR) malloc(1024 * sizeof(TCHAR));
    CheckMemory(textwin.AboutText);
    wsprintf(textwin.AboutText,
	TEXT("Version %hs patchlevel %hs\n") \
	TEXT("last modified %hs\n") \
	TEXT("%hs\n%hs, %hs and many others\n") \
	TEXT("gnuplot home:     http://www.gnuplot.info\n"),
	gnuplot_version, gnuplot_patchlevel,
	gnuplot_date,
	gnuplot_copyright, authors[1], authors[0]);
    textwin.AboutText = (LPTSTR) realloc(textwin.AboutText, (_tcslen(textwin.AboutText) + 1) * sizeof(TCHAR));
    CheckMemory(textwin.AboutText);

    menuwin.szMenuName = szMenuName;
#endif

    pausewin.hInstance = hInstance;
    pausewin.hPrevInstance = hPrevInstance;
    pausewin.Title = L"gnuplot pause";

    graphwin->hInstance = hInstance;
    graphwin->hPrevInstance = hPrevInstance;
#ifdef WGP_CONSOLE
    graphwin->lptw = NULL;
#else
    graphwin->lptw = &textwin;
#endif

    /* COM Initialization */
    if (!SUCCEEDED(CoInitialize(NULL))) {
	// FIXME: Need to abort
    }

    /* init common controls */
    {
	INITCOMMONCONTROLSEX initCtrls;
	initCtrls.dwSize = sizeof(INITCOMMONCONTROLSEX);
	initCtrls.dwICC = ICC_WIN95_CLASSES;
	InitCommonControlsEx(&initCtrls);
    }

#ifndef WGP_CONSOLE
    if (TextInit(&textwin))
	gp_exit(EXIT_FAILURE);
    textwin.hIcon = LoadIcon(hInstance, TEXT("TEXTICON"));
    SetClassLongPtr(textwin.hWndParent, GCLP_HICON, (LONG_PTR)textwin.hIcon);

    /* Note: we want to know whether this is an interactive session so that we can
     * decide whether or not to write status information to stderr.  The old test
     * for this was to see if (argc > 1) but the addition of optional command line
     * switches broke this.  What we really wanted to know was whether any of the
     * command line arguments are file names or an explicit in-line "-e command".
     * (This is a copy of a code snippet from plot.c)
     */
    for (i = 1; i < argc; i++) {
	if (!_stricmp(argv[i], "/noend"))
	    continue;
	if ((argv[i][0] != '-') || (argv[i][1] == 'e')) {
	    interactive = FALSE;
	    break;
	}
    }
    if (interactive)
	ShowWindow(textwin.hWndParent, textwin.nCmdShow);
    if (IsIconic(textwin.hWndParent)) { /* update icon */
	RECT rect;
	GetClientRect(textwin.hWndParent, (LPRECT) &rect);
	InvalidateRect(textwin.hWndParent, (LPRECT) &rect, 1);
	UpdateWindow(textwin.hWndParent);
    }
# ifndef __WATCOMC__
    /* Finally, also redirect C++ standard output streams. */
    RedirectOutputStreams(TRUE);
# endif
#else  /* !WGP_CONSOLE */
# ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#  define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
# endif
    {
	/* Enable Windows 10 Console Virtual Terminal Sequences */
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	DWORD  mode;
	GetConsoleMode(handle, &mode);
	SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
    }
    // set console mode handler to catch "abort" signals
    SetConsoleCtrlHandler(ConsoleHandler, TRUE);
#endif

    gp_atexit(WinExit);

    if (!_isatty(_fileno(stdin)))
	_setmode(_fileno(stdin), O_BINARY);

    gnu_main(argc, argv);

    /* First chance to close help system for console gnuplot,
	second for wgnuplot */
    WinCloseHelp();
    gp_exit_cleanup();
    return 0;
}


void
MultiByteAccumulate(BYTE ch, LPWSTR wstr, int * count)
{
    static char mbstr[4] = "";
    static int mbwait = 0;
    static int mbcount = 0;

    *count = 0;

    /* try to re-sync on control characters */
    /* works for utf8 and sjis */
    if (ch < 32) {
	mbwait = mbcount = 0;
	mbstr[0] = NUL;
    }

    if (encoding == S_ENC_UTF8) { /* combine UTF8 byte sequences */
	if (mbwait == 0) {
	    /* first byte */
	    mbcount = 0;
	    mbstr[mbcount] = ch;
	    if ((ch & 0xE0) == 0xC0) {
		// expect one more byte
		mbwait = 1;
	    } else if ((ch & 0xF0) == 0xE0) {
		// expect two more bytes
		mbwait = 2;
	    } else if ((ch & 0xF8) == 0xF0) {
		// expect three more bytes
		mbwait = 3;
	    }
	} else {
	    /* subsequent byte */
	    /*assert((ch & 0xC0) == 0x80);*/
	    if ((ch & 0xC0) == 0x80) {
		mbcount++;
		mbwait--;
	    } else {
		/* invalid sequence */
		mbcount = 0;
		mbwait = 0;
	    }
	    mbstr[mbcount] = ch;
	}
	if (mbwait == 0) {
	    *count = MultiByteToWideChar(CP_UTF8, 0, mbstr, mbcount + 1, wstr, 2);
	}
    } else if (encoding == S_ENC_SJIS) { /* combine S-JIS sequences */
	if (mbwait == 0) {
	    /* first or single byte */
	    mbcount = 0;
	    mbstr[mbcount] = ch;
	    if (is_sjis_lead_byte(ch)) {
		/* first byte */
		mbwait = 1;
	    }
	} else {
	    if ((ch >= 0x40) && (ch <= 0xfc)) {
		/* valid */
		mbcount++;
	    } else {
		/* invalid */
		mbcount = 0;
	    }
	    mbwait = 0; /* max. double byte sequences */
	    mbstr[mbcount] = ch;
	}
	if (mbwait == 0) {
	    *count = MultiByteToWideChar(932, 0, mbstr, mbcount + 1, wstr, 2);
	}
    } else {
	mbcount = 0;
	mbwait = 0;
	mbstr[0] = (char) ch;
	*count = MultiByteToWideChar(WinGetCodepage(encoding), 0, mbstr, mbcount + 1, wstr, 2);
    }
}


/* replacement stdio routines that use
 *  -  Text Window for stdin/stdout (wgnuplot)
 *  -  Unicode console APIs to handle encodings (console gnuplot)
 * WARNING: Do not write to stdout/stderr with functions not listed
 * in win/wtext.h
*/

#undef kbhit
#undef getche
#undef getch
#undef putch

#undef fgetc
#undef getchar
#undef getc
#undef fgets
#undef gets

#undef fputc
#undef putchar
#undef putc
#undef fputs
#undef puts

#undef fprintf
#undef printf
#undef vprintf
#undef vfprintf

#undef fwrite
#undef fread

#ifndef WGP_CONSOLE
# define TEXTMESSAGE TextMessage()
# define GETCH() TextGetChE(&textwin)
# define PUTS(s) TextPutS(&textwin, (char*) s)
# define PUTCH(c) TextPutCh(&textwin, (BYTE) c)
# define isterm(f) (f==stdin || f==stdout || f==stderr)
#else
# define TEXTMESSAGE
# define GETCH() ConsoleReadCh()
# define PUTS(s) ConsolePutS(s)
# define PUTCH(c) ConsolePutCh(c)
# define isterm(f) _isatty(_fileno(f))
#endif

int
MyPutCh(int ch)
{
    return PUTCH(ch);
}

#ifndef WGP_CONSOLE
int
MyKBHit()
{
    return TextKBHit(&textwin);
}

int
MyGetCh()
{
    return TextGetCh(&textwin);
}

int
MyGetChE()
{
    return TextGetChE(&textwin);
}
#endif


int
MyFGetC(FILE *file)
{
    if (isterm(file))
	return GETCH();
    return fgetc(file);
}

char *
MyGetS(char *str)
{
    MyFGetS(str, 80, stdin);
    if (strlen(str) > 0 && str[strlen(str) - 1] == '\n')
	str[strlen(str) - 1] = '\0';
    return str;
}

char *
MyFGetS(char *str, unsigned int size, FILE *file)
{
    if (isterm(file)) {
#ifndef WGP_CONSOLE
	char * p = TextGetS(&textwin, str, size);
	if (p != NULL)
	    return str;
	return NULL;
#else
	unsigned int i;
	int c;

	c = ConsoleGetch();
	if (c == EOF)
	    return NULL;

	for (i = 1; i < size - 1; i++) {
	    c = ConsoleGetch();
	    if (str[i] == EOF)
		break;
	     str[i] = c;
	    if (str[i] == '\n')
		break;
	}
	str[i] = NUL;
	return str;
#endif
    }
    return fgets(str,size,file);
}

int
MyFPutC(int ch, FILE *file)
{
    if (isterm(file)) {
	PUTCH(ch);
	TEXTMESSAGE;
	return ch;
    }
    return fputc(ch,file);
}

int
MyFPutS(const char *str, FILE *file)
{
    if (isterm(file)) {
	PUTS(str);
	TEXTMESSAGE;
	return (*str);
    }
    return fputs(str,file);
}

int
MyPutS(const char *str)
{
    PUTS(str);
    PUTCH('\n');
    TEXTMESSAGE;
    return 0;
}

int
MyFPrintF(FILE *file, const char *fmt, ...)
{
    int count;
    va_list args;

    va_start(args, fmt);
    if (isterm(file)) {
	char *buf;

	count = vsnprintf(NULL, 0, fmt, args) + 1;
	if (count == 0)
	    count = MAXPRINTF;
	va_end(args);
	va_start(args, fmt);
	buf = (char *) malloc(count * sizeof(char));
	count = vsnprintf(buf, count, fmt, args);
	PUTS(buf);
	free(buf);
    } else {
	count = vfprintf(file, fmt, args);
    }
    va_end(args);
    return count;
}

int
MyVFPrintF(FILE *file, const char *fmt, va_list args)
{
    int count;

    if (isterm(file)) {
	char *buf;
	va_list args_copied;

	va_copy(args_copied, args);
	count = vsnprintf(NULL, 0U, fmt, args) + 1;
	if (count == 0)
	    count = MAXPRINTF;
	va_end(args_copied);
	buf = (char *) malloc(count * sizeof(char));
	count = vsnprintf(buf, count, fmt, args);
	PUTS(buf);
	free(buf);
    } else {
	count = vfprintf(file, fmt, args);
    }
    return count;
}

int
MyPrintF(const char *fmt, ...)
{
    int count;
    char *buf;
    va_list args;

    va_start(args, fmt);
    count = vsnprintf(NULL, 0, fmt, args) + 1;
    if (count == 0)
	count = MAXPRINTF;
    va_end(args);
    va_start(args, fmt);
    buf = (char *) malloc(count * sizeof(char));
    count = vsnprintf(buf, count, fmt, args);
    PUTS(buf);
    free(buf);
    va_end(args);
    return count;
}

size_t
MyFWrite(const void *ptr, size_t size, size_t n, FILE *file)
{
    if (isterm(file)) {
	size_t i;
	for (i = 0; i < n; i++)
	    PUTCH(((BYTE *)ptr)[i]);
	TEXTMESSAGE;
	return n;
    }
    return fwrite(ptr, size, n, file);
}

size_t
MyFRead(void *ptr, size_t size, size_t n, FILE *file)
{
    if (isterm(file)) {
	size_t i;

	for (i = 0; i < n; i++)
	    ((BYTE *)ptr)[i] = GETCH();
	TEXTMESSAGE;
	return n;
    }
    return fread(ptr, size, n, file);
}


#ifdef USE_FAKEPIPES

static char pipe_type = NUL;
static char * pipe_filename = NULL;
static char * pipe_command = NULL;

FILE *
fake_popen(const char * command, const char * type)
{
    FILE * f = NULL;
    char tmppath[MAX_PATH];
    char tmpfile[MAX_PATH];
    DWORD ret;

    if (type == NULL)
	return NULL;

    pipe_type = NUL;
    if (pipe_filename != NULL)
	free(pipe_filename);

    /* Random temp file name in %TEMP% */
    ret = GetTempPathA(sizeof(tmppath), tmppath);
    if ((ret == 0) || (ret > sizeof(tmppath)))
	return NULL;
    ret = GetTempFileNameA(tmppath, "gpp", 0, tmpfile);
    if (ret == 0)
	return NULL;
    pipe_filename = gp_strdup(tmpfile);

    if (*type == 'r') {
	char * cmd;
	int rc;
	LPWSTR wcmd;
	pipe_type = *type;
	/* Execute command with redirection of stdout to temporary file. */
#ifndef __WATCOMC__
	cmd = (char *) malloc(strlen(command) + strlen(pipe_filename) + 5);
	sprintf(cmd, "%s > %s", command, pipe_filename);
	wcmd = UnicodeText(cmd, encoding);
	rc = _wsystem(wcmd);
	free(wcmd);
#else
	cmd = (char *) malloc(strlen(command) + strlen(pipe_filename) + 15);
	sprintf(cmd, "cmd /c %s > %s", command, pipe_filename);
	system(cmd);
#endif
	free(cmd);
	/* Now open temporary file. */
	/* system() returns 1 if the command could not be executed. */
	if (rc != 1) {
	    f = fopen(pipe_filename, "r");
	} else {
	    remove(pipe_filename);
	    free(pipe_filename);
	    pipe_filename = NULL;
	    errno = EINVAL;
	}
    } else if (*type == 'w') {
	pipe_type = *type;
	/* Write output to temporary file and handle the rest in fake_pclose. */
	if (type[1] == 'b')
	    int_error(NO_CARET, "Could not execute pipe '%s'. Writing to binary pipes is not supported.", command);
	else
	    f = fopen(pipe_filename, "w");
	pipe_command = gp_strdup(command);
    }

    return f;
}


int
fake_pclose(FILE *stream)
{
    int rc = 0;
    if (!stream)
	return ECHILD;

    /* Close temporary file */
    fclose(stream);

    /* Finally, execute command with redirected stdin. */
    if (pipe_type == 'w') {
	char * cmd;
	LPWSTR wcmd;

#ifndef __WATCOMC__
	cmd = (char *) gp_alloc(strlen(pipe_command) + strlen(pipe_filename) + 10, "fake_pclose");
	/* FIXME: this won't work for binary data. We need a proper `cat` replacement. */
	sprintf(cmd, "type %s | %s", pipe_filename, pipe_command);
	wcmd = UnicodeText(cmd, encoding);
	rc = _wsystem(wcmd);
	free(wcmd);
#else
	cmd = (char *) gp_alloc(strlen(pipe_command) + strlen(pipe_filename) + 20, "fake_pclose");
	sprintf(cmd, "cmd/c type %s | %s", pipe_filename, pipe_command);
	system(cmd);
#endif
	free(cmd);
    }

    /* Delete temp file again. */
    if (pipe_filename) {
	remove(pipe_filename);
	errno = 0;
	free(pipe_filename);
	pipe_filename = NULL;
    }

    if (pipe_command) {
	/* system() returns 255 if the command could not be executed.
	    The real popen would have returned an error already. */
	if (rc == 255)
	    int_error(NO_CARET, "Could not execute pipe '%s'.", pipe_command);
	free(pipe_command);
    }

    return rc;
}
#endif

#ifdef WGP_CONSOLE

DWORD WINAPI
stdin_pipe_reader(LPVOID param)
{
#if 0
    HANDLE h = (HANDLE)_get_osfhandle(fileno(stdin));
    char c;
    DWORD cRead;

    if (ReadFile(h, &c, 1, &cRead, NULL))
        return c;
#else
    unsigned char c;
    if (fread(&c, 1, 1, stdin) == 1)
	return (DWORD)c;
    return EOF;
#endif
}


int
ConsoleGetch()
{
    int fd = _fileno(stdin);
    HANDLE h;
    DWORD waitResult;

    if (!_isatty(fd))
	h = CreateThread(NULL, 0, stdin_pipe_reader, NULL, 0, NULL);
    else
	h = (HANDLE)_get_osfhandle(fd);

    do {
	waitResult = MsgWaitForMultipleObjects(1, &h, FALSE, INFINITE, QS_ALLINPUT);
	if (waitResult == WAIT_OBJECT_0) {
	    DWORD c;
	    if (_isatty(fd)) {
		c = ConsoleReadCh();
		if (c != NUL)
		    return c;
	    } else {
		GetExitCodeThread(h, &c);
		CloseHandle(h);
		return c;
	    }
	} else if (waitResult == WAIT_OBJECT_0+1) {
	    WinMessageLoop();
	    if (ctrlc_flag)
		return '\r';
	} else
		break;
    } while (1);

    return '\r';
}

#endif /* WGP_CONSOLE */


int
ConsoleReadCh()
{
    const int max_input = 8;
    static char console_input[8];
    static int first_input_char = 0;
    static int last_input_char = 0;
    INPUT_RECORD rec;
    DWORD recRead;
    HANDLE h;

    if (first_input_char != last_input_char) {
	int c = console_input[first_input_char];
	first_input_char++;
	first_input_char %= max_input;
	return c;
    }

    h = GetStdHandle(STD_INPUT_HANDLE);
    if (h == NULL)
	return NUL;

    ReadConsoleInputW(h, &rec, 1, &recRead);
    /* FIXME: We should handle rec.Event.KeyEvent.wRepeatCount > 1, too. */
    if (recRead == 1 && rec.EventType == KEY_EVENT && rec.Event.KeyEvent.bKeyDown &&
       (rec.Event.KeyEvent.wVirtualKeyCode < VK_SHIFT ||
	rec.Event.KeyEvent.wVirtualKeyCode > VK_MENU)) {
	    if (rec.Event.KeyEvent.uChar.UnicodeChar) {
		if ((rec.Event.KeyEvent.dwControlKeyState == SHIFT_PRESSED) && (rec.Event.KeyEvent.wVirtualKeyCode == VK_TAB)) {
		    return 034; /* remap Shift-Tab */
		} else {
		    int i, count;
		    char mbchar[8];
		    count = WideCharToMultiByte(WinGetCodepage(encoding), 0,
				&rec.Event.KeyEvent.uChar.UnicodeChar, 1,
				mbchar, sizeof(mbchar),
				NULL, NULL);
		    for (i = 1; i < count; i++) {
			console_input[last_input_char] = mbchar[i];
			last_input_char++;
			last_input_char %= max_input;
		    }
		    return mbchar[0];
		}
	    } else {
		switch (rec.Event.KeyEvent.wVirtualKeyCode) {
		case VK_UP: return 020;
		case VK_DOWN: return 016;
		case VK_LEFT: return 002;
		case VK_RIGHT: return 006;
		case VK_HOME: return 001;
		case VK_END: return 005;
		case VK_DELETE: return 0117;
		}
	    }
    }

    /* Error reading event or, key up or, one of the following event records:
	MOUSE_EVENT_RECORD, WINDOW_BUFFER_SIZE_RECORD, MENU_EVENT_RECORD, FOCUS_EVENT_RECORD */
    return NUL;
}

#ifdef WGP_CONSOLE

static int
ConsolePutS(const char *str)
{
    LPWSTR wstr = UnicodeText(str, encoding);
    // Use standard file IO instead of Console API
    // to enable word-wrapping on Windows 10 and
    // allow for redirection of stdout/stderr.
    //HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
    //WriteConsoleW(h, wstr, wcslen(wstr), NULL, NULL);
    fputws(wstr, stdout);
    free(wstr);
    return 0;
}


static int
ConsolePutCh(int ch)
{
    WCHAR w[4];
    int count;

    MultiByteAccumulate(ch, w, &count);
    if (count > 0) {
	// Use standard file IO instead of Console API
	// to enable word-wrapping on Windows 10.
	//HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
	//WriteConsoleW(h, w, count, NULL, NULL);
	w[count] = 0;
	fputws(w, stdout);
    }
    return ch;
}


/* This is called by the system to signal various events. 
   Note that it is executed in a separate thread.  */
BOOL WINAPI
ConsoleHandler(DWORD dwType)
{
    switch (dwType) {
    case CTRL_CLOSE_EVENT:
    case CTRL_LOGOFF_EVENT:
    case CTRL_SHUTDOWN_EVENT: {
	HANDLE h;
	INPUT_RECORD rec;
	DWORD written;

	// NOTE: returning from this handler terminates the application.
	// Instead, we signal the main thread to clean up and exit and
	// then idle by sleeping.
	terminate_flag = TRUE;
	// send ^D to main thread input queue
	h = GetStdHandle(STD_INPUT_HANDLE);
	ZeroMemory(&rec, sizeof(rec));
	rec.EventType = KEY_EVENT;
	rec.Event.KeyEvent.bKeyDown = TRUE;
	rec.Event.KeyEvent.wRepeatCount = 1;
	rec.Event.KeyEvent.uChar.AsciiChar = 004;
	WriteConsoleInput(h, &rec, 1, &written);
	// give the main thread time to exit
	Sleep(10000);
	return TRUE;
    }
    default:
	break;
    }
    return FALSE;
}
#endif


/* public interface to printer routines : Windows PRN emulation
 * (formerly in win.trm)
 */

#define MAX_PRT_LEN 256
static char win_prntmp[MAX_PRT_LEN+1];

FILE *
open_printer()
{
    char *temp;

    if ((temp = getenv("TEMP")) == NULL)
	*win_prntmp = '\0';
    else  {
	safe_strncpy(win_prntmp, temp, MAX_PRT_LEN);
	/* stop X's in path being converted by _mktemp */
	for (temp = win_prntmp; *temp != NUL; temp++)
	    *temp = tolower((unsigned char)*temp);
	if ((strlen(win_prntmp) > 0) && (win_prntmp[strlen(win_prntmp) - 1] != '\\'))
	    strcat(win_prntmp, "\\");
    }
    strncat(win_prntmp, "_gptmp", MAX_PRT_LEN - strlen(win_prntmp));
    strncat(win_prntmp, "XXXXXX", MAX_PRT_LEN - strlen(win_prntmp));
    _mktemp(win_prntmp);
    return fopen(win_prntmp, "wb");
}


void
close_printer(FILE *outfile)
{
    LPTSTR fname;
    HWND hwnd;
    TCHAR title[100];

#ifdef UNICODE
    fname = UnicodeText(win_prntmp, S_ENC_DEFAULT);
#else
    fname = win_prntmp;
#endif
    fclose(outfile);

#ifndef WGP_CONSOLE
    hwnd = textwin.hWndParent;
#else
    hwnd = GetDesktopWindow();
#endif
    if (term->name != NULL)
	wsprintf(title, TEXT("gnuplot graph (%hs)"), term->name);
    else
	_tcscpy(title, TEXT("gnuplot graph"));
    DumpPrinter(hwnd, title, fname);

#ifdef UNICODE
    free(fname);
#endif
}


void
screen_dump()
{
    if (term == NULL) {
	int_error(c_token, "");
    }
    if (strcmp(term->name, "windows") == 0)
	GraphPrint(graphwin);
#ifdef WXWIDGETS
    else if (strcmp(term->name, "wxt") == 0)
	wxt_screen_dump();
#endif
#ifdef QTTERM
    //else if (strcmp(term->name, "qt") == 0)
#endif
    else
	int_error(c_token, "screendump not supported for terminal `%s`", term->name);
}


void
win_raise_terminal_window(int id)
{
    LPGW lpgw = listgraphs;
    while ((lpgw != NULL) && (lpgw->Id != id))
	lpgw = lpgw->next;
    if (lpgw != NULL) {
	if (IsIconic(lpgw->hWndGraph))
	    ShowWindow(lpgw->hWndGraph, SW_SHOWNORMAL);
	BringWindowToTop(lpgw->hWndGraph);
    }
}


void
win_raise_terminal_group(void)
{
    LPGW lpgw = listgraphs;
    while (lpgw != NULL) {
	if (IsIconic(lpgw->hWndGraph))
	    ShowWindow(lpgw->hWndGraph, SW_SHOWNORMAL);
	BringWindowToTop(lpgw->hWndGraph);
	lpgw = lpgw->next;
    }
}


void
win_lower_terminal_window(int id)
{
    LPGW lpgw = listgraphs;
    while ((lpgw != NULL) && (lpgw->Id != id))
	lpgw = lpgw->next;
    if (lpgw != NULL)
	SetWindowPos(lpgw->hWndGraph, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
}


void
win_lower_terminal_group(void)
{
    LPGW lpgw = listgraphs;
    while (lpgw != NULL) {
	SetWindowPos(lpgw->hWndGraph, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
	lpgw = lpgw->next;
    }
}


/* returns true if there are any graph windows open (win terminal) */
static TBOOLEAN
WinWindowOpened(void)
{
    LPGW lpgw;

    lpgw = listgraphs;
    while (lpgw != NULL) {
	if (GraphHasWindow(lpgw))
	    return TRUE;
	lpgw = lpgw->next;
    }
    return FALSE;
}


/* returns true if there are any graph windows open (wxt/caca/win terminals) */
/* Note: This routine is used to handle "persist". Do not test for qt windows here
         since they run in a separate process */
TBOOLEAN
WinAnyWindowOpen(void)
{
    TBOOLEAN window_opened = WinWindowOpened();
#ifdef WXWIDGETS
    window_opened |= wxt_window_opened();
#endif
#ifdef HAVE_LIBCACA
    window_opened |= CACA_window_opened();
#endif
    return window_opened;
}


#ifndef WGP_CONSOLE
void
WinPersistTextClose(void)
{
    if (!WinAnyWindowOpen() &&
	(textwin.hWndParent != NULL) && !IsWindowVisible(textwin.hWndParent))
	PostMessage(textwin.hWndParent, WM_CLOSE, 0, 0);
}
#endif


void
WinMessageLoop(void)
{
    MSG msg;

    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
	/* HBB 19990505: Petzold says we should check this: */
	if (msg.message == WM_QUIT)
	    return;
	TranslateMessage(&msg);
	DispatchMessage(&msg);
    }
}


void
WinRaiseConsole(void)
{
    HWND console = NULL;
#ifndef WGP_CONSOLE
    console = textwin.hWndParent;
    if (pausewin.bPause && IsWindow(pausewin.hWndPause))
	console = pausewin.hWndPause;
#else
    console = GetConsoleWindow();
#endif
    if (console != NULL) {
	if (IsIconic(console))
	    ShowWindow(console, SW_SHOWNORMAL);
	BringWindowToTop(console);
    }
}


/* WinGetCodepage:
    Map gnuplot's internal character encoding to Windows codepage codes.
*/
UINT
WinGetCodepage(enum set_encoding_id encoding)
{
    UINT codepage;

    /* For a list of code page identifiers see
	http://msdn.microsoft.com/en-us/library/dd317756%28v=vs.85%29.aspx
    */
    switch (encoding) {
	case S_ENC_DEFAULT:    codepage = CP_ACP; break;
	case S_ENC_ISO8859_1:  codepage = 28591; break;
	case S_ENC_ISO8859_2:  codepage = 28592; break;
	case S_ENC_ISO8859_9:  codepage = 28599; break;
	case S_ENC_ISO8859_15: codepage = 28605; break;
	case S_ENC_CP437:      codepage =   437; break;
	case S_ENC_CP850:      codepage =   850; break;
	case S_ENC_CP852:      codepage =   852; break;
	case S_ENC_CP950:      codepage =   950; break;
	case S_ENC_CP1250:     codepage =  1250; break;
	case S_ENC_CP1251:     codepage =  1251; break;
	case S_ENC_CP1252:     codepage =  1252; break;
	case S_ENC_CP1254:     codepage =  1254; break;
	case S_ENC_KOI8_R:     codepage = 20866; break;
	case S_ENC_KOI8_U:     codepage = 21866; break;
	case S_ENC_SJIS:       codepage =   932; break;
	case S_ENC_UTF8:       codepage = CP_UTF8; break;
	default: {
	    /* unknown encoding, fall back to default "ANSI" codepage */
	    codepage = CP_ACP;
	    FPRINTF((stderr, "unknown encoding: %i\n", encoding));
	}
    }
    return codepage;
}


enum set_encoding_id
WinGetEncoding(UINT cp)
{
    enum set_encoding_id encoding;

    /* The code below is the inverse to the code found in UnicodeText().
       For a list of code page identifiers see
       http://msdn.microsoft.com/en-us/library/dd317756%28v=vs.85%29.aspx
    */
    switch (cp) {
    case 437:   encoding = S_ENC_CP437; break;
    case 850:   encoding = S_ENC_CP850; break;
    case 852:   encoding = S_ENC_CP852; break;
    case 932:   encoding = S_ENC_SJIS; break;
    case 950:   encoding = S_ENC_CP950; break;
    case 1250:  encoding = S_ENC_CP1250; break;
    case 1251:  encoding = S_ENC_CP1251; break;
    case 1252:  encoding = S_ENC_CP1252; break;
    case 1254:  encoding = S_ENC_CP1254; break;
    case 20866: encoding = S_ENC_KOI8_R; break;
    case 21866: encoding = S_ENC_KOI8_U; break;
    case 28591: encoding = S_ENC_ISO8859_1; break;
    case 28592: encoding = S_ENC_ISO8859_2; break;
    case 28599: encoding = S_ENC_ISO8859_9; break;
    case 28605: encoding = S_ENC_ISO8859_15; break;
    case 65001: encoding = S_ENC_UTF8; break;
    default:
	encoding = S_ENC_DEFAULT;
    }
    return encoding;
}


LPWSTR
UnicodeText(LPCSTR str, enum set_encoding_id encoding)
{
    LPWSTR strw = NULL;
    UINT codepage = WinGetCodepage(encoding);
    int length;

    /* sanity check */
    if (str == NULL)
	return NULL;

    /* get length of converted string */
    length = MultiByteToWideChar(codepage, 0, str, -1, NULL, 0);
    strw = (LPWSTR) malloc(sizeof(WCHAR) * length);

    /* convert string to UTF-16 */
    length = MultiByteToWideChar(codepage, 0, str, -1, strw, length);

    return strw;
}


LPSTR
AnsiText(LPCWSTR strw,  enum set_encoding_id encoding)
{
    LPSTR str = NULL;
    UINT codepage = WinGetCodepage(encoding);
    int length;

    /* get length of converted string */
    length = WideCharToMultiByte(codepage, 0, strw, -1, NULL, 0, NULL, 0);
    str = (LPSTR) malloc(sizeof(char) * length);

    /* convert string to "Ansi" */
    length = WideCharToMultiByte(codepage, 0, strw, -1, str, length, NULL, 0);

    return str;
}


FILE *
win_fopen(const char *filename, const char *mode)
{
    FILE * file;
    LPWSTR wfilename = UnicodeText(filename, encoding);
    LPWSTR wmode = UnicodeText(mode, encoding);
    file = _wfopen(wfilename, wmode);
    free(wfilename);
    free(wmode);
    return file;
}


#ifndef USE_FAKEPIPES
FILE *
win_popen(const char *filename, const char *mode)
{
    FILE * file;
    LPWSTR wfilename = UnicodeText(filename, encoding);
    LPWSTR wmode = UnicodeText(mode, encoding);
    file = _wpopen(wfilename, wmode);
    free(wfilename);
    free(wmode);
    return file;
}
#endif


UINT
GetDPI()
{
    HDC hdc_screen = GetDC(NULL);
    if (hdc_screen) {
	UINT dpi = GetDeviceCaps(hdc_screen, LOGPIXELSX);
	ReleaseDC(NULL, hdc_screen);
	return dpi;
    } else {
	return 96;
    }
}