@@ -60,38 +60,70 @@ private const val POSITION_KEY = "TrayPosition"
6060private const val X_KEY = " TrayX"
6161private const val Y_KEY = " TrayY"
6262
63+ // Use a writable tmp/cache directory to avoid read-only filesystem issues (e.g., macOS app bundle working dir)
64+ private fun trayPropertiesFile (): File {
65+ val tmpBase = System .getProperty(" java.io.tmpdir" ) ? : " ."
66+ val appDir = File (File (tmpBase, " ComposeNativeTray" ), AppIdProvider .appId())
67+ // Best-effort create directory; if it fails, we will fallback to legacy file access patterns safely
68+ runCatching { if (! appDir.exists()) appDir.mkdirs() }.onFailure { /* ignore */ }
69+ return File (appDir, PROPERTIES_FILE )
70+ }
71+
72+ // Legacy properties file in working directory (for backward compatibility)
73+ private fun legacyPropertiesFile (): File = File (PROPERTIES_FILE )
74+
75+ // Previous tmp location before appId namespacing (for backward-compatible reads only)
76+ private fun oldTmpPropertiesFile (): File {
77+ val tmpBase = System .getProperty(" java.io.tmpdir" ) ? : " ."
78+ val oldDir = File (tmpBase, " ComposeNativeTray" )
79+ return File (oldDir, PROPERTIES_FILE )
80+ }
81+
82+ private fun loadPropertiesFrom (file : File ): Properties ? {
83+ if (! file.exists()) return null
84+ return runCatching {
85+ Properties ().apply { file.inputStream().use { load(it) } }
86+ }.getOrNull()
87+ }
88+
89+ private fun storePropertiesTo (file : File , props : Properties ) {
90+ // Ensure parent exists if any
91+ file.parentFile?.let { runCatching { if (! it.exists()) it.mkdirs() } }
92+ runCatching { file.outputStream().use { props.store(it, null ) } }
93+ // We intentionally swallow exceptions to avoid crashing if the FS is read-only
94+ }
95+
6396internal fun saveTrayPosition (position : TrayPosition ) {
64- val properties = Properties ()
65- val file = File ( PROPERTIES_FILE )
66- if (file.exists ()) {
67- properties.load(file.inputStream ())
68- }
97+ val preferredFile = trayPropertiesFile ()
98+ val properties = loadPropertiesFrom(preferredFile )
99+ ? : loadPropertiesFrom(oldTmpPropertiesFile ())
100+ ? : loadPropertiesFrom(legacyPropertiesFile ())
101+ ? : Properties ()
69102 properties.setProperty(POSITION_KEY , position.name)
70- file.outputStream().use { properties.store(it, null ) }
103+ storePropertiesTo(preferredFile, properties)
71104}
72105
73106internal fun saveTrayClickPosition (x : Int , y : Int , position : TrayPosition ) {
74- val properties = Properties ()
75- val file = File ( PROPERTIES_FILE )
76- if (file.exists ()) {
77- properties.load(file.inputStream ())
78- }
107+ val preferredFile = trayPropertiesFile ()
108+ val properties = loadPropertiesFrom(preferredFile )
109+ ? : loadPropertiesFrom(oldTmpPropertiesFile ())
110+ ? : loadPropertiesFrom(legacyPropertiesFile ())
111+ ? : Properties ()
79112 properties.setProperty(POSITION_KEY , position.name)
80113 properties.setProperty(X_KEY , x.toString())
81114 properties.setProperty(Y_KEY , y.toString())
82- file.outputStream().use { properties.store(it, null ) }
115+ storePropertiesTo(preferredFile, properties)
83116}
84117
85118internal fun loadTrayClickPosition (): TrayClickPosition ? {
86- val file = File (PROPERTIES_FILE )
87- if (! file.exists()) return null
88-
89- val properties = Properties ()
90- properties.load(file.inputStream())
119+ // Prefer new location, fallback to old tmp location, then legacy working dir
120+ val props = loadPropertiesFrom(trayPropertiesFile())
121+ ? : loadPropertiesFrom(oldTmpPropertiesFile())
122+ ? : loadPropertiesFrom(legacyPropertiesFile()) ? : return null
91123
92- val positionStr = properties .getProperty(POSITION_KEY ) ? : return null
93- val x = properties .getProperty(X_KEY )?.toIntOrNull() ? : return null
94- val y = properties .getProperty(Y_KEY )?.toIntOrNull() ? : return null
124+ val positionStr = props .getProperty(POSITION_KEY ) ? : return null
125+ val x = props .getProperty(X_KEY )?.toIntOrNull() ? : return null
126+ val y = props .getProperty(Y_KEY )?.toIntOrNull() ? : return null
95127
96128 return try {
97129 TrayClickPosition (x, y, TrayPosition .valueOf(positionStr))
@@ -150,11 +182,11 @@ fun getTrayPosition(): TrayPosition {
150182 }
151183
152184 // Legacy fallback - just position without coordinates
153- val properties = Properties ()
154- val file = File ( PROPERTIES_FILE )
155- if (file.exists ()) {
156- properties.load(file.inputStream ())
157- val position = properties .getProperty(POSITION_KEY , null )
185+ run {
186+ val props = loadPropertiesFrom(trayPropertiesFile() )
187+ ? : loadPropertiesFrom(oldTmpPropertiesFile ())
188+ ? : loadPropertiesFrom(legacyPropertiesFile ())
189+ val position = props? .getProperty(POSITION_KEY , null )
158190 if (position != null ) {
159191 return try {
160192 TrayPosition .valueOf(position)
0 commit comments