Skip to content

Commit b3e12e6

Browse files
ctruedenclaude
andcommitted
Fix Linux desktop integration: holistic sync, wildcard MIME resolution
Three interconnected fixes: 1. Wildcard MIME sentinel resolution (image/* -> image/x-m71 etc.) LinuxPlatform now resolves wildcard sentinels from DesktopService at write time: checks /usr/share/mime/globs2 for a system-known type matching the prefix, otherwise synthesizes prefix/x-sanitized-ext. Fixes StringIndexOutOfBoundsException in generateMimeTypeComment for extensions that produce empty parts after sanitization. 2. Holistic desktop file synthesis Add syncDesktopIntegration(webLinks, desktopIcon, fileTypes) to DesktopIntegrationProvider (default delegates to individual setters). LinuxPlatform overrides it to build the .desktop file from scratch in one pass: all-false deletes the file; otherwise Exec is always written, icon fields / scheme handlers / MIME types are added conditionally. Individual setters now read current state and delegate, staying correct when called in isolation. 3. Fix isDesktopIconPresent() and OptionsDesktop.run() isDesktopIconPresent() now checks Name+Exec in the loaded file rather than bare file existence, so a file present only for web links or MIME types is not mistaken for an icon entry. OptionsDesktop.run() gathers all three desired states then calls syncDesktopIntegration once, eliminating order-dependent stomping. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0d25ecf commit b3e12e6

File tree

3 files changed

+211
-136
lines changed

3 files changed

+211
-136
lines changed

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,32 @@ public interface DesktopIntegrationProvider {
101101
*/
102102
void setFileExtensionsEnabled(final boolean enable) throws IOException;
103103

104+
/**
105+
* Atomically applies all three desktop integration settings at once.
106+
* <p>
107+
* Implementations that store all three settings in a single artifact
108+
* (e.g. a Linux {@code .desktop} file) should override this method to
109+
* synthesize that artifact in one pass rather than via three independent
110+
* read-modify-write cycles.
111+
* </p>
112+
* <p>
113+
* The default implementation calls the three individual setters in order,
114+
* guarded by the corresponding {@code isXxxToggleable()} checks.
115+
* </p>
116+
*
117+
* @param webLinks whether URI scheme handlers should be registered
118+
* @param desktopIcon whether the application launcher entry should be present
119+
* @param fileTypes whether file-extension associations should be registered
120+
* @throws IOException if any part of the update fails
121+
*/
122+
default void syncDesktopIntegration(final boolean webLinks,
123+
final boolean desktopIcon, final boolean fileTypes) throws IOException
124+
{
125+
if (isWebLinksToggleable()) setWebLinksEnabled(webLinks);
126+
if (isDesktopIconToggleable()) setDesktopIconPresent(desktopIcon);
127+
if (isFileExtensionsToggleable()) setFileExtensionsEnabled(fileTypes);
128+
}
129+
104130
/**
105131
* Creates a SchemeInstaller for this platform.
106132
*

src/main/java/org/scijava/desktop/options/OptionsDesktop.java

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
package org.scijava.desktop.options;
3131

32-
import java.util.concurrent.Callable;
32+
import java.io.IOException;
3333
import java.util.stream.Stream;
3434

3535
import org.scijava.ItemVisibility;
@@ -99,21 +99,22 @@ public void initialize() {
9999
@Override
100100
public void run() {
101101
desktopPlatforms().forEach(p -> {
102-
if (p.isWebLinksToggleable()) {
103-
final Boolean enabled = getInputValue(webLinksItem);
104-
toggle(() -> { p.setWebLinksEnabled(enabled); return null; },
105-
enabled ? "enabling web links" : "disabling web links");
102+
// Resolve each feature's desired state: use the checkbox value
103+
// if toggleable, otherwise preserve the current platform state.
104+
final boolean webLinks = p.isWebLinksToggleable()
105+
? Boolean.TRUE.equals(getInputValue(webLinksItem))
106+
: p.isWebLinksEnabled();
107+
final boolean desktopIcon = p.isDesktopIconToggleable()
108+
? Boolean.TRUE.equals(getInputValue(desktopIconItem))
109+
: p.isDesktopIconPresent();
110+
final boolean fileTypes = p.isFileExtensionsToggleable()
111+
? Boolean.TRUE.equals(getInputValue(fileTypesItem))
112+
: p.isFileExtensionsEnabled();
113+
try {
114+
p.syncDesktopIntegration(webLinks, desktopIcon, fileTypes);
106115
}
107-
if (p.isDesktopIconToggleable()) {
108-
final Boolean enabled = getInputValue(desktopIconItem);
109-
toggle(() -> { p.setDesktopIconPresent(enabled); return null; },
110-
enabled ? "adding desktop icon" : "removing desktop icon");
111-
}
112-
if (p.isFileExtensionsToggleable()) {
113-
final Boolean enabled = getInputValue(fileTypesItem);
114-
toggle(() -> { p.setFileExtensionsEnabled(enabled); return null; },
115-
enabled ? "enabling file type associations" :
116-
"removing file type associations");
116+
catch (final IOException e) {
117+
if (log != null) log.error("Error performing desktop integration", e);
117118
}
118119
});
119120
super.run();
@@ -154,15 +155,6 @@ private Boolean getInputValue(final ModuleItem<?> item) {
154155
return (Boolean) getInput(item.getName());
155156
}
156157

157-
private void toggle(final Callable<?> toggleAction, final String errorMessage) {
158-
try {
159-
toggleAction.call();
160-
}
161-
catch (final Exception e) {
162-
if (log != null) log.error("Error " + errorMessage, e);
163-
}
164-
}
165-
166158
private boolean isDesktopIconToggleable() {
167159
return desktopPlatforms().anyMatch(p -> p.isDesktopIconToggleable());
168160
}

0 commit comments

Comments
 (0)