Xcode Project Setting for CGEventTap? - swift

A while ago, I created a extremely simple Xcode project to test CGEventTap, and it works perfectly fine when I run from Xcode. The code is at the bottom.
However, if I create a new project on Xcode, paste the exactly the same code below, and run from Xcode, I get "Failed to create event tap".
Is there a project setting that I need to change in order to get CGEventTap working? I even tried to copy and paste info.plist from the old testing project to the new one.
I'm very puzzled. Thank you for your help!
// ViewController.swift
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
func myCGEventCallback(proxy : CGEventTapProxy, type : CGEventType, event : CGEvent, refcon : UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {
if type == .keyDown || type == .keyUp || type == .flagsChanged {
let keyCode = event.getIntegerValueField(.keyboardEventKeycode)
print(keyCode)
}
return Unmanaged.passRetained(event)
}
let eventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue) | (1 << CGEventType.flagsChanged.rawValue)
guard let eventTap = CGEvent.tapCreate(tap: .cgSessionEventTap, place: .headInsertEventTap, options: .defaultTap, eventsOfInterest: CGEventMask(eventMask), callback: myCGEventCallback, userInfo: nil) else {
debugPrint("Failed to create event tap")
exit(1)
}
let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, CFRunLoopMode.commonModes)
CGEvent.tapEnable(tap: eventTap, enable: true)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
// AppDelegate.swift
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}

The answer was to uncheck Sandbox from capabilities.

Related

How to programmatically save the content written in TextView to a text file when terminating the app?

I want to save the content of the text view when the user closes the app.
I used the following codes to do so, but I cannot get the up-to-date string of the textview when closing the app. So, the produced text file is blank.
How should I access to the NSTextView from AppDelegate to save its content?
ViewController.swift
import Cocoa
class ViewController: NSViewController {
static var textViewString: String = ""
#IBOutlet var textView: NSTextView!{
didSet{
ViewController.textViewString = textView.string
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// start with hidden and show after moving to the main screen
DispatchQueue.main.async {
//keep the window top
self.view.window?.level = .floating
//set up the main display as the display where window shows up
let screens = NSScreen.screens
var pos = NSPoint()
pos.x = screens[0].visibleFrame.midX
pos.y = screens[0].visibleFrame.midY
self.view.window?.setFrameOrigin(pos)
self.view.window?.zoom(self)
self.view.window?.level = .floating
//self.view.window?.backgroundColor = NSColor.white
//stop the user from moving window
self.view.window?.isMovable = false
//disable resizable mode
self.view.window?.styleMask.remove(.resizable)
self.view.window?.setIsVisible(true)
}
//set up font for the reflectionForm
textView.font = NSFont.systemFont(ofSize: 30)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func saveTextViewString(){
if let documentDirectoryFileURL = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last {
let fileName = "savedText.txt"
let targetTextFilePath = documentDirectoryFileURL + "/" + fileName
do {
try ViewController.textViewString.write(toFile: targetTextFilePath, atomically: true, encoding: String.Encoding.utf8)
print("successfully recorded: \(ViewController.textViewString.description) at \(fileName.utf8CString)")
} catch let error as NSError {
print("failed to write: \(error)")
}
}
}
}
AppDelegate.swift
import Cocoa
#main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
//save the string in the textview into a text file
ViewController().saveTextViewString()
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
Thank you for the #jnpdx's comments, I was able to solve this by just declaring ViewController in the AppDelegate by stating var viewController: ViewController!
ViewController.swift
import Cocoa
class ViewController: NSViewController {
#IBOutlet var textView: NSTextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// start with hidden and show after moving to the main screen
DispatchQueue.main.async {
//keep the window top
self.view.window?.level = .floating
//set up the main display as the display where window shows up
let screens = NSScreen.screens
var pos = NSPoint()
pos.x = screens[0].visibleFrame.midX
pos.y = screens[0].visibleFrame.midY
self.view.window?.setFrameOrigin(pos)
self.view.window?.zoom(self)
self.view.window?.level = .floating
//self.view.window?.backgroundColor = NSColor.white
//stop the user from moving window
self.view.window?.isMovable = false
//disable resizable mode
self.view.window?.styleMask.remove(.resizable)
self.view.window?.setIsVisible(true)
}
//set up font for the reflectionForm
textView.font = NSFont.systemFont(ofSize: 30)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func saveTextViewString(){
if let documentDirectoryFileURL = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last {
let fileName = "savedText.txt"
let targetTextFilePath = documentDirectoryFileURL + "/" + fileName
do {
try textView.string.write(toFile: targetTextFilePath, atomically: true, encoding: String.Encoding.utf8)
print("successfully recorded: \(textView.string.description) at \(fileName.utf8CString)")
} catch let error as NSError {
print("failed to write: \(error)")
}
}
}
}
AppDelegate.swift
import Cocoa
#main
class AppDelegate: NSObject, NSApplicationDelegate {
//connect viewController with ViewController
var viewController: ViewController!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
//save the string in the textview into a text file
viewController.saveTextViewString()
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}

ORSSerialPort with Arduino

I've been trying for a long time to program an Xcode interface to communicate with my Arduino Mega. but the whole thing didn't work as well as intended. I did the whole thing with ORSSerialPort.
In the Xcode project I wrote this for the swift file ViewController.swift :
import Cocoa
import ORSSerial
class ViewController: NSViewController, ORSSerialPortDelegate {
var serialPort = ORSSerialPort(path: "/dev/cu.usbmodem142101")
func SendString(data: String){
let stringData = Data(data.utf8)
serialPort?.send(stringData)
}
func openPort(){
serialPort?.baudRate=9600
serialPort?.delegate=self
serialPort?.parity = .none
serialPort?.numberOfStopBits = 1
serialPort?.open()
print("serialport is open")
}
func closePort(){
serialPort?.delegate=nil
serialPort?.close()
print("serialport is close")
}
override func viewDidLoad() {
super.viewDidLoad()
}
override var representedObject: Any? {
didSet {
}
}
#IBAction func onButton(_ sender: Any) {
openPort()
}
#IBAction func OffButton(_ sender: Any) {
closePort()
}
#IBAction func SendButton(_ sender: Any) {
SendString(data: "stringdata blablabla")
}
func serialPortWasOpened(_ serialPort: ORSSerialPort) {
print("serialPort to \(serialPort) is run")
}
func serialPortWasRemovedFromSystem(_ serialPort: ORSSerialPort) {
self.serialPort = nil
}
}
and this code i have load on the Arduino mega:
String angel;
void setup() {
Serial.begin(9600);
}
void loop() {
angel = Serial.readString();
Serial.println(angel);
delay(350);
}
unfortunately it doesn't work and I don't know why.
Your question doesn't provide any detail about what part(s) don't work, but there's one definite problem.
Your Arduino program looks like it echos everything it receives on the serial port back on the same port. In order to see that on the computer, you'll have to implement the serialPort(_:didReceive:) method in your view controller. Something like this:
func serialPort(_ serialPort: ORSSerialPort, didReceive data: Data) {
guard let string = String(data: data, encoding: .ascii) else { return; }
print("Received: \(string)")
}

