Mac Swift 3 Running two bits of code at the same time

Miles.Kelly97

macrumors member
Original poster
Nov 24, 2015
34
0
Tunbridge Wells
Hi,

I am creating a status bar app that hides the desktop. I am trying to get it to run a function that checks if there is an external monitor connected and it will the update the NSOn/OffState of a menu item. I have the function working but I would like it to open the menu at the same time. At the moment i can only get either the function to run with no menu or the menu to pop up but it won't run the function.

Code:
func applicationDidFinishLaunching(_ aNotification: Notification) {
        showDesktopMenuItem.state = NSOnState
        statusItem.menu = JDOMenu
        if let button = statusItem.button {
            button.image = NSImage(named: "JDOIcon")
            button.action = #selector(queryState(sender:))
            button.target = self
          
        
        }
        
    }

func queryState() {
//Checks external monitor state
}
If I // out the statusItem.menu = JDO menu the function runs but when i leave it in it just opens the menu but doesn't run the function

Can anyone help and point me in the right direction?

Thanks
Miles
 

0002378

Suspended
May 28, 2017
676
668
Hi, I'm confused.

What is "the function" that needs to run ? Are you referring to "queryState()" as your "function" ? If queryState() is your function, it is not clear where you are invoking it. If you don't invoke it, how can it run ?

Also, what do you mean by "open the menu" ? Will the menu pop out from the status bar automatically ?

In general, when you want to run 2 pieces of code concurrently, you can submit the two pieces of code as operations or blocks to an OperationQueue.

Here's an example:

Code:
var queue: OperationQueue = OperationQueue()

// This is only if you want to execute your code on the main thread
queue.underlyingQueue = DispatchQueue.main

// Define how many pieces of code can execute at the same time
queue.maxConcurrentOperationCount = (some value more than 1)

// Execute your code

// First piece of code to execute
queue.addOperation({self.menu = myMenu})

// Second piece of code to execute
queue.addOperation({self.runMyFunction()})
By initializing an operation queue with the capacity for multiple concurrent tasks, and submitting your pieces of concurrent code to it, you are essentially executing them "at the same time", provided number of tasks <= maxConcurrentOperationCount (assuming nothing else is currently running on the queue)

But, that said, I don't think that what you want to do requires any concurrency at all. You should be able to execute your code serially and get it working. But, it is not very clear to me what you're trying to do. If you can clarify, I may be able to help more.

Hope this helps.
 

Miles.Kelly97

macrumors member
Original poster
Nov 24, 2015
34
0
Tunbridge Wells
When I click on the status bar button (of the app) in the status bar I would like it to both open the menu as it should when clicked on but also run the function queryState() at the same time

When I click on the status bar button I would like it to open the menu I have created (a few menu bar items each with IB Actions connected to them). There are two menu items that I would like to update each time the menu is opened.

So when the menu is open the state of the menu items will either switch to on or off.

So the queryState() function gets an output for me to then set the state of the menu item/s. However I can't find a way to action the function and open the NSmenu.

I am not sure if that makes any more sense.

Sorry for the poor explanation
 
Last edited:

0002378

Suspended
May 28, 2017
676
668
Ok, that makes a little more sense. Thanks.

First off, I don't think you should attach an action to the button of the status menu. I think it's only purpose is to open the menu. I think that your selector action is somehow conflicting with its main purpose of opening the menu.

But, you said that it works (runs queryState()) when the menu assignment line is commented out, so maybe the problem lies in your JDOMenu object.

Anyway, there is one more way to do what you want to do. Your menu item is an instance of NSMenuItem. You can write a custom class that extends NSMenuItem, and then override the var "isEnabled" to be a computed property that checks the external monitor state. Something like my code below:

Code:
import Cocoa

class MyMenuItem: NSMenuItem {
   
    override var isEnabled: Bool {

// Here, do the same thing that queryState() does, and return true or false
}
}
This way, you don't rely on your button to run queryState(). Any time your menu item is shown to the user, the isEnabled variable will be recomputed, and will run your queryState() logic, within your overriden var block (shown in code above).

If you write this custom class, make sure to go to Interface Builder, and set the class of your menu items to be "MyMenuItem" or whatever you decide to call the custom class.

Can you try this ?

What is JDOMenu ? Could there be something wrong with it ?
 
Last edited:

Miles.Kelly97

