I'm trying to figure out how to open the system preferred editor for a given file.
Say, we have a file manager, written in Java. User goes to folder and sees the list of files. And, for example, there is a file Icon.jpg. User double clicks on the filename and file opens in system's preferred editor (i.e. Gimp). The main issue is - how to do that?
We can do Runtime.getRuntime().exec("something file"), but this way you should know which program is preferred in user environment. But how?
We also are able to do Desktop.getDesktop().edit(File file), but this way we cannot track process and aren't able to know then this child process is closed. Other issue - function doesn't work on linux (at least on Ubuntu 8.10). There is also Desktop.getDesktop().open(File file), but it forces to open file viewer, instead of system viewer for that file type.
I am searching for a solution all week, but didn't got any suitable and generic one. Do you know the other approaches to this question? For my project it would be enough if it would work on Windows+Linux+Mac.
Thank you for your answers and advices.
Edit on 2009-02-08 23:04
Other suggestion: can I force "application selection" window in Windows and in Linux, as in Mac with "open file"? For example, then you trying to open file, you are being asked to choose application from list of system preferred ones? (something like "Open with..." in Windows explorer). Do you know?
Seems that if you can't use java.awt.Desktop you have to distinguish between the OSes:
Windows:
RUNDLL32.EXE SHELL32.DLL,OpenAs_RunDLL <file.ext>
Linux:
edit <file.ext>
Mac:
open <file.ext>
HTH. Obviously, that is not very portable...
Check out the java.awt.Desktop object. In your case, you want to invoke edit()
If you want to ensure that a given platform supports this call, then you can do something like the following (I have not tested this code):
public boolean editFile(final File file) {
if (!Desktop.isDesktopSupported()) {
return false;
}
Desktop desktop = Desktop.getDesktop();
if (!desktop.isSupported(Desktop.Action.EDIT)) {
return false;
}
try {
desktop.edit(file);
} catch (IOException e) {
// Log an error
return false;
}
return true;
}
This isn't cross-platform, but on Mac OS X you can do
Runtime.getRuntime().exec("open filename");
The open(1) executable uses LaunchServices to pick the right program to execute, and then uses that to open the file named filename.
This will work in windows
Runtime.getRuntime().exec( "CMD /C START filename.ext " );
For JavaFX applications, we can use HostServices. This question covers how to use HostServices. This should work on Ubuntu (tested)/Windows (not tested) and Mac (not tested).
import java.io.File;
import java.io.IOException;
public class App extends Application {
}
File file = new File("/your/file/path");
HostServices hostServices = getHostServices();
hostServices.showDocument(file.getAbsolutePath());
getHostServices() is a method of JavaFX Application class.
Related
Is there a way to check if a specific program is installed on Windows using Java?
I'm trying to develop a Java program that automatically creates zip archives by using the code line command from 7-Zip.
So, I would like to check in Java if on my windows OS '7-Zip' is already installed. No check for running apps or if OS is Windows or Linux. I want to get a bool (true/false) if '7-Zip' is installed on Windows.
The library Apache Commons has a class called SystemUtils - full documentation is available at https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/SystemUtils.html.
In this library you have the following static boolean properties at your disposal:
SystemUtils.IS_OS_LINUX
SystemUtils.IS_OS_WINDOWS
The unix-like solution would be to simply try to run the program with --version flag (on windows probably the /? or - like in the 7zip case - without any at all) and check whether it fails, or what the return code will be.
Something like:
public boolean is7zipInstalled() {
try {
Process process = Runtime.getRuntime().exec("7zip.exe");
int code = process.waitFor();
return code == 0;
} catch (Exception e) {
return false;
}
}
I assume that you're talking about Windows. As Java is intended to be a platform-independent language and the way how to determine it differs per platform, there's no standard Java API to check that. You can however do it with help of JNI calls on a DLL which crawls the Windows registry. You can then just check if the registry key associated with the software is present in the registry. There's a 3rd party Java API with which you can crawl the Windows registry: jRegistryKey.
Here's an SSCCE with help of jRegistryKey:
package com.stackoverflow.q2439984;
import java.io.File;
import java.util.Iterator;
import ca.beq.util.win32.registry.RegistryKey;
import ca.beq.util.win32.registry.RootKey;
public class Test {
public static void main(String... args) throws Exception {
RegistryKey.initialize(Test.class.getResource("jRegistryKey.dll").getFile());
RegistryKey key = new RegistryKey(RootKey.HKLM, "Software\\Mozilla");
for (Iterator<RegistryKey> subkeys = key.subkeys(); subkeys.hasNext();) {
RegistryKey subkey = subkeys.next();
System.out.println(subkey.getName()); // You need to check here if there's anything which matches "Mozilla FireFox".
}
}
}
If you however intend to have a platformindependent application, then you'll also have to take into account the Linux/UNIX/Mac/Solaris/etc. (in other words: anywhere where Java is able to run) ways to detect whether FF is installed. Else you'll have to distribute it as a Windows-only application and do a System#exit() along with a warning whenever System.getProperty("os.name") is not Windows.
Sorry, I don't know how to detect in other platforms whether FF is installed or not, so don't expect an answer from me for that ;)
Following code opens status very fine in notepad:
import java.util.*;
class test
{
public static void main(String args[])
{
try{
ProcessBuilder pb=new ProcessBuilder("notepad","F:/status");
pb.start();
}catch(Exception e)
{
System.out.println(e);
}
}
}
Following code does'not play the song:
import java.util.*;
class test
{
public static void main(String args[])
{
try{
ProcessBuilder pb=new ProcessBuilder("C:/Program Files (x86)/VideoLAN/VLC/vlc","D:/02 Tu Jaane Na");
pb.start();
}catch(Exception e)
{
System.out.println(e);
}
}
}
I think that the problem is that you're ignoring the fact that the files you're trying to open have filename extensions.
Windows Explorer doesn't display file extensions by default - that is probably why you are not aware of their existence.
The reason why notepad worked in your first example is that notepad automatically adds .txt extension to its filename parameter in case you didn't provide one yourself. So in reality the file that is being open is not status but status.txt.
VLC doesn't have this "advanced" functionality because there's no specific filename extension it is designed to work with.
So you will need to look up the dir command output and add the full file name as a parameter.
If that was the real issue - you might want to modify your Windows Explorer settings for it to display file extensions:
or, which is better, switch to a more programmer-friendly OS :)
For 1.6+ code, use Desktop.open(File) instead.
Of course, the sensible thing to do immediately before calling that is to check File.exists().
OTOH, Desktop.open(File) throws a slew of handy exceptions, including:
NullPointerException - if file is null
IllegalArgumentException - if the specified file doesn't exist
UnsupportedOperationException - if the current platform does not support the Desktop.Action.OPEN action
IOException - if the specified file has no associated application or the associated application fails to be launched
Properly handled, the exception would indicate the immediate problem.
As an aside, the Desktop class is designed to be cross-platform, and will handle any file type for which an association is defined. In that sense it is a lot more useful for something like this, than trying to use a Process.
I know. Heresy. But I'm in a bind. I have a lot of config files that use absolute path names, which creates an incompatibility between OS X and Windows. If I can get OS X (which I'm betting is the more flexible of the two) to recognize Q:/foo/bar/bim.properties as a valid absolute file name, it'll save me days of work spelunking through stack traces and config files.
In the end, I need this bit of Java test code to print "SUCCESS!" when it runs:
import java.io.*;
class DriveLetterTest {
static public void main(String... args) {
File f = new File("S:");
if (f.isDirectory()) {
System.out.println("SUCCESS!");
} else {
System.out.println("FAIL!");
}
}
}
Anyone know how this can be done?
UPDATE: Thanks for all the feedback, everyone. It's now obvious to me I really should have been clearer in my question.
Both the config files and the code that uses them belong to a third-party package I cannot change. (Well, I can change them, but that means incurring an ongoing maintenance load, which I want to avoid if at all possible.)
I'm in complete agreement with all of you who are appalled by this state of affairs. But the fact remains: I can't change the third-party code, and I really want to avoid forking the config files.
Short answer: No.
Long answer: For Java you should use System.getProperties(XXX).
Then you can load a Properties file or Configuration based on what you find in os.name.
Alternate Solution just strip off the S: when you read the existing configuration files on non-Windows machines and replace them with the appropriate things.
Opinion: Personally I would bite the bullet and deal with the technical debt now, fix all the configuration files at build time when the deployment for OSX is built and be done with it.
public class WhichOS
{
public static void main(final String[] args)
{
System.out.format("System.getProperty(\"os.name\") = %s\n", System.getProperty("os.name"));
System.out.format("System.getProperty(\"os.arch\") = %s\n", System.getProperty("os.arch"));
System.out.format("System.getProperty(\"os.version\") = %s\n", System.getProperty("os.version"));
}
}
the output on my iMac is:
System.getProperty("os.name") = Mac OS X
System.getProperty("os.arch") = x86_64
System.getProperty("os.version") = 10.6.4
Honestly, don't hard-code absolute paths in a program, even for a single-platform app. Do the correct thing.
The following is my wrong solution, saved to remind myself not to repeat giving a misdirected advice ... shame on me.
Just create a symbolic link named Q: just at the root directory / to / itself.
$ cd /
$ ln -s / Q:
$ ln -s / S:
You might need to use sudo. Then, at the start of your program, just chdir to /.
If you don't want Q: and S: to show up in the Finder, perform
$ /Developer/Tools/SetFile -P -a V Q:
$ /Developer/Tools/SetFile -P -a V S:
which set the invisible-to-the-Finder bit of the files.
The only way you can replace java.io.File is to replace that class in rt.jar.
I don't recommend that, but the best way to do this is to grab a bsd-port of the OpenJDK code, make necessary changes, build it and redistribute the binary with your project. Write a shell script to use your own java binary and not the built-in one.
PS. Just change your config files! Practice your regex skills and save yourself a lot of time.
If you are not willing to change your config file per OS, what are they for in first place?
Every installation should have its own set of config files and use it accordingly.
But if you insist.. you just have to detect the OS version and if is not Windows, ignore the letter:
Something along the lines:
boolean isWindows = System.getProperty("os.name").toLowerCase()
.contains("windows");
String folder = "S:";
if (isWindows && folder.matches("\\w:")) {
folder = "/";
} else if (isWindows && folder.matches("\\w:.+")) {
folder = folder.substring(2);// ignoring the first two letters S:
}
You get the idea
Most likely you'd have to provide a different java.io.File implementation that can parse out the file paths correctly, maybe there's one someone already made.
The real solution is to put this kind of stuff (hard-coded file paths) in configuration files and not in the source code.
Just tested something out, and discovered something interesting: In Windows, if the current directory is on the same logical volume (i.e. root is the same drive letter), you can leave off the drive letter when using a path. So you could just trim off all those drive letters and colons and you should be fine as long as you aren't using paths to items on different disks.
Here's what I finally ended up doing:
I downloaded the source code for the java.io package, and tweaked the code for java.io.File to look for path names that start with a letter and a colon. If it finds one, it prepends "/Volumes/" to the path name, coughs a warning into System.err, then continues as normal.
I've added symlinks under /Volumes to the "drives" I need mapped, so I have:
/Volumes/S:
/Volumes/Q:
I put it into its own jar, and put that jar at the front of the classpath for this project only. This way, the hack affects only me, and only this project.
Net result: java.io.File sees a path like "S:/bling.properties", and then checks the OS. If the OS is OS X, it prepends "/Volumes/", and looks for a file in /Volumes/S:/bling.properties, which is fine, because it can just follow the symlink.
Yeah, it's ugly as hell. But it gets the job done for today.
It looks like I cannot use Desktop.open() on PDF files regardless of location. Here's a small test program:
package com.example.bugs;
import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
public class DesktopOpenBug {
static public void main(String[] args)
{
try {
Desktop desktop = null;
// Before more Desktop API is used, first check
// whether the API is supported by this particular
// virtual machine (VM) on this particular host.
if (Desktop.isDesktopSupported()) {
desktop = Desktop.getDesktop();
for (String path : args)
{
File file = new File(path);
System.out.println("Opening "+file);
desktop.open(file);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
If I run DesktopOpenBug with arguments c:\tmp\zz1.txt c:\tmp\zz.xml c:\tmp\ss.pdf (3 files I happen to have lying around) I get this result: (the .txt and .xml files open up fine)
Opening c:\tmp\zz1.txt
Opening c:\tmp\zz.xml
Opening c:\tmp\ss.pdf
java.io.IOException: Failed to open file:/c:/tmp/ss.pdf. Error message:
The parameter is incorrect.
at sun.awt.windows.WDesktopPeer.ShellExecute(Unknown Source)
at sun.awt.windows.WDesktopPeer.open(Unknown Source)
at java.awt.Desktop.open(Unknown Source)
at com.example.bugs.DesktopOpenBug.main(DesktopOpenBug.java:21)
What the heck is going on? I'm running WinXP, I can type "c:\tmp\ss.pdf" at the command prompt and it opens up just fine.
edit: if this is an example of Sun Java bug #6764271 please help by voting for it. What a pain. >:(
I never knew about this Desktop command, untill recently through this post:
would Java's Runtime.getRuntime().exec() run on windows 7?
Previously i have been using:
Runtime.getRuntime().exec("rundll32 SHELL32.DLL,ShellExec_RunDLL "+ myfile);
And it has always worked for me. If your method does not work, may be you can think about try this command.
If you switch the order of your arugments does that cause one of the other files to get that same error. I wonder if you need to trim the end of the path before calling the File constructor.
umm...yeah ignore that... check the documentation of Desktop.open. open throws an IO exception "if the specified file has no associated application or the associated application fails to be launched " ... also from the top of the page... "The mechanism of registereing, accessing, and launching the associated application is platform-dependent. "
code for the Desktop class: http://fuseyism.com/classpath/doc/java/awt/Desktop-source.html
The open method calls DesktopPeer.open.
DesktopPeer source: http://www.jdocs.com/javase/7.b12/java/awt/peer/DesktopPeer.html
DesktopPeer is implementation specific.
Here is source for a Windows-specific implementation:
http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Platform/windows/sun/awt/windows/WDesktopPeer.java.htm
open->ShellExecute->(Native)ShellExecute
Native ShellExecute is a wrapper for Win32 ShellExecute. Here is info on the function.
http://msdn.microsoft.com/en-us/library/bb762153(VS.85).aspx
My suggestion for a work around would be to write your own implmentation of the ShellExecute function. Here is source from someone who did it. http://www.heimetli.ch/shellexec.html
I have a java class file with a main method. In Windows, I would like to be able to drag files onto a desktop icon/short/etc that would call supply the filenames to my main method. Basically, I want to allow users to drag-and-drop files at program execution instead of having type them on the command line.
Any thoughts?
To build on daub815's answer, in Windows, you can use a batch file to pass
arguments to another command. In this case, we'll use the java launcher to
launch your class with the main method.
I did a quick Google search on how to do write a batch file to take multiple arguments,
and found a page with a batch file to pass arguments to another command. Adapting from
the example, here is what you can do:
#ECHO OFF
:Loop
IF "%1" == "" GOTO Done
java YourClass %1
SHIFT
GOTO Loop
:Done
Save the above file as a batch file (with a ".bat" extension), and then you can drag-and-drop
files onto it, and it will be passed as arguments.
Also, you can call the batch file from the command line and pass arguments as well.
Edit: It appears that the batch file will not work with quoted arguments which contain spaces. Using a workaround presented in the site I've linked to will split the spaces contained in the quoted full path of the file into separate arguments, so that won't work either. If anyone has a good idea how to fix this, please either edit this entry, or post another answer. I will make this a community wiki.
PhiLho's answer works perfectly if you pack the classes in an executable JAR file (it's how you're meant to do it anyway) and make a .reg file that looks like the one below. Then just double-click that .reg file to merge it into the registry and you're good to go. This lets you both double-click a JAR file to run it, and starting it by Drag & Drop.
Do remember to change the path to where your Java executable is installed.
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.jar]
#="jarfile"
[HKEY_CLASSES_ROOT\jarfile\DefaultIcon]
#="C:\\Java\\jdk1.7.0\\bin\\java.exe,1"
[HKEY_CLASSES_ROOT\jarfile\shell\open]
#="Run Java Program"
[HKEY_CLASSES_ROOT\jarfile\shell\open\command]
#="\"C:\\Java\\jdk1.7.0\\bin\\java.exe\" -jar \"%1\" %*"
[HKEY_CLASSES_ROOT\jarfile\shellex\DropHandler]
#="{86C86720-42A0-1069-A2E8-08002B30309D}"
OK, I made it work... The base knowledge is to use DropHandler UUID in the registry. I made a base setting, as follow:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.class]
#="JavaClass"
[HKEY_CLASSES_ROOT\JavaClass\DefaultIcon]
#="C:\\Java\\jdk1.6.0_05\\bin\\java.exe,1"
[HKEY_CLASSES_ROOT\JavaClass\shell\open]
#="Run Java class"
[HKEY_CLASSES_ROOT\JavaClass\shell\open\command]
#="\"C:\\Java\\jdk1.6.0_05\\bin\\java.exe\" \"%1\" %*"
[HKEY_CLASSES_ROOT\JavaClass\shellex\DropHandler]
#="{86C86720-42A0-1069-A2E8-08002B30309D}"
and... it didn't work!
I just forgot that java.exe wants a class name, not a file name! But I see no way to do that in the registry.
Fortunately, there is a workaround, which still need a script file if we want to be generic, to work on any/all class files (with static main function, of course!). Not batch, I avoid them when I can. I chose to use WSH, as it should be available on any modern Windows system. I also chose to make a JS script, it could have been a VB script as well.
So I made the following script (LaunchJavaClass.js):
if (WScript.Arguments.count() == 0)
{
WScript.StdOut.Write("No parameters");
WScript.Quit(1);
}
var className = WScript.Arguments.Item(0);
//~ WScript.StdOut.Write(className + "\n");
var m = className.match(/^(.*)\\(.+?)\.class$/);
if (m == null)
{
WScript.StdOut.Write("Not a class file");
WScript.Quit(1);
}
var classPath = m[1];
className = m[2];
//~ WScript.StdOut.Write(classPath + " >>> " + className + "\n");
var params = new Array();
for (i = 1; i < WScript.Arguments.count(); i++)
{
params[params.length] = WScript.Arguments.Item(i);
}
var cmd = "cmd /c cd /D " + classPath +
" & C:/Java/jdk1.6.0_05/bin/java.exe " +
className + " " + params.join(" ");
//~ WScript.StdOut.Write(cmd + "\n");
var shell = WScript.CreateObject("WScript.Shell");
//~ var exec = shell.Exec(cmd); // Can be used to get stdout
shell.Run(cmd, 0);
I left some output, not useful in this context, but usable for debugging (run with cscript).
Of course, the path to the JRE must be adjusted.
And I changed the command in the registry, as follow:
[HKEY_CLASSES_ROOT\JavaClass\shell\open\command]
#="\wscript -b "D:\\_PhiLhoSoft\\WSH\\LaunchJavaClass.js\" %1 %*"
Of course, adjust path, and keep the above other lines.
Now, if I drag'n'drop some files to a .class file, it gets the short file paths as arguments of the main() function.
import java.io.*;
class TestDnD
{
public static void main(String[] args)
{
Writer output = null;
try
{
output = new BufferedWriter(new FileWriter(new File("LogFile.txt")));
for (String arg : args)
{
output.write(arg + "\n");
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
return;
}
finally
{
try { output.close(); } catch (IOException e) {}
}
}
}
I think the first version of the .reg file can be used for something else, eg. to drag'n'drop on .jar files (adapting it, of course).
This technique has limited use: we rarely make one-class programs in Java! But it looked like a good and interesting challenge, so I didn't resist to solve it. Note: you can add stuff like -Djava.ext.dirs="some path;another path" if you ever need to use external libraries (in jar files).
Adding onto Adiel A. If you create a batch file, which launches your a Java window using Swing. You would have the user drop the files onto that window. You could then be able to root through those dropped files.
So there's no way to have windows itself pass the args into main() via drag and drop?