Swift - how to make window unactivable? - swift

I want to make application unactivable.
I mean its must be inactive and non-foreground even by click on it. Active window must be the same app as it was before click on my app.
How can I do this?
upd:
Let's imagine some window/panel. Like a Dock. Let's call it "Docky"
Docky is inactive, Active window is Safari:
NSRunningApplicationSafari.isActive == true
NSRunningApplicationDocky.isActive == false
I'm clicking on ANY empty point of Docky.
Expected result:
Safari is still active window. Like before click:
NSRunningApplicationSafari.isActive == true
NSRunningApplicationDocky.isActive == false
Actual result:
Docky is active window, Safari is inactive:
NSRunningApplicationSafari.isActive == false
NSRunningApplicationDocky.isActive == true
Another sample is Keyboard Viewer. You are clicking on the virtual keyboard, but active window is another app. Ant exactly active window getting keyPress events.

Actually all you need is .nonactivatingPanel style panel. Everything else is details, like level of this window, custom views with overridden acceptsFirstMouse:, needsPanelToBecomeKey, etc. Btw, button accepts first click by default, non activating app in this case.
So your AppDelegate, for example, might look like the following:
class AppDelegate: NSObject, NSApplicationDelegate {
var docky: NSPanel!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the window and set the content view.
docky = NSPanel(
contentRect: NSRect(x: 0, y: 0, width: 120, height: 600),
styleMask: [.nonactivatingPanel],
backing: .buffered, defer: false)
docky.level = .mainMenu
....
docky.orderFrontRegardless()
}

Related

Draw an NSWindow over the entire screen, from corner to corner, including the menu bar and dock