macrumors member
Original poster
Nov 24, 2015
34
0
Tunbridge Wells
I am not sure if I know how to do that. The queryState() function runs a shell script and collects the output from the debugger window. Making the return of the function of type Void. How would i get it to be a Bool? Or convert that to a Bool.

It collects the output from the debugger (pipe) which will either be on, off, not secondary display connected.

This is the queryState Function

mirrorPath is just a variable with the path to the shell script

The parts that are commented out were the bits I was using to set the state of the menu bar originally but this didn't work because I couldn't get the menu to open

Code:
func queryState() {
        let task = Process()
        task.launchPath = mirrorPath
        task.arguments = ["-q"]
        let pipe = Pipe()
        task.standardOutput = pipe
        let outHandle = pipe.fileHandleForReading
        
        outHandle.readabilityHandler = { pipe in
            if let line = String(data: pipe.availableData, encoding: String.Encoding.utf8) {
                self.currentState = line
        }
            print(self.currentState)
            //if self.currentState == "No secondary display detected.\n" {
                //print("Working!")
            //}
            //else if self.currentState == "on\n" {
                //self.mirrorMenuItem.state = NSOnState
                //self.dualDisplayMenuItem.state = NSOffState
           // }
            //else if self.currentState == "off\n" {
                //self.dualDisplayMenuItem.state = NSOnState
                //self.mirrorMenuItem.state = NSOffState
            //}
        }
        task.launch()
    }
JDO menu is the name of the IB Outlet for the NSMenu.
 

0002378

Suspended
May 28, 2017
676
668
I am not sure if I know how to do that. The queryState() function runs a shell script and collects the output from the debugger window. Making the return of the function of type Void. How would i get it to be a Bool? Or convert that to a Bool.

It collects the output from the debugger (pipe) which will either be on, off, not secondary display connected.

This is the queryState Function

mirrorPath is just a variable with the path to the shell script

The parts that are commented out were the bits I was using to set the state of the menu bar originally but this didn't work because I couldn't get the menu to open

JDO menu is the name of the IB Outlet for the NSMenu.
Ok. From looking at your code, it looks like an enum might be more appropriate than a boolean, because you have 3 possible states: 1- no secondary display, 2 - mirror, 3 - dual display. So, create an enum like the following, and return it from the queryState function. This may not fix your menuItem problem but it's a start.

Code:
enum DisplayState {

    case noSecondaryDisplay
    case mirror
    case dualDisplay
}

// queryState will now return this enum value
func queryState() -> DisplayState {

// Inside queryState, do the following:

var displayState: DisplayState
//if self.currentState == "No secondary display detected.\n" {
    displayState = .noSecondaryDisplay
            //}
            //else if self.currentState == "on\n" {
                displayState = .mirror
           // }
            //else if self.currentState == "off\n" {
                displayState = .dualDisplay
            //}
                else {
                     // TODO: What happens in this case ? Maybe set a default value ?
                }
 
return displayState

// Then, outside queryState(), use the enum return value as follows:
let displayState = queryState()
switch displayState {
    case .noSecondaryDisplay:    // do nothing
    case .mirror:    self.mirrorMenuItem.state = NSOnState
                //self.dualDisplayMenuItem.state = NSOffState
    case .dualDisplay:    self.dualDisplayMenuItem.state = NSOnState
                //self.mirrorMenuItem.state = NSOffState
}
To be honest, it is difficult for me to really understand what is going on without looking at all your code, so I get a proper perspective. Ideally, I'd have all the source code so I can open it and run it in XCode and debug the problem that way.

If your source code is not open, ok, no worries. The best I can do is guess and offer suggestions. But, if you're willing to share your entire project source with me, I can do better.

From just looking at queryState(), it seems to me like that code is not very reliable because you are deriving your display state based on a String value, which could change, or could be prone to typos in your code. In general, comparing long strings like that is error-prone. Is there a better way to determine your display state ?
 

Miles.Kelly97

macrumors member
Original poster
Nov 24, 2015
34
0
Tunbridge Wells
I think it would be beneficial for me to send my code to you if you are cool to help me out that would be amazing, I would rather not do it on GitHub. Is there any other way I can send it to you?

Miles
 

0002378

Suspended
May 28, 2017
676
668
I'm busy traveling and am out of town currently, so I may not have time to look at it for a few days. But, can you send me a private message on this site, with an attachment ? Or you can upload a zip somewhere on the internet, and send me a link to it in a private message here ?
 

Similar threads

Register on MacRumors! This sidebar will go away, and you'll see fewer ads.