Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,19 @@ fun main() = application {

// Basic state for tray icon
var currentTrayIcon by remember { mutableStateOf(Icons.Default.Notifications) }

// States for menu item icons
var weatherIcon by remember { mutableStateOf(Icons.Default.WbSunny) }
var musicIcon by remember { mutableStateOf(Icons.Default.MusicNote) }
var settingsIcon by remember { mutableStateOf(Icons.Default.Settings) }

// States for theme and features
var isDarkTheme by remember { mutableStateOf(false) }
var isWeatherEnabled by remember { mutableStateOf(true) }
var isMusicEnabled by remember { mutableStateOf(true) }

// Counter to demonstrate onMenuOpened callback
var menuOpenCount by remember { mutableIntStateOf(0) }

// Always create the Tray composable, but make it conditional on visibility
val showTray = alwaysShowTray || !isWindowVisible
Expand All @@ -77,7 +80,11 @@ fun main() = application {
primaryAction = {
isWindowVisible = true
println("$logTag: Primary action clicked")
}
},
onMenuOpened = {
menuOpenCount++
println("$logTag: Menu opened (count: $menuOpenCount)")
},
) {
// Weather submenu with dynamic icon
SubMenu(label = "Weather", icon = weatherIcon) {
Expand Down Expand Up @@ -249,6 +256,12 @@ fun main() = application {

Text(
"This demo showcases dynamic icon changes in the system tray menu.",
modifier = Modifier.padding(bottom = 8.dp)
)

Text(
"Tray menu opened $menuOpenCount times",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(bottom = 24.dp)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ internal object LinuxNativeBridge {
callback: Runnable?,
)

/** Register a callback invoked when the menu is about to be shown. */
@JvmStatic external fun nativeSetMenuOpenedCallback(
handle: Long,
callback: Runnable?,
)

// -- Click position ----------------------------------------------------------

/** Writes [x, y] into outXY. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ internal class LinuxTrayManager(
private var iconPath: String,
private var tooltip: String = "",
private var onLeftClick: (() -> Unit)? = null,
private var onMenuOpened: (() -> Unit)? = null,
) {
companion object {
// Ensures only one systray runtime is active at a time
Expand Down Expand Up @@ -99,6 +100,7 @@ internal class LinuxTrayManager(
newTooltip: String,
newOnLeftClick: (() -> Unit)?,
newMenuItems: List<MenuItem>?,
newOnMenuOpened: (() -> Unit)? = null,
) {
val iconChanged: Boolean
val tooltipChanged: Boolean
Expand All @@ -109,6 +111,7 @@ internal class LinuxTrayManager(
iconPath = newIconPath
tooltip = newTooltip
onLeftClick = newOnLeftClick
onMenuOpened = newOnMenuOpened
if (newMenuItems != null) {
menuItems.clear()
menuItems.addAll(newMenuItems)
Expand Down Expand Up @@ -174,6 +177,12 @@ internal class LinuxTrayManager(
},
)

// Set menu-opened callback
native.nativeSetMenuOpenedCallback(
trayHandle,
JniRunnable { onMenuOpened?.invoke() },
)

// Build menu before starting the loop
rebuildMenu()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ internal object MacNativeBridge {
callback: Runnable?,
)

@JvmStatic external fun nativeSetMenuOpenedCallback(
handle: Long,
callback: Runnable?,
)

@JvmStatic external fun nativeSetTrayMenu(
trayHandle: Long,
menuHandle: Long,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal class MacTrayManager(
private var iconPath: String,
private var tooltip: String = "",
onLeftClick: (() -> Unit)? = null,
onMenuOpened: (() -> Unit)? = null,
) {
private var trayHandle: Long = 0L
private var menuHandle: Long = 0L
Expand All @@ -33,6 +34,7 @@ internal class MacTrayManager(
private val submenuHandles: MutableList<Pair<Long, Int>> = mutableListOf()

private val onLeftClickCallback = mutableStateOf(onLeftClick)
private var onMenuOpenedCallback: (() -> Unit)? = onMenuOpened

// Top level MenuItem class
data class MenuItem(
Expand Down Expand Up @@ -72,6 +74,7 @@ internal class MacTrayManager(
newTooltip: String,
newOnLeftClick: (() -> Unit)?,
newMenuItems: List<MenuItem>? = null,
newOnMenuOpened: (() -> Unit)? = null,
) {
lock.withLock {
if (!running.get() || trayHandle == 0L) return
Expand All @@ -80,11 +83,13 @@ internal class MacTrayManager(
val iconChanged = this.iconPath != newIconPath
val tooltipChanged = this.tooltip != newTooltip
val onLeftClickChanged = this.onLeftClickCallback.value != newOnLeftClick
val onMenuOpenedChanged = this.onMenuOpenedCallback != newOnMenuOpened

// Update icon path and tooltip
this.iconPath = newIconPath
this.tooltip = newTooltip
this.onLeftClickCallback.value = newOnLeftClick
this.onMenuOpenedCallback = newOnMenuOpened

if (iconChanged) {
MacNativeBridge.nativeSetTrayIcon(trayHandle, newIconPath)
Expand All @@ -95,6 +100,9 @@ internal class MacTrayManager(
if (onLeftClickChanged) {
initializeOnLeftClickCallback()
}
if (onMenuOpenedChanged) {
initializeOnMenuOpenedCallback()
}

// Update menu items if provided
if (newMenuItems != null) {
Expand Down Expand Up @@ -167,6 +175,9 @@ internal class MacTrayManager(
throw IllegalStateException("Failed to initialize tray: $initResult")
}

// Set menu-opened callback after init (TrayContext must exist)
initializeOnMenuOpenedCallback()

// Signal that initialization is complete
initLatch.countDown()

Expand Down Expand Up @@ -218,6 +229,26 @@ internal class MacTrayManager(
}
}

private fun initializeOnMenuOpenedCallback() {
if (trayHandle == 0L) return

val callback = onMenuOpenedCallback
if (callback != null) {
MacNativeBridge.nativeSetMenuOpenedCallback(
trayHandle,
Runnable {
mainScope?.launch {
ioScope?.launch {
callback()
}
}
},
)
} else {
MacNativeBridge.nativeSetMenuOpenedCallback(trayHandle, null)
}
}

private fun initializeTrayMenu() {
if (trayHandle == 0L) return

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ internal object WindowsNativeBridge {
callback: Runnable?,
)

@JvmStatic external fun nativeSetMenuOpenedCallback(
handle: Long,
callback: Runnable?,
)

@JvmStatic external fun nativeSetTrayMenu(
trayHandle: Long,
menuHandle: Long,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal class WindowsTrayManager(
private var iconPath: String,
private var tooltip: String = "",
private var onLeftClick: (() -> Unit)? = null,
private var onMenuOpened: (() -> Unit)? = null,
) {
private var trayHandle: Long = 0L
private val running = AtomicBoolean(false)
Expand Down Expand Up @@ -50,6 +51,7 @@ internal class WindowsTrayManager(
val iconPath: String,
val tooltip: String,
val onLeftClick: (() -> Unit)?,
val onMenuOpened: (() -> Unit)?,
val menuItems: List<MenuItem>,
)

Expand All @@ -69,7 +71,7 @@ internal class WindowsTrayManager(
updateLock.withLock {
if (initialized.get()) {
log("Already initialized, delegating to update()")
update(iconPath, tooltip, onLeftClick, menuItems)
update(iconPath, tooltip, onLeftClick, onMenuOpened, menuItems)
return
}

Expand Down Expand Up @@ -103,6 +105,9 @@ internal class WindowsTrayManager(
throw RuntimeException("Failed to initialize tray: $initResult")
}

// Set menu-opened callback after init (TrayContext must exist)
setupMenuOpenedCallback(handle)

initialized.set(true)

// Signal that initialization is complete before entering the loop
Expand Down Expand Up @@ -141,6 +146,7 @@ internal class WindowsTrayManager(
newIconPath: String,
newTooltip: String,
newOnLeftClick: (() -> Unit)?,
newOnMenuOpened: (() -> Unit)?,
newMenuItems: List<MenuItem>,
) {
log("update() called - icon: $newIconPath, tooltip: $newTooltip, menuItems: ${newMenuItems.size}")
Expand All @@ -150,13 +156,14 @@ internal class WindowsTrayManager(
iconPath = newIconPath
tooltip = newTooltip
onLeftClick = newOnLeftClick
onMenuOpened = newOnMenuOpened
initialize(newMenuItems)
return
}

// Queue the update to be processed on the tray thread
synchronized(updateQueueLock) {
updateQueue.add(UpdateRequest(newIconPath, newTooltip, newOnLeftClick, newMenuItems))
updateQueue.add(UpdateRequest(newIconPath, newTooltip, newOnLeftClick, newOnMenuOpened, newMenuItems))
updateQueueLock.notify()
}
}
Expand Down Expand Up @@ -357,6 +364,7 @@ internal class WindowsTrayManager(
iconPath = update.iconPath
tooltip = update.tooltip
onLeftClick = update.onLeftClick
onMenuOpened = update.onMenuOpened

val handle = trayHandle
if (handle == 0L) return
Expand All @@ -371,6 +379,7 @@ internal class WindowsTrayManager(
// Set up new callbacks and menu
setupLeftClickCallback(handle)
setupMenu(handle, update.menuItems)
setupMenuOpenedCallback(handle)

// Update the native tray
log("Calling nativeUpdateTray()")
Expand Down Expand Up @@ -416,6 +425,32 @@ internal class WindowsTrayManager(
}
}

private fun setupMenuOpenedCallback(handle: Long) {
val callback = onMenuOpened
if (callback != null) {
log("Setting up menu opened callback")
WindowsNativeBridge.nativeSetMenuOpenedCallback(
handle,
Runnable {
log("Menu opened callback invoked")
try {
mainScope?.launch {
ioScope?.launch {
callback()
}
}
} catch (e: Exception) {
log("Error in menu opened callback: ${e.message}")
e.printStackTrace()
}
},
)
} else {
log("No menu opened callback set")
WindowsNativeBridge.nativeSetMenuOpenedCallback(handle, null)
}
}

private fun setupMenu(
handle: Long,
menuItems: List<MenuItem>,
Expand Down
Loading
Loading