int main (int argc, char *argv[]) { struct RISC *risc = risc_new(); risc_set_serial(risc, &pclink); risc_set_clipboard(risc, &sdl_clipboard); struct RISC_LED leds = { .write = show_leds }; bool fullscreen = false; double zoom = 0; SDL_Rect risc_rect = { .w = RISC_FRAMEBUFFER_WIDTH, .h = RISC_FRAMEBUFFER_HEIGHT }; bool size_option = false; int mem_option = 0; const char *serial_in = NULL; const char *serial_out = NULL; bool boot_from_serial = false; int opt; while ((opt = getopt_long(argc, argv, "z:fLm:s:I:O:S", long_options, NULL)) != -1) { switch (opt) { case 'z': { double x = strtod(optarg, 0); if (x > 0) { zoom = x; } break; } case 'f': { fullscreen = true; break; } case 'L': { risc_set_leds(risc, &leds); break; } case 'm': { if (sscanf(optarg, "%d", &mem_option) != 1) { usage(); } break; } case 's': { int w, h; if (sscanf(optarg, "%dx%d", &w, &h) != 2) { usage(); } risc_rect.w = clamp(w, 32, MAX_WIDTH) & ~31; risc_rect.h = clamp(h, 32, MAX_HEIGHT); size_option = true; break; } case 'I': { serial_in = optarg; break; } case 'O': { serial_out = optarg; break; } case 'S': { boot_from_serial = true; risc_set_switches(risc, 1); break; } default: { usage(); } } } if (mem_option || size_option) { risc_configure_memory(risc, mem_option, risc_rect.w, risc_rect.h); } if (optind == argc - 1) { risc_set_spi(risc, 1, disk_new(argv[optind])); } else if (optind == argc && boot_from_serial) { /* Allow diskless boot */ risc_set_spi(risc, 1, disk_new(NULL)); } else { usage(); } if (serial_in || serial_out) { if (!serial_in) { serial_in = "/dev/null"; } if (!serial_out) { serial_out = "/dev/null"; } risc_set_serial(risc, raw_serial_new(serial_in, serial_out)); } if (SDL_Init(SDL_INIT_VIDEO) != 0) { fail(1, "Unable to initialize SDL: %s", SDL_GetError()); } atexit(SDL_Quit); SDL_EnableScreenSaver(); SDL_ShowCursor(false); SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); int window_flags = SDL_WINDOW_HIDDEN; int display = 0; if (fullscreen) { window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; display = best_display(&risc_rect); } if (zoom == 0) { SDL_Rect bounds; if (SDL_GetDisplayBounds(display, &bounds) == 0 && bounds.h >= risc_rect.h * 2 && bounds.w >= risc_rect.w * 2) { zoom = 2; } else { zoom = 1; } } SDL_Window *window = SDL_CreateWindow("Project Oberon", SDL_WINDOWPOS_UNDEFINED_DISPLAY(display), SDL_WINDOWPOS_UNDEFINED_DISPLAY(display), (int)(risc_rect.w * zoom), (int)(risc_rect.h * zoom), window_flags); if (window == NULL) { fail(1, "Could not create window: %s", SDL_GetError()); } SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0); if (renderer == NULL) { fail(1, "Could not create renderer: %s", SDL_GetError()); } SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, risc_rect.w, risc_rect.h); if (texture == NULL) { fail(1, "Could not create texture: %s", SDL_GetError()); } SDL_Rect display_rect; double display_scale = scale_display(window, &risc_rect, &display_rect); update_texture(risc, texture, &risc_rect); SDL_ShowWindow(window); SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, &risc_rect, &display_rect); SDL_RenderPresent(renderer); bool done = false; bool mouse_was_offscreen = false; while (!done) { uint32_t frame_start = SDL_GetTicks(); SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: { done = true; break; } case SDL_WINDOWEVENT: { if (event.window.event == SDL_WINDOWEVENT_RESIZED) { display_scale = scale_display(window, &risc_rect, &display_rect); } break; } case SDL_MOUSEMOTION: { int scaled_x = (int)round((event.motion.x - display_rect.x) / display_scale); int scaled_y = (int)round((event.motion.y - display_rect.y) / display_scale); int x = clamp(scaled_x, 0, risc_rect.w - 1); int y = clamp(scaled_y, 0, risc_rect.h - 1); bool mouse_is_offscreen = x != scaled_x || y != scaled_y; if (mouse_is_offscreen != mouse_was_offscreen) { SDL_ShowCursor(mouse_is_offscreen); mouse_was_offscreen = mouse_is_offscreen; } risc_mouse_moved(risc, x, risc_rect.h - y - 1); break; } case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: { bool down = event.button.state == SDL_PRESSED; risc_mouse_button(risc, event.button.button, down); break; } case SDL_KEYDOWN: case SDL_KEYUP: { bool down = event.key.state == SDL_PRESSED; switch (map_keyboard_event(&event.key)) { case ACTION_RESET: { risc_reset(risc); break; } case ACTION_TOGGLE_FULLSCREEN: { fullscreen ^= true; if (fullscreen) { SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); } else { SDL_SetWindowFullscreen(window, 0); } break; } case ACTION_QUIT: { SDL_PushEvent(&(SDL_Event){ .type=SDL_QUIT }); break; } case ACTION_FAKE_MOUSE1: { risc_mouse_button(risc, 1, down); break; } case ACTION_FAKE_MOUSE2: { risc_mouse_button(risc, 2, down); break; } case ACTION_FAKE_MOUSE3: { risc_mouse_button(risc, 3, down); break; } case ACTION_OBERON_INPUT: { uint8_t ps2_bytes[MAX_PS2_CODE_LEN]; int len = ps2_encode(event.key.keysym.scancode, down, ps2_bytes); risc_keyboard_input(risc, ps2_bytes, len); break; } } } } } risc_set_time(risc, frame_start); risc_run(risc, CPU_HZ / FPS); update_texture(risc, texture, &risc_rect); SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, &risc_rect, &display_rect); SDL_RenderPresent(renderer); uint32_t frame_end = SDL_GetTicks(); int delay = frame_start + 1000/FPS - frame_end; if (delay > 0) { SDL_Delay(delay); } }
int main (int argc, char *argv[]) { struct RISC *risc = risc_new(); risc_set_serial(risc, &pclink); risc_set_clipboard(risc, &sdl_clipboard); bool fullscreen = false; SDL_Rect risc_rect = { .w = RISC_FRAMEBUFFER_WIDTH, .h = RISC_FRAMEBUFFER_HEIGHT }; int opt; while ((opt = getopt_long(argc, argv, "fS:F:", long_options, NULL)) != -1) { switch (opt) { case 'f': { fullscreen = true; break; } case 's': { int w, h; if (sscanf(optarg, "%dx%d", &w, &h) != 2) { usage(); } risc_rect.w = clamp(w, 32, MAX_WIDTH) & ~31; risc_rect.h = clamp(h, 32, MAX_HEIGHT); risc_screen_size_hack(risc, risc_rect.w, risc_rect.h); break; } case 'F': { risc_set_serial(risc, raw_serial_new(atoi(optarg), atoi(optarg) + 1)); break; } default: { usage(); } } } if (optind != argc - 1) { usage(); } risc_set_spi(risc, 1, disk_new(argv[optind])); sdl_error_if(SDL_Init(SDL_INIT_VIDEO) != 0, "Unable to initialize SDL"); atexit(SDL_Quit); SDL_EnableScreenSaver(); SDL_ShowCursor(false); SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); int window_flags = SDL_WINDOW_HIDDEN; int window_pos = SDL_WINDOWPOS_UNDEFINED; if (fullscreen) { window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; window_pos = best_display(&risc_rect); } SDL_Window *window = SDL_CreateWindow("Project Oberon", window_pos, window_pos, risc_rect.w, risc_rect.h, window_flags); sdl_error_if(window == NULL, "Could not create window"); SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0); sdl_error_if(renderer == NULL, "Could not create renderer"); SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, risc_rect.w, risc_rect.h); sdl_error_if(texture == NULL, "Could not create texture"); SDL_Rect display_rect; double display_scale = scale_display(window, &risc_rect, &display_rect); update_texture(risc, texture, &risc_rect); SDL_ShowWindow(window); SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, &risc_rect, &display_rect); SDL_RenderPresent(renderer); bool done = false; bool mouse_was_offscreen = false; while (!done) { uint32_t frame_start = SDL_GetTicks(); SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: { done = true; } case SDL_WINDOWEVENT: { if (event.window.event == SDL_WINDOWEVENT_RESIZED) { display_scale = scale_display(window, &risc_rect, &display_rect); } break; } case SDL_MOUSEMOTION: { int scaled_x = (int)round((event.motion.x - display_rect.x) / display_scale); int scaled_y = (int)round((event.motion.y - display_rect.y) / display_scale); int x = clamp(scaled_x, 0, risc_rect.w - 1); int y = clamp(scaled_y, 0, risc_rect.h - 1); bool mouse_is_offscreen = x != scaled_x || y != scaled_y; if (mouse_is_offscreen != mouse_was_offscreen) { SDL_ShowCursor(mouse_is_offscreen); mouse_was_offscreen = mouse_is_offscreen; } risc_mouse_moved(risc, x, risc_rect.h - y - 1); break; } case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: { bool down = event.button.state == SDL_PRESSED; risc_mouse_button(risc, event.button.button, down); break; } case SDL_KEYDOWN: case SDL_KEYUP: { bool down = event.key.state == SDL_PRESSED; SDL_Keysym k = event.key.keysym; if (k.sym == SDLK_F12) { // F12 = Oberon reset if (down) { risc_reset(risc); } } else if (k.sym == SDLK_F11 || (k.sym == SDLK_f && (k.mod & KMOD_GUI) && (k.mod & KMOD_SHIFT))) { // F11 / Cmd-Shift-F = Toggle fullscreen if (down) { fullscreen ^= true; if (fullscreen) { SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); } else { SDL_SetWindowFullscreen(window, 0); } } } else if (k.sym == SDLK_F4 && (k.mod & KMOD_ALT)) { // Alt-F4 = quit (for when we're running without windowing system) if (down) { SDL_PushEvent(&(SDL_Event){ .type=SDL_QUIT }); } } else if (k.sym == SDLK_LALT) { // Emulate middle button risc_mouse_button(risc, 2, down); } else { // Pass other keys to Oberon uint8_t scancode[MAX_PS2_CODE_LEN]; int len = ps2_encode(k.scancode, down, scancode); risc_keyboard_input(risc, scancode, len); } break; } } }