Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Reliable position to pop context menus for tray icon on left click with Wayland
Forum Update on Monday, May 27th 2025

Reliable position to pop context menus for tray icon on left click with Wayland

Scheduled Pinned Locked Moved Unsolved Qt for Python
4 Posts 3 Posters 503 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • G Offline
    G Offline
    glgl-schemer
    wrote on last edited by glgl-schemer
    #1

    I got from the guidelines that I should not rely on QCursor::pos when I pop a menu on Wayland. But how could I get a reliable position when I click a tray icon by left click? I wrote a simple script with QSystemTrayIcon as below, but the member function geometry always returns a point at (0, 0) inside on_left_click. I also tried to override event and eventFilter to get a position from an event, but the functions were never called.

    from PyQt6.QtGui import QAction, QCursor, QIcon
    from PyQt6.QtWidgets import QApplication, QMenu, QSystemTrayIcon
    
    class MyTray(QSystemTrayIcon):
      def __init__(self):
        super().__init__(QIcon.fromTheme('start-here'))
        self.activated.connect(lambda reason: self.on_left_click(reason))
    
      def on_left_click(self, reason):
        if reason == QSystemTrayIcon.ActivationReason.Trigger:
          self.contextMenu().popup(QCursor.pos()) # not accurate on Wayland
    
    def main():
      app = QApplication([])
      app.setQuitOnLastWindowClosed(False)
    
      menu = QMenu()
      action = QAction()
      action.setText('Hello')
      menu.addAction(action)
    
      tray = MyTray()
      tray.setVisible(True)
      tray.setContextMenu(menu)
      app.exec()
    
    if __name__ == '__main__':
      main()
    
    1 Reply Last reply
    0
    • G Offline
      G Offline
      glgl-schemer
      wrote on last edited by
      #2

      I installed MyTray as the filter of QApplication, so MyTray is updated as follows:

      
      class MyTray(QSystemTrayIcon):
        pos = None
      
        def __init__(self, app):
          super().__init__(QIcon.fromTheme('start-here'))
          self.activated.connect(lambda reason: self.on_left_click(reason))
          app.installEventFilter(self)
      
        def on_left_click(self, reason):
          if reason == QSystemTrayIcon.ActivationReason.Trigger:
            print('Clicked!!!')
            self.contextMenu().popup(self.pos if self.pos else QCursor.pos())
      
        def eventFilter(self, _, event):
          print(event)
          return False
      

      And I got the following events:

      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5d80>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5d80>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5d80>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5d80>
      <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5d80>
      <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
      <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5ea0>
      Clicked!!!
      

      It seems none of them can provide a position.

      SGaistS 1 Reply Last reply
      0
      • G glgl-schemer

        I installed MyTray as the filter of QApplication, so MyTray is updated as follows:

        
        class MyTray(QSystemTrayIcon):
          pos = None
        
          def __init__(self, app):
            super().__init__(QIcon.fromTheme('start-here'))
            self.activated.connect(lambda reason: self.on_left_click(reason))
            app.installEventFilter(self)
        
          def on_left_click(self, reason):
            if reason == QSystemTrayIcon.ActivationReason.Trigger:
              print('Clicked!!!')
              self.contextMenu().popup(self.pos if self.pos else QCursor.pos())
        
          def eventFilter(self, _, event):
            print(event)
            return False
        

        And I got the following events:

        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5d80>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5d80>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5d80>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5d80>
        <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5d80>
        <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5e10>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QChildEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QEvent object at 0x7fc2ac0f5ea0>
        <PyQt6.QtCore.QDynamicPropertyChangeEvent object at 0x7fc2ac0f5ea0>
        Clicked!!!
        

        It seems none of them can provide a position.

        SGaistS Offline
        SGaistS Offline
        SGaist
        Lifetime Qt Champion
        wrote on last edited by
        #3

        Hi,

        I am unsure about what you are trying to achieve.

        Did you take a look at the System Tray Icon example ? Does it do what you want ?

        Interested in AI ? www.idiap.ch
        Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

        1 Reply Last reply
        0
        • F Offline
          F Offline
          friedemannkleint
          wrote on last edited by
          #4

          There is a restriction in Wayland in that the standard protocol that does allow positioning windows (see https://bugreports.qt.io/browse/QTBUG-110119 ).

          1 Reply Last reply
          2

          • Login

          • Login or register to search.
          • First post
            Last post
          0
          • Categories
          • Recent
          • Tags
          • Popular
          • Users
          • Groups
          • Search
          • Get Qt Extensions
          • Unsolved