1 /* 2 * This file is part of mpv. 3 * 4 * mpv is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Lesser General Public 6 * License as published by the Free Software Foundation; either 7 * version 2.1 of the License, or (at your option) any later version. 8 * 9 * mpv is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public 15 * License along with mpv. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 import Cocoa 19 20 class View: NSView { 21 unowned var common: Common 22 var mpv: MPVHelper? { get { return common.mpv } } 23 24 var tracker: NSTrackingArea? 25 var hasMouseDown: Bool = false 26 27 override var isFlipped: Bool { return true } 28 override var acceptsFirstResponder: Bool { return true } 29 30 31 init(frame: NSRect, common com: Common) { 32 common = com 33 super.init(frame: frame) 34 autoresizingMask = [.width, .height] 35 wantsBestResolutionOpenGLSurface = true 36 registerForDraggedTypes([ .fileURLCompat, .URLCompat, .string ]) 37 } 38 39 required init?(coder: NSCoder) { 40 fatalError("init(coder:) has not been implemented") 41 } 42 updateTrackingAreasnull43 override func updateTrackingAreas() { 44 if let tracker = self.tracker { 45 removeTrackingArea(tracker) 46 } 47 48 tracker = NSTrackingArea(rect: bounds, 49 options: [.activeAlways, .mouseEnteredAndExited, .mouseMoved, .enabledDuringMouseDrag], 50 owner: self, userInfo: nil) 51 // here tracker is guaranteed to be none-nil 52 addTrackingArea(tracker!) 53 54 if containsMouseLocation() { 55 cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_LEAVE, 0) 56 } 57 } 58 draggingEnterednull59 override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { 60 guard let types = sender.draggingPasteboard.types else { return [] } 61 if types.contains(.fileURLCompat) || types.contains(.URLCompat) || types.contains(.string) { 62 return .copy 63 } 64 return [] 65 } 66 isURLnull67 func isURL(_ str: String) -> Bool { 68 // force unwrapping is fine here, regex is guarnteed to be valid 69 let regex = try! NSRegularExpression(pattern: "^(https?|ftp)://[^\\s/$.?#].[^\\s]*$", 70 options: .caseInsensitive) 71 let isURL = regex.numberOfMatches(in: str, 72 options: [], 73 range: NSRange(location: 0, length: str.count)) 74 return isURL > 0 75 } 76 performDragOperationnull77 override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { 78 let pb = sender.draggingPasteboard 79 guard let types = pb.types else { return false } 80 81 if types.contains(.fileURLCompat) || types.contains(.URLCompat) { 82 if let urls = pb.readObjects(forClasses: [NSURL.self]) as? [URL] { 83 let files = urls.map { $0.absoluteString } 84 EventsResponder.sharedInstance().handleFilesArray(files) 85 return true 86 } 87 } else if types.contains(.string) { 88 guard let str = pb.string(forType: .string) else { return false } 89 var filesArray: [String] = [] 90 91 for val in str.components(separatedBy: "\n") { 92 let url = val.trimmingCharacters(in: .whitespacesAndNewlines) 93 let path = (url as NSString).expandingTildeInPath 94 if isURL(url) { 95 filesArray.append(url) 96 } else if path.starts(with: "/") { 97 filesArray.append(path) 98 } 99 } 100 EventsResponder.sharedInstance().handleFilesArray(filesArray) 101 return true 102 } 103 return false 104 } 105 acceptsFirstMousenull106 override func acceptsFirstMouse(for event: NSEvent?) -> Bool { 107 return true 108 } 109 becomeFirstRespondernull110 override func becomeFirstResponder() -> Bool { 111 return true 112 } 113 resignFirstRespondernull114 override func resignFirstResponder() -> Bool { 115 return true 116 } 117 mouseEnterednull118 override func mouseEntered(with event: NSEvent) { 119 if mpv?.mouseEnabled() ?? true { 120 cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_ENTER, 0) 121 } 122 common.updateCursorVisibility() 123 } 124 mouseExitednull125 override func mouseExited(with event: NSEvent) { 126 if mpv?.mouseEnabled() ?? true { 127 cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_LEAVE, 0) 128 } 129 common.titleBar?.hide() 130 common.setCursorVisiblility(true) 131 } 132 mouseMovednull133 override func mouseMoved(with event: NSEvent) { 134 if mpv?.mouseEnabled() ?? true { 135 signalMouseMovement(event) 136 } 137 common.titleBar?.show() 138 } 139 mouseDraggednull140 override func mouseDragged(with event: NSEvent) { 141 if mpv?.mouseEnabled() ?? true { 142 signalMouseMovement(event) 143 } 144 } 145 mouseDownnull146 override func mouseDown(with event: NSEvent) { 147 if mpv?.mouseEnabled() ?? true { 148 signalMouseDown(event) 149 } 150 } 151 mouseUpnull152 override func mouseUp(with event: NSEvent) { 153 if mpv?.mouseEnabled() ?? true { 154 signalMouseUp(event) 155 } 156 common.window?.isMoving = false 157 } 158 rightMouseDownnull159 override func rightMouseDown(with event: NSEvent) { 160 if mpv?.mouseEnabled() ?? true { 161 signalMouseDown(event) 162 } 163 } 164 rightMouseUpnull165 override func rightMouseUp(with event: NSEvent) { 166 if mpv?.mouseEnabled() ?? true { 167 signalMouseUp(event) 168 } 169 } 170 otherMouseDownnull171 override func otherMouseDown(with event: NSEvent) { 172 if mpv?.mouseEnabled() ?? true { 173 signalMouseDown(event) 174 } 175 } 176 otherMouseUpnull177 override func otherMouseUp(with event: NSEvent) { 178 if mpv?.mouseEnabled() ?? true { 179 signalMouseUp(event) 180 } 181 } 182 magnifynull183 override func magnify(with event: NSEvent) { 184 event.phase == .ended ? 185 common.windowDidEndLiveResize() : common.windowWillStartLiveResize() 186 187 common.window?.addWindowScale(Double(event.magnification)) 188 } 189 signalMouseDownnull190 func signalMouseDown(_ event: NSEvent) { 191 signalMouseEvent(event, MP_KEY_STATE_DOWN) 192 if event.clickCount > 1 { 193 signalMouseEvent(event, MP_KEY_STATE_UP) 194 } 195 } 196 signalMouseUpnull197 func signalMouseUp(_ event: NSEvent) { 198 signalMouseEvent(event, MP_KEY_STATE_UP) 199 } 200 signalMouseEventnull201 func signalMouseEvent(_ event: NSEvent, _ state: UInt32) { 202 hasMouseDown = state == MP_KEY_STATE_DOWN 203 let mpkey = getMpvButton(event) 204 cocoa_put_key_with_modifiers((mpkey | Int32(state)), Int32(event.modifierFlags.rawValue)) 205 } 206 signalMouseMovementnull207 func signalMouseMovement(_ event: NSEvent) { 208 var point = convert(event.locationInWindow, from: nil) 209 point = convertToBacking(point) 210 point.y = -point.y 211 212 common.window?.updateMovableBackground(point) 213 if !(common.window?.isMoving ?? false) { 214 mpv?.setMousePosition(point) 215 } 216 } 217 preciseScrollnull218 func preciseScroll(_ event: NSEvent) { 219 var delta: Double 220 var cmd: Int32 221 222 if abs(event.deltaY) >= abs(event.deltaX) { 223 delta = Double(event.deltaY) * 0.1 224 cmd = delta > 0 ? SWIFT_WHEEL_UP : SWIFT_WHEEL_DOWN 225 } else { 226 delta = Double(event.deltaX) * 0.1 227 cmd = delta > 0 ? SWIFT_WHEEL_RIGHT : SWIFT_WHEEL_LEFT 228 } 229 230 mpv?.putAxis(cmd, delta: abs(delta)) 231 } 232 scrollWheelnull233 override func scrollWheel(with event: NSEvent) { 234 if !(mpv?.mouseEnabled() ?? true) { 235 return 236 } 237 238 if event.hasPreciseScrollingDeltas { 239 preciseScroll(event) 240 } else { 241 let modifiers = event.modifierFlags 242 let deltaX = modifiers.contains(.shift) ? event.scrollingDeltaY : event.scrollingDeltaX 243 let deltaY = modifiers.contains(.shift) ? event.scrollingDeltaX : event.scrollingDeltaY 244 var mpkey: Int32 245 246 if abs(deltaY) >= abs(deltaX) { 247 mpkey = deltaY > 0 ? SWIFT_WHEEL_UP : SWIFT_WHEEL_DOWN 248 } else { 249 mpkey = deltaX > 0 ? SWIFT_WHEEL_RIGHT : SWIFT_WHEEL_LEFT 250 } 251 252 cocoa_put_key_with_modifiers(mpkey, Int32(modifiers.rawValue)) 253 } 254 } 255 containsMouseLocationnull256 func containsMouseLocation() -> Bool { 257 var topMargin: CGFloat = 0.0 258 let menuBarHeight = NSApp.mainMenu?.menuBarHeight ?? 23.0 259 260 guard let window = common.window else { return false } 261 guard var vF = window.screen?.frame else { return false } 262 263 if window.isInFullscreen && (menuBarHeight > 0) { 264 topMargin = TitleBar.height + 1 + menuBarHeight 265 } 266 267 vF.size.height -= topMargin 268 269 let vFW = window.convertFromScreen(vF) 270 let vFV = convert(vFW, from: nil) 271 let pt = convert(window.mouseLocationOutsideOfEventStream, from: nil) 272 273 var clippedBounds = bounds.intersection(vFV) 274 if !window.isInFullscreen { 275 clippedBounds.origin.y += TitleBar.height 276 clippedBounds.size.height -= TitleBar.height 277 } 278 return clippedBounds.contains(pt) 279 } 280 canHideCursornull281 func canHideCursor() -> Bool { 282 guard let window = common.window else { return false } 283 return !hasMouseDown && containsMouseLocation() && window.isKeyWindow 284 } 285 getMpvButtonnull286 func getMpvButton(_ event: NSEvent) -> Int32 { 287 let buttonNumber = event.buttonNumber 288 switch (buttonNumber) { 289 case 0: return SWIFT_MBTN_LEFT 290 case 1: return SWIFT_MBTN_RIGHT 291 case 2: return SWIFT_MBTN_MID 292 case 3: return SWIFT_MBTN_BACK 293 case 4: return SWIFT_MBTN_FORWARD 294 default: return SWIFT_MBTN9 + Int32(buttonNumber - 5) 295 } 296 } 297 } 298