OSDN Git Service

Implemented SetConsoleIcon() fallback method.
[mutilities/MUtilities.git] / src / Terminal_Win32.cpp
index 298f7d6..e0b9ff8 100644 (file)
@@ -1,6 +1,6 @@
 ///////////////////////////////////////////////////////////////////////////////
-// LameXP - Audio Encoder Front-End
-// Copyright (C) 2004-2014 LoRd_MuldeR <MuldeR2@GMX.de>
+// MuldeR's Utilities for Qt
+// Copyright (C) 2004-2016 LoRd_MuldeR <MuldeR2@GMX.de>
 //
 // This program is free software; you can redistribute it and/or modify
 // it under the terms of the GNU General Public License as published by
 // http://www.gnu.org/licenses/gpl-2.0.txt
 ///////////////////////////////////////////////////////////////////////////////
 
-#include <MUtils/Terminal.h>
+//Windows includes
+#define NOMINMAX
+#define WIN32_LEAN_AND_MEAN 1
+#include <Windows.h>
 
 //Internal
+#include <MUtils/Terminal.h>
 #include <MUtils/Global.h>
 #include <MUtils/OSSupport.h>
+#include "Utils_Win32.h"
 #include "CriticalSection_Win32.h"
 
-//Windows includes
-#define NOMINMAX
-#define WIN32_LEAN_AND_MEAN 1
-#include <Windows.h>
-
 //Qt
 #include <QFile>
 #include <QStringList>
+#include <QIcon>
 
 //CRT
 #include <iostream>
 #include <fstream>
+#include <ctime>
+#include <stdarg.h>
 #include <io.h>
 #include <fcntl.h>
-#include <ctime>
 
-//Lock
+#ifdef _MSC_VER
+#define stricmp(X,Y) _stricmp((X),(Y))
+#endif
+
+#define VALID_HANLDE(X) (((X) != NULL) && ((X) != INVALID_HANDLE_VALUE))
+
+///////////////////////////////////////////////////////////////////////////////
+// TERMINAL VARIABLES
+///////////////////////////////////////////////////////////////////////////////
+
+//Critical section
 static MUtils::Internal::CriticalSection g_terminal_lock;
 
+//Is terminal attached?
+static volatile bool g_terminal_attached = false;
+
+//Terminal output buffer
+static const size_t BUFF_SIZE = 8192;
+static char g_conOutBuff[BUFF_SIZE] = { '\0' };
+
+//Buffer objects
+static QScopedPointer<std::filebuf> g_fileBuf_stdout;
+static QScopedPointer<std::filebuf> g_fileBuf_stderr;
+
+//The log file
+static QScopedPointer<QFile> g_terminal_log_file;
+
+//Terminal icon
+static HICON g_terminal_icon = NULL;
+
 ///////////////////////////////////////////////////////////////////////////////
 // HELPER FUNCTIONS
 ///////////////////////////////////////////////////////////////////////////////
 
-static void make_timestamp(char *timestamp, const size_t &buffsize)
+static inline void make_timestamp(char *timestamp, const size_t &buffsize)
 {
        time_t rawtime;
        struct tm timeinfo;
@@ -58,7 +87,7 @@ static void make_timestamp(char *timestamp, const size_t &buffsize)
        time(&rawtime);
        if(localtime_s(&timeinfo, &rawtime) == 0)
        {
-               strftime(timestamp, 32, "%H:%M:%S", &timeinfo);
+               strftime(timestamp, buffsize, "%H:%M:%S", &timeinfo);
        }
        else
        {
@@ -66,46 +95,101 @@ static void make_timestamp(char *timestamp, const size_t &buffsize)
        }
 }
 
-static const char *clean_str(char *str)
+static inline bool null_or_whitespace(const char *const str)
 {
-       //Clean
-       char *ptr = &str[0];
-       while((*ptr) && isspace(*ptr))
+       if (str)
        {
-               *(ptr++) = 0x20;
+               size_t pos = 0;
+               while (str[pos])
+               {
+                       if (!(isspace(str[pos]) || iscntrl(str[pos])))
+                       {
+                               return false;
+                       }
+                       pos++;
+               }
        }
+       return true;
+}
 
-       //Trim left
-       while((*str) && isspace(*str))
-       {
-               str++;
-       }
+static inline size_t clean_string(char *const str)
+{
+       bool space_flag = true;
+       size_t src = 0, out = 0;
 
-       //Trim right
-       size_t pos = strlen(str);
-       while(pos > 0)
+       while (str[src])
        {
-               if(isspace(str[--pos]))
+               if (isspace(str[src]) || iscntrl(str[src])) /*replace any space-sequence with a single space character*/
+               {
+                       src++;
+                       if (!space_flag)
+                       {
+                               space_flag = true;
+                               str[out++] = 0x20;
+                       }
+               }
+               else /*otherwise we'll just copy over the current character*/
                {
-                       str[pos] = '\0';
-                       continue;
+                       if (src != out)
+                       {
+                               str[out] = str[src];
+                       }
+                       space_flag = false;
+                       out++; src++;
                }
-               break;
        }
 
-       return str;
+       if (space_flag && (out > 0)) /*trim trailing space, if any*/
+       {
+               out--;
+       }
+
+       str[out] = NULL;
+       return out;
+}
+
+static inline void set_hicon(HICON *const ptr, const HICON val)
+{
+       if (*ptr)
+       {
+               DestroyIcon(*ptr);
+       }
+       *ptr = val;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // TERMINAL SETUP
 ///////////////////////////////////////////////////////////////////////////////
 
-static bool g_terminal_attached = false;
-static QScopedPointer<std::filebuf> g_filebufStdOut;
-static QScopedPointer<std::filebuf> g_filebufStdErr;
-static QScopedPointer<QFile> g_log_file;
+static inline std::filebuf *terminal_connect(FILE *const fs, std::ostream &os)
+{
+       std::filebuf *result = NULL;
+       FILE *temp;
+       if (freopen_s(&temp, "CONOUT$", "wb", fs) == 0)
+       {
+               os.rdbuf(result = new std::filebuf(temp));
+       }
+       return result;
+}
+
+static void terminal_shutdown(void)
+{
+       MUtils::Internal::CSLocker lock(g_terminal_lock);
+
+       if (g_terminal_attached)
+       {
+               g_fileBuf_stdout.reset();
+               g_fileBuf_stderr.reset();
+               FILE *temp[2];
+               if(stdout) freopen_s(&temp[0], "NUL", "wb", stdout);
+               if(stderr) freopen_s(&temp[1], "NUL", "wb", stderr);
+               FreeConsole();
+               set_hicon(&g_terminal_icon, NULL);
+               g_terminal_attached = false;
+       }
+}
 
-void MUtils::Terminal::setup(const QStringList &argv, const bool forceEnabled)
+void MUtils::Terminal::setup(int &argc, char **argv, const char* const appName, const bool forceEnabled)
 {
        MUtils::Internal::CSLocker lock(g_terminal_lock);
        bool enableConsole = (MUTILS_DEBUG) || forceEnabled;
@@ -117,11 +201,11 @@ void MUtils::Terminal::setup(const QStringList &argv, const bool forceEnabled)
                {
                        if(logfile && (logfile_len > 0))
                        {
-                               g_log_file.reset(new QFile(MUTILS_QSTR(logfile)));
-                               if(g_log_file->open(QIODevice::WriteOnly))
+                               g_terminal_log_file.reset(new QFile(MUTILS_QSTR(logfile)));
+                               if(g_terminal_log_file->open(QIODevice::WriteOnly))
                                {
                                        static const char MARKER[3] = { char(0xEF), char(0xBB), char(0xBF) };
-                                       g_log_file->write(MARKER, 3);
+                                       g_terminal_log_file->write(MARKER, 3);
                                }
                                free(logfile);
                        }
@@ -130,13 +214,13 @@ void MUtils::Terminal::setup(const QStringList &argv, const bool forceEnabled)
 
        if(!MUTILS_DEBUG)
        {
-               for(int i = 0; i < argv.count(); i++)
+               for(int i = 0; i < argc; i++)
                {
-                       if(!argv.at(i).compare("--console", Qt::CaseInsensitive))
+                       if(!stricmp(argv[i], "--console"))
                        {
                                enableConsole = true;
                        }
-                       else if(!argv.at(i).compare("--no-console", Qt::CaseInsensitive))
+                       else if(!stricmp(argv[i], "--no-console"))
                        {
                                enableConsole = false;
                        }
@@ -149,47 +233,36 @@ void MUtils::Terminal::setup(const QStringList &argv, const bool forceEnabled)
                {
                        if(AllocConsole() != FALSE)
                        {
-                               SetConsoleCtrlHandler(NULL, TRUE);
-                               SetConsoleTitle(L"LameXP - Audio Encoder Front-End | Debug Console");
                                SetConsoleOutputCP(CP_UTF8);
+                               SetConsoleCtrlHandler(NULL, TRUE);
+                               if(appName && appName[0])
+                               {
+                                       char title[128];
+                                       _snprintf_s(title, 128, _TRUNCATE, "%s | Debug Console", appName);
+                                       SetConsoleTitleA(title);
+                               }
                                g_terminal_attached = true;
                        }
                }
-               
+
                if(g_terminal_attached)
                {
-                       //-------------------------------------------------------------------
-                       //See: http://support.microsoft.com/default.aspx?scid=kb;en-us;105305
-                       //-------------------------------------------------------------------
-                       const int flags = _O_WRONLY | _O_U8TEXT;
-                       const int hCrtStdOut = _open_osfhandle((intptr_t) GetStdHandle(STD_OUTPUT_HANDLE), flags);
-                       const int hCrtStdErr = _open_osfhandle((intptr_t) GetStdHandle(STD_ERROR_HANDLE ), flags);
-                       FILE *const hfStdOut = (hCrtStdOut >= 0) ? _fdopen(hCrtStdOut, "wb") : NULL;
-                       FILE *const hfStdErr = (hCrtStdErr >= 0) ? _fdopen(hCrtStdErr, "wb") : NULL;
-                       if(hfStdOut)
-                       {
-                               *stdout = *hfStdOut;
-                               g_filebufStdOut.reset(new std::filebuf(hfStdOut));
-                               std::cout.rdbuf(g_filebufStdOut.data());
-                       }
-                       if(hfStdErr)
-                       {
-                               *stderr = *hfStdErr;
-                               g_filebufStdErr.reset(new std::filebuf(hfStdErr));
-                               std::cerr.rdbuf(g_filebufStdErr.data());
-                       }
-               }
+                       g_fileBuf_stdout.reset(terminal_connect(stdout, std::cout));
+                       g_fileBuf_stderr.reset(terminal_connect(stderr, std::cerr));
 
-               HWND hwndConsole = GetConsoleWindow();
-               if((hwndConsole != NULL) && (hwndConsole != INVALID_HANDLE_VALUE))
-               {
-                       HMENU hMenu = GetSystemMenu(hwndConsole, 0);
-                       EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
-                       RemoveMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
+                       atexit(terminal_shutdown);
 
-                       SetWindowPos (hwndConsole, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_FRAMECHANGED);
-                       SetWindowLong(hwndConsole, GWL_STYLE, GetWindowLong(hwndConsole, GWL_STYLE) & (~WS_MAXIMIZEBOX) & (~WS_MINIMIZEBOX));
-                       SetWindowPos (hwndConsole, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_FRAMECHANGED);
+                       const HWND hwndConsole = GetConsoleWindow();
+                       if((hwndConsole != NULL) && (hwndConsole != INVALID_HANDLE_VALUE))
+                       {
+                               HMENU hMenu = GetSystemMenu(hwndConsole, 0);
+                               EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
+                               RemoveMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
+
+                               SetWindowPos (hwndConsole, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_FRAMECHANGED);
+                               SetWindowLong(hwndConsole, GWL_STYLE, GetWindowLong(hwndConsole, GWL_STYLE) & (~WS_MAXIMIZEBOX) & (~WS_MINIMIZEBOX));
+                               SetWindowPos (hwndConsole, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_FRAMECHANGED);
+                       }
                }
        }
 }
@@ -204,12 +277,15 @@ static const WORD COLOR_YELLOW = FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_
 static const WORD COLOR_WHITE  = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
 static const WORD COLOR_DEFAULT= FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
 
-static void set_terminal_color(FILE* file, const WORD &attributes)
+static void set_terminal_color(FILE *const fp, const WORD &attributes)
 {
-       const HANDLE hConsole = (HANDLE)(_get_osfhandle(_fileno(file)));
-       if((hConsole != NULL) && (hConsole != INVALID_HANDLE_VALUE))
+       if(_isatty(_fileno(fp)))
        {
-               SetConsoleTextAttribute(hConsole, attributes);
+               const HANDLE hConsole = (HANDLE)(_get_osfhandle(_fileno(fp)));
+               if (VALID_HANLDE(hConsole))
+               {
+                       SetConsoleTextAttribute(hConsole, attributes);
+               }
        }
 }
 
@@ -220,89 +296,98 @@ static void set_terminal_color(FILE* file, const WORD &attributes)
 static const char *const FORMAT = "[%c][%s] %s\r\n";
 static const char *const GURU_MEDITATION = "\n\nGURU MEDITATION !!!\n\n";
 
-static void write_logfile_helper(QFile *const file, const int &type, const char *const message)
+static void write_to_logfile(QFile *const file, const int &type, const char *const message)
 {
-       static char input[1024], output[1024], timestamp[32];
+       int len = -1;
+
+       if (null_or_whitespace(message))
+       {
+               return; /*don't write empty message to log file*/
+       }
+
+       static char timestamp[32];
        make_timestamp(timestamp, 32);
-       strncpy_s(input, 1024, message, _TRUNCATE);
-       const char *const temp = clean_str(input);
 
        switch(type)
        {
        case QtCriticalMsg:
        case QtFatalMsg:
-               _snprintf_s(output, 1024, FORMAT, 'C', timestamp, temp);
+               len = _snprintf_s(g_conOutBuff, BUFF_SIZE, _TRUNCATE, FORMAT, 'C', timestamp, message);
                break;
        case QtWarningMsg:
-               _snprintf_s(output, 1024, FORMAT, 'W', timestamp, temp);
+               len = _snprintf_s(g_conOutBuff, BUFF_SIZE, _TRUNCATE, FORMAT, 'W', timestamp, message);
                break;
        default:
-               _snprintf_s(output, 1024, FORMAT, 'I', timestamp, temp);
+               len = _snprintf_s(g_conOutBuff, BUFF_SIZE, _TRUNCATE, FORMAT, 'I', timestamp, message);
                break;
        }
 
-       file->write(output);
-       file->flush();
+       if (len > 0)
+       {
+               if (clean_string(g_conOutBuff) > 0)
+               {
+                       file->write(g_conOutBuff);
+                       file->flush();
+               }
+       }
 }
 
-static void write_debugger_helper(const int &type, const char *const message)
+static void write_to_debugger(const int &type, const char *const message)
 {
-       static char input[1024], output[1024], timestamp[32];
+       int len = -1;
+
+       if (null_or_whitespace(message))
+       {
+               return; /*don't send empty message to debugger*/
+       }
+
+       static char timestamp[32];
        make_timestamp(timestamp, 32);
-       strncpy_s(input, 1024, message, _TRUNCATE);
-       const char *const temp = clean_str(input);
 
        switch(type)
        {
        case QtCriticalMsg:
        case QtFatalMsg:
-               _snprintf_s(output, 1024, FORMAT, 'C', timestamp, temp);
+               len = _snprintf_s(g_conOutBuff, BUFF_SIZE, _TRUNCATE, FORMAT, 'C', timestamp, message);
                break;
        case QtWarningMsg:
-               _snprintf_s(output, 1024, FORMAT, 'W', timestamp, temp);
+               len = _snprintf_s(g_conOutBuff, BUFF_SIZE, _TRUNCATE, FORMAT, 'W', timestamp, message);
                break;
        default:
-               _snprintf_s(output, 1024, FORMAT, 'I', timestamp, temp);
+               len = _snprintf_s(g_conOutBuff, BUFF_SIZE, _TRUNCATE, FORMAT, 'I', timestamp, message);
                break;
        }
 
-       OutputDebugStringA(output);
+       if (len > 0)
+       {
+               if (clean_string(g_conOutBuff) > 0)
+               {
+                       OutputDebugStringA(g_conOutBuff);
+               }
+       }
 }
 
-static void write_terminal_helper(const int &type, const char *const message)
+static void write_to_terminal(const int &type, const char *const message)
 {
-       if(_isatty(_fileno(stderr)))
+       switch(type)
        {
-               UINT oldOutputCP = GetConsoleOutputCP();
-               if(oldOutputCP != CP_UTF8) SetConsoleOutputCP(CP_UTF8);
-
-               switch(type)
-               {
-               case QtCriticalMsg:
-               case QtFatalMsg:
-                       set_terminal_color(stderr, COLOR_RED);
-                       fprintf(stderr, GURU_MEDITATION);
-                       fprintf(stderr, "%s\n", message);
-                       fflush(stderr);
-                       break;
-               case QtWarningMsg:
-                       set_terminal_color(stderr, COLOR_YELLOW);
-                       fprintf(stderr, "%s\n", message);
-                       fflush(stderr);
-                       break;
-               default:
-                       set_terminal_color(stderr, COLOR_WHITE);
-                       fprintf(stderr, "%s\n", message);
-                       fflush(stderr);
-                       break;
-               }
-       
-               set_terminal_color(stderr, COLOR_DEFAULT);
-               if(oldOutputCP != CP_UTF8)
-               {
-                       SetConsoleOutputCP(oldOutputCP);
-               }
+       case QtCriticalMsg:
+       case QtFatalMsg:
+               set_terminal_color(stderr, COLOR_RED);
+               fprintf(stderr, GURU_MEDITATION);
+               fprintf(stderr, "%s\n", message);
+               break;
+       case QtWarningMsg:
+               set_terminal_color(stderr, COLOR_YELLOW);
+               fprintf(stderr, "%s\n", message);
+               break;
+       default:
+               set_terminal_color(stderr, COLOR_WHITE);
+               fprintf(stderr, "%s\n", message);
+               break;
        }
+
+       fflush(stderr);
 }
 
 void MUtils::Terminal::write(const int &type, const char *const message)
@@ -311,15 +396,60 @@ void MUtils::Terminal::write(const int &type, const char *const message)
 
        if(g_terminal_attached)
        {
-               write_terminal_helper(type, message);
+               write_to_terminal(type, message);
        }
        else
        {
-               write_debugger_helper(type, message);
+               write_to_debugger(type, message);
        }
 
-       if(!g_log_file.isNull())
+       if(!g_terminal_log_file.isNull())
        {
-               write_logfile_helper(g_log_file.data(), type, message);
+               write_to_logfile(g_terminal_log_file.data(), type, message);
+       }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TERMINAL ICON
+///////////////////////////////////////////////////////////////////////////////
+
+void MUtils::Terminal::set_icon(const QIcon &icon)
+{
+       MUtils::Internal::CSLocker lock(g_terminal_lock);
+
+       if(g_terminal_attached && (!(icon.isNull() || MUtils::OS::running_on_wine())))
+       {
+               if(const HICON hIcon = (HICON) MUtils::Win32Utils::qicon_to_hicon(icon, 16, 16))
+               {
+                       typedef BOOL(__stdcall *SetConsoleIconFun)(HICON);
+                       bool success = false;
+                       if (const SetConsoleIconFun pSetConsoleIconFun = MUtils::Win32Utils::resolve<SetConsoleIconFun>(QLatin1String("kernel32"), QLatin1String("SetConsoleIcon")))
+                       {
+                               const DWORD before = GetLastError();
+                               qWarning("[Before: 0x%08X]", before);
+                               if (pSetConsoleIconFun(hIcon))
+                               {
+                                       success = true;
+                               }
+                               else
+                               {
+                                       const DWORD error = GetLastError();
+                                       qWarning("SetConsoleIcon() has failed! [Error: 0x%08X]", error);
+                               }
+                       }
+                       if (!success)
+                       {
+                               const HWND hwndConsole = GetConsoleWindow();
+                               if ((hwndConsole != NULL) && (hwndConsole != INVALID_HANDLE_VALUE))
+                               {
+                                       SendMessage(hwndConsole, WM_SETICON, ICON_SMALL, LPARAM(hIcon));
+                                       success = true;
+                               }
+                       }
+                       if (success)
+                       {
+                               set_hicon(&g_terminal_icon, hIcon);
+                       }
+               }
        }
 }