diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java
index d6ffd5d58..6376be835 100644
--- a/app/src/processing/app/Base.java
+++ b/app/src/processing/app/Base.java
@@ -463,6 +463,9 @@ public class Base {
new Thread(new BuiltInCoreIsNewerCheck(this)).start();
+ // Check for boards which need an additional core
+ new Thread(new NewBoardListener(this)).start();
+
// Check for updates
if (PreferencesData.getBoolean("update.check")) {
new UpdateCheck(this);
diff --git a/app/src/processing/app/NewBoardListener.java b/app/src/processing/app/NewBoardListener.java
new file mode 100644
index 000000000..c33340cb7
--- /dev/null
+++ b/app/src/processing/app/NewBoardListener.java
@@ -0,0 +1,77 @@
+package processing.app;
+
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowFocusListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.SwingUtilities;
+
+import cc.arduino.UpdatableBoardsLibsFakeURLsHandler;
+import cc.arduino.view.NotificationPopup;
+import processing.app.Base;
+
+public class NewBoardListener implements PropertyChangeListener, Runnable {
+ private Base base;
+ private Editor ed;
+
+ public NewBoardListener(Base base) {
+ this.base = base;
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent event) {
+ checkForNewBoardAttached();
+ }
+
+ @Override
+ public void run() {
+ while (base.getActiveEditor() == null) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ BaseNoGui.addPropertyChangeListener(this);
+ checkForNewBoardAttached();
+ }
+
+ public void checkForNewBoardAttached() {
+ String newBoardManagerLink = BaseNoGui.getBoardManagerLink();
+ if (newBoardManagerLink.isEmpty()) {
+ return;
+ }
+
+ SwingUtilities.invokeLater(() -> {
+
+ ed = base.getActiveEditor();
+ NotificationPopup notificationPopup = new NotificationPopup(ed,
+ new UpdatableBoardsLibsFakeURLsHandler(base),
+ newBoardManagerLink, false);
+ if (ed.isFocused()) {
+ notificationPopup.begin();
+ return;
+ }
+
+ // If the IDE is not focused wait until it is focused again to
+ // display the notification, this avoids the annoying side effect
+ // to "steal" the focus from another application.
+ WindowFocusListener wfl = new WindowFocusListener() {
+ @Override
+ public void windowLostFocus(WindowEvent evt) {
+ }
+
+ @Override
+ public void windowGainedFocus(WindowEvent evt) {
+ notificationPopup.begin();
+ for (Editor e : base.getEditors())
+ e.removeWindowFocusListener(this);
+ }
+ };
+
+ for (Editor e : base.getEditors())
+ e.addWindowFocusListener(wfl);
+ });
+ }
+}
diff --git a/arduino-core/src/cc/arduino/packages/discoverers/serial/SerialBoardsLister.java b/arduino-core/src/cc/arduino/packages/discoverers/serial/SerialBoardsLister.java
index dac28e087..6b5873892 100644
--- a/arduino-core/src/cc/arduino/packages/discoverers/serial/SerialBoardsLister.java
+++ b/arduino-core/src/cc/arduino/packages/discoverers/serial/SerialBoardsLister.java
@@ -159,6 +159,8 @@ public class SerialBoardsLister extends TimerTask {
} else {
if (parts[1] != "0000") {
boardPort.setVIDPID(parts[1], parts[2]);
+ // ask Cloud API to match the board with known VID/PID pair
+ platform.getBoardWithMatchingVidPidFromCloud(parts[1], parts[2]);
} else {
boardPort.setVIDPID("0000", "0000");
boardPort.setISerial("");
diff --git a/arduino-core/src/processing/app/BaseNoGui.java b/arduino-core/src/processing/app/BaseNoGui.java
index 475bae13e..2c66dc468 100644
--- a/arduino-core/src/processing/app/BaseNoGui.java
+++ b/arduino-core/src/processing/app/BaseNoGui.java
@@ -25,6 +25,8 @@ import processing.app.legacy.PApplet;
import processing.app.packages.LibraryList;
import processing.app.packages.UserLibrary;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
@@ -87,6 +89,8 @@ public class BaseNoGui {
public static ContributionsIndexer indexer;
public static LibrariesIndexer librariesIndexer;
+ private static String boardManagerLink = "";
+
// Returns a File object for the given pathname. If the pathname
// is not absolute, it is interpreted relative to the current
// directory when starting the IDE (which is not the same as the
@@ -398,6 +402,21 @@ public class BaseNoGui {
return libs.filterLibrariesInSubfolder(getSketchbookFolder());
}
+ static public String getBoardManagerLink() {
+ return boardManagerLink;
+ }
+
+ protected static PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(BaseNoGui.class);;
+
+ public static void setBoardManagerLink(String temp) {
+ boardManagerLink = temp;
+ propertyChangeSupport.firePropertyChange("boardManagerLink", "", boardManagerLink);
+ }
+
+ public static void addPropertyChangeListener(PropertyChangeListener listener) {
+ propertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
/**
* Given a folder, return a list of the header files in that folder (but not
* the header files in its sub-folders, as those should be included from
diff --git a/arduino-core/src/processing/app/Platform.java b/arduino-core/src/processing/app/Platform.java
index 3c1372f77..9cf423f0e 100644
--- a/arduino-core/src/processing/app/Platform.java
+++ b/arduino-core/src/processing/app/Platform.java
@@ -38,6 +38,13 @@ import java.util.Map;
import java.util.ArrayList;
import java.util.Arrays;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.HttpURLConnection;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import java.io.InputStream;
+
import static processing.app.I18n.tr;
@@ -181,6 +188,46 @@ public class Platform {
return list;
}
+ public static class BoardCloudAPIid {
+ public BoardCloudAPIid() { }
+ private String name;
+ private String architecture;
+ private String id;
+ public String getName() { return name; }
+ public String getArchitecture() { return architecture; }
+ public String getId() { return id; }
+ public void setName(String tmp) { name = tmp; }
+ public void setArchitecture(String tmp) { architecture = tmp; }
+ public void setId(String tmp) { id = tmp; }
+ }
+
+ public synchronized void getBoardWithMatchingVidPidFromCloud(String vid, String pid) {
+ // this method is less useful in Windows < WIN10 since you need drivers to be already installed
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ URLConnection con;
+ try {
+ URL jsonUrl = new URL("http", "api-builder.arduino.cc", 80, "/builder/boards/0x"+vid+"/0x"+pid);
+ URLConnection connection = jsonUrl.openConnection();
+ connection.connect();
+ HttpURLConnection httpConnection = (HttpURLConnection) connection;
+ int code = httpConnection.getResponseCode();
+ if (code == 404) {
+ return;
+ }
+ InputStream is = httpConnection.getInputStream();
+ BoardCloudAPIid board = mapper.readValue(is, BoardCloudAPIid.class);
+ // Launch a popup with a link to boardmanager#board.getName()
+ // replace spaces with &
+ String boardNameReplaced = board.getName().replaceAll(" ", "&");
+ String message = I18n.format(tr("{0}Install{1} the package to use your {2}"), "", "", board.getName());
+ BaseNoGui.setBoardManagerLink(message);
+ } catch (Exception e) {
+ // No connection no problem, fail silently
+ //e.printStackTrace();
+ }
+ }
+
public synchronized Map resolveDeviceByVendorIdProductId(String serial, Map packages) {
String vid_pid_iSerial = resolveDeviceAttachedToNative(serial);
for (TargetPackage targetPackage : packages.values()) {