How to bind selector to method inside NSObject [duplicate]

This question already has an answer here:
NSStatusItem in NSStatusBar, action selector method not responding
(1 answer)
Closed 2 years ago.
In the macOS swiftui project I have the following code
import Cocoa
import SwiftUI
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var statusItem: StatusItem = StatusItem()
func applicationDidFinishLaunching(_ aNotification: Notification) {
}
#objc public func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
print("Right click! (AppDelegate)")
} else {
print("Left click! (AppDelegate)")
}
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
import Cocoa
class StatusItem : NSObject {
private let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
override init() {
super.init()
self.item.button?.title = "title"
self.item.button?.action = #selector(self.statusBarButtonClicked(sender:))
self.item.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
}
#objc public func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
print("Right click! (NSObject)")
} else {
print("Left click! (NSObject)")
}
}
}
But when I click NSStatusBarButton it prints "Left click! (AppDelegate)" and "Right click! (AppDelegate)" to console.
Why does it happen? And how to make it call statusBarButtonClicked method defined in StatusItem class?
Setting the button's action is only one half of what you need to do. You also need to specify a target. Add
self.item.button?.target = self
and I believe you will get the result you are looking for.
What's happening is action specifies the selector to invoke and target specifies the object on which to invoke it.

Parse PFUser not registering subclass

