Skip to content

Commit 619b7bd

Browse files
committed
Refactor Linux native library loading:
- Adjusted load order for `System.loadLibrary` to prioritize Conveyor and JVM library paths. - Improved temporary directory handling and dependency preloading process. - Streamlined library extraction and loading logic to improve reliability. - Updated JNA search path and `java.library.path` for runtime modifications. - Removed deprecated logic and refactored `preloadLinuxDependencies` function.
1 parent b23a25f commit 619b7bd

4 files changed

Lines changed: 92 additions & 43 deletions

File tree

linuxlib

Submodule linuxlib deleted from c50e6cb

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

Lines changed: 92 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,21 @@ interface SNIWrapper : Library {
1414
* Robust native library loading strategy to work with packagers like Conveyor.
1515
*
1616
* Resolution order (Linux):
17-
* 1) System property override with absolute path:
17+
* 1) System.loadLibrary("tray") - FIRST to support external extractors like Conveyor
18+
* (app.jvm.extract-native-libraries=true) that provide the library in JVM library path
19+
* 2) System property override with absolute path:
1820
* -Dcomposetray.native.lib=/abs/path/libtray.so
19-
* 2) System property pointing to a directory containing the library:
21+
* 3) System property pointing to a directory containing the library:
2022
* -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").
23+
* 4) Fallback to JNA resource/classpath discovery Native.load("tray")
2524
*/
25+
private fun tryLoadViaSystemLibrary(): Boolean = try {
26+
System.loadLibrary("tray")
27+
true
28+
} catch (_: Throwable) {
29+
false
30+
}
31+
2632
private fun tryLoadViaSystemProperties(): Boolean {
2733
val explicitFile = System.getProperty("composetray.native.lib")?.trim()?.takeIf { it.isNotEmpty() }
2834
if (!explicitFile.isNullOrEmpty()) {
@@ -51,11 +57,6 @@ interface SNIWrapper : Library {
5157
return false
5258
}
5359

54-
private fun tryLoadViaSystemLibrary(): Boolean = try {
55-
System.loadLibrary("tray")
56-
true
57-
} catch (_: Throwable) { false }
58-
5960
private fun isLinux(): Boolean = System.getProperty("os.name")?.lowercase()?.contains("linux") == true
6061

6162
private fun archFolder(): String {
@@ -86,34 +87,59 @@ interface SNIWrapper : Library {
8687

8788
private fun preloadLinuxDependencies() {
8889
if (!isLinux()) return
90+
8991
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"
92+
93+
// First, extract ALL libraries to the temp directory
94+
val base = archFolder()
95+
val libraries = listOf(
96+
"$base/libdbusmenu-qt5.so.2",
9597
)
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-
}
98+
99+
// Extract all libraries first
100+
for (res in libraries) {
101+
extractResourceToDir(res, tmpBase)
108102
}
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
103+
104+
// Add the temp directory to JNA's search path BEFORE loading
105+
try {
106+
NativeLibrary.addSearchPath("tray", tmpBase.toAbsolutePath().toString())
107+
// Also add to java.library.path for System.loadLibrary to find them
108+
val currentPath = System.getProperty("java.library.path", "")
109+
val newPath = if (currentPath.isNotEmpty()) {
110+
"$currentPath:${tmpBase.toAbsolutePath()}"
111+
} else {
112+
tmpBase.toAbsolutePath().toString()
113+
}
114+
System.setProperty("java.library.path", newPath)
115+
116+
// Force ClassLoader to reload the library path
117+
// This is a hack but necessary for runtime modifications
113118
try {
114-
NativeLibrary.addSearchPath("tray", tmpBase.toAbsolutePath().toString())
119+
val sysPathsField = ClassLoader::class.java.getDeclaredField("sys_paths")
120+
sysPathsField.isAccessible = true
121+
sysPathsField.set(null, null)
115122
} catch (_: Throwable) {
116-
// ignore
123+
// Ignore - this hack might not work on all JVMs
124+
}
125+
} catch (_: Throwable) {
126+
// ignore
127+
}
128+
129+
// Now load libraries in dependency order
130+
val loadOrder = listOf(
131+
"libdbusmenu-qt5.so.2"
132+
)
133+
134+
for (libName in loadOrder) {
135+
val libPath = tmpBase.resolve(libName)
136+
if (libPath.toFile().exists()) {
137+
try {
138+
System.load(libPath.toAbsolutePath().toString())
139+
} catch (e: Throwable) {
140+
// Log but continue - some might already be loaded
141+
println("Warning: Could not load $libName: ${e.message}")
142+
}
117143
}
118144
}
119145
}
@@ -122,13 +148,36 @@ interface SNIWrapper : Library {
122148
// Preload Linux-only dependencies bundled in resources
123149
preloadLinuxDependencies()
124150

125-
// 1) Property overrides
126-
if (!tryLoadViaSystemProperties()) {
127-
// 2) System library path
128-
tryLoadViaSystemLibrary()
151+
var loaded = false
152+
153+
// 1) FIRST try System.loadLibrary - this supports Conveyor and standard JVM library paths
154+
if (tryLoadViaSystemLibrary()) {
155+
loaded = true
156+
println("Loaded libtray via System.loadLibrary")
157+
}
158+
159+
// 2) If not loaded, try property overrides
160+
if (!loaded && tryLoadViaSystemProperties()) {
161+
loaded = true
162+
println("Loaded libtray via system properties")
163+
}
164+
165+
// 3) Always call JNA's Native.load as final step
166+
// If library was already loaded above, JNA will detect this and just create the proxy
167+
// If not loaded yet, JNA will try to load from resources/classpath
168+
try {
169+
Native.load("tray", SNIWrapper::class.java) as SNIWrapper
170+
} catch (e: UnsatisfiedLinkError) {
171+
// If we thought we loaded it but JNA can't create proxy, provide helpful error
172+
if (loaded) {
173+
throw UnsatisfiedLinkError(
174+
"Library was loaded via System.loadLibrary or properties but JNA couldn't create proxy. " +
175+
"This might indicate a version mismatch or architecture incompatibility: ${e.message}"
176+
)
177+
} else {
178+
throw e
179+
}
129180
}
130-
// 3) Always let JNA resolve as well (will succeed if already loaded)
131-
Native.load("tray", SNIWrapper::class.java) as SNIWrapper
132181
}
133182
}
134183

@@ -172,7 +221,8 @@ interface SNIWrapper : Library {
172221
fun set_context_menu(handle: Pointer?, menu: Pointer?)
173222
fun add_menu_action(menu_handle: Pointer?, text: String?, cb: ActionCallback?, data: Pointer?): Pointer?
174223
fun add_disabled_menu_action(menu_handle: Pointer?, text: String?, cb: ActionCallback?, data: Pointer?): 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?)
224+
fun add_checkable_menu_action(menu_handle: Pointer?, text: String?, checked: Int, cb: ActionCallback?, data: Pointer?): Pointer?
225+
fun add_menu_separator(menu_handle: Pointer?)
176226
fun create_submenu(menu_handle: Pointer?, text: String?): Pointer?
177227
fun set_menu_item_text(menu_item_handle: Pointer?, text: String?)
178228
fun set_menu_item_enabled(menu_item_handle: Pointer?, enabled: Int)
@@ -199,4 +249,4 @@ interface SNIWrapper : Library {
199249

200250
//Debug mode management
201251
fun sni_set_debug_mode(enabled: Int)
202-
}
252+
}

src/commonMain/resources/linux-x86-64/libdbusmenu-qt5.so.2

Lines changed: 0 additions & 1 deletion
This file was deleted.
212 KB
Binary file not shown.
-212 KB
Binary file not shown.

0 commit comments

Comments
 (0)