Sean Proctor
03/22/2022, 11:08 PMSrSouza
03/23/2022, 3:49 PMStefan Oltmann
03/24/2022, 8:08 AMDesktop.getDesktop().setOpenURIHandler { event ->
// do something
}
but for Windows things get messy.
My Windows solution goes like this:
1. Register a custom URI scheme on start via registry editor
2. Let this call a custom EXE I build with Kotlin Native that just writes everything given to it out as a text file
3. Have a File Watcher pick this change up and further process it.
This code registers the URI scheme:
private fun registerUriSchemeOnWindows() {
try {
/*
* We look for a file in the JAR that must exist.
*/
val resourceName = "icon.ico"
val url = Thread.currentThread().contextClassLoader.getResource(resourceName)
checkNotNull(url) { "Resource $resourceName was not found." }
val trimSize = "!/$resourceName".length
val jarPath = url.path.substring(0, url.path.length - trimSize)
val pathToExe = Paths.get(URI(jarPath))
.resolve("../resources/cmdargshelper.exe")
.toFile()
.canonicalPath
<http://Log.info|Log.info>("App location: $pathToExe")
val runtime = Runtime.getRuntime()
val protocolPath = "HKEY_CURRENT_USER\\Software\\Classes\\my-app-name"
var process = runtime.exec("reg add $protocolPath /t REG_SZ /d \"My App Name\" /f")
process.waitFor()
process = runtime.exec("reg add $protocolPath /v \"URL Protocol\" /t REG_SZ /d \"\" /f")
process.waitFor()
process = runtime.exec(
"reg add $protocolPath\\shell /f"
)
process.waitFor()
process = runtime.exec(
"reg add $protocolPath\\shell\\open /f"
)
process.waitFor()
process = runtime.exec(
"reg add $protocolPath\\shell\\open\\command /t REG_SZ /d \"$pathToExe %1\" /f"
)
process.waitFor()
<http://Log.info|Log.info>("Custom URI scheme registered.")
} catch (ex: Exception) {
Log.error("Could not register URI scheme.", ex)
}
}
This is my cmdargshelper.exe:
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toKString
import platform.posix.EOF
import platform.posix.fclose
import platform.posix.fopen
import platform.posix.fputs
import platform.posix.getenv
import platform.posix.mkdir
import kotlin.system.getTimeMillis
fun main(args: Array<String>) {
if (args.size != 1) {
println("USAGE: Must be called with one argument.")
return
}
val argument = args[0]
val appDataPath = getenv("LOCALAPPDATA")?.toKString()
val millis = getTimeMillis()
val dirPath = "$appDataPath\\My App Name\\cmdargs"
val filePath = "$dirPath\\$millis.cmdargs"
println("$argument >> $filePath")
val mkdirResult = mkdir(dirPath)
if (mkdirResult == 0)
println("Directory 'cmdargs' created.")
writeAllText(filePath, argument)
}
private fun writeAllText(filePath: String, text: String) {
val file =
fopen(filePath, "w") ?: throw IllegalArgumentException("Failed to open $filePath")
try {
memScoped {
if (fputs(text, file) == EOF)
error("File write error")
}
} finally {
fclose(file)
}
}
And in my main() I do this:
if (isRunningOnWindows()) {
/* Watch args.temp */
fileWatchService = FileSystems.getDefault().newWatchService()
launch(<http://Dispatchers.IO|Dispatchers.IO>) {
val watchService = fileWatchService
checkNotNull(watchService) { "WatchService was not initialized." }
try {
val argsTempPath = Paths.get(getLocalCachePath() + "/cmdargs")
val argsTempPathFile = argsTempPath.toFile()
if (!argsTempPathFile.exists())
argsTempPathFile.mkdirs()
argsTempPath.register(watchService, ENTRY_MODIFY)
var poll = true
while (poll) {
val watchKey = watchService.take()
for (event in watchKey.pollEvents()) {
val file = File(argsTempPathFile, event.context().toString())
val text = file.readText()
withContext(Dispatchers.Main) {
// Handle the URI here! I do this:
store.dispatch(AppAction.UriOpened(text))
}
/*
* Clean up with JVM exit.
*
* Immediate deletion can cause problems.
*/
file.deleteOnExit()
}
poll = watchKey.reset()
}
} catch (ex: ClosedWatchServiceException) {
Log.debug("Watch service ended.")
} catch (ex: Exception) {
Log.error("Error watching file system.", ex)
}
}
}
At the end of main() I need to call fileWatchService?.close()
That's why my Main.kt holds private var fileWatchService: WatchService? = null
It works for me, but I'm happy to replace it with a simpler solution if someone has one. 😄Sean Proctor
03/24/2022, 10:29 AMStefan Oltmann
03/24/2022, 10:32 AMStefan Oltmann
03/24/2022, 10:34 AMStefan Oltmann
03/24/2022, 11:16 AMStefan Oltmann
03/24/2022, 11:17 AMSean Proctor
03/24/2022, 2:42 PM