Python - How to make a daemon from a GUI application on Mac OS X?

On Windows, it's easy. Just run your program using pythonw instead using python, and the code will execute in the background.

So the thing that I want to achieve is easily arranged.

I have an application that really is a service doing underground things. But this service requires a control panel.

So, on Windows, I use wxPython to create a GUI, even some wx things to provide the necessary service, and when the user does the settings, he / she clicks β€œHide” and β€œShow (False)” is called in the main window.

Thus, the graphical interface disappears and the service continues to run in the background. The user can always return it using the hotkey.

The problem is that on Mac OS X this strategy only works to some extent.

When wx.Frame.Show (False) is called, the window disappears along with its menu bar, and the service works fine, but the application is still visible there.

You can switch to it no matter what you can do about it. It is still present in Dock, etc. Etc.

This happens when a program uses python or pythonw or when it is associated with Py2App.

No matter what I do, the icon stays there.

There must be some trick that allows the programmer to remove this naughty icon and thus stop bothering the poor little user when he / she does not want to bother.

The hide window is obviously not enough. Does anyone know a trick?

NB: I would really like to do this as I described above, and not interfere in two separate processes and IPC.

Edit:

After long jerks, I found them:

How to hide an application icon from a Mac OS X dock

http://codesorcery.net/2008/02/06/feature-requests-versus-the-right-way-to-do-it

How to hide the dock icon

According to the last link, the correct way to do this is:

[NSApp setActivationPolicy: NSApplicationActivationPolicyAccessory]; 

or

 [NSApp setActivationPolicy: NSApplicationActivationPolicyProhibited]; 

So what I want (switching runtime from background to front and back) is possible.

But how to do it with Python ???

Constants: NSApplicationActivationPolicyProhibited and NSApplicationActivationPolicyAccessory are present in AppKit, but I cannot find the setApplicationActivationPolicy function anywhere.

NSApp () does not have it.

I know there is a way to do this by loading objc dylib using ctypes, delegating NSApp and sending "setApplicationActivationPolicy: <constant_value>", but I don’t know how much clutter this will be with wx.App (), and this is a bit of a lot of work for what should be available already.

In my experience, NSApp () and wx.App () are active, but at the same time they really do not like each other.

Perhaps we can get an instance of NSApp () that wx uses somehow and uses the wx delegate

Remember that already proposed solutions with starting as an agent and moving to the foreground or running several processes and running IPC are very undesirable in my case.

So, ideally, using setApplicationActivationPolicy is my goal, but how? (Simple and easy and without interference for wx.App (), please.)

Any ideas ???

+5
source share
2 answers

OK people, there is a good, pleasant and correct solution without any problems.

First, I want to explain why the Windows GUI process goes into the background when wx.Frame.Show (MyFrame, False) is called.

A very short explanation and skipping details is what Windows considers Window and the application to be the same.

those. The main element of the MS Windows application is your main GUI window.

So, when this window is hidden, the application no longer has a GUI and continues to run in the background.

Mac OS X considers the application your application, and any windows you choose for this are its children, so to speak.

This allows you to launch the application without displaying any windows, except for the menu bar, from which you can select the action that then created the desired window.

It is very convenient for editors, where you can have several files that open immediately, each in its own window, and when you close the last one, you can still open a new one or create an empty one, etc. etc.

Therefore, the main element of the Mac OS X application is the application itself, so it remains open after the last window is logically hidden. Destroying its menu bar will also not help. The application name will remain present in the Dock in both the application switcher and Force Quit. You can switch to it and do nothing .: D But, fortunately, the Mac provides us with a feature to put it in the background. And this function is already mentioned by setApplicationActivationPolicy () from the NSApp object.

The problem was naming it in the Python AppKit, which is NSApp.setActivationPolicy_ (). To complicate matters, it is not available directly from the Python interactive shell, but it must be called from at least the imported module.

Why? I have no idea. In any case, here is a complete example to run the application in the background, which will work on Mac and Windows.

I have not tried this on Linux, which combines the behavior of Mac and Windows with regard to the application, so is it enough to hide the window, which is not yet visible.

Feel free to try and submit editing to make the example more cross-platform.

