Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion indra/llui/llmenugl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,15 @@ bool LLMenuItemGL::addToAcceleratorList(std::list <LLMenuKeyboardBinding*> *list
// the current accelerator key and mask to the provided string.
void LLMenuItemGL::appendAcceleratorString( std::string& st ) const
{
st = LLKeyboard::stringFromAccelerator( mAcceleratorMask, mAcceleratorKey );
st = LLKeyboard::stringFromAccelerator(mAcceleratorMask);
std::string key_string = LLKeyboard::stringFromAcceleratorMenuKey(mAcceleratorKey);
if ((mAcceleratorMask & MASK_NORMALKEYS) &&
!key_string.empty() &&
(key_string[0] == '-' || key_string[0] == '=' || key_string[0] == '+'))
{
st.append(" ");
}
st.append(key_string);
LL_DEBUGS("HotKeys") << "appendAcceleratorString: " << st << LL_ENDL;
}

Expand Down
16 changes: 16 additions & 0 deletions indra/llwindow/llkeyboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,22 @@ std::string LLKeyboard::stringFromKey(KEY key, bool translate)
return res;
}

// static
std::string LLKeyboard::stringFromAcceleratorMenuKey(KEY key, bool translate)
{
if (gKeyboard != NULL)
{
return gKeyboard->stringFromAcceleratorMenuKeyImpl(key, translate);
}

return stringFromKey(key, translate);
}
Comment on lines +372 to +380
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LLKeyboard::stringFromAcceleratorMenuKey() checks gKeyboard != NULL, but in the gKeyboard == NULL path it calls stringFromKey(key, translate), which (when translate is true) dereferences gKeyboard->mStringTranslator and can still crash. Consider returning stringFromKey(key, /*translate=*/false) here (or otherwise avoiding any gKeyboard access) so the NULL-guard is actually effective.

Copilot uses AI. Check for mistakes.

std::string LLKeyboard::stringFromAcceleratorMenuKeyImpl(KEY key, bool translate)
{
return stringFromKey(key, translate);
}

//static
std::string LLKeyboard::stringFromMouse(EMouseClickType click, bool translate)
{
Expand Down
2 changes: 2 additions & 0 deletions indra/llwindow/llkeyboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class LLKeyboard
static bool maskFromString(const std::string& str, MASK *mask); // False on failure
static bool keyFromString(const std::string& str, KEY *key); // False on failure
static std::string stringFromKey(KEY key, bool translate = true);
static std::string stringFromAcceleratorMenuKey(KEY key, bool translate = true);
static std::string stringFromMouse(EMouseClickType click, bool translate = true);
static std::string stringFromAccelerator( MASK accel_mask ); // separated for convinience, returns with "+": "Shift+" or "Shift+Alt+"...
static std::string stringFromAccelerator( MASK accel_mask, KEY key );
Expand All @@ -108,6 +109,7 @@ class LLKeyboard

protected:
void addKeyName(KEY key, const std::string& name);
virtual std::string stringFromAcceleratorMenuKeyImpl(KEY key, bool translate);

protected:
std::map<U16, KEY> mTranslateKeyMap; // Map of translations from OS keys to Linden KEYs
Expand Down
42 changes: 42 additions & 0 deletions indra/llwindow/llkeyboardwin32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,46 @@ U16 LLKeyboardWin32::inverseTranslateExtendedKey(const KEY translated_key)
return inverseTranslateKey(converted_key);
}

std::string LLKeyboardWin32::stringFromAcceleratorMenuKeyImpl(KEY key, bool translate)
{
U16 os_key = inverseTranslateExtendedKey(key);
if (os_key == 0)
{
return LLKeyboard::stringFromAcceleratorMenuKeyImpl(key, translate);
}

HKL layout = GetKeyboardLayout(0);
UINT scan_code = MapVirtualKeyEx(os_key, MAPVK_VK_TO_VSC, layout);
BYTE keyboard_state[256] = {};
wchar_t chars[8] = {};
int res = ToUnicodeEx(
os_key,
scan_code,
keyboard_state,
chars,
8,
1 << 2,
layout);

if ((res == 1 || res == -1) && chars[0] >= 0x20)
{
std::string key_string = ll_convert_wide_to_string(std::wstring(chars, 1));
if (!key_string.empty())
{
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ToUnicodeEx() is being called here to derive the display label, but keyboard_state is all zeros; for letter keys this typically produces lowercase (e.g. "a") whereas the existing accelerator labels use uppercase ("A"). This will change the appearance of many menu shortcuts on Windows; consider normalizing the result (e.g., uppercasing ASCII a-z) or short-circuiting A–Z to LLKeyboard::stringFromKey() to keep menu labels consistent while still translating OEM keys.

Suggested change
{
{
if (key_string.size() == 1 && key_string[0] >= 'a' && key_string[0] <= 'z')
{
key_string[0] = static_cast<char>(key_string[0] - 'a' + 'A');
}

Copilot uses AI. Check for mistakes.
if (translate)
{
LLKeyStringTranslatorFunc* trans = mStringTranslator;
if (trans != NULL)
{
key_string = trans(key_string);
}
}

return key_string;
}
}
Comment on lines +336 to +361
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ToUnicodeEx() can have thread-global side effects related to dead-key and keyboard-buffer state on some Windows versions (see the existing cautionary comment around ToUnicodeEx usage in llviewerwindow.cpp). In particular, treating res == -1 (dead key) as success without clearing the dead-key state risks impacting subsequent character translation/WM_CHAR handling on this UI thread. Consider avoiding the res == -1 path (fall back to the base implementation for dead keys) or explicitly clearing the dead-key state after the call (or using an alternative like MapVirtualKeyEx(..., MAPVK_VK_TO_CHAR, ...) that doesn’t mutate dead-key state).

Copilot uses AI. Check for mistakes.

return LLKeyboard::stringFromAcceleratorMenuKeyImpl(key, translate);
}

#endif
1 change: 1 addition & 0 deletions indra/llwindow/llkeyboardwin32.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class LLKeyboardWin32 : public LLKeyboard

protected:
MASK updateModifiers();
std::string stringFromAcceleratorMenuKeyImpl(KEY key, bool translate) override;
//void setModifierKeyLevel( KEY key, bool new_state );
private:
std::map<U16, KEY> mTranslateNumpadMap;
Expand Down
Loading