diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e5956d0..d2954789 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,7 +85,7 @@ jobs: cache: 'npm' # Workaround due to a bug in node-gyp: https://github.com/electron/rebuild/issues/1116 - name: Install Setuptools - run: pip install setuptools + run: python3 -m pip install --break-system-packages setuptools - name: Install deps uses: nick-fields/retry@v2 with: diff --git a/README.md b/README.md index 7f07253b..7394edaf 100644 --- a/README.md +++ b/README.md @@ -20,32 +20,32 @@ everything, the hardware is not working, or you have any other _support_ problem ## Installation -Depending on the target operating system, _INAV Configurator_ is distributed as a _standalone_ application or Chrome App. + _INAV Configurator_ is distributed as a _standalone_ application. ### Windows 1. Visit [release page](https://github.com/iNavFlight/inav-configurator/releases) -2. Download Configurator for Windows platform (win32 or win64 is present) +2. Download Configurator for Windows platform (ia32 or win64 is present) 3. Install * Extract ZIP archive and run the INAV Configurator app from the unpacked folder - * OR just use the setup program `INAV Configurator.msi` + * OR just use the setup program `INAV-Configurator_win32_arch_x.y.z.msi`, **arch** is your computer architecture (ia32 (32bit) or x64 (64bit)), **x.y.z** is the INAV Configurator version number. 4. Configurator is not signed, so you have to allow Windows to run untrusted applications. There might be a monit for it during the first run ### Linux 1. Visit [release page](https://github.com/iNavFlight/inav-configurator/releases) -2. Download Configurator for Linux platform (linux32 and linux64 are present) - * **.rpm** is the Fedora installation file. Just download and install using `sudo dnf localinstall /path/to/INAV-Configurator_linux64-x.y.z-x86_64.rpm` or open it with a package manager (e.g. via Files) - * **.deb** is the Debian/Ubuntu installation file. Just download and install using `sudo apt install /path/to/INAV-Configurator_linux64_x.y.z.deb` or open it with a package manager (e.g. via the File Manager) +2. Download Configurator for Linux platform (only linux64 is present) + * **.rpm** is the Fedora installation file. Just download and install using `sudo dnf localinstall /path/to/INAV-Configurator_linux_x64-x.y.z.rpm` or open it with a package manager (e.g. via Files) + * **.deb** is the Debian/Ubuntu installation file. Just download and install using `sudo apt install /path/to/INAV-Configurator_linux_x64_x.y.z.deb` or open it with a package manager (e.g. via the File Manager) * **.zip** is a universal archive. Download and continue with these instructions to install 3. Change to the directory containing the downloaded **zip** file 4. download [this](https://raw.githubusercontent.com/iNavFlight/inav-configurator/master/assets/linux/inav-configurator.desktop) file to the same directory. Its filename should be `inav-configurator.desktop`. 5. Extract **zip** archive ``` -unzip INAV-Configurator_linuxNN_x.y.z.tar.gz -d /tmp/ +unzip INAV-Configurator_linux_arch_x.y.z.zip -d /tmp/ ``` - **NN** is the bits of your OS. **x.y.z** is the INAV Configurator version number. + **arch** is your computer architecture (x64, armv7l, ...), **x.y.z** is the INAV Configurator version number. 6. If this is the first time installing INAV Configurator, create a home for its files ``` @@ -93,21 +93,27 @@ Options: See [Electron Forge CLI Documentation](https://www.electronforge.io/cli#options-2) for details +Note: Not all architectures are available for all platforms. For example, ia32 (32bit) support is not available for Linux. +Tested architectures: +- Windows: x64 and ia32 +- Linux: x64 and armv7l +- MacOS: x64 and arm64 + To build the setup program for windows, you have to install [WiX Toolset V3](https://github.com/wixtoolset/wix3/releases) and add the `bin` folder to you `PATH`, e.g. ```C:\Program Files (x86)\WiX Toolset v3.14\bin``` To build deb and rpm packages for Linux, you have to install the following packages: -- Ubuntu/Debian: `dpkg, fakeroot, rpmbuild, build-essential, libudev-dev` +- Ubuntu/Debian: `dpkg, fakeroot, rpm, build-essential, libudev-dev` - OpenSuse/Fedora: `dpkg, fakeroot, rpmbuild, systemd-devel, devel-basis (zypper install -t pattern devel_basis), zip` Example (note the double -- ): -``` npm run make -- --arch="x64" ``` +```npm run make -- --arch="x64"``` ### Running with debug | Inspector To be able to open Inspector, set envorinment variable `NODE_ENV` to `develpoment` or set the flag directly when run `npm start`: -```NODE_ENV=development npm start``` +```NODE_ENV=development npm start``` or ```$env:NODE_ENV="development" | npm start``` for Windows PowerShell Or use vscode and start a debug session `Debug Configurator` (Just hit F5!) diff --git a/forge.config.js b/forge.config.js index 092043be..223287f5 100644 --- a/forge.config.js +++ b/forge.config.js @@ -74,7 +74,8 @@ module.exports = { name: '@electron-forge/maker-dmg', config: { name: "INAV Configurator", - background: "./assets/osx/dmg-background.png" + background: "./assets/osx/dmg-background.png", + icon: "./images/inav.icns" } }, { diff --git a/js/gui.js b/js/gui.js index b388a9c3..3d48048c 100644 --- a/js/gui.js +++ b/js/gui.js @@ -1,4 +1,6 @@ 'use strict'; +const { dialog } = require("@electron/remote"); + const CONFIGURATOR = require('./data_storage'); const Switchery = require('./libraries/switchery/switchery') @@ -529,6 +531,13 @@ GUI_control.prototype.update_dataflash_global = function () { } }; +/** +* Don't use alert() in Electron, it has a nasty bug: https://github.com/electron/electron/issues/31917 +*/ +GUI_control.prototype.alert = function(message) { + dialog.showMessageBoxSync({ message: message, icon: "./images/inav_icon_128.png" }); +} + // initialize object into GUI variable var GUI = new GUI_control(); diff --git a/js/main.js b/js/main.js index e3a6b232..dd8e93da 100644 --- a/js/main.js +++ b/js/main.js @@ -1,4 +1,4 @@ -const { app, BrowserWindow, ipcMain } = require('electron'); +const { app, BrowserWindow, ipcMain, Menu, MenuItem } = require('electron'); const windowStateKeeper = require('electron-window-state'); const path = require('path'); const Store = require('electron-store'); @@ -53,6 +53,10 @@ function createDeviceChooser() { } app.on('ready', () => { + createWindow(); +}); + +function createWindow() { let mainWindowState = windowStateKeeper({ defaultWidth: 800, @@ -72,6 +76,24 @@ app.on('ready', () => { }, }); + mainWindow.webContents.on('context-menu', (_, props) => { + const menu = new Menu() ; + menu.append(new MenuItem({ label: "Undo", role: "undo", accelerator: 'CmdOrCtrl+Z', visible: props.isEditable })); + menu.append(new MenuItem({ label: "Redo", role: "redo", accelerator: 'CmdOrCtrl+Y', visible: props.isEditable })); + menu.append(new MenuItem({ type: "separator", visible: props.isEditable })); + menu.append(new MenuItem({ label: 'Cut', role: 'cut', accelerator: 'CmdOrCtrl+X', visible: props.isEditable && props.selectionText })); + menu.append(new MenuItem({ label: 'Copy', role: 'copy', accelerator: 'CmdOrCtrl+C', visible: props.selectionText })); + menu.append(new MenuItem({ label: 'Paste', role: 'paste', accelerator: 'CmdOrCtrl+V', visible: props.isEditable })); + menu.append(new MenuItem({ label: "Select all", role: 'selectAll', accelerator: 'CmdOrCtrl+A', visible: props.isEditable})); + + menu.items.forEach(item => { + if (item.visible) { + menu.popup(); + return; + } + }); + }); + mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => { event.preventDefault(); selectBluetoothCallback = callback; @@ -147,7 +169,7 @@ app.on('ready', () => { if (process.env.NODE_ENV === 'development') { mainWindow.webContents.openDevTools(); } -}); +} app.on('window-all-closed', () => { diff --git a/js/msp/MSPHelper.js b/js/msp/MSPHelper.js index 61a17cb4..00e8c4c2 100644 --- a/js/msp/MSPHelper.js +++ b/js/msp/MSPHelper.js @@ -922,7 +922,7 @@ var mspHelper = (function () { directions = []; for (directionLetterIndex = 0; directionLetterIndex < MSP.ledDirectionLetters.length; directionLetterIndex++) { - if (bit_check(directionMask, directionLetterIndex)) { + if (BitHelper.bit_check(directionMask, directionLetterIndex)) { directions.push(MSP.ledDirectionLetters[directionLetterIndex]); } } @@ -932,7 +932,7 @@ var mspHelper = (function () { functions = []; for (var functionLetterIndex = 0; functionLetterIndex < MSP.ledFunctionLetters.length; functionLetterIndex++) { - if (bit_check(functionMask, functionLetterIndex)) { + if (BitHelper.bit_check(functionMask, functionLetterIndex)) { functions.push(MSP.ledFunctionLetters[functionLetterIndex]); } } @@ -962,7 +962,7 @@ var mspHelper = (function () { var overlayMask = (mask >> 12) & 0x3F; for (var overlayLetterIndex = 0; overlayLetterIndex < MSP.ledOverlayLetters.length; overlayLetterIndex++) { - if (bit_check(overlayMask, overlayLetterIndex)) { + if (BitHelper.bit_check(overlayMask, overlayLetterIndex)) { functions.push(MSP.ledOverlayLetters[overlayLetterIndex]); } } @@ -971,7 +971,7 @@ var mspHelper = (function () { directions = []; for (directionLetterIndex = 0; directionLetterIndex < MSP.ledDirectionLetters.length; directionLetterIndex++) { - if (bit_check(directionMask, directionLetterIndex)) { + if (BitHelper.bit_check(directionMask, directionLetterIndex)) { directions.push(MSP.ledDirectionLetters[directionLetterIndex]); } } @@ -1017,7 +1017,7 @@ var mspHelper = (function () { var overlayMask = (mask >> 16) & 0xFF; for (var overlayLetterIndex = 0; overlayLetterIndex < MSP.ledOverlayLetters.length; overlayLetterIndex++) { - if (bit_check(overlayMask, overlayLetterIndex)) { + if (BitHelper.bit_check(overlayMask, overlayLetterIndex)) { functions.push(MSP.ledOverlayLetters[overlayLetterIndex]); } } @@ -1026,7 +1026,7 @@ var mspHelper = (function () { directions = []; for (directionLetterIndex = 0; directionLetterIndex < MSP.ledDirectionLetters.length; directionLetterIndex++) { - if (bit_check(directionMask, directionLetterIndex)) { + if (BitHelper.bit_check(directionMask, directionLetterIndex)) { directions.push(MSP.ledDirectionLetters[directionLetterIndex]); } } @@ -2260,7 +2260,7 @@ var mspHelper = (function () { buffer.push(BitHelper.lowByte(servoConfiguration.middle)); buffer.push(BitHelper.highByte(servoConfiguration.middle)); - buffer.push(lowByte(servoConfiguration.rate)); + buffer.push(BitHelper.lowByte(servoConfiguration.rate)); // prepare for next iteration servoIndex++; @@ -2650,7 +2650,7 @@ var mspHelper = (function () { bitIndex = MSP.ledOverlayLetters.indexOf(led.functions[overlayLetterIndex]); if (bitIndex >= 0) { - mask |= bit_set(mask, bitIndex + 16); + mask |= BitHelper.bit_set(mask, bitIndex + 16); } } @@ -2662,9 +2662,9 @@ var mspHelper = (function () { bitIndex = MSP.ledDirectionLetters.indexOf(led.directions[directionLetterIndex]); if (bitIndex >= 0) { if(bitIndex < 4) { - mask |= bit_set(mask, bitIndex + 28); + mask |= BitHelper.bit_set(mask, bitIndex + 28); } else { - extra |= bit_set(extra, bitIndex - 4); + extra |= BitHelper.bit_set(extra, bitIndex - 4); } } } diff --git a/tabs/cli.js b/tabs/cli.js index abde8503..e22c2c62 100644 --- a/tabs/cli.js +++ b/tabs/cli.js @@ -1,6 +1,7 @@ 'use strict'; const path = require('path'); +const fs = require('fs'); const { dialog } = require("@electron/remote"); const MSP = require('./../js/msp'); @@ -170,7 +171,6 @@ TABS.cli.initialize = function (callback) { return; } - const fs = require('fs'); fs.writeFile(result.filePath, self.outputHistory, (err) => { if (err) { GUI.log(i18n.getMessage('ErrorWritingFile')); @@ -255,7 +255,6 @@ TABS.cli.initialize = function (callback) { } if (result.filePaths.length == 1) { - const fs = require('fs'); fs.readFile(result.filePaths[0], (err, data) => { if (err) { GUI.log(i18n.getMessage('ErrorReadingFile')); diff --git a/tabs/firmware_flasher.js b/tabs/firmware_flasher.js index bcd4a483..c738347e 100755 --- a/tabs/firmware_flasher.js +++ b/tabs/firmware_flasher.js @@ -1,6 +1,7 @@ 'use strict'; const { marked } = require('marked'); +const fs = require('fs'); const path = require('path'); const semver = require('semver'); const { dialog } = require('@electron/remote'); @@ -254,8 +255,6 @@ TABS.firmware_flasher.initialize = function (callback) { if (result.filePaths.length == 1) { filename = result.filePaths[0]; } - - const fs = require('fs'); $('div.git_info').slideUp(); diff --git a/tabs/led_strip.js b/tabs/led_strip.js index b6b29e82..f9192ca4 100644 --- a/tabs/led_strip.js +++ b/tabs/led_strip.js @@ -230,9 +230,8 @@ TABS.led_strip.initialize = function (callback, scrollPosition) { $('.colors').on('dblclick', 'button', function(e) { - var pp = $('.tab-led-strip').position(); - var moveLeft = $('.tab-led-strip').position().left + ($('.colorDefineSliders').width() / 2); - var moveUp = $('.tab-led-strip').position().top + $('.colorDefineSliders').height() + 20; + var moveLeft = $('.tab-led-strip').offset().left + ($('.colorDefineSliders').width() / 2); + var moveUp = $('.tab-led-strip').offset().top + $('.colorDefineSliders').height() + 20; $('.colorDefineSliders').css('left', e.pageX - e.offsetX - moveLeft); $('.colorDefineSliders').css('top', e.pageY - e.offsetY - moveUp); diff --git a/tabs/logging.js b/tabs/logging.js index 43de3709..69a7a508 100644 --- a/tabs/logging.js +++ b/tabs/logging.js @@ -1,6 +1,7 @@ 'use strict'; const path = require('path'); +const fs = require('fs'); const { dialog } = require("@electron/remote"); const Store = require('electron-store'); const store = new Store(); @@ -84,7 +85,6 @@ TABS.logging.initialize = function (callback) { } interval.add('log_data_poll', log_data_poll, parseInt($('select.speed').val()), true); // refresh rate goes here - const fs = require('fs'); interval.add('write_data', function write_data() { if (log_buffer.length && readyToWrite) { // only execute when there is actual data to write diff --git a/tabs/mission_control.js b/tabs/mission_control.js index d07ecd57..c7da7cc9 100644 --- a/tabs/mission_control.js +++ b/tabs/mission_control.js @@ -1,7 +1,9 @@ 'use strict'; const path = require('path'); +const fs = require('fs'); const ol = require('openlayers'); +const xml2js = require('xml2js'); const Store = require('electron-store'); const store = new Store(); const { dialog } = require("@electron/remote"); @@ -483,7 +485,7 @@ TABS.mission_control.initialize = function (callback) { function checkApproachAltitude(altitude, isSeaLevelRef, sealevel) { if (altitude - (isSeaLevelRef ? sealevel * 100 : 0 ) < 0) { - alert(i18n.getMessage('MissionPlannerAltitudeChangeReset')); + GUI.alert(i18n.getMessage('MissionPlannerAltitudeChangeReset')); return false; } @@ -493,7 +495,7 @@ TABS.mission_control.initialize = function (callback) { function checkLandingAltitude(altitude, isSeaLevelRef, sealevel) { if (altitude - (isSeaLevelRef ? sealevel * 100 : 0 ) < MAX_NEG_FW_LAND_ALT) { - alert(i18n.getMessage('MissionPlannerFwLAndingAltitudeChangeReset')); + GUI.alert(i18n.getMessage('MissionPlannerFwLAndingAltitudeChangeReset')); return false; } @@ -1489,26 +1491,26 @@ TABS.mission_control.initialize = function (callback) { if ($(this).val() >= 360 || ($(this).val() < 0 && $(this).val() != -1)) { $(this).val(-1); - alert(i18n.getMessage('MissionPlannerHeadSettingsCheck')); + GUI.alert(i18n.getMessage('MissionPlannerHeadSettingsCheck')); } } else if (MWNP.WPTYPE.REV[element.getAction()] == "RTH") { if ($(this).val() != 0 && $(this).val() != 1) { $(this).val(0); - alert(i18n.getMessage('MissionPlannerRTHSettingsCheck')); + GUI.alert(i18n.getMessage('MissionPlannerRTHSettingsCheck')); } } else if (MWNP.WPTYPE.REV[element.getAction()] == "JUMP") { if ($(this).val() > mission.getNonAttachedList().length || $(this).val() < 1) { $(this).val(1); - alert(i18n.getMessage('MissionPlannerJumpSettingsCheck')); + GUI.alert(i18n.getMessage('MissionPlannerJumpSettingsCheck')); } else if (mission.getPoiList().length != 0 && mission.getPoiList()) { if (mission.getPoiList().includes(mission.convertJumpNumberToWaypoint(Number($(this).val())-1))) { $(this).val(1); - alert(i18n.getMessage('MissionPlannerJump3SettingsCheck')); + GUI.alert(i18n.getMessage('MissionPlannerJump3SettingsCheck')); } } } @@ -1523,7 +1525,7 @@ TABS.mission_control.initialize = function (callback) { if ($(this).val() > 10 || ($(this).val() < 0 && $(this).val() != -1)) { $(this).val(0); - alert(i18n.getMessage('MissionPlannerJump2SettingsCheck')); + GUI.alert(i18n.getMessage('MissionPlannerJump2SettingsCheck')); } } element.setP2(Number($(this).val())); @@ -2310,7 +2312,7 @@ TABS.mission_control.initialize = function (callback) { let found = false; mission.get().forEach(wp => { if (wp.getAction() == MWNP.WPTYPE.LAND) { - alert(i18n.getMessage('MissionPlannerOnlyOneLandWp')); + GUI.alert(i18n.getMessage('MissionPlannerOnlyOneLandWp')); found = true; $(event.currentTarget).val(selectedMarker.getAction()); } @@ -2684,7 +2686,7 @@ TABS.mission_control.initialize = function (callback) { $('#addSafehome').on('click', () => { if (FC.SAFEHOMES.safehomeCount() + 1 > FC.SAFEHOMES.getMaxSafehomeCount()){ - alert(i18n.getMessage('missionSafehomeMaxSafehomesReached')); + GUI.alert(i18n.getMessage('missionSafehomeMaxSafehomesReached')); return; } @@ -3016,7 +3018,7 @@ TABS.mission_control.initialize = function (callback) { $('#removePoint').on('click', function () { if (selectedMarker) { if (mission.isJumpTargetAttached(selectedMarker)) { - alert(i18n.getMessage('MissionPlannerJumpTargetRemoval')); + GUI.alert(i18n.getMessage('MissionPlannerJumpTargetRemoval')); } else if (mission.getAttachedFromWaypoint(selectedMarker) && mission.getAttachedFromWaypoint(selectedMarker).length != 0) { if (confirm(i18n.getMessage('confirm_delete_point_with_options'))) { @@ -3096,7 +3098,7 @@ TABS.mission_control.initialize = function (callback) { $('#saveMissionButton').on('click', function () { if (mission.isEmpty()) { - alert(i18n.getMessage('no_waypoints_to_save')); + GUI.alert(i18n.getMessage('no_waypoints_to_save')); return; } $(this).addClass('disabled'); @@ -3115,7 +3117,7 @@ TABS.mission_control.initialize = function (callback) { $('#saveEepromMissionButton').on('click', function () { if (mission.isEmpty()) { - alert(i18n.getMessage('no_waypoints_to_save')); + GUI.alert(i18n.getMessage('no_waypoints_to_save')); return; } $(this).addClass('disabled'); @@ -3161,9 +3163,6 @@ TABS.mission_control.initialize = function (callback) { // ///////////////////////////////////////////// function loadMissionFile(filename) { - const fs = require('fs'); - if (!window.xml2js) return GUI.log(i18n.getMessage('errorReadingFileXml2jsNotFound')); - for (let i = FC.SAFEHOMES.getMaxSafehomeCount(); i < FC.FW_APPROACH.getMaxFwApproachCount(); i++) { FC.FW_APPROACH.clean(i); } @@ -3174,7 +3173,7 @@ TABS.mission_control.initialize = function (callback) { return console.error(err); } - window.xml2js.Parser({ 'explicitChildren': true, 'preserveChildrenOrder': true }).parseString(data, (err, result) => { + xml2js.Parser({ 'explicitChildren': true, 'preserveChildrenOrder': true }).parseString(data, (err, result) => { if (err) { GUI.log(i18n.getMessage('errorParsingFile')); return console.error(err); @@ -3344,9 +3343,6 @@ TABS.mission_control.initialize = function (callback) { } function saveMissionFile(filename) { - const fs = require('fs'); - if (!window.xml2js) return GUI.log(i18n.getMessage('errorWritingFileXml2jsNotFound')); - var center = ol.proj.toLonLat(map.getView().getCenter()); var zoom = map.getView().getZoom(); let multimission = multimissionCount && !singleMissionActive(); @@ -3408,7 +3404,7 @@ TABS.mission_control.initialize = function (callback) { approachIdx++; } - var builder = new window.xml2js.Builder({ 'rootName': 'mission', 'renderOpts': { 'pretty': true, 'indent': '\t', 'newline': '\n' } }); + var builder = new xml2js.Builder({ 'rootName': 'mission', 'renderOpts': { 'pretty': true, 'indent': '\t', 'newline': '\n' } }); var xml = builder.buildObject(data); xml = xml.replace(/missionitem mission/g, 'meta mission'); fs.writeFile(filename, xml, (err) => { @@ -3445,7 +3441,7 @@ TABS.mission_control.initialize = function (callback) { $('#loadMissionButton').removeClass('disabled'); } if (!FC.MISSION_PLANNER.getCountBusyPoints()) { - alert(i18n.getMessage('no_waypoints_to_load')); + GUI.alert(i18n.getMessage('no_waypoints_to_load')); return; } mission.reinit(); @@ -3555,7 +3551,7 @@ TABS.mission_control.initialize = function (callback) { if (AbsAltCheck) { if (checkAltitude < 100 * elevation) { if (resetAltitude) { - alert(i18n.getMessage('MissionPlannerAltitudeChangeReset')); + GUI.alert(i18n.getMessage('MissionPlannerAltitudeChangeReset')); altitude = selectedMarker.getAlt(); } else { altitude = settings.alt + 100 * elevation; @@ -3566,7 +3562,7 @@ TABS.mission_control.initialize = function (callback) { let elevationAtHome = HOME.getAlt(); if ((checkAltitude / 100 + elevationAtHome) < elevation) { if (resetAltitude) { - alert(i18n.getMessage('MissionPlannerAltitudeChangeReset')); + GUI.alert(i18n.getMessage('MissionPlannerAltitudeChangeReset')); altitude = selectedMarker.getAlt(); } else { let currentGroundClearance = 100 * Number($('#groundClearanceValueAtWP').text()); @@ -3702,9 +3698,9 @@ TABS.mission_control.setBit = function(bits, bit, value) { // function handleError(evt) { // if (evt.message) { // Chrome sometimes provides this - // alert("error: "+evt.message +" at linenumber: "+evt.lineno+" of file: "+evt.filename); + // GUI.alert("error: "+evt.message +" at linenumber: "+evt.lineno+" of file: "+evt.filename); // } else { - // alert("error: "+evt.type+" from element: "+(evt.srcElement || evt.target)); + // GUI.alert("error: "+evt.type+" from element: "+(evt.srcElement || evt.target)); // } // } diff --git a/tabs/onboard_logging.html b/tabs/onboard_logging.html index 7df3f776..c1e976c0 100644 --- a/tabs/onboard_logging.html +++ b/tabs/onboard_logging.html @@ -152,7 +152,6 @@