diff --git a/higan/GNUmakefile b/higan/GNUmakefile index 63c5a6d3..e3c62903 100644 --- a/higan/GNUmakefile +++ b/higan/GNUmakefile @@ -1,4 +1,4 @@ -target := bsnes +target := higan binary := application build := performance openmp := true diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 7ab0b575..f9a2a936 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.51"; + static const string Version = "106.52"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "https://byuu.org/"; diff --git a/higan/emulator/interface.hpp b/higan/emulator/interface.hpp index bdde0888..d8e1dc62 100644 --- a/higan/emulator/interface.hpp +++ b/higan/emulator/interface.hpp @@ -3,42 +3,24 @@ namespace Emulator { struct Interface { + //information struct Information { string manufacturer; string name; - bool overscan = false; + string extension; bool resettable = false; - } information; - - struct Medium { - uint id; - string name; - string type; //extension }; - vector media; - - struct Device { - uint id; - string name; - struct Input { - uint type; //0 = digital, 1 = analog (relative), 2 = rumble - string name; - }; - vector inputs; - }; - - struct Port { - uint id; - string name; - vector devices; - }; - vector ports; - - //information + virtual auto information() -> Information = 0; virtual auto manifest() -> string = 0; virtual auto title() -> string = 0; - struct VideoInformation { + struct Display { + struct Type { enum : uint { + CRT, + LCD, + };}; + uint type = 0; + uint colors = 0; uint width = 0; uint height = 0; uint internalWidth = 0; @@ -46,23 +28,48 @@ struct Interface { double aspectCorrection = 0; double refreshRate = 0; }; - virtual auto videoInformation() -> VideoInformation = 0; - virtual auto videoColors() -> uint32 = 0; - virtual auto videoColor(uint32 color) -> uint64 = 0; + virtual auto display() -> Display = 0; + virtual auto color(uint32 color) -> uint64 = 0; - //media interface - virtual auto loaded() -> bool { return false; } + //game interface + virtual auto loaded() -> bool = 0; virtual auto sha256() -> string { return ""; } - virtual auto load(uint id) -> bool { return false; } - virtual auto save() -> void {} - virtual auto unload() -> void {} + virtual auto load() -> bool = 0; + virtual auto save() -> void = 0; + virtual auto unload() -> void = 0; //system interface + struct Port { + uint id; + string name; + }; + virtual auto ports() -> vector = 0; + + struct Device { + uint id; + string name; + }; + virtual auto devices(uint port) -> vector = 0; + + struct Input { + struct Type { enum : uint { + Hat, + Button, + Trigger, + Control, + Axis, + Rumble, + };}; + uint type; + string name; + }; + virtual auto inputs(uint device) -> vector = 0; + virtual auto connected(uint port) -> uint { return 0; } virtual auto connect(uint port, uint device) -> void {} - virtual auto power() -> void {} + virtual auto power() -> void = 0; virtual auto reset() -> void {} - virtual auto run() -> void {} + virtual auto run() -> void = 0; //time functions virtual auto rtc() -> bool { return false; } diff --git a/higan/fc/interface/interface.cpp b/higan/fc/interface/interface.cpp index 36d9ed0e..58a622ca 100644 --- a/higan/fc/interface/interface.cpp +++ b/higan/fc/interface/interface.cpp @@ -2,40 +2,16 @@ namespace Famicom { +#define returns(T) T { return ([&] { struct With : T { With() { +#define $ }}; return With(); })(); } + Settings settings; -Interface::Interface() { - information.manufacturer = "Nintendo"; - information.name = "Famicom"; - information.overscan = true; - information.resettable = true; - - media.append({ID::Famicom, "Famicom", "fc"}); - - Port controllerPort1{ID::Port::Controller1, "Controller Port 1"}; - Port controllerPort2{ID::Port::Controller2, "Controller Port 2"}; - - { Device device{ID::Device::None, "None"}; - controllerPort1.devices.append(device); - controllerPort2.devices.append(device); - } - - { Device device{ID::Device::Gamepad, "Gamepad"}; - device.inputs.append({0, "Up" }); - device.inputs.append({0, "Down" }); - device.inputs.append({0, "Left" }); - device.inputs.append({0, "Right" }); - device.inputs.append({0, "B" }); - device.inputs.append({0, "A" }); - device.inputs.append({0, "Select"}); - device.inputs.append({0, "Start" }); - controllerPort1.devices.append(device); - controllerPort2.devices.append(device); - } - - ports.append(move(controllerPort1)); - ports.append(move(controllerPort2)); -} +auto Interface::information() -> returns(Information) { + manufacturer = "Nintendo"; + name = "Famicom"; + resettable = true; +}$ auto Interface::manifest() -> string { return cartridge.manifest(); @@ -45,22 +21,18 @@ auto Interface::title() -> string { return cartridge.title(); } -auto Interface::videoInformation() -> VideoInformation { - VideoInformation vi; - vi.width = 256; - vi.height = 240; - vi.internalWidth = 256; - vi.internalHeight = 240; - vi.aspectCorrection = 8.0 / 7.0; - vi.refreshRate = system.frequency() / (ppu.vlines() * ppu.rate() * 341.0); - return vi; -} +auto Interface::display() -> returns(Display) { + type = Display::Type::CRT; + colors = 1 << 9; + width = 256; + height = 240; + internalWidth = 256; + internalHeight = 240; + aspectCorrection = 8.0 / 7.0; + refreshRate = system.frequency() / (ppu.vlines() * ppu.rate() * 341.0); +}$ -auto Interface::videoColors() -> uint32 { - return 1 << 9; -} - -auto Interface::videoColor(uint32 n) -> uint64 { +auto Interface::color(uint32 n) -> uint64 { double saturation = 2.0; double hue = 0.0; double contrast = 1.0; @@ -119,7 +91,7 @@ auto Interface::sha256() -> string { return cartridge.sha256(); } -auto Interface::load(uint id) -> bool { +auto Interface::load() -> bool { return system.load(this); } @@ -132,6 +104,50 @@ auto Interface::unload() -> void { system.unload(); } +auto Interface::ports() -> vector { return { + {ID::Port::Controller1, "Controller Port 1"}, + {ID::Port::Controller2, "Controller Port 2"}, + {ID::Port::Expansion, "Expansion Port" }}; +} + +auto Interface::devices(uint port) -> vector { + if(port == ID::Port::Controller1) return { + {ID::Device::None, "None" }, + {ID::Device::Gamepad, "Gamepad"} + }; + + if(port == ID::Port::Controller2) return { + {ID::Device::None, "None" }, + {ID::Device::Gamepad, "Gamepad"} + }; + + if(port == ID::Port::Expansion) return { + {ID::Device::None, "None"} + }; + + return {}; +} + +auto Interface::inputs(uint device) -> vector { + using Type = Input::Type; + + if(device == ID::Device::None) return { + }; + + if(device == ID::Device::Gamepad) return { + {Type::Hat, "Up" }, + {Type::Hat, "Down" }, + {Type::Hat, "Left" }, + {Type::Hat, "Right" }, + {Type::Button, "B" }, + {Type::Button, "A" }, + {Type::Control, "Select"}, + {Type::Control, "Start" } + }; + + return {}; +} + auto Interface::connected(uint port) -> uint { if(port == ID::Port::Controller1) return settings.controllerPort1; if(port == ID::Port::Controller2) return settings.controllerPort2; @@ -191,4 +207,7 @@ auto Interface::set(const string& name, const any& value) -> bool { return false; } +#undef returns +#undef $ + } diff --git a/higan/fc/interface/interface.hpp b/higan/fc/interface/interface.hpp index a184a8c1..26462ea1 100644 --- a/higan/fc/interface/interface.hpp +++ b/higan/fc/interface/interface.hpp @@ -21,21 +21,24 @@ struct ID { struct Interface : Emulator::Interface { using Emulator::Interface::load; - Interface(); + auto information() -> Information override; auto manifest() -> string override; auto title() -> string override; - auto videoInformation() -> VideoInformation override; - auto videoColors() -> uint32 override; - auto videoColor(uint32 color) -> uint64 override; + auto display() -> Display override; + auto color(uint32 color) -> uint64 override; auto loaded() -> bool override; auto sha256() -> string override; - auto load(uint id) -> bool override; + auto load() -> bool override; auto save() -> void override; auto unload() -> void override; + auto ports() -> vector override; + auto devices(uint port) -> vector override; + auto inputs(uint device) -> vector override; + auto connected(uint port) -> uint override; auto connect(uint port, uint device) -> void override; auto power() -> void override; diff --git a/higan/gb/interface/game-boy-color.cpp b/higan/gb/interface/game-boy-color.cpp index 80c2213d..615263f4 100644 --- a/higan/gb/interface/game-boy-color.cpp +++ b/higan/gb/interface/game-boy-color.cpp @@ -1,16 +1,10 @@ -GameBoyColorInterface::GameBoyColorInterface() { - information.manufacturer = "Nintendo"; - information.name = "Game Boy Color"; - information.overscan = false; +auto GameBoyColorInterface::information() -> returns(Information) { + manufacturer = "Nintendo"; + name = "Game Boy Color"; + extension = "gbc"; +}$ - media.append({ID::GameBoyColor, "Game Boy Color", "gbc"}); -} - -auto GameBoyColorInterface::videoColors() -> uint32 { - return 1 << 15; -} - -auto GameBoyColorInterface::videoColor(uint32 color) -> uint64 { +auto GameBoyColorInterface::color(uint32 color) -> uint64 { uint r = color.bits( 0, 4); uint g = color.bits( 5, 9); uint b = color.bits(10,14); @@ -31,7 +25,6 @@ auto GameBoyColorInterface::videoColor(uint32 color) -> uint64 { return R << 32 | G << 16 | B << 0; } -auto GameBoyColorInterface::load(uint id) -> bool { - if(id == ID::GameBoyColor) return system.load(this, System::Model::GameBoyColor); - return false; +auto GameBoyColorInterface::load() -> bool { + return system.load(this, System::Model::GameBoyColor); } diff --git a/higan/gb/interface/game-boy.cpp b/higan/gb/interface/game-boy.cpp index 2dd0b9d7..1d9a2ea3 100644 --- a/higan/gb/interface/game-boy.cpp +++ b/higan/gb/interface/game-boy.cpp @@ -1,16 +1,10 @@ -GameBoyInterface::GameBoyInterface() { - information.manufacturer = "Nintendo"; - information.name = "Game Boy"; - information.overscan = false; +auto GameBoyInterface::information() -> returns(Information) { + manufacturer = "Nintendo"; + name = "Game Boy"; + extension = "gb"; +}$ - media.append({ID::GameBoy, "Game Boy", "gb"}); -} - -auto GameBoyInterface::videoColors() -> uint32 { - return 1 << 2; -} - -auto GameBoyInterface::videoColor(uint32 color) -> uint64 { +auto GameBoyInterface::color(uint32 color) -> uint64 { if(!settings.colorEmulation) { uint64 L = image::normalize(3 - color, 2, 16); return L << 32 | L << 16 | L << 0; @@ -46,7 +40,6 @@ auto GameBoyInterface::videoColor(uint32 color) -> uint64 { } } -auto GameBoyInterface::load(uint id) -> bool { - if(id == ID::GameBoy) return system.load(this, System::Model::GameBoy); - return false; +auto GameBoyInterface::load() -> bool { + return system.load(this, System::Model::GameBoy); } diff --git a/higan/gb/interface/interface.cpp b/higan/gb/interface/interface.cpp index 1fb71003..8726ccd7 100644 --- a/higan/gb/interface/interface.cpp +++ b/higan/gb/interface/interface.cpp @@ -2,32 +2,14 @@ namespace GameBoy { +#define returns(T) T { return ([&] { struct With : T { With() { +#define $ }}; return With(); })(); } + SuperGameBoyInterface* superGameBoy = nullptr; Settings settings; #include "game-boy.cpp" #include "game-boy-color.cpp" -Interface::Interface() { - Port hardwarePort{ID::Port::Hardware, "Hardware"}; - - { Device device{ID::Device::Controls, "Controls"}; - device.inputs.append({0, "Up" }); - device.inputs.append({0, "Down" }); - device.inputs.append({0, "Left" }); - device.inputs.append({0, "Right" }); - device.inputs.append({0, "B" }); - device.inputs.append({0, "A" }); - device.inputs.append({0, "Select"}); - device.inputs.append({0, "Start" }); - device.inputs.append({1, "X-axis"}); - device.inputs.append({1, "Y-axis"}); - device.inputs.append({2, "Rumble"}); - hardwarePort.devices.append(device); - } - - ports.append(move(hardwarePort)); -} - auto Interface::manifest() -> string { return cartridge.manifest(); } @@ -36,16 +18,16 @@ auto Interface::title() -> string { return cartridge.title(); } -auto Interface::videoInformation() -> VideoInformation { - VideoInformation vi; - vi.width = 160; - vi.height = 144; - vi.internalWidth = 160; - vi.internalHeight = 144; - vi.aspectCorrection = 1.0; - vi.refreshRate = (4.0 * 1024.0 * 1024.0) / (154.0 * 456.0); - return vi; -} +auto Interface::display() -> returns(Display) { + type = Display::Type::LCD; + colors = Model::GameBoyColor() ? 1 << 15 : 1 << 2; + width = 160; + height = 144; + internalWidth = 160; + internalHeight = 144; + aspectCorrection = 1.0; + refreshRate = (4.0 * 1024.0 * 1024.0) / (154.0 * 456.0); +}$ auto Interface::loaded() -> bool { return system.loaded(); @@ -64,6 +46,38 @@ auto Interface::unload() -> void { system.unload(); } +auto Interface::ports() -> vector { return { + {ID::Port::Hardware, "Hardware"}}; +} + +auto Interface::devices(uint port) -> vector { + if(port == ID::Port::Hardware) return { + {ID::Device::Controls, "Controls"} + }; + + return {}; +} + +auto Interface::inputs(uint device) -> vector { + using Type = Input::Type; + + if(device == ID::Device::Controls) return { + {Type::Hat, "Up" }, + {Type::Hat, "Down" }, + {Type::Hat, "Left" }, + {Type::Hat, "Right" }, + {Type::Button, "B" }, + {Type::Button, "A" }, + {Type::Control, "Select"}, + {Type::Control, "Start" }, + {Type::Axis, "X-axis"}, + {Type::Axis, "Y-axis"}, + {Type::Rumble, "Rumble"} + }; + + return {}; +} + auto Interface::power() -> void { system.power(); } @@ -115,4 +129,7 @@ auto Interface::set(const string& name, const any& value) -> bool { return false; } +#undef returns +#undef $ + } diff --git a/higan/gb/interface/interface.hpp b/higan/gb/interface/interface.hpp index d07104b4..d464c84b 100644 --- a/higan/gb/interface/interface.hpp +++ b/higan/gb/interface/interface.hpp @@ -18,12 +18,10 @@ struct ID { }; struct Interface : Emulator::Interface { - Interface(); - auto manifest() -> string override; auto title() -> string override; - auto videoInformation() -> VideoInformation override; + auto display() -> Display override; auto loaded() -> bool override; auto sha256() -> string override; @@ -31,6 +29,10 @@ struct Interface : Emulator::Interface { auto save() -> void override; auto unload() -> void override; + auto ports() -> vector override; + auto devices(uint port) -> vector override; + auto inputs(uint device) -> vector override; + auto power() -> void override; auto run() -> void override; @@ -47,23 +49,21 @@ struct Interface : Emulator::Interface { struct GameBoyInterface : Interface { using Emulator::Interface::load; - GameBoyInterface(); + auto information() -> Information override; - auto videoColors() -> uint32 override; - auto videoColor(uint32 color) -> uint64 override; + auto color(uint32 color) -> uint64 override; - auto load(uint id) -> bool override; + auto load() -> bool override; }; struct GameBoyColorInterface : Interface { using Emulator::Interface::load; - GameBoyColorInterface(); + auto information() -> Information override; - auto videoColors() -> uint32 override; - auto videoColor(uint32 color) -> uint64 override; + auto color(uint32 color) -> uint64 override; - auto load(uint id) -> bool override; + auto load() -> bool override; }; struct SuperGameBoyInterface { diff --git a/higan/gba/interface/interface.cpp b/higan/gba/interface/interface.cpp index 696cade0..bd629da8 100644 --- a/higan/gba/interface/interface.cpp +++ b/higan/gba/interface/interface.cpp @@ -2,34 +2,15 @@ namespace GameBoyAdvance { +#define returns(T) T { return ([&] { struct With : T { With() { +#define $ }}; return With(); })(); } + Settings settings; -Interface::Interface() { - information.manufacturer = "Nintendo"; - information.name = "Game Boy Advance"; - information.overscan = false; - - media.append({ID::GameBoyAdvance, "Game Boy Advance", "gba"}); - - Port hardwarePort{ID::Port::Hardware, "Hardware"}; - - { Device device{ID::Device::Controls, "Controls"}; - device.inputs.append({0, "Up" }); - device.inputs.append({0, "Down" }); - device.inputs.append({0, "Left" }); - device.inputs.append({0, "Right" }); - device.inputs.append({0, "B" }); - device.inputs.append({0, "A" }); - device.inputs.append({0, "L" }); - device.inputs.append({0, "R" }); - device.inputs.append({0, "Select"}); - device.inputs.append({0, "Start" }); - device.inputs.append({2, "Rumble"}); - hardwarePort.devices.append(device); - } - - ports.append(move(hardwarePort)); -} +auto Interface::information() -> returns(Information) { + manufacturer = "Nintendo"; + name = "Game Boy Advance"; +}$ auto Interface::manifest() -> string { return cartridge.manifest(); @@ -39,26 +20,22 @@ auto Interface::title() -> string { return cartridge.title(); } -auto Interface::videoInformation() -> VideoInformation { - VideoInformation vi; - vi.width = 240; - vi.height = 160; - vi.internalWidth = 240; - vi.internalHeight = 160; - vi.aspectCorrection = 1.0; - vi.refreshRate = system.frequency() / (228.0 * 1232.0); +auto Interface::display() -> returns(Display) { + type = Display::Type::LCD; + colors = 1 << 15; + width = 240; + height = 160; + internalWidth = 240; + internalHeight = 160; + aspectCorrection = 1.0; + refreshRate = system.frequency() / (228.0 * 1232.0); if(settings.rotateLeft) { - swap(vi.width, vi.height); - swap(vi.internalWidth, vi.internalHeight); + swap(width, height); + swap(internalWidth, internalHeight); } - return vi; -} +}$ -auto Interface::videoColors() -> uint32 { - return 1 << 15; -} - -auto Interface::videoColor(uint32 color) -> uint64 { +auto Interface::color(uint32 color) -> uint64 { uint R = color.bits( 0, 4); uint G = color.bits( 5, 9); uint B = color.bits(10,14); @@ -84,7 +61,7 @@ auto Interface::loaded() -> bool { return system.loaded(); } -auto Interface::load(uint id) -> bool { +auto Interface::load() -> bool { return system.load(this); } @@ -97,6 +74,38 @@ auto Interface::unload() -> void { system.unload(); } +auto Interface::ports() -> vector { return { + {ID::Port::Hardware, "Hardware"}}; +} + +auto Interface::devices(uint port) -> vector { + if(port == ID::Port::Hardware) return { + {ID::Device::Controls, "Controls"} + }; + + return {}; +} + +auto Interface::inputs(uint device) -> vector { + using Type = Input::Type; + + if(device == ID::Device::Controls) return { + {Type::Hat, "Up" }, + {Type::Hat, "Down" }, + {Type::Hat, "Left" }, + {Type::Hat, "Right" }, + {Type::Button, "B" }, + {Type::Button, "A" }, + {Type::Trigger, "L" }, + {Type::Trigger, "R" }, + {Type::Control, "Select"}, + {Type::Control, "Start" }, + {Type::Rumble, "Rumble"} + }; + + return {}; +} + auto Interface::power() -> void { system.power(); } @@ -150,4 +159,7 @@ auto Interface::set(const string& name, const any& value) -> bool { return false; } +#undef returns +#undef $ + } diff --git a/higan/gba/interface/interface.hpp b/higan/gba/interface/interface.hpp index bfba2859..1281f71b 100644 --- a/higan/gba/interface/interface.hpp +++ b/higan/gba/interface/interface.hpp @@ -18,20 +18,23 @@ struct ID { struct Interface : Emulator::Interface { using Emulator::Interface::load; - Interface(); + auto information() -> Information override; auto manifest() -> string override; auto title() -> string override; - auto videoInformation() -> VideoInformation override; - auto videoColors() -> uint32 override; - auto videoColor(uint32 color) -> uint64 override; + auto display() -> Display override; + auto color(uint32 color) -> uint64 override; auto loaded() -> bool override; - auto load(uint id) -> bool override; + auto load() -> bool override; auto save() -> void override; auto unload() -> void override; + auto ports() -> vector override; + auto devices(uint port) -> vector override; + auto inputs(uint device) -> vector override; + auto power() -> void override; auto run() -> void override; diff --git a/higan/md/interface/interface.cpp b/higan/md/interface/interface.cpp index cba5cb22..790cfc3d 100644 --- a/higan/md/interface/interface.cpp +++ b/higan/md/interface/interface.cpp @@ -2,60 +2,16 @@ namespace MegaDrive { +#define returns(T) T { return ([&] { struct With : T { With() { +#define $ }}; return With(); })(); } + Settings settings; -Interface::Interface() { - information.manufacturer = "Sega"; - information.name = "Mega Drive"; - information.overscan = true; - information.resettable = true; - - media.append({ID::MegaDrive, "Mega Drive", "md"}); - - Port controllerPort1{ID::Port::Controller1, "Controller Port 1"}; - Port controllerPort2{ID::Port::Controller2, "Controller Port 2"}; - Port extensionPort{ID::Port::Extension, "Extension Port"}; - - { Device device{ID::Device::None, "None"}; - controllerPort1.devices.append(device); - controllerPort2.devices.append(device); - extensionPort.devices.append(device); - } - - { Device device{ID::Device::ControlPad, "Control Pad"}; - device.inputs.append({0, "Up" }); - device.inputs.append({0, "Down" }); - device.inputs.append({0, "Left" }); - device.inputs.append({0, "Right"}); - device.inputs.append({0, "A" }); - device.inputs.append({0, "B" }); - device.inputs.append({0, "C" }); - device.inputs.append({0, "Start"}); - controllerPort1.devices.append(device); - controllerPort2.devices.append(device); - } - - { Device device{ID::Device::FightingPad, "Fighting Pad"}; - device.inputs.append({0, "Up" }); - device.inputs.append({0, "Down" }); - device.inputs.append({0, "Left" }); - device.inputs.append({0, "Right"}); - device.inputs.append({0, "A" }); - device.inputs.append({0, "B" }); - device.inputs.append({0, "C" }); - device.inputs.append({0, "X" }); - device.inputs.append({0, "Y" }); - device.inputs.append({0, "Z" }); - device.inputs.append({0, "Mode" }); - device.inputs.append({0, "Start"}); - controllerPort1.devices.append(device); - controllerPort2.devices.append(device); - } - - ports.append(move(controllerPort1)); - ports.append(move(controllerPort2)); - ports.append(move(extensionPort)); -} +auto Interface::information() -> returns(Information) { + manufacturer = "Sega"; + name = "Mega Drive"; + resettable = true; +}$ auto Interface::manifest() -> string { return cartridge.manifest(); @@ -65,22 +21,18 @@ auto Interface::title() -> string { return cartridge.title(); } -auto Interface::videoInformation() -> VideoInformation { - VideoInformation vi; - vi.width = 320; - vi.height = 240; - vi.internalWidth = 1280; - vi.internalHeight = 480; - vi.aspectCorrection = 1.0; - vi.refreshRate = (system.frequency() / 2.0) / (vdp.frameHeight() * 1710.0); - return vi; -} +auto Interface::display() -> returns(Display) { + type = Display::Type::CRT; + colors = 3 * (1 << 9); + width = 320; + height = 240; + internalWidth = 1280; + internalHeight = 480; + aspectCorrection = 1.0; + refreshRate = (system.frequency() / 2.0) / (vdp.frameHeight() * 1710.0); +}$ -auto Interface::videoColors() -> uint32 { - return 3 * (1 << 9); -} - -auto Interface::videoColor(uint32 color) -> uint64 { +auto Interface::color(uint32 color) -> uint64 { uint R = color.bits(0, 2); uint G = color.bits(3, 5); uint B = color.bits(6, 8); @@ -103,7 +55,7 @@ auto Interface::loaded() -> bool { return system.loaded(); } -auto Interface::load(uint id) -> bool { +auto Interface::load() -> bool { return system.load(this); } @@ -116,6 +68,67 @@ auto Interface::unload() -> void { system.unload(); } +auto Interface::ports() -> vector { return { + {ID::Port::Controller1, "Controller Port 1"}, + {ID::Port::Controller2, "Controller Port 2"}, + {ID::Port::Extension, "Extension Port" }}; +} + +auto Interface::devices(uint port) -> vector { + if(port == ID::Port::Controller1) return { + {ID::Device::None, "None" }, + {ID::Device::ControlPad, "Control Pad" }, + {ID::Device::FightingPad, "Fighting Pad"} + }; + + if(port == ID::Port::Controller2) return { + {ID::Device::None, "None" }, + {ID::Device::ControlPad, "Control Pad" }, + {ID::Device::FightingPad, "Fighting Pad"} + }; + + if(port == ID::Port::Extension) return { + {ID::Device::None, "None"} + }; + + return {}; +} + +auto Interface::inputs(uint device) -> vector { + using Type = Input::Type; + + if(device == ID::Device::None) return { + }; + + if(device == ID::Device::ControlPad) return { + {Type::Hat, "Up" }, + {Type::Hat, "Down" }, + {Type::Hat, "Left" }, + {Type::Hat, "Right"}, + {Type::Button, "A" }, + {Type::Button, "B" }, + {Type::Button, "C" }, + {Type::Control, "Start"} + }; + + if(device == ID::Device::FightingPad) return { + {Type::Hat, "Up" }, + {Type::Hat, "Down" }, + {Type::Hat, "Left" }, + {Type::Hat, "Right"}, + {Type::Button, "A" }, + {Type::Button, "B" }, + {Type::Button, "C" }, + {Type::Button, "X" }, + {Type::Button, "Y" }, + {Type::Button, "Z" }, + {Type::Control, "Mode" }, + {Type::Control, "Start"} + }; + + return {}; +} + auto Interface::connected(uint port) -> uint { if(port == ID::Port::Controller1) return settings.controllerPort1; if(port == ID::Port::Controller2) return settings.controllerPort2; @@ -166,4 +179,7 @@ auto Interface::set(const string& name, const any& value) -> bool { return false; } +#undef returns +#undef $ + } diff --git a/higan/md/interface/interface.hpp b/higan/md/interface/interface.hpp index 46d9679b..89ad0696 100644 --- a/higan/md/interface/interface.hpp +++ b/higan/md/interface/interface.hpp @@ -22,20 +22,23 @@ struct ID { struct Interface : Emulator::Interface { using Emulator::Interface::load; - Interface(); + auto information() -> Information override; auto manifest() -> string override; auto title() -> string override; - auto videoInformation() -> VideoInformation override; - auto videoColors() -> uint32 override; - auto videoColor(uint32 color) -> uint64 override; + auto display() -> Display override; + auto color(uint32 color) -> uint64 override; auto loaded() -> bool override; - auto load(uint id) -> bool override; + auto load() -> bool override; auto save() -> void override; auto unload() -> void override; + auto ports() -> vector override; + auto devices(uint port) -> vector override; + auto inputs(uint device) -> vector override; + auto connected(uint port) -> uint override; auto connect(uint port, uint device) -> void override; auto power() -> void override; diff --git a/higan/sfc/interface/interface.cpp b/higan/sfc/interface/interface.cpp index 49138dcd..113d24db 100644 --- a/higan/sfc/interface/interface.cpp +++ b/higan/sfc/interface/interface.cpp @@ -2,112 +2,17 @@ namespace SuperFamicom { +#define returns(T) T { return ([&] { struct With : T { With() { +#define $ }}; return With(); })(); } + Settings settings; -Interface::Interface() { - information.manufacturer = "Nintendo"; - information.name = "Super Famicom"; - information.overscan = true; - information.resettable = true; - - media.append({ID::SuperFamicom, "Super Famicom", "sfc"}); - - Port controllerPort1{ID::Port::Controller1, "Controller Port 1"}; - Port controllerPort2{ID::Port::Controller2, "Controller Port 2"}; - Port expansionPort{ID::Port::Expansion, "Expansion Port"}; - - { Device device{ID::Device::None, "None"}; - controllerPort1.devices.append(device); - controllerPort2.devices.append(device); - expansionPort.devices.append(device); - } - - { Device device{ID::Device::Gamepad, "Gamepad"}; - device.inputs.append({0, "Up" }); - device.inputs.append({0, "Down" }); - device.inputs.append({0, "Left" }); - device.inputs.append({0, "Right" }); - device.inputs.append({0, "B" }); - device.inputs.append({0, "A" }); - device.inputs.append({0, "Y" }); - device.inputs.append({0, "X" }); - device.inputs.append({0, "L" }); - device.inputs.append({0, "R" }); - device.inputs.append({0, "Select"}); - device.inputs.append({0, "Start" }); - controllerPort1.devices.append(device); - controllerPort2.devices.append(device); - } - - { Device device{ID::Device::Mouse, "Mouse"}; - device.inputs.append({1, "X-axis"}); - device.inputs.append({1, "Y-axis"}); - device.inputs.append({0, "Left" }); - device.inputs.append({0, "Right" }); - controllerPort1.devices.append(device); - controllerPort2.devices.append(device); - } - - { Device device{ID::Device::SuperMultitap, "Super Multitap"}; - for(uint p = 2; p <= 5; p++) { - device.inputs.append({0, {"Port ", p, " - ", "Up" }}); - device.inputs.append({0, {"Port ", p, " - ", "Down" }}); - device.inputs.append({0, {"Port ", p, " - ", "Left" }}); - device.inputs.append({0, {"Port ", p, " - ", "Right" }}); - device.inputs.append({0, {"Port ", p, " - ", "B" }}); - device.inputs.append({0, {"Port ", p, " - ", "A" }}); - device.inputs.append({0, {"Port ", p, " - ", "Y" }}); - device.inputs.append({0, {"Port ", p, " - ", "X" }}); - device.inputs.append({0, {"Port ", p, " - ", "L" }}); - device.inputs.append({0, {"Port ", p, " - ", "R" }}); - device.inputs.append({0, {"Port ", p, " - ", "Select"}}); - device.inputs.append({0, {"Port ", p, " - ", "Start" }}); - } - controllerPort2.devices.append(device); - } - - { Device device{ID::Device::SuperScope, "Super Scope"}; - device.inputs.append({1, "X-axis" }); - device.inputs.append({1, "Y-axis" }); - device.inputs.append({0, "Trigger"}); - device.inputs.append({0, "Cursor" }); - device.inputs.append({0, "Turbo" }); - device.inputs.append({0, "Pause" }); - controllerPort2.devices.append(device); - } - - { Device device{ID::Device::Justifier, "Justifier"}; - device.inputs.append({1, "X-axis" }); - device.inputs.append({1, "Y-axis" }); - device.inputs.append({0, "Trigger"}); - device.inputs.append({0, "Start" }); - controllerPort2.devices.append(device); - } - - { Device device{ID::Device::Justifiers, "Justifiers"}; - device.inputs.append({1, "Port 1 - X-axis" }); - device.inputs.append({1, "Port 1 - Y-axis" }); - device.inputs.append({0, "Port 1 - Trigger"}); - device.inputs.append({0, "Port 1 - Start" }); - device.inputs.append({1, "Port 2 - X-axis" }); - device.inputs.append({1, "Port 2 - Y-axis" }); - device.inputs.append({0, "Port 2 - Trigger"}); - device.inputs.append({0, "Port 2 - Start" }); - controllerPort2.devices.append(device); - } - - { Device device{ID::Device::Satellaview, "Satellaview"}; - expansionPort.devices.append(device); - } - - { Device device{ID::Device::S21FX, "21fx"}; - expansionPort.devices.append(device); - } - - ports.append(move(controllerPort1)); - ports.append(move(controllerPort2)); - ports.append(move(expansionPort)); -} +auto Interface::information() -> returns(Information) { + manufacturer = "Nintendo"; + name = "Super Famicom"; + extension = "sfc"; + resettable = true; +}$ auto Interface::manifest() -> string { return cartridge.manifest(); @@ -117,23 +22,19 @@ auto Interface::title() -> string { return cartridge.title(); } -auto Interface::videoInformation() -> VideoInformation { - VideoInformation vi; - vi.width = 256; - vi.height = 240; - vi.internalWidth = 512; - vi.internalHeight = 480; - vi.aspectCorrection = 8.0 / 7.0; - if(Region::NTSC()) vi.refreshRate = system.cpuFrequency() / (262.0 * 1364.0); - if(Region::PAL()) vi.refreshRate = system.cpuFrequency() / (312.0 * 1364.0); - return vi; -} +auto Interface::display() -> returns(Display) { + type = Display::Type::CRT; + colors = 1 << 19; + width = 256; + height = 240; + internalWidth = 512; + internalHeight = 480; + aspectCorrection = 8.0 / 7.0; + if(Region::NTSC()) refreshRate = system.cpuFrequency() / (262.0 * 1364.0); + if(Region::PAL()) refreshRate = system.cpuFrequency() / (312.0 * 1364.0); +}$ -auto Interface::videoColors() -> uint32 { - return 1 << 19; -} - -auto Interface::videoColor(uint32 color) -> uint64 { +auto Interface::color(uint32 color) -> uint64 { uint r = color.bits( 0, 4); uint g = color.bits( 5, 9); uint b = color.bits(10,14); @@ -169,12 +70,8 @@ auto Interface::sha256() -> string { return cartridge.sha256(); } -auto Interface::load(uint id) -> bool { - if(id == ID::SuperFamicom) return system.load(this); - if(id == ID::BSMemory) return cartridge.loadBSMemory(); - if(id == ID::SufamiTurboA) return cartridge.loadSufamiTurboA(); - if(id == ID::SufamiTurboB) return cartridge.loadSufamiTurboB(); - return false; +auto Interface::load() -> bool { + return system.load(this); } auto Interface::save() -> void { @@ -186,6 +83,121 @@ auto Interface::unload() -> void { system.unload(); } +auto Interface::ports() -> vector { return { + {ID::Port::Controller1, "Controller Port 1"}, + {ID::Port::Controller2, "Controller Port 2"}, + {ID::Port::Expansion, "Expansion Port" }}; +} + +auto Interface::devices(uint port) -> vector { + if(port == ID::Port::Controller1) return { + {ID::Device::None, "None" }, + {ID::Device::Gamepad, "Gamepad"}, + {ID::Device::Mouse, "Mouse" } + }; + + if(port == ID::Port::Controller2) return { + {ID::Device::None, "None" }, + {ID::Device::Gamepad, "Gamepad" }, + {ID::Device::Mouse, "Mouse" }, + {ID::Device::SuperMultitap, "Super Multitap"}, + {ID::Device::SuperScope, "Super Scope" }, + {ID::Device::Justifier, "Justifier" }, + {ID::Device::Justifiers, "Justifiers" } + }; + + if(port == ID::Port::Expansion) return { + {ID::Device::None, "None" }, + {ID::Device::Satellaview, "Satellaview"}, + {ID::Device::S21FX, "21fx" } + }; + + return {}; +} + +auto Interface::inputs(uint device) -> vector { + using Type = Input::Type; + + if(device == ID::Device::None) return { + }; + + if(device == ID::Device::Gamepad) return { + {Type::Hat, "Up" }, + {Type::Hat, "Down" }, + {Type::Hat, "Left" }, + {Type::Hat, "Right" }, + {Type::Button, "B" }, + {Type::Button, "A" }, + {Type::Button, "Y" }, + {Type::Button, "X" }, + {Type::Trigger, "L" }, + {Type::Trigger, "R" }, + {Type::Control, "Select"}, + {Type::Control, "Start" } + }; + + if(device == ID::Device::Mouse) return { + {Type::Axis, "X-axis"}, + {Type::Axis, "Y-axis"}, + {Type::Button, "Left" }, + {Type::Button, "Right" } + }; + + if(device == ID::Device::SuperMultitap) { + vector inputs; + for(uint p = 2; p <= 5; p++) inputs.append({ + {Type::Hat, {"Port ", p, " - ", "Up" }}, + {Type::Hat, {"Port ", p, " - ", "Down" }}, + {Type::Hat, {"Port ", p, " - ", "Left" }}, + {Type::Hat, {"Port ", p, " - ", "Right" }}, + {Type::Button, {"Port ", p, " - ", "B" }}, + {Type::Button, {"Port ", p, " - ", "A" }}, + {Type::Button, {"Port ", p, " - ", "Y" }}, + {Type::Button, {"Port ", p, " - ", "X" }}, + {Type::Trigger, {"Port ", p, " - ", "L" }}, + {Type::Trigger, {"Port ", p, " - ", "R" }}, + {Type::Control, {"Port ", p, " - ", "Select"}}, + {Type::Control, {"Port ", p, " - ", "Start" }} + }); + return inputs; + } + + if(device == ID::Device::SuperScope) return { + {Type::Axis, "X-axis" }, + {Type::Axis, "Y-axis" }, + {Type::Control, "Trigger"}, + {Type::Control, "Cursor" }, + {Type::Control, "Turbo" }, + {Type::Control, "Pause" } + }; + + if(device == ID::Device::Justifier) return { + {Type::Axis, "X-axis" }, + {Type::Axis, "Y-axis" }, + {Type::Control, "Trigger"}, + {Type::Control, "Start" } + }; + + if(device == ID::Device::Justifiers) return { + {Type::Axis, "Port 1 - X-axis" }, + {Type::Axis, "Port 1 - Y-axis" }, + {Type::Control, "Port 1 - Trigger"}, + {Type::Control, "Port 1 - Start" }, + {Type::Axis, "Port 2 - X-axis" }, + {Type::Axis, "Port 2 - Y-axis" }, + {Type::Control, "Port 2 - Trigger"}, + {Type::Control, "Port 2 - Start" } + }; + + if(device == ID::Device::Satellaview) return { + }; + + if(device == ID::Device::S21FX) return { + }; + + return {}; +} + auto Interface::connected(uint port) -> uint { if(port == ID::Port::Controller1) return settings.controllerPort1; if(port == ID::Port::Controller2) return settings.controllerPort2; @@ -295,4 +307,7 @@ auto Interface::set(const string& name, const any& value) -> bool { return false; } +#undef returns +#undef $ + } diff --git a/higan/sfc/interface/interface.hpp b/higan/sfc/interface/interface.hpp index 6b59e4e7..9be79c42 100644 --- a/higan/sfc/interface/interface.hpp +++ b/higan/sfc/interface/interface.hpp @@ -33,21 +33,23 @@ struct ID { struct Interface : Emulator::Interface { using Emulator::Interface::load; - Interface(); - + auto information() -> Information; auto manifest() -> string override; auto title() -> string override; - auto videoInformation() -> VideoInformation override; - auto videoColors() -> uint32 override; - auto videoColor(uint32 color) -> uint64 override; + auto display() -> Display override; + auto color(uint32 color) -> uint64 override; auto loaded() -> bool override; auto sha256() -> string override; - auto load(uint id) -> bool override; + auto load() -> bool override; auto save() -> void override; auto unload() -> void override; + auto ports() -> vector override; + auto devices(uint port) -> vector override; + auto inputs(uint device) -> vector override; + auto connected(uint port) -> uint override; auto connect(uint port, uint device) -> void override; auto power() -> void override; diff --git a/higan/target-bsnes/bsnes.cpp b/higan/target-bsnes/bsnes.cpp index 64f371d2..b00a06ba 100644 --- a/higan/target-bsnes/bsnes.cpp +++ b/higan/target-bsnes/bsnes.cpp @@ -27,6 +27,7 @@ auto nall::main(string_vector arguments) -> void { } } Application::setName("bsnes"); + Application::setScreenSaver(false); Application::locale().scan(locate("locales/")); Application::locale().select(locale); emulator = new SuperFamicom::Interface; diff --git a/higan/target-bsnes/input/input.cpp b/higan/target-bsnes/input/input.cpp index a86f9acc..1c9d1ff6 100644 --- a/higan/target-bsnes/input/input.cpp +++ b/higan/target-bsnes/input/input.cpp @@ -187,11 +187,11 @@ auto InputManager::initialize() -> void { input->onChange({&InputManager::onChange, this}); frequency = max(1u, settings["Input/Frequency"].natural()); - for(auto& port : emulator->ports) { + for(auto& port : emulator->ports()) { InputPort inputPort{port.id, port.name}; - for(auto& device : port.devices) { + for(auto& device : emulator->devices(port.id)) { InputDevice inputDevice{device.id, device.name}; - for(auto& input : device.inputs) { + for(auto& input : emulator->inputs(device.id)) { InputMapping inputMapping; inputMapping.name = input.name; inputMapping.type = input.type; diff --git a/higan/target-bsnes/input/input.hpp b/higan/target-bsnes/input/input.hpp index 59d93a7c..94deeb59 100644 --- a/higan/target-bsnes/input/input.hpp +++ b/higan/target-bsnes/input/input.hpp @@ -7,9 +7,16 @@ struct InputMapping { auto rumble(bool enable) -> void; auto displayName() -> string; - auto isDigital() const -> bool { return type == 0; } - auto isAnalog() const -> bool { return type == 1; } - auto isRumble() const -> bool { return type == 2; } + using Type = Emulator::Interface::Input::Type; + auto isDigital() const -> bool { + return type == Type::Hat || type == Type::Button || type == Type::Trigger || type == Type::Control; + } + auto isAnalog() const -> bool { + return type == Type::Axis; + } + auto isRumble() const -> bool { + return type == Type::Rumble; + } string path; //configuration file key path string name; //input name (human readable) diff --git a/higan/target-bsnes/presentation/presentation.cpp b/higan/target-bsnes/presentation/presentation.cpp index 5fd8d2f2..8f4bf138 100644 --- a/higan/target-bsnes/presentation/presentation.cpp +++ b/higan/target-bsnes/presentation/presentation.cpp @@ -183,14 +183,6 @@ Presentation::Presentation() { Application::Windows::onModalChange([&](bool modal) { if(modal && audio) audio->clear(); }); - Application::Windows::onScreenSaver([&]() -> bool { - if(emulator->loaded()) { - if(pauseEmulation.checked()) return true; - if(!program->focused() && settingsWindow->input.pauseEmulation.checked()) return true; - return false; - } - return true; - }); #endif #if defined(PLATFORM_MACOS) @@ -346,7 +338,7 @@ auto Presentation::updateDeviceMenu() -> void { controllerPort2.reset(); expansionPort.reset(); - for(auto& port : emulator->ports) { + for(auto& port : emulator->ports()) { Menu* menu = nullptr; if(port.name == "Controller Port 1") menu = &controllerPort1; if(port.name == "Controller Port 2") menu = &controllerPort2; @@ -358,7 +350,7 @@ auto Presentation::updateDeviceMenu() -> void { auto deviceID = emulator->connected(port.id); Group devices; - for(auto& device : port.devices) { + for(auto& device : emulator->devices(port.id)) { if(port.name == "Expansion Port" && device.name == "21fx") continue; MenuRadioItem item{menu}; @@ -380,7 +372,7 @@ auto Presentation::updateDeviceMenu() -> void { } auto Presentation::updateDeviceSelections() -> void { - for(auto& port : emulator->ports) { + for(auto& port : emulator->ports()) { Menu* menu = nullptr; if(port.name == "Controller Port 1") menu = &controllerPort1; if(port.name == "Controller Port 2") menu = &controllerPort2; diff --git a/higan/target-bsnes/program/game.cpp b/higan/target-bsnes/program/game.cpp index 68dc4522..caf28c1c 100644 --- a/higan/target-bsnes/program/game.cpp +++ b/higan/target-bsnes/program/game.cpp @@ -1,60 +1,52 @@ auto Program::load() -> void { unload(); - - for(auto& media : emulator->media) { - if(media.type != "sfc") continue; - - if(emulator->load(media.id)) { - screenshot = {}; - frameAdvance = false; - if(!verified() && settingsWindow->advanced.warnOnUnverifiedGames.checked()) { - //todo: MessageDialog crashes with GTK+; unsure the reason why this happens - //once MessageDialog functions, add an "Always" option - if(MessageWindow( - "Warning: this game image is unverified. Running it *may* be a security risk.\n\n" - "Do you wish to run the game anyway?" - ).setParent(*presentation).question() == MessageWindow::Response::No) { - emulator->unload(); - return showMessage("Game loading cancelled"); - } - } - hackCompatibility(); - emulator->power(); - if(settingsWindow->advanced.autoLoadStateOnLoad.checked()) { - program->loadState("quick/undo"); - } - showMessage({ - verified() ? "Verified game loaded" : "Game loaded", - appliedPatch() ? " and patch applied" : "" - }); - presentation->setTitle(emulator->title()); - presentation->resetSystem.setEnabled(true); - presentation->unloadGame.setEnabled(true); - presentation->toolsMenu.setVisible(true); - presentation->speedNormal.setChecked(); - presentation->pauseEmulation.setChecked(false); - presentation->updateStatusIcon(); - presentation->resizeViewport(); - toolsWindow->cheatEditor.loadCheats(); - toolsWindow->stateManager.loadStates(); - toolsWindow->manifestViewer.loadManifest(); - - string locations = superFamicom.location; - if(auto location = gameBoy.location) locations.append("|", location); - if(auto location = bsMemory.location) locations.append("|", location); - if(auto location = sufamiTurboA.location) locations.append("|", location); - if(auto location = sufamiTurboB.location) locations.append("|", location); - presentation->addRecentGame(locations); - - updateVideoPalette(); - updateAudioEffects(); - updateAudioFrequency(); - } - - break; - } + if(!emulator->load()) return; gameQueue = {}; + screenshot = {}; + frameAdvance = false; + if(!verified() && settingsWindow->advanced.warnOnUnverifiedGames.checked()) { + //todo: MessageDialog crashes with GTK+; unsure the reason why this happens + //once MessageDialog functions, add an "Always" option + if(MessageWindow( + "Warning: this game image is unverified. Running it *may* be a security risk.\n\n" + "Do you wish to run the game anyway?" + ).setParent(*presentation).question() == MessageWindow::Response::No) { + emulator->unload(); + return showMessage("Game loading cancelled"); + } + } + hackCompatibility(); + emulator->power(); + if(settingsWindow->advanced.autoLoadStateOnLoad.checked()) { + program->loadState("quick/undo"); + } + showMessage({ + verified() ? "Verified game loaded" : "Game loaded", + appliedPatch() ? " and patch applied" : "" + }); + presentation->setTitle(emulator->title()); + presentation->resetSystem.setEnabled(true); + presentation->unloadGame.setEnabled(true); + presentation->toolsMenu.setVisible(true); + presentation->speedNormal.setChecked(); + presentation->pauseEmulation.setChecked(false); + presentation->updateStatusIcon(); + presentation->resizeViewport(); + toolsWindow->cheatEditor.loadCheats(); + toolsWindow->stateManager.loadStates(); + toolsWindow->manifestViewer.loadManifest(); + + string locations = superFamicom.location; + if(auto location = gameBoy.location) locations.append("|", location); + if(auto location = bsMemory.location) locations.append("|", location); + if(auto location = sufamiTurboA.location) locations.append("|", location); + if(auto location = sufamiTurboB.location) locations.append("|", location); + presentation->addRecentGame(locations); + + updateVideoPalette(); + updateAudioEffects(); + updateAudioFrequency(); } auto Program::loadFile(string location) -> vector { diff --git a/higan/target-higan/GNUmakefile b/higan/target-higan/GNUmakefile index 018f597c..c2e656b3 100644 --- a/higan/target-higan/GNUmakefile +++ b/higan/target-higan/GNUmakefile @@ -3,12 +3,12 @@ flags += -DSFC_SUPERGAMEBOY include fc/GNUmakefile include sfc/GNUmakefile -include ms/GNUmakefile +#include ms/GNUmakefile include md/GNUmakefile -include pce/GNUmakefile +#include pce/GNUmakefile include gb/GNUmakefile include gba/GNUmakefile -include ws/GNUmakefile +#include ws/GNUmakefile include processor/GNUmakefile hiro.path := ../hiro diff --git a/higan/target-higan/higan.cpp b/higan/target-higan/higan.cpp index 3abd1384..67c4dcc3 100644 --- a/higan/target-higan/higan.cpp +++ b/higan/target-higan/higan.cpp @@ -15,6 +15,7 @@ auto locate(string name) -> string { #include auto nall::main(string_vector args) -> void { Application::setName("higan"); + Application::setScreenSaver(false); new Program(args); Application::run(); } diff --git a/higan/target-higan/input/input.cpp b/higan/target-higan/input/input.cpp index ec4bba0c..0d655ee3 100644 --- a/higan/target-higan/input/input.cpp +++ b/higan/target-higan/input/input.cpp @@ -192,12 +192,12 @@ InputManager::InputManager() { for(auto& emulator : program->emulators) { InputEmulator inputEmulator; inputEmulator.interface = emulator; - inputEmulator.name = emulator->information.name; - for(auto& port : emulator->ports) { + inputEmulator.name = emulator->information().name; + for(auto& port : emulator->ports()) { InputPort inputPort{port.id, port.name}; - for(auto& device : port.devices) { + for(auto& device : emulator->devices(port.id)) { InputDevice inputDevice{device.id, device.name}; - for(auto& input : device.inputs) { + for(auto& input : emulator->inputs(device.id)) { InputMapping inputMapping; inputMapping.name = input.name; inputMapping.type = input.type; diff --git a/higan/target-higan/input/input.hpp b/higan/target-higan/input/input.hpp index dbe10d20..aeea3e48 100644 --- a/higan/target-higan/input/input.hpp +++ b/higan/target-higan/input/input.hpp @@ -6,9 +6,16 @@ struct InputMapping { auto poll() -> int16; auto rumble(bool enable) -> void; - auto isDigital() const -> bool { return type == 0; } - auto isAnalog() const -> bool { return type == 1; } - auto isRumble() const -> bool { return type == 2; } + using Type = Emulator::Interface::Input::Type; + auto isDigital() const -> bool { + return type == Type::Hat || type == Type::Button || type == Type::Trigger || type == Type::Control; + } + auto isAnalog() const -> bool { + return type == Type::Axis; + } + auto isRumble() const -> bool { + return type == Type::Rumble; + } auto displayName() -> string; diff --git a/higan/target-higan/presentation/presentation.cpp b/higan/target-higan/presentation/presentation.cpp index f15fea51..43704ce5 100644 --- a/higan/target-higan/presentation/presentation.cpp +++ b/higan/target-higan/presentation/presentation.cpp @@ -122,8 +122,8 @@ Presentation::Presentation() { viewport.setDroppable().onDrop([&](auto locations) { if(!directory::exists(locations(0))) return; - program->mediumQueue.append(locations(0)); - program->loadMedium(); + program->gameQueue.append(locations(0)); + program->load(); }); onSize([&] { @@ -143,14 +143,6 @@ Presentation::Presentation() { Application::Windows::onModalChange([&](bool modal) { if(modal && audio) audio->clear(); }); - Application::Windows::onScreenSaver([&]() -> bool { - if(emulator && emulator->loaded()) { - if(program->pause) return true; - if(!program->focused() && settingsManager->input.pauseEmulation.checked()) return true; - return false; - } - return true; - }); #endif #if defined(PLATFORM_MACOS) @@ -164,9 +156,10 @@ Presentation::Presentation() { auto Presentation::updateEmulatorMenu() -> void { if(!emulator) return; + auto information = emulator->information(); systemMenu.reset(); - for(auto& port : emulator->ports) { + for(auto& port : emulator->ports()) { Menu menu{&systemMenu}; menu.setProperty("portID", port.id); menu.setText(port.name); @@ -176,12 +169,12 @@ auto Presentation::updateEmulatorMenu() -> void { menu.setIcon(Icon::Device::Joypad); } - auto path = string{emulator->information.name, "/", port.name}.replace(" ", ""); + auto path = string{information.name, "/", port.name}.replace(" ", ""); auto deviceName = settings(path).text(); auto deviceID = emulator->connected(port.id); Group devices; - for(auto& device : port.devices) { + for(auto& device : emulator->devices(port.id)) { MenuRadioItem item{&menu}; item.setProperty("deviceID", device.id); item.setText(device.name); @@ -205,7 +198,7 @@ auto Presentation::updateEmulatorMenu() -> void { systemMenu.append(MenuSeparator()); } - if(emulator->information.resettable) { + if(information.resettable) { systemMenu.append(MenuItem().setText("Soft Reset").setIcon(Icon::Action::Refresh).onActivate([&] { program->softReset(); })); @@ -216,7 +209,7 @@ auto Presentation::updateEmulatorMenu() -> void { })); systemMenu.append(MenuItem().setText("Unload").setIcon(Icon::Media::Eject).onActivate([&] { - program->unloadMedium(); + program->unload(); })); updateEmulatorDeviceSelections(); @@ -225,7 +218,7 @@ auto Presentation::updateEmulatorMenu() -> void { auto Presentation::updateEmulatorDeviceSelections() -> void { if(!emulator) return; - for(auto& port : emulator->ports) { + for(auto& port : emulator->ports()) { for(auto& action : systemMenu->actions()) { auto portID = action.property("portID"); if(portID && portID.natural() == port.id) { @@ -293,11 +286,11 @@ auto Presentation::resizeViewport(bool resizeWindow) -> void { double emulatorHeight = 240; double aspectCorrection = 1.0; if(emulator) { - auto information = emulator->videoInformation(); - emulatorWidth = information.width; - emulatorHeight = information.height; - aspectCorrection = information.aspectCorrection; - if(emulator->information.overscan) { + auto display = emulator->display(); + emulatorWidth = display.width; + emulatorHeight = display.height; + aspectCorrection = display.aspectCorrection; + if(display.type == Emulator::Interface::Display::Type::CRT) { uint overscanHorizontal = settings["Video/Overscan/Horizontal"].natural(); uint overscanVertical = settings["Video/Overscan/Vertical"].natural(); emulatorWidth -= overscanHorizontal * 2; @@ -387,37 +380,38 @@ auto Presentation::loadSystems() -> void { systemsMenu.reset(); for(auto system : settings.find("Systems/System")) { if(!system["Visible"].boolean()) continue; - MenuItem item; + MenuItem item{&systemsMenu}; string name = system.text(); string filename = system["Load"].text(); string load = Location::base(filename).trimRight("/", 1L); string alias = system["Alias"].text(); - item - .setIcon(load ? Icon::Emblem::Folder : Icon::Device::Storage) - .setText({alias ? alias : load ? load : name, " ..."}).onActivate([=] { + item.setIcon(load ? Icon::Emblem::Folder : Icon::Device::Storage); + item.setText({alias ? alias : load ? load : name, " ..."}); + item.onActivate([=] { for(auto& emulator : program->emulators) { - if(name == emulator->information.name) { - if(filename) program->mediumQueue.append(filename); - program->loadMedium(*emulator, emulator->media(0)); + auto information = emulator->information(); + if(name == information.name) { + if(filename) program->gameQueue.append(filename); + program->load(*emulator); break; } } }); - systemsMenu.append(item); } //add icarus menu option -- but only if icarus binary is present if(execute("icarus", "--name").output.strip() == "icarus") { if(systemsMenu.actionCount()) systemsMenu.append(MenuSeparator()); - systemsMenu.append(MenuItem() - .setIcon(Icon::Emblem::File) - .setText("Load ROM File ...").onActivate([&] { + MenuItem item{&systemsMenu}; + item.setIcon(Icon::Emblem::File); + item.setText("Load ROM File ..."); + item.onActivate([&] { audio->clear(); if(auto location = execute("icarus", "--import")) { - program->mediumQueue.append(location.output.strip()); - program->loadMedium(); + program->gameQueue.append(location.output.strip()); + program->load(); } - })); + }); } } diff --git a/higan/target-higan/program/medium.cpp b/higan/target-higan/program/game.cpp similarity index 62% rename from higan/target-higan/program/medium.cpp rename to higan/target-higan/program/game.cpp index efff4d18..70236046 100644 --- a/higan/target-higan/program/medium.cpp +++ b/higan/target-higan/program/game.cpp @@ -1,29 +1,28 @@ -auto Program::loadMedium() -> void { - if(!mediumQueue) return; +auto Program::load() -> void { + if(!gameQueue) return; - string location = mediumQueue.left(); - string type = Location::suffix(location).trimLeft(".", 1L); + string location = gameQueue.left(); + string extension = Location::suffix(location).trimLeft(".", 1L); for(auto& emulator : emulators) { - for(auto& medium : emulator->media) { - if(medium.type != type) continue; - return loadMedium(*emulator, medium); - } + auto information = emulator->information(); + if(information.extension == extension) return load(*emulator); } - mediumQueue.reset(); + gameQueue.reset(); } -auto Program::loadMedium(Emulator::Interface& interface, const Emulator::Interface::Medium& medium) -> void { - unloadMedium(); +auto Program::load(Emulator::Interface& interface) -> void { + unload(); - mediumPaths.append(locate({"systems/", medium.name, ".sys/"})); + auto information = interface.information(); + gamePaths.append(locate({"systems/", information.name, ".sys/"})); inputManager->bind(emulator = &interface); presentation->updateEmulatorMenu(); - if(!emulator->load(medium.id)) { + if(!emulator->load()) { emulator = nullptr; - mediumPaths.reset(); + gamePaths.reset(); return; } emulator->power(); @@ -35,7 +34,7 @@ auto Program::loadMedium(Emulator::Interface& interface, const Emulator::Interfa presentation->resizeViewport(); presentation->setTitle(emulator->title()); - presentation->systemMenu.setText(medium.name).setVisible(true); + presentation->systemMenu.setText(information.name).setVisible(true); presentation->toolsMenu.setVisible(true); toolsManager->cheatEditor.loadCheats(); toolsManager->stateManager.doRefresh(); @@ -43,7 +42,7 @@ auto Program::loadMedium(Emulator::Interface& interface, const Emulator::Interfa toolsManager->gameNotes.loadNotes(); } -auto Program::unloadMedium() -> void { +auto Program::unload() -> void { if(!emulator) return; presentation->clearViewport(); @@ -51,7 +50,7 @@ auto Program::unloadMedium() -> void { toolsManager->gameNotes.saveNotes(); emulator->unload(); emulator = nullptr; - mediumPaths.reset(); + gamePaths.reset(); presentation->resizeViewport(); presentation->setTitle({"higan v", Emulator::Version}); diff --git a/higan/target-higan/program/platform.cpp b/higan/target-higan/program/platform.cpp index 32c65b4a..7e75b15d 100644 --- a/higan/target-higan/program/platform.cpp +++ b/higan/target-higan/program/platform.cpp @@ -1,5 +1,5 @@ auto Program::path(uint id) -> string { - return mediumPaths(id); + return gamePaths(id); } auto Program::open(uint id, string name, vfs::file::mode mode, bool required) -> vfs::shared::file { @@ -25,8 +25,8 @@ auto Program::open(uint id, string name, vfs::file::mode mode, bool required) -> auto Program::load(uint id, string name, string type, string_vector options) -> Emulator::Platform::Load { string location, option; - if(mediumQueue) { - auto entry = mediumQueue.takeLeft().split("|", 1L); + if(gameQueue) { + auto entry = gameQueue.takeLeft().split("|", 1L); location = entry.right(); if(entry.size() == 1) option = options(0); if(entry.size() == 2) option = entry.left(); @@ -41,12 +41,12 @@ auto Program::load(uint id, string name, string type, string_vector options) -> option = dialog.option(); } if(!directory::exists(location)) { - mediumQueue.reset(); + gameQueue.reset(); return {}; } - uint pathID = mediumPaths.size(); - mediumPaths.append(location); + uint pathID = gamePaths.size(); + gamePaths.append(location); return {pathID, option}; } @@ -56,12 +56,12 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig pitch >>= 2; - if(emulator->information.overscan) { + auto display = emulator->display(); + if(display.type == Emulator::Interface::Display::Type::CRT) { uint overscanHorizontal = settings["Video/Overscan/Horizontal"].natural(); uint overscanVertical = settings["Video/Overscan/Vertical"].natural(); - auto information = emulator->videoInformation(); - overscanHorizontal *= information.internalWidth / information.width; - overscanVertical *= information.internalHeight / information.height; + overscanHorizontal *= display.internalWidth / display.width; + overscanVertical *= display.internalHeight / display.height; data += overscanVertical * pitch + overscanHorizontal; width -= overscanHorizontal * 2; height -= overscanVertical * 2; diff --git a/higan/target-higan/program/program.cpp b/higan/target-higan/program/program.cpp index 7505f237..46687087 100644 --- a/higan/target-higan/program/program.cpp +++ b/higan/target-higan/program/program.cpp @@ -1,14 +1,14 @@ #include "../higan.hpp" #include #include -#include +//#include #include -#include +//#include #include #include -#include +//#include #include "platform.cpp" -#include "medium.cpp" +#include "game.cpp" #include "state.cpp" #include "utility.cpp" unique_pointer program; @@ -19,17 +19,17 @@ Program::Program(string_vector args) { Emulator::platform = this; emulators.append(new Famicom::Interface); emulators.append(new SuperFamicom::Interface); - emulators.append(new MasterSystem::MasterSystemInterface); +// emulators.append(new MasterSystem::MasterSystemInterface); emulators.append(new MegaDrive::Interface); - emulators.append(new PCEngine::PCEngineInterface); - emulators.append(new PCEngine::SuperGrafxInterface); +// emulators.append(new PCEngine::PCEngineInterface); +// emulators.append(new PCEngine::SuperGrafxInterface); emulators.append(new GameBoy::GameBoyInterface); emulators.append(new GameBoy::GameBoyColorInterface); emulators.append(new GameBoyAdvance::Interface); - emulators.append(new MasterSystem::GameGearInterface); - emulators.append(new WonderSwan::WonderSwanInterface); - emulators.append(new WonderSwan::WonderSwanColorInterface); - emulators.append(new WonderSwan::PocketChallengeV2Interface); +// emulators.append(new MasterSystem::GameGearInterface); +// emulators.append(new WonderSwan::WonderSwanInterface); +// emulators.append(new WonderSwan::WonderSwanColorInterface); +// emulators.append(new WonderSwan::PocketChallengeV2Interface); new Presentation; presentation->setVisible(); @@ -68,14 +68,14 @@ Program::Program(string_vector args) { presentation->toggleFullScreen(); } else if(directory::exists(argument.split("|", 1L).right())) { if(!argument.transform("\\", "/").endsWith("/")) argument.append("/"); - mediumQueue.append(argument); + gameQueue.append(argument); } else if(file::exists(argument)) { if(auto result = execute("icarus", "--import", argument)) { - mediumQueue.append(result.output.strip()); + gameQueue.append(result.output.strip()); } } } - loadMedium(); + if(gameQueue) load(); Application::onMain({&Program::main, this}); } @@ -107,7 +107,7 @@ auto Program::main() -> void { auto Program::quit() -> void { hasQuit = true; - unloadMedium(); + unload(); settings.save(); inputManager->quit(); video.reset(); diff --git a/higan/target-higan/program/program.hpp b/higan/target-higan/program/program.hpp index 5a9cecee..224e4c50 100644 --- a/higan/target-higan/program/program.hpp +++ b/higan/target-higan/program/program.hpp @@ -15,10 +15,10 @@ struct Program : Emulator::Platform { auto dipSettings(Markup::Node node) -> uint override; auto notify(string text) -> void override; - //medium.cpp - auto loadMedium() -> void; - auto loadMedium(Emulator::Interface& interface, const Emulator::Interface::Medium& medium) -> void; - auto unloadMedium() -> void; + //game.cpp + auto load() -> void; + auto load(Emulator::Interface& interface) -> void; + auto unload() -> void; //state.cpp auto stateName(uint slot, bool managed = false) -> string; @@ -47,8 +47,8 @@ struct Program : Emulator::Platform { vector emulators; - vector mediumQueue; //for command-line and drag-and-drop loading - vector mediumPaths; //for keeping track of loaded folder locations + vector gameQueue; //for command-line and drag-and-drop loading + vector gamePaths; //for keeping track of loaded folder locations time_t autoSaveTime = 0; //for automatically saving RAM periodically diff --git a/higan/target-higan/program/state.cpp b/higan/target-higan/program/state.cpp index 17542b86..033222b8 100644 --- a/higan/target-higan/program/state.cpp +++ b/higan/target-higan/program/state.cpp @@ -1,6 +1,6 @@ auto Program::stateName(uint slot, bool managed) -> string { return { - mediumPaths(1), "higan/states/", + gamePaths(1), "higan/states/", managed ? "managed/" : "quick/", "slot-", slot, ".bst" }; diff --git a/higan/target-higan/program/utility.cpp b/higan/target-higan/program/utility.cpp index 43f7ec91..6ebd10e3 100644 --- a/higan/target-higan/program/utility.cpp +++ b/higan/target-higan/program/utility.cpp @@ -63,8 +63,7 @@ auto Program::initializeInputDriver() -> void { } auto Program::softReset() -> void { - if(!emulator) return; - if(!emulator->information.resettable) return; + if(!emulator || !emulator->information().resettable) return; emulator->reset(); showMessage("System has been soft reset"); } diff --git a/higan/target-higan/settings/system-properties.cpp b/higan/target-higan/settings/system-properties.cpp index 38f5a86f..8dc054de 100644 --- a/higan/target-higan/settings/system-properties.cpp +++ b/higan/target-higan/settings/system-properties.cpp @@ -4,15 +4,13 @@ SystemProperties::SystemProperties() { layout.setPadding(5); systemLabel.setAlignment(1.0).setText("System:"); for(auto& emulator : program->emulators) { - systemOption.append(ComboButtonItem().setText(emulator->information.name)); + systemOption.append(ComboButtonItem().setText(emulator->information().name)); } loadLabel.setAlignment(1.0).setText("Load:"); loadBrowse.setText("Browse ...").onActivate([&] { string filters = "Games|"; for(auto& emulator : program->emulators) { - for(auto& media : emulator->media) { - filters.append("*.", media.type, ":"); - } + filters.append("*.", emulator->information().extension, ":"); } filters.trimRight(":", 1L); if(auto location = BrowserDialog() @@ -21,17 +19,13 @@ SystemProperties::SystemProperties() { .setFilters(filters) .openFolder()) { loadEdit.setText(location); - //change system option to match the media selected - auto suffix = Location::suffix(location).trimLeft(".", 1L); + //change system option to match the game selected + auto extension = Location::suffix(location).trimLeft(".", 1L); for(auto& emulator : program->emulators) { - for(auto& media : emulator->media) { - if(media.type == suffix) { - for(auto item : systemOption.items()) { - if(item.text() == emulator->information.name) { - item.setSelected(); - return; - } - } + auto information = emulator->information(); + if(information.extension == extension) { + for(auto item : systemOption.items()) { + if(item.text() == information.name) return item.setSelected(), void(); } } } diff --git a/higan/target-higan/tools/cheat-editor.cpp b/higan/target-higan/tools/cheat-editor.cpp index 3e8c0af1..95fddd6c 100644 --- a/higan/target-higan/tools/cheat-editor.cpp +++ b/higan/target-higan/tools/cheat-editor.cpp @@ -125,7 +125,7 @@ auto CheatEditor::addCode(const string& code, const string& description, bool en auto CheatEditor::loadCheats() -> void { doReset(true); - auto contents = string::read({program->mediumPaths(1), "higan/cheats.bml"}); + auto contents = string::read({program->gamePaths(1), "higan/cheats.bml"}); auto document = BML::unserialize(contents); for(auto cheat : document["cartridge"].find("cheat")) { if(!addCode(cheat["code"].text(), cheat["description"].text(), (bool)cheat["enabled"])) break; @@ -146,10 +146,10 @@ auto CheatEditor::saveCheats() -> void { count++; } if(count) { - directory::create({program->mediumPaths(1), "higan/"}); - file::write({program->mediumPaths(1), "higan/cheats.bml"}, document); + directory::create({program->gamePaths(1), "higan/"}); + file::write({program->gamePaths(1), "higan/cheats.bml"}, document); } else { - file::remove({program->mediumPaths(1), "higan/cheats.bml"}); + file::remove({program->gamePaths(1), "higan/cheats.bml"}); } doReset(true); } diff --git a/higan/target-higan/tools/game-notes.cpp b/higan/target-higan/tools/game-notes.cpp index 15c11118..0a0c0b0a 100644 --- a/higan/target-higan/tools/game-notes.cpp +++ b/higan/target-higan/tools/game-notes.cpp @@ -7,16 +7,16 @@ GameNotes::GameNotes(TabFrame* parent) : TabFrameItem(parent) { } auto GameNotes::loadNotes() -> void { - auto contents = string::read({program->mediumPaths(1), "higan/notes.txt"}); + auto contents = string::read({program->gamePaths(1), "higan/notes.txt"}); notes.setText(contents); } auto GameNotes::saveNotes() -> void { auto contents = notes.text(); if(contents) { - directory::create({program->mediumPaths(1), "higan/"}); - file::write({program->mediumPaths(1), "higan/notes.txt"}, contents); + directory::create({program->gamePaths(1), "higan/"}); + file::write({program->gamePaths(1), "higan/notes.txt"}, contents); } else { - file::remove({program->mediumPaths(1), "higan/notes.txt"}); + file::remove({program->gamePaths(1), "higan/notes.txt"}); } } diff --git a/higan/video/video.cpp b/higan/video/video.cpp index 7ddbd09f..84a07de3 100644 --- a/higan/video/video.cpp +++ b/higan/video/video.cpp @@ -30,10 +30,10 @@ auto Video::setPalette() -> void { if(!interface) return; delete palette; - colors = interface->videoColors(); + colors = interface->display().colors; palette = new uint32[colors]; for(auto index : range(colors)) { - uint64 color = interface->videoColor(index); + uint64 color = interface->color(index); uint16 b = color.bits( 0,15); uint16 g = color.bits(16,31); uint16 r = color.bits(32,47); diff --git a/hiro/cocoa/application.cpp b/hiro/cocoa/application.cpp index 1155fd22..1878e63c 100644 --- a/hiro/cocoa/application.cpp +++ b/hiro/cocoa/application.cpp @@ -82,6 +82,10 @@ auto pApplication::quit() -> void { } } +auto pApplication::setScreenSaver(bool screenSaver) -> void { + //TODO: not implemented +} + auto pApplication::initialize() -> void { @autoreleasepool { [NSApplication sharedApplication]; diff --git a/hiro/cocoa/application.hpp b/hiro/cocoa/application.hpp index b6973113..14777c66 100644 --- a/hiro/cocoa/application.hpp +++ b/hiro/cocoa/application.hpp @@ -15,6 +15,7 @@ struct pApplication { static auto pendingEvents() -> bool; static auto processEvents() -> void; static auto quit() -> void; + static auto setScreenSaver(bool screenSaver) -> void; static auto initialize() -> void; }; diff --git a/hiro/core/application.cpp b/hiro/core/application.cpp index 232c74cb..039b56c2 100644 --- a/hiro/core/application.cpp +++ b/hiro/core/application.cpp @@ -51,6 +51,10 @@ auto Application::scale(float value) -> float { return value * state.scale; } +auto Application::screenSaver() -> bool { + return state.screenSaver; +} + auto Application::setFont(const Font& font) -> void { state.font = font; } @@ -63,6 +67,11 @@ auto Application::setScale(float scale) -> void { state.scale = scale; } +auto Application::setScreenSaver(bool screenSaver) -> void { + state.screenSaver = screenSaver; + pApplication::setScreenSaver(screenSaver); +} + auto Application::unscale(float value) -> float { return value * (1.0 / state.scale); } @@ -74,19 +83,10 @@ auto Application::Windows::doModalChange(bool modal) -> void { if(state.windows.onModalChange) return state.windows.onModalChange(modal); } -auto Application::Windows::doScreenSaver() -> bool { - if(state.windows.onScreenSaver) return state.windows.onScreenSaver(); - return true; //true = allow screen saver (default); false = suppress screen saver -} - auto Application::Windows::onModalChange(const function& callback) -> void { state.windows.onModalChange = callback; } -auto Application::Windows::onScreenSaver(const function& callback) -> void { - state.windows.onScreenSaver = callback; -} - //Cocoa //===== diff --git a/hiro/core/application.hpp b/hiro/core/application.hpp new file mode 100644 index 00000000..bc626c41 --- /dev/null +++ b/hiro/core/application.hpp @@ -0,0 +1,70 @@ +#if defined(Hiro_Application) +struct Application { + Application() = delete; + + static auto doMain() -> void; + static auto font() -> Font; + static auto locale() -> Locale&; + static auto modal() -> bool; + static auto name() -> string; + static auto onMain(const function& callback = {}) -> void; + static auto run() -> void; + static auto scale() -> float; + static auto scale(float value) -> float; + static auto pendingEvents() -> bool; + static auto processEvents() -> void; + static auto quit() -> void; + static auto screenSaver() -> bool; + static auto setFont(const Font& font = {}) -> void; + static auto setName(const string& name = "") -> void; + static auto setScale(float scale = 1.0) -> void; + static auto setScreenSaver(bool screenSaver = true) -> void; + static auto unscale(float value) -> float; + + struct Windows { + static auto doModalChange(bool modal) -> void; + static auto onModalChange(const function& callback = {}) -> void; + }; + + struct Cocoa { + static auto doAbout() -> void; + static auto doActivate() -> void; + static auto doPreferences() -> void; + static auto doQuit() -> void; + static auto onAbout(const function& callback = {}) -> void; + static auto onActivate(const function& callback = {}) -> void; + static auto onPreferences(const function& callback = {}) -> void; + static auto onQuit(const function& callback = {}) -> void; + }; + + struct Namespace : Locale::Namespace { + Namespace(const string& value) : Locale::Namespace(Application::locale(), value) {} + }; + +//private: + struct State { + Font font; + Locale locale; + int modal = 0; + string name; + function onMain; + bool quit = false; + float scale = 1.0; + bool screenSaver = true; + + struct Windows { + function onModalChange; + function onScreenSaver; + } windows; + + struct Cocoa { + function onAbout; + function onActivate; + function onPreferences; + function onQuit; + } cocoa; + }; + static State state; + static auto initialize() -> void; +}; +#endif diff --git a/hiro/core/core.hpp b/hiro/core/core.hpp index 301efe18..0e404fbd 100644 --- a/hiro/core/core.hpp +++ b/hiro/core/core.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -369,84 +370,8 @@ struct Hotkey { }; #endif -#if defined(Hiro_Application) -struct Application { - Application() = delete; - - static auto doMain() -> void; - static auto font() -> Font; - static auto locale() -> Locale&; - static auto modal() -> bool; - static auto name() -> string; - static auto onMain(const function& callback = {}) -> void; - static auto run() -> void; - static auto scale() -> float; - static auto scale(float value) -> float; - static auto pendingEvents() -> bool; - static auto processEvents() -> void; - static auto quit() -> void; - static auto setFont(const Font& font = {}) -> void; - static auto setName(const string& name = "") -> void; - static auto setScale(float scale = 1.0) -> void; - static auto unscale(float value) -> float; - - struct Windows { - static auto doModalChange(bool modal) -> void; - static auto doScreenSaver() -> bool; - static auto onModalChange(const function& callback = {}) -> void; - static auto onScreenSaver(const function& callback = {}) -> void; - }; - - struct Cocoa { - static auto doAbout() -> void; - static auto doActivate() -> void; - static auto doPreferences() -> void; - static auto doQuit() -> void; - static auto onAbout(const function& callback = {}) -> void; - static auto onActivate(const function& callback = {}) -> void; - static auto onPreferences(const function& callback = {}) -> void; - static auto onQuit(const function& callback = {}) -> void; - }; - - struct Namespace : Locale::Namespace { - Namespace(const string& value) : Locale::Namespace(Application::locale(), value) {} - }; - -//private: - struct State { - Font font; - Locale locale; - int modal = 0; - string name; - function onMain; - bool quit = false; - float scale = 1.0; - - struct Windows { - function onModalChange; - function onScreenSaver; - } windows; - - struct Cocoa { - function onAbout; - function onActivate; - function onPreferences; - function onQuit; - } cocoa; - }; - static State state; - static auto initialize() -> void; -}; -#endif - -#if defined(Hiro_Desktop) -struct Desktop { - Desktop() = delete; - - static auto size() -> Size; - static auto workspace() -> Geometry; -}; -#endif +#include "application.hpp" +#include "desktop.hpp" #if defined(Hiro_Monitor) struct Monitor { diff --git a/hiro/core/desktop.hpp b/hiro/core/desktop.hpp new file mode 100644 index 00000000..3c18988d --- /dev/null +++ b/hiro/core/desktop.hpp @@ -0,0 +1,8 @@ +#if defined(Hiro_Desktop) +struct Desktop { + Desktop() = delete; + + static auto size() -> Size; + static auto workspace() -> Geometry; +}; +#endif diff --git a/hiro/gtk/application.cpp b/hiro/gtk/application.cpp index 717d6665..d9c18831 100644 --- a/hiro/gtk/application.cpp +++ b/hiro/gtk/application.cpp @@ -6,6 +6,7 @@ vector pApplication::windows; #if defined(DISPLAY_XORG) XlibDisplay* pApplication::display = nullptr; +bool pApplication::xdgScreenSaver = false; #endif auto pApplication::run() -> void { @@ -29,16 +30,21 @@ auto pApplication::quit() -> void { if(gtk_main_level()) gtk_main_quit(); #if defined(DISPLAY_XORG) - //TODO: Keyboard::poll() is being called after Application::quit(); - //so if display is closed; this causes a segfault - //XCloseDisplay(display); - //display = nullptr; + XCloseDisplay(display); + display = nullptr; + #endif +} + +auto pApplication::setScreenSaver(bool screenSaver) -> void { + #if defined(DISPLAY_XORG) + for(auto& window : windows) window->_setScreenSaver(screenSaver); #endif } auto pApplication::initialize() -> void { #if defined(DISPLAY_XORG) display = XOpenDisplay(nullptr); + xdgScreenSaver = (bool)execute("xdg-screensaver", "--version").output.find("xdg-screensaver"); #endif //set WM_CLASS to Application::name() diff --git a/hiro/gtk/application.hpp b/hiro/gtk/application.hpp index 9440bd1e..d8aea834 100644 --- a/hiro/gtk/application.hpp +++ b/hiro/gtk/application.hpp @@ -7,6 +7,7 @@ struct pApplication { static auto pendingEvents() -> bool; static auto processEvents() -> void; static auto quit() -> void; + static auto setScreenSaver(bool screenSaver) -> void; static auto initialize() -> void; @@ -14,6 +15,7 @@ struct pApplication { #if defined(DISPLAY_XORG) static XlibDisplay* display; + static bool xdgScreenSaver; #endif }; diff --git a/hiro/gtk/keyboard.cpp b/hiro/gtk/keyboard.cpp index 23ec016d..6c6a32c6 100644 --- a/hiro/gtk/keyboard.cpp +++ b/hiro/gtk/keyboard.cpp @@ -3,6 +3,8 @@ namespace hiro { auto pKeyboard::poll() -> vector { + if(Application::state.quit) return {}; + vector result; char state[256]; #if defined(DISPLAY_XORG) diff --git a/hiro/gtk/widget/viewport.cpp b/hiro/gtk/widget/viewport.cpp index 8c9754e6..3816a860 100644 --- a/hiro/gtk/widget/viewport.cpp +++ b/hiro/gtk/widget/viewport.cpp @@ -79,14 +79,16 @@ auto pViewport::destruct() -> void { gtkParent = nullptr; } -auto pViewport::handle() const -> uintptr_t { +auto pViewport::handle() const -> uintptr { #if defined(DISPLAY_WINDOWS) - return (uintptr_t)GDK_WINDOW_HWND(gtk_widget_get_window(gtkWidget)); + return (uintptr)GDK_WINDOW_HWND(gtk_widget_get_window(gtkWidget)); #endif #if defined(DISPLAY_XORG) return GDK_WINDOW_XID(gtk_widget_get_window(gtkWidget)); #endif + + return (uintptr)nullptr; } auto pViewport::setDroppable(bool droppable) -> void { diff --git a/hiro/gtk/widget/viewport.hpp b/hiro/gtk/widget/viewport.hpp index ed0b17f7..4eb4fe2e 100644 --- a/hiro/gtk/widget/viewport.hpp +++ b/hiro/gtk/widget/viewport.hpp @@ -5,7 +5,7 @@ namespace hiro { struct pViewport : pWidget { Declare(Viewport, Widget) - auto handle() const -> uintptr_t; + auto handle() const -> uintptr; auto setDroppable(bool droppable) -> void; }; diff --git a/hiro/gtk/window.cpp b/hiro/gtk/window.cpp index 5aeabc2c..ba44d6ff 100644 --- a/hiro/gtk/window.cpp +++ b/hiro/gtk/window.cpp @@ -99,6 +99,10 @@ static auto Window_keyRelease(GtkWidget* widget, GdkEventKey* event, pWindow* p) return false; } +static auto Window_realize(GtkWidget* widget, pWindow* p) -> void { + p->_setScreenSaver(Application::screenSaver()); +} + static auto Window_sizeAllocate(GtkWidget* widget, GtkAllocation* allocation, pWindow* p) -> void { p->_synchronizeState(); p->_synchronizeGeometry(); @@ -125,6 +129,10 @@ static auto Window_stateEvent(GtkWidget* widget, GdkEvent* event, pWindow* p) -> } } +static auto Window_unrealize(GtkWidget* widget, pWindow* p) -> void { + p->_setScreenSaver(true); +} + auto pWindow::construct() -> void { widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_resizable(GTK_WINDOW(widget), true); @@ -186,6 +194,7 @@ auto pWindow::construct() -> void { g_signal_connect(G_OBJECT(widget), "drag-data-received", G_CALLBACK(Window_drop), (gpointer)this); g_signal_connect(G_OBJECT(widget), "key-press-event", G_CALLBACK(Window_keyPress), (gpointer)this); g_signal_connect(G_OBJECT(widget), "key-release-event", G_CALLBACK(Window_keyRelease), (gpointer)this); + g_signal_connect(G_OBJECT(widget), "realize", G_CALLBACK(Window_realize), (gpointer)this); g_signal_connect(G_OBJECT(formContainer), "size-allocate", G_CALLBACK(Window_sizeAllocate), (gpointer)this); #if HIRO_GTK==2 g_signal_connect(G_OBJECT(formContainer), "size-request", G_CALLBACK(Window_sizeRequest), (gpointer)this); @@ -194,6 +203,7 @@ auto pWindow::construct() -> void { widgetClass->get_preferred_width = Window_getPreferredWidth; widgetClass->get_preferred_height = Window_getPreferredHeight; #endif + g_signal_connect(G_OBJECT(widget), "unrealize", G_CALLBACK(Window_unrealize), (gpointer)this); g_signal_connect(G_OBJECT(widget), "window-state-event", G_CALLBACK(Window_stateEvent), (gpointer)this); g_object_set_data(G_OBJECT(widget), "hiro::window", (gpointer)this); @@ -247,6 +257,18 @@ auto pWindow::frameMargin() const -> Geometry { }; } +auto pWindow::handle() const -> uintptr { + #if defined(DISPLAY_WINDOWS) + return (uintptr)GDK_WINDOW_HWND(gtk_widget_get_window(widget)); + #endif + + #if defined(DISPLAY_XORG) + return GDK_WINDOW_XID(gtk_widget_get_window(widget)); + #endif + + return (uintptr)nullptr; +} + auto pWindow::remove(sMenuBar menuBar) -> void { _setMenuVisible(false); } @@ -458,6 +480,19 @@ auto pWindow::_setMenuVisible(bool visible) -> void { gtk_widget_set_visible(gtkMenu, visible); } +auto pWindow::_setScreenSaver(bool screenSaver) -> void { + if(!gtk_widget_get_realized(widget)) return; + + #if defined(DISPLAY_XORG) + if(pApplication::xdgScreenSaver) { + if(this->screenSaver != screenSaver) { + this->screenSaver = screenSaver; + execute("xdg-screensaver", screenSaver ? "resume" : "suspend", string{"0x", hex(handle())}); + } + } + #endif +} + auto pWindow::_setStatusEnabled(bool enabled) -> void { gtk_widget_set_sensitive(gtkStatus, enabled); } diff --git a/hiro/gtk/window.hpp b/hiro/gtk/window.hpp index e2ca7a0b..73e03281 100644 --- a/hiro/gtk/window.hpp +++ b/hiro/gtk/window.hpp @@ -10,6 +10,7 @@ struct pWindow : pObject { auto append(sStatusBar statusBar) -> void; auto focused() const -> bool override; auto frameMargin() const -> Geometry; + auto handle() const -> uintptr; auto remove(sMenuBar menuBar) -> void; auto remove(sSizable sizable) -> void; auto remove(sStatusBar statusBar) -> void; @@ -33,6 +34,7 @@ struct pWindow : pObject { auto _append(mMenu& menu) -> void; auto _menuHeight() const -> int; auto _menuTextHeight() const -> int; + auto _setScreenSaver(bool screenSaver) -> void; auto _setIcon(const string& basename) -> bool; auto _setMenuEnabled(bool enabled) -> void; auto _setMenuFont(const Font& font) -> void; @@ -55,6 +57,7 @@ struct pWindow : pObject { GtkWidget* gtkStatus = nullptr; GtkAllocation lastMove = {0}; GtkAllocation lastSize = {0}; + bool screenSaver = true; }; } diff --git a/hiro/qt/application.cpp b/hiro/qt/application.cpp index 7887dab6..84db1907 100644 --- a/hiro/qt/application.cpp +++ b/hiro/qt/application.cpp @@ -28,6 +28,10 @@ auto pApplication::quit() -> void { qtApplication = nullptr; //note: deleting QApplication will crash libQtGui } +auto pApplication::setScreenSaver(bool screenSaver) -> void { + //TODO: not implemented +} + //this is sadly necessary for things like determining window frame geometry //obviously, it is used as sparingly as possible auto pApplication::syncX() -> void { diff --git a/hiro/qt/application.hpp b/hiro/qt/application.hpp index 3789df7b..2beae538 100644 --- a/hiro/qt/application.hpp +++ b/hiro/qt/application.hpp @@ -7,6 +7,7 @@ struct pApplication { static auto pendingEvents() -> bool; static auto processEvents() -> void; static auto quit() -> void; + static auto setScreenSaver(bool screenSaver) -> void; static auto initialize() -> void; static auto syncX() -> void; diff --git a/hiro/windows/application.cpp b/hiro/windows/application.cpp index 67e9f7c4..9b6610e0 100644 --- a/hiro/windows/application.cpp +++ b/hiro/windows/application.cpp @@ -54,6 +54,9 @@ auto pApplication::quit() -> void { PostQuitMessage(0); } +auto pApplication::setScreenSaver(bool screenSaver) -> void { +} + auto pApplication::initialize() -> void { CoInitialize(0); InitCommonControls(); @@ -262,7 +265,7 @@ static auto CALLBACK Application_windowProc(HWND hwnd, UINT msg, WPARAM wparam, case WM_EXITMENULOOP: case WM_EXITSIZEMOVE: pWindow->onModalEnd(); return false; case WM_SYSCOMMAND: if(wparam == SC_SCREENSAVE || wparam == SC_MONITORPOWER) { - if(!Application::Windows::doScreenSaver()) return 0; + if(!Application::screenSaver()) return 0; } } diff --git a/hiro/windows/application.hpp b/hiro/windows/application.hpp index 8e5b1f83..97b29d7f 100644 --- a/hiro/windows/application.hpp +++ b/hiro/windows/application.hpp @@ -7,6 +7,7 @@ struct pApplication { static auto pendingEvents() -> bool; static auto processEvents() -> void; static auto quit() -> void; + static auto setScreenSaver(bool screenSaver) -> void; static auto initialize() -> void; };