#include "lsnes.hpp" #include "core/command.hpp" #include "core/dispatch.hpp" #include "core/framebuffer.hpp" #include "core/framerate.hpp" #include "core/window.hpp" #include "plat-sdl/platform.hpp" screen_model screenmod; #define USERCODE_TIMER 0 #define USERCODE_PAINT 1 #define SPECIALMODE_NORMAL 0 #define SPECIALMODE_COMMAND 1 #define SPECIALMODE_IDENTIFY 2 #define SPECIALMODE_MODAL 3 #ifdef SDL_NO_JOYSTICK unsigned translate_sdl_joystick(SDL_Event& e, keypress& k1) { return 0; } #endif namespace { //Dirty flags for various displays. volatile bool messages_dirty = false; volatile bool status_dirty = false; volatile bool screen_dirty = false; volatile bool fullscreen_console = false; bool fullscreen_console_active = false; //If true, repaint request is in flight. Protected by ui_mutex. volatile bool repaint_in_flight = false; //If true, timer event has occured. volatile bool timer_triggered = false; //If true, modal dialog is to be displayed. Pull low to ack the dialog. Protected by ui_mutex. volatile bool modal_dialog_active = false; //If modal_dialog_active is true, this is the text for the dialog box. Protected by ui_mutex. std::string modal_dialog_text; //If true, the modal dialog is confirmation dialog. Pull low if confirmation dialog is canceled. Protected //by ui_mutex. volatile bool modal_dialog_confirm = true; //Set if emulator panics. volatile bool paniced = false; //Set when user dismisses panic prompt (emulator can exit). volatile bool panic_ack = false; //Set after SIGALRM handler has got installed. bool sigalrm_handler_installed = false; //Special mode. unsigned special_mode; volatile bool identify_requested; volatile bool emulator_thread_exited = false; //The command line modal to use. commandline_model cmdline; //Mutex protecting various variables and associated mutex for waking the emulator thread from some blocking //operations. mutex* ui_mutex; condition* ui_condition; //Set to true when SDL has been initialized. bool sdl_init = false; //Thread ID of UI thread (for identifying it). thread_id* ui_thread; //Timer for implementing stuff like autorepeat. SDL_TimerID timer_id; //Timer IRQ counter. Used by identify key stuff. volatile unsigned timer_irq_counter; void sigalrm_handler(int s) { _exit(1); } void send_sdl_userevent(int code) { SDL_Event e; e.type = SDL_USEREVENT; e.user.code = USERCODE_PAINT; e.user.data1 = NULL; e.user.data2 = NULL; ui_mutex->lock(); //SDL_PushEvent(&e); ui_mutex->unlock(); } Uint32 timer_cb(Uint32 interval, void* param) { //send_sdl_userevent(USERCODE_TIMER); timer_triggered = true; timer_irq_counter = timer_irq_counter + 1; return interval; } void arm_sigalrm() { #ifdef SIGALRM if(!sigalrm_handler_installed) signal(SIGALRM, sigalrm_handler); sigalrm_handler_installed = true; //alarm(15); #endif } void ui_panic() { //Be very careful what you use here, as program state can be really unpredictable! try { platform::message("PANIC: Cannot continue, press ESC or close window to exit."); screenmod.repaint_full(); screenmod.flip(); } catch(...) { //Just crash. panic_ack = true; return; } while(true) { SDL_Event e; if(SDL_WaitEvent(&e)) { if(e.type == SDL_QUIT) break; if(e.type == SDL_ACTIVEEVENT) { screenmod.repaint_full(); screenmod.flip(); } if(e.type == SDL_KEYUP && e.key.keysym.sym == SDLK_ESCAPE) break; } } panic_ack = true; } void wake_ui() { mutex::holder h(*ui_mutex); if(!repaint_in_flight) { //Wake the UI. repaint_in_flight = true; //send_sdl_userevent(USERCODE_PAINT); } } function_ptr_command<> identify_key("identify-key", "Identify a key", "Syntax: identify-key\nIdentifies a (pseudo-)key.\n", []() throw(std::bad_alloc, std::runtime_error) { identify_requested = true; wake_ui(); }); function_ptr_command<> scroll_up("scroll-up", "Scroll messages a page up", "Syntax: scroll-up\nScrolls message console backward one page.\n", []() throw(std::bad_alloc, std::runtime_error) { mutex::holder h(platform::msgbuf_lock()); platform::msgbuf.scroll_up_page(); }); function_ptr_command<> scroll_fullup("scroll-fullup", "Scroll messages to beginning", "Syntax: scroll-fullup\nScrolls message console to its beginning.\n", []() throw(std::bad_alloc, std::runtime_error) { mutex::holder h(platform::msgbuf_lock()); platform::msgbuf.scroll_beginning(); }); function_ptr_command<> scroll_fulldown("scroll-fulldown", "Scroll messages to end", "Syntax: scroll-fulldown\nScrolls message console to its end.\n", []() throw(std::bad_alloc, std::runtime_error) { mutex::holder h(platform::msgbuf_lock()); platform::msgbuf.scroll_end(); }); function_ptr_command<> scrolldown("scroll-down", "Scroll messages a page down", "Syntax: scroll-up\nScrolls message console forward one page.\n", []() throw(std::bad_alloc, std::runtime_error) { mutex::holder h(platform::msgbuf_lock()); platform::msgbuf.scroll_down_page(); }); function_ptr_command<> toggle_console("toggle-console", "Toggle console between small and full window", "Syntax: toggle-console\nToggles console between small and large.\n", []() throw(std::bad_alloc, std::runtime_error) { fullscreen_console = !fullscreen_console; wake_ui(); }); class keygrabber : public information_dispatch { public: keygrabber() : information_dispatch("sdl-key-grabber") { idmode = false; } void enter_id_mode() { keys = ""; idmode = true; } bool got_id() { return keys != ""; } std::string get_id() { return keys; } void leave_id_mode() { keys = ""; idmode = false; } void on_key_event(const modifier_set& modifiers, keygroup& keygroup, unsigned subkey, bool polarity, const std::string& name) { if(idmode && !polarity) { keys = keys + "key: " + name + "\n"; } } bool idmode; std::string keys; } keygrabber; void emu_ungrab_keys(void* dummy) { keygrabber.ungrab_keys(); keygrabber.leave_id_mode(); } void emu_grab_keys_identify(void* dummy) { keygrabber.enter_id_mode(); keygrabber.grab_keys(); } void emu_grab_keys_nonid(void* dummy) { keygrabber.leave_id_mode(); keygrabber.grab_keys(); } void emu_handle_quit_signal(void* dummy) { information_dispatch::do_close(); } void emu_handle_identify(void* dummy) { if(keygrabber.got_id()) { std::string k = keygrabber.get_id(); keygrabber.leave_id_mode(); platform::modal_message(k, false); } //Exiting the modal mode undoes key grab and modal pause. } //Grab keys, setting or unsetting id mode. void ui_grab_keys(bool idmode) { if(idmode) platform::queue(emu_grab_keys_identify, NULL, true); else platform::queue(emu_grab_keys_nonid, NULL, true); } //Grab keys, unset id mode, special. void ui_grab_keys_special() { keygrabber.leave_id_mode(); keygrabber.grab_keys(); } //Ungrab keys. void ui_ungrab_keys(bool direct = false) { if(direct) { keygrabber.ungrab_keys(); keygrabber.leave_id_mode(); } else platform::queue(emu_ungrab_keys, NULL, true); } //Handle identify timer interrupt. void ui_handle_identify() { //This call has to be asynchronous. platform::queue(emu_handle_identify, NULL, false); } //Handle QUIT in normal state. void ui_handle_quit_signal() { platform::queue(emu_handle_quit_signal, NULL, false); } } void notify_emulator_exit() { emulator_thread_exited = true; wake_ui(); } void ui_loop() { uint32_t mouse_state = 0; uint32_t k; uint64_t kts; std::string cmd; bool modal_dialog_was_active = false; while(!emulator_thread_exited) { bool commandline_updated = false; SDL_Event e; SDLKey kbdkey; bool iskbd = false; bool polarity; bool full = false; memset(&e, 0, sizeof(e)); { ui_mutex->lock(); if(!repaint_in_flight && !timer_triggered && !SDL_PollEvent(&e)) { ui_mutex->unlock(); usleep(5000); continue; } ui_mutex->unlock(); } if(e.type == SDL_KEYUP) { iskbd = true; polarity = false; kbdkey = e.key.keysym.sym; //std::cerr << "Keyup symbol " << kbdkey << "(held=" << (get_utime() - kts) << "us)" << std::endl; } else if(e.type == SDL_KEYDOWN) { iskbd = true; polarity = true; kbdkey = e.key.keysym.sym; //std::cerr << "Keydown symbol " << kbdkey << std::endl; kts = get_utime(); } if(e.type == SDL_VIDEOEXPOSE || e.type == SDL_ACTIVEEVENT) full = true; //Handle panics. if(paniced) { ui_panic(); while(true); } if(e.type == SDL_MOUSEBUTTONDOWN) { int i; switch(e.button.button) { case SDL_BUTTON_LEFT: mouse_state |= 1; break; case SDL_BUTTON_MIDDLE: mouse_state |= 2; break; case SDL_BUTTON_RIGHT: mouse_state |= 4; break; }; send_mouse_click(e.button.x, e.button.y, mouse_state); } if(e.type == SDL_MOUSEBUTTONUP) { switch(e.button.button) { case SDL_BUTTON_LEFT: mouse_state &= ~1; break; case SDL_BUTTON_MIDDLE: mouse_state &= ~2; break; case SDL_BUTTON_RIGHT: mouse_state &= ~4; break; }; send_mouse_click(e.button.x, e.button.y, mouse_state); } //Handle entering identify mode. if(identify_requested) { identify_requested = false; special_mode = SPECIALMODE_IDENTIFY; ui_grab_keys(true); platform::message("Press key to identify..."); } //Handle entering modal dialog. if(!modal_dialog_was_active && modal_dialog_active) { screenmod.set_modal(modal_dialog_text, modal_dialog_confirm); special_mode = SPECIALMODE_MODAL; modal_dialog_was_active = true; ui_grab_keys_special(); } //Handle special modes. switch(special_mode) { case SPECIALMODE_NORMAL: //Enable command line if needed. if(iskbd && kbdkey == SDLK_ESCAPE) { if(!cmdline.enabled() && !polarity) { cmdline.enable(); commandline_updated = true; special_mode = SPECIALMODE_COMMAND; platform::set_modal_pause(true); ui_grab_keys(false); //std::cerr << "Entered commandline mode." << std::endl; } continue; } break; case SPECIALMODE_COMMAND: if(timer_triggered || (e.type == SDL_USEREVENT && e.user.code == USERCODE_TIMER)) { cmdline.tick(); timer_triggered = false; } cmd = cmdline.key(get_command_edit_operation(e, true)); if(cmd != "") { //std::cerr << "To execute: '" << cmd << "'" << std::endl; platform::queue(cmd); } commandline_updated = true; if(!cmdline.enabled()) { //std::cerr << "Exited commandline mode." << std::endl; //Exiting commandline mode. special_mode = SPECIALMODE_NORMAL; platform::set_modal_pause(false); ui_ungrab_keys(); } break; case SPECIALMODE_IDENTIFY: if(timer_triggered || (e.type == SDL_USEREVENT && e.user.code == USERCODE_TIMER)) { ui_handle_identify(); timer_triggered = false; } break; case SPECIALMODE_MODAL: if((iskbd && !polarity && kbdkey == SDLK_ESCAPE) || e.type == SDL_QUIT) { //Negative response. modal_dialog_confirm = false; modal_dialog_active = false; screenmod.clear_modal(); special_mode = SPECIALMODE_NORMAL; //We NAK the command in case modal dialog was somehow entered with command active. cmdline.key(SPECIAL_NAK); ui_ungrab_keys(true); platform::set_modal_pause(false); modal_dialog_was_active = false; mutex::holder h(*ui_mutex); ui_condition->signal(); } if(iskbd && !polarity && (kbdkey == SDLK_RETURN || kbdkey == SDLK_KP_ENTER)) { //Positive response. modal_dialog_active = false; screenmod.clear_modal(); modal_dialog_was_active = false; special_mode = SPECIALMODE_NORMAL; //We NAK the command in case modal dialog was somehow entered with command active. cmdline.key(SPECIAL_NAK); platform::set_modal_pause(false); ui_ungrab_keys(true); mutex::holder h(*ui_mutex); ui_condition->signal(); } break; } timer_triggered = false; if(e.type == SDL_QUIT) ui_handle_quit_signal(); //Yes, normal key handling is done even if in commandline or other modal mode. keypress k; if(translate_sdl_key(e, k)) { platform::queue(k); } if(translate_sdl_joystick(e, k)) { platform::queue(k); } //Handle repaints. bool status = false; bool screen = false; bool pmessages = false; bool new_fsc = false; bool toggle_fsc = false; { mutex::holder h(*ui_mutex); pmessages = messages_dirty; status = status_dirty; screen = screen_dirty; new_fsc = fullscreen_console; messages_dirty = false; screen_dirty = false; status_dirty = false; if(new_fsc != fullscreen_console_active) toggle_fsc = true; else toggle_fsc = false; repaint_in_flight = false; } //If screen is dirty (irrespective if full repaint would be done), render the screeen. if(screen) render_framebuffer(); bool any = status || pmessages || screen || commandline_updated || toggle_fsc || full; if(special_mode == SPECIALMODE_MODAL || full) { //FIXME: Use less intensive paint for SPECIALMODE_MODAL. screenmod.repaint_full(); any = true; } else { if(status) { screenmod.repaint_status(); } if(pmessages) { screenmod.repaint_messages(); } if(screen) { screenmod.repaint_screen(); } if(commandline_updated) { screenmod.repaint_commandline(); } if(toggle_fsc) { screenmod.set_fullscreen_console(new_fsc); fullscreen_console_active = new_fsc; } } if(any) screenmod.flip(); } } void graphics_plugin::init() throw() { if(!ui_mutex) ui_mutex = &mutex::aquire(); if(!ui_condition) ui_condition = &condition::aquire(*ui_mutex); screenmod.set_command_line(&cmdline); arm_sigalrm(); ui_thread = &thread_id::me(); init_sdl_keys(); if(!sdl_init) { SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_TIMER); SDL_EnableUNICODE(true); sdl_init = true; timer_id = SDL_AddTimer(30, timer_cb, NULL); } //Doing full repaint will open the window. screenmod.repaint_full(); std::string windowname = "lsnes rr" + lsnes_version + "[" + bsnes_core_version + "]"; SDL_WM_SetCaption(windowname.c_str(), "lsnes"); } void graphics_plugin::quit() throw() { if(sdl_init) { SDL_Quit(); sdl_init = false; SDL_RemoveTimer(timer_id); } deinit_sdl_keys(); } void graphics_plugin::notify_message() throw() { messages_dirty = true; wake_ui(); } void graphics_plugin::notify_status() throw() { status_dirty = true; wake_ui(); } void graphics_plugin::notify_screen() throw() { screen_dirty = true; wake_ui(); } bool graphics_plugin::modal_message(const std::string& text, bool confirm) throw() { bool answer = false; try { //Make the UI thread do the prompting. mutex::holder h(*ui_mutex); modal_dialog_active = true; modal_dialog_text = text; modal_dialog_confirm = confirm; while(modal_dialog_active) ui_condition->wait(100000); answer = modal_dialog_confirm; } catch(std::bad_alloc& e) { OOM_panic(); } return answer; } void graphics_plugin::fatal_error() throw() { //Fun... This can be called from any thread. if(ui_thread->is_me()) { ui_panic(); } else { paniced = true; wake_ui(); //Busywait as program state may be very unpredictable. while(!panic_ack); } } const char* graphics_plugin::name = "SDL graphics plugin";