mirror of
https://github.com/TomHarte/CLK.git
synced 2025-08-12 09:25:19 +00:00
Merge pull request #653 from TomHarte/CommandCapture
Allows machines to declare modifiers as 'essential'.
This commit is contained in:
@@ -10,13 +10,14 @@
|
|||||||
|
|
||||||
using namespace Inputs;
|
using namespace Inputs;
|
||||||
|
|
||||||
Keyboard::Keyboard() {
|
Keyboard::Keyboard(const std::set<Key> &essential_modifiers) : essential_modifiers_(essential_modifiers) {
|
||||||
for(int k = 0; k < int(Key::Help); ++k) {
|
for(int k = 0; k < int(Key::Help); ++k) {
|
||||||
observed_keys_.insert(Key(k));
|
observed_keys_.insert(Key(k));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Keyboard::Keyboard(const std::set<Key> &observed_keys) : observed_keys_(observed_keys), is_exclusive_(false) {}
|
Keyboard::Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers) :
|
||||||
|
observed_keys_(observed_keys), essential_modifiers_(essential_modifiers), is_exclusive_(false) {}
|
||||||
|
|
||||||
void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) {
|
void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) {
|
||||||
std::size_t key_offset = static_cast<std::size_t>(key);
|
std::size_t key_offset = static_cast<std::size_t>(key);
|
||||||
@@ -28,6 +29,10 @@ void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) {
|
|||||||
if(delegate_) delegate_->keyboard_did_change_key(this, key, is_pressed);
|
if(delegate_) delegate_->keyboard_did_change_key(this, key, is_pressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::set<Inputs::Keyboard::Key> &Keyboard::get_essential_modifiers() {
|
||||||
|
return essential_modifiers_;
|
||||||
|
}
|
||||||
|
|
||||||
void Keyboard::reset_all_keys() {
|
void Keyboard::reset_all_keys() {
|
||||||
std::fill(key_states_.begin(), key_states_.end(), false);
|
std::fill(key_states_.begin(), key_states_.end(), false);
|
||||||
if(delegate_) delegate_->reset_all_keys(this);
|
if(delegate_) delegate_->reset_all_keys(this);
|
||||||
|
@@ -39,10 +39,10 @@ class Keyboard {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Constructs a Keyboard that declares itself to observe all keys.
|
/// Constructs a Keyboard that declares itself to observe all keys.
|
||||||
Keyboard();
|
Keyboard(const std::set<Key> &essential_modifiers = {});
|
||||||
|
|
||||||
/// Constructs a Keyboard that declares itself to observe only members of @c observed_keys.
|
/// Constructs a Keyboard that declares itself to observe only members of @c observed_keys.
|
||||||
Keyboard(const std::set<Key> &observed_keys);
|
Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers);
|
||||||
|
|
||||||
// Host interface.
|
// Host interface.
|
||||||
virtual void set_key_pressed(Key key, char value, bool is_pressed);
|
virtual void set_key_pressed(Key key, char value, bool is_pressed);
|
||||||
@@ -51,10 +51,18 @@ class Keyboard {
|
|||||||
/// @returns a set of all Keys that this keyboard responds to.
|
/// @returns a set of all Keys that this keyboard responds to.
|
||||||
virtual const std::set<Key> &observed_keys();
|
virtual const std::set<Key> &observed_keys();
|
||||||
|
|
||||||
/*
|
/// @returns the list of modifiers that this keyboard considers 'essential' (i.e. both mapped and highly used).
|
||||||
|
virtual const std::set<Inputs::Keyboard::Key> &get_essential_modifiers();
|
||||||
|
|
||||||
|
/*!
|
||||||
@returns @c true if this keyboard, on its original machine, looked
|
@returns @c true if this keyboard, on its original machine, looked
|
||||||
like a complete keyboard — i.e. if a user would expect this keyboard
|
like a complete keyboard — i.e. if a user would expect this keyboard
|
||||||
to be the only thing a real keyboard maps to.
|
to be the only thing a real keyboard maps to.
|
||||||
|
|
||||||
|
So this would be true of something like the Amstrad CPC, which has a full
|
||||||
|
keyboard, but it would be false of something like the Sega Master System
|
||||||
|
which has some buttons that you'd expect an emulator to map to its host
|
||||||
|
keyboard but which does not offer a full keyboard.
|
||||||
*/
|
*/
|
||||||
virtual bool is_exclusive();
|
virtual bool is_exclusive();
|
||||||
|
|
||||||
@@ -68,6 +76,7 @@ class Keyboard {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::set<Key> observed_keys_;
|
std::set<Key> observed_keys_;
|
||||||
|
std::set<Key> essential_modifiers_;
|
||||||
std::vector<bool> key_states_;
|
std::vector<bool> key_states_;
|
||||||
Delegate *delegate_ = nullptr;
|
Delegate *delegate_ = nullptr;
|
||||||
bool is_exclusive_ = true;
|
bool is_exclusive_ = true;
|
||||||
|
@@ -291,7 +291,7 @@ class Keyboard {
|
|||||||
Provides a mapping from idiomatic PC keys to Macintosh keys.
|
Provides a mapping from idiomatic PC keys to Macintosh keys.
|
||||||
*/
|
*/
|
||||||
class KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
|
class KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
|
||||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override;
|
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) final;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -78,6 +78,11 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
|||||||
using Target = Analyser::Static::Macintosh::Target;
|
using Target = Analyser::Static::Macintosh::Target;
|
||||||
|
|
||||||
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||||
|
KeyboardMachine::MappedMachine({
|
||||||
|
Inputs::Keyboard::Key::LeftShift, Inputs::Keyboard::Key::RightShift,
|
||||||
|
Inputs::Keyboard::Key::LeftOption, Inputs::Keyboard::Key::RightOption,
|
||||||
|
Inputs::Keyboard::Key::LeftMeta, Inputs::Keyboard::Key::RightMeta,
|
||||||
|
}),
|
||||||
mc68000_(*this),
|
mc68000_(*this),
|
||||||
iwm_(CLOCK_RATE),
|
iwm_(CLOCK_RATE),
|
||||||
video_(audio_, drive_speed_accumulator_),
|
video_(audio_, drive_speed_accumulator_),
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
using namespace KeyboardMachine;
|
using namespace KeyboardMachine;
|
||||||
|
|
||||||
MappedMachine::MappedMachine() {
|
MappedMachine::MappedMachine(const std::set<Inputs::Keyboard::Key> &essential_modifiers) : keyboard_(essential_modifiers) {
|
||||||
keyboard_.set_delegate(this);
|
keyboard_.set_delegate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
#include "../Inputs/Keyboard.hpp"
|
#include "../Inputs/Keyboard.hpp"
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ class Machine: public KeyActions {
|
|||||||
*/
|
*/
|
||||||
class MappedMachine: public Inputs::Keyboard::Delegate, public Machine {
|
class MappedMachine: public Inputs::Keyboard::Delegate, public Machine {
|
||||||
public:
|
public:
|
||||||
MappedMachine();
|
MappedMachine(const std::set<Inputs::Keyboard::Key> &essential_modifiers = {});
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys.
|
A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys.
|
||||||
|
@@ -101,7 +101,7 @@ class ConcreteMachine:
|
|||||||
audio_queue_,
|
audio_queue_,
|
||||||
sn76489_divider),
|
sn76489_divider),
|
||||||
speaker_(sn76489_),
|
speaker_(sn76489_),
|
||||||
keyboard_({Inputs::Keyboard::Key::Enter, Inputs::Keyboard::Key::Escape}) {
|
keyboard_({Inputs::Keyboard::Key::Enter, Inputs::Keyboard::Key::Escape}, {}) {
|
||||||
// Pick the clock rate based on the region.
|
// Pick the clock rate based on the region.
|
||||||
const double clock_rate = target.region == Target::Region::Europe ? 3546893.0 : 3579540.0;
|
const double clock_rate = target.region == Target::Region::Europe ? 3546893.0 : 3579540.0;
|
||||||
speaker_.set_input_rate(static_cast<float>(clock_rate / sn76489_divider));
|
speaker_.set_input_rate(static_cast<float>(clock_rate / sn76489_divider));
|
||||||
|
@@ -656,6 +656,7 @@
|
|||||||
4BCE005D227D30CC000CA200 /* MemoryPacker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005B227D30CC000CA200 /* MemoryPacker.cpp */; };
|
4BCE005D227D30CC000CA200 /* MemoryPacker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005B227D30CC000CA200 /* MemoryPacker.cpp */; };
|
||||||
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005E227D39AB000CA200 /* Video.cpp */; };
|
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005E227D39AB000CA200 /* Video.cpp */; };
|
||||||
4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; };
|
4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; };
|
||||||
|
4BD0FBC3233706A200148981 /* CSApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD0FBC2233706A200148981 /* CSApplication.m */; };
|
||||||
4BD191F42191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
|
4BD191F42191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
|
||||||
4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
|
4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
|
||||||
4BD388882239E198002D14B5 /* 68000Tests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD388872239E198002D14B5 /* 68000Tests.mm */; };
|
4BD388882239E198002D14B5 /* 68000Tests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD388872239E198002D14B5 /* 68000Tests.mm */; };
|
||||||
@@ -1095,6 +1096,7 @@
|
|||||||
4B90467222C6FA31000E2074 /* TestRunner68000.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TestRunner68000.hpp; sourceTree = "<group>"; };
|
4B90467222C6FA31000E2074 /* TestRunner68000.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TestRunner68000.hpp; sourceTree = "<group>"; };
|
||||||
4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000BitwiseTests.mm; sourceTree = "<group>"; };
|
4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000BitwiseTests.mm; sourceTree = "<group>"; };
|
||||||
4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000ArithmeticTests.mm; sourceTree = "<group>"; };
|
4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000ArithmeticTests.mm; sourceTree = "<group>"; };
|
||||||
|
4B911A9B2337D8AB00A2BB1D /* CSApplication.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSApplication.h; sourceTree = "<group>"; };
|
||||||
4B92294222B04A3D00A1458F /* MouseMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MouseMachine.hpp; sourceTree = "<group>"; };
|
4B92294222B04A3D00A1458F /* MouseMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MouseMachine.hpp; sourceTree = "<group>"; };
|
||||||
4B92294422B04ACB00A1458F /* Mouse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Mouse.hpp; sourceTree = "<group>"; };
|
4B92294422B04ACB00A1458F /* Mouse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Mouse.hpp; sourceTree = "<group>"; };
|
||||||
4B92294A22B064FD00A1458F /* QuadratureMouse.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = QuadratureMouse.hpp; sourceTree = "<group>"; };
|
4B92294A22B064FD00A1458F /* QuadratureMouse.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = QuadratureMouse.hpp; sourceTree = "<group>"; };
|
||||||
@@ -1478,6 +1480,7 @@
|
|||||||
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Oric/Oric.hpp; sourceTree = "<group>"; };
|
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Oric/Oric.hpp; sourceTree = "<group>"; };
|
||||||
4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
|
4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
|
||||||
4BD0692B22828A2D00D2A54F /* RealTimeClock.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealTimeClock.hpp; sourceTree = "<group>"; };
|
4BD0692B22828A2D00D2A54F /* RealTimeClock.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealTimeClock.hpp; sourceTree = "<group>"; };
|
||||||
|
4BD0FBC2233706A200148981 /* CSApplication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSApplication.m; sourceTree = "<group>"; };
|
||||||
4BD191D9219113B80042E144 /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = "<group>"; };
|
4BD191D9219113B80042E144 /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = "<group>"; };
|
||||||
4BD191F22191180E0042E144 /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = "<group>"; };
|
4BD191F22191180E0042E144 /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = "<group>"; };
|
||||||
4BD191F32191180E0042E144 /* ScanTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ScanTarget.hpp; sourceTree = "<group>"; };
|
4BD191F32191180E0042E144 /* ScanTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ScanTarget.hpp; sourceTree = "<group>"; };
|
||||||
@@ -3005,22 +3008,24 @@
|
|||||||
4BB73EA01B587A5100552FC2 /* Clock Signal */ = {
|
4BB73EA01B587A5100552FC2 /* Clock Signal */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */,
|
|
||||||
4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */,
|
4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */,
|
||||||
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */,
|
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */,
|
||||||
|
4B911A9B2337D8AB00A2BB1D /* CSApplication.h */,
|
||||||
|
4BD0FBC2233706A200148981 /* CSApplication.m */,
|
||||||
4BB73EAD1B587A5100552FC2 /* Info.plist */,
|
4BB73EAD1B587A5100552FC2 /* Info.plist */,
|
||||||
4BB73EA11B587A5100552FC2 /* AppDelegate.swift */,
|
4BB73EA11B587A5100552FC2 /* AppDelegate.swift */,
|
||||||
4BB73EA81B587A5100552FC2 /* Assets.xcassets */,
|
4BB73EA81B587A5100552FC2 /* Assets.xcassets */,
|
||||||
4B2A538F1D117D36003C6002 /* Audio */,
|
4B2A538F1D117D36003C6002 /* Audio */,
|
||||||
4B643F3D1D77B88000D431D6 /* Document Controller */,
|
4B643F3D1D77B88000D431D6 /* Document Controller */,
|
||||||
4B55CE551C3B7D360093A61B /* Documents */,
|
4B55CE551C3B7D360093A61B /* Documents */,
|
||||||
|
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */,
|
||||||
4B2A53921D117D36003C6002 /* Machine */,
|
4B2A53921D117D36003C6002 /* Machine */,
|
||||||
4B55DD7F20DF06680043F2E5 /* MachinePicker */,
|
4B55DD7F20DF06680043F2E5 /* MachinePicker */,
|
||||||
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
|
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
|
||||||
4BE5F85A1C3E1C2500C43F01 /* Resources */,
|
4BE5F85A1C3E1C2500C43F01 /* Resources */,
|
||||||
|
4BDA00DB22E60EE900AC3CD0 /* ROMRequester */,
|
||||||
4BD5F1961D1352A000631CD1 /* Updater */,
|
4BD5F1961D1352A000631CD1 /* Updater */,
|
||||||
4B55CE5A1C3B7D6F0093A61B /* Views */,
|
4B55CE5A1C3B7D6F0093A61B /* Views */,
|
||||||
4BDA00DB22E60EE900AC3CD0 /* ROMRequester */,
|
|
||||||
);
|
);
|
||||||
path = "Clock Signal";
|
path = "Clock Signal";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -4273,6 +4278,7 @@
|
|||||||
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
|
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
|
||||||
4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */,
|
4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */,
|
||||||
4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */,
|
4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */,
|
||||||
|
4BD0FBC3233706A200148981 /* CSApplication.m in Sources */,
|
||||||
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */,
|
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */,
|
||||||
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
|
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
|
||||||
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
|
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
|
||||||
|
@@ -67,7 +67,7 @@
|
|||||||
</Testables>
|
</Testables>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
enableASanStackUseAfterReturn = "YES"
|
enableASanStackUseAfterReturn = "YES"
|
||||||
@@ -76,8 +76,6 @@
|
|||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
stopOnEveryThreadSanitizerIssue = "YES"
|
|
||||||
stopOnEveryUBSanitizerIssue = "YES"
|
|
||||||
migratedStopOnEveryIssue = "YES"
|
migratedStopOnEveryIssue = "YES"
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
allowLocationSimulation = "NO">
|
allowLocationSimulation = "NO">
|
||||||
|
@@ -13,16 +13,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
|
|
||||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||||
// Insert code here to initialize your application.
|
// Insert code here to initialize your application.
|
||||||
|
|
||||||
if NSDocumentController.shared.documents.count == 0 {
|
|
||||||
NSDocumentController.shared.openDocument(self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationWillTerminate(_ aNotification: Notification) {
|
func applicationWillTerminate(_ aNotification: Notification) {
|
||||||
// Insert code here to tear down your application.
|
// Insert code here to tear down your application.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var hasShownOpenDocument = false
|
||||||
func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool {
|
func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool {
|
||||||
// Decline to show the 'New...' selector by default; the 'Open...'
|
// Decline to show the 'New...' selector by default; the 'Open...'
|
||||||
// dialogue has already been shown if this application was started
|
// dialogue has already been shown if this application was started
|
||||||
@@ -31,6 +28,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
// Obiter: I dislike it when other applications do this for me, but it
|
// Obiter: I dislike it when other applications do this for me, but it
|
||||||
// seems to be the new norm, and I've had user feedback that showing
|
// seems to be the new norm, and I've had user feedback that showing
|
||||||
// nothing is confusing. So here it is.
|
// nothing is confusing. So here it is.
|
||||||
|
if !hasShownOpenDocument {
|
||||||
|
NSDocumentController.shared.openDocument(self)
|
||||||
|
hasShownOpenDocument = true
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
OSBindings/Mac/Clock Signal/CSApplication.h
Normal file
31
OSBindings/Mac/Clock Signal/CSApplication.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// CSApplication.h
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 22/09/2019.
|
||||||
|
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef CSApplication_h
|
||||||
|
#define CSApplication_h
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
@protocol CSApplicationKeyboardEventDelegate
|
||||||
|
- (void)sendEvent:(nonnull NSEvent *)event;
|
||||||
|
@end
|
||||||
|
|
||||||
|
/*!
|
||||||
|
CSApplication differs from NSApplication in only one regard: it supports a keyboardEventDelegate.
|
||||||
|
|
||||||
|
If a keyboardEventDelegate is installed, all keyboard events — @c NSEventTypeKeyUp,
|
||||||
|
@c NSEventTypeKeyDown and @c NSEventTypeFlagsChanged — will be diverted to it
|
||||||
|
rather than passed through the usual processing. As a result keyboard shortcuts and assistive
|
||||||
|
dialogue navigations won't work.
|
||||||
|
*/
|
||||||
|
@interface CSApplication: NSApplication
|
||||||
|
@property(nonatomic, weak, nullable) id<CSApplicationKeyboardEventDelegate> keyboardEventDelegate;
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* CSApplication_h */
|
37
OSBindings/Mac/Clock Signal/CSApplication.m
Normal file
37
OSBindings/Mac/Clock Signal/CSApplication.m
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// Application.m
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 21/09/2019.
|
||||||
|
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "CSApplication.h"
|
||||||
|
|
||||||
|
@implementation CSApplication
|
||||||
|
|
||||||
|
- (void)sendEvent:(NSEvent *)event {
|
||||||
|
// The only reason to capture events here rather than at the window or view
|
||||||
|
// is to divert key combinations such as command+w or command+q in the few
|
||||||
|
// occasions when the user would expect those to affect a running machine
|
||||||
|
// rather than to affect application state.
|
||||||
|
//
|
||||||
|
// Most obviously: when emulating a Macintosh, you'd expect command+q to
|
||||||
|
// quit the application inside the Macintosh, not to quit the emulator.
|
||||||
|
|
||||||
|
switch(event.type) {
|
||||||
|
case NSEventTypeKeyUp:
|
||||||
|
case NSEventTypeKeyDown:
|
||||||
|
case NSEventTypeFlagsChanged:
|
||||||
|
if(self.keyboardEventDelegate) {
|
||||||
|
[self.keyboardEventDelegate sendEvent:event];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
[super sendEvent:event];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
@@ -209,8 +209,10 @@ class MachineDocument:
|
|||||||
openGLView.delegate = self
|
openGLView.delegate = self
|
||||||
openGLView.responderDelegate = self
|
openGLView.responderDelegate = self
|
||||||
|
|
||||||
// If this machine has a mouse, enable mouse capture.
|
// If this machine has a mouse, enable mouse capture; also indicate whether usurption
|
||||||
|
// of the command key is desired.
|
||||||
openGLView.shouldCaptureMouse = machine.hasMouse
|
openGLView.shouldCaptureMouse = machine.hasMouse
|
||||||
|
openGLView.shouldUsurpCommand = machine.shouldUsurpCommand
|
||||||
|
|
||||||
setupAudioQueueClockRate()
|
setupAudioQueueClockRate()
|
||||||
|
|
||||||
|
@@ -554,6 +554,6 @@
|
|||||||
<key>NSMainNibFile</key>
|
<key>NSMainNibFile</key>
|
||||||
<string>MainMenu</string>
|
<string>MainMenu</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>CSApplication</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@@ -94,6 +94,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
|||||||
|
|
||||||
// Input control.
|
// Input control.
|
||||||
@property (nonatomic, readonly) BOOL hasExclusiveKeyboard;
|
@property (nonatomic, readonly) BOOL hasExclusiveKeyboard;
|
||||||
|
@property (nonatomic, readonly) BOOL shouldUsurpCommand;
|
||||||
@property (nonatomic, readonly) BOOL hasJoystick;
|
@property (nonatomic, readonly) BOOL hasJoystick;
|
||||||
@property (nonatomic, readonly) BOOL hasMouse;
|
@property (nonatomic, readonly) BOOL hasMouse;
|
||||||
@property (nonatomic, assign) CSMachineKeyboardInputMode inputMode;
|
@property (nonatomic, assign) CSMachineKeyboardInputMode inputMode;
|
||||||
|
@@ -671,6 +671,14 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
return !!_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive();
|
return !!_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)shouldUsurpCommand {
|
||||||
|
if(!_machine->keyboard_machine()) return NO;
|
||||||
|
|
||||||
|
const auto essential_modifiers = _machine->keyboard_machine()->get_keyboard().get_essential_modifiers();
|
||||||
|
return essential_modifiers.find(Inputs::Keyboard::Key::LeftMeta) != essential_modifiers.end() ||
|
||||||
|
essential_modifiers.find(Inputs::Keyboard::Key::RightMeta) != essential_modifiers.end();
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Activity observation
|
#pragma mark - Activity observation
|
||||||
|
|
||||||
- (void)addLED:(NSString *)led {
|
- (void)addLED:(NSString *)led {
|
||||||
|
@@ -110,8 +110,22 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
|
|||||||
@property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
|
@property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
|
||||||
@property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate;
|
@property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate;
|
||||||
|
|
||||||
|
/// Determines whether the view offers mouse capturing — i.e. if the user clicks on the view then
|
||||||
|
/// then the system cursor is disabled and the mouse events defined by CSOpenGLViewResponderDelegate
|
||||||
|
/// are forwarded, unless and until the user releases the mouse using the control+command shortcut.
|
||||||
@property (nonatomic, assign) BOOL shouldCaptureMouse;
|
@property (nonatomic, assign) BOOL shouldCaptureMouse;
|
||||||
|
|
||||||
|
/// Determines whether the CSOpenGLViewResponderDelegate of this window expects to use the command
|
||||||
|
/// key as though it were any other key — i.e. all command combinations should be forwarded to the delegate,
|
||||||
|
/// not being allowed to trigger regular application shortcuts such as command+q or command+h.
|
||||||
|
///
|
||||||
|
/// How the view respects this will depend on other state; if this view is one that captures the mouse then it
|
||||||
|
/// will usurp command only while the mouse is captured.
|
||||||
|
///
|
||||||
|
/// TODO: what's smart behaviour if this view doesn't capture the mouse? Probably
|
||||||
|
/// force a similar capturing behaviour?
|
||||||
|
@property (nonatomic, assign) BOOL shouldUsurpCommand;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Ends the timer tracking time; should be called prior to giving up the last owning reference
|
Ends the timer tracking time; should be called prior to giving up the last owning reference
|
||||||
to ensure that any retain cycles implied by the timer are resolved.
|
to ensure that any retain cycles implied by the timer are resolved.
|
||||||
|
@@ -7,10 +7,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "CSOpenGLView.h"
|
#import "CSOpenGLView.h"
|
||||||
|
#import "CSApplication.h"
|
||||||
@import CoreVideo;
|
@import CoreVideo;
|
||||||
@import GLKit;
|
@import GLKit;
|
||||||
|
|
||||||
@interface CSOpenGLView () <NSDraggingDestination>
|
@interface CSOpenGLView () <NSDraggingDestination, CSApplicationKeyboardEventDelegate>
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation CSOpenGLView {
|
@implementation CSOpenGLView {
|
||||||
@@ -139,23 +140,43 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
|||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)propagateKeyboardEvent:(NSEvent *)event {
|
||||||
|
switch(event.type) {
|
||||||
|
default: break;
|
||||||
|
|
||||||
|
case kCGEventKeyDown:
|
||||||
|
[self.responderDelegate keyDown:event];
|
||||||
|
break;
|
||||||
|
case kCGEventKeyUp:
|
||||||
|
[self.responderDelegate keyUp:event];
|
||||||
|
break;
|
||||||
|
case kCGEventFlagsChanged:
|
||||||
|
// Release the mouse upon a control + command.
|
||||||
|
if(_mouseIsCaptured &&
|
||||||
|
event.modifierFlags & NSEventModifierFlagControl &&
|
||||||
|
event.modifierFlags & NSEventModifierFlagCommand) {
|
||||||
|
[self releaseMouse];
|
||||||
|
}
|
||||||
|
|
||||||
|
[self.responderDelegate flagsChanged:event];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (void)keyDown:(NSEvent *)theEvent {
|
- (void)keyDown:(NSEvent *)theEvent {
|
||||||
[self.responderDelegate keyDown:theEvent];
|
[self propagateKeyboardEvent:theEvent];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)keyUp:(NSEvent *)theEvent {
|
- (void)keyUp:(NSEvent *)theEvent {
|
||||||
[self.responderDelegate keyUp:theEvent];
|
[self propagateKeyboardEvent:theEvent];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)flagsChanged:(NSEvent *)theEvent {
|
- (void)flagsChanged:(NSEvent *)theEvent {
|
||||||
[self.responderDelegate flagsChanged:theEvent];
|
[self propagateKeyboardEvent:theEvent];
|
||||||
|
}
|
||||||
|
|
||||||
// Release the mouse upon a control + command.
|
- (void)sendEvent:(NSEvent *)event {
|
||||||
if(_mouseIsCaptured &&
|
[self propagateKeyboardEvent:event];
|
||||||
theEvent.modifierFlags & NSEventModifierFlagControl &&
|
|
||||||
theEvent.modifierFlags & NSEventModifierFlagCommand) {
|
|
||||||
[self releaseMouse];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)paste:(id)sender {
|
- (void)paste:(id)sender {
|
||||||
@@ -224,6 +245,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
|||||||
CGAssociateMouseAndMouseCursorPosition(true);
|
CGAssociateMouseAndMouseCursorPosition(true);
|
||||||
[NSCursor unhide];
|
[NSCursor unhide];
|
||||||
[self.delegate openGLViewDidReleaseMouse:self];
|
[self.delegate openGLViewDidReleaseMouse:self];
|
||||||
|
((CSApplication *)[NSApplication sharedApplication]).keyboardEventDelegate = nil;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,6 +305,9 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
|||||||
[NSCursor hide];
|
[NSCursor hide];
|
||||||
CGAssociateMouseAndMouseCursorPosition(false);
|
CGAssociateMouseAndMouseCursorPosition(false);
|
||||||
[self.delegate openGLViewDidCaptureMouse:self];
|
[self.delegate openGLViewDidCaptureMouse:self];
|
||||||
|
if(self.shouldUsurpCommand) {
|
||||||
|
((CSApplication *)[NSApplication sharedApplication]).keyboardEventDelegate = self;
|
||||||
|
}
|
||||||
|
|
||||||
// Don't report the first click to the delegate; treat that as merely
|
// Don't report the first click to the delegate; treat that as merely
|
||||||
// an invitation to capture the cursor.
|
// an invitation to capture the cursor.
|
||||||
|
Reference in New Issue
Block a user