More cleanups in the keyboard drivers, especially the AT one (with new changes from 86Box.) Small update to fix the CD sound volume when using SoundBlaster.
1309 lines
30 KiB
C
1309 lines
30 KiB
C
/*
|
|
* VARCem Virtual ARchaeological Computer EMulator.
|
|
* An emulator of (mostly) x86-based PC systems and devices,
|
|
* using the ISA,EISA,VLB,MCA and PCI system buses, roughly
|
|
* spanning the era between 1981 and 1995.
|
|
*
|
|
* This file is part of the VARCem Project.
|
|
*
|
|
* Implement the user Interface module.
|
|
*
|
|
* Version: @(#)win_ui.c 1.0.35 2019/04/26
|
|
*
|
|
* Authors: Fred N. van Kempen, <decwiz@yahoo.com>
|
|
* Miran Grca, <mgrca8@gmail.com>
|
|
* Sarah Walker, <tommowalker@tommowalker.co.uk>
|
|
*
|
|
* Copyright 2017-2019 Fred N. van Kempen.
|
|
* Copyright 2016-2018 Miran Grca.
|
|
* Copyright 2008-2018 Sarah Walker.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the:
|
|
*
|
|
* Free Software Foundation, Inc.
|
|
* 59 Temple Place - Suite 330
|
|
* Boston, MA 02111-1307
|
|
* USA.
|
|
*/
|
|
#define UNICODE
|
|
#include <windows.h>
|
|
#include <windowsx.h>
|
|
#include <commctrl.h>
|
|
#include <commdlg.h>
|
|
#ifdef USE_HOST_CDROM
|
|
# include <dbt.h>
|
|
#endif
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <wchar.h>
|
|
#include "../emu.h"
|
|
#include "../version.h"
|
|
#include "../config.h"
|
|
#include "../device.h"
|
|
#include "../ui/ui.h"
|
|
#include "../plat.h"
|
|
#include "../devices/input/keyboard.h"
|
|
#include "../devices/input/mouse.h"
|
|
#include "../devices/video/video.h"
|
|
#include "../devices/cdrom/cdrom.h"
|
|
#include "win.h"
|
|
#include "resource.h"
|
|
|
|
|
|
#define TIMER_1SEC 1 /* ID of the one-second timer */
|
|
#define ICONS_MAX 256 /* number of icons we can cache */
|
|
|
|
|
|
/* Platform Public data, specific. */
|
|
HWND hwndMain = NULL, /* application main window */
|
|
hwndRender = NULL; /* machine render window */
|
|
HICON hIcon[ICONS_MAX]; /* icon data loaded from resources */
|
|
RECT oldclip; /* mouse rect */
|
|
|
|
|
|
/* Local data. */
|
|
static wchar_t wTitle[512];
|
|
static HHOOK hKeyboardHook;
|
|
static LONG_PTR input_orig_proc,
|
|
stbar_orig_proc;
|
|
static HWND input_orig_hwnd = NULL,
|
|
hwndSBAR = NULL; /* application status bar */
|
|
static HMENU menuMain = NULL, /* application menu bar */
|
|
menuSBAR = NULL, /* status bar menu bar */
|
|
*sb_menu = NULL;
|
|
static int sb_nparts = 0;
|
|
static const sbpart_t *sb_parts = NULL;
|
|
static int infocus = 1;
|
|
static int hook_enabled = 0;
|
|
static int save_window_pos = 0;
|
|
static int cruft_x = 0,
|
|
cruft_y = 0,
|
|
cruft_sb = 0;
|
|
static int kbd_flags, /* current keyboard flags */
|
|
win_kbd_flags; /* original host keyboard flags */
|
|
|
|
|
|
static VOID APIENTRY
|
|
PopupMenu(HWND hwnd, POINT pt, int part)
|
|
{
|
|
if (part >= (sb_nparts - 1)) return;
|
|
|
|
pt.x = part * SB_ICON_WIDTH; /* justify to the left */
|
|
pt.y = 1; /* justify to the top */
|
|
|
|
ClientToScreen(hwnd, (LPPOINT)&pt);
|
|
|
|
TrackPopupMenu(sb_menu[part],
|
|
TPM_LEFTALIGN | TPM_BOTTOMALIGN | TPM_LEFTBUTTON,
|
|
pt.x, pt.y, 0, hwndSBAR, NULL);
|
|
}
|
|
|
|
|
|
/* Handle messages for the Status Bar window. */
|
|
static WIN_RESULT CALLBACK
|
|
sb_dlg_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
RECT r;
|
|
POINT pt;
|
|
int idm, tag;
|
|
|
|
switch (message) {
|
|
case WM_COMMAND:
|
|
idm = LOWORD(wParam) & 0xff00; /* low 8 bits */
|
|
tag = LOWORD(wParam) & 0x00ff; /* high 8 bits */
|
|
ui_sb_menu_command(idm, tag);
|
|
return(0);
|
|
|
|
case WM_LBUTTONDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
GetClientRect(hwnd, (LPRECT)&r);
|
|
pt.x = GET_X_LPARAM(lParam);
|
|
pt.y = GET_Y_LPARAM(lParam);
|
|
if (PtInRect((LPRECT)&r, pt))
|
|
PopupMenu(hwnd, pt, (pt.x / SB_ICON_WIDTH));
|
|
break;
|
|
|
|
case WM_LBUTTONDBLCLK:
|
|
GetClientRect(hwnd, (LPRECT)&r);
|
|
pt.x = GET_X_LPARAM(lParam);
|
|
pt.y = GET_Y_LPARAM(lParam);
|
|
tag = (pt.x / SB_ICON_WIDTH);
|
|
if (PtInRect((LPRECT)&r, pt))
|
|
ui_sb_click(tag);
|
|
break;
|
|
|
|
default:
|
|
return(CallWindowProc((WNDPROC)stbar_orig_proc,
|
|
hwnd, message, wParam, lParam));
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
/* Create and set up the Status Bar window. */
|
|
static void
|
|
StatusBarCreate(uintptr_t id)
|
|
{
|
|
int borders[3];
|
|
intptr_t i;
|
|
int dw, dh;
|
|
RECT r;
|
|
|
|
/* Load our icons into the cache for faster access. */
|
|
for (i = 0; i < ICONS_MAX; i++)
|
|
hIcon[i] = LoadIconEx((PCTSTR)i);
|
|
|
|
GetWindowRect(hwndMain, &r);
|
|
dw = r.right - r.left;
|
|
dh = r.bottom - r.top;
|
|
|
|
/* Create the window. */
|
|
hwndSBAR = CreateWindow(STATUSCLASSNAME,
|
|
NULL,
|
|
WS_CHILD|WS_VISIBLE|SBT_TOOLTIPS|SBARS_SIZEGRIP,
|
|
0, dh,
|
|
dw, SB_HEIGHT,
|
|
hwndMain,
|
|
(HMENU)id,
|
|
hInstance,
|
|
NULL);
|
|
|
|
/* Retrieve the width of the border the status bar got. */
|
|
memset(borders, 0x00, sizeof(borders));
|
|
SendMessage(hwndSBAR, SB_GETBORDERS, 0, (LPARAM)borders);
|
|
if (borders[1] == 0)
|
|
borders[1] = 2;
|
|
cruft_sb = SB_HEIGHT + (2 * borders[1]) + (2 * SB_PADDING);
|
|
|
|
/* Replace the original procedure with ours. */
|
|
stbar_orig_proc = GetWindowLongPtr(hwndSBAR, GWLP_WNDPROC);
|
|
SetWindowLongPtr(hwndSBAR, GWLP_WNDPROC, (LONG_PTR)sb_dlg_proc);
|
|
|
|
SendMessage(hwndSBAR, SB_SETMINHEIGHT, (WPARAM)SB_HEIGHT, (LPARAM)0);
|
|
|
|
/* Load the dummy menu for this window. */
|
|
menuSBAR = LoadMenu(hInstance, MENU_SB_NAME);
|
|
|
|
/* Clear the menus, just in case.. */
|
|
sb_nparts = 0;
|
|
sb_menu = NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* Calculate the edges of all status bar parts.
|
|
*
|
|
* We do it here, and in the platform module, because not all
|
|
* systems implement them the same way Microsoft did. For that
|
|
* reason, the UI code actually specifies widths for all the
|
|
* parts, which we "convert" here.
|
|
*/
|
|
static void
|
|
StatusBarResize(int w)
|
|
{
|
|
int i, *edges;
|
|
int j, k, x;
|
|
int grippy;
|
|
|
|
/* If no parts yet, bail out. */
|
|
if (sb_nparts == 0) return;
|
|
|
|
/* Create local 'edges' array and populate it. */
|
|
edges = (int *)mem_alloc(sb_nparts * sizeof(int));
|
|
memset(edges, 0x00, sb_nparts * sizeof(int));
|
|
|
|
/* If we have a grippy on the status bar... */
|
|
//FIXME: how to determine this programmatically? --FvK
|
|
grippy = 8;
|
|
|
|
/*
|
|
* We start at the leftmost edge of the window, and
|
|
* then move towards the right. The parameter given
|
|
* to Windows is the *right* side edge!
|
|
*
|
|
* No variable-part offset yet.
|
|
*/
|
|
k = x = 0;
|
|
for (i = 0; i < sb_nparts; i++) {
|
|
if (sb_parts[i].width == 0) {
|
|
/*
|
|
* This is the variable-length part, which
|
|
* extends to the right edge of the window.
|
|
* So, we must get the window length, and
|
|
* then calculate length for this part, and
|
|
* then the position for all parts following.
|
|
*
|
|
* First, see how much space we need for the
|
|
* parts following this one.
|
|
*/
|
|
k = w - x - grippy;
|
|
for (j = i + 1; j < sb_nparts; j++)
|
|
k -= sb_parts[j].width;
|
|
|
|
/* OK, now we know how wide this part can be. */
|
|
edges[i] = x + k;
|
|
} else {
|
|
x += sb_parts[i].width;
|
|
if (i == (sb_nparts - 1))
|
|
x += grippy;
|
|
edges[i] = k + x;
|
|
}
|
|
}
|
|
|
|
/* Send the list to the status bar window. */
|
|
SendMessage(hwndSBAR, SB_SETPARTS, (WPARAM)sb_nparts, (LPARAM)edges);
|
|
|
|
free(edges);
|
|
}
|
|
|
|
|
|
static LRESULT CALLBACK
|
|
LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
BOOL bControlKeyDown;
|
|
KBDLLHOOKSTRUCT *p;
|
|
|
|
if (nCode < 0 || nCode != HC_ACTION || (!mouse_capture && !vid_fullscreen))
|
|
return(CallNextHookEx(hKeyboardHook, nCode, wParam, lParam));
|
|
|
|
p = (KBDLLHOOKSTRUCT*)lParam;
|
|
|
|
/* disable alt-tab */
|
|
if (p->vkCode == VK_TAB && p->flags & LLKHF_ALTDOWN) return(1);
|
|
|
|
/* disable alt-space */
|
|
if (p->vkCode == VK_SPACE && p->flags & LLKHF_ALTDOWN) return(1);
|
|
|
|
/* disable alt-escape */
|
|
if (p->vkCode == VK_ESCAPE && p->flags & LLKHF_ALTDOWN) return(1);
|
|
|
|
/* disable windows keys */
|
|
if((p->vkCode == VK_LWIN) || (p->vkCode == VK_RWIN)) return(1);
|
|
|
|
/* checks ctrl key pressed */
|
|
bControlKeyDown = GetAsyncKeyState(VK_CONTROL)>>((sizeof(SHORT)*8)-1);
|
|
|
|
/* disable ctrl-escape */
|
|
if (p->vkCode == VK_ESCAPE && bControlKeyDown) return(1);
|
|
|
|
return(CallNextHookEx(hKeyboardHook, nCode, wParam, lParam));
|
|
}
|
|
|
|
|
|
#ifdef USE_HOST_CDROM
|
|
static void
|
|
HandleMediaEvent(WPARAM wParam, LPARAM lParam)
|
|
{
|
|
PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
|
|
PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;
|
|
int f = -1, i;
|
|
char d;
|
|
|
|
switch (wParam) {
|
|
case DBT_DEVICEARRIVAL:
|
|
if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) {
|
|
if (lpdbv->dbcv_flags & DBTF_MEDIA)
|
|
f = 1;
|
|
}
|
|
break;
|
|
|
|
case DBT_DEVICEREMOVECOMPLETE:
|
|
if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) {
|
|
if (lpdbv->dbcv_flags & DBTF_MEDIA)
|
|
f = 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* We don't care.. */
|
|
break;
|
|
}
|
|
|
|
/* Now report all 'changed' drives to the CD-ROM handler. */
|
|
if (f != -1) {
|
|
for (i = 0; i < 26; i++) {
|
|
if (lpdbv->dbcv_unitmask & 1) {
|
|
/* Get the drive letter. */
|
|
d = 'A' + i;
|
|
|
|
cdrom_notify(&d, f);
|
|
}
|
|
lpdbv->dbcv_unitmask >>= 1;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
static LRESULT CALLBACK
|
|
MainWindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
DWORD flags;
|
|
RECT rect;
|
|
int x, y;
|
|
int idm;
|
|
|
|
switch (message) {
|
|
case WM_CREATE:
|
|
SetTimer(hwnd, TIMER_1SEC, 1000, NULL);
|
|
hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL,
|
|
LowLevelKeyboardProc,
|
|
GetModuleHandle(NULL), 0);
|
|
hook_enabled = 1;
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
UpdateWindow(hwnd);
|
|
idm = LOWORD(wParam);
|
|
|
|
/* Let the general UI handle it first, and then we do. */
|
|
if (! ui_menu_command(idm)) switch(idm) {
|
|
case IDM_EXIT:
|
|
PostQuitMessage(0);
|
|
break;
|
|
|
|
case IDM_RESIZE:
|
|
/* Set up for resizing if configured. */
|
|
flags = WS_OVERLAPPEDWINDOW;
|
|
if (! vid_resize)
|
|
flags &= ~(WS_SIZEBOX | WS_THICKFRAME |
|
|
WS_MAXIMIZEBOX);
|
|
SetWindowLongPtr(hwnd, GWL_STYLE, flags);
|
|
|
|
/* Main Window. */
|
|
GetWindowRect(hwnd, &rect);
|
|
get_screen_size_natural(&x, &y);
|
|
MoveWindow(hwnd, rect.left, rect.top,
|
|
x + cruft_x, y + cruft_y + cruft_sb,
|
|
TRUE);
|
|
|
|
break;
|
|
|
|
case IDM_REMEMBER:
|
|
GetWindowRect(hwnd, &rect);
|
|
if (window_remember) {
|
|
window_x = rect.left;
|
|
window_y = rect.top;
|
|
window_w = rect.right - rect.left;
|
|
window_h = rect.bottom - rect.top;
|
|
}
|
|
config_save();
|
|
break;
|
|
}
|
|
return(0);
|
|
|
|
case WM_ENTERMENULOOP:
|
|
break;
|
|
|
|
case WM_SIZE:
|
|
/* Note: this is the *client area* size!! */
|
|
x = (int)(lParam & 0xffff);
|
|
y = (int)(lParam >> 16);
|
|
if (cruft_x == 0) {
|
|
/* Determine the window cruft. */
|
|
cruft_x = (scrnsz_x - x);
|
|
cruft_y = (scrnsz_y - y);
|
|
|
|
/* Update window size with cruft. */
|
|
x += cruft_x;
|
|
y += (cruft_y + cruft_sb);
|
|
}
|
|
y -= cruft_sb;
|
|
|
|
/* Request a re-size if needed. */
|
|
if ((x != scrnsz_x) || (y != scrnsz_y)) doresize = 1;
|
|
|
|
/* Set the new panel size. */
|
|
scrnsz_x = x;
|
|
scrnsz_y = y;
|
|
|
|
/* Update the render window. */
|
|
if (hwndRender != NULL)
|
|
MoveWindow(hwndRender, 0, 0, scrnsz_x, scrnsz_y, TRUE);
|
|
|
|
/* Update the Status bar. */
|
|
MoveWindow(hwndSBAR, 0, scrnsz_y, scrnsz_x, cruft_sb, TRUE);
|
|
StatusBarResize(scrnsz_x);
|
|
|
|
/* Update the renderer if needed. */
|
|
vidapi_resize(scrnsz_x, scrnsz_y);
|
|
|
|
/* Re-clip the mouse area if needed. */
|
|
if (mouse_capture) {
|
|
GetWindowRect(hwndRender, &rect);
|
|
|
|
ClipCursor(&rect);
|
|
}
|
|
|
|
if (window_remember) {
|
|
GetWindowRect(hwndMain, &rect);
|
|
|
|
window_x = rect.left;
|
|
window_y = rect.top;
|
|
window_w = rect.right - rect.left;
|
|
window_h = rect.bottom - rect.top;
|
|
save_window_pos = 1;
|
|
|
|
config_save();
|
|
}
|
|
break;
|
|
|
|
case WM_MOVE:
|
|
/*
|
|
* If window is not resizable, then tell the main thread * to resize it, as sometimes, moves can mess up the window
|
|
* size.
|
|
*/
|
|
if (! vid_resize)
|
|
doresize = 1;
|
|
|
|
if (window_remember) {
|
|
GetWindowRect(hwndMain, &rect);
|
|
|
|
window_x = rect.left;
|
|
window_y = rect.top;
|
|
window_w = rect.right - rect.left;
|
|
window_h = rect.bottom - rect.top;
|
|
save_window_pos = 1;
|
|
}
|
|
break;
|
|
|
|
case WM_TIMER:
|
|
if (wParam == TIMER_1SEC)
|
|
pc_onesec();
|
|
break;
|
|
|
|
case WM_RESET_D3D:
|
|
plat_startblit();
|
|
vidapi_reset();
|
|
plat_endblit();
|
|
break;
|
|
|
|
case WM_LEAVE_FS:
|
|
ui_fullscreen(0);
|
|
config_save();
|
|
break;
|
|
|
|
case WM_KEYDOWN:
|
|
case WM_KEYUP:
|
|
case WM_SYSKEYDOWN:
|
|
case WM_SYSKEYUP:
|
|
return(0);
|
|
|
|
case WM_DESTROY:
|
|
UnhookWindowsHookEx(hKeyboardHook);
|
|
KillTimer(hwnd, TIMER_1SEC);
|
|
PostQuitMessage(0);
|
|
break;
|
|
|
|
case WM_SHOW_CFG:
|
|
pc_pause(1);
|
|
if (dlg_settings(1) == 2)
|
|
pc_reset_hard_init();
|
|
pc_pause(0);
|
|
break;
|
|
|
|
case WM_PAUSE:
|
|
pc_pause(dopause ^ 1);
|
|
menu_set_item(IDM_PAUSE, dopause);
|
|
break;
|
|
|
|
case WM_HARD_RESET:
|
|
pc_reset(1);
|
|
break;
|
|
|
|
case WM_CTRLALTDEL:
|
|
pc_reset(0);
|
|
break;
|
|
|
|
case WM_SHUTDOWN:
|
|
PostQuitMessage(0);
|
|
break;
|
|
|
|
#ifdef USE_HOST_CDROM
|
|
case WM_DEVICECHANGE:
|
|
HandleMediaEvent(wParam, lParam);
|
|
break;
|
|
#endif
|
|
|
|
case WM_SYSCOMMAND:
|
|
/*
|
|
* Disable ALT key *ALWAYS*,
|
|
* I don't think there's any use for
|
|
* reaching the menu that way.
|
|
*/
|
|
if (wParam == SC_KEYMENU && HIWORD(lParam) <= 0) {
|
|
return 0; /*disable ALT key for menu*/
|
|
}
|
|
|
|
default:
|
|
return(DefWindowProc(hwnd, message, wParam, lParam));
|
|
}
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
|
|
/* Dummy window procedure, used by D3D when in full-screen mode. */
|
|
static LRESULT CALLBACK
|
|
SubWindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
return(DefWindowProc(hwnd, message, wParam, lParam));
|
|
}
|
|
|
|
|
|
/* Catch WM_INPUT messages for 'current focus' window. */
|
|
static WIN_RESULT CALLBACK
|
|
input_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (message) {
|
|
case WM_INPUT:
|
|
keyboard_handle(lParam, infocus);
|
|
break;
|
|
|
|
case WM_SETFOCUS:
|
|
infocus = 1;
|
|
if (! hook_enabled) {
|
|
hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL,
|
|
LowLevelKeyboardProc,
|
|
GetModuleHandle(NULL),
|
|
0);
|
|
hook_enabled = 1;
|
|
}
|
|
|
|
/* Update host keyboard state if needed. */
|
|
if (kbd_flags != win_kbd_flags)
|
|
plat_set_kbd_state(kbd_flags);
|
|
break;
|
|
|
|
case WM_KILLFOCUS:
|
|
infocus = 0;
|
|
ui_mouse_capture(0);
|
|
if (hook_enabled) {
|
|
UnhookWindowsHookEx(hKeyboardHook);
|
|
hook_enabled = 0;
|
|
}
|
|
|
|
/* Update host keyboard state if needed. */
|
|
if (kbd_flags != win_kbd_flags)
|
|
plat_set_kbd_state(win_kbd_flags);
|
|
break;
|
|
|
|
case WM_LBUTTONUP:
|
|
if (! vid_fullscreen)
|
|
ui_mouse_capture(1);
|
|
break;
|
|
|
|
case WM_MBUTTONUP:
|
|
if (mouse_get_buttons() < 3)
|
|
ui_mouse_capture(0);
|
|
break;
|
|
|
|
default:
|
|
return(CallWindowProc((WNDPROC)input_orig_proc,
|
|
hwnd, message, wParam, lParam));
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
/* Set up a handler for the 'currently active' window. */
|
|
void
|
|
plat_set_input(HWND h)
|
|
{
|
|
/* If needed, rest the old one first. */
|
|
if (input_orig_hwnd != NULL) {
|
|
SetWindowLongPtr(input_orig_hwnd, GWLP_WNDPROC,
|
|
(LONG_PTR)input_orig_proc);
|
|
}
|
|
|
|
/* Redirect the window procedure so we can catch WM_INPUT. */
|
|
input_orig_proc = GetWindowLongPtr(h, GWLP_WNDPROC);
|
|
input_orig_hwnd = h;
|
|
SetWindowLongPtr(h, GWLP_WNDPROC, (LONG_PTR)input_proc);
|
|
}
|
|
|
|
|
|
/* UI: reset the lowlevel (platform) UI. */
|
|
void
|
|
ui_plat_reset(void)
|
|
{
|
|
/* Load the main menu from the appropriate DLL. */
|
|
menuMain = LoadMenu(plat_lang_dll(), MENU_MAIN_NAME);
|
|
|
|
SetMenu(hwndMain, menuMain);
|
|
|
|
/* Load the statusbar menu. */
|
|
menuSBAR = LoadMenu(hInstance, MENU_SB_NAME);
|
|
}
|
|
|
|
|
|
/* UI: initialize the Win32 User Interface module. */
|
|
int
|
|
ui_init(int nCmdShow)
|
|
{
|
|
WCHAR title[200];
|
|
WNDCLASSEX wincl; /* buffer for main window's class */
|
|
INITCOMMONCONTROLSEX icex; /* common controls, new style */
|
|
RAWINPUTDEVICE ridev; /* RawInput device */
|
|
MSG messages; /* received-messages buffer */
|
|
HACCEL haccel; /* handle to accelerator table */
|
|
DWORD flags;
|
|
int ret;
|
|
|
|
/* Register the new version of the Common Controls. */
|
|
memset(&icex, 0x00, sizeof(INITCOMMONCONTROLSEX));
|
|
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
|
|
icex.dwICC = ICC_LISTVIEW_CLASSES;
|
|
InitCommonControlsEx(&icex);
|
|
|
|
if (settings_only) {
|
|
if (! pc_init()) {
|
|
/* Dang, no ROMs found at all! */
|
|
ui_msgbox(MBX_ERROR, (wchar_t *)IDS_ERR_NOROMS);
|
|
return(6);
|
|
}
|
|
|
|
(void)dlg_settings(0);
|
|
|
|
return(0);
|
|
}
|
|
|
|
/* Create our main window's class and register it. */
|
|
wincl.hInstance = hInstance;
|
|
wincl.lpszClassName = CLASS_NAME;
|
|
wincl.lpfnWndProc = MainWindowProcedure;
|
|
wincl.style = CS_DBLCLKS; /* Catch double-clicks */
|
|
wincl.cbSize = sizeof(WNDCLASSEX);
|
|
wincl.hIcon = LoadIcon(hInstance, (LPCTSTR)ICON_MAIN);
|
|
wincl.hIconSm = LoadIcon(hInstance, (LPCTSTR)ICON_MAIN);
|
|
wincl.hCursor = NULL;
|
|
wincl.lpszMenuName = NULL;
|
|
wincl.cbClsExtra = 0;
|
|
wincl.cbWndExtra = 0;
|
|
wincl.hbrBackground = CreateSolidBrush(RGB(0,0,0));
|
|
if (! RegisterClassEx(&wincl))
|
|
return(2);
|
|
|
|
/* Register a class for the Fullscreen renderer window. */
|
|
wincl.lpszClassName = FS_CLASS_NAME;
|
|
wincl.lpfnWndProc = SubWindowProcedure;
|
|
if (! RegisterClassEx(&wincl))
|
|
return(2);
|
|
|
|
/* Set up main window for resizing if configured. */
|
|
flags = WS_OVERLAPPEDWINDOW;
|
|
if (! vid_resize)
|
|
flags &= ~(WS_SIZEBOX | WS_THICKFRAME | WS_MAXIMIZEBOX);
|
|
|
|
/*
|
|
* Create our main window.
|
|
*
|
|
* We want our (initial) render panel to be a certain
|
|
* size (default 640x480), and we have no idea what
|
|
* our window decorations are, size-wise.
|
|
*
|
|
* Rather than depending on the GetSystemMetrics API
|
|
* calls (which return incorrect data as of Vista+),
|
|
* the simplest trick is to just set the desired window
|
|
* size, and create the window. The first WM_SIZE
|
|
* message will indicate the client size left after
|
|
* creating all the decorations, and the difference
|
|
* is the decoration overhead ('cruft') we need to
|
|
* always keep in mind when changing the window size.
|
|
*/
|
|
swprintf(title, sizeof_w(title), L"%s %s", EMU_NAME, emu_version);
|
|
hwndMain = CreateWindowEx(
|
|
#if 0
|
|
WS_EX_LAYOUTRTL | WS_EX_RIGHT,
|
|
#else
|
|
0,
|
|
#endif
|
|
CLASS_NAME, /* class name */
|
|
title, /* Title Text */
|
|
flags, /* style flags */
|
|
CW_USEDEFAULT, CW_USEDEFAULT, /* no preset position */
|
|
scrnsz_x, scrnsz_y, /* window size in pixels */
|
|
HWND_DESKTOP, /* child of desktop */
|
|
NULL, /* menu */
|
|
hInstance, /* Program Instance handler */
|
|
NULL); /* no Window Creation data */
|
|
|
|
/* Create the status bar window. */
|
|
StatusBarCreate(IDC_STATBAR);
|
|
|
|
ui_window_title(title);
|
|
|
|
/* Reset all menus to their defaults. */
|
|
ui_reset();
|
|
|
|
/* Move to the last-saved position if needed. */
|
|
if (window_remember)
|
|
MoveWindow(hwndMain, window_x, window_y, window_w, window_h, FALSE);
|
|
|
|
/* Load the accelerator table */
|
|
haccel = LoadAccelerators(hInstance, ACCEL_NAME);
|
|
if (haccel == NULL) {
|
|
ui_msgbox(MBX_CONFIG, (wchar_t *)IDS_ERR_ACCEL);
|
|
return(3);
|
|
}
|
|
|
|
/* Make the window visible on the screen. */
|
|
ShowWindow(hwndMain, nCmdShow);
|
|
|
|
/* Initialize the RawInput (keyboard) module. */
|
|
memset(&ridev, 0x00, sizeof(ridev));
|
|
ridev.usUsagePage = 0x01;
|
|
ridev.usUsage = 0x06;
|
|
ridev.dwFlags = RIDEV_NOHOTKEYS;
|
|
ridev.hwndTarget = NULL; /* current focus window */
|
|
if (! RegisterRawInputDevices(&ridev, 1, sizeof(ridev))) {
|
|
ui_msgbox(MBX_CONFIG, (wchar_t *)IDS_ERR_INPUT);
|
|
return(4);
|
|
}
|
|
keyboard_getkeymap();
|
|
|
|
/* Set up the main window for RawInput. */
|
|
plat_set_input(hwndMain);
|
|
|
|
/* Grab the current state of the (host) keyboard. */
|
|
win_kbd_flags = plat_get_kbd_state();
|
|
kbd_flags = win_kbd_flags;
|
|
|
|
/* Create the Machine Rendering window. */
|
|
hwndRender = CreateWindow(L"STATIC",
|
|
NULL,
|
|
WS_CHILD|SS_BITMAP,
|
|
0, 0,
|
|
scrnsz_x, scrnsz_y,
|
|
hwndMain,
|
|
NULL,
|
|
hInstance,
|
|
NULL);
|
|
|
|
/* That looks good, now continue setting up the machine. */
|
|
switch (pc_init()) {
|
|
case -1: /* General failure during init, give up. */
|
|
return(6);
|
|
|
|
case 0: /* Configuration error, user wants to exit. */
|
|
return(0);
|
|
|
|
case 1: /* All good. */
|
|
break;
|
|
|
|
case 2: /* Configuration error, user wants to re-config. */
|
|
if (dlg_settings(0)) {
|
|
/* Save the new configuration to file. */
|
|
config_save();
|
|
|
|
/* Remind them to restart. */
|
|
ui_msgbox(MBX_INFO, (wchar_t *)IDS_MSG_RESTART);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/* Activate the render window, this will also set the screen size. */
|
|
if (hwndRender != NULL)
|
|
MoveWindow(hwndRender, 0, 0, scrnsz_x, scrnsz_y, TRUE);
|
|
|
|
/* Initialize the configured Video API. */
|
|
again:
|
|
if (! vidapi_set(vid_api)) {
|
|
/*
|
|
* Selected renderer is not available.
|
|
*
|
|
* This can happen if one of the optional renderers
|
|
* was selected previously, but is currently not
|
|
* available for whatever reason.
|
|
*
|
|
* Inform the user, and ask if they want to reset
|
|
* to the system default one instead.
|
|
*/
|
|
swprintf(title, sizeof_w(title),
|
|
get_string(IDS_ERR_NORENDR),
|
|
vidapi_get_internal_name(vid_api));
|
|
if (ui_msgbox(MBX_CONFIG, title) != 0) {
|
|
/* Nope, they don't, so just exit. */
|
|
return(5);
|
|
}
|
|
|
|
/* OK, reset to the default one and retry. */
|
|
vid_api = vidapi_from_internal_name("default");
|
|
goto again;
|
|
}
|
|
|
|
/* Initialize the rendering window, or fullscreen. */
|
|
if (start_in_fullscreen)
|
|
ui_fullscreen(1);
|
|
|
|
/* Initialize the mouse module. */
|
|
win_mouse_init();
|
|
|
|
/* Fire up the machine. */
|
|
pc_reset_hard();
|
|
|
|
/* Set the PAUSE mode depending on the renderer. */
|
|
pc_pause(0);
|
|
|
|
#ifdef USE_MANAGER
|
|
/*
|
|
* If so requested via the command line, inform the
|
|
* application that started us of our HWND, using the
|
|
* the hWnd and unique ID the application has given
|
|
* us.
|
|
*/
|
|
if (source_hwnd)
|
|
PostMessage((HWND) (uintptr_t) source_hwnd,
|
|
WM_SEND_HWND, (WPARAM)unique_id, (LPARAM)hwndMain);
|
|
#endif
|
|
|
|
/*
|
|
* Everything has been configured, and all seems to work,
|
|
* so now it is time to start the main thread to do some
|
|
* real work, and we will hang in here, dealing with the
|
|
* UI until we're done.
|
|
*/
|
|
plat_start();
|
|
|
|
/* Run the message loop. It will run until GetMessage() returns 0 */
|
|
while (! quited) {
|
|
ret = GetMessage(&messages, NULL, 0, 0);
|
|
if ((ret == 0) || quited) break;
|
|
|
|
if (ret == -1) {
|
|
fatal("ret is -1\n");
|
|
}
|
|
|
|
if (messages.message == WM_QUIT) {
|
|
quited = 1;
|
|
break;
|
|
}
|
|
|
|
if (! TranslateAccelerator(hwndMain, haccel, &messages)) {
|
|
TranslateMessage(&messages);
|
|
DispatchMessage(&messages);
|
|
}
|
|
|
|
if (mouse_capture && keyboard_ismsexit()) {
|
|
/* Release the in-app mouse. */
|
|
ui_mouse_capture(0);
|
|
}
|
|
|
|
if (vid_fullscreen && keyboard_isfsexit()) {
|
|
/* Signal "exit fullscreen mode". */
|
|
/* INFO("leave full screen though key combination\n"); */
|
|
ui_fullscreen(0);
|
|
}
|
|
}
|
|
|
|
timeEndPeriod(1);
|
|
|
|
if (mouse_capture)
|
|
ui_mouse_capture(0);
|
|
|
|
/* Close down the emulator. */
|
|
plat_stop();
|
|
|
|
UnregisterClass(CLASS_NAME, hInstance);
|
|
UnregisterClass(FS_CLASS_NAME, hInstance);
|
|
|
|
win_mouse_close();
|
|
|
|
/* Restore host keyboard state. */
|
|
plat_set_kbd_state(win_kbd_flags);
|
|
|
|
return((int)messages.wParam);
|
|
}
|
|
|
|
|
|
/* UI support: tell the UI about a new screen resolution. */
|
|
void
|
|
ui_resize(int x, int y)
|
|
{
|
|
RECT r;
|
|
|
|
/* First, see if we should resize the UI window. */
|
|
if (vid_resize) return;
|
|
|
|
video_blit_wait();
|
|
|
|
/* Re-position and re-size the main window. */
|
|
GetWindowRect(hwndMain, &r);
|
|
MoveWindow(hwndMain, r.left, r.top,
|
|
x+cruft_x, y+cruft_y+cruft_sb, TRUE);
|
|
}
|
|
|
|
|
|
/* UI support: update the application's title bar. */
|
|
wchar_t *
|
|
ui_window_title(const wchar_t *s)
|
|
{
|
|
if (! vid_fullscreen) {
|
|
if (s != NULL)
|
|
wcscpy(wTitle, s);
|
|
else
|
|
s = wTitle;
|
|
|
|
SetWindowText(hwndMain, s);
|
|
} else {
|
|
if (s == NULL)
|
|
s = wTitle;
|
|
}
|
|
|
|
return((wchar_t *)s);
|
|
}
|
|
|
|
|
|
/* UI support: set cursor visible or not. */
|
|
void
|
|
ui_show_cursor(int val)
|
|
{
|
|
static int vis = -1;
|
|
|
|
if (val == vis)
|
|
return;
|
|
|
|
if (val == 0) {
|
|
while (1) {
|
|
if (ShowCursor(FALSE) < 0) break;
|
|
}
|
|
} else {
|
|
ShowCursor(TRUE);
|
|
}
|
|
|
|
vis = val;
|
|
}
|
|
|
|
|
|
/* UI support: show or hide the render window. */
|
|
void
|
|
ui_show_render(int on)
|
|
{
|
|
#ifndef USE_WX
|
|
if (on)
|
|
ShowWindow(hwndRender, SW_SHOW);
|
|
else
|
|
ShowWindow(hwndRender, SW_HIDE);
|
|
#endif
|
|
}
|
|
|
|
|
|
/* Set the desired fullscreen/windowed mode. */
|
|
void
|
|
plat_fullscreen(int on)
|
|
{
|
|
win_mouse_close();
|
|
|
|
#ifdef USE_WX
|
|
wx_set_fullscreen(on);
|
|
#endif
|
|
|
|
win_mouse_init();
|
|
}
|
|
|
|
|
|
/* Platform support for the PAUSE action. */
|
|
void
|
|
plat_pause(int paused)
|
|
{
|
|
#ifdef USE_MANAGER
|
|
/* Send the WM to a manager if needed. */
|
|
if (source_hwnd)
|
|
PostMessage((HWND) (uintptr_t) source_hwnd,
|
|
WM_SENDSTATUS, (WPARAM)paused, (LPARAM)hwndMain);
|
|
#endif
|
|
}
|
|
|
|
|
|
/* Grab the current keyboard state. */
|
|
int
|
|
plat_get_kbd_state(void)
|
|
{
|
|
BYTE kbdata[256];
|
|
int ret = 0x00;
|
|
|
|
/* Grab the system keyboard state. */
|
|
memset(kbdata, 0x00, sizeof(kbdata));
|
|
GetKeyboardState(kbdata);
|
|
|
|
/* Pick out the keys we are interested in. */
|
|
if (kbdata[VK_NUMLOCK]) ret |= KBD_FLAG_NUM;
|
|
if (kbdata[VK_CAPITAL]) ret |= KBD_FLAG_CAPS;
|
|
if (kbdata[VK_SCROLL]) ret |= KBD_FLAG_SCROLL;
|
|
if (kbdata[VK_PAUSE]) ret |= KBD_FLAG_PAUSE;
|
|
|
|
return(ret);
|
|
}
|
|
|
|
|
|
/* Set the active keyboard state. */
|
|
void
|
|
plat_set_kbd_state(int flags)
|
|
{
|
|
INPUT kbdata[2];
|
|
|
|
memset(kbdata, 0x00, sizeof(kbdata));
|
|
kbdata[0].type = kbdata[1].type = INPUT_KEYBOARD;
|
|
kbdata[1].ki.dwFlags = KEYEVENTF_KEYUP;
|
|
|
|
INFO("WIN kbd_state(%04x) [%04x] focus=%d\n" , flags, kbd_flags, infocus);
|
|
if (kbd_flags != flags) {
|
|
/* Save the new flags. */
|
|
kbd_flags = flags;
|
|
|
|
/* If we are active, update the host keyboard state. */
|
|
if (infocus) {
|
|
/* Pick out the keys we are interested in. */
|
|
if (flags & KBD_FLAG_NUM) {
|
|
kbdata[0].ki.wVk = kbdata[1].ki.wVk = VK_NUMLOCK;
|
|
if (SendInput(2, kbdata, sizeof(INPUT)) == 0) {
|
|
ERRLOG("WIN: cannot SendInput(%i): error %i\n",
|
|
kbdata[0].ki.wVk, GetLastError());
|
|
}
|
|
}
|
|
|
|
if (flags & KBD_FLAG_CAPS) {
|
|
kbdata[0].ki.wVk = kbdata[1].ki.wVk = VK_CAPITAL;
|
|
if (SendInput(2, kbdata, sizeof(INPUT)) == 0) {
|
|
ERRLOG("WIN: cannot SendInput(%i): error %i\n",
|
|
kbdata[0].ki.wVk, GetLastError());
|
|
}
|
|
}
|
|
|
|
if (flags & KBD_FLAG_SCROLL) {
|
|
kbdata[0].ki.wVk = kbdata[1].ki.wVk = VK_SCROLL;
|
|
if (SendInput(2, kbdata, sizeof(INPUT)) == 0) {
|
|
ERRLOG("WIN: cannot SendInput(%i): error %i\n",
|
|
kbdata[0].ki.wVk, GetLastError());
|
|
}
|
|
}
|
|
|
|
if (flags & KBD_FLAG_PAUSE) {
|
|
kbdata[0].ki.wVk = kbdata[1].ki.wVk = VK_PAUSE;
|
|
if (SendInput(2, kbdata, sizeof(INPUT)) == 0) {
|
|
ERRLOG("WIN: cannot SendInput(%i): error %i\n",
|
|
kbdata[0].ki.wVk, GetLastError());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* UI support: enable or disable mouse clipping. */
|
|
void
|
|
plat_mouse_capture(int on)
|
|
{
|
|
RECT rect;
|
|
|
|
if ((on == -1) || !on) {
|
|
/* Disable the in-app mouse. */
|
|
if (on == -1)
|
|
GetClipCursor(&oldclip);
|
|
else
|
|
ClipCursor(&oldclip);
|
|
} else if (on && !mouse_capture) {
|
|
/* Enable the in-app mouse. */
|
|
GetClipCursor(&oldclip);
|
|
GetWindowRect(hwndRender, &rect);
|
|
ClipCursor(&rect);
|
|
}
|
|
}
|
|
|
|
|
|
/* UI support: add an item to a menu. */
|
|
void
|
|
menu_add_item(int idm, int type, int new_id, const wchar_t *str)
|
|
{
|
|
MENUITEMINFO info;
|
|
HMENU menu;
|
|
|
|
/* Get the handle for the intended (sub)menu. */
|
|
memset(&info, 0x00, sizeof(info));
|
|
info.cbSize = sizeof(info);
|
|
info.fMask = MIIM_SUBMENU;
|
|
if (! GetMenuItemInfo(menuMain, idm, FALSE, &info)) {
|
|
ERRLOG("UI: cannot find submenu %d\n", idm);
|
|
return;
|
|
}
|
|
menu = info.hSubMenu;
|
|
|
|
switch(type) {
|
|
case ITEM_SEPARATOR:
|
|
AppendMenu(menu, MF_SEPARATOR, 0, NULL);
|
|
break;
|
|
|
|
default:
|
|
AppendMenu(menu, MF_STRING, new_id, str);
|
|
}
|
|
|
|
/* We changed the menu bar, so redraw it. */
|
|
DrawMenuBar(hwndMain);
|
|
}
|
|
|
|
|
|
/* UI support: enable or disable a menu item. */
|
|
void
|
|
menu_enable_item(int idm, int val)
|
|
{
|
|
EnableMenuItem(menuMain, idm, (val) ? MF_ENABLED : MF_DISABLED);
|
|
}
|
|
|
|
|
|
/* UI support: set/check a menu item. */
|
|
void
|
|
menu_set_item(int idm, int val)
|
|
{
|
|
CheckMenuItem(menuMain, idm, val ? MF_CHECKED : MF_UNCHECKED);
|
|
}
|
|
|
|
|
|
/* UI support: set a radio group menu item. */
|
|
void
|
|
menu_set_radio_item(int idm, int num, int val)
|
|
{
|
|
int i;
|
|
|
|
if (val < 0) return;
|
|
|
|
for (i = 0; i < num; i++)
|
|
menu_set_item(idm + i, 0);
|
|
|
|
menu_set_item(idm + val, 1);
|
|
}
|
|
|
|
|
|
/* UI support: delete all menus from the status bar. */
|
|
static void
|
|
menu_destroy(void)
|
|
{
|
|
int i;
|
|
|
|
if (!sb_nparts || (sb_menu == NULL)) return;
|
|
|
|
for (i = 0; i < sb_nparts; i++) {
|
|
if (sb_menu[i] != NULL)
|
|
DestroyMenu(sb_menu[i]);
|
|
}
|
|
|
|
free(sb_menu);
|
|
|
|
sb_menu = NULL;
|
|
}
|
|
|
|
|
|
/* UI support: reset the status bar. */
|
|
void
|
|
sb_setup(int nparts, const sbpart_t *data)
|
|
{
|
|
RECT r;
|
|
|
|
/* Set the info. */
|
|
sb_nparts = nparts;
|
|
sb_parts = data;
|
|
|
|
if (sb_menu != NULL) {
|
|
menu_destroy();
|
|
}
|
|
|
|
/* First, get width of the status bar window. */
|
|
if (nparts > 0) {
|
|
/* Calculate and set up the parts. */
|
|
GetWindowRect(hwndSBAR, &r);
|
|
StatusBarResize(r.right - r.left);
|
|
|
|
/* Allocate and clear a new menu. */
|
|
sb_menu = (HMENU *)mem_alloc(nparts * sizeof(HMENU));
|
|
memset(sb_menu, 0x00, nparts * sizeof(HMENU));
|
|
}
|
|
}
|
|
|
|
|
|
/* UI support: set the icon ID for a status bar field. */
|
|
void
|
|
sb_set_icon(int part, int icon)
|
|
{
|
|
HANDLE ptr;
|
|
|
|
if (icon == 255) ptr = NULL;
|
|
else ptr = hIcon[(intptr_t)icon];
|
|
|
|
SendMessage(hwndSBAR, SB_SETICON, part, (LPARAM)ptr);
|
|
}
|
|
|
|
|
|
/* UI support: set a text label for a status bar field. */
|
|
void
|
|
sb_set_text(int part, const wchar_t *str)
|
|
{
|
|
int flags;
|
|
|
|
// flags = (part < (sb_nparts - 2)) ? SBT_NOBORDERS : SBT_POPOUT;
|
|
flags = (part < (sb_nparts - 2)) ? SBT_NOBORDERS : 0;
|
|
|
|
SendMessage(hwndSBAR, SB_SETTEXT, part | flags, (LPARAM)str);
|
|
}
|
|
|
|
|
|
/* UI support: set a tooltip for a status bar field. */
|
|
void
|
|
sb_set_tooltip(int part, const wchar_t *str)
|
|
{
|
|
SendMessage(hwndSBAR, SB_SETTIPTEXT, part, (LPARAM)str);
|
|
}
|
|
|
|
|
|
/* UI support: create a menu for a status bar part. */
|
|
void
|
|
sb_menu_create(int part)
|
|
{
|
|
HMENU h;
|
|
|
|
h = CreatePopupMenu();
|
|
|
|
sb_menu[part] = h;
|
|
}
|
|
|
|
|
|
/* UI support: add an item to a (status bar) menu. */
|
|
void
|
|
sb_menu_add_item(int part, int idm, const wchar_t *str)
|
|
{
|
|
if (idm >= 0)
|
|
AppendMenu(sb_menu[part], MF_STRING, idm, str);
|
|
else
|
|
AppendMenu(sb_menu[part], MF_SEPARATOR, 0, NULL);
|
|
}
|
|
|
|
|
|
/* UI support: enable or disable a status bar menu item. */
|
|
void
|
|
sb_menu_enable_item(int part, int idm, int val)
|
|
{
|
|
EnableMenuItem(sb_menu[part], idm,
|
|
val ? MF_BYCOMMAND|MF_ENABLED : MF_BYCOMMAND|MF_GRAYED);
|
|
}
|
|
|
|
|
|
/* UI support: set or reset a status bar menu item. */
|
|
void
|
|
sb_menu_set_item(int part, int idm, int val)
|
|
{
|
|
CheckMenuItem(sb_menu[part], idm, val ? MF_CHECKED : MF_UNCHECKED);
|
|
}
|