Skip to content

Commit b23a25f

Browse files
committed
Improve Linux native library loading and dependency management
- Added robust strategies to load native libraries using system properties, library paths, or JNA discovery. - Implemented preloading of Linux-specific dependencies bundled within resources. - Extended SNIWrapper methods to support menu item and submenu icons. - Included `libdbusmenu-qt5.so` for enhanced compatibility.
1 parent 827f5f2 commit b23a25f

3 files changed

Lines changed: 129 additions & 5 deletions

File tree

src/commonMain/kotlin/com/kdroid/composetray/lib/linux/SNIWrapper.kt

Lines changed: 128 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,135 @@
11
package com.kdroid.composetray.lib.linux
22

33
import com.sun.jna.*
4+
import java.io.File
5+
import java.io.IOException
6+
import java.nio.file.Files
7+
import java.nio.file.Path
8+
import java.nio.file.StandardCopyOption
49

510
// JNA Interface for sni_wrapper library
611
interface SNIWrapper : Library {
712
companion object {
8-
// Load the shared library (adjust the path and name as needed)
9-
val INSTANCE: SNIWrapper = Native.load("tray", SNIWrapper::class.java) as SNIWrapper
13+
/**
14+
* Robust native library loading strategy to work with packagers like Conveyor.
15+
*
16+
* Resolution order (Linux):
17+
* 1) System property override with absolute path:
18+
* -Dcomposetray.native.lib=/abs/path/libtray.so
19+
* 2) System property pointing to a directory containing the library:
20+
* -Dcomposetray.native.lib.path=/abs/dir (expects libtray.so inside)
21+
* 3) System.loadLibrary("tray") so that external extractors (e.g. Conveyor
22+
* app.jvm.extract-native-libraries=true) can provide the library in the
23+
* JVM library path.
24+
* 4) Fallback to JNA resource/classpath discovery Native.load("tray").
25+
*/
26+
private fun tryLoadViaSystemProperties(): Boolean {
27+
val explicitFile = System.getProperty("composetray.native.lib")?.trim()?.takeIf { it.isNotEmpty() }
28+
if (!explicitFile.isNullOrEmpty()) {
29+
val f = File(explicitFile)
30+
if (f.isFile && f.canRead()) {
31+
try {
32+
System.load(f.absolutePath)
33+
return true
34+
} catch (_: Throwable) {
35+
// continue to other strategies
36+
}
37+
}
38+
}
39+
val dir = System.getProperty("composetray.native.lib.path")?.trim()?.takeIf { it.isNotEmpty() }
40+
if (!dir.isNullOrEmpty()) {
41+
val candidate = File(dir, System.mapLibraryName("tray"))
42+
if (candidate.isFile && candidate.canRead()) {
43+
try {
44+
System.load(candidate.absolutePath)
45+
return true
46+
} catch (_: Throwable) {
47+
// continue
48+
}
49+
}
50+
}
51+
return false
52+
}
53+
54+
private fun tryLoadViaSystemLibrary(): Boolean = try {
55+
System.loadLibrary("tray")
56+
true
57+
} catch (_: Throwable) { false }
58+
59+
private fun isLinux(): Boolean = System.getProperty("os.name")?.lowercase()?.contains("linux") == true
60+
61+
private fun archFolder(): String {
62+
val arch = System.getProperty("os.arch")?.lowercase() ?: ""
63+
return when {
64+
arch.contains("x86_64") || arch.contains("amd64") -> "linux-x86-64"
65+
else -> "linux-x86-64" // default for now; extend when adding more
66+
}
67+
}
68+
69+
private fun extractResourceToDir(resourcePath: String, targetDir: Path): Path? {
70+
val url = SNIWrapper::class.java.classLoader.getResource(resourcePath) ?: return null
71+
val fileName = File(resourcePath).name
72+
val target = targetDir.resolve(fileName)
73+
try {
74+
url.openStream().use { input ->
75+
Files.createDirectories(targetDir)
76+
Files.copy(input, target, StandardCopyOption.REPLACE_EXISTING)
77+
}
78+
// ensure readable/executable for loader
79+
target.toFile().setReadable(true, false)
80+
target.toFile().setExecutable(true, false)
81+
return target
82+
} catch (_: IOException) {
83+
return null
84+
}
85+
}
86+
87+
private fun preloadLinuxDependencies() {
88+
if (!isLinux()) return
89+
val tmpBase = Files.createTempDirectory("composetray-natives-")
90+
// Attempt to extract versioned first, then soname link
91+
val base = "${archFolder()}"
92+
val candidates = listOf(
93+
"$base/libdbusmenu-qt5.so.2.6.0",
94+
"$base/libdbusmenu-qt5.so.2"
95+
)
96+
var loaded = false
97+
for (res in candidates) {
98+
val p = extractResourceToDir(res, tmpBase)
99+
if (p != null && p.toFile().exists()) {
100+
try {
101+
System.load(p.toAbsolutePath().toString())
102+
loaded = true
103+
break
104+
} catch (_: Throwable) {
105+
// try next
106+
}
107+
}
108+
}
109+
if (!loaded) {
110+
// If extraction failed or not packaged, continue without preloading
111+
} else {
112+
// Make JNA search path include temp dir so that transitive loads can find siblings
113+
try {
114+
NativeLibrary.addSearchPath("tray", tmpBase.toAbsolutePath().toString())
115+
} catch (_: Throwable) {
116+
// ignore
117+
}
118+
}
119+
}
120+
121+
val INSTANCE: SNIWrapper = run {
122+
// Preload Linux-only dependencies bundled in resources
123+
preloadLinuxDependencies()
124+
125+
// 1) Property overrides
126+
if (!tryLoadViaSystemProperties()) {
127+
// 2) System library path
128+
tryLoadViaSystemLibrary()
129+
}
130+
// 3) Always let JNA resolve as well (will succeed if already loaded)
131+
Native.load("tray", SNIWrapper::class.java) as SNIWrapper
132+
}
10133
}
11134

12135
// Callback interfaces
@@ -49,14 +172,14 @@ interface SNIWrapper : Library {
49172
fun set_context_menu(handle: Pointer?, menu: Pointer?)
50173
fun add_menu_action(menu_handle: Pointer?, text: String?, cb: ActionCallback?, data: Pointer?): Pointer?
51174
fun add_disabled_menu_action(menu_handle: Pointer?, text: String?, cb: ActionCallback?, data: Pointer?): Pointer?
52-
fun add_checkable_menu_action(menu_handle: Pointer?, text: String?, checked: Int, cb: ActionCallback?, data: Pointer?): Pointer?
53-
fun add_menu_separator(menu_handle: Pointer?)
175+
fun add_checkable_menu_action(menu_handle: Pointer?, text: String?, checked: Int, cb: ActionCallback?, data: Pointer?): Pointer? fun add_menu_separator(menu_handle: Pointer?)
54176
fun create_submenu(menu_handle: Pointer?, text: String?): Pointer?
55177
fun set_menu_item_text(menu_item_handle: Pointer?, text: String?)
56178
fun set_menu_item_enabled(menu_item_handle: Pointer?, enabled: Int)
57179
fun set_menu_item_checked(menu_item_handle: Pointer?, checked: Int): Int
58180
fun remove_menu_item(menu_handle: Pointer?, menu_item_handle: Pointer?)
59-
181+
fun set_menu_item_icon(menu_item_handle: Pointer?, icon_path_or_name: String?)
182+
fun set_submenu_icon(submenu_handle: Pointer?, icon_path_or_name: String?)
60183
fun tray_update(handle: Pointer?)
61184

62185
fun clear_menu(menu_handle: Pointer?)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
libdbusmenu-qt5.so.2.6.0
212 KB
Binary file not shown.

0 commit comments

Comments
 (0)