diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 6fba9e0e8..feced1631 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -158,6 +158,8 @@ 4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53911D117D36003C6002 /* CSAudioQueue.m */; }; 4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A471F9B8FA70062DABF /* Typer.cpp */; }; 4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */; }; + 4B2BF19123DCC6A200C3AD60 /* BD500.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03523CEB86000B98D9E /* BD500.cpp */; }; + 4B2BF19223DCC6A800C3AD60 /* STX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03323C58B1E00B98D9E /* STX.cpp */; }; 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; }; 4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; }; 4B2C45421E3C3896002A2389 /* cartridge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B2C45411E3C3896002A2389 /* cartridge.png */; }; @@ -4252,6 +4254,7 @@ 4B055AD31FAE9B0B0060FFFF /* Microdisc.cpp in Sources */, 4B055AB41FAE860F0060FFFF /* OricTAP.cpp in Sources */, 4B055AB71FAE860F0060FFFF /* TZX.cpp in Sources */, + 4B2BF19123DCC6A200C3AD60 /* BD500.cpp in Sources */, 4B055ADA1FAE9B460060FFFF /* 1770.cpp in Sources */, 4B055ADC1FAE9B460060FFFF /* AY38910.cpp in Sources */, 4B055AD71FAE9B180060FFFF /* Keyboard.cpp in Sources */, @@ -4303,6 +4306,7 @@ 4BEBFB4E2002C4BF000708CC /* MSXDSK.cpp in Sources */, 4B055ADD1FAE9B460060FFFF /* i8272.cpp in Sources */, 4B055A9C1FAE85DA0060FFFF /* CPCDSK.cpp in Sources */, + 4B2BF19223DCC6A800C3AD60 /* STX.cpp in Sources */, 4B0ACC2723775819008902D0 /* AtariST.cpp in Sources */, 4B8318B922D3E56D006DB630 /* MemoryPacker.cpp in Sources */, 4B055ABA1FAE86170060FFFF /* Commodore.cpp in Sources */, @@ -5075,6 +5079,7 @@ GCC_WARN_UNUSED_LABEL = YES; INFOPLIST_FILE = "Clock Signal/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-Wreorder", @@ -5121,6 +5126,7 @@ GCC_WARN_UNUSED_LABEL = YES; INFOPLIST_FILE = "Clock Signal/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-Wreorder", diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp index 6a70d98dd..44bae33fb 100644 --- a/OSBindings/SDL/main.cpp +++ b/OSBindings/SDL/main.cpp @@ -35,9 +35,11 @@ namespace { struct BestEffortUpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate { Time::Seconds update(Concurrency::BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update, int flags) override { + std::lock_guard lock_guard(*machine_mutex); return machine->crt_machine()->run_until(duration, flags); } + std::mutex *machine_mutex; Machine::DynamicMachine *machine; }; @@ -103,6 +105,7 @@ class ActivityObserver: public Activity::Observer { } void set_aspect_ratio(float aspect_ratio) { + std::lock_guard lock_guard(mutex); lights_.clear(); // Generate a bunch of LEDs for connected drives. @@ -129,6 +132,7 @@ class ActivityObserver: public Activity::Observer { } void draw() { + std::lock_guard lock_guard(mutex); for(const auto &lit_led: lit_leds_) { if(blinking_leds_.find(lit_led) == blinking_leds_.end() && lights_.find(lit_led) != lights_.end()) lights_[lit_led]->draw(0.0, 0.8, 0.0); @@ -139,26 +143,31 @@ class ActivityObserver: public Activity::Observer { private: std::vector leds_; void register_led(const std::string &name) override { + std::lock_guard lock_guard(mutex); leds_.push_back(name); } std::vector drives_; void register_drive(const std::string &name) override { + std::lock_guard lock_guard(mutex); drives_.push_back(name); } void set_led_status(const std::string &name, bool lit) override { + std::lock_guard lock_guard(mutex); if(lit) lit_leds_.insert(name); else lit_leds_.erase(name); } void announce_drive_event(const std::string &name, DriveEvent event) override { + std::lock_guard lock_guard(mutex); blinking_leds_.insert(name); } std::map> lights_; std::set lit_leds_; std::set blinking_leds_; + std::mutex mutex; }; bool KeyboardKeyForSDLScancode(SDL_Keycode scancode, Inputs::Keyboard::Key &key) { @@ -442,6 +451,7 @@ int main(int argc, char *argv[]) { // Create and configure a machine. ::Machine::Error error; + std::mutex machine_mutex; std::unique_ptr<::Machine::DynamicMachine> machine(::Machine::MachineForTargets(targets, rom_fetcher, error)); if(!machine) { switch(error) { @@ -463,6 +473,7 @@ int main(int argc, char *argv[]) { } best_effort_updater_delegate.machine = machine.get(); + best_effort_updater_delegate.machine_mutex = &machine_mutex; speaker_delegate.updater = &updater; updater.set_delegate(&best_effort_updater_delegate); @@ -617,7 +628,17 @@ int main(int argc, char *argv[]) { bool should_quit = false; Uint32 fullscreen_mode = 0; while(!should_quit) { - // Process all pending events. + // Wait for vsync, draw a new frame and post a machine update. + // NB: machine_mutex is *not* currently locked, therefore it shouldn't + // be 'most' of the time. + SDL_GL_SwapWindow(window); + scan_target.update(int(window_width), int(window_height)); + scan_target.draw(int(window_width), int(window_height)); + if(activity_observer) activity_observer->draw(); + updater.update(); + + // Grab the machine lock and process all pending events. + std::lock_guard lock_guard(machine_mutex); SDL_Event event; while(SDL_PollEvent(&event)) { switch(event.type) { @@ -643,87 +664,88 @@ int main(int argc, char *argv[]) { } break; case SDL_KEYDOWN: - // Syphon off the key-press if it's control+shift+V (paste). - if(event.key.keysym.sym == SDLK_v && (SDL_GetModState()&KMOD_CTRL) && (SDL_GetModState()&KMOD_SHIFT)) { - const auto keyboard_machine = machine->keyboard_machine(); - if(keyboard_machine) { - keyboard_machine->type_string(SDL_GetClipboardText()); + case SDL_KEYUP: { + const auto keyboard_machine = machine->keyboard_machine(); + + if(event.type == SDL_KEYDOWN) { + // Syphon off the key-press if it's control+shift+V (paste). + if(event.key.keysym.sym == SDLK_v && (SDL_GetModState()&KMOD_CTRL) && (SDL_GetModState()&KMOD_SHIFT)) { + if(keyboard_machine) { + keyboard_machine->type_string(SDL_GetClipboardText()); + break; + } + } + + // Use ctrl+escape to release the mouse (if captured). + if(event.key.keysym.sym == SDLK_ESCAPE && (SDL_GetModState()&KMOD_CTRL)) { + SDL_SetRelativeMouseMode(SDL_FALSE); + window_titler.set_mouse_is_captured(false); + } + + // Capture ctrl+shift+d as a take-a-screenshot command. + if(event.key.keysym.sym == SDLK_d && (SDL_GetModState()&KMOD_CTRL) && (SDL_GetModState()&KMOD_SHIFT)) { + // Grab the screen buffer. + Outputs::Display::OpenGL::Screenshot screenshot(4, 3); + + // Pick the directory for images. Try `xdg-user-dir PICTURES` first. + std::string target_directory = system_get("xdg-user-dir PICTURES"); + + // Make sure there are no newlines. + target_directory.erase(std::remove(target_directory.begin(), target_directory.end(), '\n'), target_directory.end()); + target_directory.erase(std::remove(target_directory.begin(), target_directory.end(), '\r'), target_directory.end()); + + // Fall back on the HOME directory if necessary. + if(target_directory.empty()) target_directory = getenv("HOME"); + + // Find the first available name of the form ~/clk-screenshot-.bmp. + size_t index = 0; + std::string target; + while(true) { + target = target_directory + "/clk-screenshot-" + std::to_string(index) + ".bmp"; + + struct stat file_stats; + if(stat(target.c_str(), &file_stats)) + break; + + ++index; + } + + // Create a suitable SDL surface and save the thing. + const bool is_big_endian = SDL_BYTEORDER == SDL_BIG_ENDIAN; + SDL_Surface *const surface = SDL_CreateRGBSurfaceFrom( + screenshot.pixel_data.data(), + screenshot.width, screenshot.height, + 8*4, + screenshot.width*4, + is_big_endian ? 0xff000000 : 0x000000ff, + is_big_endian ? 0x00ff0000 : 0x0000ff00, + is_big_endian ? 0x0000ff00 : 0x00ff0000, + 0); + SDL_SaveBMP(surface, target.c_str()); + SDL_FreeSurface(surface); + break; + } + + + // Syphon off alt+enter (toggle full-screen) upon key up only; this was previously a key down action, + // but the SDL_KEYDOWN announcement was found to be reposted after changing graphics mode on some + // systems so key up is safer. + if(event.type == SDL_KEYUP && event.key.keysym.sym == SDLK_RETURN && (SDL_GetModState()&KMOD_ALT)) { + fullscreen_mode ^= SDL_WINDOW_FULLSCREEN_DESKTOP; + SDL_SetWindowFullscreen(window, fullscreen_mode); + SDL_ShowCursor((fullscreen_mode&SDL_WINDOW_FULLSCREEN_DESKTOP) ? SDL_DISABLE : SDL_ENABLE); + + // Announce a potential discontinuity in keyboard input. + const auto keyboard_machine = machine->keyboard_machine(); + if(keyboard_machine) { + keyboard_machine->get_keyboard().reset_all_keys(); + } break; } } - // Use ctrl+escape to release the mouse (if captured). - if(event.key.keysym.sym == SDLK_ESCAPE && (SDL_GetModState()&KMOD_CTRL)) { - SDL_SetRelativeMouseMode(SDL_FALSE); - window_titler.set_mouse_is_captured(false); - } - - // Capture ctrl+shift+d as a take-a-screenshot command. - if(event.key.keysym.sym == SDLK_d && (SDL_GetModState()&KMOD_CTRL) && (SDL_GetModState()&KMOD_SHIFT)) { - // Grab the screen buffer. - Outputs::Display::OpenGL::Screenshot screenshot(4, 3); - - // Pick the directory for images. Try `xdg-user-dir PICTURES` first. - std::string target_directory = system_get("xdg-user-dir PICTURES"); - - // Make sure there are no newlines. - target_directory.erase(std::remove(target_directory.begin(), target_directory.end(), '\n'), target_directory.end()); - target_directory.erase(std::remove(target_directory.begin(), target_directory.end(), '\r'), target_directory.end()); - - // Fall back on the HOME directory if necessary. - if(target_directory.empty()) target_directory = getenv("HOME"); - - // Find the first available name of the form ~/clk-screenshot-.bmp. - size_t index = 0; - std::string target; - while(true) { - target = target_directory + "/clk-screenshot-" + std::to_string(index) + ".bmp"; - - struct stat file_stats; - if(stat(target.c_str(), &file_stats)) - break; - - ++index; - } - - // Create a suitable SDL surface and save the thing. - const bool is_big_endian = SDL_BYTEORDER == SDL_BIG_ENDIAN; - SDL_Surface *const surface = SDL_CreateRGBSurfaceFrom( - screenshot.pixel_data.data(), - screenshot.width, screenshot.height, - 8*4, - screenshot.width*4, - is_big_endian ? 0xff000000 : 0x000000ff, - is_big_endian ? 0x00ff0000 : 0x0000ff00, - is_big_endian ? 0x0000ff00 : 0x00ff0000, - 0); - SDL_SaveBMP(surface, target.c_str()); - SDL_FreeSurface(surface); - break; - } - - // deliberate fallthrough... - case SDL_KEYUP: { - - // Syphon off alt+enter (toggle full-screen) upon key up only; this was previously a key down action, - // but the SDL_KEYDOWN announcement was found to be reposted after changing graphics mode on some - // systems so key up is safer. - if(event.type == SDL_KEYUP && event.key.keysym.sym == SDLK_RETURN && (SDL_GetModState()&KMOD_ALT)) { - fullscreen_mode ^= SDL_WINDOW_FULLSCREEN_DESKTOP; - SDL_SetWindowFullscreen(window, fullscreen_mode); - SDL_ShowCursor((fullscreen_mode&SDL_WINDOW_FULLSCREEN_DESKTOP) ? SDL_DISABLE : SDL_ENABLE); - - // Announce a potential discontinuity in keyboard input. - const auto keyboard_machine = machine->keyboard_machine(); - if(keyboard_machine) { - keyboard_machine->get_keyboard().reset_all_keys(); - } - break; - } - const bool is_pressed = event.type == SDL_KEYDOWN; - const auto keyboard_machine = machine->keyboard_machine(); if(keyboard_machine) { Inputs::Keyboard::Key key = Inputs::Keyboard::Key::Space; if(!KeyboardKeyForSDLScancode(event.key.keysym.scancode, key)) break; @@ -760,12 +782,13 @@ int main(int argc, char *argv[]) { } break; case SDL_MOUSEBUTTONDOWN: - if(uses_mouse && !SDL_GetRelativeMouseMode()) { + case SDL_MOUSEBUTTONUP: { + if(uses_mouse && event.type == SDL_MOUSEBUTTONDOWN && !SDL_GetRelativeMouseMode()) { SDL_SetRelativeMouseMode(SDL_TRUE); window_titler.set_mouse_is_captured(true); break; } - case SDL_MOUSEBUTTONUP: { + const auto mouse_machine = machine->mouse_machine(); if(mouse_machine) { mouse_machine->get_mouse().set_button_pressed( @@ -834,16 +857,10 @@ int main(int argc, char *argv[]) { } } } - - // Display a new frame and wait for vsync. - updater.update(); - scan_target.update(int(window_width), int(window_height)); - scan_target.draw(int(window_width), int(window_height)); - if(activity_observer) activity_observer->draw(); - SDL_GL_SwapWindow(window); } // Clean up. + updater.flush(); // Ensure no further updates will occur. joysticks.clear(); SDL_DestroyWindow( window ); SDL_Quit();