The bounty expires in 2 days. Answers to this question are eligible for a +50 reputation bounty.
JonLuca wants to draw more attention to this question.
I want to draw an NSWindow for an overlay application that completely covers the users screen - from corner to corner. I've got a borderloss, non activating panel that takes over the entire page.
import Foundation
import AppKit
import SwiftUI
class FullScreenPanel: NSPanel {
override func constrainFrameRect(_ frameRect: NSRect, to screen: NSScreen?) -> NSRect {
return frameRect
}
}
final class Panel: FullScreenPanel, NSWindowDelegate {
init(contentRect: NSRect, backing: NSWindow.BackingStoreType, defer flag: Bool) {
super.init(
contentRect: contentRect,
styleMask: [.borderless, .nonactivatingPanel],
backing: backing,
defer: flag
)
self.level = .mainMenu + 3
self.collectionBehavior.insert(.fullScreenAuxiliary) // Allows the panel to appear in a fullscreen space
self.collectionBehavior.insert(.canJoinAllSpaces)
self.titleVisibility = .hidden
self.titlebarAppearsTransparent = true
self.isMovable = false
self.isMovableByWindowBackground = false
self.isReleasedWhenClosed = false
self.isOpaque = false
self.delegate = self
}
func windowDidResignKey(_ notification: Notification) {
DispatchQueue.main.async {
appDelegate?.hideWindow()
}
}
}
I'm instantiating this with the NSScreen.main.frame CGRect
mainWindow = Panel(
contentRect: NSScreen.main!.frame,
backing: .buffered, defer: false)
However, when the window shows up, it still shows up under the menu bar. The constrainFrameRect function shows that somewhere internally the y value of the frame goes from 0 to to -44.
The window should also not trigger the native fullscreen effect, where it becomes a new "Desktop" that you can swipe between.
I think that you use NSPanel class incorrectly. Official Documentation for NSPanel:
NSPanel
A special kind of window that typically performs a function that is auxiliary to the main window.
Your window is probably main (Because it takes up the whole screen and is the only visible one), so the NSPanel is not necessary, just use generic NSWindow.
Use an NSWindowController for better code organisation.
Use
NSApplication.shared.presentationOptions = [.hideDock, .hideMenuBar]
to hide the dock and menu bar completely and
NSApplication.shared.presentationOptions = [.autoHideDock, .autoHideMenuBar]
to make them appear when you hover over the position where they have been by default.
Warning! This code might block your whole screen. Consider adding an exit button or a shortcut (In the current version you can use Cmd + Tab to focus on another window). In the worst case you must reboot your computer by holding down the power button.
Code:
class FullScreenWindowController: NSWindowController {
let viewController = FullScreenViewController()
init() {
super.init(window: NSWindow(contentViewController: viewController))
// Remove the window header
window?.styleMask = .borderless
window?.setFrame(window!.screen!.frame, display: true)
// Dock and Menu Bar are completely inaccessible
// NSApplication.shared.presentationOptions = [.hideDock, .hideMenuBar]
// Dock and Menu Bar will automatically hide when not needed
NSApplication.shared.presentationOptions = [.autoHideDock, .autoHideMenuBar]
window?.makeKeyAndOrderFront(self)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
To use the above class, just create an instance of it:
let fullScreenWindowController = FullScreenWindowController()
Also I used this view controller:
class FullScreenViewController: NSViewController {
override func loadView() {
let label = NSTextField(labelWithString: "Full Screen Window")
label.font = .systemFont(ofSize: 48)
view = label
}
}
OK I'm pretty noob in this however you could try the below (please don't flame if not working, it's just a suggestion lol I can delete the answer given if you hate it)
final class Panel: FullScreenPanel, NSWindowDelegate {
init(contentRect: NSRect, backing: NSWindow.BackingStoreType, defer flag: Bool) {
let menuBarHeight = NSStatusBar.system.thickness
let adjustedContentRect = NSRect(x: contentRect.origin.x,
y: contentRect.origin.y + menuBarHeight,
width: contentRect.size.width,
height: contentRect.size.height - menuBarHeight)
super.init(
contentRect: adjustedContentRect,
styleMask: [.borderless, .nonactivatingPanel],
backing: backing,
defer: flag
)
// Rest of your code...
}
// Rest of your code...
}

NSPanel not hiding when focus is lost

I am trying to create a window like Spotlight.
As in Spotlight it should hide when the background is clicked. I tried doing it unsuccessfully with NSWindow but I was lead to believe using NSPanel instead would solve the problem.
However, even when using NSPanel the window does not hide.
Here is the code I'm using.
let panel = NSPanel(contentRect: CGRect(x: 0, y: 0, width: 200, height: 200), styleMask: [.titled, .nonactivatingPanel], backing: .buffered, defer: true)
panel.level = .mainMenu
panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
panel.orderFrontRegardless()
It is due to used window level (.mainMenu which is above all windows), so you need to hide it explicitly via delegate methods
so assuming you create window/panel in your controller, make that controller a delegate of window
panel.delegate = self
and implement something like
extension ViewController { // << your controller class here
func windowDidResignKey(_ notification: Notification) {
if let panel = notification.object as? NSWindow {
panel.close()
}
}
}

MacOS SwiftUI, how to prevent window close action

So I have a swiftUI application, at some point I create a NSWindow and assign the contentView, like this:
// ////////////////////////////////////////////////////////
// Add token window
// ////////////////////////////////////////////////////////
let configurationView = ConfigurationView().environmentObject(store)
configurationWindow = NSWindow(
contentRect: NSRect(x:0, y: 0, width: 480, height: 500),
styleMask: [.titled, .closable, .miniaturizable, .fullSizeContentView],
backing: .buffered, defer: false
)
configurationWindow.center()
configurationWindow.setFrameAutosaveName("BSchauer")
let hostingController = NSHostingController(rootView: configurationView)
configurationWindow.contentViewController = hostingController
configurationWindow.makeKeyAndOrderFront(nil)
configurationWindow.setIsVisible(false)
...
// later on in the code
#objc func toggleConfigurationWindow() {
if self.configurationWindow.isVisible {
self.configurationWindow.setIsVisible(false)
if let button = self.statusBarItem.button {
self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
} else {
self.configurationWindow.setIsVisible(true)
self.configurationWindow.contentViewController?.view.window?.becomeKey()
}
}
You see that the way I interact with the window to present it to the user is via the visible flag, now the problem is when the window is shown and closed via the close button on the top bar, the window get somehow unmounted(?) and the next time the user tries to interact with the app and re-open the window I get a segmentation fault.
One of the things I tried was to instead of setting the visibility to false, just re-create the window again, but I still get the segmentation error.
All the previous answers I found are dealing with the old way of dealing with the NSViewController and overriding the windowShouldClose method, but I cannot seem to get that working.
TL:DR: When the user presses the red close button on the window, instead of the window getting destroyed I just want to set its visibility to false
I made it work, no need to set the contentViewController, you can use the standard contentView:
configurationWindow.contentView = NSHostingView(rootView: configurationView)
and to disable the window getting released when closed:
configurationWindow.isReleasedWhenClosed = false
I would still be interested in knowing when the window closed, to maybe perform an action afterwards, but this still solves my problem

CGDisplayCreateImage only returns wallpaper and task bar

I am trying to get the pixel colors from the screen for a specified portion of the display.
I am using CGDIsplayCreateImage(_:) to do so, but for some reason instead of creating a screenshot of the currently open windows it just gives an image of the wallpaper and the task bar.
In order to visualize the screenshot I am using a SwiftUI view. In my application I actually don't need the screenshot displayed.
struct ScreenshotView: View {
let cgImage: CGImage
var body: some View {
Image(decorative: cgImage, scale: 3)
}
}
And the applicationDidFinishLaunching(_:) methods looks like this.
func applicationDidFinishLaunching(_ aNotification: Notification) {
let contentRect = NSRect(x: 0, y: 0, width: 240, height: 240)
// Create the window and set the content view.
window = NSWindow(
contentRect: contentRect,
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: ScreenshotView(cgImage: CGDisplayCreateImage(CGMainDisplayID())!))
window.makeKeyAndOrderFront(nil)
}
Since XCode and some other apps are open it should give me a screenshot of the current screen with all open windwos. Instead I get a screenshot of the wallpaper and the taskbar showing my apps name. No apps are showing and the dock is also missing.
Am I doing something wrong or is this a bug?
In this stackoverflow answers suggest that I am using the function correctly.
Even if this might be obvious to some people I will answer my own question here using the help of Ken Thomases.
Below you can find the settings I set within my target. It now works after every rebuild.
Make sure to deactivate automatic signing and set the signing certificate to 'Sign to Run Local'.
Hope this helps someone at some point.

How do I get a titlebar to show programmatically in NSWindow?

Im trying to develop an OS X cocoa application programmatically, and I'm trying to display a window with a title bar which displays the usual traffic light (close, minimise, fullscreen) options at the top.
However, when the window displays on the screen, there is just an empty window.
Here is the code I am using:
class AppDelegate: NSObject, NSApplicationDelegate {
let window = NSWindow(contentRect: NSMakeRect(10, 10, 200, 200),
styleMask: NSWindowStyleMask.closable,
backing: NSBackingStoreType.buffered, defer: true)
func applicationDidFinishLaunching(_ aNotification: Notification) {
self.titleVisibility = .visible;
self.titlebarAppearsTransparent = false;
self.isMovableByWindowBackground = true;
let controller = NSWindowController(window: window)
controller.showWindow(self);
}
I have tried different NSWindowStyleMask when constructing the NSWindow but to no success.
This is what I see:
I am using Xcode 8.3 on 10.12
So you need 4 style masks.
NSWindowStyleMask.closable
NSWindowStyleMask.miniaturizable
NSWindowStyleMask.resizable
NSWindowStyleMask.titled
To put them all into one, you can use array
[NSWindowStyleMask.closable, NSWindowStyleMask.miniaturizable, NSWindowStyleMask.resizable, NSWindowStyleMask.titled]
But try to write in swift style
[.closable, .miniaturizable, .resizable, .titled]