moses-palmer / pystray

GNU General Public License v3.0
463 stars 57 forks source link

Got an error when using psgtray #101

Closed glight2000 closed 2 years ago

glight2000 commented 2 years ago

I'm using psgtray(which feature is being supplied by pystray) for system tray. When run the sample code(page:https://github.com/PySimpleGUI/psgtray)on my mac os 11.6. I got a error:AttributeError: 'Icon' object has no attribute '_status_item'. Full error stack:

/Users/username/anaconda3/envs/envname/bin/python /Users/username/Documents/Projects/Geek/envname/psg_tray.py
* Error performing wm_overrideredirect while hiding the hidden master root* expected boolean value but got ""
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/Users/username/anaconda3/envs/envname/lib/python3.9/threading.py", line 973, in _bootstrap_inner
    self.run()
  File "/Users/username/anaconda3/envs/envname/lib/python3.9/threading.py", line 910, in run
    self._target(*self._args, **self._kwargs)
  File "/Users/username/anaconda3/envs/envname/lib/python3.9/site-packages/psgtray/psgtray.py", line 183, in _pystray_thread
    self.tray_icon.menu = pystray.Menu(*self.menu_items)
  File "/Users/username/anaconda3/envs/envname/lib/python3.9/site-packages/pystray/_base.py", line 141, in menu
    self.update_menu()
  File "/Users/username/anaconda3/envs/envname/lib/python3.9/site-packages/pystray/_base.py", line 216, in update_menu
    self._update_menu()
  File "/Users/username/anaconda3/envs/envname/lib/python3.9/site-packages/pystray/_darwin.py", line 81, in _update_menu
    self._status_item.setMenu_(None)
AttributeError: 'Icon' object has no attribute '_status_item'

Process finished with exit code 1

I also posted an issue here: https://github.com/PySimpleGUI/psgtray/issues/2.

PySimpleGUI commented 2 years ago

I looked through the code a bit, but I've not yet been able to sort out where in pystray the problem is. I don't see it on windows nor linux.

glight2000 commented 2 years ago

It works good on my windows system but not on MacOS.

PySimpleGUI commented 2 years ago

@moses-palmer has been responsive in the past to my questions, so hoping he's got some cycles to look at this one.

moses-palmer commented 2 years ago

Thank you for your report.

What version of pynput do you use? This looks very much like a bug that was fixed in version 0.17.3. is it possible for you to upgrade?

PySimpleGUI commented 2 years ago

Personally I was at 1.6.5. I'm upgrading now and will let you know if I see anything on my Windows system.

Thank you for answering on a Sunday. Appreciate you taking time on your weekend to help out.

c:\python\pycharmprojects>pip show pynput
Name: pynput
Version: 1.6.5
Summary: Monitor and control user input devices
Home-page: https://github.com/moses-palmer/pynput
Author: Moses Palmér
Author-email: moses.palmer@gmail.com
License: LGPLv3
Location: c:\python\anaconda3\lib\site-packages
Requires: six
Required-by:

c:\python\pycharmprojects>pip install --upgrade pynput
Collecting pynput
  Downloading pynput-1.7.3-py2.py3-none-any.whl (99 kB)
     |████████████████████████████████| 99 kB 3.2 MB/s
Requirement already satisfied, skipping upgrade: six in c:\python\anaconda3\lib\site-packages (from pynput) (1.16.0)
Installing collected packages: pynput
  Attempting uninstall: pynput
    Found existing installation: pynput 1.6.5
    Uninstalling pynput-1.6.5:
      Successfully uninstalled pynput-1.6.5
Successfully installed pynput-1.7.3
glight2000 commented 2 years ago

@moses-palmer @PySimpleGUI Thank you taking time on this.

Here is pynput info which my conda env is using. Looks like it is already the latest version.

