1 // license:BSD-3-Clause
2 // copyright-holders:Aaron Giles, Vas Crabb
3 //============================================================
4 //
5 // debugwininfo.c - Win32 debug window handling
6 //
7 //============================================================
8
9 #include "emu.h"
10 #include "debugwininfo.h"
11
12 #include "debugviewinfo.h"
13
14 #include "debugger.h"
15 #include "debug/debugcon.h"
16 #include "debug/debugcpu.h"
17 #include "window.h"
18 #include "winutf8.h"
19
20 #include "winutil.h"
21 #include "modules/lib/osdobj_common.h"
22
23
24 bool debugwin_info::s_window_class_registered = false;
25
26
debugwin_info(debugger_windows_interface & debugger,bool is_main_console,LPCSTR title,WNDPROC handler)27 debugwin_info::debugwin_info(debugger_windows_interface &debugger, bool is_main_console, LPCSTR title, WNDPROC handler) :
28 debugbase_info(debugger),
29 m_is_main_console(is_main_console),
30 m_wnd(nullptr),
31 m_handler(handler),
32 m_minwidth(200),
33 m_maxwidth(0),
34 m_minheight(200),
35 m_maxheight(0),
36 m_ignore_char_lparam(0)
37 {
38 register_window_class();
39
40 m_wnd = win_create_window_ex_utf8(DEBUG_WINDOW_STYLE_EX, "MAMEDebugWindow", title, DEBUG_WINDOW_STYLE,
41 0, 0, 100, 100, std::static_pointer_cast<win_window_info>(osd_common_t::s_window_list.front())->platform_window(), create_standard_menubar(), GetModuleHandleUni(), this);
42 if (m_wnd == nullptr)
43 return;
44
45 RECT work_bounds;
46 SystemParametersInfo(SPI_GETWORKAREA, 0, &work_bounds, 0);
47 m_maxwidth = work_bounds.right - work_bounds.left;
48 m_maxheight = work_bounds.bottom - work_bounds.top;
49 }
50
51
~debugwin_info()52 debugwin_info::~debugwin_info()
53 {
54 }
55
56
destroy()57 void debugwin_info::destroy()
58 {
59 for (int curview = 0; curview < MAX_VIEWS; curview++)
60 m_views[curview].reset();
61 DestroyWindow(m_wnd);
62 }
63
set_default_focus()64 bool debugwin_info::set_default_focus()
65 {
66 return false;
67 }
68
69
prev_view(debugview_info * curview)70 void debugwin_info::prev_view(debugview_info *curview)
71 {
72 // count the number of views
73 int numviews;
74 for (numviews = 0; numviews < MAX_VIEWS; numviews++)
75 {
76 if (m_views[numviews] == nullptr)
77 break;
78 }
79
80 // if we have a curview, find out its index
81 int curindex = 1;
82 if (curview)
83 {
84 for (curindex = numviews - 1; curindex > 0; curindex--)
85 {
86 if (m_views[curindex].get() == curview)
87 break;
88 }
89 if (curindex < 0)
90 curindex = 1;
91 }
92
93 // loop until we find someone to take focus
94 while (1)
95 {
96 // advance to the previous index
97 curindex--;
98 if (curindex < -1)
99 curindex = numviews - 1;
100
101 if (curindex < 0 && set_default_focus())
102 {
103 // negative numbers mean the focuswnd
104 break;
105 }
106 else if (curindex >= 0 && m_views[curindex] != nullptr && m_views[curindex]->cursor_supported())
107 {
108 // positive numbers mean a view
109 m_views[curindex]->set_focus();
110 break;
111 }
112 }
113 }
114
115
next_view(debugview_info * curview)116 void debugwin_info::next_view(debugview_info *curview)
117 {
118 // count the number of views
119 int numviews;
120 for (numviews = 0; numviews < MAX_VIEWS; numviews++)
121 {
122 if (m_views[numviews] == nullptr)
123 break;
124 }
125
126 // if we have a curview, find out its index
127 int curindex = -1;
128 if (curview)
129 {
130 for (curindex = numviews - 1; curindex > 0; curindex--)
131 {
132 if (m_views[curindex].get() == curview)
133 break;
134 }
135 }
136
137 // loop until we find someone to take focus
138 while (1)
139 {
140 // advance to the previous index
141 curindex++;
142 if (curindex >= numviews)
143 curindex = -1;
144
145 if (curindex < 0 && set_default_focus())
146 {
147 // negative numbers mean the focuswnd
148 break;
149 }
150 else if (curindex >= 0 && m_views[curindex] != nullptr && m_views[curindex]->cursor_supported())
151 {
152 // positive numbers mean a view
153 m_views[curindex]->set_focus();
154 break;
155 }
156 }
157 }
158
159
handle_key(WPARAM wparam,LPARAM lparam)160 bool debugwin_info::handle_key(WPARAM wparam, LPARAM lparam)
161 {
162 /* ignore any keys that are received while the debug key is down */
163 if (!waiting_for_debugger() && seq_pressed())
164 return true;
165
166 switch (wparam)
167 {
168 case VK_F3:
169 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
170 SendMessage(m_wnd, WM_COMMAND, ID_HARD_RESET, 0);
171 else
172 SendMessage(m_wnd, WM_COMMAND, ID_SOFT_RESET, 0);
173 return true;
174
175 case VK_F4:
176 if (GetAsyncKeyState(VK_MENU) & 0x8000)
177 {
178 /* ajg - never gets here since 'alt' seems to be captured somewhere else - menu maybe? */
179 SendMessage(m_wnd, WM_COMMAND, ID_EXIT, 0);
180 return true;
181 }
182 break;
183
184 case VK_F5:
185 SendMessage(m_wnd, WM_COMMAND, ID_RUN, 0);
186 return true;
187
188 case VK_F6:
189 SendMessage(m_wnd, WM_COMMAND, ID_NEXT_CPU, 0);
190 return true;
191
192 case VK_F7:
193 SendMessage(m_wnd, WM_COMMAND, ID_RUN_IRQ, 0);
194 return true;
195
196 case VK_F8:
197 SendMessage(m_wnd, WM_COMMAND, ID_RUN_VBLANK, 0);
198 return true;
199
200 case VK_F10:
201 SendMessage(m_wnd, WM_COMMAND, ID_STEP_OVER, 0);
202 return true;
203
204 case VK_F11:
205 if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) && ((GetAsyncKeyState(VK_CONTROL) & 0x8000) == 0))
206 SendMessage(m_wnd, WM_COMMAND, ID_STEP_OUT, 0);
207 else if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
208 SendMessage(m_wnd, WM_COMMAND, ID_REWIND_STEP, 0);
209 else
210 SendMessage(m_wnd, WM_COMMAND, ID_STEP, 0);
211 return true;
212
213 case VK_F12:
214 SendMessage(m_wnd, WM_COMMAND, ID_RUN_AND_HIDE, 0);
215 return true;
216
217 case 'M':
218 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
219 {
220 SendMessage(m_wnd, WM_COMMAND, ID_NEW_MEMORY_WND, 0);
221 return true;
222 }
223 break;
224
225 case 'D':
226 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
227 {
228 SendMessage(m_wnd, WM_COMMAND, ID_NEW_DISASM_WND, 0);
229 return true;
230 }
231 break;
232
233 case 'L':
234 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
235 {
236 SendMessage(m_wnd, WM_COMMAND, ID_NEW_LOG_WND, 0);
237 return true;
238 }
239 break;
240
241 case 'B':
242 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
243 {
244 SendMessage(m_wnd, WM_COMMAND, ID_NEW_POINTS_WND, 0);
245 return true;
246 }
247 break;
248 }
249
250 return false;
251 }
252
253
recompute_children()254 void debugwin_info::recompute_children()
255 {
256 if (m_views[0] != nullptr)
257 {
258 // compute a client rect
259 RECT bounds;
260 bounds.top = bounds.left = 0;
261 bounds.right = m_views[0]->prefwidth() + (2 * EDGE_WIDTH);
262 bounds.bottom = 200;
263 AdjustWindowRectEx(&bounds, DEBUG_WINDOW_STYLE, FALSE, DEBUG_WINDOW_STYLE_EX);
264
265 // clamp the min/max size
266 set_maxwidth(bounds.right - bounds.left);
267
268 // get the parent's dimensions
269 RECT parent;
270 GetClientRect(window(), &parent);
271
272 // view gets the remaining space
273 InflateRect(&parent, -EDGE_WIDTH, -EDGE_WIDTH);
274 m_views[0]->set_bounds(parent);
275 }
276 }
277
278
handle_command(WPARAM wparam,LPARAM lparam)279 bool debugwin_info::handle_command(WPARAM wparam, LPARAM lparam)
280 {
281 if (HIWORD(wparam) == 0)
282 {
283 switch (LOWORD(wparam))
284 {
285 case ID_NEW_MEMORY_WND:
286 debugger().create_memory_window();
287 return true;
288
289 case ID_NEW_DISASM_WND:
290 debugger().create_disasm_window();
291 return true;
292
293 case ID_NEW_LOG_WND:
294 debugger().create_log_window();
295 return true;
296
297 case ID_NEW_POINTS_WND:
298 debugger().create_points_window();
299 return true;
300
301 case ID_RUN_AND_HIDE:
302 debugger().hide_all();
303 case ID_RUN:
304 machine().debugger().console().get_visible_cpu()->debug()->go();
305 return true;
306
307 case ID_NEXT_CPU:
308 machine().debugger().console().get_visible_cpu()->debug()->go_next_device();
309 return true;
310
311 case ID_RUN_VBLANK:
312 machine().debugger().console().get_visible_cpu()->debug()->go_vblank();
313 return true;
314
315 case ID_RUN_IRQ:
316 machine().debugger().console().get_visible_cpu()->debug()->go_interrupt();
317 return true;
318
319 case ID_STEP:
320 machine().debugger().console().get_visible_cpu()->debug()->single_step();
321 return true;
322
323 case ID_STEP_OVER:
324 machine().debugger().console().get_visible_cpu()->debug()->single_step_over();
325 return true;
326
327 case ID_STEP_OUT:
328 machine().debugger().console().get_visible_cpu()->debug()->single_step_out();
329 return true;
330
331 case ID_REWIND_STEP:
332 machine().rewind_step();
333
334 // clear all PC & memory tracks
335 for (device_t &device : device_iterator(machine().root_device()))
336 {
337 device.debug()->track_pc_data_clear();
338 device.debug()->track_mem_data_clear();
339 }
340
341 // update debugger and emulator window
342 machine().debug_view().update_all();
343 machine().debugger().refresh_display();
344 return true;
345
346 case ID_HARD_RESET:
347 machine().schedule_hard_reset();
348 return true;
349
350 case ID_SOFT_RESET:
351 machine().schedule_soft_reset();
352 machine().debugger().console().get_visible_cpu()->debug()->go();
353 return true;
354
355 case ID_EXIT:
356 set_default_focus();
357 machine().schedule_exit();
358 return true;
359 }
360 }
361 return false;
362 }
363
364
draw_contents(HDC dc)365 void debugwin_info::draw_contents(HDC dc)
366 {
367 // fill the background with light gray
368 RECT parent;
369 GetClientRect(m_wnd, &parent);
370 FillRect(dc, &parent, (HBRUSH)GetStockObject(LTGRAY_BRUSH));
371
372 // draw edges around all views
373 for (int curview = 0; curview < MAX_VIEWS; curview++)
374 {
375 if (m_views[curview] != nullptr)
376 {
377 RECT bounds;
378 m_views[curview]->get_bounds(bounds);
379 draw_border(dc, bounds);
380 }
381 }
382 }
383
384
draw_border(HDC dc,RECT & bounds)385 void debugwin_info::draw_border(HDC dc, RECT &bounds)
386 {
387 ScreenToClient(m_wnd, &((POINT *)&bounds)[0]);
388 ScreenToClient(m_wnd, &((POINT *)&bounds)[1]);
389 InflateRect(&bounds, EDGE_WIDTH, EDGE_WIDTH);
390 DrawEdge(dc, &bounds, EDGE_SUNKEN, BF_RECT);
391 }
392
393
draw_border(HDC dc,HWND child)394 void debugwin_info::draw_border(HDC dc, HWND child)
395 {
396 RECT bounds;
397 GetWindowRect(child, &bounds);
398 draw_border(dc, bounds);
399 }
400
401
window_proc(UINT message,WPARAM wparam,LPARAM lparam)402 LRESULT debugwin_info::window_proc(UINT message, WPARAM wparam, LPARAM lparam)
403 {
404 // handle a few messages
405 switch (message)
406 {
407 // paint: draw bezels as necessary
408 case WM_PAINT:
409 {
410 PAINTSTRUCT pstruct;
411 HDC dc = BeginPaint(m_wnd, &pstruct);
412 draw_contents(dc);
413 EndPaint(m_wnd, &pstruct);
414 break;
415 }
416
417 // keydown: handle debugger keys
418 case WM_KEYDOWN:
419 if (handle_key(wparam, lparam))
420 set_ignore_char_lparam(lparam);
421 break;
422
423 // char: ignore chars associated with keys we've handled
424 case WM_CHAR:
425 if (check_ignore_char_lparam(lparam))
426 {
427 if (waiting_for_debugger() || !seq_pressed())
428 return DefWindowProc(m_wnd, message, wparam, lparam);
429 }
430 break;
431
432 // activate: set the focus
433 case WM_ACTIVATE:
434 if (wparam != WA_INACTIVE)
435 set_default_focus();
436 break;
437
438 // get min/max info: set the minimum window size
439 case WM_GETMINMAXINFO:
440 {
441 auto *minmax = (MINMAXINFO *)lparam;
442 minmax->ptMinTrackSize.x = m_minwidth;
443 minmax->ptMinTrackSize.y = m_minheight;
444 minmax->ptMaxSize.x = minmax->ptMaxTrackSize.x = m_maxwidth;
445 minmax->ptMaxSize.y = minmax->ptMaxTrackSize.y = m_maxheight;
446 break;
447 }
448
449 // sizing: recompute child window locations
450 case WM_SIZE:
451 case WM_SIZING:
452 recompute_children();
453 InvalidateRect(m_wnd, nullptr, FALSE);
454 break;
455
456 // mouse wheel: forward to the first view
457 case WM_MOUSEWHEEL:
458 {
459 static int units_carryover = 0;
460
461 UINT lines_per_click;
462 if (!SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &lines_per_click, 0))
463 lines_per_click = 3;
464
465 int const units = GET_WHEEL_DELTA_WPARAM(wparam) + units_carryover;
466 int const clicks = units / WHEEL_DELTA;
467 units_carryover = units % WHEEL_DELTA;
468
469 int const delta = clicks * lines_per_click;
470 int viewnum = 0;
471 POINT point;
472
473 // figure out which view we are hovering over
474 GetCursorPos(&point);
475 ScreenToClient(m_wnd, &point);
476 HWND const child = ChildWindowFromPoint(m_wnd, point);
477 if (child)
478 {
479 for (viewnum = 0; viewnum < MAX_VIEWS; viewnum++)
480 {
481 if ((m_views[viewnum] != nullptr) && m_views[viewnum]->owns_window(child))
482 break;
483 }
484 if (viewnum == MAX_VIEWS)
485 break;
486 }
487
488 // send the appropriate message to this view's scrollbar
489 if (m_views[viewnum] != nullptr)
490 m_views[viewnum]->send_vscroll(delta);
491
492 break;
493 }
494
495 // activate: set the focus
496 case WM_INITMENU:
497 update_menu();
498 break;
499
500 // command: handle a comment
501 case WM_COMMAND:
502 if (!handle_command(wparam, lparam))
503 return DefWindowProc(m_wnd, message, wparam, lparam);
504 break;
505
506 // close: close the window if it's not the main console
507 case WM_CLOSE:
508 if (m_is_main_console)
509 {
510 debugger().hide_all();
511 machine().debugger().console().get_visible_cpu()->debug()->go();
512 }
513 else
514 {
515 destroy();
516 }
517 break;
518
519 // destroy: close down the window
520 case WM_NCDESTROY:
521 m_wnd = nullptr;
522 debugger().remove_window(*this);
523 break;
524
525 // everything else: defaults
526 default:
527 return DefWindowProc(m_wnd, message, wparam, lparam);
528 }
529
530 return 0;
531 }
532
533
create_standard_menubar()534 HMENU debugwin_info::create_standard_menubar()
535 {
536 // create the debug menu
537 HMENU const debugmenu = CreatePopupMenu();
538 if (debugmenu == nullptr)
539 return nullptr;
540 AppendMenu(debugmenu, MF_ENABLED, ID_NEW_MEMORY_WND, TEXT("New Memory Window\tCtrl+M"));
541 AppendMenu(debugmenu, MF_ENABLED, ID_NEW_DISASM_WND, TEXT("New Disassembly Window\tCtrl+D"));
542 AppendMenu(debugmenu, MF_ENABLED, ID_NEW_LOG_WND, TEXT("New Error Log Window\tCtrl+L"));
543 AppendMenu(debugmenu, MF_ENABLED, ID_NEW_POINTS_WND, TEXT("New (Break|Watch)points Window\tCtrl+B"));
544 AppendMenu(debugmenu, MF_DISABLED | MF_SEPARATOR, 0, TEXT(""));
545 AppendMenu(debugmenu, MF_ENABLED, ID_RUN, TEXT("Run\tF5"));
546 AppendMenu(debugmenu, MF_ENABLED, ID_RUN_AND_HIDE, TEXT("Run and Hide Debugger\tF12"));
547 AppendMenu(debugmenu, MF_ENABLED, ID_NEXT_CPU, TEXT("Run to Next CPU\tF6"));
548 AppendMenu(debugmenu, MF_ENABLED, ID_RUN_IRQ, TEXT("Run until Next Interrupt on This CPU\tF7"));
549 AppendMenu(debugmenu, MF_ENABLED, ID_RUN_VBLANK, TEXT("Run until Next VBLANK\tF8"));
550 AppendMenu(debugmenu, MF_DISABLED | MF_SEPARATOR, 0, TEXT(""));
551 AppendMenu(debugmenu, MF_ENABLED, ID_STEP, TEXT("Step Into\tF11"));
552 AppendMenu(debugmenu, MF_ENABLED, ID_STEP_OVER, TEXT("Step Over\tF10"));
553 AppendMenu(debugmenu, MF_ENABLED, ID_STEP_OUT, TEXT("Step Out\tShift+F11"));
554 AppendMenu(debugmenu, MF_ENABLED, ID_REWIND_STEP, TEXT("Rewind Step\tCtrl+F11"));
555 AppendMenu(debugmenu, MF_DISABLED | MF_SEPARATOR, 0, TEXT(""));
556 AppendMenu(debugmenu, MF_ENABLED, ID_SOFT_RESET, TEXT("Soft Reset\tF3"));
557 AppendMenu(debugmenu, MF_ENABLED, ID_HARD_RESET, TEXT("Hard Reset\tShift+F3"));
558 AppendMenu(debugmenu, MF_ENABLED, ID_EXIT, TEXT("Exit"));
559
560 // create the menu bar
561 HMENU const menubar = CreateMenu();
562 if (menubar == nullptr)
563 {
564 DestroyMenu(debugmenu);
565 return nullptr;
566 }
567 AppendMenu(menubar, MF_ENABLED | MF_POPUP, (UINT_PTR)debugmenu, TEXT("Debug"));
568
569 return menubar;
570 }
571
572
static_window_proc(HWND wnd,UINT message,WPARAM wparam,LPARAM lparam)573 LRESULT CALLBACK debugwin_info::static_window_proc(HWND wnd, UINT message, WPARAM wparam, LPARAM lparam)
574 {
575 if (message == WM_CREATE)
576 {
577 // set the info pointer
578 CREATESTRUCT const *const createinfo = (CREATESTRUCT *)lparam;
579 auto *const info = (debugwin_info *)createinfo->lpCreateParams;
580 SetWindowLongPtr(wnd, GWLP_USERDATA, (LONG_PTR)createinfo->lpCreateParams);
581 if (info->m_handler)
582 SetWindowLongPtr(wnd, GWLP_WNDPROC, (LONG_PTR)info->m_handler);
583 return 0;
584 }
585
586 auto *const info = (debugwin_info *)(uintptr_t)GetWindowLongPtr(wnd, GWLP_USERDATA);
587 if (info == nullptr)
588 return DefWindowProc(wnd, message, wparam, lparam);
589
590 assert((info->m_wnd == wnd) || (info->m_wnd == nullptr));
591 return info->window_proc(message, wparam, lparam);
592 }
593
594
register_window_class()595 void debugwin_info::register_window_class()
596 {
597 if (!s_window_class_registered)
598 {
599 WNDCLASS wc = { 0 };
600
601 // initialize the description of the window class
602 wc.lpszClassName = TEXT("MAMEDebugWindow");
603 wc.hInstance = GetModuleHandleUni();
604 wc.lpfnWndProc = &debugwin_info::static_window_proc;
605 wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
606 wc.hIcon = LoadIcon(wc.hInstance, MAKEINTRESOURCE(2));
607 wc.lpszMenuName = nullptr;
608 wc.hbrBackground = nullptr;
609 wc.style = 0;
610 wc.cbClsExtra = 0;
611 wc.cbWndExtra = 0;
612
613 UnregisterClass(wc.lpszClassName, wc.hInstance);
614
615 // register the class; fail if we can't
616 if (!RegisterClass(&wc))
617 fatalerror("Unable to register debug window class\n");
618
619 s_window_class_registered = true;
620 }
621 }
622