From f6aa331d77cce868d9f37ac2cd1ff67bbb80b1a0 Mon Sep 17 00:00:00 2001 From: Robin Davies Date: Fri, 20 Aug 2021 10:35:37 -0400 Subject: [PATCH] WiFi Channel Settings. --- CMakeCache.txt | 547 ----------------------- react/CMakeLists.txt | 12 +- react/package.json | 2 +- react/public/iso_codes.json | 248 +++++++++++ react/public/var/config.json | 2 +- react/src/App.tsx | 2 +- react/src/DialogEx.tsx | 119 +++++ react/src/MidiBindingsDialog.tsx | 2 +- react/src/NoChangePassword.tsx | 118 +++++ react/src/PedalBoardView.tsx | 2 +- react/src/PiPedalModel.tsx | 114 ++++- react/src/PluginControlView.tsx | 4 +- react/src/RenameDialog.tsx | 2 +- react/src/SettingsDialog.tsx | 67 ++- react/src/UploadDialog.tsx | 2 +- react/src/WifiChannel.tsx | 20 + react/src/WifiConfigDialog.tsx | 374 ++++++++++++++++ react/src/WifiConfigSettings.tsx | 45 ++ react/src/XxxSnippet.tsx | 36 ++ ruleOffet | 0 src/BeastServer.cpp | 3 + src/BeastServer.hpp | 2 + src/CMakeLists.txt | 63 ++- src/PiPedalModel.cpp | 40 +- src/PiPedalModel.hpp | 6 + src/PiPedalSocket.cpp | 34 ++ src/RegDb.cpp | 228 ++++++++++ src/RegDb.hpp | 67 +++ src/ShutdownClient.cpp | 52 +++ src/ShutdownClient.hpp | 1 + src/Storage.cpp | 72 +++ src/Storage.hpp | 8 +- src/WifiChannels.cpp | 615 ++++++++++++++++++++++++++ src/WifiChannels.hpp | 16 + src/WifiChannelsTest.cpp | 18 + src/WifiConfigSettings.cpp | 17 + src/WifiConfigSettings.hpp | 21 + test/JsonTest.cpp => src/jsonTest.cpp | 4 +- src/testMain.cpp | 5 + todo.txt | 16 +- 40 files changed, 2409 insertions(+), 597 deletions(-) delete mode 100644 CMakeCache.txt create mode 100644 react/public/iso_codes.json create mode 100644 react/src/DialogEx.tsx create mode 100644 react/src/NoChangePassword.tsx create mode 100644 react/src/WifiChannel.tsx create mode 100644 react/src/WifiConfigDialog.tsx create mode 100644 react/src/WifiConfigSettings.tsx create mode 100644 react/src/XxxSnippet.tsx create mode 100644 ruleOffet create mode 100644 src/RegDb.cpp create mode 100644 src/RegDb.hpp create mode 100644 src/WifiChannels.cpp create mode 100644 src/WifiChannels.hpp create mode 100644 src/WifiChannelsTest.cpp create mode 100644 src/WifiConfigSettings.cpp create mode 100644 src/WifiConfigSettings.hpp rename test/JsonTest.cpp => src/jsonTest.cpp (98%) create mode 100644 src/testMain.cpp diff --git a/CMakeCache.txt b/CMakeCache.txt deleted file mode 100644 index 40bce9d..0000000 --- a/CMakeCache.txt +++ /dev/null @@ -1,547 +0,0 @@ -# This is the CMakeCache file. -# For build in directory: /home/patch/src/piddle -# It was generated by CMake: /usr/local/bin/cmake -# You can edit this file to change values found and used by cmake. -# If you do not want to change any of the values, simply exit the editor. -# If you do want to change a value, simply edit, save, and exit the editor. -# The syntax for the file is as follows: -# KEY:TYPE=VALUE -# KEY is the name of a variable in the cache. -# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!. -# VALUE is the current value for the KEY. - -######################## -# EXTERNAL cache entries -######################## - -//Build the testing tree. -BUILD_TESTING:BOOL=ON - -//Path to a program. -BZRCOMMAND:FILEPATH=BZRCOMMAND-NOTFOUND - -//Path to a program. -CMAKE_ADDR2LINE:FILEPATH=/usr/bin/addr2line - -//Path to a program. -CMAKE_AR:FILEPATH=/usr/bin/ar - -//Choose the type of build, options are: None Debug Release RelWithDebInfo -// MinSizeRel ... -CMAKE_BUILD_TYPE:STRING= - -//Enable/Disable color output during build. -CMAKE_COLOR_MAKEFILE:BOOL=ON - -//CXX compiler -CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/c++ - -//A wrapper around 'ar' adding the appropriate '--plugin' option -// for the GCC compiler -CMAKE_CXX_COMPILER_AR:FILEPATH=/usr/bin/gcc-ar-8 - -//A wrapper around 'ranlib' adding the appropriate '--plugin' option -// for the GCC compiler -CMAKE_CXX_COMPILER_RANLIB:FILEPATH=/usr/bin/gcc-ranlib-8 - -//Flags used by the CXX compiler during all build types. -CMAKE_CXX_FLAGS:STRING= - -//Flags used by the CXX compiler during DEBUG builds. -CMAKE_CXX_FLAGS_DEBUG:STRING=-g - -//Flags used by the CXX compiler during MINSIZEREL builds. -CMAKE_CXX_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG - -//Flags used by the CXX compiler during RELEASE builds. -CMAKE_CXX_FLAGS_RELEASE:STRING=-O3 -DNDEBUG - -//Flags used by the CXX compiler during RELWITHDEBINFO builds. -CMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG - -//C compiler -CMAKE_C_COMPILER:FILEPATH=/usr/bin/cc - -//A wrapper around 'ar' adding the appropriate '--plugin' option -// for the GCC compiler -CMAKE_C_COMPILER_AR:FILEPATH=/usr/bin/gcc-ar-8 - -//A wrapper around 'ranlib' adding the appropriate '--plugin' option -// for the GCC compiler -CMAKE_C_COMPILER_RANLIB:FILEPATH=/usr/bin/gcc-ranlib-8 - -//Flags used by the C compiler during all build types. -CMAKE_C_FLAGS:STRING= - -//Flags used by the C compiler during DEBUG builds. -CMAKE_C_FLAGS_DEBUG:STRING=-g - -//Flags used by the C compiler during MINSIZEREL builds. -CMAKE_C_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG - -//Flags used by the C compiler during RELEASE builds. -CMAKE_C_FLAGS_RELEASE:STRING=-O3 -DNDEBUG - -//Flags used by the C compiler during RELWITHDEBINFO builds. -CMAKE_C_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG - -//Path to a program. -CMAKE_DLLTOOL:FILEPATH=CMAKE_DLLTOOL-NOTFOUND - -//Flags used by the linker during all build types. -CMAKE_EXE_LINKER_FLAGS:STRING= - -//Flags used by the linker during DEBUG builds. -CMAKE_EXE_LINKER_FLAGS_DEBUG:STRING= - -//Flags used by the linker during MINSIZEREL builds. -CMAKE_EXE_LINKER_FLAGS_MINSIZEREL:STRING= - -//Flags used by the linker during RELEASE builds. -CMAKE_EXE_LINKER_FLAGS_RELEASE:STRING= - -//Flags used by the linker during RELWITHDEBINFO builds. -CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO:STRING= - -//Enable/Disable output of compile commands during generation. -CMAKE_EXPORT_COMPILE_COMMANDS:BOOL= - -//Install path prefix, prepended onto install directories. -CMAKE_INSTALL_PREFIX:PATH=/usr/local - -//Path to a program. -CMAKE_LINKER:FILEPATH=/usr/bin/ld - -//Path to a program. -CMAKE_MAKE_PROGRAM:FILEPATH=/usr/bin/make - -//Flags used by the linker during the creation of modules during -// all build types. -CMAKE_MODULE_LINKER_FLAGS:STRING= - -//Flags used by the linker during the creation of modules during -// DEBUG builds. -CMAKE_MODULE_LINKER_FLAGS_DEBUG:STRING= - -//Flags used by the linker during the creation of modules during -// MINSIZEREL builds. -CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL:STRING= - -//Flags used by the linker during the creation of modules during -// RELEASE builds. -CMAKE_MODULE_LINKER_FLAGS_RELEASE:STRING= - -//Flags used by the linker during the creation of modules during -// RELWITHDEBINFO builds. -CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO:STRING= - -//Path to a program. -CMAKE_NM:FILEPATH=/usr/bin/nm - -//Path to a program. -CMAKE_OBJCOPY:FILEPATH=/usr/bin/objcopy - -//Path to a program. -CMAKE_OBJDUMP:FILEPATH=/usr/bin/objdump - -//Value Computed by CMake -CMAKE_PROJECT_DESCRIPTION:STATIC= - -//Value Computed by CMake -CMAKE_PROJECT_HOMEPAGE_URL:STATIC= - -//Value Computed by CMake -CMAKE_PROJECT_NAME:STATIC=PiddleTest - -//Value Computed by CMake -CMAKE_PROJECT_VERSION:STATIC=0.1.0 - -//Value Computed by CMake -CMAKE_PROJECT_VERSION_MAJOR:STATIC=0 - -//Value Computed by CMake -CMAKE_PROJECT_VERSION_MINOR:STATIC=1 - -//Value Computed by CMake -CMAKE_PROJECT_VERSION_PATCH:STATIC=0 - -//Value Computed by CMake -CMAKE_PROJECT_VERSION_TWEAK:STATIC= - -//Path to a program. -CMAKE_RANLIB:FILEPATH=/usr/bin/ranlib - -//Path to a program. -CMAKE_READELF:FILEPATH=/usr/bin/readelf - -//Flags used by the linker during the creation of shared libraries -// during all build types. -CMAKE_SHARED_LINKER_FLAGS:STRING= - -//Flags used by the linker during the creation of shared libraries -// during DEBUG builds. -CMAKE_SHARED_LINKER_FLAGS_DEBUG:STRING= - -//Flags used by the linker during the creation of shared libraries -// during MINSIZEREL builds. -CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL:STRING= - -//Flags used by the linker during the creation of shared libraries -// during RELEASE builds. -CMAKE_SHARED_LINKER_FLAGS_RELEASE:STRING= - -//Flags used by the linker during the creation of shared libraries -// during RELWITHDEBINFO builds. -CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO:STRING= - -//If set, runtime paths are not added when installing shared libraries, -// but are added when building. -CMAKE_SKIP_INSTALL_RPATH:BOOL=NO - -//If set, runtime paths are not added when using shared libraries. -CMAKE_SKIP_RPATH:BOOL=NO - -//Flags used by the linker during the creation of static libraries -// during all build types. -CMAKE_STATIC_LINKER_FLAGS:STRING= - -//Flags used by the linker during the creation of static libraries -// during DEBUG builds. -CMAKE_STATIC_LINKER_FLAGS_DEBUG:STRING= - -//Flags used by the linker during the creation of static libraries -// during MINSIZEREL builds. -CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL:STRING= - -//Flags used by the linker during the creation of static libraries -// during RELEASE builds. -CMAKE_STATIC_LINKER_FLAGS_RELEASE:STRING= - -//Flags used by the linker during the creation of static libraries -// during RELWITHDEBINFO builds. -CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO:STRING= - -//Path to a program. -CMAKE_STRIP:FILEPATH=/usr/bin/strip - -//If this value is on, makefiles will be generated without the -// .SILENT directive, and all commands will be echoed to the console -// during the make. This is useful for debugging only. With Visual -// Studio IDE projects all commands are done without /nologo. -CMAKE_VERBOSE_MAKEFILE:BOOL=FALSE - -//Path to the coverage program that CTest uses for performing coverage -// inspection -COVERAGE_COMMAND:FILEPATH=/usr/bin/gcov - -//Extra command line flags to pass to the coverage tool -COVERAGE_EXTRA_FLAGS:STRING=-l - -//How many times to retry timed-out CTest submissions. -CTEST_SUBMIT_RETRY_COUNT:STRING=3 - -//How long to wait between timed-out CTest submissions. -CTEST_SUBMIT_RETRY_DELAY:STRING=5 - -//Path to a program. -CVSCOMMAND:FILEPATH=/usr/bin/cvs - -//Options passed to the cvs update command. -CVS_UPDATE_OPTIONS:STRING=-d -A -P - -//Maximum time allowed before CTest will kill the test. -DART_TESTING_TIMEOUT:STRING=1500 - -//Path to a program. -GITCOMMAND:FILEPATH=/usr/bin/git - -//Path to a program. -HGCOMMAND:FILEPATH=/usr/bin/hg - -//Command to build the project -MAKECOMMAND:STRING=/usr/local/bin/cmake --build . --config "${CTEST_CONFIGURATION_TYPE}" - -//Path to the memory checking command, used for memory error detection. -MEMORYCHECK_COMMAND:FILEPATH=MEMORYCHECK_COMMAND-NOTFOUND - -//File that contains suppressions for the memory checker -MEMORYCHECK_SUPPRESSIONS_FILE:FILEPATH= - -//Path to a program. -P4COMMAND:FILEPATH=P4COMMAND-NOTFOUND - -//pkg-config executable -PKG_CONFIG_EXECUTABLE:FILEPATH=/usr/bin/pkg-config - -//Value Computed by CMake -PiddleTest_BINARY_DIR:STATIC=/home/patch/src/piddle - -//Value Computed by CMake -PiddleTest_SOURCE_DIR:STATIC=/home/patch/src/piddle/test - -//Name of the computer/site where compile is being run -SITE:STRING=patchbox - -//Path to the SLURM sbatch executable -SLURM_SBATCH_COMMAND:FILEPATH=SLURM_SBATCH_COMMAND-NOTFOUND - -//Path to the SLURM srun executable -SLURM_SRUN_COMMAND:FILEPATH=SLURM_SRUN_COMMAND-NOTFOUND - -//Path to a program. -SVNCOMMAND:FILEPATH=/usr/bin/svn - -//Path to a library. -pkgcfg_lib_LILV_0_dl:FILEPATH=/usr/lib/arm-linux-gnueabihf/libdl.so - -//Path to a library. -pkgcfg_lib_LILV_0_lilv-0:FILEPATH=/usr/lib/arm-linux-gnueabihf/liblilv-0.so - -//Path to a library. -pkgcfg_lib_LILV_0_serd-0:FILEPATH=/usr/lib/arm-linux-gnueabihf/libserd-0.so - -//Path to a library. -pkgcfg_lib_LILV_0_sord-0:FILEPATH=/usr/lib/arm-linux-gnueabihf/libsord-0.so - -//Path to a library. -pkgcfg_lib_LILV_0_sratom-0:FILEPATH=/usr/lib/arm-linux-gnueabihf/libsratom-0.so - - -######################## -# INTERNAL cache entries -######################## - -//ADVANCED property for variable: BZRCOMMAND -BZRCOMMAND-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_ADDR2LINE -CMAKE_ADDR2LINE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_AR -CMAKE_AR-ADVANCED:INTERNAL=1 -//This is the directory where this CMakeCache.txt was created -CMAKE_CACHEFILE_DIR:INTERNAL=/home/patch/src/piddle -//Major version of cmake used to create the current loaded cache -CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3 -//Minor version of cmake used to create the current loaded cache -CMAKE_CACHE_MINOR_VERSION:INTERNAL=19 -//Patch version of cmake used to create the current loaded cache -CMAKE_CACHE_PATCH_VERSION:INTERNAL=4268486 -//ADVANCED property for variable: CMAKE_COLOR_MAKEFILE -CMAKE_COLOR_MAKEFILE-ADVANCED:INTERNAL=1 -//Path to CMake executable. -CMAKE_COMMAND:INTERNAL=/usr/local/bin/cmake -//Path to cpack program executable. -CMAKE_CPACK_COMMAND:INTERNAL=/usr/local/bin/cpack -//ADVANCED property for variable: CMAKE_CTEST_COMMAND -CMAKE_CTEST_COMMAND-ADVANCED:INTERNAL=1 -//Path to ctest program executable. -CMAKE_CTEST_COMMAND:INTERNAL=/usr/local/bin/ctest -//ADVANCED property for variable: CMAKE_CXX_COMPILER -CMAKE_CXX_COMPILER-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_CXX_COMPILER_AR -CMAKE_CXX_COMPILER_AR-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_CXX_COMPILER_RANLIB -CMAKE_CXX_COMPILER_RANLIB-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_CXX_FLAGS -CMAKE_CXX_FLAGS-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_CXX_FLAGS_DEBUG -CMAKE_CXX_FLAGS_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_CXX_FLAGS_MINSIZEREL -CMAKE_CXX_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELEASE -CMAKE_CXX_FLAGS_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELWITHDEBINFO -CMAKE_CXX_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_C_COMPILER -CMAKE_C_COMPILER-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_C_COMPILER_AR -CMAKE_C_COMPILER_AR-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_C_COMPILER_RANLIB -CMAKE_C_COMPILER_RANLIB-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_C_FLAGS -CMAKE_C_FLAGS-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_C_FLAGS_DEBUG -CMAKE_C_FLAGS_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_C_FLAGS_MINSIZEREL -CMAKE_C_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_C_FLAGS_RELEASE -CMAKE_C_FLAGS_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_C_FLAGS_RELWITHDEBINFO -CMAKE_C_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_DLLTOOL -CMAKE_DLLTOOL-ADVANCED:INTERNAL=1 -//Executable file format -CMAKE_EXECUTABLE_FORMAT:INTERNAL=ELF -//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS -CMAKE_EXE_LINKER_FLAGS-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_DEBUG -CMAKE_EXE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_MINSIZEREL -CMAKE_EXE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELEASE -CMAKE_EXE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO -CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_EXPORT_COMPILE_COMMANDS -CMAKE_EXPORT_COMPILE_COMMANDS-ADVANCED:INTERNAL=1 -//Name of external makefile project generator. -CMAKE_EXTRA_GENERATOR:INTERNAL= -//Name of generator. -CMAKE_GENERATOR:INTERNAL=Unix Makefiles -//Generator instance identifier. -CMAKE_GENERATOR_INSTANCE:INTERNAL= -//Name of generator platform. -CMAKE_GENERATOR_PLATFORM:INTERNAL= -//Name of generator toolset. -CMAKE_GENERATOR_TOOLSET:INTERNAL= -//Source directory with the top level CMakeLists.txt file for this -// project -CMAKE_HOME_DIRECTORY:INTERNAL=/home/patch/src/piddle/test -//Install .so files without execute permission. -CMAKE_INSTALL_SO_NO_EXE:INTERNAL=1 -//ADVANCED property for variable: CMAKE_LINKER -CMAKE_LINKER-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_MAKE_PROGRAM -CMAKE_MAKE_PROGRAM-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS -CMAKE_MODULE_LINKER_FLAGS-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_DEBUG -CMAKE_MODULE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL -CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELEASE -CMAKE_MODULE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO -CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_NM -CMAKE_NM-ADVANCED:INTERNAL=1 -//number of local generators -CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1 -//ADVANCED property for variable: CMAKE_OBJCOPY -CMAKE_OBJCOPY-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_OBJDUMP -CMAKE_OBJDUMP-ADVANCED:INTERNAL=1 -//Platform information initialized -CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_RANLIB -CMAKE_RANLIB-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_READELF -CMAKE_READELF-ADVANCED:INTERNAL=1 -//Path to CMake installation. -CMAKE_ROOT:INTERNAL=/usr/local/share/cmake-3.19 -//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS -CMAKE_SHARED_LINKER_FLAGS-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_DEBUG -CMAKE_SHARED_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL -CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELEASE -CMAKE_SHARED_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO -CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_SKIP_INSTALL_RPATH -CMAKE_SKIP_INSTALL_RPATH-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_SKIP_RPATH -CMAKE_SKIP_RPATH-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS -CMAKE_STATIC_LINKER_FLAGS-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_DEBUG -CMAKE_STATIC_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL -CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELEASE -CMAKE_STATIC_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO -CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_STRIP -CMAKE_STRIP-ADVANCED:INTERNAL=1 -//uname command -CMAKE_UNAME:INTERNAL=/usr/bin/uname -//ADVANCED property for variable: CMAKE_VERBOSE_MAKEFILE -CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: COVERAGE_COMMAND -COVERAGE_COMMAND-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: COVERAGE_EXTRA_FLAGS -COVERAGE_EXTRA_FLAGS-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CTEST_SUBMIT_RETRY_COUNT -CTEST_SUBMIT_RETRY_COUNT-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CTEST_SUBMIT_RETRY_DELAY -CTEST_SUBMIT_RETRY_DELAY-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CVSCOMMAND -CVSCOMMAND-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CVS_UPDATE_OPTIONS -CVS_UPDATE_OPTIONS-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: DART_TESTING_TIMEOUT -DART_TESTING_TIMEOUT-ADVANCED:INTERNAL=1 -//Details about finding PkgConfig -FIND_PACKAGE_MESSAGE_DETAILS_PkgConfig:INTERNAL=[/usr/bin/pkg-config][v0.29()] -//ADVANCED property for variable: GITCOMMAND -GITCOMMAND-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: HGCOMMAND -HGCOMMAND-ADVANCED:INTERNAL=1 -LILV_0_CFLAGS:INTERNAL=-I/usr/local/include;-I/usr/include/lilv-0;-I/usr/include/sratom-0;-I/usr/include/sord-0;-I/usr/include/serd-0 -LILV_0_CFLAGS_I:INTERNAL= -LILV_0_CFLAGS_OTHER:INTERNAL= -LILV_0_FOUND:INTERNAL=1 -LILV_0_INCLUDEDIR:INTERNAL=/usr/include -LILV_0_INCLUDE_DIRS:INTERNAL=/usr/local/include;/usr/include/lilv-0;/usr/include/sratom-0;/usr/include/sord-0;/usr/include/serd-0 -LILV_0_LDFLAGS:INTERNAL=-llilv-0;-ldl;-lsratom-0;-lsord-0;-lserd-0 -LILV_0_LDFLAGS_OTHER:INTERNAL= -LILV_0_LIBDIR:INTERNAL=/usr/lib/arm-linux-gnueabihf -LILV_0_LIBRARIES:INTERNAL=lilv-0;dl;sratom-0;sord-0;serd-0 -LILV_0_LIBRARY_DIRS:INTERNAL= -LILV_0_LIBS:INTERNAL= -LILV_0_LIBS_L:INTERNAL= -LILV_0_LIBS_OTHER:INTERNAL= -LILV_0_LIBS_PATHS:INTERNAL= -LILV_0_MODULE_NAME:INTERNAL=lilv-0 -LILV_0_PREFIX:INTERNAL=/usr -LILV_0_STATIC_CFLAGS:INTERNAL=-I/usr/local/include;-I/usr/include/lilv-0;-I/usr/include/sratom-0;-I/usr/include/sord-0;-I/usr/include/serd-0 -LILV_0_STATIC_CFLAGS_I:INTERNAL= -LILV_0_STATIC_CFLAGS_OTHER:INTERNAL= -LILV_0_STATIC_INCLUDE_DIRS:INTERNAL=/usr/local/include;/usr/include/lilv-0;/usr/include/sratom-0;/usr/include/sord-0;/usr/include/serd-0 -LILV_0_STATIC_LDFLAGS:INTERNAL=-llilv-0;-ldl;-lsratom-0;-lsord-0;-lserd-0;-lm -LILV_0_STATIC_LDFLAGS_OTHER:INTERNAL= -LILV_0_STATIC_LIBDIR:INTERNAL= -LILV_0_STATIC_LIBRARIES:INTERNAL=lilv-0;dl;sratom-0;sord-0;serd-0;m -LILV_0_STATIC_LIBRARY_DIRS:INTERNAL= -LILV_0_STATIC_LIBS:INTERNAL= -LILV_0_STATIC_LIBS_L:INTERNAL= -LILV_0_STATIC_LIBS_OTHER:INTERNAL= -LILV_0_STATIC_LIBS_PATHS:INTERNAL= -LILV_0_VERSION:INTERNAL=0.24.2 -LILV_0_lilv-0_INCLUDEDIR:INTERNAL= -LILV_0_lilv-0_LIBDIR:INTERNAL= -LILV_0_lilv-0_PREFIX:INTERNAL= -LILV_0_lilv-0_VERSION:INTERNAL= -//ADVANCED property for variable: MAKECOMMAND -MAKECOMMAND-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: MEMORYCHECK_COMMAND -MEMORYCHECK_COMMAND-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: MEMORYCHECK_SUPPRESSIONS_FILE -MEMORYCHECK_SUPPRESSIONS_FILE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: P4COMMAND -P4COMMAND-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: PKG_CONFIG_EXECUTABLE -PKG_CONFIG_EXECUTABLE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: SITE -SITE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: SLURM_SBATCH_COMMAND -SLURM_SBATCH_COMMAND-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: SLURM_SRUN_COMMAND -SLURM_SRUN_COMMAND-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: SVNCOMMAND -SVNCOMMAND-ADVANCED:INTERNAL=1 -__pkg_config_arguments_LILV_0:INTERNAL=lilv-0 -__pkg_config_checked_LILV_0:INTERNAL=1 -//ADVANCED property for variable: pkgcfg_lib_LILV_0_dl -pkgcfg_lib_LILV_0_dl-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: pkgcfg_lib_LILV_0_lilv-0 -pkgcfg_lib_LILV_0_lilv-0-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: pkgcfg_lib_LILV_0_serd-0 -pkgcfg_lib_LILV_0_serd-0-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: pkgcfg_lib_LILV_0_sord-0 -pkgcfg_lib_LILV_0_sord-0-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: pkgcfg_lib_LILV_0_sratom-0 -pkgcfg_lib_LILV_0_sratom-0-ADVANCED:INTERNAL=1 -prefix_result:INTERNAL=/usr/lib/arm-linux-gnueabihf - diff --git a/react/CMakeLists.txt b/react/CMakeLists.txt index 1e4eb30..c4f69ae 100644 --- a/react/CMakeLists.txt +++ b/react/CMakeLists.txt @@ -5,20 +5,22 @@ find_program(NPM_COMMAND npm) if (CMAKE_BUILD_TYPE MATCHES Debug) set(BUILD_REACT "echo") + set(BUILD_REACT_ARGS Skipping react debug build) else() set(BUILD_REACT ${NPM_COMMAND}) + set(BUILD_REACT_ARGS run build) endif() set (BUILD_DIRECTORY ${PROJECT_SOURCE_DIR}/build/react/build) -message(STATUS "React Build...") add_custom_command( OUTPUT ${BUILD_DIRECTORY}/index.html COMMAND ${BUILD_REACT} - ARGS run build + ARGS ${BUILD_REACT_ARGS} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/react DEPENDS + src/WifiChannel.tsx src/JackServerSettingsDialog.tsx src/JackServerSettings.tsx @@ -74,7 +76,13 @@ add_custom_command( src/Utility.tsx src/VuMeter.tsx src/PluginPreset.tsx + src/XxxSnippet.tsx + src/WifiConfigSettings.tsx + src/WifiConfigDialog.tsx + src/NoChangePassword.tsx + src/XxxSnippet.tsx + public/iso_codes.json public/logo512.png public/index.html public/manifest.json diff --git a/react/package.json b/react/package.json index c86e3cf..f41ec12 100644 --- a/react/package.json +++ b/react/package.json @@ -20,7 +20,7 @@ }, "scripts": { "start": "react-scripts start", - "build": "BUILD_PATH='../../build/react/src/build' react-scripts build", + "build": "BUILD_PATH='../build/react/build' react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, diff --git a/react/public/iso_codes.json b/react/public/iso_codes.json new file mode 100644 index 0000000..0e970af --- /dev/null +++ b/react/public/iso_codes.json @@ -0,0 +1,248 @@ +{ +"AF": "Afghanistan", +"AX": "Aland Islands", +"AL": "Albania", +"DZ": "Algeria", +"AS": "American Samoa", +"AD": "Andorra", +"AO": "Angola", +"AI": "Anguilla", +"AQ": "Antarctica", +"AG": "Antigua And Barbuda", +"AR": "Argentina", +"AM": "Armenia", +"AW": "Aruba", +"AU": "Australia", +"AT": "Austria", +"AZ": "Azerbaijan", +"BS": "Bahamas", +"BH": "Bahrain", +"BD": "Bangladesh", +"BB": "Barbados", +"BY": "Belarus", +"BE": "Belgium", +"BZ": "Belize", +"BJ": "Benin", +"BM": "Bermuda", +"BT": "Bhutan", +"BO": "Bolivia", +"BA": "Bosnia And Herzegovina", +"BW": "Botswana", +"BV": "Bouvet Island", +"BR": "Brazil", +"IO": "British Indian Ocean Territory", +"BN": "Brunei Darussalam", +"BG": "Bulgaria", +"BF": "Burkina Faso", +"BI": "Burundi", +"KH": "Cambodia", +"CM": "Cameroon", +"CA": "Canada", +"CV": "Cape Verde", +"KY": "Cayman Islands", +"CF": "Central African Republic", +"TD": "Chad", +"CL": "Chile", +"CN": "China", +"CX": "Christmas Island", +"CC": "Cocos (Keeling) Islands", +"CO": "Colombia", +"KM": "Comoros", +"CG": "Congo", +"CD": "Congo, Democratic Republic", +"CK": "Cook Islands", +"CR": "Costa Rica", +"CI": "Cote D\"Ivoire", +"HR": "Croatia", +"CU": "Cuba", +"CY": "Cyprus", +"CZ": "Czech Republic", +"DK": "Denmark", +"DJ": "Djibouti", +"DM": "Dominica", +"DO": "Dominican Republic", +"EC": "Ecuador", +"EG": "Egypt", +"SV": "El Salvador", +"GQ": "Equatorial Guinea", +"ER": "Eritrea", +"EE": "Estonia", +"ET": "Ethiopia", +"FK": "Falkland Islands (Malvinas)", +"FO": "Faroe Islands", +"FJ": "Fiji", +"FI": "Finland", +"FR": "France", +"GF": "French Guiana", +"PF": "French Polynesia", +"TF": "French Southern Territories", +"GA": "Gabon", +"GM": "Gambia", +"GE": "Georgia", +"DE": "Germany", +"GH": "Ghana", +"GI": "Gibraltar", +"GR": "Greece", +"GL": "Greenland", +"GD": "Grenada", +"GP": "Guadeloupe", +"GU": "Guam", +"GT": "Guatemala", +"GG": "Guernsey", +"GN": "Guinea", +"GW": "Guinea-Bissau", +"GY": "Guyana", +"HT": "Haiti", +"HM": "Heard Island & Mcdonald Islands", +"VA": "Holy See (Vatican City State)", +"HN": "Honduras", +"HK": "Hong Kong", +"HU": "Hungary", +"IS": "Iceland", +"IN": "India", +"ID": "Indonesia", +"IR": "Iran, Islamic Republic Of", +"IQ": "Iraq", +"IE": "Ireland", +"IM": "Isle Of Man", +"IL": "Israel", +"IT": "Italy", +"JM": "Jamaica", +"JP": "Japan", +"JE": "Jersey", +"JO": "Jordan", +"KZ": "Kazakhstan", +"KE": "Kenya", +"KI": "Kiribati", +"KR": "Korea", +"KP": "North Korea", +"KW": "Kuwait", +"KG": "Kyrgyzstan", +"LA": "Lao People\"s Democratic Republic", +"LV": "Latvia", +"LB": "Lebanon", +"LS": "Lesotho", +"LR": "Liberia", +"LY": "Libyan Arab Jamahiriya", +"LI": "Liechtenstein", +"LT": "Lithuania", +"LU": "Luxembourg", +"MO": "Macao", +"MK": "Macedonia", +"MG": "Madagascar", +"MW": "Malawi", +"MY": "Malaysia", +"MV": "Maldives", +"ML": "Mali", +"MT": "Malta", +"MH": "Marshall Islands", +"MQ": "Martinique", +"MR": "Mauritania", +"MU": "Mauritius", +"YT": "Mayotte", +"MX": "Mexico", +"FM": "Micronesia, Federated States Of", +"MD": "Moldova", +"MC": "Monaco", +"MN": "Mongolia", +"ME": "Montenegro", +"MS": "Montserrat", +"MA": "Morocco", +"MZ": "Mozambique", +"MM": "Myanmar", +"NA": "Namibia", +"NR": "Nauru", +"NP": "Nepal", +"NL": "Netherlands", +"AN": "Netherlands Antilles", +"NC": "New Caledonia", +"NZ": "New Zealand", +"NI": "Nicaragua", +"NE": "Niger", +"NG": "Nigeria", +"NU": "Niue", +"NF": "Norfolk Island", +"MP": "Northern Mariana Islands", +"NO": "Norway", +"OM": "Oman", +"PK": "Pakistan", +"PW": "Palau", +"PS": "Palestinian Territory, Occupied", +"PA": "Panama", +"PG": "Papua New Guinea", +"PY": "Paraguay", +"PE": "Peru", +"PH": "Philippines", +"PN": "Pitcairn", +"PL": "Poland", +"PT": "Portugal", +"PR": "Puerto Rico", +"QA": "Qatar", +"RE": "Reunion", +"RO": "Romania", +"RU": "Russian Federation", +"RW": "Rwanda", +"BL": "Saint Barthelemy", +"SH": "Saint Helena", +"KN": "Saint Kitts And Nevis", +"LC": "Saint Lucia", +"MF": "Saint Martin", +"PM": "Saint Pierre And Miquelon", +"VC": "Saint Vincent And Grenadines", +"WS": "Samoa", +"SM": "San Marino", +"ST": "Sao Tome And Principe", +"SA": "Saudi Arabia", +"SN": "Senegal", +"RS": "Serbia", +"SC": "Seychelles", +"SL": "Sierra Leone", +"SG": "Singapore", +"SK": "Slovakia", +"SI": "Slovenia", +"SB": "Solomon Islands", +"SO": "Somalia", +"ZA": "South Africa", +"GS": "South Georgia And Sandwich Isl.", +"ES": "Spain", +"LK": "Sri Lanka", +"SD": "Sudan", +"SR": "Suriname", +"SJ": "Svalbard And Jan Mayen", +"SZ": "Swaziland", +"SE": "Sweden", +"CH": "Switzerland", +"SY": "Syrian Arab Republic", +"TW": "Taiwan", +"TJ": "Tajikistan", +"TZ": "Tanzania", +"TH": "Thailand", +"TL": "Timor-Leste", +"TG": "Togo", +"TK": "Tokelau", +"TO": "Tonga", +"TT": "Trinidad And Tobago", +"TN": "Tunisia", +"TR": "Turkey", +"TM": "Turkmenistan", +"TC": "Turks And Caicos Islands", +"TV": "Tuvalu", +"UG": "Uganda", +"UA": "Ukraine", +"AE": "United Arab Emirates", +"GB": "United Kingdom", +"US": "United States", +"UM": "United States Outlying Islands", +"UY": "Uruguay", +"UZ": "Uzbekistan", +"VU": "Vanuatu", +"VE": "Venezuela", +"VN": "Vietnam", +"VG": "Virgin Islands, British", +"VI": "Virgin Islands, U.S.", +"WF": "Wallis And Futuna", +"EH": "Western Sahara", +"YE": "Yemen", +"ZM": "Zambia", +"ZW": "Zimbabwe" +} \ No newline at end of file diff --git a/react/public/var/config.json b/react/public/var/config.json index c1902a0..a16e5a4 100644 --- a/react/public/var/config.json +++ b/react/public/var/config.json @@ -1,5 +1,5 @@ { - "socket_server_port": 80, + "socket_server_port": 8080, "socket_server_address": "*", "max_upload_size": 1048576, diff --git a/react/src/App.tsx b/react/src/App.tsx index a41a438..754b717 100644 --- a/react/src/App.tsx +++ b/react/src/App.tsx @@ -646,7 +646,7 @@ const App = withStyles(appStyles)(class extends ResizeResponsiveComponent )} - { this.hideDrawer(); }} > Banks diff --git a/react/src/DialogEx.tsx b/react/src/DialogEx.tsx new file mode 100644 index 0000000..8fee909 --- /dev/null +++ b/react/src/DialogEx.tsx @@ -0,0 +1,119 @@ +import React from 'react'; + +import Dialog, {DialogProps} from '@material-ui/core/Dialog'; + + +interface DialogExProps extends DialogProps { + tag: string; +} + +interface DialogExState { + +} + +class DialogEx extends React.Component { + constructor(props: DialogExProps) + { + super(props); + this.state = { + + }; + this.handlePopState = this.handlePopState.bind(this); + + } + + mounted: boolean = false; + + hasHooks: boolean = false; + + stateWasPopped: boolean = false; + handlePopState(e: any): any { + this.stateWasPopped = true; + let shouldClose = (!e.state || !e.state[this.props.tag]); + if (shouldClose) + { + if (this.props.onClose) + { + this.props.onClose(e,"backdropClick"); + } + } + } + + updateHooks() : void { + let wantHooks = this.mounted && this.props.open; + if (wantHooks !== this.hasHooks) + { + this.hasHooks = wantHooks; + + if (this.hasHooks) + { + this.stateWasPopped = false; + window.addEventListener("popstate",this.handlePopState); + // eslint-disable-next-line no-restricted-globals + let state = history.state; + if (!state) + { + state = {}; + } + state[this.props.tag] = true; + + // eslint-disable-next-line no-restricted-globals + history.pushState( + state, + "", + "#" + this.props.tag + ); + } else { + window.removeEventListener("popstate",this.handlePopState); + if (!this.stateWasPopped) + { + // eslint-disable-next-line no-restricted-globals + history.back(); + } + } + + } + } + onWindowSizeChanged(width: number, height: number): void + { + this.setState({fullScreen: height < 200}) + } + + + componentDidMount() + { + if (super.componentDidMount) + { + super.componentDidMount(); + } + this.mounted = true; + this.updateHooks(); + } + componentWillUnmount() + { + if (super.componentWillUnmount) + { + super.componentWillUnmount(); + } + this.mounted = false; + this.updateHooks(); + } + + componentDidUpdate() + { + this.updateHooks(); + } + + + render() { + let { tag, ...extra} = this.props; + return ( + + {this.props.children} + + ); + } + +}; + +export default DialogEx; \ No newline at end of file diff --git a/react/src/MidiBindingsDialog.tsx b/react/src/MidiBindingsDialog.tsx index a777929..1338e0d 100644 --- a/react/src/MidiBindingsDialog.tsx +++ b/react/src/MidiBindingsDialog.tsx @@ -187,7 +187,7 @@ export const MidiBindingDialog = this.updateHooks(); } componentWillUnmount() { - super.componentDidMount(); + super.componentWillUnmount(); this.mounted = false; this.updateHooks(); diff --git a/react/src/NoChangePassword.tsx b/react/src/NoChangePassword.tsx new file mode 100644 index 0000000..f36eb15 --- /dev/null +++ b/react/src/NoChangePassword.tsx @@ -0,0 +1,118 @@ +import React from 'react'; +import InputAdornment from '@material-ui/core/InputAdornment'; +import IconButton from '@material-ui/core/IconButton'; +import FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import InputLabel from '@material-ui/core/InputLabel'; +import Input from '@material-ui/core/Input'; + +import Visibility from '@material-ui/icons/Visibility'; +import VisibilityOff from '@material-ui/icons/VisibilityOff'; + + + +interface NoChangePasswordProps { + hasPassword: boolean; + label: string; + defaultValue: string; + onPasswordChange: (text: string) => void; + disabled?: boolean; + helperText?: string; + error?: boolean; +}; + +interface NoChangePasswordState { + focused: boolean; + passwordText: string; + showPassword: boolean; +}; + +class NoChangePassword extends React.Component { + refText: React.Ref; + + constructor(props: NoChangePasswordProps) { + super(props); + this.refText = React.createRef(); + this.state = { + focused: false, + showPassword: false, + passwordText: this.props.defaultValue as string + }; + } + + handleShowPassword() { + this.setState({ + showPassword: !this.state.showPassword + }); + } + handleChange(e: React.ChangeEvent) { + this.textChanged = true; + this.props.onPasswordChange(e.target.value); + } + + textChanged: boolean = false; + handleFocus(e: React.FocusEvent) { + console.log("onFocus"); + if (!this.state.focused) { + this.textChanged = false; + e.target.value = this.state.passwordText; + } + e.currentTarget.removeAttribute("readonly"); + this.setState({ focused: true }); + } + handleBlur(e: React.FocusEvent) { + console.log("onBlur"); + let text = e.target.value as string; + this.setState({ focused: false, passwordText: text }); + this.props.onPasswordChange(e.target.value); + this.textChanged = false; + if (text.length === 0 && this.props.hasPassword) + { + e.target.value = "(Unchanged)"; + } + } + + render() { + console.log("Render focus: " + this.state.focused) + let showUnchanged = (this.state.passwordText.length === 0) && this.props.hasPassword && (!this.state.focused); + let thisDefaultValue = showUnchanged ? "(Unchanged)" : this.props.defaultValue; + + let isText = showUnchanged || this.state.showPassword; + + + return ( + + {this.props.label} + this.handleChange(e)} + value={thisDefaultValue} + onFocus={(e) => this.handleFocus(e)} + onBlur={(e) => this.handleBlur(e)} + type={isText ? 'text' : 'password'} + style={ { color: showUnchanged? '#888': '#000'} } + + endAdornment={ + + this.handleShowPassword()} + onMouseDown={(e) => e.preventDefault()} + > + {this.state.showPassword ? : } + + + } + /> + { this.props.helperText} + + ); + } +}; + +export default NoChangePassword; diff --git a/react/src/PedalBoardView.tsx b/react/src/PedalBoardView.tsx index 8a08f91..7b1a441 100644 --- a/react/src/PedalBoardView.tsx +++ b/react/src/PedalBoardView.tsx @@ -117,7 +117,7 @@ const pedalBoardStyles = (theme: Theme) => createStyles({ marginBottom: (CELL_HEIGHT - FRAME_SIZE) / 2, width: FRAME_SIZE, height: FRAME_SIZE, - border: "1pt black solid", + border: "1pt #666 solid", borderRadius: 6 }, pedalIcon: { diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx index 51c0d2d..befeffe 100644 --- a/react/src/PiPedalModel.tsx +++ b/react/src/PiPedalModel.tsx @@ -13,6 +13,8 @@ import JackHostStatus from './JackHostStatus'; import JackServerSettings from './JackServerSettings'; import MidiBinding from './MidiBinding'; import PluginPreset from './PluginPreset'; +import WifiConfigSettings from './WifiConfigSettings'; +import WifiChannel from './WifiChannel'; export enum State { @@ -235,6 +237,7 @@ interface ControlChangedBody { export interface PiPedalModel { clientId: number; + countryCodes: Object; serverVersion?: PiPedalVersion; errorMessage: ObservableProperty; alertMessage: ObservableProperty; @@ -245,6 +248,7 @@ export interface PiPedalModel { jackSettings: ObservableProperty; banks: ObservableProperty; jackServerSettings: ObservableProperty; + wifiConfigSettings: ObservableProperty; presets: ObservableProperty; @@ -320,13 +324,17 @@ export interface PiPedalModel { uploadPreset(file: File, uploadAfter: number): Promise; + setWifiConfigSettings(wifiConfigSettings: WifiConfigSettings): Promise; + + getWifiChannels(countryIso3661: string): Promise; + }; class PiPedalModelImpl implements PiPedalModel { clientId: number = -1; serverVersion?: PiPedalVersion; - + countryCodes: Object = {}; socketServerUrl: string = ""; varServerUrl: string = ""; lv2Path: string = ""; @@ -346,6 +354,8 @@ class PiPedalModelImpl implements PiPedalModel { jackServerSettings: ObservableProperty = new ObservableProperty(new JackServerSettings()); + wifiConfigSettings: ObservableProperty = new ObservableProperty(new WifiConfigSettings()); + presets: ObservableProperty = new ObservableProperty ( @@ -459,6 +469,9 @@ class PiPedalModelImpl implements PiPedalModel { } else if (message === "onJackServerSettingsChanged") { let jackServerSettings = new JackServerSettings().deserialize(body); this.jackServerSettings.set(jackServerSettings); + } else if (message === "onWifiConfigSettingsChanged") { + let wifiConfigSettings = new WifiConfigSettings().deserialize(body); + this.wifiConfigSettings.set(wifiConfigSettings); } else if (message === "onBanksChanged") { let banks = new BankIndex().deserialize(body); this.banks.set(banks); @@ -537,6 +550,11 @@ class PiPedalModelImpl implements PiPedalModel { .then(data => { this.pedalBoard.set(new PedalBoard().deserialize(data)); + return this.getWebSocket().request("getWifiConfigSettings"); + }) + .then(data => { + this.wifiConfigSettings.set(new WifiConfigSettings().deserialize(data)); + return this.getWebSocket().request("getJackServerSettings"); }) .then(data => { @@ -573,7 +591,7 @@ class PiPedalModelImpl implements PiPedalModel { }) } makeSocketServerUrl(hostName: string, port: number): string { - return "ws://" + hostName + ":" + port + "/piddle"; + return "ws://" + hostName + ":" + port + "/pipedal"; } makeVarServerUrl(protocol: string, hostName: string, port: number): string { @@ -587,11 +605,12 @@ class PiPedalModelImpl implements PiPedalModel { const myRequest = new Request(this.varRequest('config.json')); return fetch(myRequest) .then( - response => response.json() - + (response) => + { + return response.json(); + } ) .then(data => { - console.log("Got config"); if (data.max_upload_size) { this.maxUploadSize = data.max_upload_size; @@ -617,6 +636,15 @@ class PiPedalModelImpl implements PiPedalModel { return this.webSocket.connect(); }) .then(() => { + const isoRequest = new Request('iso_codes.json'); + return fetch(isoRequest); + }) + .then((response) => { + return response.json(); + }) + .then((countryCodes) => { + this.countryCodes = countryCodes as Object; + return this.getWebSocket().request("hello"); }) .then((clientId) => { @@ -652,7 +680,12 @@ class PiPedalModelImpl implements PiPedalModel { }) .then((data) => { this.presets.set(new PresetIndex().deserialize(data)); - + return this.getWebSocket().request("getWifiConfigSettings"); + }) + .then(data => { + this.wifiConfigSettings.set(new WifiConfigSettings().deserialize(data)); + + return this.getWebSocket().request("getJackServerSettings"); }) .then(data => { @@ -1427,6 +1460,75 @@ class PiPedalModelImpl implements PiPedalModel { }); return result; } + setWifiConfigSettings(wifiConfigSettings: WifiConfigSettings): Promise + { + let result = new Promise((resolve,reject) =>{ + let oldSettings = this.wifiConfigSettings.get(); + wifiConfigSettings = wifiConfigSettings.clone(); + + if ((!oldSettings.enable) && (!wifiConfigSettings.enable)) + { + // no effective change. + resolve(); + return; + } + let countryCodeChanged = wifiConfigSettings.countryCode !== oldSettings.countryCode; + if (!wifiConfigSettings.enable) + { + wifiConfigSettings.hasPassword = false; + wifiConfigSettings.hotspotName = oldSettings.hotspotName; + } else { + if (!wifiConfigSettings.hasPassword) { + if (wifiConfigSettings.hotspotName === oldSettings.hotspotName && !countryCodeChanged) { + // no effective change. + resolve(); + return; + } + } + } + // save a version for the server (potentially carrying a password) + let serverConfigSettings = wifiConfigSettings.clone(); + + + + // notify the server. + let ws = this.webSocket; + if (!ws) + { + reject("Not connected."); + return; + } + ws.request( + "setWifiConfigSettings", + serverConfigSettings + ) + .then(()=> { + resolve(); + }) + .catch((err) =>{ + reject(err); + }); + + }); + return result; + } + + getWifiChannels(countryIso3661: string): Promise + { + let result = new Promise((resolve,reject) => { + if (!this.webSocket) { + reject("Connection closed."); + return; + } + this.webSocket.request("getWifiChannels",countryIso3661) + .then((data) => { + resolve(WifiChannel.deserialize_array(data)); + }) + .catch((err)=> reject(err)); + }); + return result; + } + }; diff --git a/react/src/PluginControlView.tsx b/react/src/PluginControlView.tsx index 4da3a83..9efcc3f 100644 --- a/react/src/PluginControlView.tsx +++ b/react/src/PluginControlView.tsx @@ -100,12 +100,12 @@ const styles = (theme: Theme) => createStyles({ marginRight: 8, marginBottom: 12, position: "relative", - paddingLeft: 0, + paddingLeft: 3, paddingRight: 0, paddingTop: 0, paddingBottom: 0, border: "2pt #AAA solid", - borderRadius: 10, + borderRadius: 4, elevation: 12, display: "flex", flexDirection: "row", flexWrap: "wrap", diff --git a/react/src/RenameDialog.tsx b/react/src/RenameDialog.tsx index 880bf82..87e4cbe 100644 --- a/react/src/RenameDialog.tsx +++ b/react/src/RenameDialog.tsx @@ -95,7 +95,7 @@ export default class RenameDialog extends ResizeResponsiveComponent { + }) + .catch((err) => { + this.model.showAlert(err); + }); + } + + handleWifiConfigSettingsChanged(): void { + this.setState( + { + wifiConfigSettings: this.model.wifiConfigSettings.get() + } + ) + } handleJackSettingsChanged(): void { this.setState({ jackSettings: this.model.jackSettings.get() @@ -177,6 +202,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( this.model.jackSettings.addOnChangedHandler(this.handleJackSettingsChanged); this.model.jackConfiguration.addOnChangedHandler(this.handleJackConfigurationChanged); this.model.jackServerSettings.addOnChangedHandler(this.handleJackServerSettingsChanged); + this.model.wifiConfigSettings.addOnChangedHandler(this.handleWifiConfigSettingsChanged); this.model.getJackStatus() .then((jackStatus) => this.setState( @@ -193,7 +219,8 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( this.handleJackConfigurationChanged(); this.handleJackSettingsChanged(); this.handleJackServerSettingsChanged(); - this.timerHandle = setInterval(() => this.tick(), 1000); + this.handleWifiConfigSettingsChanged(); + // xxx UNCOMMENT ME! this.timerHandle = setInterval(() => this.tick(), 1000); } else { if (this.timerHandle) { clearInterval(this.timerHandle); @@ -201,6 +228,8 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( this.model.jackConfiguration.removeOnChangedHandler(this.handleJackConfigurationChanged); this.model.jackSettings.removeOnChangedHandler(this.handleJackSettingsChanged); this.model.jackServerSettings.removeOnChangedHandler(this.handleJackServerSettingsChanged); + this.model.wifiConfigSettings.removeOnChangedHandler(this.handleWifiConfigSettingsChanged); + } } @@ -292,25 +321,33 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( if (ports.length === 1) return ports[0]; return ports.length + " channels"; } - + handleShowWifiConfigDialog() { + this.setState({ + showWifiConfigDialog: true + }); + } handleRestart() { + this.setState({ restarting: true }); this.model.restart() .then(() => { - this.setState({ restarting: true }); + // this.setState({ restarting: true }); }) .catch((error) => { this.model.showAlert(error); + this.setState({ restarting: false }); }); } handleShutdown() { + this.setState({ shuttingDown: true }); this.model.shutdown() .then(() => { this.setState({ shuttingDown: true }); }) .catch((error) => { this.model.showAlert(error); + this.setState({ shuttingDown: false }); }); } @@ -322,7 +359,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( let disableShutdown = this.state.shuttingDown || this.state.restarting; return ( - { this.props.onClose() }} TransitionComponent={Transition}>
@@ -428,6 +465,18 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
SYSTEM + this.handleShowWifiConfigDialog()} > + +
+ + Configure Wi-fi Hotspot + + {this.state.wifiConfigSettings.getSummaryText()} + + +
+
this.handleRestart()} > @@ -452,7 +501,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( ) : ( Shut down ) - } + }
@@ -483,8 +532,12 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( ) } + this.setState({showWifiConfigDialog: false})} + onOk={(wifiConfigSettings) => this.handleApplyWifiConfig(wifiConfigSettings)} + /> -
+ ); diff --git a/react/src/UploadDialog.tsx b/react/src/UploadDialog.tsx index 18ebaf3..c93f97b 100644 --- a/react/src/UploadDialog.tsx +++ b/react/src/UploadDialog.tsx @@ -93,7 +93,7 @@ export default class UploadDialog extends ResizeResponsiveComponent createStyles({ + pgraph: { + paddingBottom: 16 + } +}); + +const NBSP = "\u00A0"; + +export interface WifiConfigProps extends WithStyles { + open: boolean, + wifiConfigSettings: WifiConfigSettings, + onOk: (wifiConfigSettings: WifiConfigSettings) => void, + onClose: () => void +}; + +export interface WifiConfigState { + fullScreen: boolean; + showWifiWarningDialog: boolean; + wifiWarningGiven: boolean; + enabled: boolean; + name: string; + newPassword: string; + nameError: boolean; + nameErrorMessage: string; + passwordError: boolean; + passwordErrorMessage: string; + countryCode: string; + channel: string; + wifiChannels: WifiChannel[]; +}; + + +let validNameRegex = /^[a-zA-Z][a-zA-Z0-9_-]*$/ +function isValidName(text: string) +{ + return validNameRegex.test(text); +} + +const WifiConfigDialog = withStyles(styles, { withTheme: true })( + class extends ResizeResponsiveComponent { + + refName: React.RefObject; + refPassword: React.RefObject; + model: PiPedalModel; + + constructor(props: WifiConfigProps) { + super(props); + this.model = PiPedalModelFactory.getInstance(); + this.state = { + fullScreen: false, + enabled: this.props.wifiConfigSettings.enable, + name: this.props.wifiConfigSettings.hotspotName, + newPassword: "", + nameError: false, + nameErrorMessage: NBSP, + passwordError: false, + showWifiWarningDialog: false, + wifiWarningGiven: this.props.wifiConfigSettings.wifiWarningGiven, + passwordErrorMessage: NBSP, + countryCode: this.props.wifiConfigSettings.countryCode, + channel: this.props.wifiConfigSettings.channel, + wifiChannels: [] + }; + this.model.getWifiChannels(this.props.wifiConfigSettings.countryCode) + .then((wifiChannels_) => { + this.setState({wifiChannels: wifiChannels_}); + }).catch((err)=> {}); + + this.refName = React.createRef(); + this.refPassword = React.createRef(); + this.handlePopState = this.handlePopState.bind(this); + } + mounted: boolean = false; + + hasHooks: boolean = false; + + stateWasPopped: boolean = false; + + handleEnableChanged(e: any) { + this.setState({ enabled: e.target.checked }); + } + handlePopState(e: any): any { + this.stateWasPopped = true; + let shouldClose = (!e.state || !e.state.WifiConfig); + if (shouldClose) { + this.props.onClose(); + } + } + + + updateHooks(): void { + let wantHooks = this.mounted && this.props.open; + if (wantHooks !== this.hasHooks) { + this.hasHooks = wantHooks; + + if (this.hasHooks) { + this.stateWasPopped = false; + window.addEventListener("popstate", this.handlePopState); + // eslint-disable-next-line no-restricted-globals + let state = history.state; + if (!state) { + state = {}; + } + state.WifiConfig = true; + + // eslint-disable-next-line no-restricted-globals + history.pushState( + state, + "Wi-Fi Configuration", + "#WifiConfig" + ); + } else { + window.removeEventListener("popstate", this.handlePopState); + if (!this.stateWasPopped) { + // eslint-disable-next-line no-restricted-globals + history.back(); + } + } + + } + } + onWindowSizeChanged(width: number, height: number): void { + this.setState({ fullScreen: height < 200 }) + } + + + componentDidMount() { + super.componentDidMount(); + this.mounted = true; + this.updateHooks(); + } + componentWillUnmount() { + super.componentWillUnmount(); + this.mounted = false; + this.updateHooks(); + } + + componentDidUpdate(prevProps: WifiConfigProps) { + this.updateHooks(); + + if (this.props.open && !prevProps.open) + { + this.setState({ + enabled: this.props.wifiConfigSettings.enable, + name: this.props.wifiConfigSettings.hotspotName, + newPassword: "", + wifiWarningGiven: this.props.wifiConfigSettings.wifiWarningGiven, + countryCode: this.props.wifiConfigSettings.countryCode, + channel: this.props.wifiConfigSettings.channel + }); + + if (this.props.wifiConfigSettings.countryCode !== this.state.countryCode) + { + this.model.getWifiChannels(this.props.wifiConfigSettings.countryCode) + .then((wifiChannels) => { + this.setState({wifiChannels: wifiChannels}); + }) + .catch((error) => {}); + + } + + } + + } + + handleOk(wifiWarningGiven: boolean) { + let hasError = false; + if (this.state.enabled) + { + let name = this.state.name; + if (name.length === 0) { + this.setState({nameError: true, nameErrorMessage: "* Required"}); + hasError = true; + } else { + if (!isValidName(name)) + { + this.setState({nameError: true, nameErrorMessage: "Invalid name"}); + hasError = true; + } else if (name.length > 15) + { + this.setState({nameError: true, nameErrorMessage: "> 15 characters"}); + hasError = true; + } + } + } + if (this.state.enabled && (!this.props.wifiConfigSettings.hasPassword) && this.state.newPassword.length === 0) + { + this.setState({passwordError: true, passwordErrorMessage: "* Required"}); + hasError = true; + } + if (!hasError) + { + let wifiConfigSettings = new WifiConfigSettings(); + wifiConfigSettings.valid = true; + + wifiConfigSettings.enable = this.state.enabled; + wifiConfigSettings.hotspotName = this.state.name; + if (this.state.newPassword.length === 0 || !this.state.enabled) + { + wifiConfigSettings.hasPassword = false; + } else { + wifiConfigSettings.hasPassword = true; + wifiConfigSettings.password = this.state.newPassword; + } + + if (!this.props.wifiConfigSettings.wifiWarningGiven && !wifiWarningGiven) { + this.setState({showWifiWarningDialog: true}); + } else { + wifiConfigSettings.wifiWarningGiven = true; + this.props.onOk(wifiConfigSettings); + } + } + + } + handleChannelChange(e: any) + { + let value = e.target.value as string; + this.setState({channel: value }); + } + handleCountryChanged(e: any) + { + let value = e.target.value as string; + + this.setState({countryCode: value}); + this.model.getWifiChannels(value) + .then(wifiChannels => { + this.setState({wifiChannels: wifiChannels}); + }) + .catch((error)=> { }); + } + + render() { + let props = this.props; + let classes = this.props.classes; + let { open, onClose} = props; + + const handleClose = () => { + onClose(); + }; + + return ( + + { this.state.fullScreen && ( + Wi-fi Hotspot + )} + + + this.handleEnableChanged(e)} + color="secondary" + /> + )} + label="Enable" + /> + this.setState({name: e.target.value, nameError: false, nameErrorMessage: NBSP})} + inputRef={this.refName} + disabled={!this.state.enabled} + /> +
+ { this.setState({ newPassword: text, passwordError: false, passwordErrorMessage: NBSP }); }} + hasPassword={this.props.wifiConfigSettings.hasPassword} + label="WEP Passphrase" + error={this.state.passwordError} + helperText={this.state.passwordErrorMessage} + defaultValue={this.state.newPassword} + disabled={!this.state.enabled} + /> +
+
+ + Country + + + +
+
+ + Channel + + + +
+ +
+ + + + + + + + Enabling the Wi-Fi hotspot will disable regular Wi-Fi network access on the PiPedal device. Once + enabled, connect to the hotspot and launch http://172.24.1.1 or http://{this.state.name}.local to access the PiPedal web app again. + + + Are you sure you want to proceed? + + + + + + + +
+ ); + } +}); + +export default WifiConfigDialog; \ No newline at end of file diff --git a/react/src/WifiConfigSettings.tsx b/react/src/WifiConfigSettings.tsx new file mode 100644 index 0000000..9541bc7 --- /dev/null +++ b/react/src/WifiConfigSettings.tsx @@ -0,0 +1,45 @@ + + +export default class WifiConfigSettings { + deserialize(input: any) : WifiConfigSettings{ + this.valid = input.valid; + this.wifiWarningGiven = input.wifiWarningGiven; + this.rebootRequired = input.rebootRequired; + this.enable = input.enable; + this.hotspotName = input.hotspotName; + this.hasPassword = input.hasPassword; + this.password = input.password; + this.countryCode = input.countryCode; + this.channel = input.channel; + return this; + } + clone() : WifiConfigSettings { + return this.deserialize(this); + } + valid: boolean = true; + wifiWarningGiven: boolean = false; + enable: boolean = true; + hasPassword: boolean = false; + rebootRequired = false; + hotspotName: string = "pipedal"; + password: string = ""; + countryCode: string = "US"; + channel: string = "g6"; + + getSummaryText() { + let result: string; + if (!this.valid) { + result = "Not available."; + } else if (!this.enable) { + result = "Disabled."; + } else { + result = this.hotspotName; + } + if (this.rebootRequired) + { + result += " (Restart required)"; + } + return result; + } + +} \ No newline at end of file diff --git a/react/src/XxxSnippet.tsx b/react/src/XxxSnippet.tsx new file mode 100644 index 0000000..a2844ac --- /dev/null +++ b/react/src/XxxSnippet.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Theme, withStyles, WithStyles,createStyles } from '@material-ui/core/styles'; + + +const styles = (theme: Theme) => createStyles({ +}); + + +interface XxxProps extends WithStyles { + theme: Theme, +}; + +interface XxxState { + +}; + +const Xxx = withStyles(styles, { withTheme: true })( + + class extends React.Component { + constructor(props: XxxProps) + { + super(props); + this.state = { + + }; + } + + render() { + return ( +
+ ); + } + } +); + +export default Xxx; diff --git a/ruleOffet b/ruleOffet new file mode 100644 index 0000000..e69de29 diff --git a/src/BeastServer.cpp b/src/BeastServer.cpp index 3e29690..1c9065a 100644 --- a/src/BeastServer.cpp +++ b/src/BeastServer.cpp @@ -458,6 +458,9 @@ public: { this->ws_.close(websocket::close_reason()); } + virtual std::string getFromAddress() const { + return this->fromAddress; + } virtual void writeCallback(const std::string &text) { { diff --git a/src/BeastServer.hpp b/src/BeastServer.hpp index 9759c02..2f81d57 100644 --- a/src/BeastServer.hpp +++ b/src/BeastServer.hpp @@ -28,6 +28,7 @@ public: virtual void close() = 0; virtual void writeCallback(const std::string& text) = 0; + virtual std::string getFromAddress() const = 0; }; private: @@ -43,6 +44,7 @@ private: protected: virtual void onReceive(const std::string_view&text) = 0; public: + std::string getFromAddress() const { return writeCallback_->getFromAddress(); } void receive(const std::string_view&text) { onReceive(text); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ee275a2..42850d3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,13 +4,27 @@ cmake_minimum_required(VERSION 3.19.0) include(FindPkgConfig) +# Can't get the pkg_check to work. +# pkg_check_modules(LIBNL3 "nl-genl-3") +# if(!LIBNL3_FOUND) +# message(ERROR "libnl-genl-3-dev package not installed.") +# else() +# message(STATUS "LIBNL3 libraries: ${LIBNL3_LIBRARIES}") +# message(STATUS "LBNL3 includes: ${LIBNL3_INCLUDE_DIRS}") +# endif() + +# nlgenl-3 library. +execute_process(COMMAND ls /usr/include/libnl3/netlink/netlink.h RESULT_VARIABLE LNL3_MISSING OUTPUT_QUIET ERROR_QUIET) +if(LNL3_MISSING) + message(ERROR " Need to: sudo apt get libnl-3-dev libnl-genl-3-dev ") +endif() +set(LIBNL3_INCLUDE_DIRS /usr/include/libnl3) +set(LIBNL3_LIBRARIES nl-3 nl-genl-3) + pkg_check_modules(SYSTEMD "systemd") if(!SYSTEMD_FOUND) message(ERROR "libsystemd-dev package not installed.") -else() - message(STATUS "SYSTEMD_LIBRARIES: ${SYSTEMD_LIBRARIES}") - message(STATUS "SYSTEMD_INCLUDE_DIRS: ${SYSTEMD_INCLUDE_DIRS}") endif() @@ -55,8 +69,10 @@ message (STATUS "Cxx flags: ${CMAKE_CXX_FLAGS}" ) add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY) -add_executable(pipedald BeastServer.cpp BeastServer.hpp HtmlHelper.cpp HtmlHelper.hpp pch.h Uri.cpp Uri.hpp - RequestHandler.hpp main.cpp json.cpp json.hpp Scratch.cpp Lv2Host.hpp Lv2Host.cpp +set (PIPEDAL_SOURCES + BeastServer.cpp BeastServer.hpp HtmlHelper.cpp HtmlHelper.hpp pch.h Uri.cpp Uri.hpp + WifiConfigSettings.hpp WifiConfigSettings.cpp + RequestHandler.hpp json.cpp json.hpp Scratch.cpp Lv2Host.hpp Lv2Host.cpp PluginType.hpp PluginType.cpp Lv2Log.hpp Lv2Log.cpp PiPedalSocket.hpp PiPedalSocket.cpp @@ -93,22 +109,51 @@ add_executable(pipedald BeastServer.cpp BeastServer.hpp HtmlHelper.cpp HtmlHelpe Locale.hpp Locale.cpp Lv2EventBufferWriter.hpp Lv2EventBufferWriter.cpp IpSubnet.hpp - ) + WifiChannels.hpp + WifiChannels.cpp + RegDb.cpp RegDb.hpp +) configure_file(config.hpp.in config.hpp) + +################################# +add_executable(pipedald ${PIPEDAL_SOURCES} main.cpp + ) + include_directories( ${PiPedal_SOURCE_DIR}/. ../build/src) target_precompile_headers(pipedald PRIVATE pch.h) target_include_directories(pipedald PRIVATE - ${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${SYSTEMD_INCLUDE_DIRS} + ${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS} ) target_link_libraries(pipedald PRIVATE pthread atomic stdc++fs - ${LILV_0_LIBRARIES} ${JACK_LIBRARIES} ${SYSTEMD_LIBRARIES} systemd + ${LILV_0_LIBRARIES} ${JACK_LIBRARIES} ${LIBNL3_LIBRARIES} systemd ) -#target_link_libraries(pipedald boost_system) + +################################# +add_executable(pipedaltest ${PIPEDAL_SOURCES} testMain.cpp + jsonTest.cpp + WifiChannelsTest.cpp + ) + +configure_file(config.hpp.in config.hpp) +include_directories( ${PiPedal_SOURCE_DIR}/. ../build/src) + +target_precompile_headers(pipedaltest PRIVATE pch.h) + + +target_include_directories(pipedaltest PRIVATE + ${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS} + ) + +target_link_libraries(pipedaltest PRIVATE pthread atomic stdc++fs + ${LILV_0_LIBRARIES} ${JACK_LIBRARIES} ${LIBNL3_LIBRARIES} systemd + ) + +################################# add_executable(pipedalconfig json.cpp json.hpp diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index f128da9..2b74b5d 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -95,7 +95,6 @@ void PiPedalModel::Load(const PiPedalConfiguration &configuration) this->jackHost->SetNotificationCallbacks(this); - if (configuration.GetMLock()) { #ifndef NO_MLOCK @@ -126,10 +125,8 @@ void PiPedalModel::Load(const PiPedalConfiguration &configuration) catch (PiPedalException &e) { Lv2Log::error("Failed to load initial plugin. (%s)", e.what()); - } } - } IPiPedalModelSubscriber *PiPedalModel::GetNotificationSubscriber(int64_t clientId) @@ -278,7 +275,6 @@ void PiPedalModel::setPedalBoard(int64_t clientId, PedalBoard &pedalBoard) } } - void PiPedalModel::setPedalBoardItemEnable(int64_t clientId, int64_t pedalItemId, bool enabled) { std::lock_guard(this->mutex); @@ -318,7 +314,6 @@ PedalBoard PiPedalModel::getPreset(int64_t instanceId) return this->storage.GetPreset(instanceId); } - void PiPedalModel::setPresetChanged(int64_t clientId, bool value) { if (value != this->hasPresetChanged) @@ -369,14 +364,13 @@ int64_t PiPedalModel::saveCurrentPresetAs(int64_t clientId, const std::string &n return result; } -int64_t PiPedalModel::uploadPreset(const BankFile&bankFile,int64_t uploadAfter) +int64_t PiPedalModel::uploadPreset(const BankFile &bankFile, int64_t uploadAfter) { std::lock_guard(this->mutex); - int64_t newPreset = this->storage.UploadPreset(bankFile,uploadAfter); + int64_t newPreset = this->storage.UploadPreset(bankFile, uploadAfter); firePresetsChanged(-1); return newPreset; - } void PiPedalModel::loadPreset(int64_t clientId, int64_t instanceId) @@ -462,6 +456,35 @@ bool PiPedalModel::renamePreset(int64_t clientId, int64_t instanceId, const std: } } +void PiPedalModel::setWifiConfigSettings(const WifiConfigSettings &wifiConfigSettings) +{ + std::lock_guard lock(this->mutex); + // xxx Commit to local! + this->storage.SetWifiConfigSettings(wifiConfigSettings); + + { + IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; + for (size_t i = 0; i < subscribers.size(); ++i) + { + t[i] = this->subscribers[i]; + } + size_t n = this->subscribers.size(); + + WifiConfigSettings tWifiConfigSettings = storage.GetWifiConfigSettings(); // (the passwordless version) + + for (size_t i = 0; i < n; ++i) + { + t[i]->OnWifiConfigSettingsChanged(tWifiConfigSettings); + } + delete[] t; + } +} +WifiConfigSettings PiPedalModel::getWifiConfigSettings() +{ + std::lock_guard lock(this->mutex); + return this->storage.GetWifiConfigSettings(); +} + JackConfiguration PiPedalModel::getJackConfiguration() { std::lock_guard lock(this->mutex); // copy atomically. @@ -983,4 +1006,3 @@ void PiPedalModel::cancelListenForMidiEvent(int64_t clientId, int64_t clientHand jackHost->SetListenForMidiEvent(false); } } - diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp index 6df567f..364aad3 100644 --- a/src/PiPedalModel.hpp +++ b/src/PiPedalModel.hpp @@ -12,6 +12,7 @@ #include "Banks.hpp" #include "PiPedalConfiguration.hpp" #include "JackServerSettings.hpp" +#include "WifiConfigSettings.hpp" namespace pipedal { @@ -34,6 +35,7 @@ public: virtual void OnLoadPluginPreset(int64_t instanceId,const std::vector&controlValues) = 0; virtual void OnMidiValueChanged(int64_t instanceId, const std::string&symbol, float value) = 0; virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl) = 0; + virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings&wifiConfigSettings) = 0; virtual void Close() = 0; }; @@ -145,6 +147,10 @@ public: void setJackChannelSelection(int64_t clientId,const JackChannelSelection &channelSelection); JackChannelSelection getJackChannelSelection(); + void setWifiConfigSettings(const WifiConfigSettings&wifiConfigSettings); + WifiConfigSettings getWifiConfigSettings(); + + int64_t addVuSubscription(int64_t instanceId); void removeVuSubscription(int64_t subscriptionHandle); diff --git a/src/PiPedalSocket.cpp b/src/PiPedalSocket.cpp index d200c75..d2f0a14 100644 --- a/src/PiPedalSocket.cpp +++ b/src/PiPedalSocket.cpp @@ -11,6 +11,8 @@ #include #include "ShutdownClient.hpp" +#include "WifiConfigSettings.hpp" +#include "WifiChannels.hpp" using namespace std; using namespace pipedal; @@ -667,6 +669,12 @@ public: { JackHostStatus status = model.getJackStatus(); this->Reply(replyTo,"getJackStatus",status); + } else if (message == "getWifiChannels") + { + std::string country; + pReader->read(&country); + std::vector channels = pipedal::getWifiChannels(country.c_str()); + this->Reply(replyTo,"getWifiChannels",channels); } else if (message == "getPluginPresets") { @@ -688,6 +696,28 @@ public: this->Reply(replyTo,"setJackserverSettings"); } + else if (message == "setWifiConfigSettings") { + WifiConfigSettings wifiConfigSettings; + pReader->read(&wifiConfigSettings); + if (!ShutdownClient::CanUseShutdownClient()) + { + throw PiPedalException("Can't change server settings when running interactively."); + } + std::string fromAddress = this->getFromAddress(); + if (!ShutdownClient::IsOnLocalSubnet(fromAddress)) + { + throw PiPedalException("Permission denied. Not on local subnet."); + } + + + this->model.setWifiConfigSettings(wifiConfigSettings); + this->Reply(replyTo,"setWifiConfigSettings"); + } + else if (message == "getWifiConfigSettings") { + this->Reply(replyTo, "getWifiConfigSettings", model.getWifiConfigSettings()); + + } + else if (message == "getJackServerSettings") { this->Reply(replyTo, "getJackServerSettings", model.GetJackServerSettings()); @@ -1220,6 +1250,10 @@ public: Send("onJackServerSettingsChanged",jackServerSettings); } + virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings&wifiConfigSettings) { + Send("onWifiConfigSettingsChanged",wifiConfigSettings); + } + virtual void OnPedalBoardChanged(int64_t clientId, const PedalBoard &pedalBoard) { SetCurrentPedalBoardBody body; diff --git a/src/RegDb.cpp b/src/RegDb.cpp new file mode 100644 index 0000000..c6d3e18 --- /dev/null +++ b/src/RegDb.cpp @@ -0,0 +1,228 @@ +#include "pch.h" +#include "RegDb.hpp" +#include +#include +#include + +#include "PiPedalException.hpp" + +#define REGDB_MAGIC 0x52474442 +#define REGDB_VERSION 19 + + +using namespace pipedal; +using namespace std; + +struct RegDbFileHeader { + uint32_t magic; + uint32_t version; + uint32_t countryOffset; + uint32_t countryCount; + uint32_t signatureLength; + + void toNs() { + this->magic = htonl(this->magic); + this->version = htonl(this->version); + this->countryOffset = htonl(this->countryOffset); + this->countryCount = htonl(this->countryCount); + this->signatureLength = htonl(this->signatureLength); + } +}; + +struct CountryHeader { + char alpha2[2]; + uint8_t pad; + uint8_t dfsRegion; // first 2 bits only. + uint32_t rulesOffset; + + void toNs() { + this->rulesOffset = htonl(rulesOffset); + } + +}; +struct RulesCollection { + uint32_t ruleCount; + uint32_t ruleOffsets[1]; + + void toNs() { + this->ruleCount = htonl(this->ruleCount); + uint32_t *p = ruleOffsets; + for (uint32_t i = 0; i < this->ruleCount; ++i) + { + *p = htonl(*p); + ++p; + } + } +}; + +struct Rule { + uint32_t frequencyRangeOffset; + uint32_t powerRuleOffset; + uint32_t flags; + + void toNs() { + frequencyRangeOffset = htonl(frequencyRangeOffset); + powerRuleOffset = htonl(powerRuleOffset); + flags = htonl(flags); + } +}; + +struct FrequencyRange { + uint32_t startFrequency; // in khz. + uint32_t endFrequency; // in khz. + uint32_t maxBandwidth; // in khz. + void toNs() { + startFrequency = htonl(startFrequency); + endFrequency = htonl(endFrequency); + maxBandwidth = htonl(maxBandwidth); + } +}; +struct PowerRule { + uint32_t maximumAntennaGain; + uint32_t maximumEirp; + void toNs() { + maximumAntennaGain = htonl(maximumAntennaGain); + maximumEirp = htonl(maximumEirp); + } +}; + + + +const char*filePaths[] = +{ + "/usr/lib/crda/regulatory.bin" +}; + +static std::filesystem::path getFilePath() +{ + for (size_t i = 0; i < sizeof(filePaths)/sizeof(filePaths[0]); ++i) + { + std::filesystem::path path(filePaths[i]); + if (std::filesystem::exists(path)) { + return path; + } + } + throw PiPedalException("Could not find 'regulatory.bin' file."); +} + +RegDb::~RegDb() +{ + delete[] pData; +} + +RegDb::RegDb() +:pData(nullptr) +{ + RegDbFileHeader header; + std::filesystem::path path = getFilePath(); + ifstream f(path); + if (!f.is_open()) { + stringstream s; + s << "Can't read " << path; + throw PiPedalException(s.str()); + } + size_t fileSize = std::filesystem::file_size(path); + this->pData = new uint8_t[fileSize]; + f.read((char*)this->pData,fileSize); + + // convert from big-endian to local byte order; + RegDbFileHeader *fileHeader = (RegDbFileHeader*)pData; + fileHeader->toNs(); + if (fileHeader->magic != REGDB_MAGIC) + { + throw PiPedalException("Invalid file format."); + } + std::unordered_set visited; + + CountryHeader*countryHeader = (CountryHeader*)(pData + fileHeader->countryOffset); + for (uint32_t i = 0; i < fileHeader->countryCount; ++i) + { + countryHeader->toNs(); + if (visited.count(countryHeader->rulesOffset) == 0) + { + visited.insert(countryHeader->rulesOffset); + RulesCollection*pRules = (RulesCollection*)(pData + countryHeader->rulesOffset); + pRules->toNs(); + for (uint32_t nRule = 0; nRule < pRules->ruleCount; ++nRule) + { + uint32_t ruleOffset = pRules->ruleOffsets[nRule]; + if (visited.count(ruleOffset) == 0) + { + visited.insert(ruleOffset); + Rule *pRule = (Rule*)(pData + ruleOffset); + pRule->toNs(); + + if (visited.count(pRule->frequencyRangeOffset) == 0) + { + visited.insert(pRule->frequencyRangeOffset); + + FrequencyRange*pf = (FrequencyRange*)(pData + pRule->frequencyRangeOffset); + pf->toNs(); + } + if (visited.count(pRule->powerRuleOffset) == 0) + { + visited.insert(pRule->powerRuleOffset); + + PowerRule *pr = (PowerRule*)(pData + pRule->powerRuleOffset); + pr->toNs(); + } + } + } + } + ++countryHeader; + } + +} +bool CrdbRules::GetFrequencyRule(uint32_t frequencyRuleMHz, FrequencyRule*pResult) +{ + memset(pResult,0,sizeof(FrequencyRule)); + RulesCollection*pRules = (RulesCollection*)(regDb->pData + countryOffset); + uint32_t frequencyRuleKHz = frequencyRuleMHz*1000; + for (uint32_t i = 0; i < pRules->ruleCount; ++i) + { + Rule *pRule = (Rule*)(regDb->pData + pRules->ruleOffsets[i]); + FrequencyRange*pf = (FrequencyRange*)(regDb->pData + pRule->frequencyRangeOffset); + if (frequencyRuleKHz >= pf->startFrequency && frequencyRuleKHz < pf->endFrequency) + { + PowerRule *pr = (PowerRule*)(regDb->pData + pRule->powerRuleOffset); + pResult->flags = (RegRuleFlags)(pRule->flags); + pResult->maximumAntennaGain = pr->maximumAntennaGain; + pResult->maximumEirp = pr->maximumEirp; + return true; + } + } + return false; +} +CrdbRules*RegDb::GetCrdbRules(const char*countryCodeIso633) +{ + const char c0 = countryCodeIso633[0]; + if (c0 == '\0') return nullptr; + const char c1 = countryCodeIso633[1]; + + RegDbFileHeader *fileHeader = (RegDbFileHeader*)pData; + CountryHeader*countryHeader = (CountryHeader*)(pData + fileHeader->countryOffset); + for (uint32_t i = 0; i < fileHeader->countryCount; ++i) + { + if (countryHeader->alpha2[0] == c0 && countryHeader->alpha2[1] == c1) { + return new CrdbRules( + this, + countryHeader->rulesOffset, + (DfsRegion)(countryHeader->dfsRegion & 0x03) + ); + } + ++countryHeader; + } + return nullptr; +} + +CrdbRules::CrdbRules(RegDb *regDb,uint32_t countryOffset, DfsRegion dfsRegion ) +: regDb(regDb), + countryOffset(countryOffset), + dfsRegion(dfsRegion) +{ +} + +DfsRegion CrdbRules::GetDfsRegion() const { + return this->dfsRegion; +} + diff --git a/src/RegDb.hpp b/src/RegDb.hpp new file mode 100644 index 0000000..4eb81b3 --- /dev/null +++ b/src/RegDb.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include + +namespace pipedal { + +// sync with flags) != 0; } +}; + +class RegDb; + +class CrdbRules { + friend class RegDb; + RegDb *regDb = nullptr; + uint32_t countryOffset; + DfsRegion dfsRegion; + CrdbRules(RegDb *regDb,uint32_t countryOffset, DfsRegion dfsRegion ); +public: + virtual DfsRegion GetDfsRegion() const; + virtual bool GetFrequencyRule(uint32_t frequencyMHz,FrequencyRule*pResult); +}; + +class RegDb { +private: + friend class CrdbRules; + uint8_t*pData; + +public: + RegDb(); + ~RegDb(); + CrdbRules *GetCrdbRules(const char*countryCodeIso6333); + bool isValid() const { return pData != nullptr; } +}; + +} \ No newline at end of file diff --git a/src/ShutdownClient.cpp b/src/ShutdownClient.cpp index 6c7a671..bff17dd 100644 --- a/src/ShutdownClient.cpp +++ b/src/ShutdownClient.cpp @@ -6,6 +6,9 @@ #include #include "Lv2Log.hpp" #include +#include +#include + using namespace pipedal; @@ -138,3 +141,52 @@ bool ShutdownClient::SetJackServerConfiguration(const JackServerSettings & jackS return WriteMessage(s.str().c_str()); } + +bool ShutdownClient::IsOnLocalSubnet(const std::string&fromAddress) +{ + std::string address = fromAddress; + if (address.size() == 0) return false; + char lastChar = address[address.size()-1]; + if (address[0] != '[' || lastChar != ']') // i.e. not an ipv6 address; + { + size_t pos = address.find_last_of(':'); + if (pos != std::string::npos) + { + address = address.substr(0,pos); + } + } + if (address[0] == '[') return false; + + char buf[512]; + + struct in_addr inetAddr; + + memset(&inetAddr,0,sizeof(inetAddr)); + + if (inet_pton(AF_INET,address.c_str(),&inetAddr) == 0) { + return false; + } + uint32_t remoteAddress = htonl(inetAddr.s_addr); + + struct ifaddrs *ifap = nullptr; + if (getifaddrs(&ifap) != 0) return false; + + bool result = false; + for (ifaddrs*p = ifap; p != nullptr; p = p->ifa_next) { + if (p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr) { // TODO: Add support for AF_INET6 + uint32_t netmask = htonl(((sockaddr_in*)(p->ifa_netmask))->sin_addr.s_addr); + uint32_t ifAddr = htonl(((sockaddr_in*)(p->ifa_addr))->sin_addr.s_addr); + + if ((netmask & ifAddr) == (netmask & remoteAddress)) + { + result = true; + break; + } + } + } + freeifaddrs(ifap); + return result; + + + +} diff --git a/src/ShutdownClient.hpp b/src/ShutdownClient.hpp index 62cf96c..34f8282 100644 --- a/src/ShutdownClient.hpp +++ b/src/ShutdownClient.hpp @@ -11,6 +11,7 @@ public: static bool CanUseShutdownClient(); static bool RequestShutdown(bool restart); static bool SetJackServerConfiguration(const JackServerSettings & jackServerSettings); + static bool IsOnLocalSubnet(const std::string&fromAddress); }; } // namespace \ No newline at end of file diff --git a/src/Storage.cpp b/src/Storage.cpp index 1d860f7..6b9a2b6 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -11,6 +11,8 @@ using namespace pipedal; const char *BANK_EXTENSION = ".bank"; const char *BANKS_FILENAME = "index.banks"; +#define WIFI_CONFIG_SETTINGS_FILENAME "wifiConfigSettings.json"; + Storage::Storage() { SetDataRoot("~/var/PiPedal"); @@ -157,6 +159,7 @@ void Storage::Initialize() catch (const std::exception &) { } + LoadWifiConfigSettings(); } void Storage::LoadBank(int64_t instanceId) @@ -683,3 +686,72 @@ int64_t Storage::UploadPreset(const BankFile&bankFile,int64_t uploadAfter) this->SaveCurrentBank(); return lastPreset; } + +void Storage::SetWifiConfigSettings(const WifiConfigSettings & wifiConfigSettings) +{ + WifiConfigSettings copyToSave = wifiConfigSettings; + copyToSave.rebootRequired_ = false; + if (!copyToSave.enable_) + { + copyToSave.hasPassword_ = false; + } + copyToSave.password_ = ""; + + std::filesystem::path path = this->dataRoot / WIFI_CONFIG_SETTINGS_FILENAME; + { + std::ofstream f(path); + if (!f.is_open()) + { + throw PiPedalException("Unable to write to " + ((std::string)path)); + } + json_writer writer(f); + writer.write(©ToSave); + } + + WifiConfigSettings copyToStore = wifiConfigSettings; + if (copyToStore.enable_) + { + copyToStore.hasPassword_ = copyToStore.password_.length() != 0 || this->wifiConfigSettings.hasPassword_; + } else { + copyToStore.hasPassword_ = false; + } + copyToStore.password_ = ""; + copyToStore.rebootRequired_ = true; + if (copyToStore.enable_ && !copyToStore.hasPassword_) + { + copyToStore.hasPassword_ = this->wifiConfigSettings.hasPassword_; + } + this->wifiConfigSettings = copyToStore; +} + +void Storage::LoadWifiConfigSettings() +{ + std::filesystem::path path = this->dataRoot / WIFI_CONFIG_SETTINGS_FILENAME; + + try { + if (std::filesystem::is_regular_file(path)) + { + std::ifstream f(path); + if (!f.is_open()) + { + throw PiPedalException("Unable to write to " + ((std::string)path)); + } + json_reader reader(f); + WifiConfigSettings wifiConfigSettings; + reader.read(&wifiConfigSettings); + this->wifiConfigSettings = wifiConfigSettings; + } + } catch (const std::exception&) + { + + } + this->wifiConfigSettings.valid_ = true; +} + + +WifiConfigSettings Storage::GetWifiConfigSettings() +{ + return this->wifiConfigSettings; +} + + diff --git a/src/Storage.hpp b/src/Storage.hpp index 1a874d6..47e13a3 100644 --- a/src/Storage.hpp +++ b/src/Storage.hpp @@ -5,6 +5,7 @@ #include "Presets.hpp" #include "Banks.hpp" #include "JackConfiguration.hpp" +#include "WifiConfigSettings.hpp" namespace pipedal { @@ -17,7 +18,7 @@ private: std::filesystem::path dataRoot; BankIndex bankIndex; BankFile currentBank; - + private: static std::string SafeEncodeName(const std::string& name); static std::string SafeDecodeName(const std::string& name); @@ -39,6 +40,7 @@ private: std::string GetPresetCopyName(const std::string &name); bool isJackChannelSelectionValid = false; JackChannelSelection jackChannelSelection; + WifiConfigSettings wifiConfigSettings; public: Storage(); void Initialize(); @@ -51,6 +53,7 @@ public: const BankIndex & GetBanks() const { return bankIndex; } + void LoadWifiConfigSettings(); void LoadBank(int64_t instanceId); const PedalBoard& GetCurrentPreset(); void saveCurrentPreset(const PedalBoard&pedalBoard); @@ -77,6 +80,9 @@ public: void SetJackChannelSelection(const JackChannelSelection&channelSelection); const JackChannelSelection&GetJackChannelSelection(const JackConfiguration &jackConfiguration); + void SetWifiConfigSettings(const WifiConfigSettings & wifiConfigSettings); + WifiConfigSettings GetWifiConfigSettings(); + }; diff --git a/src/WifiChannels.cpp b/src/WifiChannels.cpp new file mode 100644 index 0000000..40af83a --- /dev/null +++ b/src/WifiChannels.cpp @@ -0,0 +1,615 @@ + +#include "pch.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "PiPedalException.hpp" +#include "WifiChannels.hpp" +#include "Lv2Log.hpp" + +#include "RegDb.hpp" +/* + Heavily based on code from iw (1) command. + https://kernel.googlesource.com/pub/scm/linux/kernel/git/jberg/iw/+/v0.9/COPYING + +Copyright (c) 2007, 2008 Johannes Berg +Copyright (c) 2007 Andy Lutomirski +Copyright (c) 2007 Mike Kershaw +Copyright (c) 2008 Luis R. Rodriguez +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. +*/ + +using namespace pipedal; + +struct MHzToChannel { + char band; + int channel; + int mhz; +}; + +MHzToChannel mhzToChannels[] = { +{'g', 1, 2412}, +{'g',2, 2417}, +{'g',3, 2422}, +{'g',4, 2427}, +{'g',5, 2432}, +{'g',6, 2437}, +{'g',7, 2442}, +{'g',8, 2447}, +{'g',9, 2452}, +{'g',10, 2457}, +{'g',11, 2462}, +{'g',12, 2467}, +{'g',13, 2472}, +{'g',14, 2484}, +{'a',7, 5035}, +{'a',8, 5040}, +{'a',9, 5045}, +{'a',11, 5055}, +{'a',12, 5060}, +{'a',16, 5080}, +{'a',32, 5160}, +{'a',34, 5170}, +{'a',36, 5180}, +{'a',38, 5190}, +{'a',40, 5200}, +{'a',42, 5210}, +{'a',44, 5220}, +{'a',46, 5230}, +{'a',48, 5240}, +{'a',50, 5250}, +{'a',52, 5260}, +{'a',54, 5270}, +{'a',56, 5280}, +{'a',58, 5290}, +{'a',60, 5300}, +{'a',62, 5310}, +{'a',64, 5320}, +{'a',68, 5340}, +{'a',96, 5480}, +{'a',100, 5500}, +{'a',102, 5510}, +{'a',104, 5520}, +{'a',106, 5530}, +{'a',108, 5540}, +{'a',110, 5550}, +{'a',112, 5560}, +{'a',114, 5570}, +{'a',116, 5580}, +{'a',118, 5590}, +{'a',120, 5600}, +{'a',122, 5610}, +{'a',124, 5620}, +{'a',126, 5630}, +{'a',128, 5640}, +{'a',132, 5660}, +{'a',134, 5670}, +{'a',136, 5680}, +{'a',138, 5690}, +{'a',140, 5700}, +{'a',142, 5710}, +{'a',144, 5720}, +{'a',149, 5745}, +{'a',151, 5755}, +{'a',153, 5765}, +{'a',155, 5775}, +{'a',157, 5785}, +{'a',159, 5795}, +{'a',161, 5805}, +{'a',163, 5815}, +{'a',165, 5825}, +{'a',167, 5835}, +{'a',169, 5845}, +{'a',171, 5855}, +{'a',173, 5865}, +{'a',175, 5875}, +{'a',177, 5885}, +{'a',180, 5900}, +{'a',182, 5910}, +{'a',18, 5915}, +{'a',184, 5920}, +{'a',187, 5935}, +{'a',188, 5940}, +{'a',189, 5945}, +{'a',192, 5960}, +{'a',196, 5980}, +}; + + +bool MHzToChannelNumber(int mhz, char*pBand, int*pChannel) +{ + for (size_t i = 0; i < sizeof(mhzToChannels)/sizeof(mhzToChannels[0]); ++i) + { + if (std::abs(mhz-mhzToChannels[i].mhz) <= 1) { + *pBand = mhzToChannels[i].band; + *pChannel = mhzToChannels[i].channel; + return true; + } + } + return false; +} + +static nla_policy *makeFreqPolicy() +{ + nla_policy* result = new nla_policy[NL80211_FREQUENCY_ATTR_MAX + 1]; + memset(result,0,sizeof(nla_policy[NL80211_FREQUENCY_ATTR_MAX + 1])); + + result[NL80211_FREQUENCY_ATTR_FREQ] = {NLA_U32,0,0}; + result[NL80211_FREQUENCY_ATTR_DISABLED] = {NLA_FLAG,0,0}; + result[NL80211_FREQUENCY_ATTR_PASSIVE_SCAN] = {NLA_FLAG,0,0}; + result[NL80211_FREQUENCY_ATTR_NO_IR] = {NLA_FLAG,0,0}; + result[NL80211_FREQUENCY_ATTR_RADAR] = {NLA_FLAG,0,0}; + result[NL80211_FREQUENCY_ATTR_INDOOR_ONLY] = {NLA_FLAG,0,0}; + result[NL80211_FREQUENCY_ATTR_INDOOR_ONLY] = {NLA_FLAG,0,0}; + result[NL80211_FREQUENCY_ATTR_NO_20MHZ] = {NLA_FLAG,0,0}; + result[NL80211_FREQUENCY_ATTR_NO_20MHZ] = {NLA_FLAG,0,0}; + result[NL80211_FREQUENCY_ATTR_NO_HT40_MINUS] = {NLA_FLAG,0,0}; + result[NL80211_FREQUENCY_ATTR_NO_HT40_PLUS] = {NLA_FLAG,0,0}; + result[NL80211_FREQUENCY_ATTR_NO_80MHZ] = {NLA_FLAG,0,0}; + result[NL80211_FREQUENCY_ATTR_NO_160MHZ] = {NLA_FLAG,0,0}; + return result; +} + +static struct nla_policy* freq_policy = makeFreqPolicy(); + + +static nla_policy *makeRatePolicy() { + nla_policy* result = new nla_policy[NL80211_BITRATE_ATTR_MAX + 1]; + memset(result,0,sizeof(nla_policy[NL80211_BITRATE_ATTR_MAX + 1])); + result[NL80211_BITRATE_ATTR_RATE] = {.type = NLA_U32}; + result[NL80211_BITRATE_ATTR_2GHZ_SHORTPREAMBLE] = {.type = NLA_FLAG}; + return result; +}; + +static struct nla_policy* rate_policy = makeRatePolicy(); + +struct IfType { + enum nl80211_iftype iftype; + const char*name; +}; +static struct IfType ifTypes[] = { + {NL80211_IFTYPE_UNSPECIFIED,"Unspecified"}, + {NL80211_IFTYPE_ADHOC,"Adhoc"}, + {NL80211_IFTYPE_STATION,"Station"}, + {NL80211_IFTYPE_AP,"AP"}, + {NL80211_IFTYPE_AP_VLAN,"VLAN"}, + {NL80211_IFTYPE_WDS,"WDS"}, + {NL80211_IFTYPE_MONITOR,"Monitor"}, + {NL80211_IFTYPE_MESH_POINT,"Mesh point"}, + {NL80211_IFTYPE_P2P_CLIENT,"Client"}, + {NL80211_IFTYPE_P2P_GO,"P2P Go"}, + {NL80211_IFTYPE_P2P_DEVICE,"P2P"}, + {NL80211_IFTYPE_OCB,"OCB"}, + {NL80211_IFTYPE_NAN,"NAN"}, + +}; +const char *iftype_name(enum nl80211_iftype iftype) +{ + for (size_t i = 0; i < sizeof(ifTypes)/sizeof(ifTypes[0]); ++i) + { + if (ifTypes[i].iftype == iftype) + { + return ifTypes[i].name; + } + } + return "Unknown"; +} +static int phy_lookup(const char *name) +{ + char buf[200]; + int fd, pos; + snprintf(buf, sizeof(buf), "/sys/class/ieee80211/%s/index", name); + fd = open(buf, O_RDONLY); + pos = read(fd, buf, sizeof(buf) - 1); + if (pos < 0) + return -1; + buf[pos] = '\0'; + return atoi(buf); +} + +static int getWifiName_callback(struct nl_msg *msg, void *arg); +static int getWifiInfo_callback(struct nl_msg *msg, void *arg); + +struct WifiChannelInfo +{ + int channelNumber = -1; + char band = 'g'; // 'g' or 'a' + int mhz = 0; + bool disabled = false; + bool ir = true; + bool radarDetection = false; + std::vector bitrates; + bool indoorOnly = false; + bool noHt40Minus = false; + bool noHt40Plus = false; + bool no10MHz = false; + bool no20MHz = false; + bool no80MHz = false; + bool no160MHz = false; +}; + +struct WifiBand +{ + int index; + std::vector channels; +}; + +struct WifiInfo +{ + std::string name; + std::vector bands; + std::vector supportedIfTypes; +}; +class NetLink +{ +private: + + std::vector result; + nl_sock*socket = nullptr; + nl_cache*cache = nullptr; + genl_family*nl80211 = nullptr; + + int errorCode = -1; + + static int finish_handler(struct nl_msg *msg, void *arg) + { + NetLink*this_ = (NetLink*)arg; + this_->errorCode = 0; + return NL_SKIP; + } + static int ack_handler(struct nl_msg *msg, void *arg) + { + NetLink*this_ = (NetLink*)arg; + this_->errorCode = 0; + return NL_STOP; + } + + static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, + void *arg) + { + NetLink*this_ = (NetLink*)arg; + this_->errorCode = err->error; + return NL_STOP; + } + + + static int info_phy_handler(struct nl_msg *msg, void *arg) + { + return ((NetLink *)arg)->InfoPhyHandler(msg, (WifiInfo *)arg); + } + int InfoPhyHandler(struct nl_msg *msg, WifiInfo *result) + { + struct nlattr *tb_msg[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = (genlmsghdr *)nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *tb_band[NL80211_BAND_ATTR_MAX + 1]; + struct nlattr *tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1]; + struct nlattr *tb_rate[NL80211_BITRATE_ATTR_MAX + 1]; + struct nlattr *nl_band; + struct nlattr *nl_freq; + struct nlattr *nl_rate; + struct nlattr *nl_mode; + int bandidx = 1; + int rem_band, rem_freq, rem_rate, rem_mode; + int open; + nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + if (!tb_msg[NL80211_ATTR_WIPHY_BANDS]) + return NL_SKIP; + if (tb_msg[NL80211_ATTR_WIPHY_NAME]) + result->name = nla_get_string(tb_msg[NL80211_ATTR_WIPHY_NAME]); + + nla_for_each_nested(nl_band, tb_msg[NL80211_ATTR_WIPHY_BANDS], rem_band) + { + WifiBand band; + band.index = bandidx; + + bandidx++; + nla_parse(tb_band, NL80211_BAND_ATTR_MAX, (nlattr *)nla_data(nl_band), + nla_len(nl_band), NULL); + + nla_for_each_nested(nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], rem_freq) + { + nla_parse(tb_freq, NL80211_FREQUENCY_ATTR_MAX, (nlattr *)nla_data(nl_freq), + nla_len(nl_freq), freq_policy); + if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ]) + continue; + + WifiChannelInfo channel; + + channel.mhz = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]); + open = 0; + if (tb_freq[NL80211_FREQUENCY_ATTR_DISABLED]) + channel.disabled = true; + if (tb_freq[NL80211_FREQUENCY_ATTR_NO_IR]) + channel.ir = false; + if (tb_freq[NL80211_FREQUENCY_ATTR_RADAR]) + channel.radarDetection = true; + if (tb_freq[NL80211_FREQUENCY_ATTR_NO_10MHZ]) + channel.no10MHz = true; + if (tb_freq[NL80211_FREQUENCY_ATTR_INDOOR_ONLY]) + channel.indoorOnly = true; + if (tb_freq[NL80211_FREQUENCY_ATTR_NO_20MHZ]) + channel.no20MHz = true; + if (tb_freq[NL80211_FREQUENCY_ATTR_NO_HT40_MINUS]) + channel.noHt40Minus = true; + if (tb_freq[NL80211_FREQUENCY_ATTR_NO_HT40_PLUS]) + channel.noHt40Plus = true; + if (tb_freq[NL80211_FREQUENCY_ATTR_NO_80MHZ]) + channel.no80MHz = true; + if (tb_freq[NL80211_FREQUENCY_ATTR_NO_160MHZ]) + channel.no160MHz = true; + + if (MHzToChannelNumber(channel.mhz,&channel.band, &channel.channelNumber)) + { + band.channels.push_back(std::move(channel)); + } else { + // throw PiPedalException("Frequency not recognized."); + } + } + nla_for_each_nested(nl_rate, tb_band[NL80211_BAND_ATTR_RATES], rem_rate) + { + nla_parse(tb_rate, NL80211_BITRATE_ATTR_MAX, (nlattr *)nla_data(nl_rate), + nla_len(nl_rate), rate_policy); + if (!tb_rate[NL80211_BITRATE_ATTR_RATE]) + continue; + //channel.bitRates.push_back(0.1f * nla_get_u32(tb_rate[NL80211_BITRATE_ATTR_RATE]); + } + result->bands.push_back(std::move(band)); + } + if (!tb_msg[NL80211_ATTR_SUPPORTED_IFTYPES]) + return NL_SKIP; + + nla_for_each_nested(nl_mode, tb_msg[NL80211_ATTR_SUPPORTED_IFTYPES], rem_mode) + result->supportedIfTypes.push_back(iftype_name((nl80211_iftype)(nl_mode->nla_type))); + return NL_SKIP; + } + +public: + ~NetLink() + { + if (this->cache) + { + nl_cache_free(cache); + cache = nullptr; + } + if (nl80211 != nullptr) + { + genl_family_put(nl80211); // (release reference) + nl80211 = nullptr; + } + if (socket) + { + nl_close(socket); + nl_socket_free(socket); + socket = nullptr; + } + } + NetLink() + { + socket = nl_socket_alloc(); + if (!socket) + { + throw PiPedalException("Can't allocate netlink socket."); + } + nl_socket_set_buffer_size(socket, 8192, 8192); + + if (genl_connect(socket)) + { + throw PiPedalException("Failed to connect to netlink socket."); + } + if (genl_ctrl_alloc_cache(this->socket,&this->cache) != 0) { + throw PiPedalException("Failed to allocate generic netlink cache."); + } + this->nl80211 = genl_ctrl_search_by_name(this->cache, "nl80211"); + if (!this->nl80211) { + throw PiPedalException("nl80211 family not found."); + } + } + void GetAvailableChannels(const char *phyName, WifiInfo *result) + { + + struct nl_msg *msg = nullptr; + struct nl_cb*cb = nullptr; + int devidx = 0; + int err; + + devidx = phy_lookup(phyName); + try { + + + msg = nlmsg_alloc(); + if (!msg) + { + throw PiPedalException("failed to allocate netlink message"); + } + cb = nl_cb_alloc(NL_CB_DEFAULT); + if (!cb) + { + throw PiPedalException("failed to allocate netlink callbacks"); + } + genlmsg_put(msg, 0, 0, genl_family_get_id(this->nl80211), 0, + 0, NL80211_CMD_GET_WIPHY, 0); + NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, devidx); + + this->errorCode = 0; + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, info_phy_handler, result); + + nl_cb_err(cb, NL_CB_CUSTOM, error_handler, this); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, this); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, this); + + err = nl_send_auto_complete(this->socket, msg); + + if (err < 0) + throw PiPedalException("Command failed."); + nl_recvmsgs(this->socket, cb); + nlmsg_free(msg); + if (this->errorCode != 0) + { + throw PiPedalException("Command failed."); + } + nl_cb_put(cb); + return; + nla_put_failure: + throw PiPedalException("NLA Put failure."); + } catch (const std::exception&e) + { + if (cb != nullptr) + { + nl_cb_put(cb); + cb = nullptr; + } + if (msg != nullptr) + { + nlmsg_free(msg); + msg = nullptr; + } + throw; + } + } +}; + +WifiInfo getWifiInfo(const char *phyName) +{ + int index = phy_lookup(phyName); + if (index == -1) + { + throw PiPedalArgumentException("No such device."); + } + + NetLink netLink; + WifiInfo result; + netLink.GetAvailableChannels(phyName,&result); + return result; +} + +const char*allowOutdoorsLocales[] = { + "US","TW","SG", + +}; + +static bool allowOutdoors(const char*countryCode) +{ + for (size_t i = 0; i < sizeof(allowOutdoorsLocales)/sizeof(allowOutdoorsLocales[0]); ++i) + { + if (strcmp(allowOutdoorsLocales[i],countryCode) == 0) return true; + } + return false; +} + +static bool firstTime = true; +static std::unique_ptr g_regDb; +std::vector pipedal::getWifiChannels(const char*countryIso3661) +{ + // Use the wifi physical device to get a list of channels + // that the hardware supports. + // The override the hardware's regulation flags using regulation.bin + // database info for the selected country code. + // If the regulation.bin database doesn't have a matching record, + // just pass the data that the wifi reported. + + if (firstTime) { + firstTime = false; + try { + g_regDb = std::make_unique(); + } catch (const std::exception& e) + { + std::stringstream s; + s << "Failed to open the Wi-Fi regulations.bin database. " << e.what(); + Lv2Log::error(s.str()); + } + } + std::vector result; + + WifiInfo wifiInfo = getWifiInfo("phy0"); + std::unique_ptr crdbRules = nullptr; + if (g_regDb) { + crdbRules = std::unique_ptr(g_regDb->GetCrdbRules(countryIso3661)); + if (!crdbRules) { + crdbRules = std::unique_ptr(g_regDb->GetCrdbRules("00")); // unset default. + } + } + for (auto &band : wifiInfo.bands) + { + for (auto &channel: band.channels) { + if (crdbRules) + { + FrequencyRule crdbRule; + if (crdbRules->GetFrequencyRule(channel.mhz,&crdbRule)) + { + channel.disabled = false; + channel.radarDetection = crdbRule.hasFlag(RegRuleFlags::DFS); + channel.ir = !crdbRule.hasFlag(RegRuleFlags::NO_IR); + channel.indoorOnly = crdbRule.hasFlag(RegRuleFlags::NO_OUTDOOR); + if (crdbRule.hasFlag(RegRuleFlags::PTP_ONLY) + || crdbRule.hasFlag(RegRuleFlags::PTMP_ONLY)) + { + channel.disabled = true; + + } + } else { + channel.disabled = true; + } + } + if ((!channel.disabled) && (!channel.radarDetection) && channel.ir) + { + WifiChannel ch; + std::stringstream s; + s << channel.band << channel.channelNumber; + ch.channelId_ = s.str(); + + std::stringstream t; + t << channel.channelNumber; + if (channel.band == 'a') + t << " (5GHz)"; + else + t << " (2.4GHz)"; + + if (channel.indoorOnly) { + t << " Indoor only"; + } + ch.channelName_ = t.str(); + result.push_back(ch); + } + } + } + return result; +} + + +JSON_MAP_BEGIN(WifiChannel) + JSON_MAP_REFERENCE(WifiChannel,channelId) + JSON_MAP_REFERENCE(WifiChannel,channelName) +JSON_MAP_END() + + diff --git a/src/WifiChannels.hpp b/src/WifiChannels.hpp new file mode 100644 index 0000000..02300ad --- /dev/null +++ b/src/WifiChannels.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "json.hpp" +namespace pipedal { + +class WifiChannel { +public: + std::string channelId_; + std::string channelName_; + + DECLARE_JSON_MAP(WifiChannel); +}; + +std::vector getWifiChannels(const char*countryIso3661); + + +} \ No newline at end of file diff --git a/src/WifiChannelsTest.cpp b/src/WifiChannelsTest.cpp new file mode 100644 index 0000000..2b3264c --- /dev/null +++ b/src/WifiChannelsTest.cpp @@ -0,0 +1,18 @@ +#include "pch.h" +#include "catch.hpp" +#include +#include +#include + +#include "WifiChannels.hpp" + + + +using namespace pipedal; + + + +TEST_CASE( "Wifi Channel Test", "[wifi_channels_test]" ) { + std::vector result = pipedal::getWifiChannels("CA"); +} + diff --git a/src/WifiConfigSettings.cpp b/src/WifiConfigSettings.cpp new file mode 100644 index 0000000..4fab40d --- /dev/null +++ b/src/WifiConfigSettings.cpp @@ -0,0 +1,17 @@ +#include "pch.h" +#include "WifiConfigSettings.hpp" + +using namespace pipedal; + +JSON_MAP_BEGIN(WifiConfigSettings) + JSON_MAP_REFERENCE(WifiConfigSettings,valid) + JSON_MAP_REFERENCE(WifiConfigSettings,wifiWarningGiven) + JSON_MAP_REFERENCE(WifiConfigSettings,rebootRequired) + JSON_MAP_REFERENCE(WifiConfigSettings,enable) + JSON_MAP_REFERENCE(WifiConfigSettings,hotspotName) + JSON_MAP_REFERENCE(WifiConfigSettings,hasPassword) + JSON_MAP_REFERENCE(WifiConfigSettings,password) + JSON_MAP_REFERENCE(WifiConfigSettings,countryCode) + JSON_MAP_REFERENCE(WifiConfigSettings,channel) +JSON_MAP_END() + diff --git a/src/WifiConfigSettings.hpp b/src/WifiConfigSettings.hpp new file mode 100644 index 0000000..8582492 --- /dev/null +++ b/src/WifiConfigSettings.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "json.hpp" + +namespace pipedal { + + class WifiConfigSettings { + public: + bool valid_ = false; + bool wifiWarningGiven_ = false; + bool rebootRequired_ = false; + bool enable_ = false; + std::string countryCode_ = "US"; // iso 3661 + std::string hotspotName_ = "pipedal"; + bool hasPassword_ = false; + std::string password_; + std::string channel_ = "g6"; + public: + DECLARE_JSON_MAP(WifiConfigSettings); + }; +} \ No newline at end of file diff --git a/test/JsonTest.cpp b/src/jsonTest.cpp similarity index 98% rename from test/JsonTest.cpp rename to src/jsonTest.cpp index c394aa2..9335a6a 100644 --- a/test/JsonTest.cpp +++ b/src/jsonTest.cpp @@ -5,9 +5,9 @@ #include -#include +#include "json.hpp" -using namespace piddle; +using namespace pipedal; class JsonTestTarget { private: diff --git a/src/testMain.cpp b/src/testMain.cpp new file mode 100644 index 0000000..e7ab4e8 --- /dev/null +++ b/src/testMain.cpp @@ -0,0 +1,5 @@ +#include "pch.h" +#define CATCH_CONFIG_MAIN +#include + +uint16_t g_ShutdownPort = 0; diff --git a/todo.txt b/todo.txt index 9f1811a..2494858 100644 --- a/todo.txt +++ b/todo.txt @@ -3,9 +3,10 @@ Features: Patches. -8 Updload/download patches. -9 Upload/download banks. - +9 Hotspot config. +9 Upload/download banks. +9 Delete bank warning dialo. +7 Settings dialog doesn't have back button nav. - have to make sure it's set in the USER's environment. @@ -30,8 +31,7 @@ pulseaudio --kill Reconnect of controls after config change. - -Layout of 2nd and subsequent splits. +Run when there's no midi device. Crash when changing input channels to mono. @@ -55,6 +55,12 @@ Open source copyrights. add pipedal.local to etc/hosts. +/etc/dhcpcd.config: + nohook wpa_supplicant + +Dependents: + +libnl-3-dev libnl-genl-3-dev