1 // This is core/vgui/impl/win32/vgui_win32_utils.cxx
2 
3 #include <iostream>
4 #include <cstddef>
5 #include "vgui_win32_utils.h"
6 #include <cassert>
7 #ifdef _MSC_VER
8 #  include "vcl_msvc_warnings.h"
9 #endif
10 
11 // (Create if necessary and) return singleton instance of this class.
12 vgui_win32_utils *
instance()13 vgui_win32_utils::instance()
14 {
15   static vgui_win32_utils instance_;
16   return &instance_;
17 }
18 
19 
20 // Convert a vgui_menu to equivalent MENUTEMPLATE structure used by
21 // Win32 function LoadMenuIndirect.
22 HMENU
vgui_menu_to_win32(vgui_menu const & vguimenu,std::vector<vgui_command_sptr> & callbacks_,HACCEL * hAccel,bool isPopup)23 vgui_win32_utils::vgui_menu_to_win32(vgui_menu const & vguimenu,
24                                      std::vector<vgui_command_sptr> & callbacks_,
25                                      HACCEL * hAccel,
26                                      bool isPopup)
27 {
28   HMENU hMenu;
29   MENUITEMTEMPLATEHEADER * pMenuHeader;
30   MENUITEMTEMPLATE * pMenuItem;
31 
32   // Note that the MENUITEMTEMPLATE structures are variable in length,
33   // Therefore we allocate capacity*sizeof(char) initially.
34   menu_capacity = 1024; // in unit of unsigned char
35   item_count = 0;
36   callbacks.clear();
37 
38   pAccel = 0;
39   accel_capacity = 16;
40   accel_count = 0;
41 
42   pMenu = (unsigned char *)malloc(sizeof(MENUITEMTEMPLATEHEADER) + menu_capacity);
43   if (pMenu == NULL)
44   {
45     std::cerr << "Memory allocation error\n";
46     return NULL;
47   }
48 
49   pAccel = (ACCEL *)malloc(sizeof(ACCEL) * accel_capacity);
50   if (pAccel == NULL)
51     std::cerr << "Memory allocation error\n";
52 
53   // Fill up the MENUITEMTEMPLATEHEADER structure.
54   pMenuHeader = (MENUITEMTEMPLATEHEADER *)pMenu;
55   pMenuHeader->versionNumber = 0; // This member must be zero.
56   pMenuHeader->offset = 0;        // The menu item list follows immediately after the header.
57   int offset = sizeof(MENUITEMTEMPLATEHEADER);
58 
59   if (isPopup)
60   {
61     // Add a dumb header so that TrackPopMenu shows the popup menu properly.
62     pMenuItem = (MENUITEMTEMPLATE *)(pMenu + offset);
63     pMenuItem->mtOption = MF_END | MF_POPUP;
64     offset += sizeof(pMenuItem->mtOption);
65     pMenuItem->mtID = 0;
66     offset += sizeof(WCHAR);
67   }
68 
69   addMenuItems(vguimenu, offset, isPopup);
70 
71   hMenu = LoadMenuIndirect(pMenu);
72   if (!hMenu)
73     ShowErrorMessage(GetLastError());
74 
75   if (!isPopup) // create an accelerator table for non-popup menu.
76     *hAccel = CreateAcceleratorTable(pAccel, accel_count);
77 
78   free(pMenu);
79   free(pAccel);
80 
81   callbacks_ = callbacks;
82   return hMenu;
83 }
84 
85 
86 int
addMenuItems(vgui_menu const & vguimenu,int offset_in,bool is_popup)87 vgui_win32_utils::addMenuItems(vgui_menu const & vguimenu, int offset_in, bool is_popup)
88 {
89   static unsigned int max_menuitem_size = 256;
90 
91   MENUITEMTEMPLATE * pMenuItem;
92   WCHAR * pMenuItemText;
93   std::string menuItemText;
94   int stride, offset; // in unit of unsigned char
95 
96   // Loop over all menu items and convert them into MENUITEMTEMPLATE
97   // Note that there are four types of menu item in vgui_menu:
98   // command, submenu, toggle button, and separator.
99   offset = offset_in;
100   for (unsigned int i = 0; i < vguimenu.size(); ++i)
101   {
102     pMenuItem = (MENUITEMTEMPLATE *)(pMenu + offset);
103 
104     // MENUITEMTEMPLATE does not have separator.
105     if (vguimenu[i].is_separator() || vguimenu[i].is_toggle_button())
106       continue;
107 
108     stride = 0;
109     pMenuItem->mtOption = 0;
110     stride += sizeof(pMenuItem->mtOption);
111     if (i == vguimenu.size() - 1)
112       pMenuItem->mtOption |= MF_END; // indiate this is the last menu item
113 
114     if (vguimenu[i].is_command())
115     {
116       int menuItemID = is_popup ? POPUPMENU_ID_START + item_count++ : MENU_ID_START + item_count++;
117       pMenuItem->mtID = menuItemID;
118       stride += sizeof(pMenuItem->mtID);
119 
120       menuItemText = vguimenu[i].name;
121       addAccelerator(menuItemText, vguimenu[i], menuItemID);
122 
123       // Copy the menu item text.
124       std::size_t j;
125       pMenuItemText = pMenuItem->mtString;
126       for (j = 0; j < menuItemText.size(); j++)
127         *(pMenuItemText + j) = (WCHAR)menuItemText.c_str()[j];
128       *(pMenuItemText + j) = 0;
129       stride += sizeof(WCHAR) * (wcslen(pMenuItemText) + 1);
130 
131       // Add the associated callback function pointer to the callback list.
132       callbacks.push_back(vguimenu[i].cmnd);
133     }
134     else if (vguimenu[i].is_submenu())
135     {
136       pMenuItem->mtOption |= MF_POPUP;
137 
138       menuItemText = vguimenu[i].name;
139       std::size_t j;
140       // Note that the MENUITEMTEMPLATE structure for an item that opens a
141       // drop-down menu or submenu does not contain the mtID member.
142       pMenuItemText = (WCHAR *)&pMenuItem->mtID;
143       for (j = 0; j < menuItemText.size(); j++)
144         *(pMenuItemText + j) = menuItemText.c_str()[j];
145       *(pMenuItemText + j) = 0;
146       stride += sizeof(WCHAR) * (wcslen(pMenuItemText) + 1);
147 
148       // Call itself recursively for submenu
149       stride += addMenuItems(*vguimenu[i].menu, offset + stride, is_popup);
150     }
151 
152     offset += stride;
153     // Check for illegal buffer access. We'are too late at this point.
154     // Increase maximum size of a menu item "max_menuitem_size" (default 256)
155     // to avoid this error.
156     assert(offset < menu_capacity);
157 
158     // Deal with overflow.
159     if (offset > menu_capacity - (int)max_menuitem_size)
160     {
161       menu_capacity <<= 1; // double the capacity.
162       pMenu = (unsigned char *)realloc(pMenu, sizeof(MENUITEMTEMPLATEHEADER) + menu_capacity);
163       if (pMenu == NULL)
164       {
165         std::cerr << "Memory allocation error\n";
166         return 0;
167       }
168     }
169   }
170 
171   return offset - offset_in;
172 }
173 
174 // Convert a vgui_menu to equivalent extended MENUTEMPLATE structure used by
175 // Win32 function LoadMenuIndirect.
176 HMENU
vgui_menu_to_win32ex(vgui_menu const & vguimenu,std::vector<vgui_command_sptr> & callbacks_,HACCEL * hAccel,bool isPopup)177 vgui_win32_utils::vgui_menu_to_win32ex(vgui_menu const & vguimenu,
178                                        std::vector<vgui_command_sptr> & callbacks_,
179                                        HACCEL * hAccel,
180                                        bool isPopup)
181 {
182   HMENU hMenu;
183   MENUEX_TEMPLATE_HEADER * pMenuHeader;
184   MENUEX_TEMPLATE_ITEM * pMenuItem;
185 
186   // Note that the MENUEX_TEMPLATE_ITEM structures are variable in length,
187   // but are aligned on DWORD boundaries. Therefore we allocate
188   // capacity*sizeof(WORD) initially.
189   menu_capacity = 1024;
190   item_count = 0;
191   callbacks.clear();
192 
193   pAccel = 0;
194   accel_capacity = 16;
195   accel_count = 0;
196 
197   pMenu = (unsigned char *)malloc(sizeof(MENUEX_TEMPLATE_HEADER) + menu_capacity);
198   if (pMenu == NULL)
199   {
200     std::cerr << "Memory allocation error\n";
201     return NULL;
202   }
203 
204   pAccel = (ACCEL *)malloc(sizeof(ACCEL) * accel_capacity);
205   if (pAccel == NULL)
206     std::cerr << "Memory allocation error\n";
207 
208   // Fill up the MENUEX_TEMPLATE_HEADER structure.
209   pMenuHeader = (MENUEX_TEMPLATE_HEADER *)pMenu;
210   pMenuHeader->wVersion = 1; // This member must be 1.
211   pMenuHeader->wOffset = 4;  // The menu item list follows immediately after the header.
212   pMenuHeader->dwHelpId = 0;
213   int offset = sizeof(MENUEX_TEMPLATE_HEADER);
214 
215   if (isPopup)
216   {
217     // Add a dumb header so that TrackPopMenu shows the popup menu properly.
218     pMenuItem = (MENUEX_TEMPLATE_ITEM *)(pMenu + offset);
219     pMenuItem->dwType = MFT_STRING;
220     offset += sizeof(pMenuItem->dwType);
221     pMenuItem->dwState = MFS_ENABLED;
222     offset += sizeof(pMenuItem->dwState);
223     pMenuItem->menuId = 0;
224     offset += sizeof(pMenuItem->menuId);
225     pMenuItem->bResInfo = 0x01 | 0x80;
226     offset += sizeof(pMenuItem->bResInfo);
227     pMenuItem->szText = 0;
228     offset += sizeof(pMenuItem->szText);
229     pMenuItem->dwHelpId = 0;
230     offset += sizeof(pMenuItem->dwHelpId);
231   }
232 
233   addMenuItemsEx(vguimenu, offset, isPopup);
234 
235   hMenu = LoadMenuIndirect(pMenu);
236   if (!hMenu)
237     ShowErrorMessage(GetLastError());
238 
239   if (!isPopup) // create an accelerator table for non-popup menu.
240     *hAccel = CreateAcceleratorTable(pAccel, accel_count);
241 
242   free(pMenu);
243   free(pAccel);
244 
245   callbacks_ = callbacks;
246   return hMenu;
247 }
248 
249 int
addMenuItemsEx(vgui_menu const & vguimenu,int offset_in,bool is_popup)250 vgui_win32_utils::addMenuItemsEx(vgui_menu const & vguimenu, int offset_in, bool is_popup)
251 {
252   static unsigned int max_menuitem_size = 256;
253 
254   MENUEX_TEMPLATE_ITEM * pMenuItem;
255   WCHAR * pMenuItemText;
256   std::string menuItemText;
257   int stride, offset;
258   bool last_item;
259 
260   // Loop over all menu items and convert them into MENUTEMPLATE
261   // Note that there are four types of menu item in vgui_menu:
262   // command, submenu, toggle button, and separator.
263   offset = offset_in;
264   for (unsigned int i = 0; i < vguimenu.size(); ++i)
265   {
266     pMenuItem = (MENUEX_TEMPLATE_ITEM *)(pMenu + offset);
267     stride = 0;
268 
269     // indiate this is the last menu item
270     last_item = (i == vguimenu.size() - 1) ? true : false;
271 
272     pMenuItem->dwType = MFT_STRING;
273     stride += sizeof(pMenuItem->dwType);
274     pMenuItem->dwState = MFS_ENABLED;
275     stride += sizeof(pMenuItem->dwState);
276     pMenuItem->menuId = 0;
277     stride += sizeof(pMenuItem->menuId);
278 
279     if (vguimenu[i].is_separator())
280     {
281       pMenuItem->dwType = MFT_SEPARATOR;
282       pMenuItem->bResInfo = last_item ? 0x80 : 0;
283       stride += sizeof(pMenuItem->bResInfo);
284       pMenuItem->szText = 0;
285       stride += sizeof(pMenuItem->szText);
286       if (stride % 4)
287       { // aligned on DWORD boundary.
288         stride += 4;
289         stride &= ~3;
290       }
291     }
292     else if (vguimenu[i].is_command())
293     {
294       int menuItemID = is_popup ? POPUPMENU_ID_START + item_count++ : MENU_ID_START + item_count++;
295       pMenuItem->menuId = menuItemID;
296 
297       pMenuItem->bResInfo = last_item ? 0x80 : 0;
298       stride += sizeof(pMenuItem->bResInfo);
299 
300       menuItemText = vguimenu[i].name;
301       addAccelerator(menuItemText, vguimenu[i], menuItemID);
302 
303       // Copy the menu item text.
304       pMenuItemText = (WCHAR *)&pMenuItem->szText;
305       std::size_t j;
306       for (j = 0; j < menuItemText.size(); j++)
307         *(pMenuItemText + j) = menuItemText.c_str()[j];
308       *(pMenuItemText + j) = 0;
309 
310       stride += sizeof(WCHAR) * (wcslen(pMenuItemText) + 1);
311 
312       if (stride % 4)
313       { // aligned on DWORD boundary.
314         stride += 4;
315         stride &= ~3;
316       }
317 
318       // Add the associated callback function pointer to the callback list.
319       callbacks.push_back(vguimenu[i].cmnd);
320     }
321     else if (vguimenu[i].is_submenu())
322     {
323       pMenuItem->bResInfo = last_item ? 0x80 | 0x01 : 0x01;
324       stride += sizeof(pMenuItem->bResInfo);
325 
326       menuItemText = vguimenu[i].name;
327       pMenuItemText = (WCHAR *)&pMenuItem->szText;
328       std::size_t j;
329       for (j = 0; j < menuItemText.size(); j++)
330         *(pMenuItemText + j) = menuItemText.c_str()[j];
331       *(pMenuItemText + j) = 0;
332       stride += sizeof(WCHAR) * (wcslen(pMenuItemText) + 1);
333 
334       if (stride % 4)
335       { // aligned on DWORD boundary.
336         stride += 4;
337         stride &= ~3;
338       }
339 
340       DWORD * dwHelpId = (DWORD *)(pMenu + offset + stride);
341       *dwHelpId = 0;
342       stride += sizeof(pMenuItem->dwHelpId);
343 
344       // Call itself recursively for submenu
345       stride += addMenuItemsEx(*vguimenu[i].menu, offset + stride, is_popup);
346     }
347     else if (vguimenu[i].is_toggle_button())
348     {
349       std::cerr << "vgui_win32_utils: toggle button is not converted\n";
350     }
351 
352     offset += stride;
353     // Check for illegal buffer access. We'are too late at this point.
354     // Increase maximum size of a menu item "max_menuitem_size" (default 256)
355     // to avoid this error.
356     assert(offset < menu_capacity);
357 
358     // Deal with buffer overflow.
359     if (offset > menu_capacity - (int)max_menuitem_size)
360     {
361       menu_capacity <<= 1; // double the capacity.
362       pMenu = (unsigned char *)realloc(pMenu, sizeof(MENUEX_TEMPLATE_HEADER) + menu_capacity);
363       if (pMenu == NULL)
364       {
365         std::cerr << "Memory allocation error\n";
366         return NULL;
367       }
368     }
369   }
370 
371   return offset - offset_in;
372 }
373 
374 inline void
addAccelerator(std::string & menuItemText,vgui_menu_item const & vguimenu,int menuItemId)375 vgui_win32_utils::addAccelerator(std::string & menuItemText, vgui_menu_item const & vguimenu, int menuItemId)
376 {
377   ACCEL * pa = pAccel + accel_count;
378   pa->cmd = menuItemId;
379   // Give a virtual key, even if there is no modifier.
380   pa->fVirt = FVIRTKEY;
381 
382   if (vguimenu.short_cut.mod != vgui_MODIFIER_NULL || vguimenu.short_cut.key != vgui_KEY_NULL)
383     menuItemText += "\t"; // A tab key is required.
384 
385   if (vguimenu.short_cut.mod & vgui_CTRL)
386   {
387     menuItemText += "Ctrl+";
388     pa->fVirt |= FCONTROL;
389   }
390   if (vguimenu.short_cut.mod & vgui_SHIFT)
391   {
392     menuItemText += "Shift+";
393     pa->fVirt |= FSHIFT;
394   }
395   if (vguimenu.short_cut.mod & vgui_ALT)
396   {
397     menuItemText += "Alt+";
398     pa->fVirt |= FALT;
399   }
400 
401   vgui_key key = vguimenu.short_cut.key;
402   if (key != vgui_KEY_NULL)
403   {
404     if (key >= 'A' && key <= 'Z' || key >= 'a' && key <= 'z')
405     {
406       if (key >= 'a' && key <= 'z')
407         key = vgui_key(key + 'A' - 'a');
408       menuItemText += key;
409       pa->key = key;
410     }
411     else
412     {
413       menuItemText += vgui_key_to_string(key);
414       pa->key = vgui_key_to_virt_key(key);
415     }
416     // Deal with the case of buffer overflow.
417     if (++accel_count >= accel_capacity)
418     {
419       accel_capacity <<= 1; // double the capacity.
420       pAccel = (ACCEL *)realloc(pAccel, sizeof(ACCEL) * accel_capacity);
421       if (pAccel == NULL)
422         std::cerr << "Memory allocation error\n";
423     }
424   }
425 }
426 
427 
428 inline std::string
vgui_key_to_string(vgui_key key)429 vgui_win32_utils::vgui_key_to_string(vgui_key key)
430 {
431   std::string str;
432 
433   switch (key)
434   {
435     // Function keys
436     case vgui_F1:
437       str = "F1";
438       break;
439     case vgui_F2:
440       str = "F2";
441       break;
442     case vgui_F3:
443       str = "F3";
444       break;
445     case vgui_F4:
446       str = "F4";
447       break;
448     case vgui_F5:
449       str = "F5";
450       break;
451     case vgui_F6:
452       str = "F6";
453       break;
454     case vgui_F7:
455       str = "F7";
456       break;
457     case vgui_F8:
458       str = "F8";
459       break;
460     case vgui_F9:
461       str = "F9";
462       break;
463     case vgui_F10:
464       str = "F10";
465       break;
466     case vgui_F11:
467       str = "F11";
468       break;
469     case vgui_F12:
470       str = "F12";
471       break;
472     case vgui_CURSOR_LEFT:
473       str = "Left";
474       break;
475     case vgui_CURSOR_UP:
476       str = "Up";
477       break;
478     case vgui_CURSOR_RIGHT:
479       str = "Right";
480       break;
481     case vgui_CURSOR_DOWN:
482       str = "Down";
483       break;
484     case vgui_PAGE_UP:
485       str = "PageUp";
486       break;
487     case vgui_PAGE_DOWN:
488       str = "PageDown";
489       break;
490     case vgui_HOME:
491       str = "Home";
492       break;
493     case vgui_END:
494       str = "End";
495       break;
496     case vgui_DELETE:
497       str = "Del";
498       break;
499     case vgui_INSERT:
500       str = "Ins";
501       break;
502     default:
503       str = "";
504       break;
505   }
506 
507   return str;
508 }
509 
510 UINT
vgui_key_to_virt_key(vgui_key key)511 vgui_win32_utils::vgui_key_to_virt_key(vgui_key key)
512 {
513   UINT virt_key;
514 
515   switch (key)
516   {
517     // Function keys
518     case vgui_F1:
519     case vgui_F2:
520     case vgui_F3:
521     case vgui_F4:
522     case vgui_F5:
523     case vgui_F6:
524     case vgui_F7:
525     case vgui_F8:
526     case vgui_F9:
527     case vgui_F10:
528     case vgui_F11:
529     case vgui_F12:
530       virt_key = VK_F1 + key - vgui_F1;
531       break;
532     case vgui_CURSOR_LEFT:
533       virt_key = VK_LEFT;
534       break;
535     case vgui_CURSOR_UP:
536       virt_key = VK_UP;
537       break;
538     case vgui_CURSOR_RIGHT:
539       virt_key = VK_RIGHT;
540       break;
541     case vgui_CURSOR_DOWN:
542       virt_key = VK_DOWN;
543       break;
544     case vgui_PAGE_UP:
545       virt_key = VK_PRIOR;
546       break;
547     case vgui_PAGE_DOWN:
548       virt_key = VK_NEXT;
549       break;
550     case vgui_HOME:
551       virt_key = VK_HOME;
552       break;
553     case vgui_END:
554       virt_key = VK_END;
555       break;
556     case vgui_DELETE:
557       virt_key = VK_DELETE;
558       break;
559     case vgui_INSERT:
560       virt_key = VK_INSERT;
561       break;
562     default: // undefined
563       virt_key = 0x07;
564       break;
565   }
566 
567   return virt_key;
568 }
569 
570 void
ShowErrorMessage(DWORD dwErrorNo)571 vgui_win32_utils::ShowErrorMessage(DWORD dwErrorNo)
572 {
573   LPSTR lpBuffer;
574   FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
575                 NULL,
576                 dwErrorNo,
577                 LANG_NEUTRAL,
578                 (LPTSTR)&lpBuffer,
579                 0,
580                 NULL);
581   std::cerr << lpBuffer;
582 }
583