NSRunningApplication: Bring all windows to front - swift

How can I force all windows of an NSRunningApplication to come to the foreground? The docs say this should work:
app.activate(options: .activateAllWindows)
But .activateAllWindows seems to have no effect - only the key window comes forward (macOS 12.2).
The Dock manages to achieve this behavior, so I inspected its log and found that it sends a rapp (reopen) AppleEvent to the target app. I tried replicating this event as below:
let app = NSWorkspace.shared.runningApplications.first(where: {$0.bundleIdentifier == "com.apple.Terminal"})!
var pid = app.processIdentifier
let target = NSAppleEventDescriptor(descriptorType: typeKernelProcessID, bytes: &pid, length: MemoryLayout<pid_t>.size)
let event = NSAppleEventDescriptor(eventClass: kCoreEventClass, eventID: kAEReopenApplication, targetDescriptor: target, returnID: AEReturnID(kAutoGenerateReturnID), transactionID: AETransactionID(kAnyTransactionID))
try! event.sendEvent(timeout: TimeInterval(kAEDefaultTimeout))
But no luck. Terminal logs that it receives the event, but no windows are brought forward.
Any other options?

Related

MacOS Quartz Event Tap listening to wrong events

I am trying to intercept mouse move events using the CGEvent.tapCreate(tap:place:options:eventsOfInterest:callback:userInfo:) method as shown below:
let cfMachPort = CGEvent.tapCreate(tap: CGEventTapLocation.cghidEventTap,
place: CGEventTapPlacement.headInsertEventTap,
options: CGEventTapOptions.defaultTap,
eventsOfInterest:CGEventMask(CGEventType.mouseMoved.rawValue),
callback: {(eventTapProxy, eventType, event, mutablePointer) -> Unmanaged<CGEvent>? in event
print(event.type.rawValue) //Breakpoint
return nil
}, userInfo: nil)
let runloopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, cfMachPort!, 0)
let runLoop = RunLoop.current
let cfRunLoop = runLoop.getCFRunLoop()
CFRunLoopAddSource(cfRunLoop, runloopSource, CFRunLoopMode.defaultMode)
I pass as event type eventsOfInterest mouseMoved events with a raw value of 5 as seen in the documentation. But for some reason my print() is not executed unless I click with the mouse. Inspecting the send mouse event in the debugger gives me a raw value of 2, which according to the documentation is a leftMouseUp event.
In the documentation for CGEvent.tapCreate(tap:place:options:eventsOfInterest:callback:userInfo:) it says:
Event taps receive key up and key down events [...]
So it seems like the method ignores mouseMoved events in general?! But how am I supposed to listen to mouseMoved events? I am trying to prevent my cursor (custom cursor) from being replaced (for example when I hover over the application dock at the bottom of the screen).
You need to bitshift the CGEventType value used to create the CGEventMask parameter. In Objective-C, there is a macro to do this: CGEventMaskBit.
From the CGEventMask documentation:
to form the bit mask, use the CGEventMaskBit macro to convert each constant into an event mask and then OR the individual masks together
I don't know the equivalent mechanism in swift; but the macro itself looks like this:
*/ #define CGEventMaskBit(eventType) ((CGEventMask)1 << (eventType))
In your example, it's sufficient to just manually shift the argument; e.g.
eventsOfInterest:CGEventMask(1 << CGEventType.mouseMoved.rawValue),
I would point out that the code example given in the question is a little dangerous; as it creates a default event tap and then drops the events rather than allowing them to be processed. This messes up mouse click handling and it was tricky to actually terminate the application using the mouse. Anyone running the example could set the event tap type to CGEventTapOptions.listenOnly to prevent that.
Here is a way to listen for mouseMove global events (tested with Xcode 11.2+, macOS 10.15)
// ... say in AppDelegate
var globalObserver: Any!
var localObserver: Any!
func applicationDidFinishLaunching(_ aNotification: Notification) {
globalObserver = NSEvent.addGlobalMonitorForEvents(matching: .mouseMoved) { event in
let location = event.locationInWindow
print("in background: {\(location.x), \(location.y)}")
}
localObserver = NSEvent.addLocalMonitorForEvents(matching: .mouseMoved) { event in
let location = event.locationInWindow
print("active: {\(location.x), \(location.y)}")
return event
}
...
There's another thing incorrect in your code, although you might be lucky and it isn't normally causing a problem.
As documented for the mode parameter to CFRunLoopAddSource: "Use the constant kCFRunLoopCommonModes to add source to the set of objects monitored by all the common modes."
That third parameter should instead be CFRunLoopMode.commonModes.
What you have, CFRunLoopMode.defaultMode aka kCFRunLoopDefaultMode, is instead for use when calling CFRunLoopRun.

GtkAppChooser Content type for all applications and/or audio mixers

I'd like to have a GtkAppChooserButton which allows the user to select a program to run which will most likely want to be an audio mixer such as pavucontrol. Despite vague documentation on the matter I gather the app chooser's content type is meant to be a MIME type, however I cannot find a suitable MIME type for an audio mixer, or more generally just "all applications".
Some types such as application/ will give two Other Application... options if Other... items are enabled, both of which are identical and neither of which contain half the applications I have, including any audio mixers. Aside from that, nothing else I do gets me remotely close to what I'm after.
Is there a MIME type and/or GtkAppChooser content type (they seem to be the same thing?) for audio mixers or just all programs in general? (I.e. Any program that would have an icon in the likes of the Gnome app launcher/xfce4-whisker-menu/etc.)
Alright so I've come up with a solution, thought it may not be as clean as you hoped.
This thread mentioned a way to get the GtkAppChooser to "show all", but it doesn't actually show all applications you have installed. However from that I was able to work out how the GtkAppChooser is using Gio.AppInfo, which has Gio.AppInfo.get_all() (this is for PyObject) which returns a full list of Gio.AppInfos for all applications I have installed, provided they have a .desktop file.
So, my "solution" is to write my own app chooser which gets a list of apps from Gio.AppInfo.get_all().
I've cleaned up my previous solution to this and written a class 'AllAppChooser' to inherrit Gtk.Dialog, giving much greater customisation. The completed dialog looks like so:
And the code used:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio
class AllAppChooser(Gtk.Dialog):
"""Provide a dialog to select an app from all those installed.
The regular Gtk.AppChooserDialog does not seem to provide any way to allow
selection from all installed apps, so this dialog serves as a replacement.
"""
def __init__(self, parent=None):
super().__init__(self)
self.set_default_size(350, 400)
self.set_icon_name('gtk-search')
self.set_title('App Chooser')
if parent:
self.set_parent(parent)
self.content_box = self.get_content_area()
self.content_box.set_margin_left(8)
self.content_box.set_margin_right(8)
self.content_box.set_margin_top(8)
self.content_box.set_margin_bottom(8)
self.content_box.set_spacing(8)
self.button_box = self.get_action_area()
self.button_box.set_margin_left(4)
self.button_box.set_margin_right(4)
self.button_box.set_margin_top(4)
self.button_box.set_margin_bottom(4)
self.label = Gtk.Label('Choose An Application')
self.content_box.pack_start(self.label, False, False, 0)
self.list_store = Gtk.ListStore(str, str, int)
pixbuf_renderer = Gtk.CellRendererPixbuf()
text_renderer = Gtk.CellRendererText()
icon_column = Gtk.TreeViewColumn('icon', pixbuf_renderer, icon_name=1)
text_column = Gtk.TreeViewColumn('text', text_renderer, text=0)
self.tree_view = Gtk.TreeView()
self.tree_view.set_model(self.list_store)
self.tree_view.set_headers_visible(False)
self.tree_view.append_column(icon_column)
self.tree_view.append_column(text_column)
self.view_port = Gtk.Viewport()
self.view_port.add(self.tree_view)
self.scroll_window = Gtk.ScrolledWindow()
self.scroll_window.add(self.view_port)
self.content_box.pack_start(self.scroll_window, True, True, 0)
self.ok_button = self.add_button(Gtk.STOCK_OK, 1)
self.ok_button.connect('clicked', self.on_ok)
self.cancel_button = self.add_button(Gtk.STOCK_CANCEL, 0)
self.selected_app = None
self.app_list = []
def populate_app_list(self):
"""Populate the list of apps with all installed apps.
Icons are provided by icon-name, however some apps may return a full
path to a custom icon rather than a themed-icon name, or even no name
at all. In these cases the generic 'gtk-missing-icon' icon is used.
"""
self.app_list = Gio.AppInfo.get_all()
for i in range(len(self.app_list)):
gio_icon = self.app_list[i].get_icon()
app_icon = 'gtk-missing-icon'
if gio_icon:
app_icon = gio_icon.to_string()
app_name = self.app_list[i].get_display_name()
self.list_store.append([app_name, app_icon, i])
self.list_store.set_sort_column_id(0, Gtk.SortType.ASCENDING)
def run(self):
"""Run the dialog to get a selected app."""
self.populate_app_list()
self.show_all()
super().run()
self.destroy()
return self.selected_app
def set_label(self, text):
"""Set the label text, \"Choose An App\" by default."""
self.label.set_text(text)
def on_ok(self, button):
"""Get Gio.AppInfo of selected app when user presses OK."""
selection = self.tree_view.get_selection()
tree_model, tree_iter = selection.get_selected()
app_index = tree_model.get_value(tree_iter, 2)
self.selected_app = self.app_list[app_index]
This is then run similar to a regular dialog:
app_chooser = AllAppChooser()
application = app_chooser.run()
If the user exits the dialog box or presses cancel then the result will be None, but if they selected an application then run() will return a Gio.AppInfo object for the application which you can then do with as you please. For example, to launch your newly selected application:
application.launch()
I feel this is now a relatively solid solution, but I still welcome additional suggestions. Additionally, if there is a way to do this in Gtk without having to do all this stuff I would still love to hear it.

Capture Function keys on Mac OS X

I'm a long-time Objective-C user and slowly migrating towards Swift with new projects. I'm using CocoaPods for the bigger things and can't find a good library to cover this.
So I have this code inside my NSViewController viewDidLoad to start with:
_ = NSEvent.addGlobalMonitorForEventsMatchingMask(NSEventMask.KeyDownMask) {
(event) -> Void in
print("Event is \(event)")
}
let event = NSEvent.keyEventWithType(NSEventType.KeyDown,
location: CGPoint(x:0,y:0),
modifierFlags: NSEventModifierFlags(rawValue: 0),
timestamp: 0.0,
windowNumber: 0,
context: nil,
characters: "\n",
charactersIgnoringModifiers: "",
isARepeat: false,
keyCode: 0) //NSF7FunctionKey
NSApplication.sharedApplication().sendEvent(event!)
So the first event capturing works perfect after having my app checked in the System Preferences' Accessibility list. Anywhere in OS X it will capture key-presses. Now in the docs it says for Function-keys I should use keyEventWithType.
I found this gist and noticed that it addresses the same sharedApplication instance, yet I don't know how to catch the event. Do I delegate in a certain way? Also the F-key constant is int and the method says it only wants to receive Uint16. I can typecast it, but I guess I'm using it wrong.
Fixed it by using a CocoaPods pod that I found later. Works perfectly.

Open a new window and get its location after loading

I am using this code to open a new window:
Window w = window.open('example2.com', 'example2'); // Consisder than my domain
// is example1.com
This part of code works ok and succesfully opens a new window. Than i am trying to call my function after loading finishes.
w.onLoad.listen(locationGetter);
This is a code of locationGetter() function:
locationGetter(Event e) {
Location currentLocation = w.location;
currentHref = currentLocation.href; // currentHref is var defined
} // in main() function
But this code doesn't work well. Every time when I run my script both currentLocation and currentHref is null. At the beginning i thought that problem was in onLoad event, so i tried to call w.location exacly after opening a window:
Window w = window.open('example2.com', 'example2');
Location currentLocation = w.location; // still null
I am pretty sure that both Window and WindowBase has location property. Please help me with my problem or provide alternative solution of this task.
I don't think this is a Dart issue.
Seems you run into this
http://blog.carbonfive.com/2012/08/17/cross-domain-browser-window-messaging-with-html5-and-javascript/
CORS prevents accessing windows of other domains, you can use postMessage instead.

Automatically open the Safari Debugger when the iPhone Simulator is launched

The iOS web debugger in Safari is the bee's knees, but it closes every time the Simulator is restarted. Not only is it annoying to re-open it from the menu after every build, but it makes it tricky to debug any behavior that happens during startup.
Is there a way to set up a trigger in Xcode to automatically open the Safari debugger after every build, or perhaps a way to build a shell script or Automator action to do a build and immediately open the debugger?
This is a partial solution. This opens the debug window of Safari with one click which is a lot better but not automatic.
Open Script Editor on your mac (Command + Space Bar and type in Script Editor)
Paste in this code:
-- `menu_click`, by Jacob Rus, September 2006
--
-- Accepts a list of form: `{"Finder", "View", "Arrange By", "Date"}`
-- Execute the specified menu item. In this case, assuming the Finder
-- is the active application, arranging the frontmost folder by date.
on menu_click(mList)
local appName, topMenu, r
-- Validate our input
if mList's length < 3 then error "Menu list is not long enough"
-- Set these variables for clarity and brevity later on
set {appName, topMenu} to (items 1 through 2 of mList)
set r to (items 3 through (mList's length) of mList)
-- This overly-long line calls the menu_recurse function with
-- two arguments: r, and a reference to the top-level menu
tell application "System Events" to my menu_click_recurse(r, ((process appName)'s ¬
(menu bar 1)'s (menu bar item topMenu)'s (menu topMenu)))
end menu_click
on menu_click_recurse(mList, parentObject)
local f, r
-- `f` = first item, `r` = rest of items
set f to item 1 of mList
if mList's length > 1 then set r to (items 2 through (mList's length) of mList)
-- either actually click the menu item, or recurse again
tell application "System Events"
if mList's length is 1 then
click parentObject's menu item f
else
my menu_click_recurse(r, (parentObject's (menu item f)'s (menu f)))
end if
end tell
end menu_click_recurse
menu_click({"Safari", "Develop", "Simulator", "index.html"})
Once the simulator has opened, click run on your script (you might need to allow the script editor in the settings the first time).
(Optional) You can save your the scripts as an app so that you don't have to have the script editor open.
There is question that should be marked a duplicate that describes using setTimeout() to give you enough time to switch windows over to Safari and set a breakpoint.
Something like this, where startMyApp is the bootstrap function of your app:
setTimeout(function () {
startMyApp();
}, 20000);
It is super ghetto, but does work. I've submitted a feature request via http://www.apple.com/feedback/safari.html too to close the loop.
Extending upon the #Prisoner's answer, if you use WKWebView you could:
let contentController:WKUserContentController = WKUserContentController()
let pauseForDebugScript = WKUserScript(source: "window.alert(\"Go, turn on Inspector, I'll hold them back!\")",
injectionTime: WKUserScriptInjectionTime.AtDocumentStart,
forMainFrameOnly: true)
contentController.addUserScript(pauseForDebugScript)
let config = WKWebViewConfiguration()
config.userContentController = contentController
//Init browser with configuration (our injected script)
browser = WKWebView(frame: CGRect(x:0, y:0, width: view.frame.width, height: containerView.frame.height), configuration:config)
also important thing is to implement alert handler from WKUIDelegate protocol
//MARK: WKUIDelegate
func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String,
initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void) {
let alertController = UIAlertController(title: message, message: nil,
preferredStyle: UIAlertControllerStyle.Alert);
alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel) {
_ in completionHandler()}
);
self.presentViewController(alertController, animated: true, completion: {});
}
and one little thing just in case you could have an UIAlertController:supportedInterfaceOrientations was invoked recursively error add following extension (From this SO Answer)
extension UIAlertController {
public override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Portrait
}
public override func shouldAutorotate() -> Bool {
return false
}
}
Combining Tom's answer with my solution, first do the following:
Create an application as Tom describes above
In "System Preferences -> Security & Privacy -> Privacy -> Accessibility" add your new Script Applicaiton and make sure it is allowed to control your computer
Now, I'm using cordova and from the command line the following builds, runs emulator and opens safari debug console:
cordova build ios; cordova emulate ios; open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app; sleep 1 ; open /PATH/TO/OpenDevelop.app/
Make sure to replace /PATH/TO/ with the appropriate path to where you saved your script.
Humm,I don't think you can do that,but you can just "CTRL+R" in the web debugger.