Example:

 """ This app will show you small window with the randomly generated code that will confirm that reopened window is still the same app returned from background, and the button allowing you to send it to background. After you send it to background, wait 8 seconds and application will return to foreground again. Too prove that the application is continuing its work in the background, the app will call wx.Bell() every second. You should hear the sound while app is in the foreground and when it is in background too. Merry Christmas and a happy New Year! """ import wx import random, sys if sys.platform=="darwin": from AppKit import NSBundle, NSApp, NSAutoreleasePool, NSApplicationActivationPolicyRegular, NSApplicationActivationPolicyProhibited # Use Info.plist values to know whether our process started as daemon # Also, change this dict in case anyone is later checking it (eg some module) # Note: Changing this dict doesn't change Info.plist file info = NSBundle.mainBundle().infoDictionary() def SendToBackground (): # Change info, just in case someone checks it later info["LSUIElement"] = "1" NSApp.setActivationPolicy_(NSApplicationActivationPolicyProhibited) def ReturnToForeground (): # Change info, just in case someone checks it later info["LSUIElement"] = "0" NSApp.setActivationPolicy_(NSApplicationActivationPolicyRegular) else: # Simulate Mac OS X App - Info.plist info = {"LSUIElement": "0"} # Assume non background at startup # If programmer chose not to display GUI at startup then she/he should change this before calling ReturnToForeground() # To preserve consistency and allow correct IsDaemon() answer def SendToBackground (): info["LSUIElement"] = "1" def ReturnToForeground (): info["LSUIElement"] = "0" def IsDaemon (): return info["LSUIElement"]=="1" class Interface (wx.Frame): def __init__ (self): wx.Frame.__init__(self, None, -1, "Test", pos=(100, 100), size=(100, 100)) wx.StaticText(self, -1, "Test code: "+str(random.randint(1000, 10000)), pos=(10, 10), size=(80, 20)) b = wx.Button(self, -1, "DAEMONIZE ME", size=(80, 20), pos=(10, 50)) wx.EVT_BUTTON(self, b.GetId(), self.OnDaemonize) self.belltimer = wx.Timer(self) wx.EVT_TIMER(self, self.belltimer.GetId(), self.OnBellTimer) self.belltimer.Start(1000) # On Mac OS X, you wouldn't be able to quit the app without the menu bar: if sys.platform=="darwin": self.SetMenuBar(wx.MenuBar()) self.Show() def OnBellTimer (self, e): wx.Bell() def OnDaemonize (self, e): self.Show(False) SendToBackground() self.timer = wx.Timer(self) wx.EVT_TIMER(self, self.timer.GetId(), self.OnExorcize) self.timer.Start(8000) def OnExorcize (self, e): self.timer.Stop() ReturnToForeground() self.Show() self.Raise() app = wx.App() i = Interface() app.MainLoop() 

Of course, this example can be run from the terminal or using the CLI window. In this case, the terminal control over your program will remain open until the application appears and disappears.

To complete the GUI daemon, you must run it using pythonw (on Windows) or run it from the daemontest.pyw file,

and on Mac you should use:

 % nohup python daemontest.py & 

either link it to py2app or use the Python launcher that comes with the python.org version of Python to run daemontest.py without a terminal.

Note. This example suffers from the same flaw in Mac OS X that is mentioned in the links that I put in my question. I mean the problem of incorrect focus and the menu bar, which does not appear immediately when the application comes from the background. The user must switch and return to the newly received application for it to work correctly. Hope someone solves this. And so on. This is pretty annoying.

One more note. If there are threads in your program, pause them during the demo and exile. Especially if they are communicating with another application using Apple events. To be honest, something about wx.Timers needs to be done too. If you are not careful, you may have problems with the non-existent NSAutoreleasePool and / or SegmentationFault after the program terminates.

+1
source

Ok Here is the code you want to do:

 import AppKit info = AppKit.NSBundle.mainBundle().infoDictionary() info["LSUIElement"] = "1" 

This is a messy answer that you don't want to do, but I will list it anyway. In the info.plist file add this key:

 <key>LSUIElement</key> <string>1</string> 

Another daemonish solution, but means that it cannot have a graphical interface, you add info.plist to this key to the file:

 <key>LSBackgroundOnly</key> <string>1</string> 

A source

-1
source

Source: https://habr.com/ru/post/1237999/


All Articles