Skip to content

Commit 272f411

Browse files
ctruedenclaude
andcommitted
Complete phase 1: fix hardcoded elements
1. Remove Hardcoded Scheme Names - WindowsPlatform: Now queries LinkService for schemes - LinuxPlatform: Now queries LinkService for schemes - Both platforms now: - Inject LinkService as a parameter - Implement collectSchemes() helper method - Check/install/uninstall ALL schemes dynamically 2. Add getSchemeInstaller method - New method: DesktopIntegrationProvider#getSchemeInstaller - Allows platforms to provide SchemeInstaller instances - All three platforms now implement this method: - WindowsPlatform: Returns WindowsSchemeInstaller - LinuxPlatform: Returns LinuxSchemeInstaller - MacOSPlatform: Returns null (Info.plist only) 3. Refactor DefaultLinkService#createInstaller() - Removed: Hardcoded OS name string checks - Added: Uses PlatformService.getTargetPlatforms() to find platform - Result: Plugin-based architecture, no OS-specific code in LinkService Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 82281bf commit 272f411

File tree

8 files changed

+221
-67
lines changed

8 files changed

+221
-67
lines changed

NEXT.md

Lines changed: 81 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,19 @@ The component uses a plugin system where Platform implementations (LinuxPlatform
2929
- File extension registration
3030
- Windows Start Menu icon generation
3131
- First launch dialog for desktop integration opt-in
32-
- SchemeInstallerProvider interface for platforms
32+
33+
## Implementation Phases
34+
35+
### Phase 1: Fix Hardcoded Elements (High Priority)
36+
These fixes are required for scijava-desktop to work for applications other than Fiji.
37+
38+
### Phase 2: File Extension Registration (High Priority)
39+
Core functionality for Fiji and other scientific applications.
40+
41+
### Phase 3: Polish (Medium Priority)
42+
First launch dialog, Windows desktop icon, etc.
43+
44+
---
3345

3446
## Priority Work Items
3547

@@ -89,36 +101,16 @@ private Set<String> collectSchemes() {
89101

90102
Similar changes needed for LinuxPlatform.
91103

92-
### 2. Add SchemeInstallerProvider Interface
104+
### 2. Add getSchemeInstaller method
93105

94106
**Goal**: Allow platforms to provide SchemeInstaller instances without hardcoding in DefaultLinkService.
95107

96-
**New file**: `src/main/java/org/scijava/desktop/links/SchemeInstallerProvider.java`
97-
98-
```java
99-
package org.scijava.desktop.links;
100-
101-
/**
102-
* Interface for platforms that provide {@link SchemeInstaller} functionality.
103-
* <p>
104-
* Platform implementations can implement this interface to provide
105-
* platform-specific URI scheme installation capabilities.
106-
* </p>
107-
*/
108-
public interface SchemeInstallerProvider {
109-
/**
110-
* Creates a SchemeInstaller for this platform.
111-
*
112-
* @return a SchemeInstaller, or null if not supported
113-
*/
114-
SchemeInstaller getSchemeInstaller();
115-
}
116-
```
108+
**New method**: `DesktopIntegrationProvider#getSchemeInstaller()`
117109

118110
**Files to modify**:
119-
- WindowsPlatform.java - implement SchemeInstallerProvider
120-
- LinuxPlatform.java - implement SchemeInstallerProvider
121-
- MacOSPlatform.java - implement SchemeInstallerProvider (return null)
111+
- WindowsPlatform.java - implement getSchemeInstaller
112+
- LinuxPlatform.java - implement getSchemeInstaller
113+
- MacOSPlatform.java - implement getSchemeInstaller (return null)
122114

123115
### 3. Refactor DefaultLinkService#createInstaller()
124116

@@ -151,8 +143,8 @@ private SchemeInstaller createInstaller() {
151143
if (platformService == null) return null;
152144

153145
final Platform platform = platformService.platform();
154-
if (platform instanceof SchemeInstallerProvider) {
155-
return ((SchemeInstallerProvider) platform).getSchemeInstaller();
146+
if (platform instanceof DesktopIntegrationProvider) {
147+
return ((DesktopIntegrationProvider) platform).getSchemeInstaller();
156148
}
157149

158150
return null;
@@ -189,7 +181,7 @@ Either way: "To change these settings in the future, use Edit > Options > Deskto
189181
- If user selects "No", do nothing
190182
- Store user preference by writing to a local configuration file -- avoids showing dialog again
191183

192-
### 5. File Extension Registration (Future)
184+
### 5. File Extension Registration (High Priority)
193185

194186
**Scope**: Extend DesktopIntegrationProvider to support file type associations.
195187

@@ -201,9 +193,52 @@ void setFileExtensionsEnabled(boolean enable) throws IOException;
201193
```
202194

203195
**Platform implementations**:
204-
- **Linux**: Add MIME types to .desktop file (e.g., `image/tiff`, `application/x-imagej-macro`)
205-
- **Windows**: Register file associations in Registry under `HKCU\Software\Classes\.<ext>`
206-
- **macOS**: Declared in Info.plist (build-time, read-only)
196+
197+
#### Linux (Complex - Custom MIME Types Required)
198+
199+
**Problem**: Microscopy formats (.sdt, .czi, .nd2, .lif, etc.) lack standard MIME types.
200+
201+
**Solution**: Register custom MIME types in `~/.local/share/mime/packages/fiji.xml`
202+
203+
**Steps**:
204+
1. Create extension → MIME type mapping
205+
- Standard formats: use existing types (`image/tiff`, `image/png`)
206+
- Microscopy formats: define custom types (`application/x-zeiss-czi`, `application/x-nikon-nd2`)
207+
2. Generate `fiji.xml` with MIME type definitions
208+
3. Install to `~/.local/share/mime/packages/`
209+
4. Run `update-mime-database ~/.local/share/mime`
210+
5. Add MIME types to .desktop file's `MimeType=` field
211+
212+
**MIME type naming convention**:
213+
- Use vendor-specific: `application/x-{vendor}-{format}`
214+
- Examples: `application/x-zeiss-czi`, `application/x-becker-hickl-sdt`
215+
216+
**Unregistration**:
217+
- Remove MIME types from .desktop file (preserve URI schemes)
218+
- Optionally delete `~/.local/share/mime/packages/fiji.xml`
219+
- Run `update-mime-database` again
220+
221+
#### Windows (Simple - SupportedTypes Only)
222+
223+
**Solution**: Use `Applications\fiji.exe\SupportedTypes` registry approach
224+
225+
**Steps**:
226+
1. Create `HKCU\Software\Classes\Applications\fiji.exe\SupportedTypes`
227+
2. Add each extension as a value: `.tif = ""`
228+
3. All ~150-200 extensions in one registry location
229+
230+
**Safety**: Deletion is safe - only removes our own `Applications\fiji.exe` tree
231+
232+
**Unregistration**:
233+
- Delete entire `HKCU\Software\Classes\Applications\fiji.exe` key
234+
235+
#### macOS (Build-Time Only)
236+
237+
**Solution**: Declare all extensions in Info.plist at build time
238+
239+
**Format**: `CFBundleDocumentTypes` array with extension lists
240+
241+
**No runtime action needed** - this is a packaging/build concern
207242

208243
### 6. Windows Start Menu Icon
209244

@@ -216,6 +251,7 @@ void setFileExtensionsEnabled(boolean enable) throws IOException;
216251

217252
## Testing Checklist
218253

254+
### Phase 1 (Hardcoded Elements)
219255
- [ ] Test URI scheme registration on Windows (registry manipulation)
220256
- [ ] Test URI scheme registration on Linux (.desktop file updates)
221257
- [ ] Test desktop icon installation on Linux
@@ -225,6 +261,16 @@ void setFileExtensionsEnabled(boolean enable) throws IOException;
225261
- [ ] Test toggling features on/off via UI
226262
- [ ] Verify no hardcoded "fiji" references remain
227263

264+
### Phase 2 (File Extensions)
265+
- [ ] Test Linux MIME type generation and installation
266+
- [ ] Verify `update-mime-database` runs successfully
267+
- [ ] Test file extension associations appear in file managers (Nautilus, Dolphin)
268+
- [ ] Test Windows SupportedTypes registration
269+
- [ ] Verify Fiji appears in "Open With" for all extensions
270+
- [ ] Test unregistration (verify complete cleanup)
271+
- [ ] Test with ~150-200 actual file extensions
272+
- [ ] Verify no `application/octet-stream` claims
273+
228274
## System Properties Reference
229275

230276
- `scijava.app.executable` - Path to application executable (required for all platforms)
@@ -243,7 +289,6 @@ void setFileExtensionsEnabled(boolean enable) throws IOException;
243289

244290
## Questions to Resolve
245291

246-
1. Should SchemeInstallerProvider be a separate interface or extend Platform?
247-
2. How to handle partial scheme installation failures?
248-
3. Should first launch dialog be mandatory or optional?
249-
4. What file extensions should be supported by default?
292+
1. How to handle partial scheme installation failures?
293+
2. Should first launch dialog be mandatory or optional?
294+
3. What file extensions should be supported by default?

spec/DESKTOP_INTEGRATION_PLAN.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ Applications configure desktop integration via system properties:
172172

173173
2. **OS Checks**: DefaultLinkService:119-132 hardcodes OS name checks
174174
- Should use PlatformService to get active platform
175-
- Should add SchemeInstallerProvider interface
175+
- Should add getSchemeInstaller method
176176
- See NEXT.md Work Items #2 and #3
177177

178178
### Missing Features
@@ -256,7 +256,7 @@ java -Dscijava.app.executable="/path/to/myapp" \
256256

257257
- Remove hardcoded "fiji" scheme references
258258
- Remove hardcoded OS checks in DefaultLinkService
259-
- Add SchemeInstallerProvider interface
259+
- Add getSchemeInstaller method
260260

261261
### ❌ Not Implemented
262262

spec/IMPLEMENTATION_SUMMARY.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ The component uses SciJava's plugin architecture to avoid hardcoded OS checks:
108108
**Impact**: Violates plugin architecture; makes adding new platforms difficult.
109109

110110
**Fix Required**:
111-
1. Add SchemeInstallerProvider interface
112-
2. Platforms implement SchemeInstallerProvider
111+
1. Add getSchemeInstaller method
112+
2. Platforms implement getSchemeInstaller
113113
3. DefaultLinkService queries PlatformService.platform() instead of checking OS name
114114

115115
### ❌ Not Yet Implemented
@@ -345,7 +345,7 @@ Linux .desktop files are used by both LinuxPlatform (for desktop icon) and Linux
345345
See NEXT.md for detailed implementation plan, including:
346346

347347
1. Remove hardcoded scheme names (Priority 1)
348-
2. Add SchemeInstallerProvider interface (Priority 2)
348+
2. Add getSchemeInstaller method (Priority 2)
349349
3. Refactor DefaultLinkService#createInstaller() (Priority 3)
350350
4. Implement first launch dialog (Optional)
351351
5. Add file extension registration (Future)

src/main/java/org/scijava/desktop/DesktopIntegrationProvider.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
import java.io.IOException;
3333

34+
import org.scijava.desktop.links.SchemeInstaller;
35+
3436
/**
3537
* Marker interface for platform implementations that provide desktop
3638
* integration features.
@@ -79,4 +81,11 @@ public interface DesktopIntegrationProvider {
7981
* @throws UnsupportedOperationException if not supported on this platform
8082
*/
8183
void setDesktopIconPresent(final boolean install) throws IOException;
84+
85+
/**
86+
* Creates a SchemeInstaller for this platform.
87+
*
88+
* @return a SchemeInstaller, or null if not supported on this platform
89+
*/
90+
SchemeInstaller getSchemeInstaller();
8291
}

src/main/java/org/scijava/desktop/links/DefaultLinkService.java

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@
3030

3131
import org.scijava.event.ContextCreatedEvent;
3232
import org.scijava.event.EventHandler;
33+
import org.scijava.desktop.DesktopIntegrationProvider;
3334
import org.scijava.desktop.links.SchemeInstaller;
34-
import org.scijava.desktop.platform.linux.LinuxSchemeInstaller;
35-
import org.scijava.desktop.platform.windows.WindowsSchemeInstaller;
3635
import org.scijava.log.LogService;
36+
import org.scijava.platform.Platform;
37+
import org.scijava.platform.PlatformService;
3738
import org.scijava.plugin.AbstractHandlerService;
3839
import org.scijava.plugin.Parameter;
3940
import org.scijava.plugin.Plugin;
@@ -55,6 +56,9 @@ public class DefaultLinkService extends AbstractHandlerService<URI, LinkHandler>
5556
@Parameter(required = false)
5657
private LogService log;
5758

59+
@Parameter(required = false)
60+
private PlatformService platformService;
61+
5862
@EventHandler
5963
private void onEvent(final ContextCreatedEvent evt) {
6064
// Register URI handler with the desktop system, if possible.
@@ -112,23 +116,23 @@ private void installSchemes() {
112116
/**
113117
* Creates the appropriate {@link SchemeInstaller} for the current platform.
114118
* <p>
115-
* Windows and Linux are supported via runtime registration. macOS uses Info.plist
116-
* in the .app bundle (configured at build time, not at runtime).
119+
* Uses the platform plugin system to obtain the installer, avoiding
120+
* hardcoded OS checks. Windows and Linux platforms provide runtime registration.
121+
* macOS uses Info.plist in the .app bundle (configured at build time).
117122
* </p>
118123
*/
119124
private SchemeInstaller createInstaller() {
120-
final String os = System.getProperty("os.name");
121-
if (os == null) return null;
125+
if (platformService == null) return null;
122126

123-
final String osLower = os.toLowerCase();
124-
if (osLower.contains("linux")) {
125-
return new LinuxSchemeInstaller(log);
126-
}
127-
else if (osLower.contains("win")) {
128-
return new WindowsSchemeInstaller(log);
127+
// Find a platform that provides a SchemeInstaller
128+
for (final Platform platform : platformService.getTargetPlatforms()) {
129+
if (platform instanceof DesktopIntegrationProvider) {
130+
final SchemeInstaller installer = ((DesktopIntegrationProvider) platform).getSchemeInstaller();
131+
if (installer != null) return installer;
132+
}
129133
}
130134

131-
return null; // macOS or other unsupported platforms
135+
return null;
132136
}
133137

134138
/**

src/main/java/org/scijava/desktop/platform/linux/LinuxPlatform.java

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@
3131

3232
import org.scijava.app.AppService;
3333
import org.scijava.desktop.DesktopIntegrationProvider;
34+
import org.scijava.desktop.links.LinkHandler;
3435
import org.scijava.desktop.links.LinkService;
36+
import org.scijava.desktop.links.SchemeInstaller;
3537
import org.scijava.log.LogService;
3638
import org.scijava.platform.AbstractPlatform;
3739
import org.scijava.platform.Platform;
@@ -44,6 +46,9 @@
4446
import java.nio.file.Files;
4547
import java.nio.file.Path;
4648
import java.nio.file.Paths;
49+
import java.util.HashSet;
50+
import java.util.List;
51+
import java.util.Set;
4752

4853
/**
4954
* A platform implementation for handling Linux platform issues.
@@ -109,7 +114,14 @@ public void open(final URL url) throws IOException {
109114
public boolean isWebLinksEnabled() {
110115
try {
111116
final DesktopFile df = getOrCreateDesktopFile();
112-
return df.hasMimeType("x-scheme-handler/myapp");
117+
final Set<String> schemes = collectSchemes();
118+
if (schemes.isEmpty()) return false;
119+
120+
// Check if any scheme is registered
121+
for (final String scheme : schemes) {
122+
if (df.hasMimeType("x-scheme-handler/" + scheme)) return true;
123+
}
124+
return false;
113125
} catch (final IOException e) {
114126
if (log != null) {
115127
log.debug("Failed to check web links status", e);
@@ -124,13 +136,17 @@ public boolean isWebLinksEnabled() {
124136
@Override
125137
public void setWebLinksEnabled(final boolean enable) throws IOException {
126138
final DesktopFile df = getOrCreateDesktopFile();
127-
128-
if (enable) {
129-
df.addMimeType("x-scheme-handler/fiji");
130-
} else {
131-
df.removeMimeType("x-scheme-handler/fiji");
139+
140+
final Set<String> schemes = collectSchemes();
141+
for (final String scheme : schemes) {
142+
final String mimeType = "x-scheme-handler/" + scheme;
143+
if (enable) {
144+
df.addMimeType(mimeType);
145+
} else {
146+
df.removeMimeType(mimeType);
147+
}
132148
}
133-
149+
134150
df.save();
135151
}
136152

@@ -195,6 +211,11 @@ public void setDesktopIconPresent(final boolean install) throws IOException {
195211
}
196212
}
197213

214+
@Override
215+
public SchemeInstaller getSchemeInstaller() {
216+
return new LinuxSchemeInstaller(log);
217+
}
218+
198219
// -- Helper methods --
199220

200221
/**
@@ -296,4 +317,22 @@ private boolean isDesktopFileUpToDate(final Path desktopFile) {
296317
private String sanitizeFileName(final String name) {
297318
return name.replaceAll("[^a-zA-Z0-9._-]", "-").toLowerCase();
298319
}
320+
321+
// -- Helper methods --
322+
323+
/**
324+
* Collects all URI schemes from registered LinkHandler plugins.
325+
*/
326+
private Set<String> collectSchemes() {
327+
final Set<String> schemes = new HashSet<>();
328+
if (linkService == null) return schemes;
329+
330+
for (final LinkHandler handler : linkService.getInstances()) {
331+
final List<String> handlerSchemes = handler.getSchemes();
332+
if (handlerSchemes != null) {
333+
schemes.addAll(handlerSchemes);
334+
}
335+
}
336+
return schemes;
337+
}
299338
}

0 commit comments

Comments
 (0)