Name: pynput
Version: 1.7.3
Summary: Monitor and control user input devices
Home-page: https://github.com/moses-palmer/pynput
Author: Moses Palmér
Author-email: moses.palmer@gmail.com
License: LGPLv3
Location: /Users/username/anaconda3/envs/envname/lib/python3.9/site-packages
Requires: pyobjc-framework-Quartz, six
Required-by:
moses-palmer commented 2 years ago

@glight2000, I looked through the psgtray source code. As noted in the documentation, the pystray.Icon.run method must be called from the main thread; for all but macOS, this is just a recommendation, but because of limitations in the operating system, it will not work on macOS.

I am afraid that I will have to close this issue, as the bug is in another library, and I see no workaround in pystray.

PySimpleGUI commented 2 years ago

Can you tell us which library the problem is in?

If the problem is one layer down in the cake, then I would like to be able to go have a chat/look/log an issue with that layer.

Given the error is that the attribute is missing, this seems like a locking error where code should have held up until the threaded initialization completes. Can you perhaps add some protection so that the timing is right for the threaded version to work?

PySimpleGUI commented 2 years ago

I noticed in your release notes this info:

v0.17.3 - macOS and AppIndicator bug fixes Let the default timeout for notifications when using the AppIndicator backend be decided by the desktop environment, not infinity. Thanks to Angelo Naselli! Do not attempt to create a menu before the icon has started on macOS

I'll check my code to see how the timing of showing an icon is compared to when the icon has started.

Is there a particular technique you recommend for checking to see that the "icon has started on macOS"?

For example, will this startup sequence fail on the Mac because the Menu is created prior to calling tray_icon.run()? I think it's the "has started" part that I'm confused on.

        self.tray_icon = pystray.Icon(self.title, self._create_image(self.icon))
        self.tray_icon.default_action = self._default_action_callback
        self.tray_icon.menu = pystray.Menu(*self.menu_items)
        self.tray_icon.title = self.tooltip  # tooltip for the icon
        self.thread_started = True
        self.tray_icon.run()
moses-palmer commented 2 years ago

The problem lies in psgtray which attempts to start a thread in which to run the system tray code. This is not supported on macOS.

If I remove almost all code from the _run implementation and leave only self._app.run() which is absolutely necessary, I get the following error message in the console:

2021-10-11 05:07:34.367 Python[15732:137393] WARNING: nextEventMatchingMask should only be called from the Main Thread! This will throw an exception in the future.
2021-10-11 05:07:34.367 Python[15732:137393] *** Assertion failure in +[NSUndoManager _endTopLevelGroupings], /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/Foundation/Foundation-1677.104/Foundation/Misc.subproj/NSUndoManager.m:363
2021-10-11 05:07:34.368 Python[15732:137393] *** Assertion failure in +[NSUndoManager _endTopLevelGroupings], /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/Foundation/Foundation-1677.104/Foundation/Misc.subproj/NSUndoManager.m:363
An error occurred in the main loop
Traceback (most recent call last):
  File "/Users/moses/src/pystray/lib/pystray/_darwin.py", line 115, in _run
    self._app.run()
objc.error: NSInternalInconsistencyException - +[NSUndoManager(NSInternal) _endTopLevelGroupings] is only safe to invoke on the main thread.

This is a problem when integrating with other loop driven frameworks, since they also must hijack the main thread. To solve it on the pystray side would require some changes of API's to allow injecting a foreign NSApplication instance, and adding another way to run it apart from the blocking run.

This would be a rather pointless exercise, though, if the framework to integrate with did not provide the means to cooperate. I have no experience with Tkinter; do you know if it is possible to get access to the presumed NSApplication used inside?

PySimpleGUI commented 2 years ago

This would be a rather pointless exercise, though, if the framework to integrate with did not provide the means to cooperate. I have no experience with Tkinter; do you know if it is possible to get access to the presumed NSApplication used inside?

image

And right over the top of my head goes Moses! I don't know what an NSApplication is, so I'm worthless in the short-term.

I've been super-impressed by your work on pystray in getting it to run as a thread. It's really opened up the possibilities with tkinter and PySimpleGUI. I've finally got a solid way of communicating with threads and the PySimpleGUI event loop so that integrating with them is trivial now.

