Files
UniWindow-Controller/Xcode/LibUniWinC/LibUniWinC.swift
2021-12-12 09:58:28 +09:00

1210 lines
46 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Unified Window Controller macOS plugin
//
// Author: Kirurobo
// License: MIT
//
// Acknowledgement:
// This code is based on transparent.swift created by kriver1 on 2018/05/23.
// https://qiita.com/KRiver1/items/9ecf65759cf1349f56af
//
// References:
// - https://qiita.com/KRiver1/items/9ecf65759cf1349f56af
// - http://tatsudoya.blog.fc2.com/blog-entry-244.html
// - https://qiita.com/mybdesign/items/fe3e390741799c1814ad
// - https://blog.fenrir-inc.com/jp/2011/07/nsview_uiview.html
//
import Foundation
import Cocoa
/// Window controller main logic
@objcMembers
public class LibUniWinC : NSObject {
// MARK: - Internal structs and classes
///
private struct State {
public var isReady: Bool = false
public var isTopmost: Bool = false
public var isBottommost: Bool = false
public var isBorderless: Bool = false
public var isTransparent: Bool = false
public var alphaValue: Float32 = 1
// 使
public var isZoomed: Bool = false
// Keep unzoomed size for the borderless window
public var normalWindowRect: NSRect = NSRect(x: 0, y: 0, width: 0, height: 0)
}
/// Event types for WindowStyleChanged
private enum EventType : Int32 {
case None = 0
case Style = 1
case Size = 2
case Order = 4
}
/// Flag constants for file dialog
public enum PanelFlag : Int32 {
case None = 0
case FileMustExist = 1
case FolderMustExist = 2
case AllowMultipleSelection = 4
//case CanCreateDirectories = 16
case OverwritePrompt = 256
case CreatePrompt = 512
case ShowHidden = 4096
case RetrieveLink = 8192
public func containedIn(value: Int32) -> Bool {
return (self.rawValue & value > 0)
}
}
public struct PanelSettings {
public var structSize: Int32 = 0;
public var flags: Int32 = 0;
public var titleText: UnsafePointer<UniChar>?;
public var filterText: UnsafePointer<UniChar>?;
public var initialFile: UnsafePointer<UniChar>?;
public var initialDirectory: UnsafePointer<UniChar>?;
public var defaultExt: UnsafePointer<UniChar>?;
}
///
private class OriginalWindowInfo {
/// StyleMask
public var styleMask: NSWindow.StyleMask = []
/// CollectionBehavior
public var collectionBehavior: NSWindow.CollectionBehavior = []
/// Level
public var level: NSWindow.Level = NSWindow.Level.normal
public var titlebarAppearsTransparent: Bool = false
public var titleVisibility: NSWindow.TitleVisibility = NSWindow.TitleVisibility.visible
public var backgroundColor: NSColor = NSColor.clear
public var isOpaque: Bool = true
public var hasShadow: Bool = true
public var isKeyWindow: Bool = true
public var alphaValue: CGFloat = 1
public var contentViewWantsLayer: Bool = true
public var contentViewLayerIsOpaque: Bool = true
public var contentViewLayerBackgroundColor: CGColor? = CGColor.clear
///
public func Store(window: NSWindow) -> Void {
self.collectionBehavior = window.collectionBehavior
self.styleMask = window.styleMask
self.level = window.level
self.titlebarAppearsTransparent = window.titlebarAppearsTransparent
self.titleVisibility = window.titleVisibility
self.backgroundColor = window.backgroundColor
self.isOpaque = window.isOpaque
self.hasShadow = window.hasShadow
self.isKeyWindow = window.isKeyWindow
self.alphaValue = window.alphaValue
if let view = window.contentView {
self.contentViewWantsLayer = view.wantsLayer
if let layer = view.layer {
self.contentViewLayerIsOpaque = layer.isOpaque
self.contentViewLayerBackgroundColor = layer.backgroundColor
}
}
}
///
public func Restore(window: NSWindow) -> Void {
window.collectionBehavior = self.collectionBehavior
window.styleMask = self.styleMask
window.level = self.level
window.titlebarAppearsTransparent = self.titlebarAppearsTransparent
window.titleVisibility = self.titleVisibility
window.backgroundColor = self.backgroundColor
window.isOpaque = self.isOpaque
window.hasShadow = self.hasShadow
window.alphaValue = self.alphaValue
window.contentView?.wantsLayer = self.contentViewWantsLayer
window.contentView?.layer?.isOpaque = self.contentViewLayerIsOpaque
window.contentView?.layer?.backgroundColor = self.contentViewLayerBackgroundColor
}
}
// MARK: - Static variables
/// nil
private static var targetWindow: NSWindow? = nil
///
private static var state: State = State()
///
private static var orgWindowInfo: OriginalWindowInfo = OriginalWindowInfo()
/// Callback function with wchar_t pointer
public typealias stringCallback = (@convention(c) (UnsafeRawPointer) -> Void)
public typealias intCallback = (@convention(c) (Int32) -> Void)
public static var dropFilesCallback: stringCallback? = nil
public static var openFilesCallback: stringCallback? = nil
public static var saveFilesCallback: stringCallback? = nil
public static var monitorChangedCallback: intCallback? = nil
public static var windowStyleChangedCallback: intCallback? = nil
private static var observerObject: Any? = nil
/// Sub view to implement file dropping
private static var overlayView: OverlayView? = nil
///
private static var primaryMonitorHeight: CGFloat = 0
private static var monitorCount: Int = 0
private static var monitorRectangles: [CGRect] = []
private static var monitorIndices: [Int] = []
// MARK: - Properties
///
/// - Returns: true
@objc public static func isActive() -> Bool {
if (state.isReady && targetWindow == nil) {
return false
}
return true
}
@objc public static func isTransparent() -> Bool {
return state.isTransparent
}
@objc public static func isBorderless() -> Bool {
return state.isBorderless
}
@objc public static func isTopmost() -> Bool {
return state.isTopmost
}
@objc public static func isBottommost() -> Bool {
return state.isBottommost
}
@objc public static func isMaximized() -> Bool {
return state.isZoomed
//return _isZoomedActually()
}
@objc public static func isMinimized() -> Bool {
return (targetWindow?.isMiniaturized ?? false)
}
private static func _isZoomedActually() -> Bool {
if (targetWindow == nil) {
return false
} else if (targetWindow!.isMiniaturized) {
return false
} else if (state.isTransparent) {
// When the window is transparent
let monitorIndex = getCurrentMonitor()
let rect = monitorRectangles[monitorIndices[Int(monitorIndex)]]
let frame = targetWindow!.frame
return (frame.size == rect.size) && (frame.origin == rect.origin)
} else {
// When the window is opaque
return targetWindow!.isZoomed
}
}
// MARK: - Initialize, window handling
/// Initialize
private static func _setup() -> Void {
// Get the screen size
_updateScreenInfo()
// Prepare notification to refresh the screen size
NotificationCenter.default.addObserver(
forName: NSApplication.didChangeScreenParametersNotification,
object: NSApplication.shared,
queue: OperationQueue.main
) {
notification -> Void in _onMonitorChanged()
}
// Flag as initialized
state.isReady = true
}
/// Called when screen parameeters changed
private static func _onMonitorChanged() -> Void {
_updateScreenInfo()
// Run callback
let count = getMonitorCount()
monitorChangedCallback?(count)
}
/// Retrieve current monitor settings
private static func _updateScreenInfo() -> Void {
// Reference: https://stackoverrun.com/ja/q/1746184
primaryMonitorHeight = NSScreen.screens.map {$0.frame.origin.y + $0.frame.height}.max()!
// Get the number of monitors
monitorCount = NSScreen.screens.count
// Clear the list
monitorRectangles.removeAll()
monitorIndices.removeAll()
// Get each screen rectangle
for i in 0..<monitorCount {
let screen = NSScreen.screens[i]
//monitorRectangles.append(screen.visibleFrame)
monitorRectangles.append(screen.frame)
monitorIndices.append(i)
}
// Sort the list so that the top left monitor is at the zero
monitorIndices = monitorIndices.sorted(by: {
(monitorRectangles[$0].minX < monitorRectangles[$1].minX)
|| (monitorRectangles[$0].minX == monitorRectangles[$1].minX && monitorRectangles[$0].maxY < monitorRectangles[$1].maxY)
})
}
/// Find my own window
private static func _findMyWindow() -> NSWindow {
let myWindow: NSWindow = NSApp.orderedWindows.first!
return myWindow
}
/// Detach from the window
@objc public static func detachWindow() -> Void {
_detachWindow()
}
/// Attach to my main window
@objc public static func attachMyWindow() -> Bool {
let window: NSWindow = _findMyWindow()
_attachWindow(window: window)
return true
}
/// Set the target window
/// Restore the former winodw if exist
public static func _attachWindow(window: NSWindow) -> Void {
// Do nothing if the same window is the target
if (targetWindow == window) {
return
}
// Release the former window if exist
detachWindow()
// Initialize when the first call
if (!state.isReady) {
_setup()
}
// Set to the target
targetWindow = window
// Store the original state
orgWindowInfo.Store(window: window)
// Apply the state
_reapplyWindowStyles()
// Add observers for window state changed callback and reapply styles
let center = NotificationCenter.default
center.addObserver(self, selector: #selector(_fullScreenChangedObserver(notification:)), name: NSWindow.didEnterFullScreenNotification, object: window)
center.addObserver(self, selector: #selector(_fullScreenChangedObserver(notification:)), name: NSWindow.didExitFullScreenNotification, object: window)
center.addObserver(self, selector: #selector(_windowStateChangedObserver(notification:)), name: NSWindow.didMiniaturizeNotification, object: window)
center.addObserver(self, selector: #selector(_windowStateChangedObserver(notification:)), name: NSWindow.didDeminiaturizeNotification, object: window)
//center.addObserver(self, selector: #selector(_resizedObserver(notification:)), name: NSWindow.didResizeNotification, object: window)
center.addObserver(self, selector: #selector(_resizedObserver(notification:)), name: NSWindow.didEndLiveResizeNotification, object: window)
//center.addObserver(self, selector: #selector(_keepKeyWindowObserver(notification:)), name: NSWindow.didExposeNotification, object: window)
center.addObserver(self, selector: #selector(_keepKeyWindowObserver(notification:)), name: NSWindow.didResignKeyNotification, object: window)
center.addObserver(self, selector: #selector(_keepBottommostObserver(notification:)), name: NSWindow.didBecomeKeyNotification, object: window)
}
private static func _detachWindow() -> Void {
if (targetWindow != nil) {
let center = NotificationCenter.default
center.removeObserver(self, name: NSWindow.didEnterFullScreenNotification, object: targetWindow)
center.removeObserver(self, name: NSWindow.didExitFullScreenNotification, object: targetWindow)
center.removeObserver(self, name: NSWindow.didMiniaturizeNotification, object: targetWindow)
center.removeObserver(self, name: NSWindow.didDeminiaturizeNotification, object: targetWindow)
//center.removeObserver(self, name: NSWindow.didResizeNotification, object: targetWindow)
center.removeObserver(self, name: NSWindow.didEndLiveResizeNotification, object: targetWindow)
//center.removeObserver(self, name: NSWindow.didExposeNotification, object: targetWindow)
center.removeObserver(self, name: NSWindow.didResignKeyNotification, object: targetWindow)
center.removeObserver(self, name: NSWindow.didBecomeKeyNotification, object: targetWindow)
//center.removeObserver(self)
// Restore the original style
orgWindowInfo.Restore(window: targetWindow!)
// Remove the subview
if (overlayView != nil) {
overlayView?.removeFromSuperview()
overlayView = nil
}
targetWindow = nil
}
}
@objc static func _fullScreenChangedObserver(notification: Notification) {
// Reapply the state at fullscreen
_reapplyWindowStyles()
_doWindowStyleChangedCallback(num: EventType.Size)
}
@objc static func _windowStateChangedObserver(notification: Notification) {
_doWindowStyleChangedCallback(num: EventType.Size)
}
@objc static func _resizedObserver(notification: Notification) {
if (targetWindow != nil) {
let zoomed = _isZoomedActually()
if (state.isZoomed != zoomed) {
state.isZoomed = zoomed
}
_doWindowStyleChangedCallback(num: EventType.Size)
}
}
@objc static func _keepKeyWindowObserver(notification: Notification) {
if (targetWindow != nil && !state.isBottommost) {
if (orgWindowInfo.isKeyWindow && !targetWindow!.isKeyWindow) {
_makeKeyWindow()
//_doWindowStyleChangedCallback(num: EventType.Order)
}
}
}
@objc static func _keepBottommostObserver(notification: Notification) {
if ((targetWindow != nil) && state.isBottommost) {
targetWindow!.level = orgWindowInfo.level
targetWindow!.order(NSWindow.OrderingMode.below, relativeTo:0)
_doWindowStyleChangedCallback(num: EventType.Order)
}
}
/// Call this periodically to maintain window state.
@objc public static func update() {
if (targetWindow != nil) {
if (state.isTransparent) {
// Keep window transparent
if (targetWindow!.isOpaque) {
_setWindowTransparent(window: targetWindow!, isTransparent: true)
}
// Keep contentView transparent
if (targetWindow!.contentView?.layer?.isOpaque ?? false) {
_setContentViewTransparent(window: targetWindow!, isTransparent: true)
}
}
}
}
private static func _makeKeyWindow() {
guard let window = targetWindow else {
return
}
if (state.isBorderless) {
// Restore the key window state. NSWindow.canBecomeKeyWindow is false by default for borderless window, so makeKey() is unavailable...
state.isBorderless = false; // Suppress the callback
setBorderless(isBorderless: false)
window.makeKey()
state.isBorderless = true; // Suppress the callback
setBorderless(isBorderless: true)
} else {
window.makeKey()
}
}
private static func _doWindowStyleChangedCallback(num : EventType) -> Void {
windowStyleChangedCallback?(num.rawValue)
}
/// Create an overlay view to handle file dropping
private static func _setupOverlayView() -> Void {
guard let window = targetWindow
else {
return
}
// Add a subview to handle file dropping
overlayView = OverlayView(frame: window.frame)
window.contentView?.addSubview(overlayView!)
overlayView?.fitToSuperView()
}
/// Apply current window state
private static func _reapplyWindowStyles() -> Void {
if (targetWindow != nil) {
if (state.isBottommost) {
setBottommost(isBottommost: state.isBottommost)
} else {
setTopmost(isTopmost: state.isTopmost)
}
setBorderless(isBorderless: state.isBorderless)
setTransparent(isTransparent: state.isTransparent)
setMaximized(isZoomed: state.isZoomed)
setAlphaValue(alpha: state.alphaValue)
}
}
/// Copy UTF-16 string to uint16 buffer and add null for the end of the string
private static func _copyUTF16ToBuffer(text: String.UTF16View, buffer: UnsafeMutablePointer<UTF16Char>) -> Bool {
let count = text.count
if (count <= 0) {
return false
}
var i = 0
for c in text {
buffer[i] = c
i += 1
}
buffer[count] = UTF16Char.zero // End of the string
return true
}
// MARK: - Functions to get or set the window state
///
/// - Parameters:
/// - window:
/// - isTransparent: truefalse
private static func _setWindowTransparent(window: NSWindow, isTransparent: Bool) -> Void {
if (isTransparent) {
// window.styleMask = orgWindowInfo.styleMask
// //window.styleMask = []
// if (state.isBorderless) {
// window.titlebarAppearsTransparent = true
// window.titleVisibility = .hidden
// window.styleMask.insert(.borderless)
// }
window.backgroundColor = NSColor.clear
window.isOpaque = false
window.hasShadow = false
//window.contentView?.wantsLayer = true
} else {
// window.styleMask = orgWindowInfo.styleMask
// if (state.isBorderless) {
// window.styleMask.insert(.borderless)
// }
window.backgroundColor = orgWindowInfo.backgroundColor
window.isOpaque = orgWindowInfo.isOpaque
window.hasShadow = orgWindowInfo.hasShadow
}
}
/// ContentView
/// - Parameters:
/// - window:
/// - isTransparent: truefalse
private static func _setContentViewTransparent(window: NSWindow, isTransparent: Bool) -> Void {
if let view: NSView = window.contentView {
if (isTransparent) {
view.wantsLayer = true
view.layer?.backgroundColor = CGColor.clear
view.layer?.isOpaque = false
} else {
view.wantsLayer = orgWindowInfo.contentViewWantsLayer
view.layer?.backgroundColor = orgWindowInfo.contentViewLayerBackgroundColor
view.layer?.isOpaque = orgWindowInfo.contentViewLayerIsOpaque
}
}
}
///
/// - Parameters:
/// - window:
/// - isBorderless:
private static func _setWindowBorderless(window: NSWindow, isBorderless: Bool) -> Void {
if (isBorderless) {
window.styleMask = [.borderless]
//window.styleMask.insert(.borderless)
window.titlebarAppearsTransparent = true
window.titleVisibility = .hidden
} else {
window.styleMask = orgWindowInfo.styleMask
if (!orgWindowInfo.styleMask.contains(.borderless)) {
// .borderless
window.styleMask.remove(.borderless)
}
window.titlebarAppearsTransparent = orgWindowInfo.titlebarAppearsTransparent
window.titleVisibility = orgWindowInfo.titleVisibility
}
}
///
/// Windows
/// - Parameter type: 0:None, 1:Alpha, 2:ColorKey
@objc public static func setTransparentType(type: Int32) -> Void {
}
///
/// Windows
/// - Parameter color:
@objc public static func setKeyColor(color: Int32) -> Void {
}
/// Sets window alpha value
/// - Parameter alpha: 0.0 - 1.0
@objc public static func setAlphaValue(alpha: Float32) -> Void {
if let window: NSWindow = targetWindow {
window.alphaValue = CGFloat(alpha)
}
state.alphaValue = alpha
}
///
/// - Parameter isTransparent: true
@objc public static func setTransparent(isTransparent: Bool) -> Void {
if let window: NSWindow = targetWindow {
_setWindowTransparent(window: window, isTransparent: isTransparent)
_setContentViewTransparent(window: window, isTransparent: isTransparent)
}
if (state.isTransparent != isTransparent) {
_doWindowStyleChangedCallback(num: EventType.Style)
}
state.isTransparent = isTransparent
}
/// Hide or show the window border
/// - Parameter isBorderless: true for borderless
@objc public static func setBorderless(isBorderless: Bool) -> Void {
if let window: NSWindow = targetWindow {
if (!state.isZoomed) {
if (isBorderless != state.isBorderless) {
// Store the window size when the window become borderless
state.normalWindowRect = window.frame
}
}
if (orgWindowInfo.isKeyWindow) {
if (isBorderless) {
//
window.makeKey()
_setWindowBorderless(window: window, isBorderless: isBorderless)
} else {
//
_setWindowBorderless(window: window, isBorderless: isBorderless)
window.makeKey()
}
} else {
_setWindowBorderless(window: window, isBorderless: isBorderless)
}
//
// window.makeMain()
// window.makeKey()
if (state.isZoomed) {
if (!window.isZoomed) {
window.zoom(nil)
}
if (isBorderless) {
// Stretch to the full-screen size
let monitorIndex = getCurrentMonitor()
let rect = monitorRectangles[monitorIndices[Int(monitorIndex)]]
window.setFrame(rect, display: true, animate: false)
}
} else {
//
// if (!isBorderless && state.isBorderless) {
// // Restore the window size when the window become bordered
// if (state.normalWindowRect.width != 0 && state.normalWindowRect.height != 0) {
// window.setFrame(state.normalWindowRect, display: true, animate: false)
// }
// }
}
}
if (state.isBorderless != isBorderless) {
_doWindowStyleChangedCallback(num: EventType.Style)
}
state.isBorderless = isBorderless
}
///
/// - Parameter isTopmost: true for topmost (higher than the menu bar)
@objc public static func setTopmost(isTopmost: Bool) -> Void {
if let window: NSWindow = targetWindow {
if (isTopmost) {
window.collectionBehavior = [.fullScreenAuxiliary]
window.level = NSWindow.Level.popUpMenu
} else {
window.collectionBehavior = orgWindowInfo.collectionBehavior
window.level = orgWindowInfo.level
}
}
if (state.isTopmost != isTopmost) {
_doWindowStyleChangedCallback(num: EventType.Style)
}
state.isTopmost = isTopmost
state.isBottommost = false
}
///
/// - Parameter isBottommost: true
@objc public static func setBottommost(isBottommost: Bool) -> Void {
if let window: NSWindow = targetWindow {
if (isBottommost) {
window.collectionBehavior = [.fullScreenAuxiliary]
window.level = orgWindowInfo.level
window.order(NSWindow.OrderingMode.below, relativeTo:0)
} else {
window.collectionBehavior = orgWindowInfo.collectionBehavior
window.level = orgWindowInfo.level
}
}
if (state.isBottommost != isBottommost) {
_doWindowStyleChangedCallback(num: EventType.Style)
}
state.isBottommost = isBottommost
state.isTopmost = false
}
///
@objc public static func setClickThrough(isTransparent: Bool) -> Void {
targetWindow!.ignoresMouseEvents = isTransparent
}
/// Maximize the window
@objc public static func setMaximized(isZoomed: Bool) -> Void {
if let window: NSWindow = targetWindow {
if (state.isBorderless) {
// window.zoom() is unavailable if the window is ransparent (borderless)
if (isZoomed) {
// Store the window size when the window become zoomed
//if (!state.isZoomed && state.isBorderless && !_isZoomedActually()) {
if (!_isZoomedActually() && state.isBorderless) {
state.normalWindowRect = window.frame
}
// The window couldn't be zoomed when it is borderless
let monitorIndex = getCurrentMonitor()
let rect = monitorRectangles[monitorIndices[Int(monitorIndex)]]
window.setFrame(rect, display: true, animate: false)
} else {
if (state.normalWindowRect.width != 0 && state.normalWindowRect.height != 0) {
window.setFrame(state.normalWindowRect, display: true, animate: false)
}
}
state.isZoomed = isZoomed
} else {
// The window is opaque
if (window.isZoomed != isZoomed) {
// Toggle
window.zoom(nil)
state.isZoomed = window.isZoomed
}
}
} else {
// Remember the state
state.isZoomed = isZoomed
}
}
///
/// - Parameters:
/// - x:
/// - y:
/// - Returns: true
@objc public static func setPosition(x: Float32, y: Float32) -> Bool {
if (targetWindow == nil) {
return false
}
//// Windows
//let cocoaY = primaryMonitorHeight - CGFloat(y)
//let position: NSPoint = NSMakePoint(CGFloat(x), cocoaY)
//targetWindow?.setFrameTopLeftPoint(position)
//
let position: NSPoint = NSMakePoint(CGFloat(x), CGFloat(y))
targetWindow?.setFrameOrigin(position)
return true
}
///
/// - x:
/// - y:
/// - Returns: true
@objc public static func getPosition(x: UnsafeMutablePointer<Float32>, y: UnsafeMutablePointer<Float32>) -> Bool {
if (targetWindow == nil) {
x.pointee = 0;
y.pointee = 0;
return false
}
let frame = targetWindow!.frame
x.pointee = Float32(frame.minX)
y.pointee = Float32(frame.minY)
// Windows
//y.pointee = Float32(primaryMonitorHeight - frame.maxY)
return true
}
///
/// - Parameters:
/// - width:
/// - height:
/// - Returns: true
@objc public static func setSize(width: Float32, height:Float32) -> Bool {
if (targetWindow == nil) {
return false
}
var frame = targetWindow!.frame
frame.size.width = CGFloat(width)
frame.size.height = CGFloat(height)
targetWindow?.setFrame(frame, display: true, animate: false)
return true
}
///
/// - Parameters:
/// - width:
/// - height:
/// - Returns: true
@objc public static func getSize(width: UnsafeMutablePointer<Float32>, height: UnsafeMutablePointer<Float32>) -> Bool {
if (targetWindow == nil) {
width.pointee = 0;
height.pointee = 0;
return false
}
let currentSize = targetWindow!.frame.size
width.pointee = Float32(currentSize.width)
height.pointee = Float32(currentSize.height)
return true
}
@objc public static func registerWindowStyleChangedCallback(callback: @escaping intCallback) -> Bool {
windowStyleChangedCallback = callback
return true
}
@objc public static func unregisterWindowStyleChangedCallback() -> Bool {
windowStyleChangedCallback = nil
return true
}
// MARK: - Monitor Info.
///
/// - Returns:
@objc public static func getCurrentMonitor() -> Int32 {
var primaryMonitorIndex: Int = 0
//
if (targetWindow == nil) {
for i in 0..<monitorCount {
let screen = NSScreen.screens[monitorIndices[i]]
let sf = screen.visibleFrame
// 
if (sf.minX == 0 && sf.minY == 0) {
primaryMonitorIndex = i
break;
}
}
return Int32(primaryMonitorIndex)
}
//
let frame = targetWindow!.frame;
let cx: CGFloat = (frame.minX + frame.maxX) / 2.0
let cy: CGFloat = (frame.minY + frame.maxY) / 2.0
for i in 0..<monitorCount {
let screen = NSScreen.screens[monitorIndices[i]]
let sf = screen.visibleFrame
//
if (sf.minX <= cx && cx <= sf.maxX && sf.minY <= cy && cy <= sf.maxY) {
return Int32(i)
}
// 
if (sf.minX == 0 && sf.minY == 0) {
primaryMonitorIndex = i
}
}
return Int32(primaryMonitorIndex)
}
///
/// - Returns:
@objc public static func getMonitorCount() -> Int32 {
// NOTE: UnityScreenDisplayMonitor
return Int32(monitorCount)
}
///
/// - Parameters:
/// - monitorIndex:
/// - x: X
/// - y: Y
/// - width:
/// - height:
/// - Returns: true
@objc public static func getMonitorRectangle(
monitorIndex: Int32,
x: UnsafeMutablePointer<Float32>, y: UnsafeMutablePointer<Float32>,
width: UnsafeMutablePointer<Float32>, height: UnsafeMutablePointer<Float32>
) -> Bool {
// false
if (monitorIndex < 0 || monitorIndex >= monitorCount || monitorIndex >= NSScreen.screens.count) {
return false
}
let frame = NSScreen.screens[monitorIndices[Int(monitorIndex)]].visibleFrame
x.pointee = Float32(frame.minX)
y.pointee = Float32(frame.minY)
width.pointee = Float32(frame.width)
height.pointee = Float32(frame.height)
return true
}
@objc public static func registerMonitorChangedCallback(callback: @escaping intCallback) -> Bool {
monitorChangedCallback = callback
return true
}
@objc public static func unregisterMonitorChangedCallback() -> Bool {
monitorChangedCallback = nil
return true
}
// MARK: - File drop
@objc public static func setAllowDrop(enabled: Bool) -> Bool {
if (overlayView == nil) {
_setupOverlayView()
}
overlayView?.setEnabled(enabled: enabled)
return true
}
@objc public static func registerDropFilesCallback(callback: @escaping stringCallback) -> Bool {
dropFilesCallback = callback
return true
}
@objc public static func unregisterDropFilesCallback() -> Bool {
dropFilesCallback = nil
return true
}
// MARK: - Mouser curosor
///
/// - Parameters:
/// - x: X
/// - y: Y
/// - Returns: true
@objc public static func getCursorPosition(x: UnsafeMutablePointer<Float32>, y: UnsafeMutablePointer<Float32>) -> Bool {
let mousePos = NSEvent.mouseLocation
x.pointee = Float32(mousePos.x)
y.pointee = Float32(mousePos.y)
return true
}
///
/// - Parameters:
/// - x: X
/// - y: Y
/// - Returns: true
@objc public static func setCursorPosition(x: Float32, y: Float32) -> Bool {
let position = NSMakePoint(CGFloat(x), CGFloat(y))
let moveEvent = CGEvent(mouseEventSource: nil, mouseType: .mouseMoved,
mouseCursorPosition: position, mouseButton: .left)
moveEvent?.post(tap: .cgSessionEventTap)
return true
}
// MARK: - File dialogs
/// Open dialog
/// - Parameters:
/// - lpSettings: Pointer of PanelSettings
/// - lpBuffer: Pointer of UTF-16 string for output
/// - bufferSize: Size of UTF-16 string buffer
@objc public static func openFilePanel(lpSettings: UnsafeRawPointer, lpBuffer: UnsafeMutablePointer<UniChar>?, bufferSize: UInt32) -> Bool {
let panel = NSOpenPanel()
let panelHelper = CustomPanelHelper(panel: panel)
let pPanelSettings = lpSettings.bindMemory(to: PanelSettings.self, capacity: MemoryLayout<PanelSettings>.size)
let ps = pPanelSettings.pointee
let initialDir = getStringFromUtf16Array(textPointer: ps.initialDirectory)
let initialFile = getStringFromUtf16Array(textPointer: ps.initialFile) as NSString
if (targetWindow != nil) {
if (state.isTopmost) {
// Temporarily disable always on top in order to show the dialog
targetWindow?.level = NSWindow.Level.floating
}
//  panel.parent accessoryView
// Set attached window as the parent
//panel.parent = targetWindow
} else {
// Find my window if the window is not attached
//let myWindow: NSWindow? = NSApp.orderedWindows.first
//panel.parent = myWindow
}
panel.allowsMultipleSelection = PanelFlag.AllowMultipleSelection.containedIn(value: ps.flags)
panel.showsHiddenFiles = PanelFlag.ShowHidden.containedIn(value: ps.flags)
//panel.allowedFileTypes = fileTypes
panelHelper.addFileTypes(text: getStringFromUtf16Array(textPointer: ps.filterText))
panel.isAccessoryViewDisclosed = true // Options
panel.message = getStringFromUtf16Array(textPointer: ps.titleText)
//panel.title = getStringFromUtf16Array(textPointer: ps.titleText)
if (initialDir != "") {
panel.directoryURL = URL(fileURLWithPath: initialDir, isDirectory: true)
} else if (initialFile.deletingLastPathComponent != "") {
panel.directoryURL = URL(fileURLWithPath: initialFile.deletingLastPathComponent, isDirectory: true)
}
panel.nameFieldStringValue = initialFile.lastPathComponent
panel.canChooseFiles = true
panel.canChooseDirectories = false
panel.allowsOtherFileTypes = false
panel.canCreateDirectories = true
//panel.showsTagField = false
panel.allowsOtherFileTypes = false
panel.level = NSWindow.Level.popUpMenu
panel.orderFrontRegardless()
panel.center()
let result = panel.runModal();
var text: String = ""
if (result == .OK) {
if (panel.urls.count > 0) {
// Make new-line separated string
for url in panel.urls {
text += "\"" + url.path.replacingOccurrences(of: "\"", with: "\"\"") + "\"\n"
}
}
}
if (targetWindow != nil) {
if (state.isTopmost) {
// Re-enable always on top
targetWindow?.level = NSWindow.Level.popUpMenu
}
if (state.isBorderless) {
_makeKeyWindow()
// // Restore the key window state. NSWindow.canBecomeKeyWindow is false by default for borderless window, so makeKey() is unavailable...
// state.isBorderless = false; // Suppress the callback
// setBorderless(isBorderless: false)
// state.isBorderless = true; // Suppress the callback
// setBorderless(isBorderless: true)
}
targetWindow?.makeKeyAndOrderFront(nil)
}
return outputToStringBuffer(text: text, lpBuffer: lpBuffer, bufferSize: bufferSize)
}
/// Open file select dialog to save
/// - Parameters:
/// - lpSettings: Pointer of PanelSettings
/// - lpBuffer: Pointer of UTF-16 string for output
/// - bufferSize: Size of UTF-16 string buffer
@objc public static func openSavePanel(lpSettings: UnsafeRawPointer, lpBuffer: UnsafeMutablePointer<UniChar>?, bufferSize: UInt32) -> Bool {
let panel = NSSavePanel()
let panelHelper = CustomPanelHelper(panel: panel)
let pPanelSettings = lpSettings.bindMemory(to: PanelSettings.self, capacity: MemoryLayout<PanelSettings>.size)
let ps = pPanelSettings.pointee;
let initialDir = getStringFromUtf16Array(textPointer: ps.initialDirectory)
let initialFile = getStringFromUtf16Array(textPointer: ps.initialFile) as NSString
if (targetWindow != nil) {
if (state.isTopmost) {
// Temporarily disable always on top in order to show the dialog
targetWindow?.level = NSWindow.Level.floating
}
//  panel.parent accessoryView
// Set attached window as the parent
//panel.parent = targetWindow
} else {
// Find my window if the window is not attached
//let myWindow: NSWindow = NSApp.orderedWindows[0]
//panel.parent = myWindow
}
panel.parent = targetWindow // Nil if not attatched
panel.showsHiddenFiles = PanelFlag.ShowHidden.containedIn(value: ps.flags)
//panel.message = getStringFromUtf16Array(textPointer: ps.titleText)
panel.title = getStringFromUtf16Array(textPointer: ps.titleText)
if (initialDir != "") {
panel.directoryURL = URL(fileURLWithPath: initialDir, isDirectory: true)
} else if (initialFile.deletingLastPathComponent != "") {
panel.directoryURL = URL(fileURLWithPath: initialFile.deletingLastPathComponent, isDirectory: true)
}
panel.nameFieldStringValue = initialFile.lastPathComponent
//panel.allowedFileTypes = fileTypes
panelHelper.addFileTypes(text: getStringFromUtf16Array(textPointer: ps.filterText))
panel.allowsOtherFileTypes = true
panel.canCreateDirectories = true //PanelFlag.CanCreateDirectories.containedIn(value: ps.flags)
//panel.canSelectHiddenExtension = false
//panel.showsTagField = false
panel.level = NSWindow.Level.popUpMenu
panel.orderFrontRegardless()
panel.center()
let result = panel.runModal();
var text: String = ""
if (result == .OK && (panel.url != nil)) {
let url: String = panel.url!.path
text = "\"" + url.replacingOccurrences(of: "\"", with: "\"\"") + "\"\n"
}
if (targetWindow != nil) {
if (state.isTopmost) {
targetWindow?.level = NSWindow.Level.popUpMenu
}
if (state.isBorderless) {
// Re-enable always on top
_makeKeyWindow()
// // Restore the key window state. NSWindow.canBecomeKeyWindow is false by default for borderless window, so makeKey() is unavailable...
// state.isBorderless = false; // Suppress the callback
// setBorderless(isBorderless: false)
// state.isBorderless = true; // Suppress the callback
// setBorderless(isBorderless: true)
}
targetWindow?.makeKeyAndOrderFront(nil)
}
return outputToStringBuffer(text: text, lpBuffer: lpBuffer, bufferSize: bufferSize)
}
/// Parse an UTF-16 null terminated string pointer to String
private static func getStringFromUtf16Array(textPointer: UnsafePointer<UniChar>?) -> String {
if (textPointer == nil) {
return ""
}
var len = 0
while textPointer![len] != UniChar.zero {
len += 1
}
return String(utf16CodeUnits: textPointer!, count: len)
}
/// Call a StringCallback with UTF-16 paramete
/// - Parameters:
/// - callback: Registered callback function
/// - text: Parrameter as String
/// - Returns: True if success
public static func callStringCallback(callback: stringCallback?, text: String) -> Bool {
if (callback == nil)
{
return false
}
let count = text.utf16.count
if (count <= 0) {
return false
}
let buffer = UnsafeMutablePointer<UniChar>.allocate(capacity: count + 1)
var i = 0
for c in text.utf16 {
buffer[i] = c
i += 1
}
buffer[count] = UniChar.zero // End of the string
// Do callback
callback?(buffer)
buffer.deallocate()
return true
}
/// Return an UTF-16 string by using a pointer
/// - Parameters:
/// - text: Parrameter as String
/// - lpBuffer: UTF-16 string buffer that allocated by caller
/// - bufferSize: Size of the string buffer
/// - Returns: True if success
private static func outputToStringBuffer(text: String, lpBuffer: UnsafeMutablePointer<UniChar>?, bufferSize: UInt32) -> Bool {
let size = Int(bufferSize)
//let buffer = lpBuffer.bindMemory(to: UniChar.self, capacity: size)
guard let buffer = lpBuffer else {
return false
}
// Fill in zero
for i in 0..<size {
buffer[i] = UniChar.zero
}
let utf16text = text.utf16
let count = utf16text.count
if (count <= 0) {
return false
}
var i = 0
for c in utf16text {
buffer[i] = c
i += 1
}
return true
}
/// Return some information for debugging
@objc public static func getDebugInfo() -> Int32 {
var result: Int32 = 0
if (targetWindow != nil) {
if (targetWindow!.canBecomeMain) { result += 1 }
if (targetWindow!.canBecomeKey) { result += 2 }
if (targetWindow!.isKeyWindow) { result += 4 }
}
return result
}
}