Skip to content

Commit 5869893

Browse files
authored
[KBSWITCH] Support System Pen Icon (reactos#8080)
## Purpose Supporting System Pen icon and IME menus for East-Asian users. JIRA issue: CORE-20142 ## Overview The East-Asian system has the system pen icon in taskbar additionally. The system pen icon shows the current IME status. If the user clicked the IME system pen icon, the IME menu will open. The IME system pen icon can be customized by the IME module by posting indicator messages. ## Proposed changes - Add default pen icon resources. - Add base/applications/kbswitch/imemenu.c to handle IME menus. - Add code for adding, modifying and deleting the System Pen icon. - Modify indicdll.spec. - Fix popup menu alignment.
1 parent 85cde17 commit 5869893

File tree

18 files changed

+938
-88
lines changed

18 files changed

+938
-88
lines changed
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11

22
add_rc_deps(kbswitch.rc ${CMAKE_CURRENT_SOURCE_DIR}/res/kbswitch.ico)
3-
add_executable(kbswitch kbswitch.c kbswitch.rc)
3+
add_executable(kbswitch kbswitch.c imemenu.c kbswitch.rc)
44
set_module_type(kbswitch win32gui UNICODE)
55
target_link_libraries(kbswitch wine)
66
add_importlibs(kbswitch advapi32 imm32 user32 shell32 shlwapi gdi32 msvcrt kernel32 ntdll)
77
add_cd_file(TARGET kbswitch DESTINATION reactos/system32 FOR all)
88

99
add_subdirectory(indicdll)
10+
add_dependencies(kbswitch indicdll)
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
/*
2+
* PROJECT: ReactOS Keyboard Layout Switcher
3+
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4+
* PURPOSE: IME menu handling
5+
* COPYRIGHT: Copyright 2025 Katayama Hirofumi MZ ([email protected])
6+
*/
7+
8+
#include "kbswitch.h"
9+
#include "imemenu.h"
10+
11+
#include <wine/debug.h>
12+
WINE_DEFAULT_DEBUG_CHANNEL(internat);
13+
14+
PIMEMENUNODE g_pMenuList = NULL;
15+
INT g_nNextMenuID = 0;
16+
17+
static BOOL MakeImeMenu(_In_ HMENU hMenu, _In_ const IMEMENUNODE *pMenu);
18+
19+
static VOID
20+
AddImeMenuNode(_In_ PIMEMENUNODE pMenu)
21+
{
22+
if (!g_pMenuList)
23+
{
24+
g_pMenuList = pMenu;
25+
return;
26+
}
27+
28+
pMenu->m_pNext = g_pMenuList;
29+
g_pMenuList = pMenu;
30+
}
31+
32+
static PIMEMENUNODE
33+
AllocateImeMenu(_In_ DWORD itemCount)
34+
{
35+
SIZE_T cbMenu = sizeof(IMEMENUNODE) + (itemCount - 1) * sizeof(IMEMENUITEM);
36+
PIMEMENUNODE pMenu = LocalAlloc(LPTR, cbMenu);
37+
if (!pMenu)
38+
return NULL;
39+
pMenu->m_nItems = itemCount;
40+
AddImeMenuNode(pMenu);
41+
return pMenu;
42+
}
43+
44+
static VOID
45+
GetImeMenuItem(
46+
_In_ HIMC hIMC,
47+
_Out_ PIMEMENUITEMINFO lpImeParentMenu,
48+
_In_ BOOL bRightMenu,
49+
_Out_ PIMEMENUITEM pItem)
50+
{
51+
ZeroMemory(pItem, sizeof(IMEMENUITEM));
52+
pItem->m_Info = *lpImeParentMenu;
53+
54+
if (lpImeParentMenu->fType & IMFT_SUBMENU)
55+
pItem->m_pSubMenu = CreateImeMenu(hIMC, lpImeParentMenu, bRightMenu);
56+
57+
pItem->m_nRealID = pItem->m_Info.wID;
58+
pItem->m_Info.wID = ID_STARTIMEMENU + g_nNextMenuID++;
59+
}
60+
61+
PIMEMENUNODE
62+
CreateImeMenu(
63+
_In_ HIMC hIMC,
64+
_Inout_opt_ PIMEMENUITEMINFO lpImeParentMenu,
65+
_In_ BOOL bRightMenu)
66+
{
67+
const DWORD dwFlags = (bRightMenu ? IGIMIF_RIGHTMENU : 0);
68+
const DWORD dwTypes = IGIMII_CMODE |
69+
IGIMII_SMODE |
70+
IGIMII_CONFIGURE |
71+
IGIMII_TOOLS |
72+
IGIMII_HELP |
73+
IGIMII_OTHER;
74+
DWORD itemCount = ImmGetImeMenuItems(hIMC, dwFlags, dwTypes, lpImeParentMenu, NULL, 0);
75+
if (!itemCount)
76+
return NULL;
77+
78+
PIMEMENUNODE pMenu = AllocateImeMenu(itemCount);
79+
if (!pMenu)
80+
return NULL;
81+
82+
DWORD cbItems = sizeof(IMEMENUITEMINFO) * itemCount;
83+
PIMEMENUITEMINFO pImeMenuItems = LocalAlloc(LPTR, cbItems);
84+
if (!pImeMenuItems)
85+
{
86+
LocalFree(pMenu);
87+
return NULL;
88+
}
89+
90+
itemCount = ImmGetImeMenuItems(hIMC, dwFlags, dwTypes, lpImeParentMenu, pImeMenuItems, cbItems);
91+
if (!itemCount)
92+
{
93+
LocalFree(pImeMenuItems);
94+
LocalFree(pMenu);
95+
return NULL;
96+
}
97+
98+
PIMEMENUITEM pItems = pMenu->m_Items;
99+
for (DWORD iItem = 0; iItem < itemCount; ++iItem)
100+
{
101+
GetImeMenuItem(hIMC, &pImeMenuItems[iItem], bRightMenu, &pItems[iItem]);
102+
}
103+
104+
LocalFree(pImeMenuItems);
105+
return pMenu;
106+
}
107+
108+
static BOOL
109+
FillImeMenuItem(_Out_ LPMENUITEMINFO pItemInfo, _In_ const IMEMENUITEM *pItem)
110+
{
111+
ZeroMemory(pItemInfo, sizeof(MENUITEMINFO));
112+
pItemInfo->cbSize = sizeof(MENUITEMINFO);
113+
pItemInfo->fMask = MIIM_ID | MIIM_STATE | MIIM_DATA;
114+
pItemInfo->wID = pItem->m_Info.wID;
115+
pItemInfo->fState = pItem->m_Info.fState;
116+
pItemInfo->dwItemData = pItem->m_Info.dwItemData;
117+
118+
if (pItem->m_Info.fType)
119+
{
120+
pItemInfo->fMask |= MIIM_FTYPE;
121+
pItemInfo->fType = 0;
122+
if (pItem->m_Info.fType & IMFT_RADIOCHECK)
123+
pItemInfo->fType |= MFT_RADIOCHECK;
124+
if (pItem->m_Info.fType & IMFT_SEPARATOR)
125+
pItemInfo->fType |= MFT_SEPARATOR;
126+
}
127+
128+
if (pItem->m_Info.fType & IMFT_SUBMENU)
129+
{
130+
pItemInfo->fMask |= MIIM_SUBMENU;
131+
pItemInfo->hSubMenu = CreatePopupMenu();
132+
if (!MakeImeMenu(pItemInfo->hSubMenu, pItem->m_pSubMenu))
133+
{
134+
DestroyMenu(pItemInfo->hSubMenu);
135+
pItemInfo->hSubMenu = NULL;
136+
return FALSE;
137+
}
138+
}
139+
140+
if (pItem->m_Info.hbmpChecked && pItem->m_Info.hbmpUnchecked)
141+
{
142+
pItemInfo->fMask |= MIIM_CHECKMARKS;
143+
pItemInfo->hbmpChecked = pItem->m_Info.hbmpChecked;
144+
pItemInfo->hbmpUnchecked = pItem->m_Info.hbmpUnchecked;
145+
}
146+
147+
if (pItem->m_Info.hbmpItem)
148+
{
149+
pItemInfo->fMask |= MIIM_BITMAP;
150+
pItemInfo->hbmpItem = pItem->m_Info.hbmpItem;
151+
}
152+
153+
PCTSTR szString = pItem->m_Info.szString;
154+
if (szString && szString[0])
155+
{
156+
pItemInfo->fMask |= MIIM_STRING;
157+
pItemInfo->dwTypeData = (PTSTR)szString;
158+
pItemInfo->cch = lstrlen(szString);
159+
}
160+
161+
return TRUE;
162+
}
163+
164+
static BOOL
165+
MakeImeMenu(_In_ HMENU hMenu, _In_ const IMEMENUNODE *pMenu)
166+
{
167+
if (!pMenu || !pMenu->m_nItems)
168+
return FALSE;
169+
170+
for (INT iItem = 0; iItem < pMenu->m_nItems; ++iItem)
171+
{
172+
MENUITEMINFO mi = { sizeof(mi) };
173+
if (!FillImeMenuItem(&mi, &pMenu->m_Items[iItem]))
174+
{
175+
ERR("FillImeMenuItem failed\n");
176+
return FALSE;
177+
}
178+
if (!InsertMenuItem(hMenu, iItem, TRUE, &mi))
179+
{
180+
ERR("InsertMenuItem failed\n");
181+
return FALSE;
182+
}
183+
}
184+
185+
return TRUE;
186+
}
187+
188+
HMENU MenuFromImeMenu(_In_ const IMEMENUNODE *pMenu)
189+
{
190+
HMENU hMenu = CreatePopupMenu();
191+
if (!pMenu)
192+
return hMenu;
193+
if (!MakeImeMenu(hMenu, pMenu))
194+
{
195+
DestroyMenu(hMenu);
196+
return NULL;
197+
}
198+
return hMenu;
199+
}
200+
201+
INT
202+
GetRealImeMenuID(_In_ const IMEMENUNODE *pMenu, _In_ INT nFakeID)
203+
{
204+
if (!pMenu || !pMenu->m_nItems || nFakeID < ID_STARTIMEMENU)
205+
return 0;
206+
207+
for (INT iItem = 0; iItem < pMenu->m_nItems; ++iItem)
208+
{
209+
const IMEMENUITEM *pItem = &pMenu->m_Items[iItem];
210+
if (pItem->m_Info.wID == nFakeID)
211+
return pItem->m_nRealID;
212+
213+
if (pItem->m_pSubMenu)
214+
{
215+
INT nRealID = GetRealImeMenuID(pItem->m_pSubMenu, nFakeID);
216+
if (nRealID)
217+
return nRealID;
218+
}
219+
}
220+
221+
return 0;
222+
}
223+
224+
static BOOL
225+
FreeMenuNode(_In_ PIMEMENUNODE pMenuNode)
226+
{
227+
if (!pMenuNode)
228+
return FALSE;
229+
230+
for (INT iItem = 0; iItem < pMenuNode->m_nItems; ++iItem)
231+
{
232+
PIMEMENUITEM pItem = &pMenuNode->m_Items[iItem];
233+
if (pItem->m_Info.hbmpChecked)
234+
DeleteObject(pItem->m_Info.hbmpChecked);
235+
if (pItem->m_Info.hbmpUnchecked)
236+
DeleteObject(pItem->m_Info.hbmpUnchecked);
237+
if (pItem->m_Info.hbmpItem)
238+
DeleteObject(pItem->m_Info.hbmpItem);
239+
}
240+
241+
LocalFree(pMenuNode);
242+
return TRUE;
243+
}
244+
245+
VOID
246+
CleanupImeMenus(VOID)
247+
{
248+
if (!g_pMenuList)
249+
return;
250+
251+
PIMEMENUNODE pNext;
252+
for (PIMEMENUNODE pNode = g_pMenuList; pNode; pNode = pNext)
253+
{
254+
pNext = pNode->m_pNext;
255+
FreeMenuNode(pNode);
256+
}
257+
258+
g_pMenuList = NULL;
259+
g_nNextMenuID = 0;
260+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* PROJECT: ReactOS Keyboard Layout Switcher
3+
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4+
* PURPOSE: IME menu handling
5+
* COPYRIGHT: Copyright 2025 Katayama Hirofumi MZ ([email protected])
6+
*/
7+
8+
#pragma once
9+
10+
#include <immdev.h>
11+
12+
#define ID_STARTIMEMENU 1000
13+
14+
struct tagIMEMENUNODE;
15+
16+
typedef struct tagIMEMENUITEM
17+
{
18+
IMEMENUITEMINFO m_Info;
19+
UINT m_nRealID;
20+
struct tagIMEMENUNODE *m_pSubMenu;
21+
} IMEMENUITEM, *PIMEMENUITEM;
22+
23+
typedef struct tagIMEMENUNODE
24+
{
25+
struct tagIMEMENUNODE *m_pNext;
26+
INT m_nItems;
27+
IMEMENUITEM m_Items[ANYSIZE_ARRAY];
28+
} IMEMENUNODE, *PIMEMENUNODE;
29+
30+
PIMEMENUNODE
31+
CreateImeMenu(
32+
_In_ HIMC hIMC,
33+
_Inout_opt_ PIMEMENUITEMINFO lpImeParentMenu,
34+
_In_ BOOL bRightMenu);
35+
36+
HMENU MenuFromImeMenu(_In_ const IMEMENUNODE *pMenu);
37+
INT GetRealImeMenuID(_In_ const IMEMENUNODE *pMenu, _In_ INT nFakeID);
38+
VOID CleanupImeMenus(VOID);

base/applications/kbswitch/indicdll/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11

22
spec2def(indicdll.dll indicdll.spec)
33

4+
file(GLOB indicdll_rc_deps res/*.*)
5+
add_rc_deps(indicdll.rc ${indicdll_rc_deps})
6+
47
list(APPEND SOURCE
58
indicdll.c
69
indicdll.rc

0 commit comments

Comments
 (0)