diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index ce74ad2d..ddc271f3 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -13,7 +13,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "106.38"; + static const string Version = "106.39"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "https://byuu.org/"; diff --git a/higan/sfc/coprocessor/sa1/io.cpp b/higan/sfc/coprocessor/sa1/io.cpp index 2ba39f85..37d58796 100644 --- a/higan/sfc/coprocessor/sa1/io.cpp +++ b/higan/sfc/coprocessor/sa1/io.cpp @@ -415,19 +415,19 @@ auto SA1::writeIO(uint24 addr, uint8 data) -> void { //(MAL) multiplicand / dividend low case 0x2251: { - mmio.ma = (mmio.ma & 0xff00) | data; + mmio.ma.byte(0) = data; return; } //(MAH) multiplicand / dividend high case 0x2252: { - mmio.ma = (data << 8) | (mmio.ma & 0x00ff); + mmio.ma.byte(1) = data; return; } //(MBL) multiplier / divisor low case 0x2253: { - mmio.mb = (mmio.mb & 0xff00) | data; + mmio.mb.byte(0) = data; return; } @@ -435,21 +435,23 @@ auto SA1::writeIO(uint24 addr, uint8 data) -> void { //multiplication / cumulative sum only resets MB //division resets both MA and MB case 0x2254: { - mmio.mb = (data << 8) | (mmio.mb & 0x00ff); + mmio.mb.byte(1) = data; if(mmio.acm == 0) { if(mmio.md == 0) { //signed multiplication - mmio.mr = (int16)mmio.ma * (int16)mmio.mb; + mmio.mr = (uint32)((int16)mmio.ma * (int16)mmio.mb); mmio.mb = 0; } else { //unsigned division if(mmio.mb == 0) { mmio.mr = 0; } else { - int16 quotient = (int16)mmio.ma / (uint16)mmio.mb; - uint16 remainder = (int16)mmio.ma % (uint16)mmio.mb; - mmio.mr = (remainder << 16) | quotient; + int16 dividend = mmio.ma; + uint16 divisor = mmio.mb; + uint16 remainder = dividend >= 0 ? dividend % divisor : (dividend % divisor + divisor) % divisor; + uint16 quotient = (dividend - remainder) / divisor; + mmio.mr = remainder << 16 | quotient; } mmio.ma = 0; mmio.mb = 0; @@ -457,8 +459,8 @@ auto SA1::writeIO(uint24 addr, uint8 data) -> void { } else { //sigma (accumulative multiplication) mmio.mr += (int16)mmio.ma * (int16)mmio.mb; - mmio.overflow = (mmio.mr >= (1ULL << 40)); - mmio.mr &= (1ULL << 40) - 1; + mmio.overflow = mmio.mr >> 40; + mmio.mr = (uint40)mmio.mr; mmio.mb = 0; } return; diff --git a/higan/target-bsnes/presentation/presentation.cpp b/higan/target-bsnes/presentation/presentation.cpp index 7d752e35..2b37b562 100644 --- a/higan/target-bsnes/presentation/presentation.cpp +++ b/higan/target-bsnes/presentation/presentation.cpp @@ -99,10 +99,12 @@ Presentation::Presentation() { statusBar.setVisible(showStatusBar.checked()); if(visible()) resizeWindow(); }); - inputSettings.setText("Input ...").onActivate([&] { settingsWindow->show(0); }); - hotkeySettings.setText("Hotkeys ...").onActivate([&] { settingsWindow->show(1); }); - pathSettings.setText("Paths ...").onActivate([&] { settingsWindow->show(2); }); - advancedSettings.setText("Advanced ...").onActivate([&] { settingsWindow->show(3); }); + videoSettings.setText("Video ...").onActivate([&] { settingsWindow->show(0); }); + audioSettings.setText("Audio ...").onActivate([&] { settingsWindow->show(1); }); + inputSettings.setText("Input ...").onActivate([&] { settingsWindow->show(2); }); + hotkeySettings.setText("Hotkeys ...").onActivate([&] { settingsWindow->show(3); }); + pathSettings.setText("Paths ...").onActivate([&] { settingsWindow->show(4); }); + advancedSettings.setText("Advanced ...").onActivate([&] { settingsWindow->show(5); }); toolsMenu.setText("Tools").setVisible(false); saveState.setText("Save State"); @@ -171,7 +173,7 @@ Presentation::Presentation() { #if defined(PLATFORM_MACOS) Application::Cocoa::onAbout([&] { about.doActivate(); }); Application::Cocoa::onActivate([&] { setFocused(); }); - Application::Cocoa::onPreferences([&] { settingsWindow->show(0); }); + Application::Cocoa::onPreferences([&] { settingsWindow->show(2); }); Application::Cocoa::onQuit([&] { doClose(); }); #endif } diff --git a/higan/target-bsnes/presentation/presentation.hpp b/higan/target-bsnes/presentation/presentation.hpp index 6ce66494..6dcfdb04 100644 --- a/higan/target-bsnes/presentation/presentation.hpp +++ b/higan/target-bsnes/presentation/presentation.hpp @@ -49,6 +49,8 @@ struct Presentation : Window { MenuCheckItem muteAudio{&settingsMenu}; MenuCheckItem showStatusBar{&settingsMenu}; MenuSeparator settingsSeparator{&settingsMenu}; + MenuItem videoSettings{&settingsMenu}; + MenuItem audioSettings{&settingsMenu}; MenuItem inputSettings{&settingsMenu}; MenuItem hotkeySettings{&settingsMenu}; MenuItem pathSettings{&settingsMenu}; diff --git a/higan/target-bsnes/program/program.cpp b/higan/target-bsnes/program/program.cpp index bc271f32..db4d1cf6 100644 --- a/higan/target-bsnes/program/program.cpp +++ b/higan/target-bsnes/program/program.cpp @@ -77,6 +77,7 @@ Program::Program(string_vector arguments) { auto Program::main() -> void { updateMessage(); + video->poll(); inputManager->poll(); inputManager->pollHotkeys(); diff --git a/higan/target-bsnes/program/utility.cpp b/higan/target-bsnes/program/utility.cpp index 6e02873e..cbb20c08 100644 --- a/higan/target-bsnes/program/utility.cpp +++ b/higan/target-bsnes/program/utility.cpp @@ -8,6 +8,10 @@ auto Program::initializeVideoDriver() -> void { presentation->clearViewport(); updateVideoShader(); } + + video->onUpdate([&](uint width, uint height) { + if(!emulator->loaded()) presentation->clearViewport(); + }); } auto Program::initializeAudioDriver() -> void { diff --git a/higan/target-bsnes/settings/advanced.cpp b/higan/target-bsnes/settings/advanced.cpp index 0ea1693d..8968377e 100644 --- a/higan/target-bsnes/settings/advanced.cpp +++ b/higan/target-bsnes/settings/advanced.cpp @@ -3,8 +3,9 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { setText("Advanced"); layout.setMargin(5); + driversLabel.setText("Drivers").setFont(Font().setBold()); - videoDriverLabel.setText("Video Driver:"); + videoDriverLabel.setText("Video:"); for(auto& driver : Video::availableDrivers()) { ComboButtonItem item; item.setText(driver); @@ -12,10 +13,6 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { if(video->driver() == driver) item.setSelected(); } videoDriverOption.onChange([&] { - auto item = videoDriverOption.selected(); - videoDriverChange.setEnabled(video->driver() != item.text()); - }); - videoDriverChange.setText("Change").setEnabled(false).onActivate([&] { auto item = videoDriverOption.selected(); settings["Video/Driver"].setValue(item.text()); if(!emulator->loaded() || item.text() == "None" || MessageDialog( @@ -23,6 +20,7 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { "It is highly recommended you unload your game first to avoid data loss.\n" "Do you wish to proceed with the video driver change now anyway?" ).setParent(*settingsWindow).question() == "Yes") { + program->save(); program->saveRecoveryState(); settings["Crashed"].setValue(true); settings.save(); @@ -39,11 +37,10 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { } settings["Crashed"].setValue(false); settings.save(); - videoDriverChange.setEnabled(false); } }); - audioDriverLabel.setText("Audio Driver:"); + audioDriverLabel.setText("Audio:"); for(auto& driver : Audio::availableDrivers()) { ComboButtonItem item; item.setText(driver); @@ -51,10 +48,6 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { if(audio->driver() == driver) item.setSelected(); } audioDriverOption.onChange([&] { - auto item = audioDriverOption.selected(); - audioDriverChange.setEnabled(audio->driver() != item.text()); - }); - audioDriverChange.setText("Change").setEnabled(false).onActivate([&] { auto item = audioDriverOption.selected(); settings["Audio/Driver"].setValue(item.text()); if(!emulator->loaded() || item.text() == "None" || MessageDialog( @@ -62,6 +55,7 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { "It is highly recommended you unload your game first to avoid data loss.\n" "Do you wish to proceed with the audio driver change now anyway?" ).setParent(*settingsWindow).question() == "Yes") { + program->save(); program->saveRecoveryState(); settings["Crashed"].setValue(true); settings.save(); @@ -78,11 +72,10 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { } settings["Crashed"].setValue(false); settings.save(); - audioDriverChange.setEnabled(false); } }); - inputDriverLabel.setText("Input Driver:"); + inputDriverLabel.setText("Input:"); for(auto& driver : Input::availableDrivers()) { ComboButtonItem item; item.setText(driver); @@ -90,10 +83,6 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { if(input->driver() == driver) item.setSelected(); } inputDriverOption.onChange([&] { - auto item = inputDriverOption.selected(); - inputDriverChange.setEnabled(input->driver() != item.text()); - }); - inputDriverChange.setText("Change").setEnabled(false).onActivate([&] { auto item = inputDriverOption.selected(); settings["Input/Driver"].setValue(item.text()); if(!emulator->loaded() || item.text() == "None" || MessageDialog( @@ -101,6 +90,7 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { "It is highly recommended you unload your game first to avoid data loss.\n" "Do you wish to proceed with the input driver change now anyway?" ).setParent(*settingsWindow).question() == "Yes") { + program->save(); program->saveRecoveryState(); settings["Crashed"].setValue(true); settings.save(); @@ -117,7 +107,6 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { } settings["Crashed"].setValue(false); settings.save(); - inputDriverChange.setEnabled(false); } }); } diff --git a/higan/target-bsnes/settings/audio.cpp b/higan/target-bsnes/settings/audio.cpp new file mode 100644 index 00000000..37314433 --- /dev/null +++ b/higan/target-bsnes/settings/audio.cpp @@ -0,0 +1,4 @@ +AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) { + setIcon(Icon::Device::Speaker); + setText("Audio"); +} diff --git a/higan/target-bsnes/settings/hotkeys.cpp b/higan/target-bsnes/settings/hotkeys.cpp index 68088ff5..d39c4d09 100644 --- a/higan/target-bsnes/settings/hotkeys.cpp +++ b/higan/target-bsnes/settings/hotkeys.cpp @@ -34,7 +34,7 @@ auto HotkeySettings::reloadMappings() -> void { ); for(auto& hotkey : inputManager->hotkeys) { mappingList.append(TableViewItem() - .append(TableViewCell().setText(hotkey.name)) + .append(TableViewCell().setText(hotkey.name).setFont(Font().setBold()).setBackgroundColor({240, 240, 255})) .append(TableViewCell()) ); } diff --git a/higan/target-bsnes/settings/input.cpp b/higan/target-bsnes/settings/input.cpp index d468dfac..0103af75 100644 --- a/higan/target-bsnes/settings/input.cpp +++ b/higan/target-bsnes/settings/input.cpp @@ -98,7 +98,7 @@ auto InputSettings::reloadMappings() -> void { ); for(auto& mapping : activeDevice().mappings) { mappingList.append(TableViewItem() - .append(TableViewCell().setText(mapping.name)) + .append(TableViewCell().setText(mapping.name).setFont(Font().setBold()).setBackgroundColor({240, 240, 255})) .append(TableViewCell()) ); } @@ -111,6 +111,7 @@ auto InputSettings::refreshMappings() -> void { for(auto& mapping : activeDevice().mappings) { mappingList.item(index++).cell(1).setText(mapping.displayName()); } + Application::processEvents(); mappingList.resizeColumns(); } diff --git a/higan/target-bsnes/settings/settings.cpp b/higan/target-bsnes/settings/settings.cpp index a50cd08b..0a3294f2 100644 --- a/higan/target-bsnes/settings/settings.cpp +++ b/higan/target-bsnes/settings/settings.cpp @@ -1,4 +1,6 @@ #include "../bsnes.hpp" +#include "video.cpp" +#include "audio.cpp" #include "input.cpp" #include "hotkeys.cpp" #include "paths.cpp" diff --git a/higan/target-bsnes/settings/settings.hpp b/higan/target-bsnes/settings/settings.hpp index 012e6ec4..cea008c4 100644 --- a/higan/target-bsnes/settings/settings.hpp +++ b/higan/target-bsnes/settings/settings.hpp @@ -3,6 +3,14 @@ struct Settings : Markup::Node { auto save() -> void; }; +struct VideoSettings : TabFrameItem { + VideoSettings(TabFrame*); +}; + +struct AudioSettings : TabFrameItem { + AudioSettings(TabFrame*); +}; + struct InputSettings : TabFrameItem { InputSettings(TabFrame*); auto updateControls() -> void; @@ -99,18 +107,14 @@ struct AdvancedSettings : TabFrameItem { public: VerticalLayout layout{this}; - HorizontalLayout videoDriverLayout{&layout, Size{~0, 0}}; - Label videoDriverLabel{&videoDriverLayout, Size{75, 0}}; - ComboButton videoDriverOption{&videoDriverLayout, Size{~0, 0}}; - Button videoDriverChange{&videoDriverLayout, Size{80, 0}}; - HorizontalLayout audioDriverLayout{&layout, Size{~0, 0}}; - Label audioDriverLabel{&audioDriverLayout, Size{75, 0}}; - ComboButton audioDriverOption{&audioDriverLayout, Size{~0, 0}}; - Button audioDriverChange{&audioDriverLayout, Size{80, 0}}; - HorizontalLayout inputDriverLayout{&layout, Size{~0, 0}}; - Label inputDriverLabel{&inputDriverLayout, Size{75, 0}}; - ComboButton inputDriverOption{&inputDriverLayout, Size{~0, 0}}; - Button inputDriverChange{&inputDriverLayout, Size{80, 0}}; + Label driversLabel{&layout, Size{~0, 0}, 2}; + HorizontalLayout driverLayout{&layout, Size{~0, 0}}; + Label videoDriverLabel{&driverLayout, Size{0, 0}}; + ComboButton videoDriverOption{&driverLayout, Size{~0, 0}}; + Label audioDriverLabel{&driverLayout, Size{0, 0}}; + ComboButton audioDriverOption{&driverLayout, Size{~0, 0}}; + Label inputDriverLabel{&driverLayout, Size{0, 0}}; + ComboButton inputDriverOption{&driverLayout, Size{~0, 0}}; }; struct SettingsWindow : Window { @@ -121,6 +125,8 @@ struct SettingsWindow : Window { public: VerticalLayout layout{this}; TabFrame panel{&layout, Size{~0, ~0}}; + VideoSettings video{&panel}; + AudioSettings audio{&panel}; InputSettings input{&panel}; HotkeySettings hotkeys{&panel}; PathSettings paths{&panel}; diff --git a/higan/target-bsnes/settings/video.cpp b/higan/target-bsnes/settings/video.cpp new file mode 100644 index 00000000..64fca553 --- /dev/null +++ b/higan/target-bsnes/settings/video.cpp @@ -0,0 +1,4 @@ +VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) { + setIcon(Icon::Device::Display); + setText("Video"); +} diff --git a/higan/target-bsnes/tools/state-manager.cpp b/higan/target-bsnes/tools/state-manager.cpp index d43cc088..6b99c6b6 100644 --- a/higan/target-bsnes/tools/state-manager.cpp +++ b/higan/target-bsnes/tools/state-manager.cpp @@ -75,6 +75,7 @@ StateManager::StateManager(TabFrame* parent) : TabFrameItem(parent) { stateList.onChange([&] { auto batched = stateList.batched(); loadButton.setEnabled(batched.size() == 1); + saveButton.setEnabled(batched.size() == 1); editButton.setEnabled(batched.size() == 1); removeButton.setEnabled(batched.size() >= 1); }); @@ -84,6 +85,11 @@ StateManager::StateManager(TabFrame* parent) : TabFrameItem(parent) { } }); saveButton.setText("Save").onActivate([&] { + if(auto item = stateList.selected()) { + program->saveState(item.cell(0).text()); + } + }); + addButton.setText("Add").onActivate([&] { stateWindow->show(); }); editButton.setText("Edit").onActivate([&] { diff --git a/higan/target-bsnes/tools/tools.hpp b/higan/target-bsnes/tools/tools.hpp index 89506229..1c99ee59 100644 --- a/higan/target-bsnes/tools/tools.hpp +++ b/higan/target-bsnes/tools/tools.hpp @@ -100,8 +100,9 @@ public: TableView stateList{&layout, Size{~0, ~0}}; HorizontalLayout controlLayout{&layout, Size{~0, 0}}; Button loadButton{&controlLayout, Size{80, 0}}; - Widget spacer{&controlLayout, Size{~0, 0}}; Button saveButton{&controlLayout, Size{80, 0}}; + Widget spacer{&controlLayout, Size{~0, 0}}; + Button addButton{&controlLayout, Size{80, 0}}; Button editButton{&controlLayout, Size{80, 0}}; Button removeButton{&controlLayout, Size{80, 0}}; }; diff --git a/hiro/gtk/widget/canvas.cpp b/hiro/gtk/widget/canvas.cpp index 954a0755..3e42e804 100644 --- a/hiro/gtk/widget/canvas.cpp +++ b/hiro/gtk/widget/canvas.cpp @@ -10,6 +10,11 @@ GtkSelectionData* data, unsigned type, unsigned timestamp, pCanvas* p) -> void { p->self().doDrop(paths); } +static auto Canvas_draw(GtkWidget* widget, cairo_t* context, pCanvas* p) -> signed { + p->_onDraw(context); + return true; +} + static auto Canvas_expose(GtkWidget* widget, GdkEventExpose* event, pCanvas* p) -> signed { p->_onExpose(event); return true; @@ -59,7 +64,11 @@ auto pCanvas::construct() -> void { g_signal_connect(G_OBJECT(gtkWidget), "button-press-event", G_CALLBACK(Canvas_mousePress), (gpointer)this); g_signal_connect(G_OBJECT(gtkWidget), "button-release-event", G_CALLBACK(Canvas_mouseRelease), (gpointer)this); g_signal_connect(G_OBJECT(gtkWidget), "drag-data-received", G_CALLBACK(Canvas_drop), (gpointer)this); + #if HIRO_GTK==2 g_signal_connect(G_OBJECT(gtkWidget), "expose-event", G_CALLBACK(Canvas_expose), (gpointer)this); + #elif HIRO_GTK==3 + g_signal_connect(G_OBJECT(gtkWidget), "draw", G_CALLBACK(Canvas_draw), (gpointer)this); + #endif g_signal_connect(G_OBJECT(gtkWidget), "leave-notify-event", G_CALLBACK(Canvas_mouseLeave), (gpointer)this); g_signal_connect(G_OBJECT(gtkWidget), "motion-notify-event", G_CALLBACK(Canvas_mouseMove), (gpointer)this); @@ -106,7 +115,36 @@ auto pCanvas::update() -> void { _redraw(); } +auto pCanvas::_onDraw(cairo_t* context) -> void { + #if HIRO_GTK==3 + int sx = 0, sy = 0, dx = 0, dy = 0; + int width = surfaceWidth, height = surfaceHeight; + auto geometry = pSizable::state().geometry; + + if(width <= geometry.width()) { + sx = 0; + dx = (geometry.width() - width) / 2; + } else { + sx = (width - geometry.width()) / 2; + dx = 0; + } + + if(height <= geometry.height()) { + sy = 0; + dy = (geometry.height() - height) / 2; + } else { + sy = (height - geometry.height()) / 2; + dy = 0; + } + + //TODO: support non-zero sx,sy + gdk_cairo_set_source_pixbuf(context, surface, dx, dy); + cairo_paint(context); + #endif +} + auto pCanvas::_onExpose(GdkEventExpose* expose) -> void { + #if HIRO_GTK==2 if(surface == nullptr) return; int sx = 0, sy = 0, dx = 0, dy = 0; @@ -132,10 +170,7 @@ auto pCanvas::_onExpose(GdkEventExpose* expose) -> void { height = geometry.height(); } - #if HIRO_GTK==2 gdk_draw_pixbuf(gtk_widget_get_window(gtkWidget), nullptr, surface, sx, sy, dx, dy, width, height, GDK_RGB_DITHER_NONE, 0, 0); - #elif HIRO_GTK==3 - //TODO: use cairo here, but how? no examples show to use sx, sy #endif } diff --git a/hiro/gtk/widget/canvas.hpp b/hiro/gtk/widget/canvas.hpp index 3a9c61a7..368b261a 100644 --- a/hiro/gtk/widget/canvas.hpp +++ b/hiro/gtk/widget/canvas.hpp @@ -13,6 +13,7 @@ struct pCanvas : pWidget { auto setIcon(const image& icon) -> void; auto update() -> void; + auto _onDraw(cairo_t* context) -> void; auto _onExpose(GdkEventExpose* event) -> void; auto _rasterize() -> void; auto _redraw() -> void; diff --git a/hiro/gtk/widget/viewport.cpp b/hiro/gtk/widget/viewport.cpp index 53201811..8c9754e6 100644 --- a/hiro/gtk/widget/viewport.cpp +++ b/hiro/gtk/widget/viewport.cpp @@ -10,6 +10,10 @@ GtkSelectionData* data, unsigned type, unsigned timestamp, pViewport* p) -> void p->self().doDrop(paths); } +static auto Viewport_draw(GtkWidget* widget, cairo_t* context, pViewport* p) -> signed { + return true; +} + static auto Viewport_expose(GtkWidget* widget, GdkEventExpose* event) -> signed { return true; } @@ -59,7 +63,11 @@ auto pViewport::construct() -> void { g_signal_connect(G_OBJECT(gtkWidget), "button-press-event", G_CALLBACK(Viewport_mousePress), (gpointer)this); g_signal_connect(G_OBJECT(gtkWidget), "button-release-event", G_CALLBACK(Viewport_mouseRelease), (gpointer)this); g_signal_connect(G_OBJECT(gtkWidget), "drag-data-received", G_CALLBACK(Viewport_dropEvent), (gpointer)this); + #if HIRO_GTK==2 g_signal_connect(G_OBJECT(gtkWidget), "expose-event", G_CALLBACK(Viewport_expose), (gpointer)this); + #elif HIRO_GTK==3 + g_signal_connect(G_OBJECT(gtkWidget), "draw", G_CALLBACK(Viewport_draw), (gpointer)this); + #endif g_signal_connect(G_OBJECT(gtkWidget), "leave-notify-event", G_CALLBACK(Viewport_mouseLeave), (gpointer)this); g_signal_connect(G_OBJECT(gtkWidget), "motion-notify-event", G_CALLBACK(Viewport_mouseMove), (gpointer)this); diff --git a/hiro/gtk/window.cpp b/hiro/gtk/window.cpp index 07d39127..5475a842 100644 --- a/hiro/gtk/window.cpp +++ b/hiro/gtk/window.cpp @@ -30,7 +30,6 @@ static auto Window_draw(GtkWidget* widget, cairo_t* context, pWindow* p) -> sign cairo_set_operator(context, CAIRO_OPERATOR_SOURCE); cairo_paint(context); - cairo_destroy(context); } return false; } @@ -39,7 +38,8 @@ static auto Window_draw(GtkWidget* widget, cairo_t* context, pWindow* p) -> sign static auto Window_expose(GtkWidget* widget, GdkEvent* event, pWindow* p) -> signed { if(auto color = p->state().backgroundColor) { cairo_t* context = gdk_cairo_create(gtk_widget_get_window(widget)); - return Window_draw(widget, context, p); + Window_draw(widget, context, p); + cairo_destroy(context); } return false; } diff --git a/ruby/ruby.hpp b/ruby/ruby.hpp index f4c1af06..59af2962 100644 --- a/ruby/ruby.hpp +++ b/ruby/ruby.hpp @@ -33,9 +33,16 @@ struct Video { virtual auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool { return false; } virtual auto unlock() -> void {} virtual auto output() -> void {} + virtual auto poll() -> void {} + + auto onUpdate(const nall::function& callback) { _onUpdate = callback; } + auto doUpdate(uint width, uint height) -> void { + if(_onUpdate) return _onUpdate(width, height); + } private: nall::string _driver; + nall::function _onUpdate; }; struct Audio { @@ -100,7 +107,7 @@ struct Input { auto onChange(const nall::function, uint, uint, int16_t, int16_t)>& callback) { _onChange = callback; } auto doChange(nall::shared_pointer device, uint group, uint input, int16_t oldValue, int16_t newValue) -> void { - if(_onChange) _onChange(device, group, input, oldValue, newValue); + if(_onChange) return _onChange(device, group, input, oldValue, newValue); } private: diff --git a/ruby/video/glx.cpp b/ruby/video/glx.cpp index 624cb0a1..0e956d38 100644 --- a/ruby/video/glx.cpp +++ b/ruby/video/glx.cpp @@ -91,6 +91,18 @@ struct VideoGLX : Video, OpenGL { if(_doubleBuffer) glXSwapBuffers(_display, _glXWindow); } + auto poll() -> void { + while(XPending(_display)) { + XEvent event; + XNextEvent(_display, &event); + if(event.type == Expose) { + XWindowAttributes attributes; + XGetWindowAttributes(_display, _window, &attributes); + doUpdate(attributes.width, attributes.height); + } + } + } + private: auto initialize() -> bool { terminate(); @@ -136,6 +148,7 @@ private: /* x = */ 0, /* y = */ 0, windowAttributes.width, windowAttributes.height, /* border_width = */ 0, vi->depth, InputOutput, vi->visual, CWColormap | CWBorderPixel, &attributes); + XSelectInput(_display, _window, ExposureMask); XSetWindowBackground(_display, _window, /* color = */ 0); XMapWindow(_display, _window); XFlush(_display); diff --git a/ruby/video/glx2.cpp b/ruby/video/glx2.cpp index a997217b..5fcb6775 100644 --- a/ruby/video/glx2.cpp +++ b/ruby/video/glx2.cpp @@ -111,6 +111,18 @@ struct VideoGLX2 : Video { if(_isDoubleBuffered) glXSwapBuffers(_display, _glXWindow); } + auto poll() -> void { + while(XPending(_display)) { + XEvent event; + XNextEvent(_display, &event); + if(event.type == Expose) { + XWindowAttributes attributes; + XGetWindowAttributes(_display, _window, &attributes); + doUpdate(attributes.width, attributes.height); + } + } + } + private: auto initialize() -> bool { terminate(); @@ -147,6 +159,7 @@ private: attributes.border_pixel = 0; _window = XCreateWindow(_display, (Window)_context, 0, 0, windowAttributes.width, windowAttributes.height, 0, vi->depth, InputOutput, vi->visual, CWColormap | CWBorderPixel, &attributes); + XSelectInput(_display, _window, ExposureMask); XSetWindowBackground(_display, _window, 0); XMapWindow(_display, _window); XFlush(_display); diff --git a/ruby/video/xshm.cpp b/ruby/video/xshm.cpp index 66a05427..d5978d1d 100644 --- a/ruby/video/xshm.cpp +++ b/ruby/video/xshm.cpp @@ -88,12 +88,24 @@ struct VideoXShm : Video { XFlush(_display); } + auto poll() -> void override { + while(XPending(_display)) { + XEvent event; + XNextEvent(_display, &event); + if(event.type == Expose) { + XWindowAttributes attributes; + XGetWindowAttributes(_display, _window, &attributes); + doUpdate(attributes.width, attributes.height); + } + } + } + private: auto initialize() -> bool { terminate(); if(!_context) return false; - _display = XOpenDisplay(0); + _display = XOpenDisplay(nullptr); _screen = DefaultScreen(_display); XWindowAttributes getAttributes; @@ -109,12 +121,12 @@ private: XSetWindowAttributes setAttributes = {}; setAttributes.border_pixel = 0; - setAttributes.event_mask = ExposureMask; _window = XCreateWindow(_display, (Window)_context, 0, 0, 256, 256, 0, getAttributes.depth, InputOutput, getAttributes.visual, CWBorderPixel, &setAttributes ); + XSelectInput(_display, _window, ExposureMask); XSetWindowBackground(_display, _window, 0); XMapWindow(_display, _window); XFlush(_display); diff --git a/ruby/video/xvideo.cpp b/ruby/video/xvideo.cpp index 8b3d5fcc..70533bd2 100644 --- a/ruby/video/xvideo.cpp +++ b/ruby/video/xvideo.cpp @@ -91,6 +91,18 @@ struct VideoXVideo : Video { true); } + auto poll() -> void { + while(XPending(_display)) { + XEvent event; + XNextEvent(_display, &event); + if(event.type == Expose) { + XWindowAttributes attributes; + XGetWindowAttributes(_display, _window, &attributes); + doUpdate(attributes.width, attributes.height); + } + } + } + private: auto initialize() -> bool { terminate(); @@ -148,11 +160,11 @@ private: XSetWindowAttributes attributes; attributes.colormap = _colormap; attributes.border_pixel = 0; - attributes.event_mask = StructureNotifyMask; _window = XCreateWindow(_display, /* parent = */ (Window)_context, /* x = */ 0, /* y = */ 0, window_attributes.width, window_attributes.height, /* border_width = */ 0, _depth, InputOutput, visualInfo->visual, CWColormap | CWBorderPixel | CWEventMask, &attributes); + XSelectInput(_display, _window, ExposureMask); XFree(visualInfo); XSetWindowBackground(_display, _window, /* color = */ 0); XMapWindow(_display, _window);