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()) {