Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
927096e
Refactor Linux tray implementation: replace AppIndicator/GTK-based ap…
kdroidFilter Jul 15, 2025
417f29a
Refactor LinuxTrayMenuBuilderImpl: improve checkable property handlin…
kdroidFilter Jul 15, 2025
64b0f86
Enhance Linux tray disposal and initialization: add `tray_exit` befor…
kdroidFilter Jul 15, 2025
833f8ab
Introduce Linux-native tray support using Qt and CMake
kdroidFilter Jul 20, 2025
1328e8b
Add Linux-specific build target to .gitignore and new `libtray` library
kdroidFilter Jul 20, 2025
c636e17
Refactor Linux tray system: introduce signal-slot mechanism for threa…
kdroidFilter Jul 20, 2025
ab4b2e1
Remove `QtAppManager` and simplify Qt-based Linux tray system: stream…
kdroidFilter Jul 20, 2025
c255b90
Ensure proper cleanup of Qt tray icon before application exit: hide a…
kdroidFilter Jul 20, 2025
e0f235d
Refactor QtTrayMenu lifecycle management: improve application and tra…
kdroidFilter Jul 20, 2025
3db9d08
Introduce cross-platform tray update functionality for improved lifec…
kdroidFilter Jul 20, 2025
3f1cbfd
Improve Qt tray menu lifecycle management: enhance cleanup by adding …
kdroidFilter Jul 20, 2025
9a8db5f
Filter out specific Qt warnings in `QtTrayMenu` by introducing a cust…
kdroidFilter Jul 20, 2025
37519c9
Refactor tray cleanup logic: streamline Qt tray icon disposal, enhanc…
kdroidFilter Jul 20, 2025
7560353
Simplify Linux tray cleanup logic: reduce `processEvents` iterations,…
kdroidFilter Jul 20, 2025
17d19a9
Refactor Windows tray lifecycle management:
kdroidFilter Jul 20, 2025
ceac420
Enhance tray recomposition logic:
kdroidFilter Jul 21, 2025
928c33f
feat(macos): add native macOS tray support
kdroidFilter Jul 21, 2025
108b8a0
Refactor tray recomposition logic:
kdroidFilter Jul 21, 2025
7c3877c
Refactor `CheckableItem` API:
kdroidFilter Jul 21, 2025
2bcbefd
refactor: improve `CheckableItem` usage with `onCheckedChange` for be…
kdroidFilter Jul 21, 2025
c937f2e
feat(macos): add dynamic theme detection for macOS menu bar
kdroidFilter Jul 21, 2025
d3e89c6
refactor(macos): remove obsolete `MenuBarModeObserver` implementation
kdroidFilter Jul 21, 2025
d61e4fc
feat: extend dark mode detection to Windows and Linux platforms
kdroidFilter Jul 21, 2025
c2ef51e
Migrate `darkmodedetector` dependency to version catalog for cleaner …
kdroidFilter Jul 21, 2025
a5f7987
Update `platformtools` to v0.5.0 and enhance Linux dark mode detection:
kdroidFilter Jul 21, 2025
7d103ec
Refactor dark mode detection:
kdroidFilter Jul 21, 2025
d9ae4d4
Add demo implementations for ImageVector and Painter-based tray icons:
kdroidFilter Jul 21, 2025
636b8f6
Replace `Deployed_code_update` with `AcademicCap` in `DemoWithImageVe…
kdroidFilter Jul 21, 2025
dcf9418
Replace `Log` with `Kermit` for logging across the project:
kdroidFilter Jul 21, 2025
fac5e19
Add logging level customization and simplify Maven publishing configu…
kdroidFilter Jul 21, 2025
35a768a
Refactor Linux tray cleanup and messaging:
kdroidFilter Jul 21, 2025
553f4c0
Add GNOME-specific tray menu workaround:
kdroidFilter Jul 21, 2025
88609d0
Switch GNOME workaround to D-Bus-based approach:
kdroidFilter Jul 21, 2025
02618d6
Add thread-safety and logging to tray management operations
kdroidFilter Jul 22, 2025
9fc35fb
fix(windows): avoid GC of tray callbacks by maintaining strong refere…
kdroidFilter Jul 22, 2025
8327467
Refactor tray logic for improved readability and performance
kdroidFilter Jul 22, 2025
4aeea47
Add StatusNotifierItem support with D-Bus and Qt integration
kdroidFilter Jul 23, 2025
37445e0
Add StatusNotifierItem support with D-Bus and Qt integration
kdroidFilter Jul 23, 2025
82d768f
Refactor SNI wrapper with modernized Qt threading and cleanup.
kdroidFilter Jul 23, 2025
baafb78
Enhance SNI wrapper initialization and disposal:
kdroidFilter Jul 24, 2025
ee9ace3
Streamline DBus tray system with improved threading and API
kdroidFilter Jul 24, 2025
4117485
Remove LinuxLib and LinuxDBus modules along with related files and co…
kdroidFilter Jul 25, 2025
fabdc19
Comment out example Kotlin implementation in `SNIWrapper` and refine …
kdroidFilter Jul 25, 2025
efc59ce
Refactor `SNIWrapper` example and enhance Linux tray resource managem…
kdroidFilter Jul 25, 2025
fad219b
Remove LinuxLib module and associated files for cleanup:
kdroidFilter Jul 25, 2025
0247d96
Enhance Linux tray menu state handling and thread safety:
kdroidFilter Jul 25, 2025
f6e4d49
Fix crash on LinuxTrayManager.kt
kdroidFilter Jul 26, 2025
010bb66
Improve `LinuxTrayManager` exception handling and optimize event loop:
kdroidFilter Jul 26, 2025
26f35a5
Refine `LinuxTrayManager` menu state updates and thread handling:
kdroidFilter Jul 26, 2025
b533e7f
Remove `SNIWrapper` example code to streamline the file and reduce re…
kdroidFilter Jul 26, 2025
61c0a30
Handle `IllegalStateException` during `shutdownHook` removal in `Linu…
kdroidFilter Jul 26, 2025
cb61b93
Replace `println` with logging utilities in `LinuxTrayManager` and `W…
kdroidFilter Jul 26, 2025
cd0544d
Update `libtray.so` binary for Linux x86-64
kdroidFilter Jul 26, 2025
17cdff2
Remove `linuxlib` module and all associated files:
kdroidFilter Jul 26, 2025
9de1e26
Introduce `requestMenuRefresh` in `LinuxTrayManager` for optimized me…
kdroidFilter Jul 26, 2025
f2684d2
Add `set_menu_item_checked` in `SNIWrapper` and incremental state upd…
kdroidFilter Jul 27, 2025
3f67da1
Add timestamp to log messages for improved traceability:
kdroidFilter Jul 27, 2025
9b678b4
Add dark mode theme detection and platform tools dependency:
kdroidFilter Jul 27, 2025
7825b6c
Add `sni_set_debug_mode` in `SNIWrapper` and configure debug mode on …
kdroidFilter Jul 27, 2025
d3f8a5b
Remove Kermit logging in demos and replace with `println`:
kdroidFilter Jul 27, 2025
da126af
Capture tray click position for precise window placement:
kdroidFilter Jul 27, 2025
8c59594
Refactor Windows tray management for stability and scalability
kdroidFilter Jul 27, 2025
e2e5da8
fix(windows): optimize message loop responsiveness and reduce CPU usage
kdroidFilter Jul 27, 2025
53f0e5d
Ensure precise tray icon positioning on Windows
kdroidFilter Jul 27, 2025
9899fc4
Refactor Linux tray logic and enhance comments for clarity
kdroidFilter Jul 27, 2025
eb4f624
feat(macos): add precise tray positioning and region detection support
kdroidFilter Jul 27, 2025
9927170
Enhance Linux tray position logic with desktop environment defaults:
kdroidFilter Jul 27, 2025
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,6 @@ bin/

.idea
tray_position.properties

linuxlib/build-x86-64
linuxlibdbus/build-x86-64
22 changes: 18 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import com.vanniktech.maven.publish.SonatypeHost
import org.jetbrains.dokka.gradle.DokkaTask

plugins {
Expand Down Expand Up @@ -38,11 +37,14 @@ kotlin {
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.ui)
implementation(libs.jna)
implementation(libs.jna.platform)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kmp.log)
implementation(libs.kotlinx.coroutines.swing)
implementation(libs.platformtools.core)
implementation(libs.platformtools.darkmodedetector)

}
}

Expand All @@ -54,8 +56,20 @@ val buildWin: TaskProvider<Exec> = tasks.register<Exec>("buildNativeWin") {
commandLine("cmd", "/c", "build.bat")
}

val buildMac: TaskProvider<Exec> = tasks.register<Exec>("buildNativeMac") {
onlyIf { System.getProperty("os.name").startsWith("Mac") }
workingDir(rootDir.resolve("maclib"))
commandLine("sh", "build.sh")
}

val buildLinux: TaskProvider<Exec> = tasks.register<Exec>("buildNativeLinux") {
// onlyIf { System.getProperty("os.name").toLowerCase().contains("linux") }
// workingDir(rootDir.resolve("linuxlibdbus"))
//// commandLine("./build.sh")
}

tasks.register("buildNativeLibraries") {
dependsOn(buildWin)
dependsOn(buildWin, buildLinux, buildMac)
}

mavenPublishing {
Expand Down Expand Up @@ -95,7 +109,7 @@ mavenPublishing {
}

// Configure publishing to Maven Central
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
publishToMavenCentral()


// Enable GPG signing for all publications
Expand Down
26 changes: 24 additions & 2 deletions demo/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.gradle.api.tasks.JavaExec

plugins {
alias(libs.plugins.multiplatform)
Expand All @@ -18,8 +19,10 @@ kotlin {
implementation(compose.desktop.currentOs)
implementation(compose.components.resources)
implementation(compose.material3)
implementation("org.jetbrains.compose.material:material-icons-core:1.7.3")
implementation(libs.kmp.log)
implementation(compose.materialIconsExtended)
implementation(libs.kermit)
implementation(libs.platformtools.darkmodedetector)

}
}
}
Expand All @@ -41,3 +44,22 @@ compose.desktop {
}
}
}

// Task to build native libraries and run the demo
tasks.register("buildAndRunDemo") {
// Depend on the buildNativeLibraries task from the root project
dependsOn(rootProject.tasks.named("buildNativeLibraries"))

// This task doesn't do anything by itself, it just depends on buildNativeLibraries
// and will be followed by the run task
doLast {
println("Native libraries built successfully. Starting demo application...")
}

// Make sure the run task is executed after this task
finalizedBy(tasks.named("run"))

// Description for the task
description = "Builds the native libraries and then runs the demo application"
group = "application"
}
17 changes: 11 additions & 6 deletions demo/src/jvmMain/kotlin/com/kdroid/composetray/demo/App.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.kdroid.composetray.demo

import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.github.kdroidfilter.platformtools.darkmodedetector.isSystemInDarkMode
import kotlin.system.exitProcess

@OptIn(ExperimentalMaterial3Api::class)
Expand All @@ -19,14 +21,17 @@ fun App(
onToggleChange: (Boolean, Boolean) -> Unit
) {
var currentScreen by remember { mutableStateOf(Screen.Screen1) }

// Automatically detect system theme
val isDarkTheme = isSystemInDarkMode()

// Material3 Theme
// Material3 Theme with dark mode support
MaterialTheme(
colorScheme = lightColorScheme(
primary = MaterialTheme.colorScheme.primary,
secondary = MaterialTheme.colorScheme.secondary,
tertiary = MaterialTheme.colorScheme.tertiary
)
colorScheme = if (isDarkTheme) {
darkColorScheme()
} else {
lightColorScheme()
}
) {
Scaffold(
topBar = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,33 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import com.kdroid.composetray.tray.api.Tray
import com.kdroid.composetray.utils.ComposeNativeTrayLoggingLevel
import com.kdroid.composetray.utils.SingleInstanceManager
import com.kdroid.composetray.utils.allowComposeNativeTrayLogging
import com.kdroid.composetray.utils.composeNativeTrayloggingLevel
import com.kdroid.composetray.utils.getTrayPosition
import com.kdroid.composetray.utils.getTrayWindowPosition
import com.kdroid.kmplog.Log
import com.kdroid.kmplog.d
import com.kdroid.kmplog.i
import composenativetray.demo.generated.resources.Res
import composenativetray.demo.generated.resources.icon
import org.jetbrains.compose.resources.painterResource

fun main() = application {
Log.setDevelopmentMode(true)
allowComposeNativeTrayLogging = false
composeNativeTrayloggingLevel = ComposeNativeTrayLoggingLevel.DEBUG
val logTag = "NativeTray"

Log.d("TrayPosition", getTrayPosition().toString())
println("$logTag: TrayPosition: ${getTrayPosition()}")

var isWindowVisible by remember { mutableStateOf(true) }
var textVisible by remember { mutableStateOf(false) }
var alwaysShowTray by remember { mutableStateOf(false) }
var alwaysShowTray by remember { mutableStateOf(true) }
var hideOnClose by remember { mutableStateOf(true) }
var notificationsEnabled by remember { mutableStateOf(false) }
var initialChecked by remember { mutableStateOf(true) }

val isSingleInstance = SingleInstanceManager.isSingleInstance(onRestoreRequest = {
isWindowVisible = true
Expand Down Expand Up @@ -62,7 +66,7 @@ fun main() = application {
},
primaryAction = {
isWindowVisible = true
Log.i(logTag, "On Primary Clicked")
println("$logTag: On Primary Clicked")
},
primaryActionLabel = "Open the Application",
tooltip = "My Application"
Expand All @@ -71,46 +75,64 @@ fun main() = application {
// Tools SubMenu
SubMenu(label = "Tools") {
Item(label = "Calculator") {
Log.i(logTag, "Calculator launched")
println("$logTag: Calculator launched")
}
Item(label = "Notepad") {
Log.i(logTag, "Notepad opened")
println("$logTag: Notepad opened")
}
}

Divider()

// Checkable Items
CheckableItem(label = "Enable notifications") { isChecked ->
Log.i(logTag, "Notifications ${if (isChecked) "enabled" else "disabled"}")
}
CheckableItem(label = "Initial Checked", checked = true) { isChecked ->
Log.i(logTag, "Initial Checked ${if (isChecked) "enabled" else "disabled"}")
}
CheckableItem(
label = "Enable notifications",
checked = notificationsEnabled,
onCheckedChange = { isChecked ->
notificationsEnabled = isChecked
println("$logTag: Notifications ${if (isChecked) "enabled" else "disabled"}")
}
)
CheckableItem(
label = "Initial Checked",
checked = initialChecked,
onCheckedChange = { isChecked ->
initialChecked = isChecked
println("$logTag: Initial Checked ${if (isChecked) "enabled" else "disabled"}")
}
)

Divider()

Item(label = "About") {
Log.i(logTag, "Application v1.0 - Developed by Elyahou")
println("$logTag: Application v1.0 - Developed by Elyahou")
}

Divider()

CheckableItem(label = "Always show tray", checked = alwaysShowTray) { isChecked ->
alwaysShowTray = isChecked
Log.i(logTag, "Always show tray ${if (isChecked) "enabled" else "disabled"}")
}

CheckableItem(label = "Hide on close", checked = hideOnClose) { isChecked ->
hideOnClose = isChecked
Log.i(logTag, "Hide on close ${if (isChecked) "enabled" else "disabled"}")
}
CheckableItem(
label = "Always show tray",
checked = alwaysShowTray,
onCheckedChange = { isChecked ->
alwaysShowTray = isChecked
println("$logTag: Always show tray ${if (isChecked) "enabled" else "disabled"}")
}
)

CheckableItem(
label = "Hide on close",
checked = hideOnClose,
onCheckedChange = { isChecked ->
hideOnClose = isChecked
println("$logTag: Hide on close ${if (isChecked) "enabled" else "disabled"}")
}
)

Divider()


Item(label = "Exit", isEnabled = true) {
Log.i(logTag, "Exiting the application")
println("$logTag: Exiting the application")
dispose()
exitApplication()
}
Expand All @@ -120,7 +142,7 @@ fun main() = application {
}


val windowWidth = 800
val windowWidth = 300
val windowHeight = 600
val windowPosition = getTrayWindowPosition(windowWidth, windowHeight)

Expand All @@ -132,7 +154,7 @@ fun main() = application {
exitApplication()
}
},
state = rememberWindowState(
state = WindowState(
width = windowWidth.dp,
height = windowHeight.dp,
position = windowPosition
Expand All @@ -146,4 +168,4 @@ fun main() = application {
hideOnClose = hideOnCloseState
}
}
}
}
Loading