diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/IDFEnvironmentVariables.java b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/IDFEnvironmentVariables.java
index 12867f621..480eb0b4e 100644
--- a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/IDFEnvironmentVariables.java
+++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/IDFEnvironmentVariables.java
@@ -52,6 +52,10 @@ public class IDFEnvironmentVariables
public static final String IDF_CCACHE_ENABLE = "IDF_CCACHE_ENABLE"; //$NON-NLS-1$
public static final String ESP_IDF_EIM_ID = "ESP_IDF_EIM_ID"; //$NON-NLS-1$
+
+ public static final String EIM_PATH = "EIM_PATH"; //$NON-NLS-1$
+
+ public static final String SYSTEM_PATH = "SYSTEM_PATH"; //$NON-NLS-1$
/**
* @param variableName Environment variable Name
diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/build/Messages.java b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/build/Messages.java
index aa552a03d..1bcfde257 100644
--- a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/build/Messages.java
+++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/build/Messages.java
@@ -50,6 +50,9 @@ public class Messages extends NLS
public static String OldConfigExportCompleteSuccessMsg;
public static String OldConfigExportCompleteFailMsgTitle;
public static String OldConfigExportCompleteFailMsg;
+
+ public static String EIMNotInApplicationsTitle;
+ public static String EIMNotInApplicationsMessage;
static
{
diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/build/messages.properties b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/build/messages.properties
index a101c23f5..7e31a1293 100644
--- a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/build/messages.properties
+++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/build/messages.properties
@@ -19,8 +19,8 @@ IDFBuildConfiguration_ParseCommand=Parse Compile Commands File
IncreasePartitionSizeTitle=Low Application Partition Size
IncreasePartitionSizeMessage=Less than 30% of application partition size is free({0} of {1} bytes), would you like to increase it? Please click here to check more details.
ToolsInitializationDifferentPathMessageBoxTitle=Different IDF path found in the config file
-ToolsInitializationEimMissingMsgBoxTitle=ESP-IDF Not Found
-ToolsInitializationEimMissingMsgBoxMessage=ESP-IDF is not found on your system. To use the IDE, install ESP-IDF using EIM - GUI Installer. \n\nOnce installed, the IDE will automatically detect ESP-IDF. You can verify and activate it from the ESP-IDF Manager, accessible via the menu: Espressif > ESP-IDF Manager.\n\n
+ToolsInitializationEimMissingMsgBoxTitle=ESP-IDF Installation Required
+ToolsInitializationEimMissingMsgBoxMessage=ESP-IDF is not currently installed on your system.\n\nThe IDE can automatically download and launch the EIM - GUI Installer to help you install it.\n\nWould you like to proceed with the installation?\n\nOnce complete, ESP-IDF will be detected automatically. You can also manage installations later from the ESP-IDF Manager (Espressif > ESP-IDF Manager).
ToolsInitializationDifferentPathMessageBoxMessage=A different ESP-IDF path was found in the esp_idf.json.json configuration file. Do you want to install the tools in the new path or the old path? Please click on the appropriate button.\nNew Path: {0}\nOld Path: {1}
ToolsInitializationDifferentPathMessageBoxOptionYes=Yes
ToolsInitializationDifferentPathMessageBoxOptionNo=No
@@ -31,6 +31,9 @@ IDFToolChainsMissingErrorMsg=Toolchains are missing. Please use ESP-IDF Manager
NoActiveEspIdfInWorkspaceMsgTitle=ESP-IDF Setup
NoActiveEspIdfInWorkspaceMsg=ESP-IDF is required to use Espressif IDE. Would you like to configure it now?
+EIMNotInApplicationsTitle=EIM Not Located in Applications Folder
+EIMNotInApplicationsMessage=Espressif Installation Manager (EIM) is not located in the Applications folder.\nRunning EIM from a temporary location (e.g., a mounted disk image) may cause issues during configuration import or tool setup.\nTo avoid problems, please move EIM to the /Applications directory and launch it from there.
+
OldConfigFoundMsgBoxTitle=Old Configuration Detected
OldConfigFoundMsgBoxMsg=Espressif IDE now uses the EIM system to manage ESP-IDF installations. A legacy configuration was found in your current workspace. Converting it to the EIM format will allow proper environment setup and ensure the IDE works seamlessly with your existing projects. Would you like to convert the configuration now?
OldConfigExportDirectorSelectionDialogTitle=Select Destination
@@ -38,4 +41,4 @@ OldConfigExportDirectorSelectionDialogInfo=Choose a directory to save the export
OldConfigExportCompleteSuccessMsgTitle=Import Successful
OldConfigExportCompleteSuccessMsg=The configuration has been successfully Imported
OldConfigExportCompleteFailMsgTitle=Conversion Failed
-OldConfigExportCompleteFailMsg=An error occurred while converting old configuration.
\ No newline at end of file
+OldConfigExportCompleteFailMsg=An error occurred while converting old configuration.
diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/DownloadListener.java b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/DownloadListener.java
new file mode 100644
index 000000000..959713f13
--- /dev/null
+++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/DownloadListener.java
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright 2025 Espressif Systems (Shanghai) PTE LTD. All rights reserved.
+ * Use is subject to license terms.
+ *******************************************************************************/
+package com.espressif.idf.core.tools;
+
+/**
+ * Interface to use for the download listening this can be used in your own classes.
+ * Added specifically for {@link EimLoader}
+ * @author Ali Azam Rana
+ *
+ */
+public interface DownloadListener
+{
+ public void onProgress(int percent);
+ public void onCompleted(String filePath);
+ public void onError(String message, Exception e);
+
+}
diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimConstants.java b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimConstants.java
index 1a43560ef..984e9ebbe 100644
--- a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimConstants.java
+++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimConstants.java
@@ -1,12 +1,17 @@
package com.espressif.idf.core.tools;
+import java.io.File;
+import java.nio.file.Paths;
+
public interface EimConstants
{
String EIM_JSON = "eim_idf.json"; //$NON-NLS-1$
- String EIM_POSIX_DIR = System.getProperty("user.home").concat("/.espressif/tools/"); //$NON-NLS-1$//$NON-NLS-2$
+ String EIM_POSIX_DIR = System.getProperty("user.home").concat(File.separator + ".espressif" + File.separator + "tools" + File.separator); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
+
+ String EIM_WIN_ESPRESSIF_DIR = "C:" + File.separator + "Espressif"; //$NON-NLS-1$ //$NON-NLS-2$
- String EIM_WIN_DIR = "C:\\Espressif\\tools\\"; //$NON-NLS-1$
+ String EIM_WIN_DIR = EIM_WIN_ESPRESSIF_DIR + File.separator + "tools" + File.separator; //$NON-NLS-1$
String EIM_WIN_PATH = EIM_WIN_DIR + EIM_JSON;
@@ -19,4 +24,7 @@ public interface EimConstants
String TOOL_SET_CONFIG_LEGACY_CONFIG_FILE = "tool_set_config.json"; //$NON-NLS-1$
String OLD_CONFIG_EXPORTED_FLAG = "OLD_CONFIG_EXPORTED_FLAG"; //$NON-NLS-1$
+
+ String USER_EIM_DIR = Paths.get(System.getProperty("user.home"), ".espressif", "eim_gui").toString(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
}
diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimLoader.java b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimLoader.java
new file mode 100644
index 000000000..4db1005c2
--- /dev/null
+++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimLoader.java
@@ -0,0 +1,552 @@
+/*******************************************************************************
+ * Copyright 2025 Espressif Systems (Shanghai) PTE LTD. All rights reserved.
+ * Use is subject to license terms.
+ *******************************************************************************/
+package com.espressif.idf.core.tools;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+import java.util.Optional;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.IJobChangeEvent;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.core.runtime.jobs.JobChangeAdapter;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.console.MessageConsoleStream;
+
+import com.espressif.idf.core.IDFEnvironmentVariables;
+import com.espressif.idf.core.logging.Logger;
+import com.espressif.idf.core.tools.watcher.EimJsonWatchService;
+import com.espressif.idf.core.util.StringUtil;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+/**
+ * This class is responsible for downloading and launching the EIM.
+ * The clients using this must take care of UI refreshes and pausing any listeners.
+ * @author Ali Azam Rana
+ *
+ */
+public class EimLoader
+{
+ private static final String URL_JSON = "https://dl.espressif.com/dl/eim/eim_unified_release.json"; //$NON-NLS-1$
+ private static final Path DOWNLOAD_DIR = Paths.get(System.getProperty("java.io.tmpdir"), "eim_gui"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ private String os;
+ private String arch;
+ private DownloadListener listener;
+ private MessageConsoleStream standardConsoleStream;
+ private MessageConsoleStream errorConsoleStream;
+ private Display display;
+ private long windowsPid;
+
+ public EimLoader(DownloadListener listener, MessageConsoleStream standardConsoleStream, MessageConsoleStream errorConsoleStream, Display display)
+ {
+ os = Platform.getOS();
+ arch = Platform.getOSArch();
+ this.listener = listener;
+ this.standardConsoleStream = standardConsoleStream;
+ this.errorConsoleStream = errorConsoleStream;
+ this.display = display;
+ }
+
+ private void logMessage(String message)
+ {
+ display.asyncExec(()->{
+ try
+ {
+ standardConsoleStream.write(message);
+ }
+ catch (IOException e)
+ {
+ Logger.log(e);
+ logError(e.getMessage());
+ }
+ });
+
+ Logger.log(message);
+ }
+
+ private void logError(String message)
+ {
+ display.asyncExec(()->{
+ try
+ {
+ errorConsoleStream.write(message);
+ }
+ catch (IOException e)
+ {
+ Logger.log(e);
+ }
+ });
+
+ Logger.log(message);
+ }
+
+ public Process launchEim(String eimPath) throws IOException
+ {
+ if (!Files.exists(Paths.get(eimPath)))
+ throw new FileNotFoundException("EIM path not found: " + eimPath); //$NON-NLS-1$
+
+ String os = Platform.getOS();
+ List command;
+
+ if (os.equals(Platform.OS_WIN32))
+ {
+ String escapedPathForPowershell = eimPath.replace("'", "''"); //$NON-NLS-1$ //$NON-NLS-2$
+ String powershellCmd = String.format(
+ "Start-Process -FilePath '%s' -PassThru | " //$NON-NLS-1$
+ + "Select-Object -ExpandProperty Id", //$NON-NLS-1$
+ escapedPathForPowershell);
+
+ command = List.of("powershell.exe", //$NON-NLS-1$
+ "-Command", powershellCmd); //$NON-NLS-1$
+ }
+ else if (os.equals(Platform.OS_MACOSX))
+ {
+ command = List.of("open", "-W", "-a", eimPath); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
+ }
+ else if (os.equals(Platform.OS_LINUX))
+ {
+ command = List.of("bash", "-c", "\"" + eimPath + "\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+ else
+ {
+ throw new UnsupportedOperationException("Unsupported OS: " + os); //$NON-NLS-1$
+ }
+
+ Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
+ if (os.equals(Platform.OS_WIN32))
+ {
+ // store the PID returned by powershell query to a variable
+ storePid(process);
+ }
+
+ logMessage("Launched EIM application: " + eimPath + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ return process;
+ }
+
+ private void storePid(Process powershellProcess)
+ {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(powershellProcess.getInputStream())))
+ {
+ String line;
+ while ((line = reader.readLine()) != null)
+ {
+ line = line.trim();
+ if (!line.isEmpty())
+ {
+ try
+ {
+ windowsPid = Long.parseLong(line);
+
+ }
+ catch (NumberFormatException ignored)
+ {
+ // skipping invalid lines
+ }
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ Logger.log(e);
+ }
+ }
+
+ public String installAndLaunchDmg(Path dmgPath) throws IOException, InterruptedException
+ {
+ logMessage("Mounting DMG…\n"); //$NON-NLS-1$
+ Process mountProcess = new ProcessBuilder("hdiutil", "attach", dmgPath.toString()) //$NON-NLS-1$ //$NON-NLS-2$
+ .redirectErrorStream(true).start();
+
+ int mountExit = mountProcess.waitFor();
+ if (mountExit != 0)
+ throw new IOException("hdiutil attach failed (exit " + mountExit + "): " //$NON-NLS-1$ //$NON-NLS-2$
+ + readProcessOutput(mountProcess));
+
+ String volumePath = parseVolumePath(mountProcess.getInputStream());
+ if (volumePath == null)
+ throw new IOException("Failed to mount DMG: volume path not found."); //$NON-NLS-1$
+
+ File[] apps = new File(volumePath).listFiles((dir, name) -> name.endsWith(".app")); //$NON-NLS-1$
+ if (apps == null || apps.length == 0)
+ throw new FileNotFoundException("No .app found inside DMG."); //$NON-NLS-1$
+
+ File appBundle = apps[0];
+ Path targetAppPath = Paths.get("/Applications", appBundle.getName()); //$NON-NLS-1$
+
+ logMessage("Copying app to /Applications…\n"); //$NON-NLS-1$
+ Process copyProcess = new ProcessBuilder("cp", "-R", appBundle.getAbsolutePath(), //$NON-NLS-1$ //$NON-NLS-2$
+ targetAppPath.toString()).redirectErrorStream(true).start();
+
+ int copyExit = copyProcess.waitFor();
+ if (copyExit != 0)
+ throw new IOException("Copy failed (exit " + copyExit + "): " //$NON-NLS-1$ //$NON-NLS-2$
+ + readProcessOutput(copyProcess));
+
+ logMessage("Unmounting DMG…\n"); //$NON-NLS-1$
+ Process detachProcess = new ProcessBuilder("hdiutil", "detach", volumePath) //$NON-NLS-1$ //$NON-NLS-2$
+ .redirectErrorStream(true).start();
+
+ int detachExit = detachProcess.waitFor();
+ if (detachExit != 0)
+ throw new IOException("hdiutil detach failed (exit " + detachExit + "): " //$NON-NLS-1$ //$NON-NLS-2$
+ + readProcessOutput(detachProcess));
+
+ String eimPath = targetAppPath.resolve("Contents/MacOS/eim").toString(); //$NON-NLS-1$
+ new IDFEnvironmentVariables().addEnvVariable(IDFEnvironmentVariables.EIM_PATH, eimPath);
+ return eimPath;
+ }
+
+ private String readProcessOutput(Process p) throws IOException
+ {
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())))
+ {
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = br.readLine()) != null) sb.append(line).append(System.lineSeparator());
+ return sb.toString();
+ }
+ }
+
+ private String parseVolumePath(InputStream mountOut) throws IOException
+ {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(mountOut)))
+ {
+ String line;
+ while ((line = reader.readLine()) != null)
+ {
+ if (line.contains("/Volumes/")) //$NON-NLS-1$
+ {
+ for (String part : line.split("\t")) //$NON-NLS-1$
+ if (part.startsWith("/Volumes/")) return part.trim(); //$NON-NLS-1$
+ }
+ }
+ }
+ return null;
+ }
+
+
+ public void downloadEim(IProgressMonitor monitor)
+ {
+ try
+ {
+ JsonObject root = fetchJson();
+ JsonArray assets = root.getAsJsonArray("assets"); //$NON-NLS-1$
+ Optional match = findMatchingAsset(assets);
+
+ if (match.isEmpty())
+ {
+ listener.onError("No suitable EIM GUI asset found.", null); //$NON-NLS-1$
+ monitor.done();
+ return;
+ }
+
+ JsonObject asset = match.get();
+ String name = asset.get("name").getAsString(); //$NON-NLS-1$
+ String downloadUrl = asset.get("browser_download_url").getAsString(); //$NON-NLS-1$
+
+ Files.createDirectories(DOWNLOAD_DIR);
+ cleanupDownloadDirectory();
+ Path downloadPath = DOWNLOAD_DIR.resolve(name);
+
+ downloadFile(downloadUrl, downloadPath, listener, monitor);
+ Path eimPath = Paths.get(EimConstants.USER_EIM_DIR);
+ Files.createDirectories(eimPath);
+
+ if (name.endsWith(".zip")) //$NON-NLS-1$
+ {
+ Path extracted = unzip(downloadPath, eimPath);
+ listener.onCompleted(extracted.toAbsolutePath().toString());
+ }
+ else if (name.endsWith(".exe")) //$NON-NLS-1$
+ {
+ eimPath = Paths.get(eimPath.toString(), name);
+ Files.copy(downloadPath, eimPath, StandardCopyOption.REPLACE_EXISTING);
+ listener.onCompleted(eimPath.toString());
+ }
+ else
+ {
+ listener.onCompleted(downloadPath.toAbsolutePath().toString());
+ }
+ }
+ catch (IOException e)
+ {
+ listener.onError("Download failed", e); //$NON-NLS-1$
+ } finally
+ {
+ monitor.done();
+ }
+
+ }
+
+ private IStatus waitForProcessWindows()
+ {
+ while (isWindowsProcessAlive(windowsPid))
+ {
+ try
+ {
+ Thread.sleep(1000);
+ }
+ catch (InterruptedException e)
+ {
+ Logger.log(e);
+ }
+ }
+
+ return Status.OK_STATUS;
+ }
+
+ private boolean isWindowsProcessAlive(long pid)
+ {
+ try
+ {
+ Process check = new ProcessBuilder("cmd.exe", //$NON-NLS-1$
+ "/c", //$NON-NLS-1$
+ "tasklist", //$NON-NLS-1$
+ "/FI", //$NON-NLS-1$
+ "\"PID eq " + pid + "\"").redirectErrorStream(true).start(); //$NON-NLS-1$ //$NON-NLS-2$
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(check.getInputStream())))
+ {
+ String line;
+ while ((line = reader.readLine()) != null)
+ {
+ if (line.contains(String.valueOf(windowsPid)))
+ {
+ return true;
+ }
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ Logger.log(e);
+ }
+
+ return false;
+ }
+
+ private IStatus waitForProcess(Process process)
+ {
+ try
+ {
+ process.waitFor();
+ return Status.OK_STATUS;
+ }
+ catch (InterruptedException e)
+ {
+ return Status.CANCEL_STATUS;
+ }
+ catch (Exception e)
+ {
+ Logger.log(e);
+ return Status.error(e.getMessage());
+ }
+ }
+
+ public void waitForEimClosure(Process process, Runnable callback)
+ {
+ Job waitJob = new Job("Wait for EIM Closure") //$NON-NLS-1$
+ {
+ @Override
+ protected IStatus run(IProgressMonitor monitor)
+ {
+ return os.equals(Platform.OS_WIN32) ? waitForProcessWindows() : waitForProcess(process);
+ }
+ };
+ waitJob.setSystem(true);
+
+
+ waitJob.addJobChangeListener(new JobChangeAdapter()
+ {
+ @Override
+ public void aboutToRun(IJobChangeEvent event)
+ {
+ EimJsonWatchService.getInstance().pauseListeners();
+ }
+
+ @Override
+ public void done(IJobChangeEvent event)
+ {
+ Display.getDefault().asyncExec(() -> {
+ try
+ {
+ standardConsoleStream.write("EIM has been closed.\n"); //$NON-NLS-1$
+ }
+ catch (IOException e)
+ {
+ Logger.log(e);
+ }
+ });
+
+ if (callback != null)
+ {
+ callback.run();
+ }
+
+ EimJsonWatchService.getInstance().unpauseListeners();
+ }
+ });
+
+ waitJob.schedule();
+ }
+
+ private JsonObject fetchJson() throws IOException
+ {
+ URL url = new URL(URL_JSON);
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestProperty("accept", "application/json"); //$NON-NLS-1$//$NON-NLS-2$
+ connection.setConnectTimeout(10000);
+ connection.setReadTimeout(10000);
+
+ try (InputStreamReader reader = new InputStreamReader(connection.getInputStream()))
+ {
+ return JsonParser.parseReader(reader).getAsJsonObject();
+ }
+ }
+
+ private void cleanupDownloadDirectory()
+ {
+ try
+ {
+ Files.list(DOWNLOAD_DIR)
+ .filter(Files::isRegularFile)
+ .forEach(path -> {
+ try
+ {
+ Files.deleteIfExists(path);
+ }
+ catch (IOException e)
+ {
+ Logger.log("Failed to delete old download: " + path); //$NON-NLS-1$
+ }
+ });
+ }
+ catch (IOException e)
+ {
+ Logger.log("Failed to clean up download directory: " + e.getMessage()); //$NON-NLS-1$
+ }
+ }
+
+ private Optional findMatchingAsset(JsonArray assets)
+ {
+ String osToken = switch (os)
+ {
+ case Platform.OS_WIN32 -> "windows"; //$NON-NLS-1$
+ case Platform.OS_MACOSX -> "macos"; //$NON-NLS-1$
+ case Platform.OS_LINUX -> "linux"; //$NON-NLS-1$
+ default -> StringUtil.EMPTY;
+ };
+
+ String archToken = switch (arch)
+ {
+ case Platform.ARCH_X86_64 -> "x64"; //$NON-NLS-1$
+ case Platform.ARCH_AARCH64, "arm64" -> "aarch64"; //$NON-NLS-1$ //$NON-NLS-2$
+ default -> StringUtil.EMPTY;
+ };
+
+ for (int i = 0; i < assets.size(); i++)
+ {
+ JsonObject asset = assets.get(i).getAsJsonObject();
+ String name = asset.get("name").getAsString().toLowerCase(); //$NON-NLS-1$
+ if (name.contains("eim-gui") && //$NON-NLS-1$
+ name.contains(osToken) && name.contains(archToken)
+ && (name.endsWith(".exe") || name.endsWith(".dmg") || name.endsWith(".zip"))) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ {
+ return Optional.of(asset);
+ }
+ }
+
+ return Optional.empty();
+ }
+
+ private void downloadFile(String fileURL, Path targetPath, DownloadListener listener, IProgressMonitor monitor)
+ throws IOException
+ {
+ URL url = new URL(fileURL);
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setConnectTimeout(10000);
+ connection.setReadTimeout(10000);
+
+ int contentLength = connection.getContentLength();
+ monitor.beginTask("Downloading " + targetPath.getFileName(), contentLength); //$NON-NLS-1$
+
+ try (InputStream in = connection.getInputStream(); OutputStream out = Files.newOutputStream(targetPath))
+ {
+
+ byte[] buffer = new byte[8192];
+ int bytesRead;
+ long totalRead = 0;
+ int lastPercent = 0;
+
+ while ((bytesRead = in.read(buffer)) != -1)
+ {
+ out.write(buffer, 0, bytesRead);
+ totalRead += bytesRead;
+ if (contentLength > 0)
+ {
+ int percent = (int) ((totalRead * 100) / contentLength);
+ if (percent != lastPercent)
+ {
+ listener.onProgress(percent);
+ lastPercent = percent;
+ monitor.worked(bytesRead);
+ }
+ }
+ }
+ }
+ }
+
+ private Path unzip(Path zipPath, Path destDir) throws IOException
+ {
+ Files.createDirectories(destDir);
+ Path firstExecutable = null;
+
+ try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipPath.toFile())))
+ {
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null)
+ {
+ Path newPath = destDir.resolve(entry.getName());
+ if (entry.isDirectory())
+ {
+ Files.createDirectories(newPath);
+ }
+ else
+ {
+ Files.createDirectories(newPath.getParent());
+ Files.copy(zis, newPath, StandardCopyOption.REPLACE_EXISTING);
+ if (firstExecutable == null && Files.isRegularFile(newPath))
+ {
+ newPath.toFile().setExecutable(true);
+ firstExecutable = newPath;
+ }
+ }
+ }
+ }
+ return firstExecutable != null ? firstExecutable : destDir;
+ }
+
+}
diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/SetupToolsInIde.java b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/SetupToolsInIde.java
index 1adfc9621..a4b603a7d 100644
--- a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/SetupToolsInIde.java
+++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/SetupToolsInIde.java
@@ -221,31 +221,18 @@ public void run()
}
}
- private String replacePathVariable(String value)
+ private String getUpdatedPathWithSystemPath()
{
- // Get system PATH
- Map systemEnv = new HashMap<>(System.getenv());
- String pathEntry = systemEnv.get("PATH"); //$NON-NLS-1$
- if (pathEntry == null)
- {
- pathEntry = systemEnv.get("Path"); // for Windows //$NON-NLS-1$
- if (pathEntry == null) // no idea
- {
- Logger.log(new Exception("No PATH found in the system environment variables")); //$NON-NLS-1$
- }
- }
-
- if (!StringUtil.isEmpty(pathEntry))
- {
- value = value.concat(File.pathSeparator).concat(pathEntry);
- }
+ // EIM is giving us SYSTEM_PATH variable as well so we can simply add it to the PATH here and remove it from ENV vars
+ IDFEnvironmentVariables idfEnvironmentVariables = new IDFEnvironmentVariables();
- if (Platform.getOS().equals(Platform.OS_MACOSX))
- {
- value = value.concat(File.pathSeparator).concat("/opt/homebrew/bin").concat(File.pathSeparator).concat("/usr/local/bin"); //$NON-NLS-1$ //$NON-NLS-2$
- }
+ String systemPath = idfEnvironmentVariables.getEnvValue(IDFEnvironmentVariables.SYSTEM_PATH);
+ String path = idfEnvironmentVariables.getEnvValue(IDFEnvironmentVariables.PATH) + File.pathSeparator + systemPath;
+
+ // we can remove the system_path from build vars as we dont need it here
+ idfEnvironmentVariables.removeEnvVariable(IDFEnvironmentVariables.SYSTEM_PATH);
- return value;
+ return path;
}
private IStatus loadTargetsAvailableFromIdfInCurrentToolSet(boolean rollback)
@@ -390,7 +377,7 @@ private void setupEnvVarsInEclipse()
idfEnvironmentVariables.addEnvVariable(entry.getKey(), entry.getValue());
}
- String path = replacePathVariable(envVarsFromActivationScriptMap.get(IDFEnvironmentVariables.PATH));
+ String path = getUpdatedPathWithSystemPath();
idfEnvironmentVariables.addEnvVariable(IDFEnvironmentVariables.PATH, path);
idfEnvironmentVariables.addEnvVariable(IDFEnvironmentVariables.IDF_COMPONENT_MANAGER, "1"); //$NON-NLS-1$
@@ -400,6 +387,8 @@ private void setupEnvVarsInEclipse()
idfEnvironmentVariables.addEnvVariable(IDFEnvironmentVariables.PYTHON_EXE_PATH, idfInstalled.getPython());
+
+
IDFUtil.updateEspressifPrefPageOpenocdPath();
}
diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/ToolInitializer.java b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/ToolInitializer.java
index 42fb18a12..7b7e514f8 100644
--- a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/ToolInitializer.java
+++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/ToolInitializer.java
@@ -6,6 +6,8 @@
import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
@@ -17,9 +19,11 @@
import org.osgi.service.prefs.Preferences;
import com.espressif.idf.core.IDFCorePlugin;
+import com.espressif.idf.core.IDFEnvironmentVariables;
import com.espressif.idf.core.ProcessBuilderFactory;
import com.espressif.idf.core.logging.Logger;
import com.espressif.idf.core.tools.vo.EimJson;
+import com.espressif.idf.core.util.StringUtil;
/**
* Initializer class to be used on startup of eclipse and also
@@ -31,16 +35,26 @@ public class ToolInitializer
{
private final Preferences preferences;
private final EimIdfConfiguratinParser parser;
+ private IDFEnvironmentVariables idfEnvironmentVariables;
public ToolInitializer(Preferences preferences)
{
this.preferences = preferences;
this.parser = new EimIdfConfiguratinParser();
+ idfEnvironmentVariables = new IDFEnvironmentVariables();
}
public boolean isEimInstalled()
{
- return isEimIdfJsonPresent();
+ String eimExePathEnv = idfEnvironmentVariables.getEnvValue(IDFEnvironmentVariables.EIM_PATH);
+ return !StringUtil.isEmpty(eimExePathEnv) && Files.exists(Paths.get(eimExePathEnv));
+ }
+
+ public boolean isEimIdfJsonPresent()
+ {
+ String path = Platform.getOS().equals(Platform.OS_WIN32) ? EimConstants.EIM_WIN_PATH
+ : EimConstants.EIM_POSIX_PATH;
+ return new File(path).exists();
}
public EimJson loadEimJson()
@@ -61,16 +75,18 @@ public boolean isOldEspIdfConfigPresent()
return getOldConfigFile().exists();
}
- public IStatus exportOldConfig() throws IOException
+ public IStatus exportOldConfig(String eimPath) throws IOException
{
File oldConfig = getOldConfigFile();
if (oldConfig.exists())
{
// eim import pathToOldConfigJson
List commands = new ArrayList<>();
- commands.add(loadEimJson().getEimPath());
+ commands.add(StringUtil.isEmpty(eimPath) ?
+ idfEnvironmentVariables.getEnvValue(IDFEnvironmentVariables.EIM_PATH) : eimPath);
commands.add("import"); //$NON-NLS-1$
commands.add(oldConfig.getAbsolutePath());
+ Logger.log("Running: " + commands.toString()); //$NON-NLS-1$
ProcessBuilderFactory processBuilderFactory = new ProcessBuilderFactory();
IStatus status = processBuilderFactory.runInBackground(commands, org.eclipse.core.runtime.Path.ROOT,
System.getenv());
@@ -93,13 +109,6 @@ private File getOldConfigFile()
return new File(path.toOSString(), EimConstants.TOOL_SET_CONFIG_LEGACY_CONFIG_FILE);
}
- private boolean isEimIdfJsonPresent()
- {
- String path = Platform.getOS().equals(Platform.OS_WIN32) ? EimConstants.EIM_WIN_PATH
- : EimConstants.EIM_POSIX_PATH;
- return new File(path).exists();
- }
-
public boolean isEspIdfSet()
{
return preferences.getBoolean(EimConstants.INSTALL_TOOLS_FLAG, false);
diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonChangeListener.java b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonChangeListener.java
index 37d12f78d..3a8810fac 100644
--- a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonChangeListener.java
+++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonChangeListener.java
@@ -13,5 +13,5 @@
*/
public interface EimJsonChangeListener
{
- void onJsonFileChanged(Path file);
+ void onJsonFileChanged(Path file, boolean paused);
}
diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonWatchService.java b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonWatchService.java
index 9d751b0db..38cdc1029 100644
--- a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonWatchService.java
+++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/watcher/EimJsonWatchService.java
@@ -6,6 +6,7 @@
import java.io.IOException;
import java.nio.file.*;
+import java.nio.file.attribute.FileAttribute;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -26,13 +27,19 @@ public class EimJsonWatchService extends Thread
private final Path watchDirectoryPath;
private final List eimJsonChangeListeners = new CopyOnWriteArrayList<>();
private volatile boolean running = true;
-
+ private volatile boolean paused = false;
+
+
private EimJsonWatchService() throws IOException
{
String directoryPathString = Platform.getOS().equals(Platform.OS_WIN32) ? EimConstants.EIM_WIN_DIR
: EimConstants.EIM_POSIX_DIR;
watchDirectoryPath = Paths.get(directoryPathString);
+ if (!Files.exists(watchDirectoryPath))
+ {
+ Files.createDirectories(watchDirectoryPath);
+ }
watchService = FileSystems.getDefault().newWatchService();
watchDirectoryPath.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
@@ -73,11 +80,43 @@ public void addEimJsonChangeListener(EimJsonChangeListener listener)
eimJsonChangeListeners.add(listener);
}
}
-
+
public void removeAllListeners()
{
eimJsonChangeListeners.clear();
}
+
+ public static void withPausedListeners(Runnable task)
+ {
+ EimJsonWatchService watchService = getInstance();
+ boolean wasPaused = watchService.paused;
+ watchService.pauseListeners();
+
+ try
+ {
+ task.run();
+ }
+ catch (Exception e)
+ {
+ Logger.log(e);
+ }
+ finally {
+ if (!wasPaused)
+ watchService.unpauseListeners();
+ }
+ }
+
+ public void pauseListeners()
+ {
+ Logger.log("Listeners are paused"); //$NON-NLS-1$
+ paused = true;
+ }
+
+ public void unpauseListeners()
+ {
+ Logger.log("Listeners are resumed"); //$NON-NLS-1$
+ paused = false;
+ }
@Override
public void run()
@@ -113,7 +152,7 @@ public void run()
Path fullPath = watchDirectoryPath.resolve(path);
for (EimJsonChangeListener listener : eimJsonChangeListeners)
{
- listener.onJsonFileChanged(fullPath);
+ listener.onJsonFileChanged(fullPath, paused);
}
}
}
diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/util/IDFUtil.java b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/util/IDFUtil.java
index 91d1208d8..4f00c4b90 100644
--- a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/util/IDFUtil.java
+++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/util/IDFUtil.java
@@ -191,6 +191,30 @@ public static String getIDFPythonEnvPath()
return idfEnvironmentVariables.getEnvValue(IDFEnvironmentVariables.PYTHON_EXE_PATH);
}
+
+ public static String getIDFPythonEnvPath(String idfPyEnvPath)
+ {
+ idfPyEnvPath = idfPyEnvPath.strip();
+ if (!StringUtil.isEmpty(idfPyEnvPath))
+ {
+
+ if (Platform.getOS().equals(Platform.OS_WIN32))
+ {
+ idfPyEnvPath = idfPyEnvPath + "/" + "Scripts"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ else
+ {
+ idfPyEnvPath = idfPyEnvPath + "/" + "bin"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ java.nio.file.Path commandPath = findCommand(IDFConstants.PYTHON_CMD, idfPyEnvPath);
+ if (commandPath != null)
+ {
+ return commandPath.toFile().getAbsolutePath();
+ }
+ }
+ return findCommandFromBuildEnvPath(IDFConstants.PYTHON_CMD);
+
+ }
public static boolean checkIfIdfSupportsSpaces()
{
diff --git a/bundles/com.espressif.idf.ui/plugin.xml b/bundles/com.espressif.idf.ui/plugin.xml
index 5365f564c..f0e744069 100644
--- a/bundles/com.espressif.idf.ui/plugin.xml
+++ b/bundles/com.espressif.idf.ui/plugin.xml
@@ -37,17 +37,6 @@
id="espidftoolsInstall"
style="push">
-
-
-
-
-
-
@@ -412,11 +401,6 @@
id="com.espressif.idf.ui.command.eclipeguide"
name="%command.name.9">
-
-
addJsonParseCommand()
public boolean isVersionAtLeast(String currentIDFVersion, String minimumIDFVersion)
{
+ if (currentIDFVersion.equalsIgnoreCase("master")) //$NON-NLS-1$
+ {
+ return true;
+ }
+
Version currentVersion = Version.parse(currentIDFVersion);
Version minVersion = Version.parse(minimumIDFVersion);
return currentVersion.compareTo(minVersion) >= 0;
diff --git a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/EimButtonLaunchListener.java b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/EimButtonLaunchListener.java
new file mode 100644
index 000000000..22201670b
--- /dev/null
+++ b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/EimButtonLaunchListener.java
@@ -0,0 +1,220 @@
+/*******************************************************************************
+ * Copyright 2025 Espressif Systems (Shanghai) PTE LTD. All rights reserved.
+ * Use is subject to license terms.
+ *******************************************************************************/
+package com.espressif.idf.ui.tools;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.console.MessageConsoleStream;
+import org.eclipse.ui.ide.IDE;
+import org.osgi.service.prefs.Preferences;
+
+import com.espressif.idf.core.IDFEnvironmentVariables;
+import com.espressif.idf.core.logging.Logger;
+import com.espressif.idf.core.tools.DownloadListener;
+import com.espressif.idf.core.tools.EimIdfConfiguratinParser;
+import com.espressif.idf.core.tools.EimLoader;
+import com.espressif.idf.core.tools.ToolInitializer;
+import com.espressif.idf.core.tools.vo.EimJson;
+import com.espressif.idf.ui.UIPlugin;
+import com.espressif.idf.ui.handlers.EclipseHandler;
+import com.espressif.idf.ui.tools.manager.ESPIDFManagerEditor;
+import com.espressif.idf.ui.tools.manager.EimEditorInput;
+import com.espressif.idf.ui.tools.manager.pages.ESPIDFMainTablePage;
+
+/**
+ * This is a class that other UI elements can also use to trigger
+ * a simulated event on any widget to launch or download the EIM.
+ * The primary usage is in {@link ESPIDFMainTablePage}
+ * @author Ali Azam Rana
+ *
+ */
+public class EimButtonLaunchListener extends SelectionAdapter
+{
+ private ESPIDFMainTablePage espidfMainTablePage;
+ private Display display;
+ private Preferences preferences;
+ private ToolInitializer toolInitializer;
+ private IDFEnvironmentVariables idfEnvironmentVariables = new IDFEnvironmentVariables();
+ private MessageConsoleStream standardConsoleStream;
+ private MessageConsoleStream errorConsoleStream;
+ private EimLoader eimLoader;
+
+ public EimButtonLaunchListener(ESPIDFMainTablePage espidfMainTablePage, Display display,
+ MessageConsoleStream standardConsoleStream, MessageConsoleStream errorConsoleStream)
+ {
+ this.espidfMainTablePage = espidfMainTablePage;
+ this.display = display;
+ this.standardConsoleStream = standardConsoleStream;
+ this.errorConsoleStream = errorConsoleStream;
+ preferences = org.eclipse.core.runtime.preferences.InstanceScope.INSTANCE.getNode(UIPlugin.PLUGIN_ID);
+ toolInitializer = new ToolInitializer(preferences);
+ eimLoader = new EimLoader(new EimDownlaodListener(), standardConsoleStream, errorConsoleStream, display);
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent selectionEvent)
+ {
+ if (!toolInitializer.isEimInstalled())
+ {
+ Job downloadJob = new Job("Download and Launch EIM")
+ {
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor)
+ {
+ eimLoader.downloadEim(monitor);
+ return Status.OK_STATUS;
+ }
+ };
+ downloadJob.setUser(true);
+ downloadJob.schedule();
+ }
+ else
+ {
+ try
+ {
+ Process process = eimLoader.launchEim(idfEnvironmentVariables.getEnvValue(IDFEnvironmentVariables.EIM_PATH));
+ eimLoader.waitForEimClosure(process, EimButtonLaunchListener.this::refreshAfterEimClose);
+ }
+ catch (IOException e)
+ {
+ Logger.log(e);
+ }
+ }
+ }
+
+ private void refreshAfterEimClose()
+ {
+ display.asyncExec(() -> {
+ try
+ {
+ launchEspIdfManager();
+ standardConsoleStream.write("Refreshing UI after EIM closed...\n");
+ espidfMainTablePage.refreshEditorUI();
+ espidfMainTablePage.setupInitialEspIdf();
+ }
+ catch (IOException | PartInitException e)
+ {
+ Logger.log(e);
+ }
+ });
+ }
+
+ private void launchEspIdfManager() throws PartInitException
+ {
+ Display.getDefault().asyncExec(() -> {
+ IWorkbenchWindow activeww = EclipseHandler.getActiveWorkbenchWindow();
+ if (activeww == null || activeww.getActivePage() == null)
+ {
+ Logger.log("Cannot open ESP-IDF Manager. No active workbench window or active page.");
+ return;
+ }
+
+ try
+ {
+ EimIdfConfiguratinParser eimIdfConfiguratinParser = new EimIdfConfiguratinParser();
+ EimJson eimJson = eimIdfConfiguratinParser.getEimJson(true);
+ IDE.openEditor(activeww.getActivePage(), new EimEditorInput(eimJson), ESPIDFManagerEditor.EDITOR_ID,
+ true);
+ }
+ catch (PartInitException | IOException e)
+ {
+ Logger.log("Failed to open ESP-IDF Manager Editor.");
+ Logger.log(e);
+ }
+ });
+
+ }
+
+ private class EimDownlaodListener implements DownloadListener
+ {
+ @Override
+ public void onProgress(int percent)
+ {
+ display.asyncExec(() -> {
+ try
+ {
+ int blocks = percent / 10;
+ String bar = "[" + "#".repeat(blocks) + " ".repeat(10 - blocks) + "] " + percent + "%";
+ standardConsoleStream.write("\r" + bar);
+ }
+ catch (IOException e)
+ {
+ Logger.log(e);
+ }
+ });
+ }
+
+ @Override
+ public void onCompleted(String filePath)
+ {
+ display.syncExec(() -> {
+ try
+ {
+ standardConsoleStream.write("\nEIM Downloaded to: " + filePath + "\nLaunching...\n");
+ }
+ catch (IOException e)
+ {
+ Logger.log(e);
+ }
+ });
+
+ String appToLaunch = filePath;
+
+ if (filePath.endsWith(".dmg"))
+ {
+ try
+ {
+ appToLaunch = eimLoader.installAndLaunchDmg(Paths.get(filePath));
+ }
+ catch (
+ IOException
+ | InterruptedException e)
+ {
+ Logger.log(e);
+ }
+ }
+
+ Process process;
+ try
+ {
+ idfEnvironmentVariables.addEnvVariable(IDFEnvironmentVariables.EIM_PATH, appToLaunch);
+ process = eimLoader.launchEim(appToLaunch);
+ eimLoader.waitForEimClosure(process, EimButtonLaunchListener.this::refreshAfterEimClose);
+ }
+ catch (IOException e)
+ {
+ Logger.log(e);
+ }
+ }
+
+ @Override
+ public void onError(String message, Exception e)
+ {
+ display.asyncExec(() -> {
+ try
+ {
+ errorConsoleStream.write("Download Failed: " + e.getMessage());
+ }
+ catch (IOException e1)
+ {
+ Logger.log(e1);
+ }
+ });
+ }
+
+ }
+
+}
diff --git a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/EspressifToolStartup.java b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/EspressifToolStartup.java
index 830e6922e..84d1fd6a0 100644
--- a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/EspressifToolStartup.java
+++ b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/EspressifToolStartup.java
@@ -5,31 +5,43 @@
package com.espressif.idf.ui.tools;
import java.io.IOException;
-import java.text.MessageFormat;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IStartup;
+import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.console.MessageConsoleStream;
import org.eclipse.ui.ide.IDE;
+import org.eclipse.ui.intro.IIntroManager;
import org.osgi.service.prefs.Preferences;
+import com.espressif.idf.core.IDFEnvironmentVariables;
import com.espressif.idf.core.build.Messages;
import com.espressif.idf.core.logging.Logger;
+import com.espressif.idf.core.tools.DownloadListener;
import com.espressif.idf.core.tools.EimConstants;
+import com.espressif.idf.core.tools.EimLoader;
import com.espressif.idf.core.tools.ToolInitializer;
import com.espressif.idf.core.tools.vo.EimJson;
import com.espressif.idf.core.tools.watcher.EimJsonStateChecker;
import com.espressif.idf.core.tools.watcher.EimJsonWatchService;
import com.espressif.idf.core.util.IDFUtil;
import com.espressif.idf.core.util.StringUtil;
+import com.espressif.idf.ui.IDFConsole;
import com.espressif.idf.ui.UIPlugin;
-import com.espressif.idf.ui.dialogs.MessageLinkDialog;
import com.espressif.idf.ui.handlers.EclipseHandler;
import com.espressif.idf.ui.tools.manager.ESPIDFManagerEditor;
import com.espressif.idf.ui.tools.manager.EimEditorInput;
@@ -46,41 +58,57 @@ public class EspressifToolStartup implements IStartup
private EimJsonUiChangeHandler eimJsonUiChangeHandler;
private ToolInitializer toolInitializer;
private Preferences preferences;
+ private EimJson eimJson;
+ private EimLoader eimLoader;
+ private MessageConsoleStream standardConsoleStream;
+ private MessageConsoleStream errorConsoleStream;
+ private IDFEnvironmentVariables idfEnvironmentVariables;
@Override
public void earlyStartup()
{
- Preferences preferences = org.eclipse.core.runtime.preferences.InstanceScope.INSTANCE
+ preferences = org.eclipse.core.runtime.preferences.InstanceScope.INSTANCE
.getNode(UIPlugin.PLUGIN_ID);
- ToolInitializer toolInitializer = new ToolInitializer(preferences);
+ toolInitializer = new ToolInitializer(preferences);
+ standardConsoleStream = getConsoleStream(false);
+ errorConsoleStream = getConsoleStream(true);
+ idfEnvironmentVariables = new IDFEnvironmentVariables();
+ eimLoader = new EimLoader(new StartupClassDownloadEimDownloadListener(),
+ standardConsoleStream, errorConsoleStream, Display.getDefault());
EimJsonStateChecker stateChecker = new EimJsonStateChecker(preferences);
eimJsonUiChangeHandler = new EimJsonUiChangeHandler(preferences);
stateChecker.updateLastSeenTimestamp();
EimJsonWatchService.getInstance().addEimJsonChangeListener(eimJsonUiChangeHandler);
- if (!toolInitializer.isEimInstalled())
+ if (!toolInitializer.isEimInstalled() && !toolInitializer.isEimIdfJsonPresent())
{
Logger.log("EIM not installed");
notifyMissingTools();
return;
}
- if (toolInitializer.isOldEspIdfConfigPresent()
- && !toolInitializer.isOldConfigExported())
+ eimJson = toolInitializer.loadEimJson();
+
+ if (toolInitializer.isOldEspIdfConfigPresent() && !toolInitializer.isOldConfigExported())
{
Logger.log("Old configuration found and not converted");
- handleOldConfigExport();
+ boolean isEimInApplications = checkIfEimPathMacOsIsInApplications();
+ if (!isEimInApplications)
+ {
+ promptUserToMoveEimToApplications();
+ }
+
+ EimJsonWatchService.withPausedListeners(()-> handleOldConfigExport());
}
-
- EimJson eimJson = toolInitializer.loadEimJson();
- if (eimJson == null)
+ else if (toolInitializer.isEimIdfJsonPresent() && !toolInitializer.isEspIdfSet())
{
- return;
+ promptUserToOpenToolManager(eimJson);
}
- if (!toolInitializer.isEspIdfSet())
+ // Set EimPath on every startup to ensure proper path in configurations
+ if (eimJson != null)
{
- promptUserToOpenToolManager(eimJson);
+ idfEnvironmentVariables.addEnvVariable(IDFEnvironmentVariables.EIM_PATH, eimJson.getEimPath());
}
if (stateChecker.wasModifiedSinceLastRun())
@@ -89,6 +117,29 @@ public void earlyStartup()
}
}
+ private boolean checkIfEimPathMacOsIsInApplications()
+ {
+ if (!Platform.getOS().equals(Platform.OS_MACOSX))
+ return true;
+
+ String eimPath = idfEnvironmentVariables.getEnvValue(IDFEnvironmentVariables.EIM_PATH);
+ if (!StringUtil.isEmpty(eimPath))
+ {
+ if (Files.exists(Paths.get(eimPath)))
+ {
+ boolean isInApplications = eimPath.startsWith("/Applications/") ||
+ eimPath.startsWith(System.getProperty("user.home") + "/Applications/");
+ if (!isInApplications)
+ {
+ Logger.log("EIM_PATH not in applications: " + eimPath);
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
private void handleOldConfigExport()
{
final int[] response = new int[] { -1 };
@@ -105,7 +156,7 @@ private void handleOldConfigExport()
{
try
{
- IStatus status = toolInitializer.exportOldConfig();
+ IStatus status = toolInitializer.exportOldConfig(eimJson != null ? eimJson.getEimPath() : StringUtil.EMPTY);
Logger.log("Tools Conversion Process Message: ");
Logger.log(status.getMessage());
if (status.getSeverity() != IStatus.ERROR)
@@ -145,13 +196,66 @@ private void showEimJsonStateChangeNotification()
private void notifyMissingTools()
{
- Display.getDefault().asyncExec(() -> {
- MessageLinkDialog.openWarning(Display.getDefault().getActiveShell(),
+ boolean [] userAgreed = new boolean[1];
+ Display.getDefault().syncExec(() -> {
+ userAgreed[0] = MessageDialog.openQuestion(Display.getDefault().getActiveShell(),
Messages.ToolsInitializationEimMissingMsgBoxTitle,
- MessageFormat.format(Messages.ToolsInitializationEimMissingMsgBoxMessage, EimConstants.EIM_URL));
+ Messages.ToolsInitializationEimMissingMsgBoxMessage);
+ });
+
+ if (userAgreed[0])
+ {
+ // Download Launch EIM
+ downloadAndLaunchEim();
+ }
+ else
+ {
+ Logger.log("User selected No to download EIM");
+ }
+ }
+
+ private void downloadAndLaunchEim()
+ {
+ closeWelcomePage();
+ Job downloadJob = new Job("Download and Launch EIM")
+ {
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor)
+ {
+ eimLoader.downloadEim(monitor);
+ return Status.OK_STATUS;
+ }
+ };
+ downloadJob.setUser(true);
+ downloadJob.schedule();
+ }
+
+ private void closeWelcomePage()
+ {
+ Display.getDefault().asyncExec(() -> {
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ if (workbench != null)
+ {
+ IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
+ if (window != null)
+ {
+ IIntroManager introManager = workbench.getIntroManager();
+ if (introManager.getIntro() != null)
+ {
+ introManager.closeIntro(introManager.getIntro());
+ }
+ }
+ }
});
}
+ private MessageConsoleStream getConsoleStream(boolean errorStream)
+ {
+ IDFConsole idfConsole = new IDFConsole();
+ return idfConsole.getConsoleStream("EIM Launch Console", null, errorStream);
+ }
+
private void promptUserToOpenToolManager(EimJson eimJson)
{
Display.getDefault().syncExec(() -> {
@@ -175,20 +279,110 @@ private void promptUserToOpenToolManager(EimJson eimJson)
}
});
}
+
+ private void promptUserToMoveEimToApplications()
+ {
+ Display.getDefault().asyncExec(() -> {
+ MessageDialog.openInformation(
+ Display.getDefault().getActiveShell(),
+ Messages.EIMNotInApplicationsTitle, Messages.EIMNotInApplicationsMessage);
+ });
+ }
private void openEspIdfManager(EimJson eimJson)
{
- IWorkbenchWindow window = EclipseHandler.getActiveWorkbenchWindow();
- IDFUtil.closeWelcomePage(window);
- try
+ Display.getDefault().asyncExec(() -> {
+ IWorkbenchWindow window = EclipseHandler.getActiveWorkbenchWindow();
+ try
+ {
+ EimEditorInput input = new EimEditorInput(eimJson);
+ input.setFirstStartup(true);
+ IDE.openEditor(window.getActivePage(), input, ESPIDFManagerEditor.EDITOR_ID);
+ IDFUtil.closeWelcomePage(window);
+ }
+ catch (PartInitException e)
+ {
+ Logger.log(e);
+ }
+ });
+ }
+
+ private class StartupClassDownloadEimDownloadListener implements DownloadListener
+ {
+
+ @Override
+ public void onProgress(int percent)
+ {
+ Display.getDefault().asyncExec(() -> {
+ try
+ {
+ int blocks = percent / 10;
+ String bar = "[" + "#".repeat(blocks) + " ".repeat(10 - blocks) + "] " + percent + "%";
+ standardConsoleStream.write("\r" + bar);
+ }
+ catch (IOException e)
+ {
+ Logger.log(e);
+ }
+ });
+
+ }
+
+ @Override
+ public void onCompleted(String filePath)
{
- EimEditorInput input = new EimEditorInput(eimJson);
- input.setFirstStartup(true);
- IDE.openEditor(window.getActivePage(), input, ESPIDFManagerEditor.EDITOR_ID);
+ Display.getDefault().syncExec(() -> {
+ try
+ {
+ standardConsoleStream.write("\nEIM Downloaded to: " + filePath + "\nLaunching...\n");
+ }
+ catch (IOException e)
+ {
+ Logger.log(e);
+ }
+ });
+
+ Process process = null;
+ String appToLaunch = filePath;
+ try
+ {
+ if (filePath.endsWith(".dmg"))
+ {
+ appToLaunch = eimLoader.installAndLaunchDmg(Paths.get(filePath));
+ }
+
+ idfEnvironmentVariables.addEnvVariable(IDFEnvironmentVariables.EIM_PATH, appToLaunch);
+ process = eimLoader.launchEim(appToLaunch);
+ }
+ catch (IOException | InterruptedException e)
+ {
+ Logger.log(e);
+ }
+
+ eimLoader.waitForEimClosure(process, () -> {
+ if (toolInitializer.isOldEspIdfConfigPresent() && !toolInitializer.isOldConfigExported())
+ {
+ Logger.log("Old configuration found and not converted");
+ handleOldConfigExport();
+ }
+ eimJson = toolInitializer.loadEimJson();
+ openEspIdfManager(eimJson);
+ });
}
- catch (PartInitException e)
+
+ @Override
+ public void onError(String message, Exception e)
{
- Logger.log(e);
+ Display.getDefault().asyncExec(() -> {
+ try
+ {
+ errorConsoleStream.write("Download Failed: " + e.getMessage());
+ }
+ catch (IOException e1)
+ {
+ Logger.log(e1);
+ }
+ });
}
}
}
diff --git a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/ManageEspIdfVersionsHandler.java b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/ManageEspIdfVersionsHandler.java
index dc1dff888..abba3d179 100644
--- a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/ManageEspIdfVersionsHandler.java
+++ b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/ManageEspIdfVersionsHandler.java
@@ -1,8 +1,6 @@
package com.espressif.idf.ui.tools;
import java.io.IOException;
-import java.text.MessageFormat;
-import java.util.ArrayList;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
@@ -11,14 +9,10 @@
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.ide.IDE;
-import com.espressif.idf.core.build.Messages;
import com.espressif.idf.core.logging.Logger;
-import com.espressif.idf.core.tools.EimConstants;
import com.espressif.idf.core.tools.EimIdfConfiguratinParser;
import com.espressif.idf.core.tools.vo.EimJson;
-import com.espressif.idf.core.tools.vo.IdfInstalled;
import com.espressif.idf.core.util.IDFUtil;
-import com.espressif.idf.ui.dialogs.MessageLinkDialog;
import com.espressif.idf.ui.handlers.EclipseHandler;
import com.espressif.idf.ui.tools.manager.ESPIDFManagerEditor;
import com.espressif.idf.ui.tools.manager.EimEditorInput;
@@ -65,22 +59,7 @@ public void run()
{
Logger.log(e);
}
-
- if (eimJson.getIdfInstalled().isEmpty())
- {
- notifyIDFNotFound();
- }
}
});
}
-
- private void notifyIDFNotFound()
- {
- Display.getDefault().asyncExec(() -> {
- MessageLinkDialog.openWarning(Display.getDefault().getActiveShell(),
- Messages.ToolsInitializationEimMissingMsgBoxTitle,
- MessageFormat.format(Messages.ToolsInitializationEimMissingMsgBoxMessage, EimConstants.EIM_URL));
-
- });
- }
}
diff --git a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/Messages.java b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/Messages.java
index a6659697c..15cc07a12 100644
--- a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/Messages.java
+++ b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/Messages.java
@@ -23,6 +23,8 @@ public class Messages extends NLS
public static String EspIdfManagerNameCol;
public static String EspIdfManagerReloadBtnToolTip;
public static String IDFGuideLinkLabel_Text;
+ public static String EIMButtonDownloadText;
+ public static String EIMButtonLaunchText;
public static String IDFToolsHandler_ToolsManagerConsole;
diff --git a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java
index 3e27a4c43..0ae883786 100644
--- a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java
+++ b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/manager/pages/ESPIDFMainTablePage.java
@@ -40,11 +40,13 @@
import com.espressif.idf.core.tools.EimConstants;
import com.espressif.idf.core.tools.EimIdfConfiguratinParser;
import com.espressif.idf.core.tools.SetupToolsInIde;
+import com.espressif.idf.core.tools.ToolInitializer;
import com.espressif.idf.core.tools.util.ToolsUtility;
import com.espressif.idf.core.tools.vo.EimJson;
import com.espressif.idf.core.tools.vo.IdfInstalled;
import com.espressif.idf.ui.IDFConsole;
import com.espressif.idf.ui.UIPlugin;
+import com.espressif.idf.ui.tools.EimButtonLaunchListener;
import com.espressif.idf.ui.tools.Messages;
import com.espressif.idf.ui.tools.SetupToolsJobListener;
@@ -63,12 +65,14 @@ public class ESPIDFMainTablePage
private TableViewerColumn activateColumn;
private TableViewerColumn removeColumn;
private TableViewerColumn nameColumn;
+ private Button eimLaunchBtn;
private TableColumnLayout tableColumnLayout;
private Composite tableComposite;
private List idfInstalledList;
private static EimJson eimJson;
private EimIdfConfiguratinParser eimIdfConfiguratinParser;
+ private ToolInitializer toolInitializer;
private static final String RELOAD_ICON = "icons/tools/reload.png"; //$NON-NLS-1$
private static final String IDF_TOOL_SET_BTN_KEY = "IDFToolSet"; //$NON-NLS-1$
@@ -78,6 +82,8 @@ public class ESPIDFMainTablePage
private ESPIDFMainTablePage()
{
eimIdfConfiguratinParser = new EimIdfConfiguratinParser();
+ toolInitializer = new ToolInitializer(org.eclipse.core.runtime.preferences.InstanceScope.INSTANCE
+ .getNode(UIPlugin.PLUGIN_ID));
}
public static ESPIDFMainTablePage getInstance(EimJson eimJson)
@@ -98,16 +104,16 @@ public Composite createPage(Composite composite)
final int numColumns = 2;
GridLayout gridLayout = new GridLayout(numColumns, false);
container.setLayout(gridLayout);
- createGuideLink(container);
+ createButtonAndGuideLink(container);
createIdfTable(container);
return container;
}
- private void createGuideLink(Composite composite)
+ private void createButtonAndGuideLink(Composite composite)
{
Link guideLink = new Link(composite, SWT.WRAP);
guideLink.setText(Messages.IDFGuideLinkLabel_Text);
- guideLink.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 2, 1));
+ guideLink.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 1, 1));
guideLink.addListener(SWT.Selection, e -> {
try
{
@@ -119,6 +125,10 @@ private void createGuideLink(Composite composite)
Logger.log(ex);
}
});
+
+ eimLaunchBtn = new Button(composite, SWT.PUSH);
+ eimLaunchBtn.setText(!toolInitializer.isEimInstalled() ? Messages.EIMButtonDownloadText : Messages.EIMButtonLaunchText);
+ eimLaunchBtn.addSelectionListener(new EimButtonLaunchListener(espidfMainTablePage, Display.getDefault(), getConsoleStream(false), getConsoleStream(true)));
}
public void setupInitialEspIdf()
@@ -184,6 +194,7 @@ public void refreshEditorUI()
tableViewer.setInput(idfInstalledList);
tableViewer.getControl().requestLayout();
tableViewer.refresh();
+ eimLaunchBtn.setText(!toolInitializer.isEimInstalled() ? Messages.EIMButtonDownloadText : Messages.EIMButtonLaunchText);
container.redraw();
}
@@ -192,7 +203,7 @@ private Composite createIdfTable(Composite parent)
Group idfToolsGroup = new Group(parent, SWT.SHADOW_ETCHED_IN);
idfToolsGroup.setText("IDF Tools");
idfToolsGroup.setLayout(new GridLayout(2, false));
- idfToolsGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ idfToolsGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
// Composite for the TableViewer, with TableColumnLayout
tableComposite = new Composite(idfToolsGroup, SWT.NONE);
diff --git a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/messages.properties b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/messages.properties
index 025d33362..1a3663fb5 100644
--- a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/messages.properties
+++ b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/messages.properties
@@ -6,8 +6,10 @@ EspIdfManagerNameCol=Name
EspIdfManagerReloadBtnToolTip=Reload from disk
IDFToolsHandler_ToolsManagerConsole=Espressif IDF Tools Console
IDFGuideLinkLabel_Text=Select the version of ESP-IDF you want to use. Click the radio button in Active column next to the version you want. For help in choosing the correct version, visit ESP-IDF Version Guide.
+EIMButtonDownloadText=Download and Launch EIM
+EIMButtonLaunchText=Launch EIM
EimJsonChangedMsgTitle=ESP-IDF Installation Changed
EimJsonChangedMsgDetail=It seems that the ESP-IDF tools have been modified. If you just installed ESP-IDF we recommend refereshing via ESP-IDF Manager . Would you like to proceed with the update?
EimJsonStateChangedMsgDetail=It looks like the ESP-IDF installation was modified since last start. The Espressif IDE cannot guarantee the consistency. Kindly refresh the installation via ESP-IDF Manager.
- MsgYes=Yes
- MsgNo=No
\ No newline at end of file
+MsgYes=Yes
+MsgNo=No
diff --git a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/watcher/EimJsonUiChangeHandler.java b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/watcher/EimJsonUiChangeHandler.java
index 6bf8c4734..4517e2a0f 100644
--- a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/watcher/EimJsonUiChangeHandler.java
+++ b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/tools/watcher/EimJsonUiChangeHandler.java
@@ -47,8 +47,13 @@ public EimJsonUiChangeHandler(Preferences preferences)
}
@Override
- public void onJsonFileChanged(Path file)
+ public void onJsonFileChanged(Path file, boolean paused)
{
+ if (paused)
+ {
+ Logger.log("Listener is paused");
+ return;
+ }
int response = displayMessageToUser();
handleUserResponse(response);
}
@@ -87,12 +92,21 @@ public void handleUserResponse(int response)
else
{
// multiple entries in json so launch manager for user to handle this
- launchEspIdfManager();
+ Display.getDefault().asyncExec(() -> {
+ try
+ {
+ launchEspIdfManager();
+ }
+ catch (PartInitException e)
+ {
+ Logger.log(e);
+ }
+ ESPIDFMainTablePage.getInstance(eimJson).refreshEditorUI();
+ });
}
}
catch (
- IOException
- | PartInitException e)
+ IOException e)
{
Logger.log(e);
}