Skip to content

Commit a93e445

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents 1653fd3 + 0f2f192 commit a93e445

11 files changed

Lines changed: 285 additions & 94 deletions

File tree

README.md

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
- **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.
2323
- **Tray Position Detection**: Allows determining the position of the system tray, which helps in positioning related windows appropriately.
2424
- **Compose Recomposition Support**: The tray supports Compose recomposition, making it possible to dynamically show or hide the tray icon, for example:
25-
25+
2626
<p align="center">
2727
<img src="screenshots/demo.gif" alt="demo">
2828
</p>
@@ -63,7 +63,7 @@ application {
6363
Icon(
6464
Icons.Default.Favorite,
6565
contentDescription = "",
66-
tint = Color.White,
66+
tint = Color.Unspecified, // If not defined icon will be tinted with LocalContentColor.current
6767
modifier = Modifier.fillMaxSize() // This is crucial!
6868
)
6969
},
@@ -131,6 +131,41 @@ application {
131131
- **dispose**: Call to remove the system tray icon and exit gracefully.
132132
- **Primary Action**: An action triggered by a left-click on the tray icon (Windows and macOS) or as a top item in the context menu (Linux).
133133

134+
### 🎨 Icon Rendering Customization
135+
136+
The `IconRenderProperties` class allows you to customize how your tray icon is rendered. This is an optional parameter when using the Tray component, with a default value already provided:
137+
138+
```kotlin
139+
iconRenderProperties = IconRenderProperties.forCurrentOperatingSystem()
140+
```
141+
142+
#### Available Factory Methods:
143+
144+
1. **forCurrentOperatingSystem()**: Creates properties optimized for the current operating system:
145+
- Windows: 32x32 pixels
146+
- macOS: 44x44 pixels
147+
- Linux: 24x24 pixels
148+
149+
```kotlin
150+
IconRenderProperties.forCurrentOperatingSystem(
151+
sceneWidth = 192, // Optional: Width of the compose scene (default: 192)
152+
sceneHeight = 192, // Optional: Height of the compose scene (default: 192)
153+
density = Density(2f) // Optional: Density for the compose scene (default: 2f)
154+
)
155+
```
156+
157+
2. **withoutScalingAndAliasing()**: Creates properties that don't force icon scaling and aliasing:
158+
159+
```kotlin
160+
IconRenderProperties.withoutScalingAndAliasing(
161+
sceneWidth = 192, // Optional: Width of the compose scene (default: 192)
162+
sceneHeight = 192, // Optional: Height of the compose scene (default: 192)
163+
density = Density(2f) // Optional: Density for the compose scene (default: 2f)
164+
)
165+
```
166+
167+
This allows you to fine-tune how your icon appears in the system tray across different platforms.
168+
134169
### 🔄 Single Instance Management
135170

136171
The `SingleInstanceManager` ensures that only one instance of the application is running at a time. When a second instance tries to start, it sends a restore request to the existing instance to bring it to the foreground.
@@ -153,6 +188,22 @@ if (!isSingleInstance) {
153188

154189
In this example, the `SingleInstanceManager` will check if an instance is already running. If not, it will acquire the lock and start watching for restore requests. If an instance is already running, it will send a restore request to bring the existing window to the foreground, allowing you to focus on the already-running application rather than starting a new instance.
155190

191+
#### Configuration
192+
193+
`SingleInstanceManager` can be configured:
194+
```kotlin
195+
SingleInstanceManager.configuration = Configuration(
196+
lockFilesDir = Paths.get("path/to/your/app/data/dir/single_instance_manager"),
197+
appIdentifier = "app_id"
198+
)
199+
```
200+
This is useful when you need single-instance management but want finer-grained control.
201+
202+
By specifying a custom `lockFilesDir`, you limit the scope of single-instance management
203+
from every instance of your app on the whole system to only those that share the specified data directory.
204+
205+
Setting the custom `appIdentifier` can be used for even more granular control.
206+
156207
### 📌 Tray Position Detection
157208

158209
The `getTrayPosition()` function allows you to determine the current position of the system tray on the screen. This information can be useful for aligning application windows relative to the tray icon.
@@ -213,4 +264,4 @@ Feel free to open issues or pull requests if you find any bugs or have suggestio
213264

214265
## 🙏 Acknowledgements
215266

216-
This library is developed and maintained by Elie G, aiming to provide an easy and cross-platform system tray solution for Kotlin applications.
267+
This library is developed and maintained by Elie G, aiming to provide an easy and cross-platform system tray solution for Kotlin applications.

build.gradle.kts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ plugins {
1010
}
1111

1212
group = "com.kdroid.composenativetray"
13-
version = "0.6.0"
13+
val ref = System.getenv("GITHUB_REF") ?: ""
14+
val version = if (ref.startsWith("refs/tags/")) {
15+
val tag = ref.removePrefix("refs/tags/")
16+
if (tag.startsWith("v")) tag.substring(1) else tag
17+
} else "dev"
1418

1519
repositories {
1620
mavenCentral()
@@ -48,7 +52,7 @@ mavenPublishing {
4852
coordinates(
4953
groupId = "io.github.kdroidfilter",
5054
artifactId = "composenativetray",
51-
version = version.toString()
55+
version = version
5256
)
5357

5458
// Configure POM metadata for the published artifact

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.kdroid.composetray.demo
2-
import androidx.compose.foundation.Image
32
import androidx.compose.foundation.layout.fillMaxSize
3+
import androidx.compose.material.Icon
44
import androidx.compose.material.icons.Icons
55
import androidx.compose.material.icons.filled.Notifications
66
import androidx.compose.runtime.getValue
@@ -9,13 +9,12 @@ import androidx.compose.runtime.remember
99
import androidx.compose.runtime.setValue
1010
import androidx.compose.ui.Modifier
1111
import androidx.compose.ui.graphics.Color
12-
import androidx.compose.ui.graphics.ColorFilter
13-
import androidx.compose.ui.res.painterResource
1412
import androidx.compose.ui.unit.dp
1513
import androidx.compose.ui.window.Window
1614
import androidx.compose.ui.window.application
1715
import androidx.compose.ui.window.rememberWindowState
1816
import com.kdroid.composetray.tray.api.Tray
17+
import com.kdroid.composetray.utils.IconRenderProperties
1918
import com.kdroid.composetray.utils.SingleInstanceManager
2019
import com.kdroid.composetray.utils.getTrayPosition
2120
import com.kdroid.composetray.utils.getTrayWindowPosition
@@ -55,11 +54,11 @@ fun main() = application {
5554
// Use alwaysShowTray as a key to force recomposition when it changes
5655
Tray(
5756
iconContent = {
58-
Image(
57+
Icon(
5958
Icons.Default.Notifications,
6059
contentDescription = "Application Icon",
6160
modifier = Modifier.fillMaxSize(),
62-
colorFilter = ColorFilter.tint(if (alwaysShowTray) Color.White else Color.White)
61+
tint = Color.White,
6362
)
6463
},
6564
primaryAction = {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ 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
1617
import com.kdroid.composetray.utils.SingleInstanceManager
1718
import com.kdroid.composetray.utils.getTrayPosition
1819
import com.kdroid.kmplog.Log
@@ -52,7 +53,7 @@ fun main() = application {
5253
Icons.Default.Favorite,
5354
contentDescription = "",
5455
// Use alwaysShowTray as a key to force recomposition when it changes
55-
tint = if (alwaysShowTray) Color.White else Color.White,
56+
tint = Color.White,
5657
modifier = Modifier.fillMaxSize()
5758
)
5859
},

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import androidx.compose.runtime.setValue
1010
import androidx.compose.ui.Modifier
1111
import androidx.compose.ui.draw.clip
1212
import androidx.compose.ui.graphics.Color
13-
import androidx.compose.ui.res.painterResource
1413
import androidx.compose.ui.unit.dp
1514
import androidx.compose.ui.window.Window
1615
import androidx.compose.ui.window.application

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ 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
1112
import com.kdroid.composetray.utils.SingleInstanceManager
1213
import com.kdroid.composetray.utils.getTrayPosition
1314
import com.kdroid.kmplog.Log
@@ -57,7 +58,6 @@ fun main() = application {
5758
painter = painterResource(icon),
5859
contentDescription = "Application Icon",
5960
// Use alwaysShowTray as a key to force recomposition when it changes
60-
colorFilter = if (alwaysShowTray) null else null
6161
)
6262
},
6363
primaryAction = {

gradle/libs.versions.toml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
[versions]
22
kermit = "2.0.5"
33
kotlin = "2.1.20"
4-
agp = "8.9.2"
54
kotlinx-coroutines = "1.10.2"
6-
compose = "1.7.3"
5+
compose = "1.8.0"
76
jna = "5.17.0"
87
platformtools = "0.2.9"
98

@@ -13,15 +12,12 @@ kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-c
1312
jna = { module = "net.java.dev.jna:jna", version.ref = "jna" }
1413
jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jna" }
1514
platformtools-core = { module = "io.github.kdroidfilter:platformtools.core", version.ref = "platformtools" }
16-
platformtools-darkmodedetector = { module = "io.github.kdroidfilter:platformtools.darkmodedetector", version.ref = "platformtools" }
1715
kmp-log = { module = "io.github.kdroidfilter:kmplog", version = "0.3.0" }
1816

1917
[plugins]
2018

2119
multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
22-
android-library = { id = "com.android.library", version.ref = "agp" }
2320
compose = { id = "org.jetbrains.compose", version.ref = "compose" }
2421
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
25-
android-application = { id = "com.android.application", version.ref = "agp" }
2622
vannitktech-maven-publish = {id = "com.vanniktech.maven.publish", version = "0.31.0"}
2723
dokka = { id = "org.jetbrains.dokka" , version = "2.0.0"}

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

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import com.kdroid.composetray.tray.impl.AwtTrayInitializer
99
import com.kdroid.composetray.tray.impl.LinuxTrayInitializer
1010
import com.kdroid.composetray.tray.impl.WindowsTrayInitializer
1111
import com.kdroid.composetray.utils.ComposableIconUtils
12-
12+
import com.kdroid.composetray.utils.IconRenderProperties
1313
import com.kdroid.composetray.utils.extractToTempIfDifferent
1414
import com.kdroid.kmplog.Log
1515
import com.kdroid.kmplog.d
@@ -47,37 +47,22 @@ internal class NativeTray {
4747
*/
4848
constructor(
4949
iconContent: @Composable () -> Unit,
50+
iconRenderProperties: IconRenderProperties = IconRenderProperties(),
5051
tooltip: String = "",
5152
primaryAction: (() -> Unit)?,
5253
primaryActionLinuxLabel: String,
53-
menuContent: (TrayMenuBuilder.() -> Unit)? = null,
54-
iconWidth: Int = 200,
55-
iconHeight: Int = 200,
56-
density: Float = 2f
54+
menuContent: (TrayMenuBuilder.() -> Unit)? = null
5755
) {
5856
// Render the composable to PNG file for general use
59-
val pngIconPath = ComposableIconUtils.renderComposableToPngFile(
60-
width = iconWidth,
61-
height = iconHeight,
62-
density = density,
63-
content = iconContent
64-
)
57+
val pngIconPath = ComposableIconUtils.renderComposableToPngFile(iconRenderProperties, iconContent)
6558
Log.d("NativeTray", "Generated PNG icon path: $pngIconPath")
6659

6760
// For Windows, we need an ICO file
6861
val windowsIconPath = if (getOperatingSystem() == OperatingSystem.WINDOWS) {
6962
// Create a temporary ICO file
70-
val tempFile = createTempFile(suffix = ".ico")
71-
val icoData = ComposableIconUtils.renderComposableToIcoBytes(
72-
width = iconWidth,
73-
height = iconHeight,
74-
density = density,
75-
content = iconContent
76-
)
77-
tempFile.writeBytes(icoData)
78-
val path = tempFile.absolutePath
79-
Log.d("NativeTray", "Generated Windows ICO path: $path")
80-
path
63+
ComposableIconUtils.renderComposableToIcoFile(iconRenderProperties, iconContent).also {
64+
Log.d("NativeTray", "Generated Windows ICO path: $it")
65+
}
8166
} else {
8267
pngIconPath
8368
}
@@ -193,6 +178,7 @@ fun ApplicationScope.Tray(
193178
* This version accepts a Composable for the icon instead of file paths.
194179
*
195180
* @param iconContent A Composable function that defines the icon to be displayed in the tray.
181+
* @param iconRenderProperties Properties for rendering the icon.
196182
* @param tooltip The tooltip text to be displayed when the user hovers over the tray icon.
197183
* @param primaryAction An optional callback to be invoked when the tray icon is clicked (handled only on specific platforms).
198184
* @param primaryActionLinuxLabel The label for the primary action on Linux. Defaults to "Open".
@@ -201,16 +187,18 @@ fun ApplicationScope.Tray(
201187
@Composable
202188
fun ApplicationScope.Tray(
203189
iconContent: @Composable () -> Unit,
190+
iconRenderProperties: IconRenderProperties = IconRenderProperties.forCurrentOperatingSystem(),
204191
tooltip: String,
205192
primaryAction: (() -> Unit)? = null,
206193
primaryActionLinuxLabel: String = "Open",
207194
menuContent: (TrayMenuBuilder.() -> Unit)? = null,
208195
) {
209196
// Calculate a hash of the rendered composable content to detect changes
210-
val contentHash = ComposableIconUtils.calculateContentHash(content = iconContent)
197+
val contentHash = ComposableIconUtils.calculateContentHash(iconRenderProperties, iconContent)
211198

212199
DisposableEffect(
213200
iconContent,
201+
iconRenderProperties,
214202
tooltip,
215203
primaryAction,
216204
primaryActionLinuxLabel,
@@ -219,6 +207,7 @@ fun ApplicationScope.Tray(
219207
) {
220208
NativeTray(
221209
iconContent = iconContent,
210+
iconRenderProperties = iconRenderProperties,
222211
tooltip = tooltip,
223212
primaryAction = primaryAction,
224213
primaryActionLinuxLabel = primaryActionLinuxLabel,

0 commit comments

Comments
 (0)