mii_emu/ui_gl/mii_emu_gl.c
2024-11-23 16:53:42 +00:00

895 lines
27 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* mii_emu_gl.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
/*
* This is the main file for the X11/GLX version of the MII emulator
*/
#define _GNU_SOURCE // for asprintf
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include <locale.h>
#include <pthread.h>
#include <sys/timerfd.h>
#include <sys/stat.h>
#include <unistd.h>
#include <GL/glx.h>
#include <GL/glxext.h>
#include <X11/Xatom.h>
#include "mii_mui.h"
#include "mish.h"
#include "mii_thread.h"
#include "mii_ssc.h"
#include "mii_mui_gl.h"
#include "miigl_counter.h"
#include "mii_sokol_audio.h"
#define MII_ICON64_DEFINE
#include "mii_icon64.h"
/*
* Note: This *assumes* that the GL implementation has support for
* non-power-of-2 * textures, which is not a given for older
* implementations. However, I think (by 2024) that's a safe assumption.
*/
#define WINDOW_WIDTH 1280
#define WINDOW_HEIGHT 720
#define MII_MUI_GL_POW2 0
typedef struct mii_x11_t {
mii_mui_t video;
pthread_t cpu_thread;
Cursor cursor;
Display * dpy;
Window win;
Atom wmState;
Atom wmStateFullscreen;
Atom wm_delete_window;
int width, height;
GLXContext glContext;
// miigl_counter_t videoc, redrawc, sleepc;
} mii_x11_t;
static int gl_err = 0;
static int gl_error_handler(
Display *dpy,
XErrorEvent *ev)
{
gl_err = true;
return 0;
}
#define die(_w) { fprintf(stderr,"%s\n",_w); exit(1); }
static int
has_gl_extension(
const char *string,
const char *ext)
{
if (!string || !ext)
return false;
int l = strlen(ext);
while (*string) {
char * gotit = strstr((const char *)string, ext);
if (!gotit)
return false;
if (gotit && (gotit[l] == ' ' || gotit[l] == '\0'))
return true;
string += l;
}
return false;
}
/*
* xmodmap -pke or -pk will print the list of keycodes
*/
static bool
_mii_x11_convert_keycode(
mii_x11_t *ui,
KeySym sym,
mui_event_t *out )
{
switch (sym) {
case XK_F1 ... XK_F12:
out->key.key = MUI_KEY_F1 + (sym - XK_F1);
break;
case XK_Escape: out->key.key = MUI_KEY_ESCAPE; break;
case XK_Left: out->key.key = MUI_KEY_LEFT; break;
case XK_Up: out->key.key = MUI_KEY_UP; break;
case XK_Right: out->key.key = MUI_KEY_RIGHT; break;
case XK_Down: out->key.key = MUI_KEY_DOWN; break;
// XK_Begin
case XK_Insert: out->key.key = MUI_KEY_INSERT; break;
case XK_Home: out->key.key = MUI_KEY_HOME; break;
case XK_End: out->key.key = MUI_KEY_END; break;
case XK_Page_Up: out->key.key = MUI_KEY_PAGEUP; break;
case XK_Page_Down: out->key.key = MUI_KEY_PAGEDOWN; break;
case XK_Shift_R: out->key.key = MUI_KEY_RSHIFT; break;
case XK_Shift_L: out->key.key = MUI_KEY_LSHIFT; break;
case XK_Control_R: out->key.key = MUI_KEY_RCTRL; break;
case XK_Control_L: out->key.key = MUI_KEY_LCTRL; break;
case XK_Alt_L: out->key.key = MUI_KEY_LALT; break;
case XK_Alt_R: out->key.key = MUI_KEY_RALT; break;
case XK_Super_L: out->key.key = MUI_KEY_LSUPER; break;
case XK_Super_R: out->key.key = MUI_KEY_RSUPER; break;
case XK_Caps_Lock: out->key.key = MUI_KEY_CAPSLOCK; break;
default:
out->key.key = sym & 0xff;
break;
}
// printf("%s %08x to %04x\n", __func__, sym, out->key.key);
return true;
}
static GLAPIENTRY void
debug_callback(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar* message, const void* userParam) {
// Filter out notifications if theyre too verbose
if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) return;
printf("OpenGL Debug Message:\n");
printf("Source: %s\n", source == GL_DEBUG_SOURCE_API ? "API" :
source == GL_DEBUG_SOURCE_WINDOW_SYSTEM ? "Window System" :
source == GL_DEBUG_SOURCE_SHADER_COMPILER ? "Shader Compiler" :
source == GL_DEBUG_SOURCE_THIRD_PARTY ? "Third Party" :
source == GL_DEBUG_SOURCE_APPLICATION ? "Application" :
source == GL_DEBUG_SOURCE_OTHER ? "Other" : "Unknown");
printf("Type: %s\n", type == GL_DEBUG_TYPE_ERROR ? "Error" :
type == GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR ? "Deprecated Behavior" :
type == GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR ? "Undefined Behavior" :
type == GL_DEBUG_TYPE_PORTABILITY ? "Portability" :
type == GL_DEBUG_TYPE_PERFORMANCE ? "Performance" :
type == GL_DEBUG_TYPE_MARKER ? "Marker" :
type == GL_DEBUG_TYPE_PUSH_GROUP ? "Push Group" :
type == GL_DEBUG_TYPE_POP_GROUP ? "Pop Group" :
type == GL_DEBUG_TYPE_OTHER ? "Other" : "Unknown");
printf("Severity: %s\n", severity == GL_DEBUG_SEVERITY_HIGH ? "High" :
severity == GL_DEBUG_SEVERITY_MEDIUM ? "Medium" :
severity == GL_DEBUG_SEVERITY_LOW ? "Low" :
severity == GL_DEBUG_SEVERITY_NOTIFICATION ? "Notification" : "Unknown");
printf("Message: %s\n", message);
printf("---------------------------------\n");
}
static int
mii_x11_init(
struct mii_x11_t *ui )
{
// mui_t * mui = &ui->video.mui;
if (!setlocale(LC_ALL,"") ||
!XSupportsLocale() ||
!XSetLocaleModifiers("@im=none"))
return -1;
ui->dpy = XOpenDisplay(NULL);
if (!ui->dpy)
die("Failed to open X display\n");
{
/* check glx version */
int glx_major, glx_minor;
if (!glXQueryVersion(ui->dpy, &glx_major, &glx_minor))
die("[X11]: Error: Failed to query OpenGL version\n");
if ((glx_major == 1 && glx_minor < 3) || (glx_major < 1))
die("[X11]: Error: Invalid GLX version!\n");
}
GLXFBConfig fbc;
XVisualInfo * vis = NULL;
{
/* find and pick matching framebuffer visual */
int fb_count;
static const GLint attr[] = {
GLX_X_RENDERABLE, True,
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8,
GLX_BLUE_SIZE, 8, GLX_ALPHA_SIZE, 8,
None
};
GLXFBConfig *fbc_list = glXChooseFBConfig(ui->dpy,
DefaultScreen(ui->dpy), attr, &fb_count);
if (!fbc_list)
die("[X11]: Error: failed to retrieve framebuffer configuration\n");
{
/* pick framebuffer with most samples per pixel */
int fb_best = -1, best_num_samples = -1;
for (int i = 0; i < fb_count; ++i) {
XVisualInfo *vi = glXGetVisualFromFBConfig(ui->dpy, fbc_list[i]);
if (vi) {
int sample_buffer, samples;
glXGetFBConfigAttrib(ui->dpy, fbc_list[i],
GLX_SAMPLE_BUFFERS, &sample_buffer);
glXGetFBConfigAttrib(ui->dpy, fbc_list[i],
GLX_SAMPLES, &samples);
if ((fb_best < 0) ||
(sample_buffer && samples > best_num_samples))
fb_best = i, best_num_samples = samples;
XFree(vi);
}
}
fbc = fbc_list[fb_best];
XFree(fbc_list);
vis = glXGetVisualFromFBConfig(ui->dpy, fbc);
}
}
{
/* create window */
XSetWindowAttributes swa = {
.colormap = XCreateColormap(ui->dpy,
RootWindow(ui->dpy, vis->screen),
vis->visual, AllocNone),
.background_pixmap = None,
.border_pixel = 0,
.event_mask =
ExposureMask | KeyPressMask | KeyReleaseMask |
ButtonPress | ButtonReleaseMask | ButtonMotionMask |
Button1MotionMask | Button3MotionMask |
Button4MotionMask | Button5MotionMask |
PointerMotionMask | StructureNotifyMask | FocusChangeMask,
};
ui->win = XCreateWindow(ui->dpy,
RootWindow(ui->dpy, vis->screen), 0, 0,
WINDOW_WIDTH, WINDOW_HEIGHT, 0, vis->depth, InputOutput,
vis->visual, CWBorderPixel | CWColormap | CWEventMask,
&swa);
if (!ui->win)
die("[X11]: Failed to create window\n");
XFree(vis);
XFreeColormap(ui->dpy, swa.colormap);
{
char title[128];
sprintf(title, "MII //e Emulator");
char *telnet = getenv("MISH_TELNET_PORT");
if (telnet)
sprintf(title + strlen(title), " (telnet port %s)", telnet);
else
sprintf(title + strlen(title), " (telnet disabled)");
XStoreName(ui->dpy, ui->win, title);
}
{
Atom net_wm_icon_atom = XInternAtom(ui->dpy, "_NET_WM_ICON", False);
XChangeProperty(ui->dpy, ui->win, net_wm_icon_atom, XA_CARDINAL,
32, PropModeReplace,
(unsigned char *)mii_icon64,
sizeof(mii_icon64) / sizeof(mii_icon64[0]));
XFlush(ui->dpy);
}
{
XSizeHints *hints = XAllocSizeHints();
hints->flags = PMinSize | PAspect;// | PMaxSize;
hints->min_width = WINDOW_WIDTH / 2;
hints->min_height = WINDOW_HEIGHT / 2;
hints->max_aspect.x = WINDOW_WIDTH;
hints->max_aspect.y = WINDOW_HEIGHT;
hints->min_aspect.x = WINDOW_WIDTH;
hints->min_aspect.y = WINDOW_HEIGHT;
XSetWMNormalHints(ui->dpy, ui->win, hints);
XFree(hints);
}
XMapWindow(ui->dpy, ui->win);
ui->wm_delete_window = XInternAtom(ui->dpy, "WM_DELETE_WINDOW", False);
XSetWMProtocols(ui->dpy, ui->win, &ui->wm_delete_window, 1);
ui->wmState = XInternAtom(ui->dpy, "_NET_WM_STATE", False);
ui->wmStateFullscreen = XInternAtom(ui->dpy, "_NET_WM_STATE_FULLSCREEN", False);
}
/* create invisible cursor */
{
static XColor dummy; char data[1] = {0};
Pixmap blank = XCreateBitmapFromData(ui->dpy, ui->win, data, 1, 1);
if (blank == None) return 0;
ui->cursor = XCreatePixmapCursor(ui->dpy, blank, blank, &dummy, &dummy, 0, 0);
XFreePixmap(ui->dpy, blank);
}
{
/* create opengl context */
typedef GLXContext (*glxCreateContext)(
Display *, GLXFBConfig, GLXContext, Bool, const int *);
int (*old_handler)(Display *, XErrorEvent *) =
XSetErrorHandler(gl_error_handler);
const char *extensions_str = glXQueryExtensionsString(
ui->dpy, DefaultScreen(ui->dpy));
glxCreateContext create_context = (glxCreateContext)
glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB");
gl_err = false;
if (!has_gl_extension(extensions_str, "GLX_ARB_create_context") ||
!create_context) {
fprintf(stdout, "[X11]: glXCreateContextAttribARB() not found...\n");
fprintf(stdout, "[X11]: ... using old-style GLX context\n");
ui->glContext = glXCreateNewContext(
ui->dpy, fbc, GLX_RGBA_TYPE, 0, True);
} else {
GLint attr[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
GLX_CONTEXT_MINOR_VERSION_ARB, 0,
None
};
ui->glContext = create_context(ui->dpy, fbc, 0, True, attr);
XSync(ui->dpy, False);
if (gl_err || !ui->glContext) {
attr[1] = 1;
attr[3] = 0;
gl_err = false;
fprintf(stdout, "[X11] Failed to create OpenGL 3.0 context\n");
fprintf(stdout, "[X11] ... using old-style GLX context!\n");
ui->glContext = create_context(ui->dpy, fbc, 0, True, attr);
}
}
XSync(ui->dpy, False);
XSetErrorHandler(old_handler);
if (gl_err || !ui->glContext)
die("[X11]: Failed to create an OpenGL context\n");
glXMakeCurrent(ui->dpy, ui->win, ui->glContext);
}
{
glEnable(GL_DEBUG_OUTPUT); // Enables the debug output
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); // Makes sure errors are handled immediately
typedef void (APIENTRY *PFNGLDEBUGMESSAGECALLBACKPROC)(
GLDEBUGPROC callback, const void *userParam);
PFNGLDEBUGMESSAGECALLBACKPROC glDebugMessageCallback =
(PFNGLDEBUGMESSAGECALLBACKPROC)glXGetProcAddressARB(
(const GLubyte *)"glDebugMessageCallback");
glDebugMessageCallback(debug_callback, 0); // Register the callback function
}
return 0;
}
static void
mui_read_clipboard(
struct mui_t *mui)
{
FILE *f = popen("xclip -selection clipboard -o", "r");
if (!f) {
perror("popen xclip");
return;
}
mui_utf8_t clip = {};
char buf[1024];
size_t r = 0;
do {
r = fread(buf, 1, sizeof(buf), f);
if (r > 0)
mui_utf8_append(&clip, (uint8_t*)buf, r);
} while (r > 0);
pclose(f);
mui_utf8_free(&mui->clipboard);
mui->clipboard = clip;
printf("%s %d bytes\n", __func__, mui->clipboard.count);
}
void
mii_mui_toggle_fullscreen(
mii_mui_t * _ui )
{
mii_x11_t * ui = (mii_x11_t*)_ui;
XEvent xev = {};
xev.type = ClientMessage;
xev.xclient.window = ui->win;
xev.xclient.message_type = ui->wmState;
xev.xclient.format = 32;
xev.xclient.data.l[0] = 2; // _NET_WM_STATE_TOGGLE
xev.xclient.data.l[1] = ui->wmStateFullscreen;
xev.xclient.data.l[2] = 0; // No second property
xev.xclient.data.l[3] = 1; // Normal source indication
XSendEvent(ui->dpy, DefaultRootWindow(ui->dpy), False,
SubstructureNotifyMask | SubstructureRedirectMask, &xev);
XFlush(ui->dpy);
}
static int
mii_x11_handle_event(
mii_x11_t * ui,
XEvent *evt)
{
int quit = 0;
/* We don't actually 'grab' as in warp the pointer, we just show/hide it
* dynamically when we enter/leave the video rectangle */
if (ui->video.mouse.grab) {
XDefineCursor(ui->dpy, ui->win, ui->cursor);
ui->video.mouse.grab = 0;
} else if (ui->video.mouse.ungrab) {
XUndefineCursor(ui->dpy, ui->win);
ui->video.mouse.ungrab = 0;
}
mui_t * mui = &ui->video.mui;
mii_t * mii = &ui->video.mii;
switch (evt->type) {
case FocusIn:
case FocusOut:
// printf("%s focus %d\n", __func__, evt->type == FocusIn);
/* This prevents 'stale' modifiers keys when the emulator is
* in front and we switches window/desktop with a modifier on..
* this used to trigger 'phantom' modifiers still being 'on' */
mui->modifier_keys = 0;
break;
case KeyRelease:
case KeyPress: {
int ret, down = (evt->type == KeyPress);
KeySym *code = XGetKeyboardMapping(ui->dpy,
(KeyCode)evt->xkey.keycode, 1, &ret);
ui->video.key.type = down ? MUI_EVENT_KEYDOWN : MUI_EVENT_KEYUP;
ui->video.key.key.up = 0;
bool handled = false;
bool converted = _mii_x11_convert_keycode(ui, *code,
&ui->video.key);
bool is_modifier = ui->video.key.key.key >= MUI_KEY_MODIFIERS &&
ui->video.key.key.key <= MUI_KEY_MODIFIERS_LAST;
if (converted) {
// convert keycodes into a bitfields of current modifiers
if (ui->video.key.key.key >= MUI_KEY_MODIFIERS &&
ui->video.key.key.key <= MUI_KEY_MODIFIERS_LAST) {
if (down)
mui->modifier_keys |= (1 <<
(ui->video.key.key.key - MUI_KEY_MODIFIERS));
else
mui->modifier_keys &= ~(1 <<
(ui->video.key.key.key - MUI_KEY_MODIFIERS));
}
ui->video.key.modifiers = mui->modifier_keys;
switch (ui->video.key.key.key) {
#if 0
case MUI_KEY_RALT:
case MUI_KEY_LALT: {
int apple = ui->video.key.key.key - MUI_KEY_RALT;
#else
case MUI_KEY_RSUPER:
case MUI_KEY_LSUPER: {
int apple = ui->video.key.key.key - MUI_KEY_LSUPER;
#endif
mii_bank_t *bank = &mii->bank[MII_BANK_SW];
uint8_t old = mii_bank_peek(bank, 0xc061 + apple);
mii_bank_poke(bank, 0xc061 + apple, down ? 0x80 : 0);
if (!!down != !!old) {
// printf("Apple %s %s\n", apple ? "Open" : "Close",
// down ? "down" : "up");
}
} break;
}
handled = mui_handle_event(mui, &ui->video.key);
// if not handled and theres a window visible, assume
// it's a dialog and it OUGHT to eat the key
if (!handled)
handled = mui_window_front(mui) != NULL;
// printf("%s key handled %d\n", __func__, handled);
}
if (!handled && down && !is_modifier) {
uint16_t mii_key = ui->video.key.key.key;
char buf[32] = "";
KeySym keysym = 0;
if (XLookupString((XKeyEvent*)evt, buf, 32, &keysym, NULL) != NoSymbol) {
#if 0
printf(" lookup sym %04x %d:", (int)keysym, (int)strlen(buf));
for (int i = 0; i < (int)strlen(buf); i++)
printf(" %02x", buf[i]);
printf("\n");
#endif
mii_key = buf[0];
}
switch (mii_key) {
case MUI_KEY_DOWN: mii_key = 'J' - 'A' + 1; break;
case MUI_KEY_UP: mii_key = 'K' - 'A' + 1; break;
case MUI_KEY_RIGHT: mii_key = 'U' - 'A' + 1; break;
case MUI_KEY_LEFT: mii_key = 'H' - 'A' + 1; break;
}
// printf("key %04x %4x\n", mii_key, mui->modifier_keys);
/* control shift V is hard coded! */
if (mii_key == 0x016 &&
(mui->modifier_keys & MUI_MODIFIER_SHIFT) &&
(mui->modifier_keys & MUI_MODIFIER_CTRL)) {
printf("Paste\n");
mui_read_clipboard(mui);
if (mui->clipboard.count) {
mui_utf8_add(&mui->clipboard, 0);
// convert newlines
for (uint i = 0; i < mui->clipboard.count; i++)
if (mui->clipboard.e[i] == '\n')
mui->clipboard.e[i] = '\r';
mii_th_signal_t sig = {
.cmd = SIGNAL_PASTE,
.ptr = mui->clipboard.e,
};
mui->clipboard.e = NULL;
mui->clipboard.count = mui->clipboard.size = 0;
mii_th_fifo_write(mii_thread_get_fifo(mii), sig);
}
} else if (mii_key == 0x01b && /* Toggle full screen too! */
(mui->modifier_keys & MUI_MODIFIER_SHIFT) &&
(mui->modifier_keys & MUI_MODIFIER_CTRL)) {
mii_mui_toggle_fullscreen(&ui->video);
} else
mii_keypress(mii, mii_key);
}
XFree(code);
} break;
case ButtonPress:
case ButtonRelease: {
// printf("%s %s button %d grabbed:%d\n", __func__,
// evt->type == ButtonPress ? "Down":"Up ",
// evt->xbutton.button, ui->video.mouse.grabbed);
switch (evt->xbutton.button) {
case Button1: {
ui->video.mouse.down = evt->type == ButtonPress;
ui->video.mouse.pos.x = evt->xbutton.x;
ui->video.mouse.pos.y = evt->xbutton.y;
if (ui->video.mui_visible) {
mui_event_t ev = {
.type = ui->video.mouse.down ?
MUI_EVENT_BUTTONDOWN :
MUI_EVENT_BUTTONUP,
.mouse.where = ui->video.mouse.pos,
.modifiers = mui->modifier_keys, // | MUI_MODIFIER_EVENT_TRACE,
};
mui_handle_event(mui, &ev);
}
mii_mui_update_mouse_card(&ui->video);
} break;
case Button3: {
/* right click force the UI to reappear if it was hidden */
if (!ui->video.mui_visible &&
ui->video.transition.state == MII_MUI_TRANSITION_NONE) {
ui->video.transition.state = MII_MUI_TRANSITION_SHOW_UI;
}
} break;
case Button4:
case Button5: {
// printf("%s wheel %d %d\n", __func__,
// evt->xbutton.button, ui->video.mui_visible);
if (ui->video.mui_visible) {
mui_event_t ev = {
.type = MUI_EVENT_WHEEL,
.modifiers = mui->modifier_keys,// | MUI_MODIFIER_EVENT_TRACE,
.wheel.where = ui->video.mouse.pos,
.wheel.delta = evt->xbutton.button == Button4 ? -1 : 1,
};
mui_handle_event(mui, &ev);
}
} break;
}
} break;
case ConfigureNotify:
if (evt->xconfigure.width != ui->video.window_size.x ||
evt->xconfigure.height != ui->video.window_size.y) {
// Window is being resized
// Handle the resize event here
ui->video.window_size.x = evt->xconfigure.width;
ui->video.window_size.y = evt->xconfigure.height;
ui->video.video_frame = mii_mui_get_video_position(&ui->video);
mui_resize(&ui->video.mui, &ui->video.pixels.mui,
ui->video.window_size);
ui->video.pixels.mui.texture.size = ui->video.window_size;
mui_mui_gl_regenerate_ui_texture(&ui->video);
mii_mui_update_mouse_card(&ui->video);
}
break;
case MotionNotify: {
ui->video.mouse.pos.x = evt->xmotion.x;
ui->video.mouse.pos.y = evt->xmotion.y;
mii_mui_update_mouse_card(&ui->video);
if (ui->video.mouse.grabbed)
break;
if (ui->video.mui_visible) {
mui_event_t ev = {
.type = MUI_EVENT_DRAG,
.mouse.where = ui->video.mouse.pos,
.modifiers = mui->modifier_keys,
};
mui_handle_event(mui, &ev);
}
} break;
case ClientMessage:
if ((Atom)evt->xclient.data.l[0] == ui->wm_delete_window)
quit = 1;
break;
case KeymapNotify:
XRefreshKeyboardMapping(&evt->xmapping);
break;
}
return quit;
}
static void
mii_x11_terminate(
mii_x11_t *ui)
{
glXMakeCurrent(ui->dpy, 0, 0);
glXDestroyContext(ui->dpy, ui->glContext);
XUnmapWindow(ui->dpy, ui->win);
XDestroyWindow(ui->dpy, ui->win);
XCloseDisplay(ui->dpy);
}
void
mii_ui_reconfigure_slot(
mii_t * mii,
mii_machine_config_t * config,
int slot )
{
printf("%s slot %d\n", __func__, slot);
int i = slot-1;
switch (config->slot[i].driver) {
case MII_SLOT_DRIVER_SMARTPORT: {
mii_slot_command(mii, slot,
MII_SLOT_DRIVE_LOAD,
(void*)config->slot[i].conf.smartport.drive[0].disk);
mii_slot_command(mii, slot,
MII_SLOT_DRIVE_LOAD + 1,
(void*)config->slot[i].conf.smartport.drive[1].disk);
} break;
case MII_SLOT_DRIVER_DISK2: {
for (int di = 0; di < 2; di++) {
mii_slot_command(mii, slot,
MII_SLOT_DRIVE_LOAD + di,
(void*)config->slot[i].conf.disk2.drive[di].disk);
int wp = config->slot[i].conf.disk2.drive[di].wp;
mii_slot_command(mii, slot,
MII_SLOT_DRIVE_WP + di,
(void*)&wp);
}
} break;
case MII_SLOT_DRIVER_ROM1MB: {
mii_slot_command(mii, slot,
MII_SLOT_DRIVE_LOAD,
config->slot[i].conf.rom1mb.use_default ?
"" :
(void*)config->slot[i].conf.rom1mb.drive.disk);
} break;
case MII_SLOT_DRIVER_SSC: {
mii_ssc_setconf_t ssc = {
.baud = config->slot[i].conf.ssc.baud,
.bits = config->slot[i].conf.ssc.bits,
.parity = config->slot[i].conf.ssc.parity,
.stop = config->slot[i].conf.ssc.stop,
.handshake = config->slot[i].conf.ssc.hw_handshake,
.is_device = config->slot[i].conf.ssc.kind == MII_SSC_KIND_DEVICE,
.is_socket = config->slot[i].conf.ssc.kind == MII_SSC_KIND_SOCKET,
.is_pty = config->slot[i].conf.ssc.kind == MII_SSC_KIND_PTY,
.socket_port = config->slot[i].conf.ssc.socket_port,
};
strncpy(ssc.device, config->slot[i].conf.ssc.device,
sizeof(ssc.device)-1);
mii_slot_command(mii, slot,
MII_SLOT_SSC_SET_TTY,
(void*)&ssc);
} break;
}
}
// load the content of the mii_machine_config_t into the initialized
// mii_t struct, similar to what mii_argv_parse does
// TODO: Change argv to modify that struct instead of the mii_t directly
static void
_mii_ui_load_config(
mii_t * mii,
mii_machine_config_t * config,
uint32_t *ioFlags )
{
if (config->no_slot_clock)
*ioFlags |= MII_INIT_NSC;
if (config->titan_accelerator)
*ioFlags |= MII_INIT_TITAN;
mii->audio.muted = config->audio_muted;
mii_audio_volume(&mii->speaker.source, config->audio_volume);
mii->video.color_mode = config->video_mode;
mii_video_set_mode(mii, config->video_mode);
for (int i = 0; i < 7; i++) {
if (config->slot[i].driver == MII_SLOT_DRIVER_NONE)
continue;
if (config->slot[i].driver >= MII_SLOT_DRIVER_COUNT) {
printf("%s invalid driver %d\n", __func__,
config->slot[i].driver);
continue;
}
int slot = i + 1;
if (mii_slot_drv_register(mii, slot,
mii_slot_driver[config->slot[i].driver].driver) < 0) {
printf("%s failed to register driver %s\n", __func__,
mii_slot_driver[config->slot[i].driver].driver);
}
mii_ui_reconfigure_slot(mii, config, slot);
}
}
// I want at least the 'silent' flags to be 'sticky'
uint32_t g_startup_flags = 0;
void
mii_x11_reload_config(
struct mii_x11_t *ui )
{
mii_t * mii = &ui->video.mii;
mii_machine_config_t * config = &ui->video.config;
uint32_t flags = MII_INIT_DEFAULT | g_startup_flags;
if (mii->state != MII_INIT) {
printf("%s mii is running, terminating thread\n", __func__);
mii->state = MII_TERMINATE;
pthread_join(ui->cpu_thread, NULL);
printf("%s mii terminated\n", __func__);
}
mii_mui_menu_slot_menu_update(&ui->video);
printf("%s (re)loading config\n", __func__);
// if we're silent from the command line, we are *always* silent.
mii_init(mii);
_mii_ui_load_config(mii, config, &flags);
if (g_startup_flags & MII_INIT_SILENT)
mii->audio.drv = NULL;
else
mii_sokol_audio_init(mii);
mii_prepare(mii, flags);
mii_reset(mii, true);
mii_mui_gl_prepare_textures(&ui->video);
/* start the CPU/emulator thread */
ui->cpu_thread = mii_threads_start(mii);
}
mii_x11_t g_mii = {};
extern const mii_machine_config_t mii_default_config;
int
main(
int argc,
const char *argv[])
{
char * conf_path;
asprintf(&conf_path, "%s/.local/share/mii", getenv("HOME"));
mkdir(conf_path, 0755);
mii_x11_t * ui = &g_mii;
mii_t * mii = &g_mii.video.mii;
mui_t * mui = &g_mii.video.mui;
// start with a default config, as some settings might not have been saved
ui->video.config = mii_default_config;
bool no_config_found = false;
char pwd[1024];
if (!getcwd(pwd, sizeof(pwd))) {
perror("getcwd");
exit(1);
}
if (mii_settings_load(
&ui->video.cf, pwd, ".mii_emu_config") < 0 &&
mii_settings_load(
&ui->video.cf, conf_path, "mii_emu_gl") < 0) {
asprintf(&ui->video.cf.path, "%s/.local/share/mii/mii_emu_gl",
getenv("HOME"));
printf("%s failed to load settings\n", argv[0]);
ui->video.config.load_defaults = true;
no_config_found = true;
} else {
mii_emu_load(&ui->video.cf, &ui->video.config);
}
{
mii_init(mii);
int idx = 1;
uint32_t flags = MII_INIT_DEFAULT;
if (!no_config_found) {
// load the config we have in the UI config struct into the
// mii config struct
_mii_ui_load_config(mii, &ui->video.config, &flags);
}
int r = mii_argv_parse(mii, argc, argv, &idx, &flags);
if (r == 0) {
printf("mii: Invalid argument %s, skipped\n", argv[idx]);
} else if (r == -1)
exit(1);
if (!(flags & MII_INIT_SILENT))
mii_sokol_audio_init(mii);
else
printf("Audio disabled\n");
mii_prepare(mii, flags);
g_startup_flags = flags;
}
{
mish_prepare(1);
mish_set_command_parameter(MISH_FCC('m','i','i',' '), mii);
printf("MISH_TELNET_PORT = %s\n", getenv("MISH_TELNET_PORT"));
}
mii_x11_init(ui);
mii_mui_init(&ui->video, C2_PT(WINDOW_WIDTH, WINDOW_HEIGHT));
mii_mui_gl_init(&ui->video);
asprintf(&mui->pref_directory, "%s/.local/share/mii", getenv("HOME"));
// use a 60fps timerfd here as well
int timerfd = timerfd_create(CLOCK_MONOTONIC, 0);
if (timerfd < 0) {
perror(__func__);
goto cleanup;
}
mii_thread_set_fps(timerfd, 60);
/*
* If it is the first run, theres no config files, open the
* slot dialog to let the user configure some stuff himself
*/
if (no_config_found) {
ui->video.config.reboot_on_load = true;
mii_config_open_slots_dialog(&ui->video);
}
/* start the CPU/emulator thread */
ui->cpu_thread = mii_threads_start(mii);
if (g_startup_flags & MII_INIT_HIDE_UI)
ui->video.transition.state = MII_MUI_TRANSITION_HIDE_UI;
while (mii->state != MII_INIT) {
/* Input */
XEvent evt;
while (XPending(ui->dpy)) {
XNextEvent(ui->dpy, &evt);
if (evt.type == ClientMessage) {
if (evt.xclient.message_type ==
XInternAtom(ui->dpy, "WM_PROTOCOLS", False) &&
(Atom)evt.xclient.data.l[0] == ui->wm_delete_window) {
// printf("Window close requested!\n");
goto cleanup;
}
}
if (XFilterEvent(&evt, ui->win))
continue;
mii_x11_handle_event(ui, &evt);
}
bool draw = mii_mui_gl_run(&ui->video);
if (draw) {
// miigl_counter_tick(&ui->redrawc, miigl_get_time());
// XGetWindowAttributes(ui->dpy, ui->win, &ui->attr);
glViewport(0, 0, ui->video.window_size.x, ui->video.window_size.y);
mii_mui_showhide_ui_machine(&ui->video);
mii_mui_gl_render(&ui->video);
glFlush();
glXSwapBuffers(ui->dpy, ui->win);
}
/* Wait for next frame */
uint64_t timerv;
if (read(timerfd, &timerv, sizeof(timerv)) < 0) {
perror(__func__);
goto cleanup;
}
/* This delay the switch to full screen until the window was drawn */
if (mii->video.frame_count == 30) {
if (g_startup_flags & MII_INIT_FULLSCREEN) {
mii_mui_toggle_fullscreen(&ui->video);
}
}
#if 0
miigl_counter_tick(&ui->sleepc, miigl_get_time());
if (!(current_frame % 60))
printf("VID: %3d Draw:%3d sleep:%3d\n",
miigl_counter_get_read_size(&ui->videoc),
miigl_counter_get_read_size(&ui->redrawc),
miigl_counter_get_read_size(&ui->sleepc));
#endif
}
cleanup:
mii_emu_save(&ui->video.cf, &ui->video.config);
// this was already done in the mii_thread
// mii_dispose(mii);
mii_x11_terminate(ui);
return 0;
}