Thank you for looking into all this. I really appreciate it. I know it's an obscure kind of thing. The Mac support is hellish to say the least. I've got a special config window just for Mac settings.

moses-palmer commented 2 years ago

This is one of the last issues until I am ready for a version 1.0, so I decided to put some effort into it.

If you find the time, please have a look at the branch feature-run-detached. It introduces the new method run_detached to allow running the icon in a non-blocking fashion. Please consult the documentation for more information, and see below for how to acquire the NSApplication instance:

import AppKit

darwin_nsapplication = AppKit.NSApplication.sharedApplication()
glight2000 commented 2 years ago

Oh. Thank you both for your help, very much. Expecting the new version:)

PySimpleGUI commented 2 years ago

OH WOW Moses, thank you SO SO SO much!!

image

Do I need to make changes to my code with the new version?

It sounds like I may need to perhaps re-architect how I'm running it now since I'm running it as a thread rather than doing any kind of protocol that is non-blocking.

Any hints are appreciated!

moses-palmer commented 2 years ago

You will need to make some small changes to your application.

  1. Instead of creating a separate thread here, call the method from the current thread.
  2. Make sure that the method that currently calls Icon.run instead calls Icon.run_detached.
  3. Make sure to pass darwin_nsapplication as a keyword argument to Icon.__init__. See my comment above for to to get a reference; if you can get this from Tkinter, use that instead.
danrossi commented 2 years ago

I'm having a similar issue. I am running an asyncio project. In windows something like this worked. run-detached seems to block my service working. If I run run_detached its crashing python in macOS and fails to launch the icon.

providing darwin_nsapplication stops the other error just fails to launch.

loop.create_task(run_in_threadpool(app.startSystray))
loop.create_task(server.serve())
loop.create_task(app.app_start())
loop.run_forever()

run_in_threadpool is a starlette import

Just running this crashes python launcher in macOS.

app.startSystrayDetached()
loop.run_forever()

Or this

loop.create_task(run_in_threadpool(app.startSystrayDetached))

I believe loop.run_forever() is making it fail to launch

My icon setup looks like this. I am using tkinter also but cant see any reference to get the shared application

if sys.platform == 'darwin':
            import AppKit
            darwin_nsapplication = AppKit.NSApplication.sharedApplication()
        else:
            darwin_nsapplication = None

        self.icon = icon(settings.SYSTRAY_TITLE,
            icon=self.off_icon(),
            title=settings.SYSTRAY_TITLE,
            menu=menu(
               ....
            ),
            darwin_nsapplication=darwin_nsapplication
            )
danrossi commented 2 years ago

I think its asyncio crashing the python launcher. Just running "run()" shows an icon. run_detached with an asyncio loop doesn't. I need an asyncio loop running to dispatch tasks to async methods on click events ie

def on_tray_stop(self) -> None:
        """Stopped from system tray menu or websocket."""
    loop.create_task(self.on_stop())
danrossi commented 2 years ago

I think there is a bug with asyncio and the threading call. My code launches but the icon does not.

danrossi commented 2 years ago

The only way for this to work on mac is to reverse what goes to another thread. Keep this as the mainloop and send the uvicorn asyncio process to another thread. So run_detached is not useful. If I run run_detached before the asyncio calls the icon never displays.

moses-palmer commented 2 years ago

@danrossi, your analysis of using run_detached with asyncio is correct; run_detached is only useful when integrating with a library that provides a platform mainloop, such as a Windows event loop, a glib mainloop or for macOS, a running NSApplication.

xu1jia2qi3 commented 1 year ago

@danrossi, your analysis of using run_detached with asyncio is correct; run_detached is only useful when integrating with a library that provides a platform mainloop, such as a Windows event loop, a glib mainloop or for macOS, a running NSApplication.

so what is correct way to use run_detached in MacOS, i am facing the same problem because i need my python app working on background on Mac. Could you provide some example code for doing that?