How can I tell which third party keyboard the user is using or switching to?
I did not find any notification or API that I could use to check this info.
You can't know which keyboards are installed, nor can you decide which keyboard to load next.
Apple's documentation states:
To ask the system to switch to another keyboard, call the
advanceToNextInputMode method (..) The system picks the appropriate
“next” keyboard; there is no API to obtain a list of enabled keyboards
or for picking a particular keyboard to switch to.
For further information, read Apple's Custom Keyboards API Documentation:
https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/Keyboard.html#//apple_ref/doc/uid/TP40014214-CH16-SW21
A bit late but you can get the list of active keyboards on a device. All keyboard identifiers are stored in UserDefaults and here is how you can get them:
/**
- Author:
Panayot Panayotov
- returns:
True or False
- Important:
Result is returned immediately after non system identifier is found
This method iterates through all public keyboard identifiers and
evaluates a regex that matches system keyboards
[
"nb_NO#sw=QWERTY-Norwegian;hw=Automatic",
"zh_Hant-Zhuyin#sw=Zhuyin;hw=Automatic",
"ur#sw=Urdu;hw=Automatic",
"zh_Hant-HWR#sw=HWR-Traditional",
"zh_Hant-Pinyin#sw=Pinyin10-Traditional;hw=Automatic",
"emoji#sw=Emoji"
]
*/
-(BOOL)hasUnkownKeyboard {
NSString * appleKeyboardsRegexp = #"^(?:[0-9a-zA-Z_\\-]+)(?:#sw|#hw)[0-9a-zA-Z_\\-\\-;=]*$";
NSPredicate * appleKeyboardsTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", appleKeyboardsRegexp];
NSArray *keyboards = [[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] objectForKey:#"AppleKeyboards"];
for (NSString *keyboard in keyboards) {
if(![appleKeyboardsTest evaluateWithObject:keyboard]){
return YES;
}
}
return NO;
}
Related
I'm working on a screensaver, so I'm supposed to use ScreenSaverDefaults instead of NSUserDefaults. I'd like to have my configure panel use bindings for its UI, but they need to be wired to ScreenSaverDefaults, and I can't see a way to do that; the only defaults controller available in IB (xCode) is the standard user defaults controller. Is there a workaround, or is it just not possible to use bindings in the nib in the context of a screensaver?
I have successfully used bindings in a screen saver.
(Possibly important note: I'm still developing on 10.6, with XC 3.2.6; this code has only been lightly tested so far on 10.10 (but does seem to work). Also, I mix C++ and Objective-C, so you may have minor code cleanup to do, in what follows.)
My code creates, and the UI binds to, a custom user defaults controller (controls bind to File's Owner, which is set to my screen saver view, the controller key is empty, and the model key path is self.defaultsController.values.key_val, where key_val is whatever key is used in the defaults plist to access the value bound to a control).
You must also create your own screen saver defaults, and direct your custom user defaults controller to use them, early in screen saver initialization (such as in the initWithFrame: method of your screen saver view), like so:
// Find our bundle:
NSBundle * screenSaverBundle = [NSBundle bundleForClass: [self class]];
// Load per-user screen saver defaults:
ScreenSaverDefaults * screenSaverDefaults = [ScreenSaverDefaults defaultsForModuleWithName: [screenSaverBundle bundleIdentifier]];
// Create default defaults (values to use when no other values have been established):
NSDictionary * defaultDefaultValues = [self defaultDefaults];
// Register default defaults as fallback values (in registration domain):
[screenSaverDefaults registerDefaults: defaultDefaultValues];
// Configure custom user defaults controller to use the correct defaults:
// NOTE: defaultsController of NSUserDefaultsController * type *MUST* be declared using #property in your header, and accessors must be provided (typically using #synthesize in the implementation section).
defaultsController = [[NSUserDefaultsController alloc] initWithDefaults: screenSaverDefaults initialValues: nil]; // <- change nil to factory defaults, if desired
// Make sure changes are only saved when committed by user:
[defaultsController setAppliesImmediately: false];
(The above code was slightly rearranged from various methods in my own code; might have a typo or two, but the gist is correct.)
An implementation of defaultDefaults looks something like this (pardon my unconventional style):
- (NSDictionary *) defaultDefaults
{
NSString * ResourcesPath = [[self screenSaverBundle] resourcePath];
NSString * DefaultDefaultsPath = [ResourcesPath stringByAppendingPathComponent: #"DefaultDefaults.plist"];
NSDictionary * DefaultDefaults = [NSDictionary dictionaryWithContentsOfFile: DefaultDefaultsPath];
return DefaultDefaults;
}
If you want to provide a "factory reset" capability in your screen saver, you'll need to set the initialValues: argument to an "original factory values" dictionary when creating the custom user defaults controller. In response to a "Reset to Factory Defaults" button, simply send revertToInitialValues: to the controller.
Finally, please note that depending on the intended lifetime of some of the objects created in the above code, you may need to retain some of them (and release them properly later). I assume you understand this bhaller; I'm just pointing this out for the general audience.
Swift Version:
First, register default datas.
let defaults = ScreenSaverDefaults.init(forModuleWithName: "com.your.xxxx" ?? "")!
defaults.register(defaults: ["isShowSecond":true,
"userThemeIndex":0,
"userClockIndex":0
])
You can set value:
defaults.set(false, forKey: "isShowSecond")
You can get value:
let defaults = ScreenSaverDefaults.init(forModuleWithName: "com.your.xxxx" ?? "")!
let userClockIndex = defaults.bool(forKey: "isShowSecond")
In an app I'm developing, a database is used to store all user data, usually located somewhere in Library/Application Support. In order to enable the user to switch the database, I want to implement a functionality similar to iTunes or iPhoto, where the app asks for the library's or database's location if the option key is pressed upon starting the app.
How can I check the currently pressed (modifier) keys if no NSEvent is at hand?
I already tried:
[NSResponder flagsChanged:(NSEvent *)theEvent] – Probably not called because the option key is already down before the window and any responders are instantiated.
[[NSApplication sharedApplication] currentEvent] – Returns nil.
Put this in applicationDidFinishLaunching:
NSUInteger modifiers = ([NSEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask);
if (modifiers == NSAlternateKeyMask) {
// do your stuff here
}
Beginning with macOS 10.12, this should be:
NSUInteger modifiers = ([NSEvent modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask);
if (modifiers == NSEventModifierFlagOption) {
// do your stuff here
}
Swift 5 variant:
func applicationDidFinishLaunch(_ aNotification: Notification?) {
if NSEvent.modifierFlags.intersection(.deviceIndependentFlagsMask) == .option {
// Your code
}
}
I'm writing a "UIElement" app that shows a status window on the side of the screen, similar to the Dock.
Now, when a program takes over the entire screen, I need to hide my status window, just like the Dock does.
What are my options to detect this and the inverse event?
I like to avoid polling via a timed event and also cannot use undocumented tricks (such as suggested here)
What doesn't work:
Registering a Carbon Event Handler for the kEventAppSystemUIModeChanged event isn't sufficient - it works to detect VLC's full screen mode, but not for modern Cocoa apps that use the new fullscreen widget at the top right corner of their windows.
Similarly, following Apple's instructions about the NSApplication presentationOptions API by observing changes to the currentSystemPresentationOptions property does not help, either - again, it only informs about VLC's fullscreen mode, but not about apps using the window' top right fullscreen widget.
Monitoring changes to the screen configuration using CGDisplayRegisterReconfigurationCallback is not working because there aren't any callbacks for these fullscreen modes.
Based on #Chuck's suggestion, I've come up with a solution that works somewhat, but may not be foolproof.
The solution is based on the assumption that 10.7's new fullscreen mode for windows moves these windows to a new Screen Space. Therefore, we subscribe to notifications for changes to the active space. In that notification handler, we check the window list to detect whether the menubar is included. If it is not, it probably means that we're in a fullscreen space.
Checking for the presence of the "Menubar" window is the best test I could come up with based on Chuck's idea. I don't like it too much, though, because it makes assumptions on the naming and presence of internally managed windows.
Here's the test code that goes inside AppDelegate.m, which also includes the test for the other app-wide fullscreen mode:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSApplication *app = [NSApplication sharedApplication];
// Observe full screen mode from apps setting SystemUIMode
// or invoking 'setPresentationOptions'
[app addObserver:self
forKeyPath:#"currentSystemPresentationOptions"
options:NSKeyValueObservingOptionNew
context:NULL];
// Observe full screen mode from apps using a separate space
// (i.e. those providing the fullscreen widget at the right
// of their window title bar).
[[[NSWorkspace sharedWorkspace] notificationCenter]
addObserverForName:NSWorkspaceActiveSpaceDidChangeNotification
object:NULL queue:NULL
usingBlock:^(NSNotification *note)
{
// The active space changed.
// Now we need to detect if this is a fullscreen space.
// Let's look at the windows...
NSArray *windows = CFBridgingRelease(CGWindowListCopyWindowInfo
(kCGWindowListOptionOnScreenOnly, kCGNullWindowID));
//NSLog(#"active space change: %#", windows);
// We detect full screen spaces by checking if there's a menubar
// in the window list.
// If not, we assume it's in fullscreen mode.
BOOL hasMenubar = NO;
for (NSDictionary *d in windows) {
if ([d[#"kCGWindowOwnerName"] isEqualToString:#"Window Server"]
&& [d[#"kCGWindowName"] isEqualToString:#"Menubar"]) {
hasMenubar = YES;
break;
}
}
NSLog(#"fullscreen: %#", hasMenubar ? #"No" : #"Yes");
}
];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqual:#"currentSystemPresentationOptions"]) {
NSLog(#"currentSystemPresentationOptions: %#", [change objectForKey:NSKeyValueChangeNewKey]); // a value of 4 indicates fullscreen mode
}
}
Since my earlier answer doesn't work for detecting full screen mode between apps, I did some experimentation. Starting with the solution that Thomas Tempelmann came up with of checking the presence of menu bar, I found a variation that I think could be more reliable.
The problem with checking for the menu bar is that in full screen mode you can move the mouse cursor to the top of the screen to make the menu bar appear, but you're still in full screen mode. I did some crawling through the CGWindow info, and discovered that when I enter full screen, there is window named "Fullscreen Backdrop" owned by the "Dock", and it's not there when not in full screen mode.
This is on Catalina (10.15.6) in an Xcode playground, so it should be tested in a real app, and on Big Sur (or whatever the current OS is, when you're reading this).
Here's the code (in Swift... easier to quickly test something)
func isFullScreen() -> Bool
{
guard let windows = CGWindowListCopyWindowInfo(.optionOnScreenOnly, kCGNullWindowID) else {
return false
}
for window in windows as NSArray
{
guard let winInfo = window as? NSDictionary else { continue }
if winInfo["kCGWindowOwnerName"] as? String == "Dock",
winInfo["kCGWindowName"] as? String == "Fullscreen Backdrop"
{
return true
}
}
return false
}
EDIT NOTE: This answer unfortunately doesn't provide a solution for detecting full screen in a different app, which is what the OP was asking. I'm leaving it because it does answer the question for detecting in in the same app going full screen - for example in a generic library that needs to know to automatically update keyEquivalents and title for an explicitly added "Enter Full Screen" menu item rather than Apple's automatically added menu item.
Although this question is quite old now, I've had to detect full screen mode in Swift recently. While it's not as simple as querying some flag in NSWindow, as we would hope for, there is an easy and reliable solution that has been available since macOS 10.7.
Whenever a window is about to enter full screen mode NSWindow sends a willEnterFullScreenNotification notification, and when it is about to exit full screen mode, it sends willExitFullScreenNotification. So you add an observer for those notifications. In the following code, I use them to set a global boolean flag.
import Cocoa
/*
Since notification closures can be run concurrently, we need to guard against
races on the Boolean flag. We could use DispatchSemaphore, but it's kind
over-kill for guarding a simple read/write to a boolean variable.
os_unfair_lock is appropriate for nanosecond-level contention. If the wait
could be milliseconds or longer, DispatchSemaphore is the thing to use.
This extension is just to make using it easier and safer to use.
*/
extension os_unfair_lock
{
mutating func withLock<R>(block: () throws -> R) rethrows -> R
{
os_unfair_lock_lock(&self)
defer { os_unfair_lock_unlock(&self) }
return try block()
}
}
fileprivate var fullScreenLock = os_unfair_lock()
public fileprivate(set) var isFullScreen: Bool = false
// Call this function in the app delegate's applicationDidFinishLaunching method
func initializeFullScreenDetection()
{
_ = NotificationCenter.default.addObserver(
forName: NSWindow.willEnterFullScreenNotification,
object: nil,
queue: nil)
{ _ in
fullScreenLock.withLock { isFullScreen = true }
}
_ = NotificationCenter.default.addObserver(
forName: NSWindow.willExitFullScreenNotification,
object: nil,
queue: nil)
{ _ in
fullScreenLock.withLock { isFullScreen = false }
}
}
Since observer closures can be run concurrently, I use os_unfair_lock to guard races on the _isFullScreen property. You could use DispatchSemaphore, though it's a bit heavy weight for just guarding a Boolean flag. Back when the question was first asked, OSSpinLock would have been the equivalent, but it's been deprecated since 10.12.
Just make sure to call initializeFullScreenDetection() in your application delegate's applicationDidFinishLaunching() method.
I am storing keyboard shortcuts represented as NSDictionary instances into user defaults and have some trouble representing “none” values. When my application starts I register some default shortcuts. Then the user may change keyboard shortcuts in application preferences. The keyboard setting widgets are bound directly to user defaults.
The user may click a special “clear” button that clears a keyboard shortcut, meaning that user does not wish to set a shorcut for given feature. When the keyboard shortcut is cleared, it’s set to nil. This is a problem, because setting the shortcut to nil in user defaults reverts the value to the one registered with registerDefaults:. (The objectForKey: method does not find given key in the app domain and therefore goes into the NSRegistrationDomain domain.)
How should I represent the “cleared” keyboard shortcuts so that objectForKey: returns nil? Do I have to play games with empty NSDictionary instances and other sentinel values, or is there a better way?
I ended up using this transformer:
#interface NullTransformer : NSValueTransformer {}
#end
#implementation NullTransformer
+ (BOOL) allowsReverseTransformation {
return YES;
}
- (id) transformedValue: (id) value {
return [value count] ? value : nil;
}
- (id) reverseTransformedValue: (id) value {
return value ? value : [NSDictionary dictionary];
}
#end
In other words, I use an empty dictionary to represent unset values and convert between this special value and nil using a custom transformer so that my classes don’t have to care. It’s not perfect, but seems like a tolerable solution.
I'm integrating a GTK# application into Mac OS X. GTK on Mac OS X is a wrapper over some Cocoa and Carbon fundamentals. We have some platform-specific stuff directly using Carbon global menu APIs (it's more low-level and flexible than Cocoa, and we don't need to be 64-bit).
It seems that GTK swallows up all the keyboard events before Carbon dispatches them as commands. This makes sense, because there is no mapping of Carbon commands into the GTK world. In general, this isn't a problem, because we have a global key event handler and dispatch everything via our own command system. However, this seems to be preventing Cmd-? from opening the Help search menu, and I cannot find a way to do this programmatically.
Menu Manager's MenuSelect function is promising, but I haven't figured out a way to determine the coordinate automatically, and for some reason it only works when I hit the combination twice...
Alternatively, a way to dispatch the Cmd-? keystroke to Carbon's command handling or synthesize the command event directly would be good, but I haven't had any luck in that area.
Carbon's ProcessHICommand isn't any use without a command ID and I can't figure out what it is (if there is one)
Regarding Cocoa, I can get hold of the NSWindow and call InterpretKeyEvents, but I haven't had any luck success synthesizing the NSEvent - it just beeps. The event I'm using is
var evt = NSEvent.KeyEvent (NSEventType.KeyDown, System.Drawing.PointF.Empty,
NSEventModifierMask.CommandKeyMask | NSEventModifierMask.ShiftKeyMask,
0, win.WindowNumber, NSGraphicsContext.CurrentContext, "?", "?",
false, (ushort) keycode);
Keycode is determined from a GTK keymap to be 44. I confirmed that the keycode was correct using a plain MonoMac (Cocoa) app but InterpretKeyEvents did not work with the event in that app either. And I can't find any selector associated with the command.
You can use accessibility APIs to fake a press on the menu item.
NSString *helpMenuTitle = [[[[NSApplication sharedApplication] mainMenu] itemWithTag:HELP_MENU_TAG] title];
AXUIElementRef appElement = AXUIElementCreateApplication(getpid());
AXUIElementRef menuBar;
AXError error = AXUIElementCopyAttributeValue(appElement,
kAXMenuBarAttribute,
(CFTypeRef *)&menuBar);
if (error) {
return;
}
CFIndex count = -1;
error = AXUIElementGetAttributeValueCount(menuBar, kAXChildrenAttribute, &count);
if (error) {
CFRelease(menuBar);
return;
}
NSArray *children = nil;
error = AXUIElementCopyAttributeValues(menuBar, kAXChildrenAttribute, 0, count, (CFArrayRef *)&children);
if (error) {
CFRelease(menuBar);
return;
}
for (id child in children) {
AXUIElementRef element = (AXUIElementRef)child;
id title;
AXError error = AXUIElementCopyAttributeValue(element,
kAXTitleAttribute,
(CFTypeRef *)&title);
if ([title isEqualToString:helpMenuTitle]) {
AXUIElementPerformAction(element, kAXPressAction);
CFRelease(title);
break;
}
CFRelease(title);
}
CFRelease(menuBar);
[children release];
You could do this via calling from C / Objective-C a AppleScript (GUI) script , that would essentially do the pointing and clicking of a user just as a user would do, to open the help menu program-matically.