@@ -6028,6 +6028,140 @@ def __init__(self, holder: Holder, bag: Bag, gui: GuiVar) -> None:
60286028 self.now_searching: Literal["off", "searching", "errored", "success"] = "off"
60296029
60306030 self.requested_raise = False
6031+ self.requested_tray = False
6032+ self.sdl_tray = None
6033+ self._tray_quit_cb = None
6034+ self._tray_open_cb = None
6035+ self._tray_playpause_cb = None
6036+ self._tray_next_cb = None
6037+ self._tray_prev_cb = None
6038+
6039+ def tray_quit_callback(self, userdata, entry):
6040+ """Called when the 'Quit' tray entry is clicked."""
6041+ logging.info("Exit via tray")
6042+ self.exit("Exit received from app indicator")
6043+
6044+ def tray_open_callback(self, userdata, entry):
6045+ """Open Tauon main UI."""
6046+ # Adjust to whatever your real function is
6047+ self.request_raise()
6048+
6049+
6050+ def tray_playpause_callback(self, userdata, entry):
6051+ """Toggle play/pause."""
6052+ self.pctl.play_pause()
6053+
6054+
6055+ def tray_next_callback(self, userdata, entry):
6056+ """Next track."""
6057+ self.pctl.advance()
6058+
6059+
6060+ def tray_prev_callback(self, userdata, entry):
6061+ """Previous track."""
6062+ self.pctl.back()
6063+
6064+ def load_sdl_icon(self, path: Path):
6065+ """Load an image file into an SDL_Surface suitable for a tray icon."""
6066+ # Load image as a surface (requires SDL3_image installed)
6067+ surf = sdl3.IMG_Load(str(path).encode("utf-8"))
6068+ if not surf:
6069+ raise RuntimeError(f"IMG_Load failed: {sdl3.SDL_GetError().decode()}")
6070+ return surf
6071+
6072+ def init_sdl_tray(self):
6073+ icon_surface = self.load_sdl_icon(Path(self.get_tray_icon("tray-indicator-default")))
6074+ # Create tray (no icon, just tooltip)
6075+ self.sdl_tray = sdl3.SDL_CreateTray(icon_surface, b"My tray")
6076+ if not self.sdl_tray:
6077+ raise RuntimeError(f"SDL_CreateTray failed: {sdl3.SDL_GetError().decode()}")
6078+ # Create menu
6079+ menu = sdl3.SDL_CreateTrayMenu(self.sdl_tray)
6080+ if not menu:
6081+ raise RuntimeError(f"SDL_CreateTrayMenu failed: {sdl3.SDL_GetError().decode()}")
6082+
6083+ # --- Open Tauon Music Box ---
6084+ open_entry = sdl3.SDL_InsertTrayEntryAt(
6085+ menu,
6086+ -1,
6087+ b"Open Tauon Music Box",
6088+ sdl3.SDL_TRAYENTRY_BUTTON,
6089+ )
6090+ if not open_entry:
6091+ raise RuntimeError(f"SDL_InsertTrayEntryAt(Open) failed: {sdl3.SDL_GetError().decode()}")
6092+
6093+ # --- Separator ---
6094+ sep1 = sdl3.SDL_InsertTrayEntryAt(
6095+ menu,
6096+ -1,
6097+ None, # NULL label => separator
6098+ 0, # flags ignored for separators
6099+ )
6100+ if not sep1:
6101+ raise RuntimeError(f"SDL_InsertTrayEntryAt(sep1) failed: {sdl3.SDL_GetError().decode()}")
6102+
6103+ # --- Play/Pause ---
6104+ playpause_entry = sdl3.SDL_InsertTrayEntryAt(
6105+ menu,
6106+ -1,
6107+ b"Play/Pause",
6108+ sdl3.SDL_TRAYENTRY_BUTTON,
6109+ )
6110+ if not playpause_entry:
6111+ raise RuntimeError(f"SDL_InsertTrayEntryAt(Play/Pause) failed: {sdl3.SDL_GetError().decode()}")
6112+
6113+ # --- Next Track ---
6114+ next_entry = sdl3.SDL_InsertTrayEntryAt(
6115+ menu,
6116+ -1,
6117+ b"Next Track",
6118+ sdl3.SDL_TRAYENTRY_BUTTON,
6119+ )
6120+ if not next_entry:
6121+ raise RuntimeError(f"SDL_InsertTrayEntryAt(Next) failed: {sdl3.SDL_GetError().decode()}")
6122+
6123+ # --- Previous Track ---
6124+ prev_entry = sdl3.SDL_InsertTrayEntryAt(
6125+ menu,
6126+ -1,
6127+ b"Previous Track",
6128+ sdl3.SDL_TRAYENTRY_BUTTON,
6129+ )
6130+ if not prev_entry:
6131+ raise RuntimeError(f"SDL_InsertTrayEntryAt(Previous) failed: {sdl3.SDL_GetError().decode()}")
6132+
6133+ # --- Separator ---
6134+ sep2 = sdl3.SDL_InsertTrayEntryAt(
6135+ menu,
6136+ -1,
6137+ None, # separator
6138+ 0,
6139+ )
6140+ if not sep2:
6141+ raise RuntimeError(f"SDL_InsertTrayEntryAt(sep2) failed: {sdl3.SDL_GetError().decode()}")
6142+
6143+ # --- Quit ---
6144+ quit_entry = sdl3.SDL_InsertTrayEntryAt(
6145+ menu,
6146+ -1,
6147+ b"Quit",
6148+ sdl3.SDL_TRAYENTRY_BUTTON,
6149+ )
6150+ if not quit_entry:
6151+ raise RuntimeError(f"SDL_InsertTrayEntryAt(Quit) failed: {sdl3.SDL_GetError().decode()}")
6152+
6153+ self._tray_open_cb = sdl3.SDL_TrayCallback(self.tray_open_callback)
6154+ self._tray_playpause_cb = sdl3.SDL_TrayCallback(self.tray_playpause_callback)
6155+ self._tray_next_cb = sdl3.SDL_TrayCallback(self.tray_next_callback)
6156+ self._tray_quit_cb = sdl3.SDL_TrayCallback(self.tray_quit_callback)
6157+ self._tray_prev_cb = sdl3.SDL_TrayCallback(self.tray_prev_callback)
6158+
6159+ sdl3.SDL_SetTrayEntryCallback(open_entry, self._tray_open_cb, None)
6160+ sdl3.SDL_SetTrayEntryCallback(playpause_entry, self._tray_playpause_cb, None)
6161+ sdl3.SDL_SetTrayEntryCallback(next_entry, self._tray_next_cb, None)
6162+ sdl3.SDL_SetTrayEntryCallback(quit_entry, self._tray_quit_cb, None)
6163+ sdl3.SDL_SetTrayEntryCallback(prev_entry, self._tray_prev_cb, None)
6164+
60316165
60326166 def coll(self, r: list[int]) -> bool:
60336167 return r[0] < self.inp.mouse_position[0] <= r[0] + r[2] and r[1] <= self.inp.mouse_position[1] <= r[1] + r[3]
@@ -44955,6 +45089,10 @@ def dev_mode_disable_save_state() -> None:
4495545089 tauon.raise_window()
4495645090 tauon.requested_raise = False
4495745091
45092+ if tauon.requested_tray:
45093+ tauon.init_sdl_tray()
45094+ tauon.requested_tray = False
45095+
4495845096 if pctl.playing_state != PlayingState.STOPPED:
4495945097 power += 400
4496045098
0 commit comments