DorAga DorAga - 13 days ago 4
Java Question

How to disable the swt Browser clicking sound when used in Eclipse RCP application on windows?

I have embedded an swt Browser in my Eclipse RCP application. My problem is that on Windows the

setUrl()
and
dispose()
methods of the browser cause the (annoying) internet explorer navigation sound (the 'click') which is undesirable.

I found this piece of code that successfully disables the clicking sound

OS.CoInternetSetFeatureEnabled(OS.FEATURE_DISABLE_NAVIGATION_SOUNDS, OS.SET_FEATURE_ON_PROCESS, true);


But since this is restricted API I have trouble building the application using Maven/Tycho.

[ERROR] OS.CoInternetSetFeatureEnabled(OS.FEATURE_DISABLE_NAVIGATION_SOUNDS, OS.SET_FEATURE_ON_PROCESS, true);
[ERROR] ^^
[ERROR] OS cannot be resolved to a variable
[ERROR] 4 problems (4 errors)
[ERROR] -> [Help 1]
org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal org.eclipse.tycho:tycho-compiler-plugin:0.22.0:compile (default-compile) on project com.myapp: Compilation failure
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:212)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)...


Is there a way to get Maven/Tycho to compile while using this restricted API?

Or is there another way to disable the IE browser navigation sounds on Windows?

Answer

I managed to crack this eventually and here's how.

Since this restrictive API lives in platform specific plugins i.e. SWT 32bit and SWT 64bit, I created two platform specific fragments to hold the code.

To get maven to compile the fragments the following line needs to be added to the 32-bit fragment in file build.properties:

extra.. = platform:/fragment/org.eclipse.swt.win32.win32.x86

and the following for the 64-bit fragment build.properties

extra.. = platform:/fragment/org.eclipse.swt.win32.win32.x86_64

The maven pom configuration files should also be platform specific which is done by adding the following section in the pom.xml of the 32 bit fragment

<build>
    <plugins>
        <plugin>
            <groupId>org.eclipse.tycho</groupId>
            <artifactId>target-platform-configuration</artifactId>

            <configuration>
                <environments>
                    <environment>
                        <os>win32</os>
                        <ws>win32</ws>
                        <arch>x86</arch>
                    </environment>
                </environments>
            </configuration>
        </plugin>
    </plugins>
</build>

Here's the 64-bit version.

<build>
    <plugins>
        <plugin>
            <groupId>org.eclipse.tycho</groupId>
            <artifactId>target-platform-configuration</artifactId>
            <configuration>
                <environments>
                    <environment>
                        <os>win32</os>
                        <ws>win32</ws>
                        <arch>x86_64</arch>
                    </environment>
                </environments>
            </configuration>
        </plugin>
    </plugins>
</build>

Also do not forget to set the respective platform filter in the fragment manifest as shown here

Eclipse-PlatformFilter: (& (osgi.os=win32) (osgi.arch=x86))
Eclipse-PlatformFilter: (& (osgi.os=win32) (osgi.arch=x86_64))

We then place the silencing code in a Class in each fragment. This class should implement an interface in the host plugin. The host plugin defines an extension point that takes a class implementing the interface in the host plugin. The fragments then declare an extension and provides the class name in the fragment.

When the host code needs to run the silencing code it needs to check for extensions and instantiate and call the silencing code.

Example:

package com.mypackage;

import javax.inject.Inject;

import org.apache.log4j.Logger;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.e4.core.di.annotations.Creatable;

import com.mypackage.ISilencer;

@Creatable
public class BrowserSilencer {

    private static final Logger LOGGER = Logger.getLogger(BrowserSilencer.class);

    @Inject
    IExtensionRegistry exReg;

    public void silence=() {
        IConfigurationElement[] config = exReg.getConfigurationElementsFor("com.mypackage.silencer");
        try {
            for (IConfigurationElement e : config) {
                final Object o = e.createExecutableExtension("class");
                if (o instanceof ISilencer) {
                    executeExtension(o);
                }
            }
        } catch (CoreException ex) {
            LOGGER.error("Error finding the com.mypackage.silencer extension");
        }
    }

    private void executeExtension(final Object o) {
        ISafeRunnable runnable = new ISafeRunnable() {
            @Override
            public void handleException(Throwable e) {
                LOGGER.error("Exception while attempting to silence browser");
            }

            @Override
            public void run() throws Exception {
                ((ISilencer) o).silence();
            }
        };
        SafeRunner.run(runnable);
    }
}

The interface in the host plugin

package com.mypackage;
public interface ISilencer {
   public void silence();
}

and an example of the code in the 64 bit plugin. The 32-bit is almost the same

package com.mypackage.fragment.win64;

import org.apache.log4j.Logger;
import org.eclipse.swt.internal.win32.OS;   // yes i DO mean win32 here

import com.mypackage.ISilencer;

@SuppressWarnings("restriction")
public class Silencer implements ISilencer {

    private static final Logger LOGGER = Logger.getLogger(Silencer.class);

    @Override
    public void silence() {
        // removes the annoying browser clicking sound!
        try {
            OS.CoInternetSetFeatureEnabled(OS.FEATURE_DISABLE_NAVIGATION_SOUNDS, OS.SET_FEATURE_ON_PROCESS, true);
        } catch (Throwable e1) {
            // I am just catching any exceptions that may come off this one since it is using restricted API so that if in any case it fail well it will just click.
            LOGGER.error("Caught exception while setting FEATURE_DISABLE_NAVIGATION_SOUNDS.");
        }
    }
}

Since the BrowserSilencer is marked as @Creatable you can simply inject it into your class and call the silence() method

If it's not clear how to create and extension-point I can show that in a subsequent post.

Comments