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 "Hotkeys.h" 
    32: 
    33: extern Settings* pSettings;
    34: 
    35: //===========================================================================
    36: 
    37: Hotkeys::Hotkeys(HINSTANCE hInstance)
    38: {
    39:     ZeroMemory(&hotkeyList, sizeof(hotkeyList));
    40:     RegisterHotkeys();
    41: }
    42: 
    43: Hotkeys::~Hotkeys()
    44: {
    45:     UnregisterHotkeys();
    46: }
    47: 
    48: //===========================================================================
    49: 
    50: void Hotkeys::RegisterHotkeys()
    51: {
    52:     char temp[MAX_LINE_LENGTH], validator[MAX_LINE_LENGTH], mods[MAX_LINE_LENGTH], key[MAX_LINE_LENGTH], cmd[MAX_LINE_LENGTH], args[MAX_LINE_LENGTH];
    53:     LPSTR   tokens[4];
    54:     tokens[0] = validator;
    55:     tokens[1] = mods;
    56:     tokens[2] = key;
    57:     tokens[3] = cmd;
    58: 
    59:     id = 0, hotkeysRegistered = 0;
    60: 
    61:     // Register a default hard-coded "Quit" hotkey -> CtrlWinAlt Escape
    62: //  if (ParseHotkey("CtrlWinAlt", "Esc", "@xoblite Quit", "")) hotkeysRegistered++;
    63: 
    64:     // Parse the hotkeys configuration file... (default path -> hotkeys.rc in the main .exe folder)
    65:     FILE* fp = FileOpen(pSettings->hotkeysFile);
    66:     if (fp)
    67:     {
    68:         while (ReadNextCommand(fp, temp, sizeof(temp)))
    69:         {
    70:             if (temp[0] == '!') continue; // Skip comment lines
    71: 
    72:             validator[0] = mods[0] = key[0] = cmd[0] = args[0] = '\0';
    73:             int count = BBTokenize(temp, tokens, 4, args);
    74: 
    75:             if (count == 4 && (!_stricmp(validator, "*Hotkey")))
    76:             {
    77:                 if (strchr(cmd, ' ')) // Does the command include spaces?
    78:                 {
    79:                     // Since enclosing quotes are removed by BBTokenize,
    80:                     // we need to add them back if the command includes spaces...
    81:                     strcpy_s(temp, sizeofArray(temp), cmd);
    82:                     sprintf_s(cmd, sizeofArray(cmd), "\"%s\"", temp);
    83:                 }
    84:                 if (ParseHotkey(mods, key, cmd, args, true)) hotkeysRegistered++;
    85:             }
    86:         }
    87:     }
    88:     FileClose(fp);
    89: 
    90:     char msg[256];
    91: //  sprintf_s(msg, sizeofArray(msg), "Creating hotkeys -> %d hotkeys registered.", hotkeysRegistered);
    92:     sprintf_s(msg, sizeofArray(msg), "Loading hotkeys -> %d hotkeys registered.", hotkeysRegistered);
    93:     SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_REGULAR_MESSAGE, (LPARAM)msg);
    94: 
    95:     //====================
    96: 
    97:     // Finally, we add a default ### Ctrl+Alt+Win Space ### hotkey shortcut to the xoblite run box,
    98:     // if this key combination has not already been assigned to something by the user...
    99:     ParseHotkey("CtrlAltWin", "Space", "@xoblite Run", "", false);
   100: }
   101: 
   102: //===========================================================================
   103: 
   104: void Hotkeys::UnregisterHotkeys()
   105: {
   106:     hotkeysUnregistered = 0;
   107: 
   108:     for (int i=0; i<(int)hotkeyList.size(); i++)
   109:     {
   110:         UnregisterHotKey(GetBBWnd(), hotkeyList[i]->ID);
   111:         GlobalDeleteAtom(hotkeyList[i]->ID);
   112:         delete hotkeyList[i];
   113:         hotkeysUnregistered++;
   114:     }
   115:     hotkeyList.clear();
   116: /*
   117:     char msg[256];
   118:     sprintf_s(msg, sizeofArray(msg), "Destroying hotkeys -> %d hotkeys unregistered.", hotkeysUnregistered);
   119:     SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_REGULAR_MESSAGE, (LPARAM)msg);
   120: */
   121: }
   122: 
   123: //===========================================================================
   124: 
   125: bool Hotkeys::ParseHotkey(char mods[MAX_LINE_LENGTH], char key[MAX_LINE_LENGTH], char cmd[MAX_LINE_LENGTH], char args[MAX_LINE_LENGTH], bool reportErrors)
   126: {
   127:     unsigned int keyCode = 0x00;
   128: 
   129:     // Below: Virtual key codes as per https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
   130: 
   131:     if (strlen(key) == 1) keyCode = key[0]; // A single character key string -> Regular keys (i.e. A-Z, 0-9)
   132: 
   133:     else if (!_stricmp(key, "Space") || !_stricmp(key, "Spacebar")) keyCode = VK_SPACE;
   134: 
   135:     else if (!_stricmp(key, "Plus")) keyCode = VK_OEM_PLUS; // +
   136:     else if (!_stricmp(key, "Comma")) keyCode = VK_OEM_COMMA; // ,
   137:     else if (!_stricmp(key, "Minus")) keyCode = VK_OEM_MINUS; // -
   138:     else if (!_stricmp(key, "Period")) keyCode = VK_OEM_PERIOD; // .
   139: 
   140:     else if (!_stricmp(key, "OEM1")) keyCode = VK_OEM_1; // Locale specific characters, e.g. US ;: key, or Swedish ¨^~ key.
   141:     else if (!_stricmp(key, "OEM2")) keyCode = VK_OEM_2; // Locale specific characters, e.g. US /? key, or Swedish '* key.
   142:     else if (!_stricmp(key, "OEM3")) keyCode = VK_OEM_3; // Locale specific characters, e.g. US `~ key, or Swedish öÖ key.
   143:     else if (!_stricmp(key, "OEM4")) keyCode = VK_OEM_4; // Locale specific characters, e.g. US [{ key, or Swedish ´` key.
   144:     else if (!_stricmp(key, "OEM5")) keyCode = VK_OEM_5; // Locale specific characters, e.g. US \| key, or Swedish §½ key.
   145:     else if (!_stricmp(key, "OEM6")) keyCode = VK_OEM_6; // Locale specific characters, e.g. US ]} key, or Swedish åÅ key.
   146:     else if (!_stricmp(key, "OEM7")) keyCode = VK_OEM_7; // Locale specific characters, e.g. US single-quote/double-quote key, or Swedish äÄ key.
   147:     else if (!_stricmp(key, "OEM8")) keyCode = VK_OEM_8; // Locale specific characters, e.g. ...?
   148:     else if (!_stricmp(key, "OEM102")) keyCode = VK_OEM_102; // Locale specific characters, e.g. Swedish <>| key.
   149: 
   150:     else if (!_stricmp(key, "Enter")) keyCode = VK_RETURN;
   151:     else if (!_stricmp(key, "Num0")) keyCode = VK_NUMPAD0;
   152:     else if (!_stricmp(key, "Num1")) keyCode = VK_NUMPAD1;
   153:     else if (!_stricmp(key, "Num2")) keyCode = VK_NUMPAD2;
   154:     else if (!_stricmp(key, "Num3")) keyCode = VK_NUMPAD3;
   155:     else if (!_stricmp(key, "Num4")) keyCode = VK_NUMPAD4;
   156:     else if (!_stricmp(key, "Num5")) keyCode = VK_NUMPAD5;
   157:     else if (!_stricmp(key, "Num6")) keyCode = VK_NUMPAD6;
   158:     else if (!_stricmp(key, "Num7")) keyCode = VK_NUMPAD7;
   159:     else if (!_stricmp(key, "Num8")) keyCode = VK_NUMPAD8;
   160:     else if (!_stricmp(key, "Num9")) keyCode = VK_NUMPAD9;
   161:     else if (!_stricmp(key, "Mul")) keyCode = VK_MULTIPLY;
   162:     else if (!_stricmp(key, "Div")) keyCode = VK_DIVIDE;
   163:     else if (!_stricmp(key, "Add")) keyCode = VK_ADD;
   164:     else if (!_stricmp(key, "Sub")) keyCode = VK_SUBTRACT;
   165:     else if (!_stricmp(key, "Dec")) keyCode = VK_DECIMAL;
   166: 
   167:     else if (!_stricmp(key, "F1")) keyCode = VK_F1;
   168:     else if (!_stricmp(key, "F2")) keyCode = VK_F2;
   169:     else if (!_stricmp(key, "F3")) keyCode = VK_F3;
   170:     else if (!_stricmp(key, "F4")) keyCode = VK_F4;
   171:     else if (!_stricmp(key, "F5")) keyCode = VK_F5;
   172:     else if (!_stricmp(key, "F6")) keyCode = VK_F6;
   173:     else if (!_stricmp(key, "F7")) keyCode = VK_F7;
   174:     else if (!_stricmp(key, "F8")) keyCode = VK_F8;
   175:     else if (!_stricmp(key, "F9")) keyCode = VK_F9;
   176:     else if (!_stricmp(key, "F10")) keyCode = VK_F10;
   177:     else if (!_stricmp(key, "F11")) keyCode = VK_F11;
   178:     else if (!_stricmp(key, "F12")) keyCode = VK_F12;
   179:     else if (!_stricmp(key, "F13")) keyCode = VK_F13;
   180:     else if (!_stricmp(key, "F14")) keyCode = VK_F14;
   181:     else if (!_stricmp(key, "F15")) keyCode = VK_F15;
   182:     else if (!_stricmp(key, "F16")) keyCode = VK_F16;
   183:     else if (!_stricmp(key, "F17")) keyCode = VK_F17;
   184:     else if (!_stricmp(key, "F18")) keyCode = VK_F18;
   185:     else if (!_stricmp(key, "F19")) keyCode = VK_F19;
   186:     else if (!_stricmp(key, "F20")) keyCode = VK_F20;
   187:     else if (!_stricmp(key, "F21")) keyCode = VK_F21;
   188:     else if (!_stricmp(key, "F22")) keyCode = VK_F22;
   189:     else if (!_stricmp(key, "F23")) keyCode = VK_F23;
   190:     else if (!_stricmp(key, "F24")) keyCode = VK_F24;
   191: 
   192:     else if (!_stricmp(key, "Left")) keyCode = VK_LEFT;
   193:     else if (!_stricmp(key, "Right")) keyCode = VK_RIGHT;
   194:     else if (!_stricmp(key, "Down")) keyCode = VK_DOWN;
   195:     else if (!_stricmp(key, "Up")) keyCode = VK_UP;
   196: 
   197:     else if (!_stricmp(key, "Insert")) keyCode = VK_INSERT;
   198:     else if (!_stricmp(key, "Delete")) keyCode = VK_DELETE;
   199:     else if (!_stricmp(key, "Home")) keyCode = VK_HOME;
   200:     else if (!_stricmp(key, "End")) keyCode = VK_END;
   201:     else if (!_stricmp(key, "PageUp")) keyCode = VK_PRIOR;
   202:     else if (!_stricmp(key, "PageDown")) keyCode = VK_NEXT;
   203: 
   204:     else if (!_stricmp(key, "PrtScn")) keyCode = VK_SNAPSHOT;
   205:     else if (!_stricmp(key, "Pause")) keyCode = VK_PAUSE;
   206:     else if (!_stricmp(key, "Esc") || !_stricmp(key, "Escape")) keyCode = VK_ESCAPE;
   207:     else if (!_stricmp(key, "Tab")) keyCode = VK_TAB;
   208:     else if (!_stricmp(key, "Backspace")) keyCode = VK_BACK;
   209:     else if (!_stricmp(key, "Apps") || !_stricmp(key, "Menu")) keyCode = VK_APPS;
   210: /*
   211:     // ...some keys available on extended keyboards...
   212:     // (see https://docs.microsoft.com/en-us/cpp/windows/predefined-accelerator-keys?view=vs-2019 )
   213: 
   214:     else if (!_stricmp(key, "xBack")) keyCode = VK_BROWSER_BACK;
   215:     else if (!_stricmp(key, "xForward")) keyCode = VK_BROWSER_FORWARD;
   216:     else if (!_stricmp(key, "xStop")) keyCode = VK_BROWSER_STOP;
   217:     else if (!_stricmp(key, "xRefresh")) keyCode = VK_BROWSER_REFRESH;
   218:     else if (!_stricmp(key, "xSearch")) keyCode = VK_BROWSER_SEARCH;
   219:     else if (!_stricmp(key, "xFav")) keyCode = VK_BROWSER_FAVORITES;
   220:     else if (!_stricmp(key, "xWebHome")) keyCode = VK_BROWSER_HOME;
   221: 
   222:     else if (!_stricmp(key, "xVolUp")) keyCode = VK_VOLUME_DOWN;
   223:     else if (!_stricmp(key, "xVolDown")) keyCode = VK_VOLUME_UP;
   224:     else if (!_stricmp(key, "xVolMute")) keyCode = VK_VOLUME_MUTE;
   225: 
   226:     else if (!_stricmp(key, "xPlayPause")) keyCode = VK_MEDIA_PLAY_PAUSE;
   227:     else if (!_stricmp(key, "xStop")) keyCode = VK_MEDIA_STOP;
   228:     else if (!_stricmp(key, "xPrev")) keyCode = VK_MEDIA_PREV_TRACK;
   229:     else if (!_stricmp(key, "xNext")) keyCode = VK_MEDIA_NEXT_TRACK;
   230:     else if (!_stricmp(key, "xMedia")) keyCode = VK_LAUNCH_MEDIA_SELECT;
   231: 
   232:     else if (!_stricmp(key, "xMail")) keyCode = VK_LAUNCH_MAIL;
   233:     else if (!_stricmp(key, "xApp1")) keyCode = VK_LAUNCH_APP1;
   234:     else if (!_stricmp(key, "xApp2")) keyCode = VK_LAUNCH_APP2;
   235: 
   236:     else if (!_stricmp(key, "xSleep")) keyCode = VK_SLEEP;
   237: 
   238:     // ...and a couple of keys I don't have on my keyboard at least, but here goes anyway...!?
   239:     // (also listed at https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes )
   240:     else if (!_stricmp(key, "Clear")) keyCode = VK_CLEAR;
   241:     else if (!_stricmp(key, "Select")) keyCode = VK_SELECT;
   242:     else if (!_stricmp(key, "Print")) keyCode = VK_PRINT;
   243:     else if (!_stricmp(key, "Execute")) keyCode = VK_EXECUTE;
   244:     else if (!_stricmp(key, "Help")) keyCode = VK_HELP;
   245:     else if (!_stricmp(key, "Separator")) keyCode = VK_SEPARATOR;
   246:     else if (!_stricmp(key, "Attn")) keyCode = VK_ATTN;
   247:     else if (!_stricmp(key, "CrSel")) keyCode = VK_CRSEL;
   248:     else if (!_stricmp(key, "ExSel")) keyCode = VK_EXSEL;
   249:     else if (!_stricmp(key, "Play")) keyCode = VK_PLAY;
   250:     else if (!_stricmp(key, "Zoom")) keyCode = VK_ZOOM;
   251:     else if (!_stricmp(key, "OEMClear")) keyCode = VK_OEM_CLEAR;
   252: */
   253:     else return false; // -> Invalid key!
   254: 
   255:     //====================
   256: 
   257:     hotkeyItem* hotkey = new hotkeyItem;
   258: 
   259:     hotkey->mods = 0;
   260:     if (IsInString(mods, "Win")) hotkey->mods |= MOD_WIN;
   261:     if (IsInString(mods, "Ctrl")) hotkey->mods |= MOD_CONTROL;
   262:     if (IsInString(mods, "Alt")) hotkey->mods |= MOD_ALT;
   263:     if (IsInString(mods, "Shift")) hotkey->mods |= MOD_SHIFT;
   264:     hotkey->mods |= MOD_NOREPEAT; // -> Keyboard auto-repeat does not yield multiple hotkey notifications
   265: 
   266:     hotkey->key = keyCode;
   267:     hotkey->cmd = _strdup(cmd);
   268:     hotkey->args = _strdup(args);
   269:     hotkey->keyAsString = _strdup(key);
   270: 
   271:     // Assign a unique identifier to the new hotkey...
   272:     char atomname[64];
   273:     sprintf_s(atomname, sizeofArray(atomname), "BBHotkey%d", id);
   274:     hotkey->ID = GlobalAddAtom(atomname);
   275:     if (hotkey->ID == 0)
   276:     {
   277:         // ...adding identifier failed! :(
   278:         delete hotkey;
   279: 
   280:         if (reportErrors)
   281:         {
   282:             char msg[256];
   283:             sprintf_s(msg, sizeofArray(msg), "Error adding atom for hotkey %s %s !", mods, key);
   284:             SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_ERROR_MESSAGE, (LPARAM)msg);
   285:         }
   286:         return false;
   287:     }
   288: 
   289:     //====================
   290: 
   291:     // Register the new hotkey...
   292:     if (!RegisterHotKey(GetBBWnd(), hotkey->ID, hotkey->mods, (int)hotkey->key))
   293:     {
   294:         // ...registering the new hotkey failed! :(
   295:         GlobalDeleteAtom(hotkey->ID);
   296:         delete hotkey;
   297: 
   298:         if (reportErrors)
   299:         {
   300:             char msg[256];
   301:             sprintf_s(msg, sizeofArray(msg), "Error registering hotkey %s %s !", mods, key);
   302:             SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_ERROR_MESSAGE, (LPARAM)msg);
   303:         }
   304: 
   305:         return false;
   306:     }
   307:     else
   308:     {
   309:         // ...registering the new hotkey succeeded! :D
   310:         id++;
   311:         hotkeyList.push_back(hotkey);
   312: 
   313:         return true;
   314:     }
   315: }
   316: 
   317: //===========================================================================
   318: 
   319: void Hotkeys::ExecuteHotkey(int id)
   320: {
   321:     for (int i = 0; i < (int)hotkeyList.size(); i++)
   322:     {
   323:         if (hotkeyList[i]->ID == id)
   324:         {
   325:             char cmdPlusArgs[MAX_LINE_LENGTH];
   326:             sprintf_s(cmdPlusArgs, sizeofArray(cmdPlusArgs), "%s %s", hotkeyList[i]->cmd, hotkeyList[i]->args);
   327:             pSettings->Statistics.usedAHotkey++; // Update statistics... (nb. we do this before executing the hotkey, as the latter is typically used to trigger the @xoblite Statistics...)
   328:             BBSmartExecute(cmdPlusArgs);
   329:             if (!_strnicmp(cmdPlusArgs, "@xoblite Menu Show", 18)) PlaySoundFX(SFX_MENU_SHOW);
   330:             else PlaySoundFX(SFX_HOTKEY);
   331:         }
   332:     }
   333: }
   334: 
   335: //===========================================================================
   336: