/* Miranda NG: the free IM client for Microsoft* Windows* Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org), Copyright (c) 2000-08 Miranda ICQ/IM project, all portions of this codebase are copyrighted to the people listed in contributors.txt. 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 the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "stdafx.h" #define MS_CONSOLE_SHOW_HIDE "Console/Show/Hide" #define DEFAULT_WRAPLEN 90 #define MIN_WRAPLEN 25 #define MAX_WRAPLEN 255 #define MIN_LIMIT 1000 #define MAX_LIMIT 1000000 #define IMG_EMPTY 0 #define IMG_ARROW 1 #define IMG_IN 2 #define IMG_OUT 3 #define IMG_INFO 4 #define LOGICONX_SIZE 10 #define LOGICONY_SIZE 10 #define ICON_FIRST 3 #define ICON_NOSCROLL 1 #define ICON_PAUSED 2 #define ICON_SCROLL 3 #define ICON_STARTED 4 #define HM_DUMP (WM_USER+10) #define HM_ADD (WM_USER+11) #define HM_REMOVE (WM_USER+12) #define HM_SETFONT (WM_USER+13) #define HM_SETCOLOR (WM_USER+14) #define HM_PAUSEALL (WM_USER+15) #define HM_RESTART (WM_USER+16) typedef struct { const char *pszHead; const char *pszMsg; } LOGMSG; typedef struct { char szModule[128]; wchar_t szMsg[1]; } DUMPMSG; typedef struct { HWND hwnd; HWND hList; char *Module; int Scroll; int Paused; int newline; } LOGWIN; static SortedList lModules = {}; static LOGWIN *pActive = nullptr; static int tabCount = 0; static RECT rcTabs = { 0 }; static HWND hTabs = nullptr; static HWND hwndConsole = nullptr; static HIMAGELIST gImg = nullptr; static HFONT hfLogFont = nullptr; static COLORREF colLogFont; static COLORREF colBackground; static int gIcons = 0; static int gVisible = 0; static int gSingleMode = 0; static int gLimit = 0; static int gSeparator = 0; static uint32_t gWrapLen = DEFAULT_WRAPLEN; static uint32_t OutMsgs = 0; static uint32_t InMsgs = 0; static HICON hIcons[15] = {}; static HGENMENU hMenu = nullptr; static void LoadSettings(); static void ShowConsole(int show); static INT_PTR ShowHideConsole(WPARAM wParam, LPARAM lParam); static int Openfile(wchar_t *outputFile, int selection); //////////////////////////////////////////////////////////////////////////////// static HANDLE hTTBButt = nullptr; static int OnTTBLoaded(WPARAM, LPARAM) { int state = IsWindowVisible(hwndConsole); TTBButton ttb = {}; ttb.hIconHandleUp = LoadIcon(IDI_BTN_UP); ttb.hIconHandleDn = LoadIcon(IDI_BTN_DN); ttb.dwFlags = (state ? TTBBF_PUSHED : 0) | TTBBF_VISIBLE | TTBBF_SHOWTOOLTIP; ttb.pszService = MS_CONSOLE_SHOW_HIDE; ttb.name = LPGEN("Show/Hide Console"); ttb.pszTooltipDn = LPGEN("Hide Console"); ttb.pszTooltipUp = LPGEN("Show Console"); hTTBButt = g_plugin.addTTB(&ttb); return 0; } //////////////////////////////////////////////////////////////////////////////// void ScrollDown(LOGWIN *dat) { if (dat->Scroll) ListView_EnsureVisible(dat->hList, ListView_GetItemCount(dat->hList) - 1, FALSE); } //////////////////////////////////////////////////////////////////////////////// static void ShowConsole(int show) { HWND hwnd = nullptr; if (!hwndConsole || !pActive) return; gVisible = show; if (show) { hwnd = GetForegroundWindow(); if (InMsgs == OutMsgs) ScrollDown(pActive); } ShowWindow(hwndConsole, show ? SW_SHOW : SW_HIDE); g_plugin.setByte("Show", (uint8_t)(show ? 1 : 0)); if (hwnd) SetForegroundWindow(hwnd); if (show) RedrawWindow(pActive->hList, nullptr, nullptr, RDW_INVALIDATE | RDW_FRAME | RDW_UPDATENOW | RDW_ERASE); if (hMenu) Menu_ModifyItem(hMenu, show ? LPGENW("Hide Console") : LPGENW("Show Console")); if (hTTBButt) CallService(MS_TTB_SETBUTTONSTATE, (WPARAM)hTTBButt, show ? TTBST_PUSHED : 0); } //////////////////////////////////////////////////////////////////////////////// static INT_PTR ShowHideConsole(WPARAM, LPARAM) { if (hwndConsole) ShowConsole(!IsWindowVisible(hwndConsole)); return 0; } //////////////////////////////////////////////////////////////////////////////// int LogResize(HWND hwnd, LPARAM, UTILRESIZECONTROL *urc) { switch (urc->wId) { case IDC_LIST: ListView_SetColumnWidth(GetDlgItem(hwnd, IDC_LIST), 0, urc->dlgNewSize.cx - 25); return RD_ANCHORX_WIDTH | RD_ANCHORY_HEIGHT; case IDC_STARTALL: case IDC_PAUSEALL: case IDC_CLOSE: return RD_ANCHORX_RIGHT | RD_ANCHORY_TOP; default: return RD_ANCHORX_LEFT | RD_ANCHORY_TOP; } } //////////////////////////////////////////////////////////////////////////////// typedef struct { UINT control; UINT icon; int type; wchar_t *tooltip; } controlinfo; static controlinfo ctrls[] = { // IDC_SCROLL & IDC_PAUSE must be first { IDC_SCROLL, IDI_SCROLL, BUTTONSETASFLATBTN, LPGENW("Scrolling (Ctrl+Q)") }, { IDC_PAUSE, IDI_STARTED, BUTTONSETASFLATBTN, LPGENW("Pause logging (Ctrl+P)") }, { IDC_SAVE, IDI_SAVE, BUTTONSETASFLATBTN, LPGENW("Save log to file (Ctrl+S)") }, { IDC_COPY, IDI_COPY, BUTTONSETASFLATBTN, LPGENW("Copy selected log (Ctrl+C)") }, { IDC_DELETE, IDI_DELETE, BUTTONSETASFLATBTN, LPGENW("Delete selected (Del)") }, { IDC_OPTIONS, IDI_OPTIONS, BUTTONSETASFLATBTN, LPGENW("Log options (Ctrl+O)") }, { IDC_STARTALL, IDI_START, BUTTONSETASFLATBTN, LPGENW("Start logging in all tabs") }, { IDC_PAUSEALL, IDI_PAUSE, BUTTONSETASFLATBTN, LPGENW("Pause logging in all tabs") }, { IDC_CLOSE, IDI_CLOSE, BUTTONSETASFLATBTN, LPGENW("Close tab (Ctrl+W)") }, }; //////////////////////////////////////////////////////////////////////////////// static LRESULT CALLBACK SubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { int ctrl; switch (msg) { case WM_KEYDOWN: ctrl = GetKeyState(VK_CONTROL) & 0x8000; if (wParam == VK_DELETE && !ctrl) { SendMessage(GetParent(hwnd), WM_COMMAND, IDC_DELETE, 0); break; } if (wParam == VK_LEFT && ctrl) { NMHDR nmhdr = {}; int tab = TabCtrl_GetCurSel(hTabs); if (tab == 0) tab = TabCtrl_GetItemCount(hTabs) - 1; else tab--; TabCtrl_SetCurSel(hTabs, tab); nmhdr.code = TCN_SELCHANGE; SendMessage(hwndConsole, WM_NOTIFY, IDC_TABS, (LPARAM)&nmhdr); break; } if (wParam == VK_RIGHT && ctrl) { NMHDR nmhdr = {}; int tab = TabCtrl_GetCurSel(hTabs); int count = TabCtrl_GetItemCount(hTabs); tab = (tab + 1) % count; TabCtrl_SetCurSel(hTabs, tab); nmhdr.code = TCN_SELCHANGE; SendMessage(hwndConsole, WM_NOTIFY, IDC_TABS, (LPARAM)&nmhdr); break; } break; case WM_CHAR: // CTRL if (!(GetKeyState(VK_CONTROL) & 0x8000)) break; switch (wParam) { case 1: // Ctrl+A if (ListView_GetSelectedCount(hwnd) != (UINT)ListView_GetItemCount(hwnd)) ListView_SetItemState(hwnd, -1, LVIS_SELECTED, LVIS_SELECTED); return 0; case 3: // Ctrl+D SendMessage(GetParent(hwnd), WM_COMMAND, IDC_COPY, 0); return 0; case 15: // Ctrl+O SendMessage(GetParent(hwnd), WM_COMMAND, IDC_OPTIONS, 0); return 0; case 16: // Ctrl+P SendMessage(GetParent(hwnd), WM_COMMAND, IDC_PAUSE, 0); return 0; case 17: // Ctrl+Q SendMessage(GetParent(hwnd), WM_COMMAND, IDC_SCROLL, 0); return 0; case 19: // Ctrl+S SendMessage(GetParent(hwnd), WM_COMMAND, IDC_SAVE, 0); return 0; case 23: // Ctrl+W SendMessage(GetParent(hwnd), WM_COMMAND, IDC_CLOSE, 0); return 0; } break; } return mir_callNextSubclass(hwnd, SubclassProc, msg, wParam, lParam); } //////////////////////////////////////////////////////////////////////////////// static INT_PTR CALLBACK LogDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) { LOGWIN *dat = (LOGWIN *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); switch (message) { case WM_INITDIALOG: { dat = (LOGWIN *)lParam; dat->hwnd = hwndDlg; dat->Scroll = 1; dat->Paused = 0; dat->hList = GetDlgItem(hwndDlg, IDC_LIST); SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); mir_subclassWindow(dat->hList, SubclassProc); // init buttons for (int i = 0; i < _countof(ctrls); i++) { HWND hwnd = GetDlgItem(hwndDlg, ctrls[i].control); SendMessage(hwnd, ctrls[i].type, 0, 0); SendMessage(hwnd, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcons[i + ICON_FIRST]); SendMessage(hwnd, BUTTONADDTOOLTIP, (WPARAM)TranslateW(ctrls[i].tooltip), BATF_UNICODE); } CheckDlgButton(hwndDlg, IDC_SCROLL, dat->Scroll ? BST_CHECKED : BST_UNCHECKED); SendDlgItemMessage(hwndDlg, IDC_SCROLL, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcons[(dat->Scroll ? ICON_SCROLL : ICON_NOSCROLL)]); if (gSingleMode) { ShowWindow(GetDlgItem(hwndDlg, IDC_PAUSEALL), SW_HIDE); ShowWindow(GetDlgItem(hwndDlg, IDC_STARTALL), SW_HIDE); ShowWindow(GetDlgItem(hwndDlg, IDC_CLOSE), SW_HIDE); } // init listview LVITEM lvi = { 0 }; LVCOLUMN sLC; //ListView_SetUnicodeFormat(dat->hList, TRUE); ListView_SetImageList(dat->hList, gImg, LVSIL_SMALL); sLC.mask = LVCF_FMT | LVCF_WIDTH; sLC.fmt = LVCFMT_LEFT; sLC.cx = 630; ListView_InsertColumn(dat->hList, 0, &sLC); ListView_SetExtendedListViewStyle(dat->hList, LVS_EX_FULLROWSELECT); lvi.mask = LVIF_TEXT; if (gIcons) { lvi.mask |= LVIF_IMAGE; lvi.iImage = IMG_INFO; } lvi.pszText = TranslateT("*** Console started ***"); ListView_InsertItem(dat->hList, &lvi); SendMessage(hwndDlg, WM_SIZE, 0, 0); } break; case HM_DUMP: if (!lParam) break; if (dat && !dat->Paused) { LVITEM lvi = { 0 }; int last = 0x7fffffff; wchar_t szBreak; uint32_t len, tmplen; uint32_t wraplen = gWrapLen; wchar_t *str = ((DUMPMSG *)lParam)->szMsg; lvi.iItem = 0x7fffffff; str = wcstok(str, L"\n"); if (gIcons && str != nullptr) { lvi.mask = LVIF_TEXT | LVIF_IMAGE; if (wcsstr(str, L"Data received")) { if (gSeparator) ListView_InsertItem(dat->hList, &lvi); lvi.iImage = IMG_IN; } else if (wcsstr(str, L"Data sent")) { if (gSeparator) ListView_InsertItem(dat->hList, &lvi); lvi.iImage = IMG_OUT; } else { if (gSeparator && dat->newline) { ListView_InsertItem(dat->hList, &lvi); dat->newline = 0; } lvi.iImage = IMG_INFO; } } else lvi.mask = LVIF_TEXT; while (str != nullptr) { lvi.pszText = &str[0]; tmplen = len = (uint32_t)mir_wstrlen(lvi.pszText); while (len > wraplen) { szBreak = lvi.pszText[wraplen]; lvi.pszText[wraplen] = 0; last = ListView_InsertItem(dat->hList, &lvi); lvi.pszText[wraplen] = szBreak; len -= wraplen; lvi.pszText = &str[0] + tmplen - len; dat->newline = 1; lvi.iImage = IMG_EMPTY; } if (len && lvi.pszText[len - 1] == '\r') lvi.pszText[len - 1] = 0; last = ListView_InsertItem(dat->hList, &lvi); str = wcstok(nullptr, L"\n"); if (str) dat->newline = 1; lvi.iImage = IMG_EMPTY; } if (gVisible && dat == pActive && dat->Scroll == 1) ListView_EnsureVisible(dat->hList, last, FALSE); if (last > gLimit) { int idx = last - gLimit + gLimit / 4; // leave only 75% of LIMIT while (idx >= 0) { ListView_DeleteItem(dat->hList, idx); idx--; } } } mir_free((DUMPMSG *)lParam); return TRUE; case WM_SIZE: SetWindowPos(hwndDlg, HWND_TOP, rcTabs.left, rcTabs.top, rcTabs.right - rcTabs.left, rcTabs.bottom - rcTabs.top, SWP_SHOWWINDOW); Utils_ResizeDialog(hwndDlg, g_plugin.getInst(), MAKEINTRESOURCEA(IDD_LOG), LogResize); break; case WM_COMMAND: if (!dat) break; switch (LOWORD(wParam)) { case IDC_PAUSE: { LVITEM lvi = { 0 }; dat->Paused = !(dat->Paused); lvi.mask = LVIF_TEXT | LVIF_IMAGE; lvi.iImage = IMG_INFO; lvi.iItem = 0x7FFFFFFF; lvi.pszText = (dat->Paused) ? TranslateT("*** Console paused ***") : TranslateT("*** Console resumed ***"); ListView_InsertItem(dat->hList, &lvi); CheckDlgButton(hwndDlg, IDC_PAUSE, (dat->Paused) ? BST_CHECKED : BST_UNCHECKED); SendDlgItemMessage(hwndDlg, IDC_PAUSE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcons[((dat->Paused) ? ICON_PAUSED : ICON_STARTED)]); break; } case IDC_SCROLL: dat->Scroll = !(dat->Scroll); CheckDlgButton(hwndDlg, IDC_SCROLL, (dat->Scroll) ? BST_CHECKED : BST_UNCHECKED); SendDlgItemMessage(hwndDlg, IDC_SCROLL, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcons[((dat->Scroll) ? ICON_SCROLL : ICON_NOSCROLL)]); break; case IDC_COPY: { int idx = 0; wchar_t szText[MAX_WRAPLEN + 1]; wchar_t *src, *dst, *buf; int flags = LVNI_BELOW; int count = ListView_GetSelectedCount(dat->hList); if (count) flags |= LVNI_SELECTED; else count = ListView_GetItemCount(dat->hList); dst = buf = (wchar_t *)malloc((count * (sizeof(szText) + 1) + 1) * sizeof(wchar_t)); if (!buf) break; while ((idx = ListView_GetNextItem(dat->hList, idx, flags)) > 0) { ListView_GetItemText(dat->hList, idx, 0, szText, _countof(szText) - 1); src = szText; while (*dst++ = *src++); dst--; *dst++ = '\r'; *dst++ = '\n'; *dst = 0; } if (dst - buf > 0) Utils_ClipboardCopy(buf); free(buf); break; } case IDC_DELETE: { int idx = 0; int count = ListView_GetSelectedCount(dat->hList); if (!count) break; if (count == ListView_GetItemCount(dat->hList)) { LVITEM lvi = { 0 }; ListView_DeleteAllItems(dat->hList); lvi.mask = LVIF_TEXT | LVIF_IMAGE; lvi.iImage = IMG_INFO; lvi.pszText = TranslateT("*** Console cleared ***"); ListView_InsertItem(dat->hList, &lvi); dat->newline = 0; break; } while ((idx = ListView_GetNextItem(dat->hList, idx, LVNI_BELOW | LVNI_SELECTED)) > 0) { ListView_DeleteItem(dat->hList, idx); idx--; } } break; case IDC_SAVE: { wchar_t szFile[MAX_PATH]; if (!Openfile(szFile, ListView_GetSelectedCount(dat->hList))) break; FILE *fp = _wfopen(szFile, L"wt"); if (fp) { int idx = 0; wchar_t szText[MAX_WRAPLEN + 1]; int flags = LVNI_BELOW; if (ListView_GetSelectedCount(dat->hList)) flags |= LVNI_SELECTED; while ((idx = ListView_GetNextItem(dat->hList, idx, flags)) > 0) { ListView_GetItemText(dat->hList, idx, 0, szText, _countof(szText)); fwprintf(fp, L"%s\n", szText); } fclose(fp); } break; } case IDC_OPTIONS: CallServiceSync(MS_NETLIB_LOGWIN, 0, 0); break; case IDC_STARTALL: SendMessage(hwndConsole, HM_PAUSEALL, 0, 0); break; case IDC_PAUSEALL: SendMessage(hwndConsole, HM_PAUSEALL, 0, 1); break; case IDC_CLOSE: if (tabCount > 1) SendMessage(hwndDlg, WM_CLOSE, 0, 0); break; } break; case WM_CLOSE: DestroyWindow(hwndDlg); break; case WM_DESTROY: SendMessage(hwndConsole, HM_REMOVE, 0, (LPARAM)dat); break; } return FALSE; } //////////////////////////////////////////////////////////////////////////////// int ConsoleResize(HWND, LPARAM, UTILRESIZECONTROL *urc) { switch (urc->wId) { case IDC_TABS: return RD_ANCHORX_WIDTH | RD_ANCHORY_HEIGHT; default: return RD_ANCHORX_LEFT | RD_ANCHORY_TOP; } } static INT_PTR CALLBACK ConsoleDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: { wchar_t title[MAX_PATH]; wchar_t name[MAX_PATH] = { 0 }; wchar_t path[MAX_PATH] = { 0 }; hTabs = GetDlgItem(hwndDlg, IDC_TABS); // restore position Utils_RestoreWindowPosition(hwndDlg, NULL, MODULENAME, MODULENAME, RWPF_HIDDEN); Profile_GetNameW(_countof(name), name); Profile_GetPathW(_countof(path), path); mir_snwprintf(title, L"%s - %s\\%s", TranslateT("Miranda Console"), path, name); SetWindowText(hwndDlg, title); SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcons[0]); hwndConsole = hwndDlg; SendMessage(hwndDlg, HM_ADD, 0, 0); PostMessage(hwndDlg, WM_SIZE, 0, 0); break; } case HM_DUMP: { // lParam = DUMPMSG int idx; LOGWIN *lw; LOGWIN lw2 = {}; DUMPMSG *dumpMsg = (DUMPMSG *)lParam; if (!pActive) { mir_free(dumpMsg); break; } if (!gSingleMode) { lw2.Module = dumpMsg->szModule; if (!List_GetIndex(&lModules, &lw2, &idx)) SendMessage(hwndDlg, HM_ADD, (WPARAM)idx, (LPARAM)dumpMsg->szModule); lw = (LOGWIN *)lModules.items[idx]; } else lw = pActive; if (lw->hwnd) SendMessage(lw->hwnd, HM_DUMP, wParam, lParam); else PostMessage(hwndDlg, HM_DUMP, wParam, lParam); // loop msg until window will be ready return TRUE; } case HM_ADD: { // wParam = index, lParam = module name LOGWIN *lw; COLORREF col; TCITEM tci = { 0 }; int idx = (int)wParam; char *str = (char *)lParam; if (!str) str = ""; // startup window lw = (LOGWIN *)mir_calloc(sizeof(LOGWIN)); lw->Module = (char *)mir_strdup(str); List_Insert(&lModules, lw, idx); if (!gSingleMode && lParam) { tci.mask = TCIF_PARAM | TCIF_TEXT; tci.lParam = (LPARAM)lw; tci.pszText = mir_a2u(lw->Module); idx = TabCtrl_InsertItem(hTabs, tabCount, &tci); mir_free(tci.pszText); tabCount++; } GetClientRect(hTabs, &rcTabs); TabCtrl_AdjustRect(hTabs, FALSE, &rcTabs); CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_LOG), hwndDlg, LogDlgProc, (LPARAM)lw); ShowWindow(lw->hwnd, (tabCount > 1) ? SW_HIDE : SW_SHOWNOACTIVATE); if (pActive) { col = ListView_GetBkColor(pActive->hList); ListView_SetBkColor(lw->hList, col); ListView_SetTextBkColor(lw->hList, col); col = ListView_GetTextColor(pActive->hList); ListView_SetTextColor(lw->hList, col); if (hfLogFont) SendMessage(lw->hList, WM_SETFONT, (WPARAM)hfLogFont, TRUE); } // hide startup window if (tabCount == 1) { ShowWindow(pActive->hwnd, SW_HIDE); PostMessage(pActive->hwnd, WM_CLOSE, 0, 0); pActive = lw; } if (!pActive) pActive = lw; return TRUE; } case HM_REMOVE: { // lParam = LOGWIN LOGWIN *lw = (LOGWIN *)lParam; if (!lw) break; if (lw == pActive) { int tab = TabCtrl_GetCurSel(hTabs); if (tab >= 0) { TCITEM tci = { 0 }; TabCtrl_DeleteItem(hTabs, tab); tabCount--; if (tabCount) { tab--; if (tab < 0) tab = 0; TabCtrl_SetCurSel(hTabs, tab); tci.mask = TCIF_PARAM; TabCtrl_GetItem(hTabs, tab, &tci); pActive = (LOGWIN *)tci.lParam; SendMessage(pActive->hwnd, WM_SIZE, 0, 0); ScrollDown(pActive); ShowWindow(pActive->hwnd, SW_SHOWNOACTIVATE); SetFocus(pActive->hList); } else pActive = nullptr; } } List_RemovePtr(&lModules, lw); mir_free(lw->Module); mir_free(lw); return TRUE; } case HM_SETFONT: { // wParam = font, lParam = font color int i; LOGWIN *lw; for (i = 0; i < lModules.realCount; i++) { lw = (LOGWIN *)lModules.items[i]; ListView_SetTextColor(lw->hList, (COLORREF)lParam); if (wParam) SendMessage(lw->hList, WM_SETFONT, wParam, TRUE); } return TRUE; } case HM_SETCOLOR: { // wParam = font, lParam = background color int i; LOGWIN *lw; for (i = 0; i < lModules.realCount; i++) { lw = (LOGWIN *)lModules.items[i]; ListView_SetBkColor(lw->hList, (COLORREF)lParam); ListView_SetTextBkColor(lw->hList, (COLORREF)lParam); if (wParam) SendMessage(lw->hList, WM_SETFONT, wParam, TRUE); } return TRUE; } case HM_PAUSEALL: { // lParam = 1 to pause, 0 to start int i; LOGWIN *lw; for (i = 0; i < lModules.realCount; i++) { lw = (LOGWIN *)lModules.items[i]; if (lw->Paused != (int)lParam) SendMessage(lw->hwnd, WM_COMMAND, IDC_PAUSE, 0); } return TRUE; } case HM_RESTART: { if (pActive) { pActive = nullptr; PostMessage(hwndDlg, HM_RESTART, 0, 0); return TRUE; } // close all tabs if (!lParam) { LOGWIN *lw; TabCtrl_DeleteAllItems(hTabs); while (lModules.realCount) { lw = (LOGWIN *)lModules.items[0]; SendMessage(lw->hwnd, WM_CLOSE, 0, 0); } tabCount = 0; PostMessage(hwndDlg, HM_RESTART, 0, 1); return TRUE; } LoadSettings(); SendMessage(hwndDlg, HM_ADD, 0, 0); PostMessage(hwndDlg, WM_SIZE, 0, 0); SendMessage(hwndDlg, HM_SETFONT, (WPARAM)hfLogFont, (LPARAM)colLogFont); SendMessage(hwndDlg, HM_SETCOLOR, (WPARAM)hfLogFont, (LPARAM)colBackground); return TRUE; } case WM_SETFOCUS: if (pActive) { SetFocus(pActive->hList); } return TRUE; case WM_NOTIFY: switch (wParam) { case IDC_TABS: { LPNMHDR lpnmhdr = (LPNMHDR)lParam; if (lpnmhdr->code == TCN_SELCHANGE) { int newTab = TabCtrl_GetCurSel(hTabs); if (newTab >= 0) { TCITEM tci = { 0 }; HWND hOld = pActive->hwnd; tci.mask = TCIF_PARAM; if (!TabCtrl_GetItem(hTabs, newTab, &tci)) break; pActive = (LOGWIN *)tci.lParam; SendMessage(pActive->hwnd, WM_SIZE, 0, 0); ScrollDown(pActive); ShowWindow(hOld, SW_HIDE); ShowWindow(pActive->hwnd, SW_SHOWNOACTIVATE); SetFocus(pActive->hList); } else SendMessage(pActive->hwnd, WM_SIZE, 0, 0); } break; } } break; case WM_SIZE: Utils_ResizeDialog(hwndDlg, g_plugin.getInst(), MAKEINTRESOURCEA(IDD_CONSOLE), ConsoleResize); GetClientRect(hTabs, &rcTabs); TabCtrl_AdjustRect(hTabs, FALSE, &rcTabs); if (pActive) SendMessage(pActive->hwnd, WM_SIZE, 0, 0); break; case WM_GETMINMAXINFO: { MINMAXINFO *mmi = (MINMAXINFO *)lParam; mmi->ptMinTrackSize.x = 400; mmi->ptMinTrackSize.y = 200; break; } case WM_CLOSE: if (lParam != 1) { Utils_SaveWindowPosition(hwndDlg, NULL, MODULENAME, MODULENAME); ShowConsole(0); return TRUE; } else DestroyWindow(hwndDlg); break; case WM_DESTROY: pActive = nullptr; if (hfLogFont) DeleteObject(hfLogFont); PostQuitMessage(0); break; } return FALSE; } //////////////////////////////////////////////////////////////////////////////// static void __cdecl ConsoleThread(void*) { MThreadLock threadLock(g_plugin.hConsoleThread); CoInitialize(nullptr); HWND hwnd = CreateDialog(g_plugin.getInst(), MAKEINTRESOURCE(IDD_CONSOLE), nullptr, ConsoleDlgProc); if (!hwnd) return; MSG msg; while (GetMessage(&msg, nullptr, 0, 0) > 0) { switch (msg.message) { case HM_DUMP: OutMsgs++; break; } if (hwnd != nullptr && IsDialogMessage(hwnd, &msg)) /* Wine fix. */ continue; TranslateMessage(&msg); DispatchMessage(&msg); } hwndConsole = nullptr; } /////////////////////////////////////////////////////////////////////////////// static int OnFastDump(WPARAM wParam, LPARAM lParam) { if (pActive) { LOGMSG *logMsg = (LOGMSG *)lParam; uint32_t headlen = (uint32_t)mir_strlen(logMsg->pszHead); uint32_t msglen = (uint32_t)mir_strlen(logMsg->pszMsg); uint32_t len = (headlen + msglen + 1) * sizeof(wchar_t) + sizeof(DUMPMSG); DUMPMSG *dumpMsg = (DUMPMSG *)mir_alloc(len); wchar_t *str = dumpMsg->szMsg; char *szModule = (wParam) ? ((NETLIBUSER *)wParam)->szDescriptiveName.a : "[Core]"; mir_strncpy(dumpMsg->szModule, szModule, _countof(dumpMsg->szModule)); wchar_t *ucs2 = mir_a2u(logMsg->pszHead); mir_wstrcpy(str, ucs2); mir_free(ucs2); // try to detect utf8 ucs2 = mir_utf8decodeW(logMsg->pszMsg); if (!ucs2) ucs2 = mir_a2u(logMsg->pszMsg); mir_wstrcat(str, ucs2); mir_free(ucs2); InMsgs++; PostMessage(hwndConsole, HM_DUMP, 0, (LPARAM)dumpMsg); } return 0; } /////////////////////////////////////////////////////////////////////////////// static void LoadSettings() { gIcons = g_plugin.getByte("ShowIcons", 1); gSeparator = g_plugin.getByte("Separator", 1); gSingleMode = g_plugin.getByte("SingleMode", 0); gWrapLen = g_plugin.getByte("Wrap", DEFAULT_WRAPLEN); if (gWrapLen < MIN_WRAPLEN) gWrapLen = DEFAULT_WRAPLEN; gLimit = g_plugin.getDword("Limit", MIN_LIMIT); if (gLimit > MAX_LIMIT) gLimit = MAX_LIMIT; if (gLimit < MIN_LIMIT) gLimit = MIN_LIMIT; } static void SaveSettings(HWND hwndDlg) { int len = GetDlgItemInt(hwndDlg, IDC_WRAP, nullptr, FALSE); if (len < MIN_WRAPLEN) len = MIN_WRAPLEN; else if (len > MAX_WRAPLEN) len = MAX_WRAPLEN; gWrapLen = len; SetDlgItemInt(hwndDlg, IDC_WRAP, gWrapLen, FALSE); g_plugin.setByte("Wrap", (uint8_t)len); len = GetDlgItemInt(hwndDlg, IDC_LIMIT, nullptr, FALSE); if (len < MIN_LIMIT) len = MIN_LIMIT; else if (len > MAX_LIMIT) len = MAX_LIMIT; gLimit = len; SetDlgItemInt(hwndDlg, IDC_LIMIT, gLimit, FALSE); g_plugin.setDword("Limit", len); g_plugin.setByte("SingleMode", (uint8_t)IsDlgButtonChecked(hwndDlg, IDC_SINGLE)); g_plugin.setByte("Separator", (uint8_t)IsDlgButtonChecked(hwndDlg, IDC_SEPARATOR)); g_plugin.setByte("ShowIcons", (uint8_t)IsDlgButtonChecked(hwndDlg, IDC_SHOWICONS)); g_plugin.setByte("ShowAtStart", (uint8_t)IsDlgButtonChecked(hwndDlg, IDC_START)); } static INT_PTR CALLBACK OptDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: TranslateDialogDefault(hwndDlg); CheckDlgButton(hwndDlg, IDC_START, g_plugin.getByte("ShowAtStart", 0) ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_SINGLE, gSingleMode ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_SHOWICONS, gIcons ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_SEPARATOR, gSeparator ? BST_CHECKED : BST_UNCHECKED); SetDlgItemInt(hwndDlg, IDC_WRAP, gWrapLen, FALSE); SetDlgItemInt(hwndDlg, IDC_LIMIT, gLimit, FALSE); break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDC_RESTART: { if (!pActive) break; SaveSettings(hwndDlg); PostMessage(hwndConsole, HM_RESTART, 0, 0); break; } case IDC_LIMIT: case IDC_WRAP: if (HIWORD(wParam) != EN_CHANGE || (HWND)lParam != GetFocus()) return FALSE; SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); break; case IDC_START: case IDC_SEPARATOR: case IDC_SHOWICONS: case IDC_SINGLE: SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); break; } break; case WM_NOTIFY: switch (((LPNMHDR)lParam)->idFrom) { case 0: switch (((LPNMHDR)lParam)->code) { case PSN_APPLY: { SaveSettings(hwndDlg); break; } } break; } break; } return FALSE; } static int OptInit(WPARAM wParam, LPARAM) { OPTIONSDIALOGPAGE odp = {}; odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS); odp.szGroup.a = LPGEN("Services"); odp.szTitle.a = LPGEN("Console"); odp.pfnDlgProc = OptDlgProc; odp.flags = ODPF_BOLDGROUPS; g_plugin.addOptions(wParam, &odp); return 0; } /////////////////////////////////////////////////////////////////////////////// static int OnColourChange(WPARAM, LPARAM) { if (hwndConsole) { colBackground = Colour_Get(MODULENAME, "Background"); if (colBackground != -1) SendMessage(hwndConsole, HM_SETCOLOR, (WPARAM)hfLogFont, (LPARAM)colBackground); } return 0; } static int OnFontChange(WPARAM, LPARAM) { if (hwndConsole) { HFONT hf = nullptr; LOGFONT LogFont = { 0 }; colLogFont = Font_GetW(L"Console", L"Text", &LogFont); if (LogFont.lfHeight != 0) { hf = CreateFontIndirectW(&LogFont); SendMessageW(hwndConsole, HM_SETFONT, (WPARAM)hf, (LPARAM)colLogFont); if (hfLogFont) DeleteObject(hfLogFont); hfLogFont = hf; } } return 0; } static int OnSystemModulesLoaded(WPARAM, LPARAM) { CreateServiceFunction(MS_CONSOLE_SHOW_HIDE, ShowHideConsole); FontIDW fid = {}; mir_wstrncpy(fid.group, LPGENW("Console"), _countof(fid.group)); mir_wstrncpy(fid.name, LPGENW("Text"), _countof(fid.name)); mir_strncpy(fid.dbSettingsGroup, MODULENAME, _countof(fid.dbSettingsGroup)); mir_strncpy(fid.setting, "ConsoleFont", _countof(fid.setting)); mir_wstrncpy(fid.backgroundGroup, LPGENW("Console"), _countof(fid.backgroundGroup)); mir_wstrncpy(fid.backgroundName, LPGENW("Background"), _countof(fid.backgroundName)); fid.flags = FIDF_DEFAULTVALID; fid.deffontsettings.charset = DEFAULT_CHARSET; fid.deffontsettings.colour = RGB(0, 0, 0); fid.deffontsettings.size = 10; fid.deffontsettings.style = 0; mir_wstrncpy(fid.deffontsettings.szFace, L"Courier", _countof(fid.deffontsettings.szFace)); g_plugin.addFont(&fid); HookEvent(ME_FONT_RELOAD, OnFontChange); ColourIDW cid = {}; mir_wstrncpy(cid.group, LPGENW("Console"), _countof(cid.group)); mir_wstrncpy(cid.name, LPGENW("Background"), _countof(cid.name)); mir_strncpy(cid.dbSettingsGroup, MODULENAME, _countof(cid.dbSettingsGroup)); mir_strncpy(cid.setting, "BgColor", _countof(cid.setting)); cid.defcolour = RGB(255, 255, 255); g_plugin.addColor(&cid); HookEvent(ME_COLOUR_RELOAD, OnColourChange); HOTKEYDESC hkd = {}; hkd.pszName = "Console_Show_Hide"; hkd.szSection.a = "Main"; hkd.szDescription.a = LPGEN("Show/Hide Console"); hkd.pszService = MS_CONSOLE_SHOW_HIDE; hkd.DefHotKey = HOTKEYCODE(HOTKEYF_EXT, 'C'); g_plugin.addHotkey(&hkd); if (hwndConsole && IsWindow(hwndConsole)) { HookEvent(ME_TTB_MODULELOADED, OnTTBLoaded); CMenuItem mi(&g_plugin); SET_UID(mi, 0x6d97694e, 0x2024, 0x4560, 0xbb, 0xbc, 0x20, 0x62, 0x7e, 0x5, 0xdf, 0xb3); mi.flags = CMIF_UNICODE; mi.hIcolibItem = hIcons[0]; mi.position = 1900000000; mi.name.w = (IsWindowVisible(hwndConsole)) ? LPGENW("Hide Console") : LPGENW("Show Console"); mi.pszService = MS_CONSOLE_SHOW_HIDE; hMenu = Menu_AddMainMenuItem(&mi); OnFontChange(0, 0); OnColourChange(0, 0); if (g_plugin.getByte("ShowAtStart", 0) || g_plugin.getByte("Show", 1)) ShowConsole(1); else ShowConsole(0); } return 0; } static int PreshutdownConsole(WPARAM, LPARAM) { if (hwndConsole) PostMessage(hwndConsole, WM_CLOSE, 0, 1); return 0; } /////////////////////////////////////////////////////////////////////////////// static int stringCompare(LOGWIN *lw1, LOGWIN *lw2) { return mir_strcmp(lw1->Module, lw2->Module); } /////////////////////////////////////////////////////////////////////////////// static UINT logicons[] = { IDI_EMPTY, IDI_ARROW, IDI_IN, IDI_OUT, IDI_INFO }; void InitConsole() { int i; HICON hi; lModules.sortFunc = (FSortFunc)stringCompare; lModules.increment = 5; hIcons[0] = LoadIcon(g_plugin.getInst(), MAKEINTRESOURCE(IDI_CONSOLE)); hIcons[1] = (HICON)LoadImage(g_plugin.getInst(), MAKEINTRESOURCE(IDI_NOSCROLL), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); hIcons[2] = (HICON)LoadImage(g_plugin.getInst(), MAKEINTRESOURCE(IDI_PAUSED), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); for (i = 0; i < _countof(ctrls); i++) { hIcons[i + ICON_FIRST] = (HICON)LoadImage(g_plugin.getInst(), MAKEINTRESOURCE(ctrls[i].icon), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); } gImg = ImageList_Create(LOGICONX_SIZE, LOGICONY_SIZE, ILC_COLOR24 | ILC_MASK, _countof(logicons), 0); for (i = 0; i < _countof(logicons); i++) { hi = (HICON)LoadImage(g_plugin.getInst(), MAKEINTRESOURCE(logicons[i]), IMAGE_ICON, LOGICONX_SIZE, LOGICONY_SIZE, 0); if (hi) { ImageList_AddIcon(gImg, hi); DestroyIcon(hi); } } LoadSettings(); g_plugin.hConsoleThread = mir_forkthread(ConsoleThread); HookEvent(ME_SYSTEM_PRESHUTDOWN, PreshutdownConsole); HookEvent(ME_SYSTEM_MODULESLOADED, OnSystemModulesLoaded); HookEvent(ME_OPT_INITIALISE, OptInit); HookEvent(ME_NETLIB_FASTDUMP, OnFastDump); } void ShutdownConsole(void) { List_Destroy(&lModules); if (gImg) ImageList_Destroy(gImg); for (int i = 0; i < _countof(hIcons); i++) if (hIcons[i]) DestroyIcon(hIcons[i]); if (hwndConsole) EndDialog(hwndConsole, TRUE); } //////////////////////////////////////////////////////////////////////////////// wchar_t *addstring(wchar_t *str, wchar_t *add) { mir_wstrcpy(str, add); return str + mir_wstrlen(add) + 1; } static int Openfile(wchar_t *outputFile, int selection) { wchar_t filename[MAX_PATH + 2] = L""; wchar_t *title; wchar_t *filter, *tmp, *tmp1, *tmp2; tmp1 = TranslateT("Text Files (*.txt)"); tmp2 = TranslateT("All Files"); filter = tmp = (wchar_t*)_alloca((mir_wstrlen(tmp1) + mir_wstrlen(tmp2) + 11) * sizeof(wchar_t)); tmp = addstring(tmp, tmp1); tmp = addstring(tmp, L"*.TXT"); tmp = addstring(tmp, tmp2); tmp = addstring(tmp, L"*"); *tmp = 0; if (selection) title = TranslateT("Save selection to file"); else title = TranslateT("Save log to file"); OPENFILENAME ofn = { 0 }; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hwndConsole; ofn.lpstrFile = filename; ofn.lpstrFilter = filter; ofn.Flags = OFN_HIDEREADONLY | OFN_SHAREAWARE | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT; ofn.lpstrTitle = title; ofn.nMaxFile = MAX_PATH; ofn.lpstrDefExt = L"txt"; if (!GetSaveFileName(&ofn)) return 0; mir_wstrcpy(outputFile, filename); return 1; }