1: /*
     2:  ============================================================================
     3:  xoblite™ -> an advanced "extended shell" for Microsoft® Windows® 11
     4:  Copyright © 2002-2026 Karl Henrik Henriksson [qwilk/@xoblite]
     5:  Copyright © 2001-2004 The Blackbox for Windows Development Team
     6:  http://xoblite.net/ + https://github.com/xoblite/
     7:  ============================================================================
     8: 
     9:   Blackbox for Windows is free software, released under the
    10:   GNU General Public License (GPL version 2 or later), with an extension
    11:   that allows linking of proprietary modules under a controlled interface.
    12:   What this means is that plugins etc. are allowed to be released
    13:   under any license the author wishes. Please note, however, that the
    14:   original Blackbox gradient math code used in Blackbox for Windows
    15:   is available under the BSD license.
    16: 
    17:   http://www.fsf.org/licenses/gpl.html
    18:   http://www.fsf.org/licenses/gpl-faq.html#LinkingOverControlledInterface
    19:   http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
    20: 
    21:   This program is distributed in the hope that it will be useful,
    22:   but WITHOUT ANY WARRANTY; without even the implied warranty of
    23:   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    24:   GNU General Public License for more details.
    25: 
    26:   For additional license information, please read the included license.html.
    27: 
    28:  ============================================================================
    29: */
    30: 
    31: #include "Console.h" 
    32: 
    33: const char szConsoleName[] = "BBConsole"; // Window class etc.
    34: 
    35: extern Console *pConsole;
    36: extern Settings *pSettings;
    37: extern BImage *pBImage;
    38: extern Toolbar *pToolbar;
    39: extern Desktop *pDesktop;
    40: 
    41: int consoleMessageSubscription[] = { BB_TOGGLECONSOLE, BB_CONSOLEMESSAGE, BB_RECONFIGURE, 0 };
    42: 
    43: //===========================================================================
    44: 
    45: Console::Console(HINSTANCE hInstance)
    46: {
    47:     hConsoleWnd = NULL;
    48:     hConsoleInstance = hInstance;
    49:     hBlackboxWnd = GetBBWnd();
    50: 
    51:     cachedBackground = CreateCompatibleDC(NULL);
    52: 
    53:     ZeroMemory(&messageList, sizeof(messageList));
    54:     messageToBlock[0] = '\0';
    55: 
    56:     fullscreenDetected = false;
    57: 
    58:     //====================
    59: 
    60:     // Get size and position for our window...
    61:     GetDimensions();
    62: 
    63:     //====================
    64: 
    65:     // Register window class...
    66:     WNDCLASS wc;
    67:     ZeroMemory(&wc,sizeof(wc));
    68:     wc.hInstance = hConsoleInstance;            // hInstance
    69:     wc.lpfnWndProc = ConsoleWndProc;            // window procedure
    70:     wc.lpszClassName = szConsoleName;           // window class name
    71:     wc.hbrBackground = NULL;                    // no class background brush
    72:     wc.style = CS_DBLCLKS;                      // class styles (e.g. accept doubleclicks)
    73:     wc.hCursor = LoadCursor(NULL, IDC_ARROW);   // always display the regular arrow cursor
    74: 
    75:     if (!RegisterClass(&wc))
    76:     {
    77:         MessageBox(0, "Error registering console window class!", szConsoleName, MB_OK | MB_ICONERROR | MB_TOPMOST);
    78:         Log("Console", "Error registering window class!");
    79:         return;
    80:     }
    81: 
    82:     // Create console window...
    83:     hConsoleWnd = CreateWindowEx(
    84:         WS_EX_TOOLWINDOW | WS_EX_ACCEPTFILES | WS_EX_NOACTIVATE | WS_EX_LAYERED,    // window style
    85:         szConsoleName,                                                              // window class
    86:         NULL,                                                                       // window name
    87:         WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,                               // window parameters
    88:         ConsoleX,                                                                   // x position
    89:         ConsoleY,                                                                   // y position
    90:         ConsoleWidth,                                                               // window width
    91:         ConsoleHeight,                                                              // window height
    92:         NULL, //pDesktop->hDesktopWnd,                                              // owner window
    93:         NULL,                                                                       // no menu
    94:         hConsoleInstance,                                                           // hInstance
    95:         NULL                                                                        // no window creation data
    96:     );
    97: 
    98:     if (!hConsoleWnd)
    99:     {
   100:         UnregisterClass(szConsoleName,hConsoleInstance); // unregister window class
   101:         MessageBox(0, "Error creating console window!", szConsoleName, MB_OK | MB_ICONERROR | MB_TOPMOST);
   102:         Log("Console", "Error creating window!");
   103:         return;
   104:     }
   105: 
   106:     //====================
   107: 
   108:     // Subscribe to Blackbox messages applicable to the console...
   109:     SendMessage(hBlackboxWnd, BB_REGISTERMESSAGE, (WPARAM)hConsoleWnd, (LPARAM)consoleMessageSubscription);
   110: 
   111:     // Make the console window sticky...
   112:     MakeSticky(hConsoleWnd);
   113:     // Set console window z-order position to be right above the desktop...
   114:     SetWindowPos(hConsoleWnd, pDesktop->hDesktopWnd, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_NOOWNERZORDER);
   115: 
   116:     // Update the console window...
   117:     ShowWindow(hConsoleWnd, pSettings->consoleHidden ? SW_HIDE : SW_SHOWNOACTIVATE);
   118:     UpdateConsoleWindow();
   119: }
   120: 
   121: //===========================================================================
   122: 
   123: Console::~Console()
   124: {
   125:     // Unsubscribe to previously subscribed Blackbox messages...
   126:     SendMessage(hBlackboxWnd, BB_UNREGISTERMESSAGE, (WPARAM)hConsoleWnd, (LPARAM)consoleMessageSubscription);
   127: 
   128:     if (hConsoleWnd) DestroyWindow(hConsoleWnd); // Destroy the console window...
   129:     UnregisterClass(szConsoleName, hConsoleInstance); // Unregister the console window class...
   130:     
   131:     if (cachedBackground) DeleteDC(cachedBackground); // Delete the cached gradient...
   132: 
   133:     ClearHistory(false); // Clear all elements in the messageList...
   134: }
   135: 
   136: //===========================================================================
   137: 
   138: LRESULT CALLBACK ConsoleWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
   139: {
   140:     switch (message)
   141:     {
   142:         //====================
   143: 
   144:         case BB_CONSOLEMESSAGE:
   145:         {
   146:             if (strnlen_s(pConsole->messageToBlock, sizeofArray(pConsole->messageToBlock)) > 0)
   147:             {
   148:                 if (!_stricmp((LPCSTR)lParam, pConsole->messageToBlock))
   149:                 {
   150:                     // This message (typically a bro@m part of an @Script) should not be echoed in the console...
   151:                     pConsole->messageToBlock[0] = '\0';
   152:                     break;
   153:                 }
   154:             }
   155: 
   156:             //====================
   157: 
   158:             if ((int)pConsole->messageList.size() >= 50)
   159:             {
   160:                 // We already have the maximum 50 messages in the list, remove the oldest...
   161:                 if (pConsole->messageList[0]->icon != NULL) DeleteObject(&pConsole->messageList[0]->icon);
   162:                 delete pConsole->messageList[0];
   163:                 pConsole->messageList.erase(pConsole->messageList.begin());
   164:             }
   165: 
   166:             //====================
   167: 
   168:             messageListItem *newMessage = new messageListItem;
   169: 
   170:             newMessage->type = (int)wParam;
   171: 
   172:             //====================
   173: 
   174:             if (newMessage->type == CONSOLE_INFORMATION_MESSAGE) newMessage->icon = LoadIcon(NULL, IDI_INFORMATION);
   175:             else if (newMessage->type == CONSOLE_WARNING_MESSAGE) newMessage->icon = LoadIcon(NULL, IDI_WARNING);
   176:             else if (newMessage->type == CONSOLE_ERROR_MESSAGE) newMessage->icon = LoadIcon(NULL, IDI_ERROR);
   177:             else if (newMessage->type == CONSOLE_SHELL_MESSAGE) newMessage->icon = LoadIcon(pConsole->hConsoleInstance, MAKEINTRESOURCE(IDI_XOBLITE));
   178:             else newMessage->icon = NULL; // CONSOLE_REGULAR_MESSAGE, CONSOLE_PLAIN_MESSAGE, CONSOLE_INDENTED_MESSAGE, CONSOLE_SEPARATOR
   179: 
   180:             //====================
   181: 
   182:             if (newMessage->type != CONSOLE_SEPARATOR)
   183:             {
   184:                 char message[MAX_LINE_LENGTH];
   185:                 if (newMessage->type == CONSOLE_REGULAR_MESSAGE)
   186:                 {
   187:                     // Add a timestamp to regular messages...
   188:                     _strtime(message);
   189:                     strncat_s(message, sizeof(message), " -> ", _TRUNCATE);
   190:                     strncat_s(message, sizeof(message), (LPCSTR)lParam, _TRUNCATE);
   191:                 }
   192:                 else strncpy_s(message, sizeof(message), (LPCSTR)lParam, _TRUNCATE);
   193:                 message[sizeof(message)-1] = '\0'; // Failsafe
   194: 
   195:                 // Replace any \r\n in the string with a single space character...
   196:                 LPSTR ptr;
   197:                 while (ptr = strchr(message, '\r'))
   198:                 {
   199: //                  int nLen = strlen(ptr);
   200: //                  memmove(ptr+3, ptr, nLen);
   201: //                  strncpy(ptr, " -> ", 4);
   202:                     ptr[0] = ' ';
   203:                 }
   204:                 while (ptr = strchr(message, '\n'))
   205:                 {
   206:                     int nLen = strlen(ptr);
   207:                     memmove(ptr, ptr+1, nLen);
   208:                 }
   209: 
   210: //              strncpy(newMessage->msg, message, sizeof(newMessage->msg));
   211:                 strncpy_s(newMessage->msg, sizeof(newMessage->msg), message, _TRUNCATE);
   212:                 newMessage->msg[sizeof(newMessage->msg)-1] = '\0';
   213: 
   214:                 //====================
   215: 
   216:                 // Detect any URL in the message and if found make it clickable...
   217:                 char* url = StrStrI(newMessage->msg, "http://");
   218:                 if (url == NULL) url = StrStrI(newMessage->msg, "https://");
   219:                 if (url == NULL) url = StrStrI(newMessage->msg, "file://");
   220:                 if (url == NULL) newMessage->url[0] = '\0';
   221:                 else
   222:                 {
   223:                     strncpy_s(newMessage->url, sizeof(newMessage->url), url, _TRUNCATE);
   224:                     newMessage->url[sizeof(newMessage->url)-1] = '\0';
   225: 
   226:                     // Check whether the URL is part of an @Script, and if so perform some additional operations on the two strings... (URL+message)
   227:                     if (IsInString(newMessage->msg, "@Script"))
   228:                     {
   229:                         char* first = newMessage->url;
   230:                         char* pipe = StrStrI(newMessage->url, "|");
   231:                         if (pipe != NULL) newMessage->url[pipe-first] = '\0';
   232:                         else if (newMessage->url[strnlen_s(newMessage->url, sizeofArray(newMessage->url))-1] == ']') newMessage->url[strnlen_s(newMessage->url, sizeofArray(newMessage->url))-1] = '\0';
   233: 
   234:                         char updatedMsg[350];
   235:                         updatedMsg[sizeof(updatedMsg) - 1] = '\0';
   236:                         int n = url - newMessage->msg;
   237:                         strncpy_s(updatedMsg, sizeofArray(updatedMsg), newMessage->msg, n);
   238:                         strcat_s(updatedMsg, sizeofArray(updatedMsg), " "); // ...to avoid the later URL highlighting covering the character immediately *before* the URL...
   239:                         strcat_s(updatedMsg, sizeofArray(updatedMsg), newMessage->url);
   240:                         strcat_s(updatedMsg, sizeofArray(updatedMsg), "  "); // ...to avoid the later URL highlighting covering the character immediately *after* the URL...
   241:                         strcat_s(updatedMsg, sizeofArray(updatedMsg), &url[strnlen_s(newMessage->url, sizeofArray(newMessage->url))]);
   242:                         strncpy_s(newMessage->msg, sizeof(newMessage->msg), updatedMsg, _TRUNCATE);
   243:                         newMessage->msg[sizeof(newMessage->msg)-1] = '\0';
   244:                     }
   245: 
   246:                     // If not part of an @Script, we remove everything else after the URL (using blank space as delimiter) including any ending parantheses and commas...
   247:                     else if (strchr(newMessage->url, ' '))
   248:                     {
   249:                         char temp[sizeof(newMessage->url)];
   250:                         Tokenize(newMessage->url, temp, " ");
   251:                         strcpy_s(newMessage->url, sizeofArray(newMessage->url), temp);
   252:                         switch(newMessage->url[strnlen_s(newMessage->url, sizeofArray(newMessage->url))-1])
   253:                         {
   254:                             case ')':
   255:                             case '>':
   256:                             case ',':
   257:                             {
   258:                                 newMessage->url[strnlen_s(newMessage->url, sizeofArray(newMessage->url))-1] = '\0';
   259:                             }
   260:                         }
   261:                     }
   262:                 }
   263:             }
   264: 
   265:             //====================
   266: 
   267:             pConsole->messageList.push_back(newMessage);
   268: 
   269:             //====================
   270: 
   271:             if (!pSettings->consoleHidden)
   272:             {
   273:                 pConsole->UpdateConsoleWindow();
   274: 
   275: /*
   276:                 if (!SetTimer(pConsole->hConsoleWnd, CONSOLE_SETLABEL_TIMER, 4000, (TIMERPROC)NULL))
   277:                 {
   278:                     MessageBox(0, "Error creating console timer!", szConsoleName, MB_OK | MB_ICONERROR | MB_TOPMOST);
   279:                     Log("Could not create console timer!", NULL);
   280:                     return 0;
   281:                 }
   282: */
   283:             }
   284:         }
   285:         break;
   286: 
   287:         //===========================================================================
   288: /*
   289:         case WM_TIMER:
   290:         {
   291:             if (wParam == CONSOLE_SETLABEL_TIMER)
   292:             {
   293:                 KillTimer(pConsole->hConsoleWnd, CONSOLE_SETLABEL_TIMER);
   294:                 if (pSettings->consoleHidden) ShowWindow(pConsole->hConsoleWnd, SW_HIDE);
   295:             }
   296: 
   297:             return 0;
   298:         }
   299:         break;
   300: */
   301:         //====================
   302: 
   303:         case BB_RECONFIGURE:
   304:         case WM_DISPLAYCHANGE:
   305:         {
   306:             pConsole->UpdatePosition();
   307:         }
   308:         break;
   309: 
   310:         case WM_SETTINGCHANGE:
   311:         {
   312:             // Update the console's position (and maybe also dimensions) if the work area changes...
   313:             // (e.g. if the Explorer taskbar is moved to another screen edge)
   314:             if (wParam == SPI_SETWORKAREA) pConsole->UpdatePosition();
   315:             return 0;
   316:         }
   317: 
   318:         //====================
   319: 
   320:         case BB_TOGGLECONSOLE:
   321:         {
   322:             if (pSettings->consoleHidden)
   323:             {
   324:                 // Show window and force update...
   325:                 pSettings->consoleHidden = false;
   326:                 ShowWindow(pConsole->hConsoleWnd, SW_SHOWNOACTIVATE);
   327:                 pConsole->UpdateConsoleWindow();
   328:             }
   329:             else
   330:             {
   331:                 // Hide window...
   332:                 pSettings->consoleHidden = true;
   333:                 ShowWindow(pConsole->hConsoleWnd, SW_HIDE);
   334:             }
   335: 
   336:             PlaySoundFX(SFX_TOGGLE_ELEMENT);
   337: 
   338:             WriteBool(pSettings->xobrcFile, "xoblite.console.hidden:", pSettings->consoleHidden);
   339:         }
   340:         break;
   341: 
   342:         //====================
   343: /*
   344:         case WM_WINDOWPOSCHANGING:
   345:         {
   346:             // ##### FOR SOME REASON THIS WILL CRASH THE SHELL...? #####
   347:             if (pDesktop) SetWindowPos(pConsole->hConsoleWnd, pDesktop->hDesktopWnd, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOOWNERZORDER);
   348:             return 0;
   349:         }
   350: */
   351:         //====================
   352: 
   353:         case WM_GETMINMAXINFO:
   354:         {
   355:             MINMAXINFO *mmi = (MINMAXINFO*)lParam;
   356:             mmi->ptMaxSize.y = pConsole->ConsoleHeight;
   357:             return 0;
   358:         }
   359: 
   360:         //====================
   361: 
   362:         case WM_CLOSE:
   363:             return 0;
   364: 
   365:         //====================
   366: 
   367:         case WM_MOUSEACTIVATE:
   368:             return MA_NOACTIVATE;
   369: 
   370: //      case WM_ACTIVATE:
   371: //          if (LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE) SetActiveWindow(hwnd);
   372: //      return 0;
   373: 
   374:         //====================
   375: 
   376:         case WM_NCHITTEST:
   377:         {
   378:             if ((GetAsyncKeyState(VK_CONTROL) & 0x8000)) return HTCAPTION;
   379:             else return HTCLIENT;
   380:         }
   381:         break;
   382: 
   383:         //====================
   384: 
   385:         case WM_LBUTTONDBLCLK:
   386:         {
   387:             // Check which message was clicked...
   388:             POINT pt;
   389:             pt.x = LOWORD(lParam);
   390:             pt.y = HIWORD(lParam);
   391: 
   392:             int i = 0;
   393:             for (i; i < (int)pConsole->messageList.size(); i++)
   394:             {
   395:                 if (PtInRect(&pConsole->messageList[i]->r, pt)) break;
   396:             }
   397:             if (i == (int)pConsole->messageList.size()) break; // No message clicked
   398: 
   399:             //====================
   400: 
   401:             if (strnlen_s(pConsole->messageList[i]->url, sizeofArray(pConsole->messageList[i]->url)) > 0) // Message including a clickable URL...
   402:             {
   403:                 BBExecute(GetDesktopWindow(), NULL, pConsole->messageList[i]->url, NULL, NULL, SW_SHOWNORMAL, true);
   404:             }
   405:             else
   406:             {
   407:                 // Relay all mouse actions and drag'n'dropped files to the desktop...
   408: //              SendMessage(pDesktop->hDesktopWnd, message, wParam, lParam);
   409:             }
   410:         }
   411:         break;
   412: 
   413: 
   414:         //====================
   415: 
   416:         case WM_LBUTTONDOWN:
   417:         case WM_LBUTTONUP:
   418:         case WM_RBUTTONDOWN:
   419:         case WM_RBUTTONUP:
   420:         case WM_MBUTTONDOWN:
   421:         case WM_MBUTTONUP:
   422:         case WM_XBUTTONDOWN:
   423:         case WM_XBUTTONUP:
   424:         case WM_MOUSEWHEEL:
   425:         case WM_MOUSEHWHEEL:
   426:         case WM_DROPFILES:
   427:         {
   428:             if (message == WM_LBUTTONUP)
   429:             {
   430:                 POINT pt;
   431:                 pt.x = LOWORD(lParam);
   432:                 pt.y = HIWORD(lParam);
   433: 
   434:                 // Did the user click the maximize/restore button?
   435:                 if (PtInRect(&pConsole->consoleZoomButtonRect, pt))
   436:                 {
   437:                     pConsole->ToggleMaximized();
   438:                     break;
   439:                 }
   440:             }
   441: 
   442:             // If not, we relay all mouse actions and drag'n'dropped files to the desktop...
   443:             pDesktop->MouseAndDropHandler(pDesktop->hDesktopWnd, message, wParam, lParam);
   444:         }
   445:         break;
   446: 
   447:         //====================
   448: 
   449:         default:
   450:             return DefWindowProc(hwnd,message,wParam,lParam);
   451: 
   452:     //====================
   453:     }
   454:     return 0;
   455: }
   456: 
   457: //===========================================================================
   458: // Function: UpdateConsoleWindow
   459: // Purpose: ...
   460: //===========================================================================
   461: 
   462: void Console::UpdateConsoleWindow()
   463: {
   464:     if (fullscreenDetected) return;
   465: 
   466:     // Fetch the new size and position parameters for our window...
   467: //  GetDimensions();
   468: 
   469:     RECT r;
   470:     SetRect(&r, 0, 0, ConsoleWidth, ConsoleHeight);
   471: 
   472:     HDC hdc = GetWindowDC(hConsoleWnd);
   473:     HBITMAP bufbmp = CreateCompatibleBitmap(hdc, r.right-r.left, r.bottom-r.top);
   474:     DeleteObject(SelectObject(cachedBackground, bufbmp));
   475:     ReleaseDC(hConsoleWnd, hdc);
   476: 
   477:     //====================
   478: 
   479:     // Draw console background...
   480:     HBRUSH brush;
   481:     if (pSettings->consoleDesktopMode) brush = CreateSolidBrush(0x000000);
   482:     else brush = CreateSolidBrush(pSettings->Console->Color);
   483:     FillRect(cachedBackground, &r, brush);
   484:     DeleteObject(brush);
   485: 
   486:     //====================
   487: 
   488:     if (!pSettings->consoleDesktopMode)
   489:     {
   490:         // Draw maximize/restore button background...
   491:         brush = CreateSolidBrush(pSettings->Console->PicColor);
   492:         FillRect(cachedBackground, &consoleZoomButtonRect, brush);
   493:         DeleteObject(brush);
   494: 
   495:         // Draw maximize/restore button glyph...
   496:         HPEN zoomPen = CreatePen(PS_SOLID, 1, pSettings->Console->Color);
   497:         HPEN oldPen = (HPEN) SelectObject(cachedBackground, zoomPen);
   498: 
   499:         int offsetY = consoleZoomButtonRect.top + 7;
   500:         int offsetX = consoleZoomButtonRect.left + 7;
   501: 
   502:         if (pSettings->consoleMaximized)
   503:         {
   504:             MoveToEx(cachedBackground, offsetX-4, offsetY-1, NULL);
   505:             LineTo(cachedBackground, offsetX+1, offsetY-1);
   506:             LineTo(cachedBackground, offsetX+1, offsetY+4);
   507:             LineTo(cachedBackground, offsetX-4, offsetY+4);
   508:             LineTo(cachedBackground, offsetX-4, offsetY-1);
   509:             MoveToEx(cachedBackground, offsetX-4, offsetY, NULL);
   510:             LineTo(cachedBackground, offsetX+1, offsetY);
   511: 
   512:             MoveToEx(cachedBackground, offsetX-1, offsetY-4, NULL);
   513:             LineTo(cachedBackground, offsetX+4, offsetY-4);
   514:             LineTo(cachedBackground, offsetX+4, offsetY+1);
   515:             LineTo(cachedBackground, offsetX+1, offsetY+1);
   516:             MoveToEx(cachedBackground, offsetX-1, offsetY, NULL);
   517:             LineTo(cachedBackground, offsetX-1, offsetY-4);
   518:             MoveToEx(cachedBackground, offsetX-1, offsetY-3, NULL);
   519:             LineTo(cachedBackground, offsetX+4, offsetY-3);
   520:         }
   521:         else
   522:         {
   523:             MoveToEx(cachedBackground, offsetX-4, offsetY-4, NULL);
   524:             LineTo(cachedBackground, offsetX+4, offsetY-4);
   525:             LineTo(cachedBackground, offsetX+4, offsetY+4);
   526:             LineTo(cachedBackground, offsetX-4, offsetY+4);
   527:             LineTo(cachedBackground, offsetX-4, offsetY-4);
   528:             MoveToEx(cachedBackground, offsetX-4, offsetY-3, NULL);
   529:             LineTo(cachedBackground, offsetX+4, offsetY-3);
   530:         }
   531: 
   532: //      if (pSettings->doubleScaleHiDPI) StretchBlt(cachedBackground, consoleZoomButtonRect.left, consoleZoomButtonRect.top, 30, 30, cachedBackground, consoleZoomButtonRect.left, consoleZoomButtonRect.top, 15, 15, SRCCOPY);
   533:         StretchBlt(cachedBackground, consoleZoomButtonRect.left, consoleZoomButtonRect.top, (15 * pSettings->scalingFactorHiDPI), (15 * pSettings->scalingFactorHiDPI), cachedBackground, consoleZoomButtonRect.left, consoleZoomButtonRect.top, 15, 15, SRCCOPY);
   534: 
   535:         SelectObject(cachedBackground, oldPen);
   536:         DeleteObject(zoomPen);
   537: 
   538:         //====================
   539: 
   540:         // Draw "lock" indicator if write protection is enabled...
   541:         if (pSettings->writeProtection)
   542:         {
   543:             // Draw "lock" background...
   544:             brush = CreateSolidBrush(pSettings->Console->PicColor); //0x000066
   545:             FillRect(cachedBackground, &consoleLockGlyphRect, brush);
   546:             DeleteObject(brush);
   547: 
   548:             // Draw "lock" glyph...
   549:             HPEN protectPen = CreatePen(PS_SOLID, 1, pSettings->Console->Color);
   550:             HPEN oldPen = (HPEN) SelectObject(cachedBackground, protectPen);
   551: 
   552:             MoveToEx(cachedBackground, consoleLockGlyphRect.left+4, consoleLockGlyphRect.top+7, NULL);
   553:             LineTo(cachedBackground, consoleLockGlyphRect.left+11, consoleLockGlyphRect.top+7);
   554:             MoveToEx(cachedBackground, consoleLockGlyphRect.left+4, consoleLockGlyphRect.top+8, NULL);
   555:             LineTo(cachedBackground, consoleLockGlyphRect.left+11, consoleLockGlyphRect.top+8);
   556:             MoveToEx(cachedBackground, consoleLockGlyphRect.left+4, consoleLockGlyphRect.top+9, NULL);
   557:             LineTo(cachedBackground, consoleLockGlyphRect.left+11, consoleLockGlyphRect.top+9);
   558:             MoveToEx(cachedBackground, consoleLockGlyphRect.left + 4, consoleLockGlyphRect.top + 10, NULL);
   559:             LineTo(cachedBackground, consoleLockGlyphRect.left + 11, consoleLockGlyphRect.top + 10);
   560:             MoveToEx(cachedBackground, consoleLockGlyphRect.left + 4, consoleLockGlyphRect.top + 11, NULL);
   561:             LineTo(cachedBackground, consoleLockGlyphRect.left + 11, consoleLockGlyphRect.top + 11);
   562: 
   563:             MoveToEx(cachedBackground, consoleLockGlyphRect.left + 6, consoleLockGlyphRect.top + 3, NULL);
   564:             LineTo(cachedBackground, consoleLockGlyphRect.left + 9, consoleLockGlyphRect.top + 3);
   565:             MoveToEx(cachedBackground, consoleLockGlyphRect.left + 5, consoleLockGlyphRect.top + 4, NULL);
   566:             LineTo(cachedBackground, consoleLockGlyphRect.left + 5, consoleLockGlyphRect.top + 7);
   567:             MoveToEx(cachedBackground, consoleLockGlyphRect.left + 9, consoleLockGlyphRect.top + 4, NULL);
   568:             LineTo(cachedBackground, consoleLockGlyphRect.left + 9, consoleLockGlyphRect.top + 7);
   569: 
   570:             if (pSettings->scalingFactorHiDPI >= 2)
   571:             {
   572:                 StretchBlt(cachedBackground, consoleLockGlyphRect.left, consoleLockGlyphRect.top, 30, 30, cachedBackground, consoleLockGlyphRect.left, consoleLockGlyphRect.top, 15, 15, SRCCOPY);
   573:                 MoveToEx(cachedBackground, consoleLockGlyphRect.left + 8, consoleLockGlyphRect.top + 13, NULL);
   574:                 LineTo(cachedBackground, consoleLockGlyphRect.left + 22, consoleLockGlyphRect.top + 13);
   575:                 MoveToEx(cachedBackground, consoleLockGlyphRect.left + 11, consoleLockGlyphRect.top + 7, NULL);
   576:                 LineTo(cachedBackground, consoleLockGlyphRect.left + 13, consoleLockGlyphRect.top + 9);
   577:                 MoveToEx(cachedBackground, consoleLockGlyphRect.left + 18, consoleLockGlyphRect.top + 7, NULL);
   578:                 LineTo(cachedBackground, consoleLockGlyphRect.left + 16, consoleLockGlyphRect.top + 9);
   579: 
   580:                 if (pSettings->scalingFactorHiDPI > 2)
   581:                 {
   582:                     // Temporary lazy solution... (ie. for now just stretching the 2x glyph, with no further pixel fine-tuning like for 2x above)
   583:                     StretchBlt(cachedBackground, consoleLockGlyphRect.left, consoleLockGlyphRect.top, (15 * pSettings->scalingFactorHiDPI), (15 * pSettings->scalingFactorHiDPI), cachedBackground, consoleLockGlyphRect.left, consoleLockGlyphRect.top, 30, 30, SRCCOPY);
   584:                 }
   585:             }
   586: 
   587:             SelectObject(cachedBackground, oldPen);
   588:             DeleteObject(protectPen);
   589:         }
   590:     }
   591: 
   592:     //====================
   593: 
   594:     // Draw messages...
   595:     SetRect(&r, 0, 0, ConsoleWidth, ConsoleHeight);
   596: 
   597:     HFONT font = CreateFont(pSettings->Console->FontHeight, 0, 0, 0, pSettings->Console->FontWeight, false, false, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH|FF_DONTCARE, pSettings->Console->Font);
   598:     HGDIOBJ oldfont = SelectObject(cachedBackground, font);
   599:     SetBkMode(cachedBackground, TRANSPARENT);
   600: 
   601:     for (int i=0; i < (int)messageList.size(); i++) SetRectEmpty(&messageList[i]->r); // Flush any prior message RECTs to enable detection of clicks on currently visible messages only...
   602: 
   603:     int messagesToDraw;
   604:     if (pSettings->consoleMaximized) messagesToDraw = pSettings->consoleMaximizedMessages;
   605:     else messagesToDraw = pSettings->consoleRestoredMessages;
   606: 
   607:     if (messagesToDraw > 1)
   608:     {
   609:         if (!pSettings->consoleDesktopMode) InflateRect(&r, (-5 * pSettings->scalingFactorHiDPI), (-5 * pSettings->scalingFactorHiDPI));
   610:         r.bottom = r.top + consoleTrueFontHeight;
   611: 
   612:         int i = 0;
   613:         int mlSize = (int)messageList.size();
   614:         if (mlSize > messagesToDraw) i = mlSize - messagesToDraw; // Display latest received messages
   615:         for (i; i < (int)messageList.size(); i++)
   616:         {
   617:             DrawMessage(cachedBackground, r, messageList[i]);
   618:             CopyRect(&messageList[i]->r, &r);
   619:             OffsetRect(&r, 0, lineSpacing);
   620:         }
   621:     }
   622:     else
   623:     {
   624:         if (!pSettings->consoleDesktopMode) InflateRect(&r, (-5 * pSettings->scalingFactorHiDPI), 0);
   625:         r.top = (r.bottom - r.top - consoleTrueFontHeight) / 2;
   626:         r.bottom = r.top + consoleTrueFontHeight;
   627: 
   628:         int latestMsg = (int)messageList.size() - 1;
   629:         if (latestMsg >= 0)
   630:         {
   631:             DrawMessage(cachedBackground, r, messageList[latestMsg]);
   632:             CopyRect(&messageList[latestMsg]->r, &r);
   633:         }
   634:     }
   635: 
   636:     DeleteObject(SelectObject(cachedBackground, oldfont));
   637: 
   638:     //====================
   639: 
   640:     SetRect(&r, 0, 0, ConsoleWidth, ConsoleHeight);
   641: 
   642:     if (!pSettings->consoleDesktopMode)
   643:     {
   644:         // Apply per pixel alpha, selectively per placement, creating nice rounded corners... 8)
   645:         AlphaRect(cachedBackground, r, pSettings->consoleTransparencyAlpha);
   646: 
   647:         if (pSettings->consoleRoundedCorners)
   648:         {
   649:             switch (pSettings->ConsolePlacement.placement)
   650:             {
   651:                 /*
   652:                     // Original: Rounding all applicable corners
   653:                     case PLACEMENT_MANUAL: AlphaCorner(cachedBackground, r, CORNER_TOPLEFT | CORNER_TOPRIGHT | CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   654:                     case PLACEMENT_TOP_LEFT: AlphaCorner(cachedBackground, r, CORNER_BOTTOMRIGHT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   655:                     case PLACEMENT_CENTER_LEFT: AlphaCorner(cachedBackground, r, CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   656:                     case PLACEMENT_BOTTOM_LEFT: AlphaCorner(cachedBackground, r, CORNER_TOPRIGHT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   657:                     case PLACEMENT_TOP_CENTER: AlphaCorner(cachedBackground, r, CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   658:                     case PLACEMENT_CENTER_CENTER: AlphaCorner(cachedBackground, r, CORNER_TOPLEFT | CORNER_TOPRIGHT | CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   659:                     case PLACEMENT_BOTTOM_CENTER: AlphaCorner(cachedBackground, r, CORNER_TOPLEFT | CORNER_TOPRIGHT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   660:                     case PLACEMENT_TOP_RIGHT: AlphaCorner(cachedBackground, r, CORNER_BOTTOMLEFT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   661:                     case PLACEMENT_CENTER_RIGHT: AlphaCorner(cachedBackground, r, CORNER_TOPLEFT | CORNER_BOTTOMLEFT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   662:                     case PLACEMENT_BOTTOM_RIGHT: AlphaCorner(cachedBackground, r, CORNER_TOPLEFT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   663:                     default: AlphaCorner(cachedBackground, r, CORNER_TOPLEFT | CORNER_TOPRIGHT | CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha);
   664:                 */
   665:                 // Alternative: Rounding all applicable corners *except* the top right one since that's where the square-sized "window controls" are located
   666:                 case PLACEMENT_MANUAL: AlphaCorner(cachedBackground, r, CORNER_TOPLEFT | CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   667:                 case PLACEMENT_TOP_LEFT: AlphaCorner(cachedBackground, r, CORNER_BOTTOMRIGHT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   668:                 case PLACEMENT_CENTER_LEFT: AlphaCorner(cachedBackground, r, CORNER_BOTTOMRIGHT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   669:                 case PLACEMENT_BOTTOM_LEFT: AlphaCorner(cachedBackground, r, 0, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   670:                 case PLACEMENT_TOP_CENTER: AlphaCorner(cachedBackground, r, CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   671:                 case PLACEMENT_CENTER_CENTER: AlphaCorner(cachedBackground, r, CORNER_TOPLEFT | CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   672:                 case PLACEMENT_BOTTOM_CENTER: AlphaCorner(cachedBackground, r, CORNER_TOPLEFT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   673:                 case PLACEMENT_TOP_RIGHT: AlphaCorner(cachedBackground, r, CORNER_BOTTOMLEFT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   674:                 case PLACEMENT_CENTER_RIGHT: AlphaCorner(cachedBackground, r, CORNER_TOPLEFT | CORNER_BOTTOMLEFT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   675:                 case PLACEMENT_BOTTOM_RIGHT: AlphaCorner(cachedBackground, r, CORNER_TOPLEFT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha); break;
   676:                 default: AlphaCorner(cachedBackground, r, CORNER_TOPLEFT | CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT, 0, 0, 0, pSettings->Toolbar->borderWidth, 0, pSettings->consoleTransparencyAlpha);
   677:             }
   678:         }
   679:     }
   680:     else AlphaFromRGB(cachedBackground, r, 0, pSettings->consoleTransparencyAlpha, true, pSettings->desktopWidgetColor); // Console "desktop mode" -> Fully transparent console background, white text with varying transparency, etc
   681: 
   682:     AlphaApply(cachedBackground, r);
   683: 
   684:     //====================
   685: 
   686:     if (bufbmp) DeleteObject(bufbmp);
   687: 
   688:     POINT pt;
   689:     pt.x = ConsoleX, pt.y = ConsoleY;
   690:     POINT ptSrc;
   691:     ptSrc.x = 0, ptSrc.y = 0;
   692: 
   693:     BLENDFUNCTION bf;
   694:     bf.BlendOp = AC_SRC_OVER;
   695:     bf.BlendFlags = 0;
   696:     bf.AlphaFormat = AC_SRC_ALPHA;
   697:     bf.SourceConstantAlpha = (unsigned char)255;
   698: 
   699:     SIZE windowSize = {ConsoleWidth, ConsoleHeight};
   700:     BOOL result = UpdateLayeredWindow(hConsoleWnd, NULL, &pt, &windowSize, cachedBackground, &ptSrc, 0, &bf, ULW_ALPHA);
   701: /*
   702:     if (!cachedBackground) SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_INFORMATION_MESSAGE, (LPARAM)"xoblite -> Console -> Invalid cachedBackground!");
   703: 
   704:     if (result == 0)
   705:     {
   706:         int error = GetLastError();
   707:         char msg[255];
   708:         sprintf_s(msg, sizeofArray(msg), "xoblite -> Console -> UpdateLayeredWindow failed! [error code %d]", error);
   709:         SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_ERROR_MESSAGE, (LPARAM)msg);
   710:     }
   711: */
   712: 
   713:     // Finally, we move the console window to be just above the desktop in the z-order...
   714:     if (pDesktop) SetWindowPos(hConsoleWnd, pDesktop->hDesktopWnd, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOOWNERZORDER);
   715: }
   716: 
   717: //===========================================================================
   718: // Function: DrawMessage
   719: // Purpose: ...
   720: //===========================================================================
   721: 
   722: void Console::DrawMessage(HDC hdc, RECT r, messageListItem* mLI)
   723: {
   724:     RECT msgRect, tempRect;
   725:     CopyRect(&msgRect, &r);
   726: 
   727:     if (!pSettings->consoleDesktopMode)
   728:     {
   729:         if (IntersectRect(&tempRect, &msgRect, &consoleZoomButtonRect)) msgRect.right = consoleZoomButtonRect.left - (5 * pSettings->scalingFactorHiDPI);
   730:         if (pSettings->writeProtection && IntersectRect(&tempRect, &msgRect, &consoleLockGlyphRect)) msgRect.right = consoleLockGlyphRect.left - (5 * pSettings->scalingFactorHiDPI);
   731:     }
   732: 
   733:     COLORREF colorHighAlpha, colorMediumAlpha;
   734:     if (pSettings->consoleDesktopMode)
   735:     {
   736:         colorHighAlpha = RGB(pSettings->consoleTransparencyAlpha, pSettings->consoleTransparencyAlpha, pSettings->consoleTransparencyAlpha);
   737:         colorMediumAlpha = RGB(pSettings->consoleTransparencyAlpha/2, pSettings->consoleTransparencyAlpha/2, pSettings->consoleTransparencyAlpha/2);
   738:     }
   739: 
   740:     //====================
   741: 
   742:     if (mLI->type == CONSOLE_SEPARATOR)
   743:     {
   744:         int separatorOffset = (msgRect.bottom - msgRect.top) / 2;
   745:         msgRect.top = msgRect.top + separatorOffset - 1;
   746:         msgRect.bottom = msgRect.top + 2;
   747:         if (pSettings->doubleScaleHiDPI) InflateRect(&msgRect, 0, 1);
   748:         HBRUSH background;
   749:         if (pSettings->consoleDesktopMode) background = CreateSolidBrush(colorMediumAlpha);
   750: //      else background = CreateSolidBrush(pSettings->Console->TextColor);
   751:         else background = CreateSolidBrush(pSettings->MixColors(pSettings->Console->Color, pSettings->Console->TextColor));
   752:         FillRect(hdc, &msgRect, background);
   753:         DeleteObject(background);
   754:     }
   755: 
   756:     //====================
   757: 
   758:     else if (mLI->type == CONSOLE_REGULAR_MESSAGE || mLI->type == CONSOLE_PLAIN_MESSAGE)
   759:     {
   760:         InflateRect(&msgRect, -3, 0);
   761:         if (pSettings->consoleDesktopMode)
   762:         {
   763:             char timestamp[16];
   764: //          strncpy(timestamp, mLI->msg, 12);
   765:             strncpy_s(timestamp, sizeof(timestamp), mLI->msg, 12);
   766:             timestamp[12] = '\0';
   767:             DrawTextWithEffects(hdc, msgRect, mLI->msg, DT_LEFT | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE | DT_WORD_ELLIPSIS, colorHighAlpha, false, 0, false, 0, 0, 0);
   768:             DrawTextWithEffects(hdc, msgRect, timestamp, DT_LEFT | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE | DT_WORD_ELLIPSIS, colorMediumAlpha, false, 0, false, 0, 0, 0);
   769:         }
   770:         else DrawTextWithEffects(hdc, msgRect, mLI->msg, DT_LEFT | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE | DT_WORD_ELLIPSIS, pSettings->Console->TextColor, false, 0, false, 0, 0, 0);
   771: 
   772:         // Highlight clickable URL?
   773:         if (strnlen_s(mLI->url, sizeofArray(mLI->url)) > 0)
   774:         {
   775:             char msgWithoutURL[sizeof(mLI->msg)];
   776:             strcpy_s(msgWithoutURL, sizeofArray(msgWithoutURL), mLI->msg);
   777:             char* msgPtr = msgWithoutURL;
   778:             char* urlPtr = StrStrI(msgWithoutURL, "http://");
   779:             if (urlPtr == NULL) urlPtr = StrStrI(msgWithoutURL, "https://");
   780:             if (urlPtr == NULL) urlPtr = StrStrI(msgWithoutURL, "file://");
   781:             if (urlPtr != NULL)
   782:             {
   783:                 int n = urlPtr - msgPtr;
   784:                 msgWithoutURL[n] = '\0';
   785: 
   786:                 SIZE sizeMsg, sizeURL, sizeSpace;
   787:                 HDC fonthdc = CreateDC("DISPLAY", NULL, NULL, NULL);
   788:                 HFONT tempFont = CreateFont(pSettings->Console->FontHeight, 0, 0, 0, pSettings->Console->FontWeight, false, false, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH|FF_DONTCARE, pSettings->Console->Font);
   789:                 HGDIOBJ oldFont = SelectObject(fonthdc, tempFont);
   790:                 GetTextExtentPoint32(fonthdc, msgWithoutURL, strnlen_s(msgWithoutURL, sizeofArray(msgWithoutURL)), &sizeMsg);
   791:                 GetTextExtentPoint32(fonthdc, mLI->url, strnlen_s(mLI->url, sizeofArray(mLI->url)), &sizeURL);
   792:                 GetTextExtentPoint32(fonthdc, " ", 1, &sizeSpace);
   793:                 if (sizeSpace.cx & 1) sizeSpace.cx += 1; // Note: Even number of pixels -> Extra URL background width always divideable by two (see below)
   794:                 if (sizeSpace.cx < 4) sizeSpace.cx = 4;
   795:                 DeleteObject(SelectObject(fonthdc, oldFont));
   796:                 DeleteDC(fonthdc);
   797: 
   798:                 msgRect.left = msgRect.left + sizeMsg.cx - (sizeSpace.cx/2);
   799:                 msgRect.right = msgRect.left + sizeURL.cx + sizeSpace.cx;
   800: 
   801:                 // URLs are drawn with inverted colours...
   802:                 HBRUSH background;
   803:                 if (pSettings->consoleDesktopMode) background = CreateSolidBrush(colorHighAlpha);
   804:                 else background = CreateSolidBrush(pSettings->Console->TextColor);
   805:                 FillRect(hdc, &msgRect, background);
   806:                 DeleteObject(background);
   807:                 if (pSettings->consoleDesktopMode) DrawTextWithEffects(hdc, msgRect, mLI->url, DT_CENTER | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE | DT_WORD_ELLIPSIS, 0x000000, false, 0, false, 0, 0, 0);
   808:                 else DrawTextWithEffects(hdc, msgRect, mLI->url, DT_CENTER | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE | DT_WORD_ELLIPSIS, pSettings->Console->Color, false, 0, false, 0, 0, 0);
   809:             }
   810:         }
   811:     }
   812: 
   813:     //====================
   814: 
   815:     else
   816:     {
   817:         int msgHeight = msgRect.bottom - msgRect.top;
   818:         int iconSize = msgHeight - 2;
   819: //      if (iconSize > 16) iconSize = 16;
   820:         if (iconSize > 32) iconSize = 32;
   821: //      else if (iconSize > 16) iconSize = 16;
   822: 
   823:         // Draw message icon...
   824:         RECT iconRect;
   825:         if (mLI->type != CONSOLE_INDENTED_MESSAGE)
   826:         {
   827:             iconRect.left = msgRect.left + 4;
   828:             iconRect.top = msgRect.top + (msgHeight/2) - (iconSize/2);
   829:             iconRect.right = iconRect.left + iconSize;
   830:             iconRect.bottom = iconRect.top + iconSize;
   831: //          HICON bIcon = LoadIcon(NULL, mLI->icon);
   832: //          DrawIconEx(hdc, iconRect.left, iconRect.top, bIcon, iconSize, iconSize, 0, NULL, DI_NORMAL);
   833: //          DeleteObject(bIcon);
   834:             DrawIconEx(hdc, iconRect.left, iconRect.top, mLI->icon, iconSize, iconSize, 0, NULL, DI_NORMAL);
   835:         }
   836:         else if (mLI->type == CONSOLE_INDENTED_MESSAGE) iconRect.right = msgRect.left + (iconSize/2) + 1;
   837: 
   838:         // Draw message text...
   839:         msgRect.left = iconRect.right + 3;
   840:         if (pSettings->doubleScaleHiDPI) msgRect.left += 3;
   841:         msgRect.right -= 2;
   842:         if (pSettings->consoleDesktopMode) DrawTextWithEffects(hdc, msgRect, mLI->msg, DT_LEFT | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE | DT_EXPANDTABS | DT_WORD_ELLIPSIS, colorHighAlpha, false, 0, false, 0, 0, 0);
   843:         else DrawTextWithEffects(hdc, msgRect, mLI->msg, DT_LEFT | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE | DT_EXPANDTABS | DT_WORD_ELLIPSIS, pSettings->Console->TextColor, false, 0, false, 0, 0, 0);
   844:     }
   845: }
   846: 
   847: //===========================================================================
   848: // Function: GetDimensions
   849: // Purpose: ...
   850: //===========================================================================
   851: 
   852: void Console::GetDimensions()
   853: {
   854:     if (!pSettings->explorerHidden)
   855:     {
   856:         RECT workArea;
   857:         SystemParametersInfo(SPI_GETWORKAREA, 0, (PVOID)&workArea, SPIF_SENDCHANGE);
   858:         ScreenWidth = workArea.right - workArea.left;
   859:         ScreenHeight = workArea.bottom - workArea.top;
   860:     }
   861:     else
   862:     {
   863:         ScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
   864:         ScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
   865:     }
   866: 
   867:     //====================
   868: 
   869:     // First we need to check the *real* size of the font as some style
   870:     // authors use non-matching font heights with bitmap fonts just to
   871:     // tweak the appearance of the toolbar (e.g. to shrink its size)
   872:     SIZE size;
   873:     HDC fonthdc = CreateDC("DISPLAY", NULL, NULL, NULL);
   874:     HFONT tempFont = CreateFont(pSettings->Console->FontHeight, 0, 0, 0, pSettings->Console->FontWeight, false, false, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH|FF_DONTCARE, pSettings->Console->Font);
   875:     HGDIOBJ oldFont = SelectObject(fonthdc, tempFont);
   876:     GetTextExtentPoint32(fonthdc, "TQkgfp/", 8, &size);
   877:     consoleTrueFontHeight = size.cy + 1;
   878:     if (consoleTrueFontHeight < pSettings->Console->FontHeight) consoleTrueFontHeight = pSettings->Console->FontHeight;
   879:     DeleteObject(SelectObject(fonthdc, oldFont));
   880:     DeleteDC(fonthdc);
   881: 
   882:     lineSpacing = consoleTrueFontHeight + 2;
   883: //  if (pSettings->doubleScaleHiDPI) lineSpacing += 2;
   884:     lineSpacing += (2 * pSettings->scalingFactorHiDPI);
   885: 
   886:     //====================
   887: 
   888:     if (pSettings->consoleMaximized) // Console window size "maximized" (nb. each state's #rows etc fully configurable though)
   889:     {
   890:         if (pSettings->consoleMaximizedWidth > 0)
   891:         {
   892:             if (pSettings->consoleMaximizedWidth > 100) ConsoleWidth = pSettings->consoleMaximizedWidth;
   893:             else ConsoleWidth = (ScreenWidth * pSettings->consoleMaximizedWidth) / 100;
   894:         }
   895:         else if (pSettings->consoleMaximizedWidth < 0) ConsoleWidth = ScreenWidth + pSettings->consoleMaximizedWidth;
   896:         else ConsoleWidth = ScreenWidth - 10; // Default used when consoleMaximizedWidth == 0
   897: 
   898:         if (pSettings->consoleMaximizedMessages == 1)
   899:         {
   900:             if (!pSettings->consoleDesktopMode)
   901:             {
   902: /*
   903:                 ConsoleHeight = consoleTrueFontHeight + 10;
   904:                 if (pSettings->doubleScaleHiDPI)
   905:                 {
   906:                     ConsoleHeight += 10;
   907:                     if (ConsoleHeight & 1) ConsoleHeight++;
   908:                 }
   909:                 else
   910:                 {
   911:                     ConsoleHeight = ConsoleHeight | 1;
   912:                     if (ConsoleHeight < 25) ConsoleHeight = 25;
   913:                 }
   914: */
   915:                 ConsoleHeight = consoleTrueFontHeight + (10 * pSettings->scalingFactorHiDPI);
   916:                 if ((pSettings->scalingFactorHiDPI == 2) || (pSettings->scalingFactorHiDPI == 4))
   917:                 {
   918:                     if (ConsoleHeight & 1) ConsoleHeight++;
   919:                 }
   920:                 else // if ((pSettings->scalingFactorHiDPI == 1) || (pSettings->scalingFactorHiDPI == 3))
   921:                 {
   922:                     ConsoleHeight = ConsoleHeight | 1;
   923:                     if (ConsoleHeight < 25) ConsoleHeight = 25;
   924:                 }
   925:             }
   926:             else ConsoleHeight = consoleTrueFontHeight;
   927:         }
   928:         else
   929:         {
   930:             if (!pSettings->consoleDesktopMode)
   931:             {
   932: //              ConsoleHeight = (lineSpacing * pSettings->consoleMaximizedMessages) + 8 + pSettings->consolePadding; // Padding 5+5 pixels minus 2 pixels from lineSpacing
   933: //              if (pSettings->doubleScaleHiDPI) ConsoleHeight += 8;  // Padding 10+10 pixels minus 4 pixels from lineSpacing
   934:                 ConsoleHeight = (lineSpacing * pSettings->consoleMaximizedMessages) + (10 * pSettings->scalingFactorHiDPI) + pSettings->consolePadding - (2 * pSettings->scalingFactorHiDPI);;
   935:             }
   936:             else
   937:             {
   938:                 ConsoleHeight = (lineSpacing * pSettings->consoleMaximizedMessages);
   939: //              if (pSettings->doubleScaleHiDPI) ConsoleHeight -= 4; // No padding and minus 4 pixels from lineSpacing
   940: //              else ConsoleHeight -= 2; // No padding and minus 2 pixels from lineSpacing
   941:                 ConsoleHeight -= (2 * pSettings->scalingFactorHiDPI);
   942:             }
   943:         }
   944:     }
   945:     else // Console window size "restored" (nb. each state's #rows etc fully configurable though)
   946:     {
   947:         if (pSettings->consoleRestoredWidth > 0)
   948:         {
   949:             if (pSettings->consoleRestoredWidth > 100) ConsoleWidth = pSettings->consoleRestoredWidth;
   950:             else ConsoleWidth = (ScreenWidth * pSettings->consoleRestoredWidth) / 100;
   951:         }
   952:         else if (pSettings->consoleRestoredWidth < 0) ConsoleWidth = ScreenWidth + pSettings->consoleRestoredWidth;
   953:         else ConsoleWidth = ScreenWidth / 3; // Default used when consoleRestoredWidth == 0
   954: 
   955:         if (pSettings->consoleRestoredMessages == 1)
   956:         {
   957:             if (!pSettings->consoleDesktopMode)
   958:             {
   959: /*
   960:                 ConsoleHeight = consoleTrueFontHeight + 10;
   961:                 if (pSettings->doubleScaleHiDPI)
   962:                 {
   963:                     ConsoleHeight += 10;
   964:                     if (ConsoleHeight & 1) ConsoleHeight++;
   965:                 }
   966:                 else
   967:                 {
   968:                     ConsoleHeight = ConsoleHeight | 1;
   969:                     if (ConsoleHeight < 25) ConsoleHeight = 25;
   970:                 }
   971: */
   972:                 ConsoleHeight = consoleTrueFontHeight + (10 * pSettings->scalingFactorHiDPI);
   973:                 if ((pSettings->scalingFactorHiDPI == 2) || (pSettings->scalingFactorHiDPI == 4))
   974:                 {
   975:                     if (ConsoleHeight & 1) ConsoleHeight++;
   976:                 }
   977:                 else // if ((pSettings->scalingFactorHiDPI == 1) || (pSettings->scalingFactorHiDPI == 3))
   978:                 {
   979:                     ConsoleHeight = ConsoleHeight | 1;
   980:                     if (ConsoleHeight < 25) ConsoleHeight = 25;
   981:                 }
   982:             }
   983:             else ConsoleHeight = consoleTrueFontHeight;
   984:         }
   985:         else
   986:         {
   987:             if (!pSettings->consoleDesktopMode)
   988:             {
   989: //              ConsoleHeight = (lineSpacing * pSettings->consoleRestoredMessages) + 8 + pSettings->consolePadding; // Padding 5+5 pixels minus 2 pixels padding from last lineSpacing
   990: //              if (pSettings->doubleScaleHiDPI) ConsoleHeight += 8;  // Padding 10+10 pixels minus 4 pixels padding from last lineSpacing
   991:                 ConsoleHeight = (lineSpacing * pSettings->consoleRestoredMessages) + (10 * pSettings->scalingFactorHiDPI) + pSettings->consolePadding - (2 * pSettings->scalingFactorHiDPI);;
   992:             }
   993:             else
   994:             {
   995:                 ConsoleHeight = (lineSpacing * pSettings->consoleRestoredMessages);
   996: //              if (pSettings->doubleScaleHiDPI) ConsoleHeight -= 4; // No padding and minus 4 pixels from lineSpacing
   997: //              else ConsoleHeight -= 2; // No padding and minus 2 pixels from lineSpacing
   998:                 ConsoleHeight -= (2 * pSettings->scalingFactorHiDPI);
   999:             }
  1000:         }
  1001:     }
  1002: 
  1003:     if (ConsoleWidth < 300) ConsoleWidth = 300; // Minimum console width is 300 pixels
  1004: 
  1005:     //====================
  1006: 
  1007:     if ((pSettings->consoleMaximized && (pSettings->consoleMaximizedMessages > 1)) || (!pSettings->consoleMaximized && (pSettings->consoleRestoredMessages > 1)))
  1008:     {
  1009:         SetRect(&consoleZoomButtonRect, ConsoleWidth - (20 * pSettings->scalingFactorHiDPI), (5 * pSettings->scalingFactorHiDPI), ConsoleWidth - (5 * pSettings->scalingFactorHiDPI), (20 * pSettings->scalingFactorHiDPI));
  1010:     }
  1011:     else
  1012:     {
  1013:         int padding = ((ConsoleHeight - (15 * pSettings->scalingFactorHiDPI)) / 2);
  1014:         SetRect(&consoleZoomButtonRect, ConsoleWidth - (20 * pSettings->scalingFactorHiDPI), padding, ConsoleWidth - (5 * pSettings->scalingFactorHiDPI), padding + (15 * pSettings->scalingFactorHiDPI));
  1015:     }
  1016: 
  1017:     CopyRect(&consoleLockGlyphRect, &consoleZoomButtonRect);
  1018:     OffsetRect(&consoleLockGlyphRect, (-20 * pSettings->scalingFactorHiDPI), 0);
  1019: 
  1020:     //====================
  1021: 
  1022:     pSettings->ConsolePlacement.width = ConsoleWidth;
  1023:     pSettings->ConsolePlacement.height = ConsoleHeight;
  1024: 
  1025:     pSettings->PositionFromPlacement(&pSettings->ConsolePlacement);
  1026: 
  1027:     ConsoleX = pSettings->ConsolePlacement.x;
  1028:     ConsoleY = pSettings->ConsolePlacement.y;
  1029: }
  1030: 
  1031: //===========================================================================
  1032: // Function: UpdatePosition
  1033: // Purpose: ...
  1034: //===========================================================================
  1035: 
  1036: void Console::UpdatePosition()
  1037: {
  1038:     // Get the new size and position for our window...
  1039:     GetDimensions();
  1040:     // Update the console window...
  1041:     UpdateConsoleWindow();
  1042: }
  1043: 
  1044: //===========================================================================
  1045: // Function: ToggleMaximized
  1046: // Purpose: ...
  1047: //===========================================================================
  1048: 
  1049: void Console::ToggleMaximized()
  1050: {
  1051:     if (pSettings->consoleMaximized) pSettings->consoleMaximized = false;
  1052:     else pSettings->consoleMaximized = true;
  1053: 
  1054:     // Resize the console window... (restored<->maximized)
  1055:     UpdatePosition();
  1056:     PlaySoundFX(SFX_TOGGLE_ELEMENT);
  1057: 
  1058:     // Save setting to xoblite.rc...
  1059:     WriteBool(pSettings->xobrcFile, "xoblite.console.maximized:", pSettings->consoleMaximized);
  1060: }
  1061: 
  1062: //===========================================================================
  1063: // Function: BlockMessage
  1064: // Purpose: ...
  1065: //===========================================================================
  1066: 
  1067: void Console::BlockMessage(char* message)
  1068: {
  1069:     strcpy_s(messageToBlock, sizeofArray(messageToBlock), message);
  1070: }
  1071: 
  1072: //===========================================================================
  1073: // Function: ClearHistory
  1074: // Purpose: ...
  1075: //===========================================================================
  1076: 
  1077: void Console::ClearHistory(bool redraw)
  1078: {
  1079:     // Clear all elements in the messageList...
  1080:     for (int i=0; i<(int)messageList.size(); i++)
  1081:     {
  1082:         if (messageList[i]->icon != NULL) DeleteObject(&messageList[i]->icon);
  1083:         delete messageList[i];
  1084:     }
  1085:     messageList.clear();
  1086: //  ZeroMemory(&messageList, sizeof(messageList));
  1087: 
  1088:     // Update the console window...
  1089:     if (redraw) UpdatePosition();
  1090: }
  1091: 
  1092: //===========================================================================
  1093: // Function: FullscreenDetected
  1094: // Purpose: Hide the console (no updating) if an application goes fullscreen
  1095: //===========================================================================
  1096: 
  1097: void Console::FullscreenDetected(bool fullscreen)
  1098: {
  1099:     if (fullscreen)
  1100:     {
  1101:         fullscreenDetected = true;
  1102:         ShowWindow(hConsoleWnd, SW_HIDE);
  1103:     }
  1104:     else
  1105:     {
  1106:         fullscreenDetected = false;
  1107:         if (!pSettings->consoleHidden) ShowWindow(hConsoleWnd, SW_SHOWNOACTIVATE);
  1108:         UpdateConsoleWindow();
  1109:     }
  1110: }
  1111: 
  1112: //===========================================================================
  1113: