Skip to content

Commit bba375d

Browse files
authored
Merge pull request #211 from kdroidFilter/workaround-macos
Improve macOS Tray Behavior Documentation and Parameter Naming
2 parents c263dfe + 89848f4 commit bba375d

File tree

8 files changed

+64
-42
lines changed

8 files changed

+64
-42
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
- Improves the appearance of the tray on Linux, which previously resembled Windows 95.
2626
- Adds support for checkable items, dividers, and submenus, including nested submenus.
2727
- Supports primary action for Windows, macOS, and Linux.
28-
- On Windows and macOS, the primary action is triggered by a left-click on the tray icon.
28+
- On Windows, the primary action is triggered by a left-click on the tray icon.
29+
- On macOS, left-clicking opens the tray menu (macOS convention) while right-clicking has no effect. The primary action is added as the first item in the menu.
2930
- On Linux, due to the limitations of `libappindicator`, the primary action creates an item at the top of the context menu (with a customizable label). If the context menu is empty, the library uses `gtkstatusicon` to capture the primary action without needing to add an item to the context menu.
3031
- **Single Instance Management**: Ensures that only one instance of the application can run at a time and allows restoring focus to the running instance when another instance is attempted.
3132
- **Tray Position Detection**: Allows determining the position of the system tray, which helps in positioning related windows appropriately.
@@ -53,7 +54,7 @@ To use the ComposeTray library, add it as a dependency in your `build.gradle.kts
5354

5455
```kotlin
5556
dependencies {
56-
implementation("io.github.kdroidfilter:composenativetray:0.6.0")
57+
implementation("io.github.kdroidfilter:composenativetray:<version>")
5758
}
5859
```
5960

@@ -79,7 +80,7 @@ application {
7980
primaryAction = {
8081
Log.i(logTag, "Primary action triggered")
8182
},
82-
primaryActionLinuxLabel = "Open Application"
83+
primaryActionLabel = "Open Application"
8384
) {
8485
SubMenu(label = "Options") {
8586
Item(label = "Setting 1") {

demo/src/jvmMain/kotlin/com/kdroid/composetray/demo/DemoAdaptivePositionWindows.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import androidx.compose.ui.window.Window
1414
import androidx.compose.ui.window.application
1515
import androidx.compose.ui.window.rememberWindowState
1616
import com.kdroid.composetray.tray.api.Tray
17-
import com.kdroid.composetray.utils.IconRenderProperties
1817
import com.kdroid.composetray.utils.SingleInstanceManager
1918
import com.kdroid.composetray.utils.getTrayPosition
2019
import com.kdroid.composetray.utils.getTrayWindowPosition
@@ -65,7 +64,7 @@ fun main() = application {
6564
isWindowVisible = true
6665
Log.i(logTag, "On Primary Clicked")
6766
},
68-
primaryActionLinuxLabel = "Open the Application",
67+
primaryActionLabel = "Open the Application",
6968
tooltip = "My Application"
7069
) {
7170

demo/src/jvmMain/kotlin/com/kdroid/composetray/demo/DemoWithContextMenu.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import androidx.compose.ui.graphics.Color
1313
import androidx.compose.ui.window.Window
1414
import androidx.compose.ui.window.application
1515
import com.kdroid.composetray.tray.api.Tray
16-
import com.kdroid.composetray.utils.IconRenderProperties
1716
import com.kdroid.composetray.utils.SingleInstanceManager
1817
import com.kdroid.composetray.utils.getTrayPosition
1918
import com.kdroid.kmplog.Log
@@ -61,7 +60,7 @@ fun main() = application {
6160
isWindowVisible = true
6261
Log.i(logTag, "On Primary Clicked")
6362
},
64-
primaryActionLinuxLabel = "Open the Application",
63+
primaryActionLabel = "Open the Application",
6564
tooltip = "My Application"
6665
) {
6766
// Options SubMenu

demo/src/jvmMain/kotlin/com/kdroid/composetray/demo/DemoWithoutContextMenu.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ fun main() = application {
5757
isWindowVisible = true
5858
Log.i(logTag, "On Primary Clicked")
5959
},
60-
primaryActionLinuxLabel = "Open the Application",
60+
primaryActionLabel = "Open the Application",
6161
tooltip = "My Application"
6262
)
6363
}

demo/src/jvmMain/kotlin/com/kdroid/composetray/demo/DynamicTrayMenu.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import androidx.compose.runtime.setValue
88
import androidx.compose.ui.window.Window
99
import androidx.compose.ui.window.application
1010
import com.kdroid.composetray.tray.api.Tray
11-
import com.kdroid.composetray.utils.IconRenderProperties
1211
import com.kdroid.composetray.utils.SingleInstanceManager
1312
import com.kdroid.composetray.utils.getTrayPosition
1413
import com.kdroid.kmplog.Log
@@ -64,7 +63,7 @@ fun main() = application {
6463
isWindowVisible = true
6564
Log.i(logTag, "On Primary Clicked")
6665
},
67-
primaryActionLinuxLabel = "Open the Application",
66+
primaryActionLabel = "Open the Application",
6867
tooltip = "My Application",
6968
menuContent = {
7069
Item("Change icon") {

src/commonMain/kotlin/com/kdroid/composetray/tray/api/NativeTray.kt

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,17 @@ internal class NativeTray {
3838
*/
3939
@Deprecated(
4040
message = "Use the constructor with composable icon content instead",
41-
replaceWith = ReplaceWith("NativeTray(iconContent, tooltip, primaryAction, primaryActionLinuxLabel, menuContent)")
41+
replaceWith = ReplaceWith("NativeTray(iconContent, tooltip, primaryAction, primaryActionLabel, menuContent)")
4242
)
4343
constructor(
4444
iconPath: String,
4545
windowsIconPath: String = iconPath,
4646
tooltip: String = "",
4747
primaryAction: (() -> Unit)?,
48-
primaryActionLinuxLabel: String,
48+
primaryActionLabel: String,
4949
menuContent: (TrayMenuBuilder.() -> Unit)? = null
5050
) {
51-
initializeTray(iconPath, windowsIconPath, tooltip, primaryAction, primaryActionLinuxLabel, menuContent)
51+
initializeTray(iconPath, windowsIconPath, tooltip, primaryAction, primaryActionLabel, menuContent)
5252
}
5353

5454
/**
@@ -59,7 +59,7 @@ internal class NativeTray {
5959
iconRenderProperties: IconRenderProperties = IconRenderProperties(),
6060
tooltip: String = "",
6161
primaryAction: (() -> Unit)?,
62-
primaryActionLinuxLabel: String,
62+
primaryActionLabel: String,
6363
menuContent: (TrayMenuBuilder.() -> Unit)? = null
6464
) {
6565
// Render the composable to PNG file for general use
@@ -76,15 +76,15 @@ internal class NativeTray {
7676
pngIconPath
7777
}
7878

79-
initializeTray(pngIconPath, windowsIconPath, tooltip, primaryAction, primaryActionLinuxLabel, menuContent)
79+
initializeTray(pngIconPath, windowsIconPath, tooltip, primaryAction, primaryActionLabel, menuContent)
8080
}
8181

8282
private fun initializeTray(
8383
iconPath: String,
8484
windowsIconPath: String,
8585
tooltip: String,
8686
primaryAction: (() -> Unit)?,
87-
primaryActionLinuxLabel: String,
87+
primaryActionLabel: String,
8888
menuContent: (TrayMenuBuilder.() -> Unit)? = null
8989
) {
9090
trayScope.launch {
@@ -94,7 +94,7 @@ internal class NativeTray {
9494
when (os) {
9595
LINUX -> {
9696
Log.d("NativeTray", "Initializing Linux tray with icon path: $iconPath")
97-
LinuxTrayInitializer.initialize(iconPath, tooltip, primaryAction, primaryActionLinuxLabel, menuContent)
97+
LinuxTrayInitializer.initialize(iconPath, tooltip, primaryAction, primaryActionLabel, menuContent)
9898
trayInitialized = true
9999
}
100100

@@ -146,22 +146,22 @@ internal class NativeTray {
146146
* @param windowsIconPath The file path to the tray icon specifically for Windows. Defaults to the value of `iconPath`.
147147
* @param tooltip The tooltip text to be displayed when the user hovers over the tray icon.
148148
* @param primaryAction An optional callback to be invoked when the tray icon is clicked (handled only on specific platforms).
149-
* @param primaryActionLinuxLabel The label for the primary action on Linux. Defaults to "Open".
149+
* @param primaryActionLabel The label for the primary action on Linux and macOS. Defaults to "Open".
150150
* @param menuContent A lambda that builds the tray menu using a `TrayMenuBuilder`. Define the menu structure, including items, checkable items, dividers, and submenus.
151151
*
152152
* @deprecated Use the version with composable icon content instead
153153
*/
154154
@Deprecated(
155155
message = "Use the version with composable icon content instead",
156-
replaceWith = ReplaceWith("Tray(iconContent, tooltip, primaryAction, primaryActionLinuxLabel, menuContent)")
156+
replaceWith = ReplaceWith("Tray(iconContent, tooltip, primaryAction, primaryActionLabel, menuContent)")
157157
)
158158
@Composable
159159
fun ApplicationScope.Tray(
160160
iconPath: String,
161161
windowsIconPath: String = iconPath,
162162
tooltip: String,
163163
primaryAction: (() -> Unit)? = null,
164-
primaryActionLinuxLabel: String = "Open",
164+
primaryActionLabel: String = "Open",
165165
menuContent: (TrayMenuBuilder.() -> Unit)? = null
166166
) {
167167
val absoluteIconPath = remember(iconPath) { extractToTempIfDifferent(iconPath)?.absolutePath.orEmpty() }
@@ -174,15 +174,15 @@ fun ApplicationScope.Tray(
174174
absoluteWindowsIconPath,
175175
tooltip,
176176
primaryAction,
177-
primaryActionLinuxLabel,
177+
primaryActionLabel,
178178
menuContent
179179
) {
180180
val tray = NativeTray(
181181
iconPath = absoluteIconPath,
182182
windowsIconPath = absoluteWindowsIconPath,
183183
tooltip = tooltip,
184184
primaryAction = primaryAction,
185-
primaryActionLinuxLabel = primaryActionLinuxLabel,
185+
primaryActionLabel = primaryActionLabel,
186186
menuContent = menuContent
187187
)
188188

@@ -201,7 +201,7 @@ fun ApplicationScope.Tray(
201201
* @param iconRenderProperties Properties for rendering the icon.
202202
* @param tooltip The tooltip text to be displayed when the user hovers over the tray icon.
203203
* @param primaryAction An optional callback to be invoked when the tray icon is clicked (handled only on specific platforms).
204-
* @param primaryActionLinuxLabel The label for the primary action on Linux. Defaults to "Open".
204+
* @param primaryActionLabel The label for the primary action on Linux and macOS. Defaults to "Open".
205205
* @param menuContent A lambda that builds the tray menu using a `TrayMenuBuilder`. Define the menu structure, including items, checkable items, dividers, and submenus.
206206
*/
207207
@Composable
@@ -210,7 +210,7 @@ fun ApplicationScope.Tray(
210210
iconRenderProperties: IconRenderProperties = IconRenderProperties.forCurrentOperatingSystem(),
211211
tooltip: String,
212212
primaryAction: (() -> Unit)? = null,
213-
primaryActionLinuxLabel: String = "Open",
213+
primaryActionLabel: String = "Open",
214214
menuContent: (TrayMenuBuilder.() -> Unit)? = null,
215215
) {
216216
// Calculate a hash of the rendered composable content to detect changes
@@ -221,7 +221,7 @@ fun ApplicationScope.Tray(
221221
iconRenderProperties,
222222
tooltip,
223223
primaryAction,
224-
primaryActionLinuxLabel,
224+
primaryActionLabel,
225225
menuContent,
226226
contentHash, // Use the content hash as an implicit key
227227
) {
@@ -230,7 +230,7 @@ fun ApplicationScope.Tray(
230230
iconRenderProperties = iconRenderProperties,
231231
tooltip = tooltip,
232232
primaryAction = primaryAction,
233-
primaryActionLinuxLabel = primaryActionLinuxLabel,
233+
primaryActionLabel = primaryActionLabel,
234234
menuContent = menuContent,
235235
)
236236

src/commonMain/kotlin/com/kdroid/composetray/tray/impl/AwtTrayInitializer.kt

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package com.kdroid.composetray.tray.impl
22

33
import com.kdroid.composetray.menu.api.TrayMenuBuilder
44
import com.kdroid.composetray.menu.impl.AwtTrayMenuBuilderImpl
5+
import io.github.kdroidfilter.platformtools.OperatingSystem
6+
import io.github.kdroidfilter.platformtools.getOperatingSystem
57
import java.awt.PopupMenu
68
import java.awt.SystemTray
79
import java.awt.Toolkit
@@ -42,24 +44,46 @@ object AwtTrayInitializer {
4244

4345
// Create the tray icon
4446
val trayImage = Toolkit.getDefaultToolkit().getImage(iconPath)
47+
4548
val newTrayIcon = TrayIcon(trayImage, tooltip, popupMenu).apply {
4649
isImageAutoSize = true
4750

48-
// Handle the left-click if specified
49-
onLeftClick?.let { clickAction ->
50-
addMouseListener(object : MouseAdapter() {
51-
override fun mouseClicked(e: MouseEvent) {
52-
if (e.button == MouseEvent.BUTTON1) {
53-
clickAction()
51+
// On macOS, we cannot easily override the default left-click behavior
52+
// when a popup menu is attached. We have two options:
53+
// 1. Accept that left-click opens the menu (macOS convention)
54+
// 2. Use a more complex native implementation via JNA
55+
56+
if (getOperatingSystem() == OperatingSystem.MACOS) {
57+
// For macOS, add primary action as the first menu item if both menu and action exist
58+
if (onLeftClick != null && menuContent != null) {
59+
// The primary action will be added as the first menu item
60+
// This is handled in the menu building phase
61+
}
62+
} else {
63+
// For other platforms, handle left-click normally
64+
onLeftClick?.let { clickAction ->
65+
addMouseListener(object : MouseAdapter() {
66+
override fun mouseClicked(e: MouseEvent) {
67+
if (e.button == MouseEvent.BUTTON1) {
68+
clickAction()
69+
}
5470
}
55-
}
56-
})
71+
})
72+
}
5773
}
5874
}
5975

6076
// Add the menu content if specified
6177
menuContent?.let {
62-
AwtTrayMenuBuilderImpl(popupMenu, newTrayIcon).apply(it)
78+
// For macOS, prepend the primary action to the menu if it exists
79+
if (getOperatingSystem() == OperatingSystem.MACOS && onLeftClick != null) {
80+
val menuBuilder = AwtTrayMenuBuilderImpl(popupMenu, newTrayIcon)
81+
menuBuilder.Item("Open", true) { onLeftClick.invoke() }
82+
menuBuilder.Divider()
83+
menuBuilder.apply(it)
84+
} else {
85+
AwtTrayMenuBuilderImpl(popupMenu, newTrayIcon).apply(it)
86+
}
6387
}
6488

6589
// Add the icon to the system tray

src/commonMain/kotlin/com/kdroid/composetray/tray/impl/LinuxTrayInitializer.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ object LinuxTrayInitializer {
8585
iconPath: String,
8686
tooltip: String,
8787
primaryAction: (() -> Unit)?,
88-
primaryActionLinuxLabel: String,
88+
primaryActionLabel: String,
8989
menuContent: (TrayMenuBuilder.() -> Unit)?
9090
) {
9191
cleanPreviousInstance()
@@ -97,7 +97,7 @@ object LinuxTrayInitializer {
9797
iconPath = iconPath,
9898
tooltip = tooltip,
9999
primaryAction = primaryAction,
100-
primaryActionLinuxLabel = primaryActionLinuxLabel,
100+
primaryActionLabel = primaryActionLabel,
101101
menuContent = menuContent
102102
)
103103
} else if (primaryAction != null) {
@@ -134,7 +134,7 @@ object LinuxTrayInitializer {
134134
iconPath: String,
135135
tooltip: String,
136136
primaryAction: (() -> Unit)?,
137-
primaryActionLinuxLabel: String,
137+
primaryActionLabel: String,
138138
menuContent: (TrayMenuBuilder.() -> Unit)?
139139
) {
140140
try {
@@ -152,7 +152,7 @@ object LinuxTrayInitializer {
152152
currentMenuBuilder.set(trayMenuBuilder)
153153

154154
primaryAction?.let {
155-
addPrimaryActionMenuItem(trayMenuBuilder, it, primaryActionLinuxLabel)
155+
addPrimaryActionMenuItem(trayMenuBuilder, it, primaryActionLabel)
156156
}
157157

158158
trayMenuBuilder.apply(menuContent!!)
@@ -170,9 +170,9 @@ object LinuxTrayInitializer {
170170
private fun addPrimaryActionMenuItem(
171171
trayMenuBuilder: LinuxTrayMenuBuilderImpl,
172172
primaryAction: () -> Unit,
173-
primaryActionLinuxLabel: String
173+
primaryActionLabel: String
174174
) {
175-
trayMenuBuilder.Item(primaryActionLinuxLabel) {
175+
trayMenuBuilder.Item(primaryActionLabel) {
176176
saveTrayIconPosition()
177177
primaryAction.invoke()
178178
}
@@ -229,4 +229,4 @@ object LinuxTrayInitializer {
229229
}
230230
}
231231

232-
}
232+
}

0 commit comments

Comments
 (0)