mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-12 00:30:31 +00:00
Merge pull request #653 from TomHarte/CommandCapture
Allows machines to declare modifiers as 'essential'.
This commit is contained in:
commit
e8bd538182
@ -10,13 +10,14 @@
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
const std::set<Inputs::Keyboard::Key> &Keyboard::get_essential_modifiers() {
|
||||
return essential_modifiers_;
|
||||
}
|
||||
|
||||
void Keyboard::reset_all_keys() {
|
||||
std::fill(key_states_.begin(), key_states_.end(), false);
|
||||
if(delegate_) delegate_->reset_all_keys(this);
|
||||
|
@ -39,10 +39,10 @@ class Keyboard {
|
||||
};
|
||||
|
||||
/// 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.
|
||||
Keyboard(const std::set<Key> &observed_keys);
|
||||
Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers);
|
||||
|
||||
// Host interface.
|
||||
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.
|
||||
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
|
||||
like a complete keyboard — i.e. if a user would expect this keyboard
|
||||
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();
|
||||
|
||||
@ -68,6 +76,7 @@ class Keyboard {
|
||||
|
||||
private:
|
||||
std::set<Key> observed_keys_;
|
||||
std::set<Key> essential_modifiers_;
|
||||
std::vector<bool> key_states_;
|
||||
Delegate *delegate_ = nullptr;
|
||||
bool is_exclusive_ = true;
|
||||
|
@ -291,7 +291,7 @@ class Keyboard {
|
||||
Provides a mapping from idiomatic PC keys to Macintosh keys.
|
||||
*/
|
||||
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;
|
||||
|
||||
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),
|
||||
iwm_(CLOCK_RATE),
|
||||
video_(audio_, drive_speed_accumulator_),
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
using namespace KeyboardMachine;
|
||||
|
||||
MappedMachine::MappedMachine() {
|
||||
MappedMachine::MappedMachine(const std::set<Inputs::Keyboard::Key> &essential_modifiers) : keyboard_(essential_modifiers) {
|
||||
keyboard_.set_delegate(this);
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "../Inputs/Keyboard.hpp"
|
||||
|
||||
@ -56,7 +57,7 @@ class Machine: public KeyActions {
|
||||
*/
|
||||
class MappedMachine: public Inputs::Keyboard::Delegate, public Machine {
|
||||
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.
|
||||
|
@ -101,7 +101,7 @@ class ConcreteMachine:
|
||||
audio_queue_,
|
||||
sn76489_divider),
|
||||
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.
|
||||
const double clock_rate = target.region == Target::Region::Europe ? 3546893.0 : 3579540.0;
|
||||
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 */; };
|
||||
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005E227D39AB000CA200 /* Video.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 */; };
|
||||
4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -1478,6 +1480,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -3005,22 +3008,24 @@
|
||||
4BB73EA01B587A5100552FC2 /* Clock Signal */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */,
|
||||
4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */,
|
||||
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */,
|
||||
4B911A9B2337D8AB00A2BB1D /* CSApplication.h */,
|
||||
4BD0FBC2233706A200148981 /* CSApplication.m */,
|
||||
4BB73EAD1B587A5100552FC2 /* Info.plist */,
|
||||
4BB73EA11B587A5100552FC2 /* AppDelegate.swift */,
|
||||
4BB73EA81B587A5100552FC2 /* Assets.xcassets */,
|
||||
4B2A538F1D117D36003C6002 /* Audio */,
|
||||
4B643F3D1D77B88000D431D6 /* Document Controller */,
|
||||
4B55CE551C3B7D360093A61B /* Documents */,
|
||||
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */,
|
||||
4B2A53921D117D36003C6002 /* Machine */,
|
||||
4B55DD7F20DF06680043F2E5 /* MachinePicker */,
|
||||
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
|
||||
4BE5F85A1C3E1C2500C43F01 /* Resources */,
|
||||
4BDA00DB22E60EE900AC3CD0 /* ROMRequester */,
|
||||
4BD5F1961D1352A000631CD1 /* Updater */,
|
||||
4B55CE5A1C3B7D6F0093A61B /* Views */,
|
||||
4BDA00DB22E60EE900AC3CD0 /* ROMRequester */,
|
||||
);
|
||||
path = "Clock Signal";
|
||||
sourceTree = "<group>";
|
||||
@ -4273,6 +4278,7 @@
|
||||
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
|
||||
4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */,
|
||||
4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */,
|
||||
4BD0FBC3233706A200148981 /* CSApplication.m in Sources */,
|
||||
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */,
|
||||
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
|
||||
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
|
||||
|
@ -67,7 +67,7 @@
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
@ -76,8 +76,6 @@
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
stopOnEveryThreadSanitizerIssue = "YES"
|
||||
stopOnEveryUBSanitizerIssue = "YES"
|
||||
migratedStopOnEveryIssue = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "NO">
|
||||
|
@ -13,16 +13,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
// Insert code here to initialize your application.
|
||||
|
||||
if NSDocumentController.shared.documents.count == 0 {
|
||||
NSDocumentController.shared.openDocument(self)
|
||||
}
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ aNotification: Notification) {
|
||||
// Insert code here to tear down your application.
|
||||
}
|
||||
|
||||
private var hasShownOpenDocument = false
|
||||
func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool {
|
||||
// Decline to show the 'New...' selector by default; the 'Open...'
|
||||
// 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
|
||||
// seems to be the new norm, and I've had user feedback that showing
|
||||
// nothing is confusing. So here it is.
|
||||
if !hasShownOpenDocument {
|
||||
NSDocumentController.shared.openDocument(self)
|
||||
hasShownOpenDocument = true
|
||||
}
|
||||
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.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.shouldUsurpCommand = machine.shouldUsurpCommand
|
||||
|
||||
setupAudioQueueClockRate()
|
||||
|
||||
|
@ -554,6 +554,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>CSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -94,6 +94,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
|
||||
// Input control.
|
||||
@property (nonatomic, readonly) BOOL hasExclusiveKeyboard;
|
||||
@property (nonatomic, readonly) BOOL shouldUsurpCommand;
|
||||
@property (nonatomic, readonly) BOOL hasJoystick;
|
||||
@property (nonatomic, readonly) BOOL hasMouse;
|
||||
@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();
|
||||
}
|
||||
|
||||
- (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
|
||||
|
||||
- (void)addLED:(NSString *)led {
|
||||
|
@ -110,8 +110,22 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
|
||||
@property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
|
||||
@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;
|
||||
|
||||
/// 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
|
||||
to ensure that any retain cycles implied by the timer are resolved.
|
||||
|
@ -7,10 +7,11 @@
|
||||
//
|
||||
|
||||
#import "CSOpenGLView.h"
|
||||
#import "CSApplication.h"
|
||||
@import CoreVideo;
|
||||
@import GLKit;
|
||||
|
||||
@interface CSOpenGLView () <NSDraggingDestination>
|
||||
@interface CSOpenGLView () <NSDraggingDestination, CSApplicationKeyboardEventDelegate>
|
||||
@end
|
||||
|
||||
@implementation CSOpenGLView {
|
||||
@ -139,23 +140,43 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
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 {
|
||||
[self.responderDelegate keyDown:theEvent];
|
||||
[self propagateKeyboardEvent:theEvent];
|
||||
}
|
||||
|
||||
- (void)keyUp:(NSEvent *)theEvent {
|
||||
[self.responderDelegate keyUp:theEvent];
|
||||
[self propagateKeyboardEvent:theEvent];
|
||||
}
|
||||
|
||||
- (void)flagsChanged:(NSEvent *)theEvent {
|
||||
[self.responderDelegate flagsChanged:theEvent];
|
||||
[self propagateKeyboardEvent:theEvent];
|
||||
}
|
||||
|
||||
// Release the mouse upon a control + command.
|
||||
if(_mouseIsCaptured &&
|
||||
theEvent.modifierFlags & NSEventModifierFlagControl &&
|
||||
theEvent.modifierFlags & NSEventModifierFlagCommand) {
|
||||
[self releaseMouse];
|
||||
}
|
||||
- (void)sendEvent:(NSEvent *)event {
|
||||
[self propagateKeyboardEvent:event];
|
||||
}
|
||||
|
||||
- (void)paste:(id)sender {
|
||||
@ -224,6 +245,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
CGAssociateMouseAndMouseCursorPosition(true);
|
||||
[NSCursor unhide];
|
||||
[self.delegate openGLViewDidReleaseMouse:self];
|
||||
((CSApplication *)[NSApplication sharedApplication]).keyboardEventDelegate = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@ -283,6 +305,9 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
[NSCursor hide];
|
||||
CGAssociateMouseAndMouseCursorPosition(false);
|
||||
[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
|
||||
// an invitation to capture the cursor.
|
||||
|
Loading…
x
Reference in New Issue
Block a user