diff --git a/Source/AutoUpdater.cpp b/Source/AutoUpdater.cpp
new file mode 100644
index 000000000..1fb975aff
--- /dev/null
+++ b/Source/AutoUpdater.cpp
@@ -0,0 +1,527 @@
+/*
+ ------------------------------------------------------------------
+
+ This file is part of the Open Ephys GUI
+ Copyright (C) 2023 Open Ephys
+
+ ------------------------------------------------------------------
+ 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 .
+
+ Reference : https://github.com/juce-framework/JUCE/blob/6.0.8/extras/Projucer/Source/Application/jucer_AutoUpdater.cpp
+
+*/
+
+#include "AutoUpdater.h"
+#include "CoreServices.h"
+#include "MainWindow.h"
+#ifdef _WIN32
+#include
+#include
+#endif
+
+//==============================================================================
+LatestVersionCheckerAndUpdater::LatestVersionCheckerAndUpdater()
+ : Thread ("VersionChecker")
+ , mainWindow(nullptr)
+{
+}
+
+LatestVersionCheckerAndUpdater::~LatestVersionCheckerAndUpdater()
+{
+ stopThread (6000);
+ clearSingletonInstance();
+}
+
+void LatestVersionCheckerAndUpdater::checkForNewVersion (bool background, MainWindow* mw)
+{
+ if (! isThreadRunning())
+ {
+ backgroundCheck = background;
+ mainWindow = mw;
+ startThread (3);
+ }
+}
+
+//==============================================================================
+void LatestVersionCheckerAndUpdater::run()
+{
+ LOGC("Checking for a new version....");
+ URL latestVersionURL ("https://api.github.com/repos/open-ephys/plugin-GUI/releases/latest");
+
+ std::unique_ptr inStream (latestVersionURL.createInputStream (URL::InputStreamOptions (URL::ParameterHandling::inAddress)
+ .withConnectionTimeoutMs (5000)));
+ const String commErr = "Failed to communicate with the Open Ephys update server.\n"
+ "Please try again in a few minutes.\n\n"
+ "If this problem persists you can download the latest version of Open Ephys GUI from open-ephys.org/gui";
+
+ if (inStream == nullptr)
+ {
+ if (! backgroundCheck)
+ AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
+ "Update Server Communication Error",
+ commErr);
+
+ return;
+ }
+
+ auto content = inStream->readEntireStreamAsString();
+ auto latestReleaseDetails = JSON::parse (content);
+
+ auto* json = latestReleaseDetails.getDynamicObject();
+
+ if (json == nullptr)
+ {
+ if (! backgroundCheck)
+ AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
+ "Update Server Communication Error",
+ commErr);
+
+ return;
+ }
+
+ auto versionString = json->getProperty ("tag_name").toString();
+
+ if (versionString.isEmpty())
+ return;
+
+ auto* assets = json->getProperty ("assets").getArray();
+
+ if (assets == nullptr)
+ return;
+
+ auto releaseNotes = json->getProperty ("body").toString();
+
+ std::vector parsedAssets;
+
+ for (auto& asset : *assets)
+ {
+ if (auto* assetJson = asset.getDynamicObject())
+ {
+ parsedAssets.push_back ({ assetJson->getProperty ("name").toString(),
+ assetJson->getProperty ("url").toString(),
+ (int)assetJson->getProperty("size")});
+ jassert (parsedAssets.back().name.isNotEmpty());
+ jassert (parsedAssets.back().url.isNotEmpty());
+ jassert (parsedAssets.back().size != 0);
+
+ }
+ else
+ {
+ jassertfalse;
+ }
+ }
+
+ String latestVer = versionString.substring(1);
+
+
+ if (latestVer.compareNatural(CoreServices::getGUIVersion()) <= 0)
+ {
+ if (! backgroundCheck)
+ AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
+ "No New Version Available",
+ "Your GUI version is up to date.");
+ return;
+ }
+
+ auto osString = []
+ {
+ #if JUCE_MAC
+ return "mac";
+ #elif JUCE_WINDOWS
+ return "windows";
+ #elif JUCE_LINUX
+ return "linux";
+ #else
+ jassertfalse;
+ return "Unknown";
+ #endif
+ }();
+
+ String requiredFilename ("open-ephys-" + versionString + "-" + osString + ".zip");
+
+#if JUCE_WINDOWS
+ File exeDir = File::getSpecialLocation(File::SpecialLocationType::currentExecutableFile).getParentDirectory();
+ if(exeDir.findChildFiles(File::findFiles, false, "unins*").size() > 0)
+ {
+ requiredFilename = "Install-Open-Ephys-GUI-" + versionString + ".exe";
+ }
+#elif JUCE_LINUX
+ File exeDir = File::getSpecialLocation(File::SpecialLocationType::currentExecutableFile).getParentDirectory();
+ if(exeDir.getFullPathName().contains("/usr/local/bin"))
+ {
+ requiredFilename = "open-ephys-gui-" + versionString + ".deb";
+ }
+#elif JUCE_MAC
+ File exeDir = File::getSpecialLocation(File::SpecialLocationType::currentApplicationFile).getParentDirectory();
+ File globalAppDir = File::getSpecialLocation(File::SpecialLocationType::globalApplicationsDirectory);
+ if(exeDir.getFullPathName().contains(globalAppDir.getFullPathName()))
+ {
+ requiredFilename = "Open_Ephys_GUI_" + versionString + ".dmg";
+ }
+#endif
+
+ for (auto& asset : parsedAssets)
+ {
+ if (asset.name == requiredFilename)
+ {
+
+ MessageManager::callAsync ([this, versionString, releaseNotes, asset]
+ {
+ askUserAboutNewVersion (versionString, releaseNotes, asset);
+ });
+
+ return;
+ }
+ }
+
+ if (! backgroundCheck)
+ AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
+ "Failed to find any new downloads",
+ "Please try again in a few minutes.");
+}
+
+//==============================================================================
+class UpdateDialog : public Component
+{
+public:
+ UpdateDialog (const String& newVersion, const String& releaseNotes, bool automaticVerCheck)
+ {
+ titleLabel.setText ("Open Ephys GUI version " + newVersion, dontSendNotification);
+ titleLabel.setFont (Font("Fira Sans", "SemiBold", 18.0f));
+ titleLabel.setJustificationType (Justification::centred);
+ addAndMakeVisible (titleLabel);
+
+ contentLabel.setText ("A new version of Open Ephys GUI is available - would you like to download it?", dontSendNotification);
+ contentLabel.setFont (Font("Fira Sans", "Regular", 16.0f));
+ contentLabel.setJustificationType (Justification::topLeft);
+ contentLabel.setMinimumHorizontalScale(1.0);
+ addAndMakeVisible (contentLabel);
+
+ releaseNotesEditor.setMultiLine (true);
+ releaseNotesEditor.setReadOnly (true);
+ releaseNotesEditor.setText (releaseNotes);
+ addAndMakeVisible (releaseNotesEditor);
+
+ addAndMakeVisible (downloadButton);
+ downloadButton.onClick = [this] { exitModalStateWithResult (1); };
+
+ addAndMakeVisible (cancelButton);
+ cancelButton.onClick = [this]
+ {
+ if(dontAskAgainButton.getToggleState())
+ exitModalStateWithResult (-1);
+ else
+ exitModalStateWithResult(0);
+ };
+
+ dontAskAgainButton.setToggleState (!automaticVerCheck, dontSendNotification);
+ addAndMakeVisible (dontAskAgainButton);
+
+#if JUCE_MAC
+ File iconDir = File::getSpecialLocation(File::currentApplicationFile).getChildFile("Contents/Resources");
+#else
+ File iconDir = File::getSpecialLocation(File::currentApplicationFile).getParentDirectory();
+#endif
+ juceIcon = Drawable::createFromImageFile(iconDir.getChildFile("icon-small.png"));
+ lookAndFeelChanged();
+
+ setSize (640, 480);
+ }
+
+ void resized() override
+ {
+ auto b = getLocalBounds().reduced (10);
+
+ auto topSlice = b.removeFromTop (juceIconBounds.getHeight())
+ .withTrimmedLeft (juceIconBounds.getWidth());
+
+ titleLabel.setBounds (topSlice.removeFromTop (25));
+ topSlice.removeFromTop (5);
+ contentLabel.setBounds (topSlice.removeFromTop (25));
+
+ auto buttonBounds = b.removeFromBottom (60);
+ buttonBounds.removeFromBottom (25);
+ downloadButton.setBounds (buttonBounds.removeFromLeft (buttonBounds.getWidth() / 2).reduced (20, 0));
+ cancelButton.setBounds (buttonBounds.reduced (20, 0));
+ dontAskAgainButton.setBounds (cancelButton.getBounds().withY (cancelButton.getBottom() + 5).withHeight (20));
+
+ releaseNotesEditor.setBounds (b.reduced (0, 10));
+ }
+
+ void paint (Graphics& g) override
+ {
+ g.fillAll (Colours::lightgrey);
+
+ if (juceIcon != nullptr)
+ juceIcon->drawWithin (g, juceIconBounds.toFloat(),
+ RectanglePlacement::stretchToFit, 1.0f);
+ }
+
+ static std::unique_ptr launchDialog (const String& newVersionString,
+ const String& releaseNotes,
+ bool automaticVerCheck)
+ {
+ DialogWindow::LaunchOptions options;
+
+ options.dialogTitle = "Download Open Ephys GUI version " + newVersionString + "?";
+ options.resizable = false;
+
+ auto* content = new UpdateDialog (newVersionString, releaseNotes, automaticVerCheck);
+ options.content.set (content, true);
+
+ std::unique_ptr dialog (options.create());
+
+ content->setParentWindow (dialog.get());
+ dialog->enterModalState (true, nullptr, true);
+
+ return dialog;
+ }
+
+private:
+ void lookAndFeelChanged() override
+ {
+ cancelButton.setColour (TextButton::buttonColourId, Colours::crimson);
+ releaseNotesEditor.applyFontToAllText (Font("Fira Sans", "Regular", 16.0f));
+ }
+
+ void setParentWindow (DialogWindow* parent)
+ {
+ parentWindow = parent;
+ }
+
+ void exitModalStateWithResult (int result)
+ {
+ if (parentWindow != nullptr)
+ parentWindow->exitModalState (result);
+ }
+
+ Label titleLabel, contentLabel, releaseNotesLabel;
+ TextEditor releaseNotesEditor;
+ TextButton downloadButton { "Download" }, cancelButton { "Cancel" };
+ ToggleButton dontAskAgainButton { "Don't ask again" };
+ std::unique_ptr juceIcon;
+ juce::Rectangle juceIconBounds { 10, 10, 64, 64 };
+
+ DialogWindow* parentWindow = nullptr;
+};
+
+void LatestVersionCheckerAndUpdater::askUserForLocationToDownload (const Asset& asset)
+{
+ FileChooser chooser ("Please select the location into which you would like to install the new version",
+ { File::getSpecialLocation(File::userDesktopDirectory) },
+ "*.exe;*.zip");
+
+ if (chooser.browseForDirectory())
+ {
+ auto targetFolder = chooser.getResult();
+ if (targetFolder == File{})
+ return;
+
+ File targetFile = targetFolder.getChildFile(asset.name).getNonexistentSibling();
+
+ downloadAndInstall (asset, targetFile);
+ }
+}
+
+void LatestVersionCheckerAndUpdater::askUserAboutNewVersion (const String& newVersionString,
+ const String& releaseNotes,
+ const Asset& asset)
+{
+ dialogWindow = UpdateDialog::launchDialog (newVersionString,
+ releaseNotes,
+ mainWindow->automaticVersionChecking);
+
+ if (auto* mm = ModalComponentManager::getInstance())
+ {
+ mm->attachCallback (dialogWindow.get(),
+ ModalCallbackFunction::create ([this, asset] (int result)
+ {
+ if (result == 1)
+ askUserForLocationToDownload (asset);
+ else if(result == -1)
+ mainWindow->automaticVersionChecking = false;
+ else if(result == 0)
+ mainWindow->automaticVersionChecking = true;
+
+ dialogWindow.reset();
+ }));
+ }
+}
+
+//==============================================================================
+class DownloadThread : private ThreadWithProgressWindow
+{
+public:
+ DownloadThread (const LatestVersionCheckerAndUpdater::Asset& a,
+ const File& t,
+ std::function&& cb)
+ : ThreadWithProgressWindow ("Downloading New Version", true, true),
+ asset (a), targetFile (t), completionCallback (std::move (cb))
+ {
+ launchThread (3);
+ }
+
+private:
+ void run() override
+ {
+ setProgress (0.0);
+
+ auto result = download (targetFile);
+
+ if (result.failed())
+ {
+ AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
+ "Downloading Failed",
+ result.getErrorMessage());
+ }
+ else
+ {
+ setProgress (-1.0);
+ MessageManager::callAsync (completionCallback);
+ }
+ }
+
+ Result download (File& dest)
+ {
+ setStatusMessage ("Downloading...");
+
+ int statusCode = 0;
+ URL downloadUrl (asset.url);
+ StringPairArray responseHeaders;
+
+ auto inStream = downloadUrl.createInputStream (URL::InputStreamOptions (URL::ParameterHandling::inAddress)
+ .withExtraHeaders ("Accept: application/octet-stream")
+ .withConnectionTimeoutMs (5000)
+ .withResponseHeaders (&responseHeaders)
+ .withStatusCode (&statusCode)
+ .withNumRedirectsToFollow (1));
+
+ if (inStream != nullptr && statusCode == 200)
+ {
+ int64 total = 0;
+
+ //Use the Url's input stream and write it to a file using output stream
+ std::unique_ptr out = dest.createOutputStream();
+
+ for (;;)
+ {
+ if (threadShouldExit())
+ return Result::fail ("Cancelled");
+
+
+
+ auto written = out->writeFromInputStream(*inStream, 8192);
+
+ if (written == 0)
+ break;
+
+ total += written;
+
+ setProgress((double)total / (double)asset.size);
+
+ setStatusMessage ("Downloading... "
+ + File::descriptionOfSizeInBytes (total)
+ + " / "
+ + File::descriptionOfSizeInBytes (asset.size));
+ }
+
+ out->flush();
+ return Result::ok();
+ }
+
+ return Result::fail ("Failed to download from: " + asset.url);
+ }
+
+ const LatestVersionCheckerAndUpdater::Asset asset;
+ File targetFile;
+ std::function completionCallback;
+};
+
+static void runInstaller (const File& targetFile)
+{
+ bool runInstaller = AlertWindow::showOkCancelBox(AlertWindow::WarningIcon,
+ "Quit Open Ephys GUI?",
+ "To run the installer, the current instance of GUI needs to be closed."
+ "\nAre you sure you want to continue?",
+ "Yes", "No");
+
+ if(runInstaller)
+ {
+ #if JUCE_WINDOWS
+ if (targetFile.existsAsFile())
+ {
+ auto returnCode = ShellExecute(NULL, (LPCSTR)"runas", targetFile.getFullPathName().toRawUTF8(), NULL, NULL, SW_SHOW);
+
+ if((int)returnCode > 31)
+ JUCEApplication::getInstance()->systemRequestedQuit();
+ else
+ LOGE("Failed to run the installer: ", GetLastError());
+ }
+ #endif
+ }
+}
+
+void LatestVersionCheckerAndUpdater::downloadAndInstall (const Asset& asset, const File& targetFile)
+{
+#if JUCE_WINDOWS
+ File exeDir = File::getSpecialLocation(
+ File::SpecialLocationType::currentExecutableFile).getParentDirectory();
+
+ if(exeDir.findChildFiles(File::findFiles, false, "unins*").size() > 0)
+ {
+ downloader.reset (new DownloadThread (asset, targetFile,
+ [this, targetFile]
+ {
+ downloader.reset();
+ runInstaller(targetFile);
+
+ }));
+ }
+ else
+#endif
+ {
+ String msgBoxString = String();
+
+ if(targetFile.getFileExtension().equalsIgnoreCase(".zip"))
+ {
+ msgBoxString = "Please extract the zip file located at: \n" +
+ targetFile.getFullPathName().quoted() +
+ "\nto your desired location and then run the updated version from there. "
+ "You can also overwrite the current installation after quitting the current instance.";
+
+ }
+ else
+ {
+ msgBoxString = "Please quit the GUI first, then launch the installer file located at: \n" +
+ targetFile.getFullPathName().quoted() +
+ "\nand follow the steps to finish updating the GUI.";
+ }
+
+
+ downloader.reset (new DownloadThread (asset, targetFile,
+ [this, msgBoxString]
+ {
+ downloader.reset();
+
+ AlertWindow::showMessageBoxAsync
+ (AlertWindow::InfoIcon,
+ "Download successful!",
+ msgBoxString);
+
+ }));
+ }
+}
+
+//==============================================================================
+JUCE_IMPLEMENT_SINGLETON (LatestVersionCheckerAndUpdater)
diff --git a/Source/AutoUpdater.h b/Source/AutoUpdater.h
new file mode 100644
index 000000000..cdd974939
--- /dev/null
+++ b/Source/AutoUpdater.h
@@ -0,0 +1,64 @@
+/*
+ ------------------------------------------------------------------
+
+ This file is part of the Open Ephys GUI
+ Copyright (C) 2023 Open Ephys
+
+ ------------------------------------------------------------------
+ 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 .
+
+ Reference : https://github.com/juce-framework/JUCE/blob/6.0.8/extras/Projucer/Source/Application/jucer_AutoUpdater.h
+
+*/
+
+#pragma once
+
+#include "../JuceLibraryCode/JuceHeader.h"
+
+class MainWindow;
+class DownloadThread;
+
+class LatestVersionCheckerAndUpdater : public DeletedAtShutdown,
+ private Thread
+{
+public:
+ LatestVersionCheckerAndUpdater();
+ ~LatestVersionCheckerAndUpdater() override;
+
+ struct Asset
+ {
+ const String name;
+ const String url;
+ const int size;
+ };
+
+ void checkForNewVersion (bool isBackgroundCheck, MainWindow* mw);
+
+ //==============================================================================
+ JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (LatestVersionCheckerAndUpdater)
+
+private:
+ //==============================================================================
+ void run() override;
+ void askUserAboutNewVersion (const String&, const String&, const Asset& asset);
+ void askUserForLocationToDownload (const Asset& asset);
+ void downloadAndInstall (const Asset& asset, const File& targetFile);
+
+ //==============================================================================
+ bool backgroundCheck = false;
+ MainWindow* mainWindow;
+
+ std::unique_ptr downloader;
+ std::unique_ptr dialogWindow;
+};
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 543150a83..4b3382516 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -4,6 +4,8 @@
add_sources(open-ephys
AccessClass.h
AccessClass.cpp
+ AutoUpdater.cpp
+ AutoUpdater.h
CoreServices.h
CoreServices.cpp
MainWindow.h
diff --git a/Source/MainWindow.cpp b/Source/MainWindow.cpp
index ebf6bc2da..a24d31b93 100644
--- a/Source/MainWindow.cpp
+++ b/Source/MainWindow.cpp
@@ -25,6 +25,7 @@
#include "Utils/OpenEphysHttpServer.h"
#include "UI/UIComponent.h"
#include "UI/EditorViewport.h"
+#include "AutoUpdater.h"
#include
@@ -59,6 +60,7 @@ MainWindow::MainWindow(const File& fileToLoad)
shouldReloadOnStartup = true;
shouldEnableHttpServer = true;
openDefaultConfigWindow = false;
+ automaticVersionChecking = true;
// Create ProcessorGraph and AudioComponent, and connect them.
// Callbacks will be set by the play button in the control panel
@@ -160,6 +162,10 @@ MainWindow::MainWindow(const File& fileToLoad)
disableHttpServer();
}
+#ifdef NDEBUG
+ if(automaticVersionChecking)
+ LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (true, this);
+#endif
}
MainWindow::~MainWindow()
@@ -267,6 +273,7 @@ void MainWindow::saveWindowBounds()
xml->setAttribute("version", JUCEApplication::getInstance()->getApplicationVersion());
xml->setAttribute("shouldReloadOnStartup", shouldReloadOnStartup);
xml->setAttribute("shouldEnableHttpServer", shouldEnableHttpServer);
+ xml->setAttribute("automaticVersionChecking", automaticVersionChecking);
XmlElement* bounds = new XmlElement("BOUNDS");
bounds->setAttribute("x",getScreenX());
@@ -330,6 +337,7 @@ void MainWindow::loadWindowBounds()
shouldReloadOnStartup = xml->getBoolAttribute("shouldReloadOnStartup", false);
shouldEnableHttpServer = xml->getBoolAttribute("shouldEnableHttpServer", false);
+ automaticVersionChecking = xml->getBoolAttribute("automaticVersionChecking", true);
for (auto* e : xml->getChildIterator())
{
diff --git a/Source/MainWindow.h b/Source/MainWindow.h
index 483952026..551b0bf30 100644
--- a/Source/MainWindow.h
+++ b/Source/MainWindow.h
@@ -68,8 +68,12 @@ class MainWindow : public DocumentWindow
/** Determines whether the ProcessorGraph http server is enabled. */
bool shouldEnableHttpServer;
+ /** Determines whether the default config selection window needs to open on startup. */
bool openDefaultConfigWindow;
+ /** Determines whether the Auto Updater needs to run on startup. */
+ bool automaticVersionChecking;
+
/** Ends the process() callbacks and disables all processors.*/
void shutDownGUI();
diff --git a/Source/UI/UIComponent.cpp b/Source/UI/UIComponent.cpp
index 5e33a45a7..d7cfa55d0 100755
--- a/Source/UI/UIComponent.cpp
+++ b/Source/UI/UIComponent.cpp
@@ -36,6 +36,7 @@
#include "../Processors/ProcessorGraph/ProcessorGraph.h"
#include "../Audio/AudioComponent.h"
#include "../MainWindow.h"
+#include "../AutoUpdater.h"
UIComponent::UIComponent(MainWindow* mainWindow_, ProcessorGraph* pgraph, AudioComponent* audio_)
: mainWindow(mainWindow_), processorGraph(pgraph), audio(audio_), messageCenterIsCollapsed(true)
@@ -476,6 +477,7 @@ PopupMenu UIComponent::getMenuForIndex(int menuIndex, const String& menuName)
else if (menuIndex == 3)
{
menu.addCommandItem(commandManager, showHelp);
+ menu.addCommandItem(commandManager, checkForUpdates);
}
return menu;
@@ -518,6 +520,7 @@ void UIComponent::getAllCommands(Array & commands)
setClockModeDefault,
setClockModeHHMMSS,
showHelp,
+ checkForUpdates,
resizeWindow,
openPluginInstaller,
openDefaultConfigWindow
@@ -651,6 +654,11 @@ void UIComponent::getCommandInfo(CommandID commandID, ApplicationCommandInfo& re
result.setActive(true);
break;
+ case checkForUpdates:
+ result.setInfo("Check for updates", "Checks if a newer version of the GUI is available", "General", 0);
+ result.setActive(true);
+ break;
+
case resizeWindow:
result.setInfo("Reset window bounds", "Reset window bounds", "General", 0);
break;
@@ -852,6 +860,12 @@ bool UIComponent::perform(const InvocationInfo& info)
url.launchInDefaultBrowser();
break;
}
+
+ case checkForUpdates:
+ {
+ LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (false, mainWindow);
+ break;
+ }
case toggleProcessorList:
processorList->toggleState();
diff --git a/Source/UI/UIComponent.h b/Source/UI/UIComponent.h
index 7dca1d685..c21399a52 100755
--- a/Source/UI/UIComponent.h
+++ b/Source/UI/UIComponent.h
@@ -206,6 +206,7 @@ class UIComponent : public Component,
setClockModeHHMMSS = 0x2112,
toggleHttpServer = 0x4001,
showHelp = 0x2011,
+ checkForUpdates = 0x2022,
resizeWindow = 0x2012,
reloadOnStartup = 0x2013,
saveSignalChainAs = 0x2014,