I am trying to use Parse PFUser in a software for OSX desktop. When I try to use it PFUser.query() it gives a message: Failed to set (contentViewController) user defined inspected property on (NSWindow): The class PFUser must be registered with registerSubclass before using Parse.
It is happening without registering the class.
I tried it registering the class this way: PFUser.registerSubclass() but it still doesn't work.
I will use the default PFUser without adding any fields to it, so I don't need to create a custom class to be my PFUser.
I tried to use PFUser.enableAutomaticUser() without success
Code below:
AppDelegate.swift
import Cocoa
import Parse
import Bolts
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
let APP_ID = "app_id"
let CLIENT_KEY = "client_key"
let SERVER = "https://parseserver.com/"
func applicationDidFinishLaunching(_ aNotification: Notification) {
PFUser.registerSubclass()
let configuracaoParse = ParseClientConfiguration {
$0.applicationId = self.APP_ID
$0.clientKey = self.CLIENT_KEY
$0.server = self.SERVER
}
Parse.initialize(with: configuracaoParse)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
ViewController.swift
import Cocoa
import Parse
class ViewController: NSViewController {
#IBOutlet weak var emailTextField: NSTextField!
#IBOutlet weak var senhaSecureTextField: NSSecureTextField!
override func viewDidLoad() {
super.viewDidLoad()
contaUsuarios()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func entrarButtonClicked(_ sender: NSButton) {
}
func contaUsuarios() {
let query = PFUser.query()
query?.countObjectsInBackground(block: {
(count, error) -> Void in
let numeroUsers = Int(UInt32(count))
if numeroUsers > 0 {
}
print(numeroUsers)
})
}
}
Reading some content on the internet I discovered that in OSX the ViewController is launched before the AppDelegate finishes loading, so I initialized the Parse connection and subclassing in the ViewController's viewDidLoad instead of AppDelegate and it is working just fine.

Swift 3 CFRunLoopRun in Thread?

I just made a simple testing app to display keycode of keystrokes along with modifiers. It works fine for 3 keystrokes, then the app crashes. When it crashes, debug console just shows (LLDB) at the end. Any suggestion what might be causing this? Maybe something has to do with thread or pointer, but I'm not sure how I can fix this. I'm including the code below. I'd really appreciate any help! Thanks!
import Cocoa
import Foundation
class ViewController: NSViewController {
#IBOutlet weak var textField: NSTextFieldCell!
let speech:NSSpeechSynthesizer = NSSpeechSynthesizer()
func update(msg:String) {
textField.stringValue = msg
print(msg)
speech.startSpeaking(msg)
}
func bridgeRetained<T : AnyObject>(obj : T) -> UnsafeRawPointer {
return UnsafeRawPointer(Unmanaged.passRetained(obj).toOpaque())
}
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global().async {
func myCGEventCallback(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {
let parent:ViewController = Unmanaged<ViewController>.fromOpaque(refcon!).takeRetainedValue()
if [.keyDown].contains(type) {
let flags:CGEventFlags = event.flags
let pressed = Modifiers(rawValue:flags.rawValue)
var msg = ""
if pressed.contains(Modifiers(rawValue:CGEventFlags.maskAlphaShift.rawValue)) {
msg+="caps+"
}
if pressed.contains(Modifiers(rawValue:CGEventFlags.maskShift.rawValue)) {
msg+="shift+"
}
if pressed.contains(Modifiers(rawValue:CGEventFlags.maskControl.rawValue)) {
msg+="control+"
}
if pressed.contains(Modifiers(rawValue:CGEventFlags.maskAlternate.rawValue)) {
msg+="option+"
}
if pressed.contains(Modifiers(rawValue:CGEventFlags.maskCommand.rawValue)) {
msg += "command+"
}
if pressed.contains(Modifiers(rawValue:CGEventFlags.maskSecondaryFn.rawValue)) {
msg += "function+"
}
var keyCode = event.getIntegerValueField(.keyboardEventKeycode)
msg+="\(keyCode)"
DispatchQueue.main.async {
parent.update(msg:msg)
}
if keyCode == 0 {
keyCode = 6
} else if keyCode == 6 {
keyCode = 0
}
event.setIntegerValueField(.keyboardEventKeycode, value: keyCode)
}
return Unmanaged.passRetained(event)
}
let eventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue)
guard let eventTap = CGEvent.tapCreate(tap: .cgSessionEventTap, place: .headInsertEventTap, options: .defaultTap, eventsOfInterest: CGEventMask(eventMask), callback: myCGEventCallback, userInfo: UnsafeMutableRawPointer(mutating: self.bridgeRetained(obj: self))) else {
print("failed to create event tap")
exit(1)
}
let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
CGEvent.tapEnable(tap: eventTap, enable: true)
CFRunLoopRun()
}
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
The main problem is the reference counting: You create a retained
reference to the view controller when installing the event handler, this happens exactly once.
Then you consume a reference in the callback, this happens for every
tap event. Therefore the reference count drops to zero eventually and
the view controller is deallocated, causing a crash.
Better pass unretained references to the callback, and take care that
the event handler is uninstalled when the view controller is deallocated.
Also there is no need to create a separate runloop for an OS X application, or to asynchronously dispatch the handler creation.
Make the callback a global function, not a method. Use
takeUnretainedValue() to get the view controller reference:
func myCGEventCallback(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {
let viewController = Unmanaged<ViewController>.fromOpaque(refcon!).takeUnretainedValue()
if type == .keyDown {
var keyCode = event.getIntegerValueField(.keyboardEventKeycode)
let msg = "\(keyCode)"
DispatchQueue.main.async {
viewController.update(msg:msg)
}
if keyCode == 0 {
keyCode = 6
} else if keyCode == 6 {
keyCode = 0
}
event.setIntegerValueField(.keyboardEventKeycode, value: keyCode)
}
return Unmanaged.passRetained(event)
}
In the view controller, keep a reference to the run loop source
so that you can remove it in deinit, and use
passUnretained() to pass a pointer to the view controller to
the callback:
class ViewController: NSViewController {
var eventSource: CFRunLoopSource?
override func viewDidLoad() {
super.viewDidLoad()
let eventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue)
let userInfo = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
if let eventTap = CGEvent.tapCreate(tap: .cgSessionEventTap, place: .headInsertEventTap,
options: .defaultTap, eventsOfInterest: CGEventMask(eventMask),
callback: myCGEventCallback, userInfo: userInfo) {
self.eventSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), self.eventSource, .commonModes)
} else {
print("Could not create event tap")
}
}
deinit {
if let eventSource = self.eventSource {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), eventSource, .commonModes)
}
}
// ...
}
Another option would be to install/uninstall the event handler in
viewDidAppear and viewDidDisappear.