From 1ad1ab90f8eee7b821d96e7ba0e9790a73e600c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Mon, 6 Apr 2015 18:17:40 +0200 Subject: [PATCH] Issue #350 Allow only one instance (Windows Only, can be turned off in settings) --- .../flash/configuration/Configuration.java | 7 + src/com/jpexs/decompiler/flash/gui/Main.java | 18 ++- src/com/jpexs/decompiler/flash/gui/View.java | 2 +- .../locales/AdvancedSettingsDialog.properties | 3 + .../flash/gui/pipes/FirstInstance.java | 149 ++++++++++++++++++ .../flash/gui/pipes/PipeInputStream.java | 96 +++++++++++ .../flash/gui/pipes/PipeOutputStream.java | 85 ++++++++++ src/com/sun/jna/platform/win32/Kernel32.java | 7 + 8 files changed, 362 insertions(+), 5 deletions(-) create mode 100644 src/com/jpexs/decompiler/flash/gui/pipes/FirstInstance.java create mode 100644 src/com/jpexs/decompiler/flash/gui/pipes/PipeInputStream.java create mode 100644 src/com/jpexs/decompiler/flash/gui/pipes/PipeOutputStream.java diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java index cc2408886..22bf8dde4 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java @@ -420,6 +420,11 @@ public class Configuration { @ConfigurationDefaultBoolean(false) @ConfigurationCategory("ui") public static final ConfigurationItem saveSessionOnExit = null; + + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("ui") + public static final ConfigurationItem allowOnlyOneInstance = null; private enum OSId { @@ -755,4 +760,6 @@ public class Configuration { return null; } + + } diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index 36b690c21..5edd05e9f 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -27,6 +27,7 @@ import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.console.CommandLineArgumentParser; import com.jpexs.decompiler.flash.console.ContextMenuTools; +import com.jpexs.decompiler.flash.gui.pipes.FirstInstance; import com.jpexs.decompiler.flash.gui.proxy.ProxyFrame; import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin; import com.jpexs.decompiler.flash.tags.base.FontTag; @@ -1005,7 +1006,7 @@ public class Main { * @throws IOException On error */ public static void main(String[] args) throws IOException { - + clearTemp(); String pluginPath = Configuration.pluginPath.get(); if (pluginPath != null && !pluginPath.isEmpty()) { @@ -1028,14 +1029,23 @@ public class Main { if (args.length == 0) { initGui(); - showModeFrame(); + if(Configuration.allowOnlyOneInstance.get() && FirstInstance.focus()){ //Try to focus first instance + Main.exit(); + }else{ + showModeFrame(); + } + } else { String[] filesToOpen = CommandLineArgumentParser.parseArguments(args); if (filesToOpen != null && filesToOpen.length > 0) { initGui(); shouldCloseWhenClosingLoadingDialog = true; - for (String fileToOpen : filesToOpen) { - openFile(fileToOpen, null); + if(Configuration.allowOnlyOneInstance.get() && FirstInstance.openFiles(Arrays.asList(filesToOpen))){ //Try to open in first instance + Main.exit(); + }else{ + for (String fileToOpen : filesToOpen) { + openFile(fileToOpen, null); + } } } } diff --git a/src/com/jpexs/decompiler/flash/gui/View.java b/src/com/jpexs/decompiler/flash/gui/View.java index 304c18e93..7e2e51a1c 100644 --- a/src/com/jpexs/decompiler/flash/gui/View.java +++ b/src/com/jpexs/decompiler/flash/gui/View.java @@ -644,5 +644,5 @@ public class View { }); return table; - } + } } diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties index 6d054b086..5b0860fe8 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties @@ -329,3 +329,6 @@ config.description.saveSessionOnExit = Save the current session and reopens it a config.name.showDebugMenu = Show debug menu config.description.showDebugMenu = Shows debug menu in the ribbon + +config.name.allowOnlyOneInstance = Allow only one FFDec instance (Only Windows OS) +config.description.allowOnlyOneInstance = FFDec can be then run only once, all files opened will be added to one window. It works only with Windows operating system. \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/pipes/FirstInstance.java b/src/com/jpexs/decompiler/flash/gui/pipes/FirstInstance.java new file mode 100644 index 000000000..5f6cb6d26 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/pipes/FirstInstance.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2015 Jindra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.pipes; + +import com.jpexs.decompiler.flash.ApplicationInfo; +import com.jpexs.decompiler.flash.gui.Main; +import com.sun.jna.Platform; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.WinError; +import com.sun.jna.platform.win32.WinNT; +import java.awt.Window; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.List; + +/** + * + * @author JPEXS + */ +public class FirstInstance { + + private static final String MUTEX_NAME = "FFDEC_MUTEX"; + private static WinNT.HANDLE mutex; + + public static final int PIPE_MAJOR = 1; + public static final int PIPE_MINOR = 0; + public static final String PIPE_NAME = "ffdec"; + public static final String PIPE_APP_CODE = "ffdec"; + + private static boolean isRunning() { + if (Platform.isWindows()) { + mutex = Kernel32.INSTANCE.CreateMutex(null, false, MUTEX_NAME); + if (mutex == null) { + return false; + } + int er = Kernel32.INSTANCE.GetLastError(); + if (er == WinError.ERROR_ALREADY_EXISTS) { + return true; + } + + new Thread("OtherInstanceCommunicator") { + @Override + public void run() { + while (true) { + try (PipeInputStream pis = new PipeInputStream(PIPE_NAME, true)) { + ObjectInputStream ois = new ObjectInputStream(pis); + String app = ois.readUTF(); + if (app.equals(PIPE_APP_CODE)) { + int major = ois.readInt(); + int minor = ois.readInt(); + int release = ois.readInt(); + int build = ois.readInt(); + int pipeMajor = ois.readInt(); + int pipeMinor = ois.readInt(); + + if (pipeMajor == PIPE_MAJOR) { + String command = ois.readUTF(); + switch (command) { + case "open": + int cnt = ois.readInt(); + for (int i = 0; i < cnt; i++) { + Main.openFile(ois.readUTF(), null); + } + //no break - focus too + case "focus": + + java.awt.EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + Window wnd = Main.getMainFrame().getWindow(); + wnd.setAlwaysOnTop(true); + wnd.toFront(); + wnd.requestFocus(); + wnd.setAlwaysOnTop(false); + wnd.repaint(); + } + }); + break; + } + } + + } + } catch (IOException ex) { + //ignore + } + } + } + }.start(); + + } + return false; + } + + private static ObjectOutputStream startCommand(String command) throws IOException { + PipeOutputStream pos = new PipeOutputStream(PIPE_NAME, false); + ObjectOutputStream oos = new ObjectOutputStream(pos); + oos.writeUTF(PIPE_APP_CODE); + oos.writeInt(ApplicationInfo.version_major); + oos.writeInt(ApplicationInfo.version_minor); + oos.writeInt(ApplicationInfo.version_release); + oos.writeInt(ApplicationInfo.version_build); + oos.writeInt(PIPE_MAJOR); + oos.writeInt(PIPE_MINOR); + oos.writeUTF(command); + return oos; + } + + public static boolean focus() { + if (!isRunning()) { + return false; + } + try { + ObjectOutputStream oos = startCommand("focus"); + oos.close(); + return true; + } catch (IOException ex) { + return false; + } + } + + public static boolean openFiles(List files) { + try { + ObjectOutputStream oos = startCommand("open"); + oos.writeInt(files.size()); + for (String s : files) { + oos.writeUTF(s); + } + oos.close(); + return true; + } catch (IOException ex) { + return false; + } + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/pipes/PipeInputStream.java b/src/com/jpexs/decompiler/flash/gui/pipes/PipeInputStream.java new file mode 100644 index 000000000..e42fa06e9 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/pipes/PipeInputStream.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2015 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.pipes; + +import com.sun.jna.Platform; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.ptr.IntByReference; +import java.io.IOException; +import java.io.InputStream; + +/** + * + * @author JPEXS + */ +public class PipeInputStream extends InputStream { + + protected HANDLE pipe; + private boolean closed = false; + + public PipeInputStream(String pipeName,boolean newpipe) throws IOException { + if (!Platform.isWindows()) { + throw new IOException("Cannot create Pipe on nonWindows OS"); + } + String fullPipePath = "\\\\.\\pipe\\" + pipeName; + if(newpipe){ + pipe = Kernel32.INSTANCE.CreateNamedPipe(fullPipePath, Kernel32.PIPE_ACCESS_INBOUND, Kernel32.PIPE_TYPE_BYTE, 1, 4096, 4096, 0, null); + if (pipe == null || !Kernel32.INSTANCE.ConnectNamedPipe(pipe, null)) { + throw new IOException("Cannot connect to the pipe"); + } + }else{ + pipe = Kernel32.INSTANCE.CreateFile(fullPipePath, Kernel32.GENERIC_READ,Kernel32.FILE_SHARE_READ,null,Kernel32.OPEN_EXISTING,Kernel32.FILE_ATTRIBUTE_NORMAL,null); + } + if (pipe == null) { + throw new IOException("Cannot connect to the pipe"); + } + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + try { + close(); + } catch (IOException ex) { + //ignore + } + } + + }); + } + + @Override + public synchronized void close() throws IOException { + if (!closed) { + Kernel32.INSTANCE.CloseHandle(pipe); + closed = true; + } + } + + @Override + public synchronized int read() throws IOException { + byte d[] = new byte[1]; + if (readPipe(d) == 0) { + return -1; + } + return d[0]; + } + + private int readPipe(byte res[]) throws IOException { + final IntByReference ibr = new IntByReference(); + int read = 0; + while (read < res.length) { + byte[] data = new byte[res.length - read]; + boolean result = Kernel32.INSTANCE.ReadFile(pipe, data, data.length, ibr, null); + if (!result) { + throw new IOException("Cannot read pipe. Error " + Kernel32.INSTANCE.GetLastError()); + } + int readNow = ibr.getValue(); + System.arraycopy(data, 0, res, read, readNow); + read += readNow; + } + return read; + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/pipes/PipeOutputStream.java b/src/com/jpexs/decompiler/flash/gui/pipes/PipeOutputStream.java new file mode 100644 index 000000000..dc50380c6 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/pipes/PipeOutputStream.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.pipes; + +import com.sun.jna.Platform; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.ptr.IntByReference; +import java.io.IOException; +import java.io.OutputStream; +import java.util.logging.Level; + +/** + * + * @author JPEXS + */ +public class PipeOutputStream extends OutputStream { + + protected HANDLE pipe; + private boolean closed = false; + + public PipeOutputStream(String pipeName,boolean newPipe) throws IOException { + if (!Platform.isWindows()) { + throw new IOException("Cannot create Pipe on nonWindows OS"); + } + String fullPipePath = "\\\\.\\pipe\\" + pipeName; + if(newPipe){ + pipe = Kernel32.INSTANCE.CreateNamedPipe(fullPipePath, Kernel32.PIPE_ACCESS_OUTBOUND, Kernel32.PIPE_TYPE_BYTE, 1, 4096, 4096, 0, null); + if (pipe == null || !Kernel32.INSTANCE.ConnectNamedPipe(pipe, null)) { + throw new IOException("Cannot connect to the pipe. Error " + Kernel32.INSTANCE.GetLastError()); + } + }else{ + pipe = Kernel32.INSTANCE.CreateFile(fullPipePath, Kernel32.GENERIC_WRITE,Kernel32.FILE_SHARE_WRITE,null,Kernel32.OPEN_EXISTING,Kernel32.FILE_ATTRIBUTE_NORMAL,null); + if (pipe == null) { + throw new IOException("Cannot connect to the pipe. Error " + Kernel32.INSTANCE.GetLastError()); + } + } + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + try { + close(); + } catch (IOException ex) { + //ignore + } + } + + }); + } + + @Override + public synchronized void close() throws IOException { + if (!closed) { + Kernel32.INSTANCE.CloseHandle(pipe); + closed = true; + } + } + + @Override + public synchronized void write(int b) throws IOException { + byte data[] = new byte[]{(byte) b}; + IntByReference ibr = new IntByReference(); + boolean result = Kernel32.INSTANCE.WriteFile(pipe, data, data.length, ibr, null); + if (!result) { + throw new IOException("Cannot write to the pipe. Error " + Kernel32.INSTANCE.GetLastError()); + } + if (ibr.getValue() != data.length) { + throw new IOException("Cannot write to the pipe. Error " + Kernel32.INSTANCE.GetLastError()); + } + } +} diff --git a/src/com/sun/jna/platform/win32/Kernel32.java b/src/com/sun/jna/platform/win32/Kernel32.java index 21a01004b..7c0750879 100644 --- a/src/com/sun/jna/platform/win32/Kernel32.java +++ b/src/com/sun/jna/platform/win32/Kernel32.java @@ -338,4 +338,11 @@ public interface Kernel32 extends WinNT { public static final int LOCALE_SISO639LANGNAME = 89; int GetLocaleInfo(int Locale, int LCType, char[] lpLCData, int cchData); + + public HANDLE CreateMutex(WinBase.SECURITY_ATTRIBUTES sa, boolean initialOwner, String name); + + HANDLE CreateFile(String lpFileName, int dwDesiredAccess, int dwShareMode, + WinBase.SECURITY_ATTRIBUTES lpSecurityAttributes, int dwCreationDisposition, + int dwFlagsAndAttributes, HANDLE hTemplateFile); + }