Merge pull request #267 from rerdavies/dev PiPedal 1.4.70-Beta
PiPedal 1.4.70-Beta
This commit is contained in:
@@ -11,3 +11,5 @@ react/package-lock.json
|
||||
|
||||
src/dbus/bluez_adaptor.h
|
||||
src/dbus/bluez_proxy.h
|
||||
|
||||
CMakeFiles
|
||||
Vendored
+5
-4
@@ -5,13 +5,14 @@
|
||||
"includePath": [
|
||||
"${default}",
|
||||
"${workspaceFolder}",
|
||||
"${workspaceFolder}/src",
|
||||
"${workspaceFolder}/build/src/**",
|
||||
"${workspaceFolder}/src/**",
|
||||
"${workspaceFolder}/**",
|
||||
"/usr/include/lilv-0",
|
||||
"/usr/include/x86_64-linux-gnu",
|
||||
"/usr/lib",
|
||||
"~/src/vst3sdk"
|
||||
"/usr/lib"
|
||||
],
|
||||
"compilerPath": "/usr/bin/gcc",
|
||||
"compilerPath": "/usr/bin/gcc-12",
|
||||
"cStandard": "c17",
|
||||
"cppStandard": "c++20",
|
||||
"intelliSenseMode": "linux-gcc-arm64",
|
||||
|
||||
Vendored
+48
-42
@@ -21,7 +21,9 @@
|
||||
"request": "launch",
|
||||
// Resolved by CMake Tools:
|
||||
"program": "${command:cmake.launchTargetPath}",
|
||||
"args": [ ],
|
||||
"args": [
|
||||
"--no-sudo"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [
|
||||
@@ -138,7 +140,9 @@
|
||||
//"[inverting_mutex_test]"
|
||||
// "[utf8_to_utf32]"
|
||||
//"[pipedal_alsa_test]"
|
||||
"[wifi_channels_test]"
|
||||
// "[wifi_channels_test]"
|
||||
// "[locale]"
|
||||
"[modFileTypes]"
|
||||
],
|
||||
|
||||
"stopAtEntry": false,
|
||||
@@ -266,6 +270,48 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "(gdb) pipedalconfigX",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
// Resolved by CMake Tools:
|
||||
"program": "/home/robin/src/pipedal/build/src/pipedalconfig",
|
||||
"args": [
|
||||
"--nosudo", // run without sudo (which will fail because of perms, but allow us to debug deeper into install code.)
|
||||
//"--get-current-port"
|
||||
// "--uninstall"
|
||||
//"--enable-p2p" , "CA", "PiPedalTest","12345678","14"
|
||||
// "--alsa-devices"
|
||||
"--prefix", "/usr",
|
||||
"--install", "--port", "0"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [
|
||||
{
|
||||
"name": "PATH",
|
||||
"value": "$PATH:${command:cmake.launchTargetDirectory}"
|
||||
},
|
||||
{
|
||||
"name": "ASAN_OPTIONS",
|
||||
"value": "detect_leaks=0"
|
||||
|
||||
},
|
||||
{
|
||||
"name": "OTHER_VALUE",
|
||||
"value": "Something something"
|
||||
}
|
||||
],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
@@ -312,46 +358,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "(gdb) pipedalconfig",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
// Resolved by CMake Tools:
|
||||
"program": "${command:cmake.launchTargetPath}",
|
||||
"args": [
|
||||
"--nosudo", // run without sudo (which will fail because of perms, but allow us to debug deeper into install code.)
|
||||
//"--get-current-port"
|
||||
// "--uninstall"
|
||||
//"--enable-p2p" , "CA", "PiPedalTest","12345678","14"
|
||||
"--alsa-devices"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [
|
||||
{
|
||||
"name": "PATH",
|
||||
"value": "$PATH:${command:cmake.launchTargetDirectory}"
|
||||
},
|
||||
{
|
||||
"name": "ASAN_OPTIONS",
|
||||
"value": "detect_leaks=0"
|
||||
|
||||
},
|
||||
{
|
||||
"name": "OTHER_VALUE",
|
||||
"value": "Something something"
|
||||
}
|
||||
],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "(gdb) pipedal_alsa_check",
|
||||
"type": "cppdbg",
|
||||
|
||||
Vendored
+27
-3
@@ -98,17 +98,41 @@
|
||||
"p2p_i.h": "c",
|
||||
"p2p.h": "c",
|
||||
"hash_set": "cpp",
|
||||
"barrier": "cpp"
|
||||
"barrier": "cpp",
|
||||
"format": "cpp",
|
||||
"locale": "cpp",
|
||||
"stdfloat": "cpp",
|
||||
"text_encoding": "cpp"
|
||||
},
|
||||
"cSpell.words": [
|
||||
"Alsa",
|
||||
"flac",
|
||||
"Guitarix",
|
||||
"hostapd",
|
||||
"Hotspot",
|
||||
"invada",
|
||||
"jmap",
|
||||
"Lilv",
|
||||
"midiclip",
|
||||
"midisong",
|
||||
"mlmodel",
|
||||
"Pedalboard",
|
||||
"pipedal",
|
||||
"pipedal_0",
|
||||
"pipedalconfig",
|
||||
"pipedald",
|
||||
"pipedalshutdownd",
|
||||
"pipedaltest",
|
||||
"ToobAmp"
|
||||
]
|
||||
"pipedalui",
|
||||
"rdfs",
|
||||
"ToobAmp",
|
||||
"urids",
|
||||
"Writables",
|
||||
"xruns"
|
||||
],
|
||||
"C_Cpp.default.compilerPath": "/usr/bin/g++-13",
|
||||
"cSpell.ignoreWords": [
|
||||
"nammodel"
|
||||
],
|
||||
"cSpell.enabled": false
|
||||
}
|
||||
+7
-16
@@ -1,39 +1,30 @@
|
||||
cmake_minimum_required(VERSION 3.16.0)
|
||||
project(pipedal
|
||||
VERSION 1.3.69
|
||||
VERSION 1.4.70
|
||||
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
|
||||
HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
|
||||
)
|
||||
|
||||
EXECUTE_PROCESS( COMMAND dpkg --print-architecture COMMAND tr -d '\n' OUTPUT_VARIABLE DEBIAN_ARCHITECTURE )
|
||||
|
||||
set (DISPLAY_VERSION "PiPedal v1.3.69-Release")
|
||||
set (DISPLAY_VERSION "PiPedal v1.4.70-Beta")
|
||||
set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
|
||||
set (CMAKE_INSTALL_PREFIX "/usr/")
|
||||
|
||||
include(CTest)
|
||||
enable_testing()
|
||||
|
||||
|
||||
#add_subdirectory("submodules/pipedal_p2pd")
|
||||
|
||||
add_subdirectory("PiPedalCommon")
|
||||
|
||||
add_subdirectory("react")
|
||||
add_subdirectory("vite")
|
||||
|
||||
add_subdirectory("src")
|
||||
|
||||
# Replaced with hotspot.
|
||||
#add_subdirectory("NetworkManagerP2P")
|
||||
|
||||
# install (TARGETS pipedal_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
|
||||
# )
|
||||
|
||||
# install (TARGETS pipedal_nm_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
|
||||
# )
|
||||
|
||||
# add_test(NAME Test COMMAND piddletest)
|
||||
|
||||
set (REACT_BUILD_DIRECTORY ${PROJECT_SOURCE_DIR}/build/react/build/)
|
||||
set (VITE_BUILD_DIRECTORY ${PROJECT_SOURCE_DIR}/vite/dist/)
|
||||
|
||||
# install
|
||||
|
||||
@@ -51,7 +42,7 @@ install (
|
||||
FILES ${PROJECT_SOURCE_DIR}/src/polkit-1/rules/10-pipedal-networkmanager.rules DESTINATION /etc/polkit-1/rules.d/
|
||||
)
|
||||
install (
|
||||
DIRECTORY ${REACT_BUILD_DIRECTORY} DESTINATION /etc/pipedal/react
|
||||
DIRECTORY ${VITE_BUILD_DIRECTORY} DESTINATION /etc/pipedal/react
|
||||
)
|
||||
install (FILES ${PROJECT_SOURCE_DIR}/build/src/notices.txt
|
||||
DESTINATION /etc/pipedal/react/var/)
|
||||
@@ -98,7 +89,7 @@ endif()
|
||||
|
||||
set(CPACK_GENERATOR "DEB")
|
||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Robin E. R. Davies<rerdavies@gmail.com>") # required
|
||||
set(CPACK_PACKAGE_VENDOR "Robin Davies")
|
||||
set(CPACK_PACKAGE_VENDOR "Robin E. R. Davies")
|
||||
set(CPACK_PACKAGE_DESCRIPTION "IoT guitar effect pedal for Raspberry Pi, with phone-friendly web interface.")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IoT guitar effect pedal for Raspberry Pi")
|
||||
set(CPACK_DEBIAN_PACKAGE_SECTION sound)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright 2021-2024 Robin Davies
|
||||
Copyright 2021-2024 Robin E. R. Davies
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the "Software"),
|
||||
|
||||
@@ -17,7 +17,7 @@ using namespace std;
|
||||
static void PrintHelp()
|
||||
{
|
||||
cout << "nm-pipedal-p2p - P2P Manager for NetworkManager network stack." << endl
|
||||
<< "Copyright (c) 2024 Robin Davies." << endl
|
||||
<< "Copyright (c) 2024 Robin E. R. Davies." << endl
|
||||
<< endl
|
||||
<< "Provides P2P connections on LINUX distributions that use NetworkManager." << endl
|
||||
<< endl
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) 2022 Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
@@ -57,6 +57,7 @@ add_library(PiPedalCommon STATIC
|
||||
include/dbus/org.freedesktop.NetworkManager.Device.Wireless.hpp
|
||||
include/dbus/org.freedesktop.NetworkManager.DHCP4Config.hpp
|
||||
|
||||
Utf8Utils.cpp include/Utf8Utils.hpp
|
||||
NetworkManagerInterfaces.cpp
|
||||
include/NetworkManagerInterfaces.hpp
|
||||
Lv2Log.cpp include/Lv2Log.hpp
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) 2022 Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) 2022 Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
@@ -3,7 +3,7 @@ Copyright (c) 2007, 2008 Johannes Berg
|
||||
Copyright (c) 2007 Andy Lutomirski
|
||||
Copyright (c) 2007 Mike Kershaw
|
||||
Copyright (c) 2008 Luis R. Rodriguez
|
||||
Copyright (c) 2024 Robin Davies
|
||||
Copyright (c) 2024 Robin E. R. Davies
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -3,7 +3,7 @@ Copyright (c) 2007, 2008 Johannes Berg
|
||||
Copyright (c) 2007 Andy Lutomirski
|
||||
Copyright (c) 2007 Mike Kershaw
|
||||
Copyright (c) 2008 Luis R. Rodriguez
|
||||
Copyright (c) 2024 Robin Davies
|
||||
Copyright (c) 2024 Robin E. R. Davies
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -306,3 +306,37 @@ SysExecOutput pipedal::sysExecForOutput(const std::string &program, const std::s
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
int pipedal::SudoExec(int argc, char **argv)
|
||||
{
|
||||
// re-execute with SUDO in order to prompt for SUDO credentials once only.
|
||||
std::vector<char *> args;
|
||||
std::string pkexec = "/usr/bin/sudo"; // staged because "ISO C++ forbids converting a string constant to std::vector<char*>::value_type"(!)
|
||||
args.push_back((char *)(pkexec.c_str()));
|
||||
|
||||
std::string sPath = getSelfExePath();
|
||||
args.push_back(const_cast<char *>(sPath.c_str()));
|
||||
for (int arg = 1; arg < argc; ++arg)
|
||||
{
|
||||
args.push_back(const_cast<char *>(argv[arg]));
|
||||
}
|
||||
|
||||
args.push_back(nullptr);
|
||||
|
||||
char **newArgs = &args[0];
|
||||
|
||||
int pbPid;
|
||||
int returnValue = 0;
|
||||
|
||||
if ((pbPid = fork()) == 0)
|
||||
{
|
||||
return execv(newArgs[0], newArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
waitpid(pbPid, &returnValue, 0);
|
||||
int exitStatus = WEXITSTATUS(returnValue);
|
||||
return exitStatus;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
// Copyright (c) 2023 Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "Utf8Utils.hpp"
|
||||
#include "stdexcept"
|
||||
#include <sstream>
|
||||
|
||||
using namespace std;
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
|
||||
static constexpr uint16_t UTF16_SURROGATE_TAG_MASK = 0xFC00;
|
||||
static constexpr uint16_t UTF16_SURROGATE_1_BASE = 0xD800U;
|
||||
static constexpr uint16_t UTF16_SURROGATE_2_BASE = 0xDC00U;
|
||||
|
||||
|
||||
static constexpr uint32_t UTF8_ONE_BYTE_MASK = 0x80;
|
||||
static constexpr uint32_t UTF8_ONE_BYTE_BITS = 0;
|
||||
static constexpr uint32_t UTF8_TWO_BYTES_MASK = 0xE0;
|
||||
static constexpr uint32_t UTF8_TWO_BYTES_BITS = 0xC0;
|
||||
static constexpr uint32_t UTF8_THREE_BYTES_MASK = 0xF0;
|
||||
static constexpr uint32_t UTF8_THREE_BYTES_BITS = 0xE0;
|
||||
static constexpr uint32_t UTF8_FOUR_BYTES_MASK = 0xF8;
|
||||
static constexpr uint32_t UTF8_FOUR_BYTES_BITS = 0xF0;
|
||||
static constexpr uint32_t UTF8_CONTINUATION_MASK = 0xC0;
|
||||
static constexpr uint32_t UTF8_CONTINUATION_BITS = 0x80;
|
||||
static constexpr bool enforceValidUtf8Encoding = false;
|
||||
|
||||
namespace implementation
|
||||
{
|
||||
void Utf8RangeError()
|
||||
{
|
||||
throw std::range_error("String index out of range.");
|
||||
}
|
||||
}
|
||||
size_t Utf8Index(size_t size, const std::string &text)
|
||||
{
|
||||
size_t index = 0;
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
index = Utf8Increment(index, text);
|
||||
if (index >= text.size())
|
||||
break;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
size_t Utf8Length(const std::string &text)
|
||||
{
|
||||
size_t index = 0;
|
||||
size_t length = 0;
|
||||
while (index < text.length())
|
||||
{
|
||||
index = Utf8Increment(index, text);
|
||||
++length;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
size_t Utf8Increment(size_t size, const std::string &text)
|
||||
{
|
||||
if (size >= text.length())
|
||||
{
|
||||
implementation::Utf8RangeError();
|
||||
}
|
||||
uint8_t uc = (uint8_t)text[size];
|
||||
if (uc < 0x80U)
|
||||
{
|
||||
return size + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
++size;
|
||||
while (size < text.size() && (((uint8_t)text[size]) & 0xC0) == 0x80)
|
||||
{
|
||||
++size;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
}
|
||||
size_t Utf8Decrement(size_t size, const std::string &text)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
implementation::Utf8RangeError();
|
||||
}
|
||||
uint8_t c = text[size - 1];
|
||||
if (c < 0x80U)
|
||||
{
|
||||
return size - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
while ((uint8_t)(text[size - 1] & 0xC0) == 0x80)
|
||||
{
|
||||
--size;
|
||||
}
|
||||
if (size != 0 && (uint8_t)(text[size - 1]) > 0x80)
|
||||
{
|
||||
--size;
|
||||
}
|
||||
else
|
||||
{
|
||||
implementation::Utf8RangeError();
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Utf8Erase(const std::string &text, size_t start, size_t end)
|
||||
{
|
||||
size_t uStart = Utf8Index(start, text);
|
||||
size_t uEnd = Utf8Index(end, text);
|
||||
std::string result;
|
||||
result.reserve(uStart + text.size() - uEnd);
|
||||
result.append(text.begin(), text.begin() + uStart);
|
||||
result.append(text.begin() + uEnd, text.end());
|
||||
return result;
|
||||
}
|
||||
std::string Utf8Substring(const std::string &text, size_t start, size_t end)
|
||||
{
|
||||
size_t uStart = Utf8Index(start, text);
|
||||
size_t uEnd;
|
||||
if (end == (size_t)-1)
|
||||
{
|
||||
uEnd = text.length();
|
||||
}
|
||||
else
|
||||
{
|
||||
uEnd = Utf8Index(end, text);
|
||||
}
|
||||
return text.substr(uStart, uEnd - uStart);
|
||||
}
|
||||
|
||||
static char32_t bits(int nBits, int shift, char32_t c)
|
||||
{
|
||||
return (c >> shift) & ((1 << nBits) - 1);
|
||||
}
|
||||
std::string Utf8FromUtf32(char32_t value)
|
||||
{
|
||||
std::stringstream ss;
|
||||
if (value < 0x7F)
|
||||
{
|
||||
ss << (char)value;
|
||||
}
|
||||
else if (value <= 0x7FF)
|
||||
{
|
||||
ss << (char)(0xC0 + bits(5, 6, value));
|
||||
ss << (char)(0x80 + bits(6, 0, value));
|
||||
}
|
||||
else if (value <= 0xFFFFul)
|
||||
{
|
||||
ss << (char)(0xE0 + bits(4, 12, value));
|
||||
ss << (char)(0x80 + bits(6, 6, value));
|
||||
ss << (char)(0x80 + bits(6, 0, value));
|
||||
}
|
||||
else if (value <= 0x10FFFFul)
|
||||
{
|
||||
ss << (char)(0xF0 + bits(3, 18, value));
|
||||
ss << (char)(0x80 + bits(6, 12, value));
|
||||
ss << (char)(0x80 + bits(6, 6, value));
|
||||
ss << (char)(0x80 + bits(6, 0, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Invalid unicode character.");
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string Utf16ToUtf8(const std::u16string_view&v)
|
||||
{
|
||||
std::stringstream ss;
|
||||
for (auto i = v.begin(); i != v.end(); /**/)
|
||||
{
|
||||
char32_t value = *i++;
|
||||
if ((value & UTF16_SURROGATE_TAG_MASK) == UTF16_SURROGATE_1_BASE)
|
||||
{
|
||||
if (i == v.end())
|
||||
{
|
||||
throw std::runtime_error("Invalid UTF32 character sequence.");
|
||||
}
|
||||
char32_t value2 = *i++;
|
||||
if ((value & UTF16_SURROGATE_TAG_MASK) != UTF16_SURROGATE_2_BASE)
|
||||
{
|
||||
throw std::runtime_error("Invalid UTF32 character sequence.");
|
||||
}
|
||||
value = (value << 10) + value2;
|
||||
}
|
||||
|
||||
if (value < 0x7F)
|
||||
{
|
||||
ss << (char)value;
|
||||
}
|
||||
else if (value <= 0x7FF)
|
||||
{
|
||||
ss << (char)(0xC0 + bits(5, 6, value));
|
||||
ss << (char)(0x80 + bits(6, 0, value));
|
||||
}
|
||||
else if (value <= 0xFFFFul)
|
||||
{
|
||||
ss << (char)(0xE0 + bits(4, 12, value));
|
||||
ss << (char)(0x80 + bits(6, 6, value));
|
||||
ss << (char)(0x80 + bits(6, 0, value));
|
||||
}
|
||||
else if (value <= 0x10FFFFul)
|
||||
{
|
||||
ss << (char)(0xF0 + bits(3, 18, value));
|
||||
ss << (char)(0x80 + bits(6, 12, value));
|
||||
ss << (char)(0x80 + bits(6, 6, value));
|
||||
ss << (char)(0x80 + bits(6, 0, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Invalid unicode character.");
|
||||
}
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
||||
[[noreturn]] static void throw_encoding_error()
|
||||
{
|
||||
throw std::runtime_error("Invalid UTF8 character.");
|
||||
}
|
||||
|
||||
static uint32_t continuation_byte(std::string_view::iterator &p, std::string_view::const_iterator end)
|
||||
{
|
||||
if (p == end)
|
||||
throw_encoding_error();
|
||||
uint8_t c = *p++;
|
||||
if ((c & UTF8_CONTINUATION_MASK) != UTF8_CONTINUATION_BITS)
|
||||
throw_encoding_error();
|
||||
return c & 0x3F;
|
||||
}
|
||||
|
||||
std::u16string Utf8ToUtf16(const std::string_view &v)
|
||||
{
|
||||
std::basic_stringstream<char16_t> os;
|
||||
// convert to utf-32.
|
||||
// convert utf-32 to normalized utf-16.
|
||||
// write non-7-bit and unsafe characters as \uHHHH.
|
||||
|
||||
auto p = v.begin();
|
||||
while (p != v.end())
|
||||
{
|
||||
uint32_t uc;
|
||||
uint8_t c = (uint8_t)*p++;
|
||||
if ((c & UTF8_ONE_BYTE_MASK) == UTF8_ONE_BYTE_BITS)
|
||||
{
|
||||
uc = c;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t c2 = continuation_byte(p, v.end());
|
||||
|
||||
if ((c & UTF8_TWO_BYTES_MASK) == UTF8_TWO_BYTES_BITS)
|
||||
{
|
||||
uint32_t c1 = c & (uint32_t)(~UTF8_TWO_BYTES_MASK);
|
||||
if (c1 <= 1 && enforceValidUtf8Encoding)
|
||||
{
|
||||
// overlong encoding.
|
||||
throw_encoding_error();
|
||||
}
|
||||
uc = (c1 << 6) | c2;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t c3 = continuation_byte(p, v.end());
|
||||
|
||||
if ((c & UTF8_THREE_BYTES_MASK) == UTF8_THREE_BYTES_BITS)
|
||||
{
|
||||
uint32_t c1 = c & (uint32_t)~UTF8_THREE_BYTES_MASK;
|
||||
if (c1 == 0 && c2 < 0x20 && enforceValidUtf8Encoding)
|
||||
{
|
||||
// overlong encoding.
|
||||
throw_encoding_error();
|
||||
}
|
||||
|
||||
uc = (c1) << 12 | (c2 << 6) | c3;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t c4 = continuation_byte(p, v.end());
|
||||
if ((c & UTF8_FOUR_BYTES_MASK) == UTF8_FOUR_BYTES_BITS)
|
||||
{
|
||||
uint32_t c1 = c & (uint32_t)~UTF8_FOUR_BYTES_MASK;
|
||||
if (c1 == 0 && c2 < 0x10 && enforceValidUtf8Encoding)
|
||||
{
|
||||
// overlong encoding.
|
||||
throw_encoding_error();
|
||||
}
|
||||
uc = (c1 << 18) | (c2 << 12) | (c3 << 6) | c4;
|
||||
}
|
||||
else
|
||||
{
|
||||
// outside legal UCS range.
|
||||
throw_encoding_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (uc < 0x10000ul)
|
||||
{
|
||||
os << (char16_t)uc;
|
||||
}
|
||||
else
|
||||
{
|
||||
// write UTF-16 surrogate pair.
|
||||
uc -= 0x10000;
|
||||
|
||||
char16_t s1 = (char16_t)(UTF16_SURROGATE_1_BASE + ((uc >> 10) & 0x3FFu));
|
||||
char16_t s2 = (char16_t)(UTF16_SURROGATE_2_BASE + (uc & 0x03FFu));
|
||||
// surrogate pair.
|
||||
os << s1;
|
||||
os << s2;
|
||||
}
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -43,5 +43,9 @@ namespace pipedal
|
||||
void sysExecTerminate(ProcessId pid_, int termTimeoutMs = 1000, int killTimeoutMs = 500); // returns the process's exit status.
|
||||
int sysExecWait(ProcessId pid);
|
||||
|
||||
// re-execute the current program with current argument, but with sudo.
|
||||
int SudoExec(int argc, char **argv);
|
||||
|
||||
|
||||
std::string getSelfExePath();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2023 Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace pipedal {
|
||||
size_t Utf8Index(size_t size, const std::string &text);
|
||||
size_t Utf8Decrement(size_t size, const std::string &text);
|
||||
size_t Utf8Increment(size_t size, const std::string &text);
|
||||
size_t Utf8Length(const std::string&text);
|
||||
|
||||
std::string Utf8Erase(const std::string&text, size_t start, size_t end);
|
||||
std::string Utf8Substring(const std::string&text, size_t start, size_t end);
|
||||
inline std::string Utf8Substring(const std::string&text, size_t start) {
|
||||
return Utf8Substring(text,start,(size_t)-1);
|
||||
}
|
||||
namespace implementation {
|
||||
void Utf8RangeError();
|
||||
}
|
||||
|
||||
std::string Utf8FromUtf32(char32_t value);
|
||||
|
||||
|
||||
std::u16string Utf8ToUtf16(const std::string_view&s);
|
||||
std::string Utf16ToUtf8(const std::u16string_view&s);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
#define DECLARE_JSON_MAP(CLASSNAME) \
|
||||
static pipedal::json_map::storage_type<CLASSNAME> jmap
|
||||
@@ -592,6 +593,17 @@ namespace pipedal
|
||||
write(obj.get());
|
||||
}
|
||||
}
|
||||
template <typename T>
|
||||
void write(const std::optional<T> &obj)
|
||||
{
|
||||
if (!obj) {
|
||||
write_raw("null");
|
||||
} else {
|
||||
write(obj.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
void write(const std::weak_ptr<T> &obj)
|
||||
{
|
||||
@@ -883,6 +895,20 @@ namespace pipedal
|
||||
}
|
||||
}
|
||||
template <typename T>
|
||||
void read(std::optional<T> *pOptional)
|
||||
{
|
||||
if (peek() == 'n')
|
||||
{
|
||||
consumeToken("null", "Expecting '{' or 'null'.");
|
||||
*pOptional = std::optional<T>();
|
||||
|
||||
} else {
|
||||
T value;
|
||||
read(&value);
|
||||
*pOptional = std::move(value);
|
||||
}
|
||||
}
|
||||
template <typename T>
|
||||
void read(std::weak_ptr<T> *pUniquePtr)
|
||||
{
|
||||
throw std::domain_error("Can't read std::weak_ptr");
|
||||
|
||||
@@ -20,67 +20,105 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#pragma once
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
#include <barrier>
|
||||
#include <atomic>
|
||||
#include <set>
|
||||
|
||||
namespace pipedal {
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
inline bool endsWith(const std::string& str, const std::string& suffix)
|
||||
inline bool endsWith(const std::string &str, const std::string &suffix)
|
||||
{
|
||||
return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix);
|
||||
return str.size() >= suffix.size() && 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix);
|
||||
}
|
||||
void SetThreadName(const std::string &name);
|
||||
|
||||
std::u32string ToUtf32(const std::string &s);
|
||||
|
||||
inline bool isPathSeperator(char c) { return c == '/' || c == std::filesystem::path::preferred_separator;}
|
||||
inline bool isPathSeparator(char c) { return c == '/' || c == std::filesystem::path::preferred_separator; }
|
||||
|
||||
std::string GetHostName();
|
||||
|
||||
std::vector<std::string> split(const std::string &value, char delimiter);
|
||||
|
||||
inline void ReadBarrier() {
|
||||
inline void ReadBarrier()
|
||||
{
|
||||
std::atomic_thread_fence(std::memory_order::acquire);
|
||||
}
|
||||
inline void WriteBarrier() {
|
||||
inline void WriteBarrier()
|
||||
{
|
||||
std::atomic_thread_fence(std::memory_order::release);
|
||||
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool contains(const std::vector<T>&vector,const T&value)
|
||||
inline bool contains(const std::vector<T> &vector, const T &value)
|
||||
{
|
||||
for (auto i = vector.begin(); i != vector.end(); ++i)
|
||||
{
|
||||
if ((*i) == value) {
|
||||
if ((*i) == value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
inline bool contains(const std::vector<std::string>&vector,const char*value)
|
||||
inline bool contains(const std::vector<std::string> &vector, const char *value)
|
||||
{
|
||||
return contains(vector,std::string(value));
|
||||
return contains(vector, std::string(value));
|
||||
}
|
||||
|
||||
class NoCopy {
|
||||
class NoCopy
|
||||
{
|
||||
public:
|
||||
NoCopy() { }
|
||||
NoCopy(const NoCopy&) = delete;
|
||||
NoCopy&operator=(const NoCopy&) = delete;
|
||||
NoCopy() {}
|
||||
NoCopy(const NoCopy &) = delete;
|
||||
NoCopy &operator=(const NoCopy &) = delete;
|
||||
~NoCopy() {}
|
||||
};
|
||||
class NoMove {
|
||||
class NoMove
|
||||
{
|
||||
public:
|
||||
NoMove() { }
|
||||
NoMove(NoMove&&) = delete;
|
||||
NoMove&operator=(NoMove&&) = delete;
|
||||
~NoMove() { }
|
||||
|
||||
NoMove() {}
|
||||
NoMove(NoMove &&) = delete;
|
||||
NoMove &operator=(NoMove &&) = delete;
|
||||
~NoMove() {}
|
||||
};
|
||||
|
||||
bool IsChildDirectory(const std::filesystem::path &path, const std::filesystem::path&rootPath);
|
||||
|
||||
template <typename T>
|
||||
std::set<T> Intersect(const std::set<T> &left, const std::set<T> &right)
|
||||
{
|
||||
std::set<T> result;
|
||||
for (const T &v : left)
|
||||
{
|
||||
if (right.contains(v))
|
||||
{
|
||||
result.insert(v);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
template <typename T>
|
||||
bool IsSubset(const std::set<T> &subset, const std::set<T> &set)
|
||||
{
|
||||
std::set<T> result;
|
||||
for (const T &v : subset)
|
||||
{
|
||||
if (!set.contains(v)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// C locale to lower. Only does 'A'-'Z'.
|
||||
std::string ToLower(const std::string&value);
|
||||
|
||||
std::filesystem::path MakeRelativePath(const std::filesystem::path &path, const std::filesystem::path&parentPath);
|
||||
|
||||
bool IsSubdirectory(const std::filesystem::path &path, const std::filesystem::path &basePath);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) 2022 Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include "util.hpp"
|
||||
#include "ss.hpp"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <thread>
|
||||
@@ -136,3 +137,82 @@ std::vector<std::string> pipedal::split(const std::string &value, char delimiter
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool pipedal::IsChildDirectory(const std::filesystem::path &path, const std::filesystem::path&rootPath)
|
||||
{
|
||||
auto iter = path.begin();
|
||||
|
||||
for (auto i = rootPath.begin(); i != rootPath.end(); ++i)
|
||||
{
|
||||
if (iter == path.end() || *iter != *i)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
std::string pipedal::ToLower(const std::string&value)
|
||||
{
|
||||
std::string result;
|
||||
result.resize(value.length());
|
||||
for (size_t i = 0; i < value.length(); ++i)
|
||||
{
|
||||
result[i] = (char)tolower(value[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
std::filesystem::path pipedal::MakeRelativePath(const std::filesystem::path &path, const std::filesystem::path&parentPath)
|
||||
{
|
||||
if (path.is_relative()) return path;
|
||||
|
||||
auto iter = path.begin();
|
||||
|
||||
for (auto i = parentPath.begin(); i != parentPath.end(); ++i)
|
||||
{
|
||||
if (iter == path.end() || *iter != *i) {
|
||||
// not a child directory of parent directory.
|
||||
throw std::runtime_error("Not a child directory.");
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
std::filesystem::path remander;
|
||||
while (iter != path.end())
|
||||
{
|
||||
remander /= *iter++;
|
||||
}
|
||||
return remander;
|
||||
|
||||
}
|
||||
|
||||
bool pipedal::IsSubdirectory(const std::filesystem::path &path, const std::filesystem::path &basePath)
|
||||
{
|
||||
auto iPath = path.begin();
|
||||
for (auto i = basePath.begin(); i != basePath.end(); ++i)
|
||||
{
|
||||
if (iPath == path.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((*i) != (*iPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
++iPath;
|
||||
}
|
||||
while (iPath != path.end())
|
||||
{
|
||||
if (iPath->string() == "..")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
++iPath;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<a href="https://rerdavies.github.io/pipedal/LicensePiPedal.html"><img src="https://img.shields.io/badge/MIT-MIT?label=license&color=%23808080"/></a>
|
||||
<a href="https://github.com/rerdavies/pipedal/actions"><img src="https://img.shields.io/github/actions/workflow/status/rerdavies/pipedal/cmake.yml?branch=main"/></a>
|
||||
|
||||
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.3.69</a>
|
||||
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.4.70</a>
|
||||
Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
|
||||
Documentation: [https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html).
|
||||
|
||||
@@ -15,7 +15,7 @@ Documentation: [https://rerdavies.github.io/pipedal/Documentation.html](htt
|
||||
#### Important notice: PiPedal has a submitted a request to support sharing of Pipedal presets at [PatchStorage.com](https:///patchstorage.com). We need your support! Please vote for PiPedal [here](https://patchstorage.com/requests/pipedal/).
|
||||
|
||||
|
||||
#### NEW version 1.3.69 Release, providing [snapshots](https://rerdavies.github.io/pipedal/Snaphots.html), and a new Performance View. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
|
||||
#### NEW version 1.4.70 Beta. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+5
-5
@@ -1,24 +1,24 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: pipedal
|
||||
Upstream-Contact: Robin Davies <rerdavies@gmail.com>
|
||||
Upstream-Contact: Robin E. R. Davies <rerdavies@gmail.com>
|
||||
Source: https://www.example.com/software/project
|
||||
|
||||
Files: *
|
||||
Copyright: 2021, Robin Davies
|
||||
Copyright: 2021, Robin E. R. Davies
|
||||
License: Expat
|
||||
|
||||
Files: src/*
|
||||
Copyright: 2021, Robin Davies
|
||||
Copyright: 2021, Robin E. R. Davies
|
||||
License: Expat
|
||||
|
||||
|
||||
Files: react/src
|
||||
Copyright: 2020, Robin Davies.
|
||||
Copyright: 2020, Robin E. R. Davies.
|
||||
License: Expat
|
||||
|
||||
Files: src/Lv2EventBufferWriter.hpp
|
||||
Copyright: 2008-2014, David Robillard <d@drobilla.net>
|
||||
2021, Robin Davies
|
||||
2021, Robin E. R. Davies
|
||||
License: ISC and Expat
|
||||
|
||||
Files: react/node_modules/@babel/*
|
||||
|
||||
Vendored
+2
-2
@@ -1,11 +1,11 @@
|
||||
---
|
||||
/:
|
||||
license: Expat
|
||||
copyright: 2021, Robin Davies.
|
||||
copyright: 2021, Robin E. R. Davies.
|
||||
|
||||
src/react/:
|
||||
license: Expat
|
||||
copyright: 2021, Robin Davies.
|
||||
copyright: 2021, Robin E. R. Davies.
|
||||
|
||||
react/node_modules/webpack/:
|
||||
license: Expat
|
||||
|
||||
@@ -4,7 +4,7 @@ Run the following commands to install build tools required by the PiPedal build.
|
||||
|
||||
# install CMake
|
||||
sudo apt updatee
|
||||
sudo apt install -y cmake ninja-build gcc git
|
||||
sudo apt install -y cmake ninja-build build-essential g++ git
|
||||
|
||||
The PiPedal build process also requires version 12 or later of `node.js`. Type `node --version` to see if you have a version
|
||||
of `node.js` installed already. Otherwise run the following commands as root to install the v14.x lts version of `node.js`:
|
||||
@@ -16,7 +16,7 @@ with
|
||||
|
||||
# install NodeJS latest LTS release.
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
sudo apt-get install -y nodejs npm
|
||||
|
||||
|
||||
Run the following commands to install dependent libraries required by the PiPedal build.
|
||||
|
||||
@@ -5,7 +5,7 @@ page_icon: img/Compiling.jpg
|
||||
|
||||
{% include pageIcon.html %}
|
||||
|
||||
PiPedal has only been tested on Raspberry Pi OS, and Ubuntu, but should run with some modifications on most Linux distributions. Pull requests to correct problems with building PiPedal on other versions of Linux are welcome. However, getting build pre-requisites installed on non-Debian-derived Linux distrubutions (distros that don't use `apt`) will be challenging.
|
||||
PiPedal has only been tested on Raspberry Pi OS, and Ubuntu 24.x, but should run with some modifications on most Linux distributions. Pull requests to correct problems with building PiPedal on other versions of Linux are welcome. However, getting build pre-requisites installed on non-Debian-derived Linux distrubutions (distros that don't use `apt`) will be challenging.
|
||||
|
||||
To build and debug PiPedal using Visual Studio Code, you need 8GB of memory. You can build PiPedal with 2GB of memory.
|
||||
|
||||
|
||||
@@ -3,6 +3,22 @@ page_icon: img/Setup.jpg
|
||||
icon_width: 320px
|
||||
icon_float: left
|
||||
---
|
||||
## Selecting a real-time kernel on Ubuntu.
|
||||
|
||||
This step is unneccesary on Raspberry Pi OS, which installs an RT_PREEMPT kernel by default.
|
||||
|
||||
By default, Ubuntu installs a PREEMT_DYNAMIC kernel, configured to run with volutary preemption. You will need to change this. You can do this by running the following command after you have installed PiPedal:
|
||||
|
||||
```
|
||||
pipedal_kconfig
|
||||
```
|
||||
|
||||
In order to support real-time audio processing, select Preempt=full.
|
||||
|
||||
An RT_PREEMPT kernel provides slightly better realtime audio than a PREEMPT_DYNAMIC/preempt=full kernel, but the PREEMPT_DYNAMIC should be perfectly adequate. If you are having problems with audio dropouts, you may want to try an RT_PREEMPT kernel instead. Ubuntu provides a pre-built RT_PREEMPT kernel; but on our test systems, you can acheive stable low-latency audio using only the PREEMPT_DYNAMIC/preempt=full kernel. The pre-build Ubuntu RT_PREEMPT
|
||||
kernel is only available if yo have an Ubuntu Pro subscription. But it is relatively easy to build the Ubuntu RT_PREEMPT kernel from source. That is, however, outside the scope of this document. We recommend using the PREEMPT_DYNAMIC/preempt=full Ubuntu kernel.
|
||||
|
||||
|
||||
## Configuring PiPedal After Installation
|
||||
|
||||
|
||||
|
||||
+12
-9
@@ -2,7 +2,7 @@
|
||||
|
||||
PipPedal consists of the following components:
|
||||
|
||||
* A web application build in React, found in the react subdirectory.
|
||||
* A web application built using vite/React, found in the `vite` subdirectory.
|
||||
|
||||
* `pipedald`
|
||||
|
||||
@@ -40,7 +40,7 @@ same /etc/pipedal and /var/pipedal directories as an instance of `pipedal` runn
|
||||
under systemd. Note that when running under systemd, `pipedald` runs under an unprivileged
|
||||
`piedal_d` service account, and relies it's group (also 'pipedal_d`) in order to access its
|
||||
data files, and to communicate with the `pipedaladmin` service, which does run with root
|
||||
priveleges when `pipedald` needs to perform operations that do required root privileges.
|
||||
privileges when `pipedald` needs to perform operations that do required root privileges (e.g. shutdown/reboot and starting and stopping WiFi services).
|
||||
|
||||
For what it's worth, `pipedaldmin` is virtually undebuggable, becuase it does require root privileges to run.
|
||||
If you really _must_ debug pipedaladmin, you can fire up a sudo isntance of Visual Studio Code
|
||||
@@ -53,8 +53,8 @@ audio and Wi-Fi configuration changes) may fail if the pipedaladmind service is
|
||||
with pipedaladmin via Unix docket that can only be opened by members of the pipedal_d group. So if you have
|
||||
added your own account to the `pipedal_d` group, debug instances of `pipedald` will in fact work properly.
|
||||
|
||||
In production, the pipedald web server serves the PiPedal web socket, as well as static HTML from the built
|
||||
react components. But while debugging, it is much more convenient to use the React debug server for
|
||||
In production, the pipedald web server serves the PiPedal web socket, as well as static HTML from the built
|
||||
vite/React components. But while debugging, it is much more convenient to use the Vite debug server for
|
||||
React sources, and configure pipedald to serve only the websocket.
|
||||
|
||||
Note that a debug instance of `pipedald` cannot bind to port 80, since that requires either root privileges or
|
||||
@@ -72,23 +72,26 @@ make the systemd instance of `pipedald` bind to port 8080 instead of port 80:
|
||||
|
||||
(which will also restart the `pipedald` service).
|
||||
|
||||
To start the React debug server, from a shell, `cd` to the react directory, and run `./start`. The react debug
|
||||
server will detect any changes to React sources, and rebuild them automatically (no build step required).
|
||||
Actual debugging is performed using the Chrome debugger (which is remarkably well integrated with React).
|
||||
To start the web app debug server, from a shell, `cd` to the `./vite` directory, and run `npm run dev`. The
|
||||
Vite debug server will automatically detect any changes to web app sources, and rebuild them automatically (no build step required). Note that the `pipedald` service must be running in order for the web app to function properly, either
|
||||
as a the `pipedald` service, or by running `pipedald` in a debugger.
|
||||
Actual debugging is performed using the Chrome debugger (which is remarkably well integrated with Vite/React).
|
||||
You won't actually see changes to the version of the systemd version of the static web app until you
|
||||
do a full _Release_ or _RelWithDebInfo_ build of PiPedal, followed by running `./install.sh` which pushes
|
||||
the built react app in the location where the systemd version of `pipedald` serves static web content.
|
||||
|
||||
By default, the debug React app will attempt to contact the pipedald server on ws:*:8080 -- the address on which
|
||||
the debug version of pipedald listens on. This can be reconfigured
|
||||
in the file `react/src/public/var/config.json` if desired. If you connect to the the pipedald server port (port 80), pipedald intercepts requests for this file and points the react app at itself, so the file has no effect when running in production.
|
||||
in the file `react/src/public/var/config.json` if desired. If you connect to the the pipedald server port (port 80), pipedald intercepts requests for `http://./var/config.json` and points the react app at itself, so the file has no effect when running in production.
|
||||
|
||||
The React app will display the message "Error: Failed to connect to the server", until you start the pipedald websocket server in the VSCode debugger. However, it's quite reasonable to point the react debug app at a systemd instance of the pipedald server instead, if you don't intend to debug C++ code.
|
||||
|
||||
react/public/var/config.json:
|
||||
{
|
||||
...
|
||||
"socket_server_port": 8080, # (PiPedald's port number)
|
||||
# (PiPedald's port number. 80 for the production service, 8080 (by default) for
|
||||
# pipedal running under a debugger.
|
||||
"socket_server_port": 8080,
|
||||
"socket_server_address": "*",
|
||||
...
|
||||
}
|
||||
|
||||
+8
-7
@@ -13,24 +13,25 @@ page_icon: img/Install4.jpg
|
||||
|
||||
Download the most recent Debian (.deb) package for your platform:
|
||||
|
||||
- [Raspberry Pi OS bookworm (64-bit) v1.3.69](https://github.com/rerdavies/pipedal/releases/download/)
|
||||
- [Ubuntu/Raspberry Pi OS bullseyeye (64-bit) v1.2.31](https://github.com/rerdavies/pipedal/releases/download/v1.1.31/pipedal_1.1.31_arm64.deb)
|
||||
- [Raspberry Pi OS bookworm (aarch64) v1.4.70 Beta](https://github.com/rerdavies/pipedal/releases/download/)
|
||||
- [Ubuntu 24.x (aarch64) v1.4.70 Alpha](https://github.com/rerdavies/pipedal/releases/download/)
|
||||
- [Ubuntu 24.x (amd64) v1.4.70 Alpha](https://github.com/rerdavies/pipedal/releases/download/)
|
||||
|
||||
Version 1.3.69 has not yet been tested on Ubuntu or Raspberry Pi OS bullseye. On these platforms, we recommend that you use version 1.1.31.
|
||||
|
||||
Install the package by running
|
||||
Version 1.4.70 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), and Ubuntu 24.10 (aarch64). Download the appropriate package for your platform, and install using the following proceduer:
|
||||
|
||||
```
|
||||
sudo apt update
|
||||
cd ~/Downloads
|
||||
sudo apt-get install pipedal_1.3.69_arm64.deb
|
||||
sudo apt-get install pipedal_1.4.70_arm64.deb
|
||||
```
|
||||
Adjust accordingly if you have downloaded v1.1.31.
|
||||
Adjust accordingly depending which package you downloaded.
|
||||
|
||||
On Raspberry Pi OS, if you have a graphical desktop installed, you can also install the package by double-clicking on the downloaded package in the File Manager.
|
||||
|
||||
|
||||
After installing, follow the instructions in [Configuring PiPedal after Installation](Configuring.md).
|
||||
After installing, follow the instructions in [Configuring PiPedal after Installation](Configuring.md). If
|
||||
you are using Ubuntu, you will need to reconfigure Ubuntu to use a real-time kernel.
|
||||
|
||||
|
||||
--------
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright 2021-2024 Robin Davies
|
||||
Copyright 2021-2024 Robin E. R. Davies
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the "Software"),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright 2021-2023 Robin Davies
|
||||
Copyright 2021-2023 Robin E. R. Davies
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the "Software"),
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# Release Notes
|
||||
|
||||
## PiPedal 1.4.70 Beta
|
||||
|
||||
- Ubuntu 24.x compatibility (arm64 and amd64)
|
||||
- Ubuntu CPU Freq/governor/thermal status display.
|
||||
- Support for WiFi on on Ubuntu
|
||||
- TooB Plugin compatibility issues on x64/amd64 (handling of NaNs)
|
||||
- Restart audio automatically after ALSA stalls.
|
||||
- Improvements to File Upload UI for plugins that have complex file types (e.g. Ratatouille)
|
||||
- Support for LV2 Trigger controls, and MIDI bindings to LV2 trigger controls (common in looper and recording/playback plugsin).
|
||||
- MIDI bindings for LV2 trigger controls.
|
||||
- Port Web app from CRA to Vite framework to avoid deprecated dependencies.
|
||||
- ToobAmp 1.1.57: Correctly set file path of the default file property for Toob Convolution Reverb.
|
||||
|
||||
## PiPedal 1.3.69
|
||||
|
||||
Bug fixes:
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
display: block;
|
||||
height: 24px;
|
||||
}
|
||||
img-icon {
|
||||
width: 24px;
|
||||
height:
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="footer-offset">
|
||||
@@ -41,6 +45,12 @@
|
||||
|
||||
<span style="margin-right: 60px" > </span>
|
||||
|
||||
<a rel="me" href="https://rerdavies.github.io" target="_blank" title="rerdavies.github.io"><img
|
||||
class="svg-icon" src="img/rerdavies.svg" />
|
||||
</img>rerdavies.github.io</a>
|
||||
|
||||
|
||||
<span style="margin-right: 60px" > </span>
|
||||
|
||||
<a rel="me" href="https://github.com/rerdavies" target="_blank" title="rerdavies"><svg
|
||||
class="svg-icon grey"><use xlink:href="{{ '/assets/minima-social-icons.svg#github' | relative_url }}"></use>
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
{
|
||||
let galleryPath = "gallery/";
|
||||
let images = [
|
||||
"thunder.png",
|
||||
"rig.jpg",
|
||||
"dark-sshot1.png",
|
||||
"jazz.png",
|
||||
"midi-bindings.png",
|
||||
"hotspot.png"
|
||||
"hotspot.png",
|
||||
"rig.jpg",
|
||||
"thunder.png"
|
||||
];
|
||||
let captions = [
|
||||
"PiPedal Guitar Effects Pedal running on a Raspberry Pi.",
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
Download the most recent Debian (.deb) package for your platform:
|
||||
|
||||
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.3.69/pipedal_1.3.69_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.3.69</a>
|
||||
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.4.70/pipedal_1.4.70_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.4.70</a>
|
||||
|
||||
|
||||
Install the package by running
|
||||
@@ -12,7 +12,7 @@ Install the package by running
|
||||
```
|
||||
sudo apt update
|
||||
cd ~/Downloads
|
||||
sudo apt-get install ./pipedal_1.3.69_arm64.deb
|
||||
sudo apt-get install ./pipedal_1.4.70_arm64.deb
|
||||
```
|
||||
|
||||
Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
+6
-5
@@ -1,16 +1,17 @@
|
||||
|
||||
|
||||
<img src="GithubBanner.png" width="100%"/>
|
||||
<a href="Installing.html"><i>v1.3.69</i></a>
|
||||
<a href="Installing.html"><i>v1.4.70</i></a>
|
||||
|
||||
|
||||
|
||||
To download PiPedal, click [here](download.md).
|
||||
To view PiPedal documentation, click [here](Documentation.md).
|
||||
|
||||
#### Important notice: PiPedal has a submitted a request to support sharing of Pipedal presets at [PatchStorage.com](https:///patchstorage.com). We urgently need your support! Please vote for PiPedal [here](https://patchstorage.com/requests/pipedal/).
|
||||
#### Important notice: [PatchStorage.com](https:///patchstorage.com) has added support for PiPedal. We urgently need your support! I you could please upload patches [here](https://patchstorage.com/platform/pipedal/) I would
|
||||
be enormously grateful. Note that media and model files are included in patch files, so you can share patches that
|
||||
include model files or audio samples. If you make use of plugins that are not included in the PiPedal distribution, please include a link to the plugin in the patch description.
|
||||
|
||||
#### NEW version 1.3.69 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
|
||||
#### NEW version 1.4.70 Beta, plus alpha releases of
|
||||
PiPedal for Ubuntu on aarch64 and amd64. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ cabir:impulseFile3
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
rdfs:comment """
|
||||
|
||||
TooB Cab IR is a convolution-based guitar cabinet impulse response simulator.
|
||||
|
||||
@@ -49,7 +49,7 @@ toob:frequencyResponseVector
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "TooB CabSim";
|
||||
|
||||
@@ -53,7 +53,7 @@ toobimpulse:impulseFile
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
rdfs:comment """
|
||||
|
||||
Convolution reverb is a notoriously compute-intensive effect. If you are having performance issues, use the Max T control to constrain the length of the impulse file to
|
||||
|
||||
@@ -51,7 +51,7 @@ toobimpulse:impulseFile
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
rdfs:comment """
|
||||
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ inputStage:filterGroup
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "TooB Input";
|
||||
|
||||
@@ -67,7 +67,7 @@ pstage:stage3
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "Power Stage";
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
rdfs:comment "TooB spectrum analyzer" ;
|
||||
|
||||
mod:brand "TooB";
|
||||
|
||||
@@ -55,7 +55,7 @@ tonestack:eqGroup
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
|
||||
uiext:ui <http://two-play.com/plugins/toob-tone-stack-ui>;
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -40,7 +40,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
rdfs:comment """
|
||||
Emulation of a Boss CE-2 Chorus.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
rdfs:comment """
|
||||
A straightforward no-frills digital delay.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
rdfs:comment """
|
||||
Emulation of a Boss BF-2 Flanger, based on circuit analysis and simulation of the original.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
rdfs:comment """
|
||||
Digital emulation of a Boss BF-2 Flanger.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
rdfs:comment """
|
||||
Toob Freeverb is an Lv2 implementation of the famous Freeverb reverb effect. FreeVerb delivers a well-balanced reverb with very little tonal coloration.
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ toobml:sagGroup
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
rdfs:comment """
|
||||
The TooB ML Amplifier plugin provides emulation of a variety of amplifiers and overdrive pedals that are implemented
|
||||
using neural-network-based machine learning models of real amplifiers.
|
||||
@@ -313,7 +313,7 @@ and uses model files from the GuitarML Neural Pi project (https://github.com/Gui
|
||||
lv2:default 3.0 ;
|
||||
lv2:minimum 0.0 ;
|
||||
lv2:maximum 10.0 ;
|
||||
rdfs:comment "Model gain parameter (used by BluesJR model only)"
|
||||
rdfs:comment "Model gain parameter (if supported by model) ()"
|
||||
],
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
|
||||
@@ -61,7 +61,7 @@ toobNam:eqGroup
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
rdfs:comment """
|
||||
A port of Steven Atkinson's Neural Amp Modeler to LV2.
|
||||
|
||||
@@ -326,7 +326,7 @@ toobNam:ui
|
||||
pipedal_ui:fileProperties
|
||||
[
|
||||
a pipedal_ui:fileProperty;
|
||||
rdfs:label "Model (*.nam)" ;
|
||||
rdfs:label "Model" ;
|
||||
pipedal_ui:directory "NeuralAmpModels";
|
||||
lv2:index 2 ;
|
||||
pipedal_ui:patchProperty toobNam:modelFile ;
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 53 ;
|
||||
lv2:microVersion 57 ;
|
||||
rdfs:comment """
|
||||
TooB Tuner is a chromatic guitar tuner.
|
||||
""" ;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+1
-1
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/bash
|
||||
# Install react dependencies
|
||||
cd react
|
||||
cd vite
|
||||
npm install
|
||||
cd ..
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
/package-lock.json
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -1,208 +0,0 @@
|
||||
|
||||
|
||||
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)
|
||||
set(BUILD_REACT_ARGS run build)
|
||||
endif()
|
||||
|
||||
set (BUILD_DIRECTORY ${PROJECT_SOURCE_DIR}/build/react/build)
|
||||
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${BUILD_DIRECTORY}/index.html
|
||||
COMMAND ${BUILD_REACT}
|
||||
ARGS ${BUILD_REACT_ARGS}
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/react
|
||||
|
||||
DEPENDS
|
||||
src/PiPedalError.tsx
|
||||
src/OldDeleteIcon.tsx
|
||||
src/MaterialColors.tsx
|
||||
src/Pedalboard.tsx
|
||||
src/SystemMidiBindingView.tsx
|
||||
src/GovernorSettings.tsx
|
||||
src/ObservableProperty.tsx
|
||||
src/SnapshotEditor.tsx
|
||||
src/SelectMidiChannelsDialog.tsx
|
||||
src/AlsaMidiDeviceInfo.tsx
|
||||
src/WifiDirectConfigSettings.tsx
|
||||
src/WifiConfigSettings.tsx
|
||||
src/SnapshotButton.tsx
|
||||
src/WindowScale.tsx
|
||||
src/JsonAtom.tsx
|
||||
src/PiPedalSocket.tsx
|
||||
src/TemporaryDrawer.tsx
|
||||
src/DarkMode.tsx
|
||||
src/MidiBindingsDialog.tsx
|
||||
src/MidiBinding.tsx
|
||||
src/SnapshotDialog.tsx
|
||||
src/SettingsDialog.tsx
|
||||
src/PedalboardView.tsx
|
||||
src/reportWebVitals.ts
|
||||
src/OptionsDialog.tsx
|
||||
src/SelectChannelsDialog.tsx
|
||||
src/PluginPresetSelector.tsx
|
||||
src/VuMeter.tsx
|
||||
src/ToobFrequencyResponseView.tsx
|
||||
src/SystemMidiBindingsDialog.tsx
|
||||
src/FullScreenIME.tsx
|
||||
src/App.tsx
|
||||
src/ToobInputStageView.tsx
|
||||
src/XxxSnippet.tsx
|
||||
src/JackServerSettings.tsx
|
||||
src/ToobWaveShapeView.tsx
|
||||
src/ToobPowerStage2View.tsx
|
||||
src/PluginPreset.tsx
|
||||
src/logo.svg
|
||||
src/Banks.tsx
|
||||
src/NoChangePassword.tsx
|
||||
src/SearchFilter.tsx
|
||||
src/FilePropertyDirectorySelectDialog.tsx
|
||||
src/GxTunerView.tsx
|
||||
src/UpdateDialog.tsx
|
||||
src/svg/file_download_black_24dp.svg
|
||||
src/svg/fx_filter.svg
|
||||
src/svg/fx_pitch.svg
|
||||
src/svg/fx_distortion.svg
|
||||
src/svg/fx_split_b.svg
|
||||
src/svg/fx_chorus.svg
|
||||
src/svg/fx_limiter.svg
|
||||
src/svg/fx_phaser.svg
|
||||
src/svg/snapshot_0.svg
|
||||
src/svg/snapshot_3.svg
|
||||
src/svg/file_upload_black_24dp.svg
|
||||
src/svg/ic_bank.svg
|
||||
src/svg/fx_function.svg
|
||||
src/svg/fx_dial.svg
|
||||
src/svg/ic_settings.svg
|
||||
src/svg/fx_split_a.svg
|
||||
src/svg/fx_empty.svg
|
||||
src/svg/fx_amplifier.svg
|
||||
src/svg/fx_spectral.svg
|
||||
src/svg/fx_eq.svg
|
||||
src/svg/ic_edit_banks.svg
|
||||
src/svg/ic_help_outline.svg
|
||||
src/svg/ic_save_bank_as.svg
|
||||
src/svg/ic_pluginpreset2.svg
|
||||
src/svg/fx_flanger2.svg
|
||||
src/svg/snapshot_6.svg
|
||||
src/svg/fx_lr.svg
|
||||
src/svg/fx_generator.svg
|
||||
src/svg/snapshot_1.svg
|
||||
src/svg/fx_reverb.svg
|
||||
src/svg/fx_plugin.svg
|
||||
src/svg/snapshot_4.svg
|
||||
src/svg/drive_file_rename_outline_black_24dp.svg
|
||||
src/svg/snapshot_2.svg
|
||||
src/svg/fx_instrument.svg
|
||||
src/svg/fx_modulator.svg
|
||||
src/svg/fx_error.svg
|
||||
src/svg/fx_simulator.svg
|
||||
src/svg/fx_filter_hp.svg
|
||||
src/svg/fx_compressor.svg
|
||||
src/svg/fx_gate.svg
|
||||
src/svg/fx_terminal.svg
|
||||
src/svg/ic_midi.svg
|
||||
src/svg/fx_oscillator.svg
|
||||
src/svg/fx_analyzer.svg
|
||||
src/svg/fx_flanger.svg
|
||||
src/svg/old_delete_outline_24dp.svg
|
||||
src/svg/ic_presets.svg
|
||||
src/svg/fx_delay.svg
|
||||
src/svg/fx_parametric_eq.svg
|
||||
src/svg/fx_spatial.svg
|
||||
src/svg/fx_converter.svg
|
||||
src/svg/fx_constant.svg
|
||||
src/svg/snapshot_5.svg
|
||||
src/svg/ic_pluginpreset.svg
|
||||
src/svg/fx_mixer.svg
|
||||
src/svg/fx_utility.svg
|
||||
src/Updater.tsx
|
||||
src/UploadPresetDialog.tsx
|
||||
src/RadioSelectDialog.tsx
|
||||
src/setupTests.ts
|
||||
src/ToobCabSimView.tsx
|
||||
src/RenameDialog.tsx
|
||||
src/SplitControlView.tsx
|
||||
src/SelectHoverBackground.tsx
|
||||
src/IControlViewFactory.tsx
|
||||
src/OkCancelDialog.tsx
|
||||
src/BankDialog.tsx
|
||||
src/PluginPresetsDialog.tsx
|
||||
src/App.test.tsx
|
||||
src/ControlViewFactory.tsx
|
||||
src/DraggableGrid.tsx
|
||||
src/ZoomedDial.tsx
|
||||
src/index.css
|
||||
src/Pedal.tsx
|
||||
src/Draggable.tsx
|
||||
src/PluginIcon.tsx
|
||||
src/react-app-env.d.ts
|
||||
src/TextFieldEx.tsx
|
||||
src/FilePropertyDialog.tsx
|
||||
src/FilePropertyDirectoryTree.tsx
|
||||
src/StringBuilder.tsx
|
||||
src/MainPage.tsx
|
||||
src/PluginInfoDialog.tsx
|
||||
src/AppThemed.tsx
|
||||
src/Lv2Plugin.tsx
|
||||
src/ToobSpectrumResponseView.tsx
|
||||
src/PerformanceView.tsx
|
||||
src/JackServerSettingsDialog.tsx
|
||||
src/Utility.tsx
|
||||
src/JackStatusView.tsx
|
||||
src/ToobSpectrumAnalyzerView.tsx
|
||||
src/SvgPathBuilder.tsx
|
||||
src/PresetSelector.tsx
|
||||
src/PresetDialog.tsx
|
||||
src/WifiChannel.tsx
|
||||
src/AboutDialog.tsx
|
||||
src/GxTunerControl.tsx
|
||||
src/VirtualKeyboardHandler.tsx
|
||||
src/PluginControl.tsx
|
||||
src/LoadPluginDialog.tsx
|
||||
src/ZoomedUiControl.tsx
|
||||
src/AlsaDeviceInfo.tsx
|
||||
src/ToobToneStackView.tsx
|
||||
src/UploadFileDialog.tsx
|
||||
src/PluginOutputControl.tsx
|
||||
src/SelectThemeDialog.tsx
|
||||
src/Rect.tsx
|
||||
src/AppThemed.css
|
||||
src/ColorDropdownButton.tsx
|
||||
src/AndroidHost.tsx
|
||||
src/SnapshotPanel.tsx
|
||||
src/ToobMLView.tsx
|
||||
src/WifiConfigDialog.tsx
|
||||
src/Rectangle.tsx
|
||||
src/SnapshotColors.tsx
|
||||
src/PiPedalModel.tsx
|
||||
src/ObservableEvent.tsx
|
||||
src/JackHostStatus.tsx
|
||||
src/index.tsx
|
||||
src/FilePropertyControl.tsx
|
||||
src/WifiDirectConfigDialog.tsx
|
||||
src/MidiBindingView.tsx
|
||||
src/PluginControlView.tsx
|
||||
src/SplitUiControls.tsx
|
||||
src/PluginClass.tsx
|
||||
src/DialogEx.tsx
|
||||
src/Jack.tsx
|
||||
src/ResizeResponsiveComponent.tsx
|
||||
src/NumericInput.tsx
|
||||
src/SnapshotPropertiesDialog.tsx
|
||||
src/SearchControl.tsx
|
||||
src/Units.tsx
|
||||
|
||||
)
|
||||
|
||||
add_custom_target (
|
||||
ReactBuild ALL
|
||||
DEPENDS ${BUILD_DIRECTORY}/index.html
|
||||
)
|
||||
@@ -1,46 +0,0 @@
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.\
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
@@ -1,64 +0,0 @@
|
||||
{
|
||||
"name": "pipedal",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.7.1",
|
||||
"@emotion/styled": "^11.6.0",
|
||||
"@mui/icons-material": "^5.4.1",
|
||||
"@mui/material": "^5.4.1",
|
||||
"@mui/styles": "^5.4.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/node": "^12.20.15",
|
||||
"@types/react": "^17.0.11",
|
||||
"@types/react-dom": "^17.0.8",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-remark": "^2.1.0",
|
||||
"react-scripts": "^5.0.1",
|
||||
"react-virtualized-auto-sizer": "^1.0.6",
|
||||
"react-window": "^1.8.6",
|
||||
"typescript": "^4.3.4",
|
||||
"web-vitals": "^1.1.2",
|
||||
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.2.11"
|
||||
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "BUILD_PATH='../build/react/build' react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
],
|
||||
"rules": {
|
||||
"eqeqeq": 2,
|
||||
"no-unused-expressions": 2,
|
||||
"@typescript-eslint/no-unused-vars": 2
|
||||
}
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -1,28 +0,0 @@
|
||||
found 8 vulnerabilities (4 moderate, 4 high)
|
||||
run `npm audit fix` to fix them, or `npm audit` for details
|
||||
|
||||
Created git commit.
|
||||
|
||||
Success! Created my-app at /home/patch/src/piddle/react/my-app
|
||||
Inside that directory, you can run several commands:
|
||||
|
||||
npm start
|
||||
Starts the development server.
|
||||
|
||||
npm run build
|
||||
Bundles the app into static files for production.
|
||||
|
||||
npm test
|
||||
Starts the test runner.
|
||||
|
||||
npm run eject
|
||||
Removes this tool and copies build dependencies, configuration files
|
||||
and scripts into the app directory. If you do this, you canât go back!
|
||||
|
||||
We suggest that you begin by typing:
|
||||
|
||||
cd my-app
|
||||
npm start
|
||||
|
||||
Happy hacking!
|
||||
|
||||
@@ -1,296 +0,0 @@
|
||||
|
||||
import React, { SyntheticEvent, Component } from 'react';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
|
||||
import JackHostStatus from './JackHostStatus';
|
||||
import { PiPedalError } from './PiPedalError';
|
||||
import DialogEx from './DialogEx';
|
||||
|
||||
import { Theme, createStyles } from '@mui/material/styles';
|
||||
import { withStyles, WithStyles } from '@mui/styles';
|
||||
import Slide, { SlideProps } from '@mui/material/Slide';
|
||||
|
||||
interface AboutDialogProps extends WithStyles<typeof styles> {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
interface AboutDialogState {
|
||||
jackStatus?: JackHostStatus;
|
||||
|
||||
openSourceNotices: string;
|
||||
};
|
||||
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
dialogAppBar: {
|
||||
position: 'relative',
|
||||
top: 0, left: 0
|
||||
},
|
||||
dialogTitle: {
|
||||
marginLeft: theme.spacing(2),
|
||||
flex: 1,
|
||||
},
|
||||
sectionHead: {
|
||||
marginLeft: 24,
|
||||
marginRight: 24,
|
||||
marginTop: 16,
|
||||
paddingBottom: 12
|
||||
|
||||
},
|
||||
textBlock: {
|
||||
marginLeft: 24,
|
||||
marginRight: 24,
|
||||
marginTop: 16,
|
||||
paddingBottom: 0,
|
||||
opacity: 0.95
|
||||
|
||||
},
|
||||
textBlockIndented: {
|
||||
marginLeft: 40,
|
||||
marginRight: 24,
|
||||
marginTop: 16,
|
||||
paddingBottom: 0,
|
||||
opacity: 0.95
|
||||
|
||||
},
|
||||
|
||||
setting: {
|
||||
minHeight: 64,
|
||||
width: "100%",
|
||||
textAlign: "left",
|
||||
paddingLeft: 22,
|
||||
paddingRight: 22
|
||||
},
|
||||
primaryItem: {
|
||||
|
||||
},
|
||||
secondaryItem: {
|
||||
|
||||
},
|
||||
can_select: {
|
||||
userSelect: "text",
|
||||
cursor: "text"
|
||||
},
|
||||
no_select: {
|
||||
userSelect: "none",
|
||||
cursor: "text"
|
||||
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
const Transition = React.forwardRef(function Transition(
|
||||
props: SlideProps, ref: React.Ref<unknown>
|
||||
) {
|
||||
return (<Slide direction="up" ref={ref} {...props} />);
|
||||
});
|
||||
|
||||
|
||||
const AboutDialog = withStyles(styles, { withTheme: true })(
|
||||
|
||||
class extends Component<AboutDialogProps, AboutDialogState> {
|
||||
|
||||
model: PiPedalModel;
|
||||
refNotices: React.RefObject<HTMLDivElement>;
|
||||
|
||||
|
||||
constructor(props: AboutDialogProps) {
|
||||
super(props);
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.refNotices = React.createRef();
|
||||
this.handleDialogClose = this.handleDialogClose.bind(this);
|
||||
this.state = {
|
||||
jackStatus: undefined,
|
||||
openSourceNotices: ""
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
mounted: boolean = false;
|
||||
subscribed: boolean = false;
|
||||
|
||||
tick() {
|
||||
if (this.model.state.get() === State.Ready) {
|
||||
this.model.getJackStatus()
|
||||
.then(jackStatus => {
|
||||
this.setState({ jackStatus: jackStatus });
|
||||
})
|
||||
.catch(error => { /* ignore*/ });
|
||||
}
|
||||
}
|
||||
|
||||
timerHandle?: NodeJS.Timeout;
|
||||
|
||||
updateNotifications() {
|
||||
let subscribed = this.mounted && this.props.open;
|
||||
if (subscribed !== this.subscribed) {
|
||||
if (subscribed) {
|
||||
this.timerHandle = setInterval(() => this.tick(), 1000);
|
||||
} else {
|
||||
if (this.timerHandle) {
|
||||
clearInterval(this.timerHandle);
|
||||
this.timerHandle = undefined;
|
||||
}
|
||||
}
|
||||
this.subscribed = subscribed;
|
||||
}
|
||||
}
|
||||
|
||||
startFossRequest() {
|
||||
if (this.state.openSourceNotices === "") {
|
||||
fetch("var/notices.txt")
|
||||
.then((request) => {
|
||||
if (!request.ok) {
|
||||
throw new PiPedalError("notices request failed.");
|
||||
}
|
||||
return request.text();
|
||||
})
|
||||
.then((text) => {
|
||||
if (this.mounted) {
|
||||
this.setState({ openSourceNotices: text });
|
||||
}
|
||||
|
||||
})
|
||||
.catch((err) => {
|
||||
// ok in debug builds. File doesn't get placed until install time.
|
||||
console.log("Failed to fetch open-source notices. " + err.toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
super.componentDidMount?.();
|
||||
this.mounted = true;
|
||||
this.updateNotifications();
|
||||
this.startFossRequest();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount?.();
|
||||
this.mounted = false;
|
||||
this.updateNotifications();
|
||||
}
|
||||
componentDidUpdate(prevProps: Readonly<AboutDialogProps>, prevState: Readonly<AboutDialogState>, snapshot: any): void {
|
||||
super.componentDidUpdate?.(prevProps, prevState, snapshot);
|
||||
this.updateNotifications();
|
||||
}
|
||||
|
||||
handleDialogClose(e: SyntheticEvent) {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let classes = this.props.classes;
|
||||
let addressKey = 0;
|
||||
let serverVersion = this.model.serverVersion?.serverVersion ?? "";
|
||||
let nPos = serverVersion.indexOf(' ');
|
||||
if (nPos !== -1) {
|
||||
serverVersion = serverVersion.substring(nPos + 1);
|
||||
}
|
||||
return (
|
||||
<DialogEx tag="about" fullScreen open={this.props.open}
|
||||
onClose={() => { this.props.onClose() }} TransitionComponent={Transition}
|
||||
onEnterKey={() => { this.props.onClose() }}
|
||||
style={{ userSelect: "none" }}
|
||||
>
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
<AppBar className={classes.dialogAppBar} >
|
||||
<Toolbar>
|
||||
<IconButton edge="start" color="inherit" onClick={this.handleDialogClose} aria-label="back"
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
<Typography noWrap variant="h6" className={classes.dialogTitle}>
|
||||
About
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
</div>
|
||||
<div style={{
|
||||
flex: "1 1 auto", position: "relative", overflow: "hidden",
|
||||
overflowX: "hidden", overflowY: "auto", userSelect: "text"
|
||||
}}
|
||||
>
|
||||
<div id="debug_info" className={classes.can_select} style={{ margin: 24 }}>
|
||||
<div style={{ display: "flex", flexFlow: "row nowrap" }}>
|
||||
<Typography noWrap display="block" variant="h6" color="textPrimary" style={{flexGrow: 1, flexShrink: 1}}>
|
||||
PiPedal <span style={{ fontSize: "0.7em" }}>
|
||||
{serverVersion
|
||||
+ (this.model.serverVersion?.debug ? " (Debug)" : "")}
|
||||
</span>
|
||||
</Typography>
|
||||
|
||||
</div>
|
||||
<Typography noWrap display="block" variant="body2" style={{ marginBottom: 12 }} >
|
||||
Copyright © 2022-2024 Robin Davies.
|
||||
</Typography>
|
||||
{this.model.isAndroidHosted() && (
|
||||
<Typography noWrap display="block" variant="body2" style={{ marginBottom: 0 }} >
|
||||
{this.model.getAndroidHostVersion()}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{this.model.isAndroidHosted() && (
|
||||
<Typography noWrap display="block" variant="body2" style={{ marginBottom: 12 }} >
|
||||
Copyright © 2022-2024 Robin Davies.
|
||||
</Typography>
|
||||
)}
|
||||
<Divider />
|
||||
<Typography noWrap display="block" variant="caption" >
|
||||
ADDRESSES
|
||||
</Typography>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
{
|
||||
this.model.serverVersion?.webAddresses.map((address) =>
|
||||
(
|
||||
<Typography key={addressKey++} display="block" variant="body2" style={{ marginBottom: 0, marginLeft: 24 }} >
|
||||
{address}
|
||||
</Typography>
|
||||
))
|
||||
|
||||
}
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
<Typography noWrap display="block" variant="caption" >
|
||||
SERVER OS
|
||||
</Typography>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Typography display="block" variant="body2" style={{ marginBottom: 0, marginLeft: 24 }} >
|
||||
{this.model.serverVersion?.osVersion ?? ""}
|
||||
</Typography>
|
||||
</div>
|
||||
</div><div>
|
||||
|
||||
<Divider />
|
||||
<Typography display="block" variant="caption" >
|
||||
LEGAL NOTICES
|
||||
</Typography>
|
||||
<div dangerouslySetInnerHTML={{ __html: this.state.openSourceNotices }} style={{ fontSize: "0.8em", maxWidth: 400 }}>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogEx >
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
export default AboutDialog;
|
||||
@@ -1,9 +0,0 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,218 +0,0 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Dialog, {DialogProps} from '@mui/material/Dialog';
|
||||
|
||||
|
||||
interface DialogExProps extends DialogProps {
|
||||
tag: string;
|
||||
fullwidth?: boolean;
|
||||
onEnterKey: () => void;
|
||||
}
|
||||
|
||||
interface DialogExState {
|
||||
}
|
||||
|
||||
export interface IDialogStackable {
|
||||
getTag: ()=> string;
|
||||
isOpen: ()=> boolean;
|
||||
onDialogStackClose(): void;
|
||||
}
|
||||
|
||||
class DialogStackEntry {
|
||||
constructor(dialog: IDialogStackable)
|
||||
{
|
||||
this.tag = dialog.getTag();
|
||||
this.dialog = dialog;
|
||||
}
|
||||
tag: string;
|
||||
dialog: IDialogStackable;
|
||||
};
|
||||
|
||||
let dialogStack: DialogStackEntry[] = [];
|
||||
|
||||
|
||||
export function popDialogStack(): void {
|
||||
if (dialogStack.length !== 0)
|
||||
{
|
||||
dialogStack.splice(dialogStack.length-1,1);
|
||||
}
|
||||
}
|
||||
export function pushDialogStack(dialog: IDialogStackable): void {
|
||||
dialogStack.push(new DialogStackEntry(dialog));
|
||||
}
|
||||
|
||||
export interface DialogStackState{
|
||||
tag: String;
|
||||
previousState: DialogStackState | null;
|
||||
};
|
||||
|
||||
|
||||
// Close all dialogs higher in the dialog stack than "tag".
|
||||
export function removeDialogStackEntriesAbove(tag: string)
|
||||
{
|
||||
for (let i = dialogStack.length-1; i >= 0; --i)
|
||||
{
|
||||
let entry = dialogStack[i];
|
||||
if (entry.dialog.getTag() === tag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
popDialogStack();
|
||||
if (entry.dialog.isOpen())
|
||||
{
|
||||
entry.dialog.onDialogStackClose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
class DialogEx extends React.Component<DialogExProps,DialogExState> implements IDialogStackable {
|
||||
constructor(props: DialogExProps)
|
||||
{
|
||||
super(props);
|
||||
this.state = {
|
||||
|
||||
};
|
||||
this.handlePopState = this.handlePopState.bind(this);
|
||||
|
||||
}
|
||||
|
||||
mounted: boolean = false;
|
||||
|
||||
hasHooks: boolean = false;
|
||||
stateWasPopped: boolean = false;
|
||||
|
||||
getTag() {
|
||||
return this.props.tag;
|
||||
}
|
||||
isOpen(): boolean {
|
||||
return this.props.open;
|
||||
}
|
||||
onDialogStackClose() {
|
||||
this.props.onClose?.({},"backdropClick");
|
||||
}
|
||||
|
||||
handlePopState(ev: PopStateEvent): any
|
||||
{
|
||||
let evTag: DialogStackState | null = ev.state as DialogStackState | null;
|
||||
if (evTag && evTag.tag === this.props.tag) {
|
||||
removeDialogStackEntriesAbove(this.props.tag);
|
||||
|
||||
if (!this.stateWasPopped)
|
||||
{
|
||||
this.stateWasPopped = true;
|
||||
popDialogStack();
|
||||
if (this.props.open) {
|
||||
this.props.onClose?.(ev,"backdropClick");
|
||||
}
|
||||
}
|
||||
window.history.replaceState(evTag.previousState,"",window.location.href);
|
||||
window.removeEventListener("popstate",this.handlePopState);
|
||||
|
||||
if (window.location.hash === "#menu") // The menu popstate is next?
|
||||
{
|
||||
window.history.back(); // then clear off the menu popstate too.
|
||||
}
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
pushDialogStack(this);
|
||||
let dialogStackState: DialogStackState = {tag: this.props.tag,previousState: window.history.state as DialogStackState | null};
|
||||
window.history.replaceState(dialogStackState,"",window.location.href);
|
||||
window.history.pushState(
|
||||
null,
|
||||
"",
|
||||
"#" + this.props.tag
|
||||
);
|
||||
} else {
|
||||
if (!this.stateWasPopped)
|
||||
{
|
||||
window.history.back();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount()
|
||||
{
|
||||
super.componentDidMount?.();
|
||||
|
||||
this.mounted = true;
|
||||
this.updateHooks();
|
||||
}
|
||||
componentWillUnmount()
|
||||
{
|
||||
super.componentWillUnmount?.();
|
||||
this.mounted = false;
|
||||
this.updateHooks();
|
||||
}
|
||||
|
||||
componentDidUpdate()
|
||||
{
|
||||
this.updateHooks();
|
||||
}
|
||||
myOnClose(event:{}, reason: "backdropClick" | "escapeKeyDown")
|
||||
{
|
||||
if (this.props.onClose) this.props.onClose(event,reason);
|
||||
}
|
||||
|
||||
onEnterKey() {
|
||||
if (this.props.onEnterKey)
|
||||
{
|
||||
this.props.onEnterKey();
|
||||
}
|
||||
}
|
||||
onKeyDown(evt: React.KeyboardEvent<HTMLDivElement>)
|
||||
{
|
||||
if (evt.key === 'Enter')
|
||||
{
|
||||
this.onEnterKey();
|
||||
}
|
||||
evt.stopPropagation();
|
||||
}
|
||||
render() {
|
||||
let { tag,onClose,...extra} = this.props;
|
||||
return (
|
||||
<Dialog fullWidth={this.props.fullWidth??false}
|
||||
maxWidth={this.props.fullWidth ? false: undefined} {...extra}
|
||||
onClose={(event,reason)=>{ this.myOnClose(event,reason);}}
|
||||
onKeyDown={(evt)=>{ this.onKeyDown(evt); }}
|
||||
>
|
||||
{this.props.children}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export default DialogEx;
|
||||
@@ -1,244 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { Component, SyntheticEvent } from 'react';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import { WithStyles } from '@mui/styles';
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import { UiFileProperty } from './Lv2Plugin';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { PiPedalModel, PiPedalModelFactory, ListenHandle,State} from './PiPedalModel';
|
||||
import ButtonBase from '@mui/material/ButtonBase'
|
||||
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
||||
import {PedalboardItem} from './Pedalboard';
|
||||
import {isDarkMode} from './DarkMode';
|
||||
|
||||
export const StandardItemSize = { width: 80, height: 140 }
|
||||
|
||||
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
frame: {
|
||||
position: "relative",
|
||||
margin: "12px"
|
||||
},
|
||||
switchTrack: {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
borderRadius: 14 / 2,
|
||||
zIndex: -1,
|
||||
transition: theme.transitions.create(['opacity', 'background-color'], {
|
||||
duration: theme.transitions.duration.shortest
|
||||
}),
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
opacity: theme.palette.mode === 'light' ? 0.38 : 0.3
|
||||
},
|
||||
displayValue: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 4,
|
||||
textAlign: "center",
|
||||
background: "white",
|
||||
color: "#666",
|
||||
// zIndex: -1,
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export interface FilePropertyControlProps extends WithStyles<typeof styles> {
|
||||
fileProperty: UiFileProperty;
|
||||
pedalboardItem: PedalboardItem;
|
||||
onFileClick: (fileProperty: UiFileProperty, value: string) => void;
|
||||
theme: Theme;
|
||||
}
|
||||
type FilePropertyControlState = {
|
||||
error: boolean;
|
||||
hasValue: boolean;
|
||||
value: string;
|
||||
};
|
||||
|
||||
const FilePropertyControl =
|
||||
withStyles(styles, { withTheme: true })(
|
||||
class extends Component<FilePropertyControlProps, FilePropertyControlState>
|
||||
{
|
||||
|
||||
model: PiPedalModel;
|
||||
|
||||
|
||||
constructor(props: FilePropertyControlProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
error: false,
|
||||
hasValue: false,
|
||||
value: "",
|
||||
};
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.onStateChanged = this.onStateChanged.bind(this);
|
||||
}
|
||||
|
||||
|
||||
onStateChanged(state: State) {
|
||||
if (this.mounted)
|
||||
{
|
||||
if (state === State.Ready) {
|
||||
this.subscribeToPatchProperty();
|
||||
}
|
||||
}
|
||||
}
|
||||
private propertyGetHandle?: ListenHandle;
|
||||
|
||||
monitorPropertyHandle?: ListenHandle;
|
||||
|
||||
subscribeToPatchProperty() {
|
||||
// this.propertyGetHandle = this.model.listenForAtomOutput(this.props.instanceId,(instanceId,atomOutput)=>{
|
||||
// // PARSE THE OBJECT!
|
||||
// // this.setState({value: atomOutput?.value as string});
|
||||
// });
|
||||
this.unsubscribeToPatchProperty();
|
||||
this.monitorPropertyHandle = this.model.monitorPatchProperty(this.props.pedalboardItem.instanceId,
|
||||
this.props.fileProperty.patchProperty,
|
||||
(instanceId,property,propertyValue) => {
|
||||
if (propertyValue.otype_ === "Path")
|
||||
{
|
||||
this.setState({value: propertyValue.value as string,hasValue: true});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
unsubscribeToPatchProperty() {
|
||||
if (this.monitorPropertyHandle !== undefined)
|
||||
{
|
||||
this.model.cancelMonitorPatchProperty(this.monitorPropertyHandle);
|
||||
this.monitorPropertyHandle = undefined;
|
||||
}
|
||||
|
||||
}
|
||||
private mounted: boolean = false;
|
||||
componentDidMount() {
|
||||
this.model.state.addOnChangedHandler(this.onStateChanged);
|
||||
this.mounted = true;
|
||||
this.subscribeToPatchProperty();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.unsubscribeToPatchProperty();
|
||||
this.mounted = false;
|
||||
this.model.state.removeOnChangedHandler(this.onStateChanged);
|
||||
}
|
||||
componentDidUpdate(prevProps: Readonly<FilePropertyControlProps>, prevState: Readonly<FilePropertyControlState>, snapshot?: any): void {
|
||||
if (prevProps.fileProperty.patchProperty !== this.props.fileProperty.patchProperty
|
||||
|| prevProps.pedalboardItem.instanceId !== this.props.pedalboardItem.instanceId
|
||||
|| prevProps.pedalboardItem.stateUpdateCount !== this.props.pedalboardItem.stateUpdateCount
|
||||
|
||||
) {
|
||||
this.unsubscribeToPatchProperty();
|
||||
this.subscribeToPatchProperty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
inputChanged: boolean = false;
|
||||
|
||||
onDrag(e: SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
onFileClick() {
|
||||
this.props.onFileClick(this.props.fileProperty, this.state.value);
|
||||
}
|
||||
private fileNameOnly(path: string): string {
|
||||
let slashPos = path.lastIndexOf('/');
|
||||
if (slashPos < 0) {
|
||||
slashPos = 0;
|
||||
} else {
|
||||
++slashPos;
|
||||
}
|
||||
let extPos = path.lastIndexOf('.');
|
||||
if (extPos < 0 || extPos < slashPos) {
|
||||
extPos = path.length;
|
||||
}
|
||||
|
||||
return path.substring(slashPos, extPos);
|
||||
|
||||
}
|
||||
render() {
|
||||
//let classes = this.props.classes;
|
||||
let fileProperty = this.props.fileProperty;
|
||||
|
||||
let value = "\u00A0";
|
||||
if (this.state.hasValue)
|
||||
{
|
||||
if (this.state.value.length === 0)
|
||||
{
|
||||
value = "<none>";
|
||||
} else {
|
||||
value = this.fileNameOnly(this.state.value);
|
||||
}
|
||||
}
|
||||
|
||||
let item_width = 264;
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "flex-start", width: item_width, margin: 8, paddingLeft: 8 }}>
|
||||
{/* TITLE SECTION */}
|
||||
<div style={{ flex: "0 0 auto", width: "100%", marginBottom: 8, marginLeft: 0, marginRight: 0 }}>
|
||||
<Typography variant="caption" display="block" noWrap style={{
|
||||
width: "100%",
|
||||
textAlign: "start"
|
||||
}}> {fileProperty.label}</Typography>
|
||||
</div>
|
||||
{/* CONTROL SECTION */}
|
||||
|
||||
<div style={{ flex: "0 0 auto", width: "100%" }}>
|
||||
|
||||
<ButtonBase style={{ width: "100%", borderRadius: "4px 4px 0px 0px", overflow: "hidden", marginTop: 8 }} onClick={() => { this.onFileClick() }} >
|
||||
<div style={{ width: "100%", background:
|
||||
isDarkMode()? "rgba(255,255,255,0.03)": "rgba(0,0,0,0.07)",
|
||||
borderRadius: "4px 4px 0px 0px" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", flexFlow: "row nowrap" }}>
|
||||
<Typography noWrap={true} style={{ flex: "1 1 100%", textAlign: "start", verticalAlign: "center", paddingTop: 4, paddingBottom: 4, paddingLeft: 4 }}
|
||||
variant='caption'
|
||||
>{value}</Typography>
|
||||
<MoreHorizIcon style={{ flex: "0 0 auto", width: "16px", height: "16px", verticalAlign: "center", opacity: 0.5, marginRight: 4 }} />
|
||||
</div>
|
||||
<div style={{ height: "1px", width: "100%", background: this.props.theme.palette.text.secondary }}> </div>
|
||||
</div>
|
||||
</ButtonBase>
|
||||
</div>
|
||||
|
||||
{/* LABEL/EDIT SECTION*/}
|
||||
<div style={{ flex: "0 0 auto", position: "relative", width: 60 }}>
|
||||
</div>
|
||||
</div >
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default FilePropertyControl;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,362 +0,0 @@
|
||||
// Copyright (c) 2024 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import { WithStyles } from '@mui/styles';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import FullscreenIcon from '@mui/icons-material/Fullscreen';
|
||||
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import { PiPedalModelFactory, PiPedalModel, State, ZoomedControlInfo } from './PiPedalModel';
|
||||
import ZoomedUiControl from './ZoomedUiControl'
|
||||
import MainPage from './MainPage';
|
||||
import JackStatusView from './JackStatusView';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import { isDarkMode } from './DarkMode';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import { Snapshot } from './Pedalboard';
|
||||
import ColorDropdownButton, { DropdownAlignment } from './ColorDropdownButton';
|
||||
import { getBackgroundColor } from './MaterialColors';
|
||||
|
||||
|
||||
const selectColor = isDarkMode() ? "#888" : "#FFFFFF";
|
||||
|
||||
|
||||
const appStyles = (theme: Theme) => createStyles({
|
||||
"&": { // :root
|
||||
colorScheme: (isDarkMode() ? "dark" : "light")
|
||||
},
|
||||
|
||||
|
||||
mainFrame: {
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
marginTop: 8,
|
||||
flex: "1 1 100%"
|
||||
},
|
||||
heroContent: {
|
||||
backgroundColor: theme.mainBackground,
|
||||
position: "relative",
|
||||
height: "100%",
|
||||
width: "100%"
|
||||
},
|
||||
shadowCatcher: {
|
||||
backgroundColor: theme.mainBackground
|
||||
},
|
||||
|
||||
select: { // fu fu fu.Overrides for white selector on dark background.
|
||||
'&:before': {
|
||||
borderColor: selectColor,
|
||||
},
|
||||
'&:after': {
|
||||
borderColor: selectColor,
|
||||
},
|
||||
'&:hover:not(.Mui-disabled):before': {
|
||||
borderColor: selectColor,
|
||||
}
|
||||
},
|
||||
select_icon: {
|
||||
fill: selectColor,
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
function supportsFullScreen(): boolean {
|
||||
let doc: any = window.document;
|
||||
let docEl: any = doc.documentElement;
|
||||
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
|
||||
return (!!requestFullScreen);
|
||||
|
||||
}
|
||||
|
||||
function setFullScreen(value: boolean) {
|
||||
let doc: any = window.document;
|
||||
let docEl: any = doc.documentElement;
|
||||
|
||||
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
|
||||
var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;
|
||||
|
||||
if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) {
|
||||
requestFullScreen.call(docEl);
|
||||
}
|
||||
else {
|
||||
cancelFullScreen.call(doc);
|
||||
}
|
||||
}
|
||||
|
||||
type SnapshotEditorState = {
|
||||
zoomedControlInfo: ZoomedControlInfo | undefined;
|
||||
zoomedControlOpen: boolean;
|
||||
|
||||
canFullScreen: boolean;
|
||||
isFullScreen: boolean;
|
||||
isDebug: boolean;
|
||||
collapseLabel: boolean;
|
||||
|
||||
showStatusMonitor: boolean;
|
||||
name: string,
|
||||
color: string
|
||||
|
||||
};
|
||||
interface SnapshotEditorProps extends WithStyles<typeof appStyles> {
|
||||
onClose: () => void;
|
||||
onOk: (snapshotIndex: number, name: string, color: string, newSnapshots: (Snapshot | null)[]) => void;
|
||||
snapshotIndex: number;
|
||||
}
|
||||
|
||||
const SnapshotEditor = withStyles(appStyles)(class extends ResizeResponsiveComponent<SnapshotEditorProps, SnapshotEditorState> {
|
||||
// Before the component mounts, we initialise our state
|
||||
|
||||
model_: PiPedalModel;
|
||||
|
||||
getCollapseLabel() {
|
||||
return this.windowSize.width < 500;
|
||||
}
|
||||
|
||||
constructor(props: SnapshotEditorProps) {
|
||||
super(props);
|
||||
this.model_ = PiPedalModelFactory.getInstance();
|
||||
|
||||
this.model_.zoomedUiControl.addOnChangedHandler(
|
||||
() => {
|
||||
this.setState({
|
||||
zoomedControlOpen: this.model_.zoomedUiControl.get() !== undefined,
|
||||
zoomedControlInfo: this.model_.zoomedUiControl.get()
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
let snapshot = this.model_.pedalboard.get().snapshots[this.props.snapshotIndex] ?? new Snapshot();
|
||||
this.state = {
|
||||
zoomedControlOpen: false,
|
||||
zoomedControlInfo: this.model_.zoomedUiControl.get(),
|
||||
canFullScreen: supportsFullScreen() && !this.model_.isAndroidHosted(),
|
||||
isFullScreen: !!document.fullscreenElement,
|
||||
showStatusMonitor: this.model_.showStatusMonitor.get(),
|
||||
isDebug: true,
|
||||
name: snapshot.name,
|
||||
color: snapshot.color,
|
||||
collapseLabel: this.getCollapseLabel()
|
||||
|
||||
};
|
||||
|
||||
this.showStatusMonitorHandler = this.showStatusMonitorHandler.bind(this);
|
||||
}
|
||||
|
||||
showStatusMonitorHandler() {
|
||||
this.setState({
|
||||
showStatusMonitor: this.model_.showStatusMonitor.get()
|
||||
});
|
||||
}
|
||||
toggleFullScreen(): void {
|
||||
setFullScreen(this.state.isFullScreen);
|
||||
this.setState({ isFullScreen: !this.state.isFullScreen });
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
super.componentDidMount();
|
||||
this.model_.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler);
|
||||
|
||||
}
|
||||
|
||||
|
||||
updateOverscroll(): void {
|
||||
if (this.model_.serverVersion) {
|
||||
// no pull-down refresh on android devices once we're ready (unless we're debug)
|
||||
let preventOverscroll =
|
||||
this.model_.state.get() === State.Ready
|
||||
&& !this.model_.debug;
|
||||
|
||||
let overscrollBehavior = preventOverscroll ? "none" : "auto";
|
||||
document.body.style.overscrollBehavior = overscrollBehavior;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount();
|
||||
this.model_.banks.removeOnChangedHandler(this.showStatusMonitorHandler);
|
||||
}
|
||||
|
||||
onWindowSizeChanged(width: number, height: number): void {
|
||||
super.onWindowSizeChanged(width, height);
|
||||
this.setState({
|
||||
collapseLabel: this.getCollapseLabel()
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
handleOk() {
|
||||
let currentPedalboard = this.model_.pedalboard.get();
|
||||
let selectedSnapshot = this.props.snapshotIndex;
|
||||
let currentSnapshot: Snapshot | null = currentPedalboard.snapshots[selectedSnapshot];
|
||||
if (!currentSnapshot)
|
||||
{
|
||||
this.props.onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
let changed = this.state.name !== currentSnapshot.name
|
||||
|| this.state.color !== currentSnapshot.color
|
||||
|| currentSnapshot.isModified;
|
||||
if (!changed)
|
||||
{
|
||||
this.props.onClose();
|
||||
return;
|
||||
}
|
||||
let newSnapshots = Snapshot.cloneSnapshots(currentPedalboard.snapshots);
|
||||
let newSnapshot = this.model_.pedalboard.get().makeSnapshot();
|
||||
newSnapshot.name = this.state.name;
|
||||
newSnapshot.color = this.state.color;
|
||||
newSnapshots[selectedSnapshot] = newSnapshot;
|
||||
this.model_.setSnapshots(newSnapshots, selectedSnapshot);
|
||||
|
||||
this.props.onOk(selectedSnapshot, this.state.name, this.state.color, newSnapshots);
|
||||
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const { classes } = this.props;
|
||||
|
||||
return (
|
||||
|
||||
<div
|
||||
className={classes.shadowCatcher}
|
||||
style={{
|
||||
position: "absolute", top: 0, left: 0, right: 0, bottom: 0,
|
||||
colorScheme: isDarkMode() ? "dark" : "light", // affects scrollbar color
|
||||
minHeight: 345, minWidth: 390,
|
||||
userSelect: "none",
|
||||
display: "flex", flexDirection: "column", flexWrap: "nowrap",
|
||||
overscrollBehavior: this.state.isDebug ? "auto" : "none"
|
||||
}}
|
||||
onContextMenu={(e) => {
|
||||
if (!this.model_.debug) {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CssBaseline />
|
||||
<AppBar position="static" sx={{ bgcolor: isDarkMode() ? getBackgroundColor("purple") : "#200040" }} >
|
||||
<Toolbar variant="dense" >
|
||||
<IconButton
|
||||
edge="start"
|
||||
aria-label="menu"
|
||||
color="inherit"
|
||||
onClick={() => { this.handleOk(); }}
|
||||
size="large">
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
{!this.state.collapseLabel && (
|
||||
<Typography style={{ flex: "0 1 auto", opacity: 0.66 }}
|
||||
variant="body1" noWrap
|
||||
>
|
||||
{'Snapshot ' + (this.props.snapshotIndex + 1)}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<div style={{ flex: "1 1 auto", display: "flex", flexFlow: "column nowrap", marginRight: 8 }}>
|
||||
<TextField
|
||||
sx={{
|
||||
input: { color: 'white' },
|
||||
'& .MuiInput-underline:before': { borderBottomColor: '#FFFFFFC0' },
|
||||
'& .MuiInput-underline:hover:before': { borderBottomColor: '#FFFFFFE0' },
|
||||
'& .MuiInput-underline:after': { borderBottomColor: '#FFFFFF' }
|
||||
}}
|
||||
variant="standard"
|
||||
style={{ flex: "1 1 auto", marginLeft: 16, maxWidth: 500, color: "white" }}
|
||||
value={this.state.name}
|
||||
onChange={(ev) => {
|
||||
this.setState({ name: ev.target.value });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<ColorDropdownButton
|
||||
currentColor={this.state.color}
|
||||
dropdownAlignment={DropdownAlignment.SW}
|
||||
onColorChange={(newColor) => {
|
||||
this.setState({ color: newColor });
|
||||
}} />
|
||||
{this.state.canFullScreen &&
|
||||
<IconButton
|
||||
aria-label="full-screen"
|
||||
onClick={() => { this.toggleFullScreen(); }}
|
||||
color="inherit"
|
||||
size="medium">
|
||||
{this.state.isFullScreen ? (
|
||||
<FullscreenExitIcon style={{ opacity: 0.75 }} />
|
||||
) : (
|
||||
<FullscreenIcon style={{ opacity: 0.75 }} />
|
||||
|
||||
)}
|
||||
|
||||
</IconButton>
|
||||
}
|
||||
<IconButton
|
||||
aria-label="cancel"
|
||||
onClick={() => { this.props.onClose(); }}
|
||||
color="inherit"
|
||||
size="medium">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<main className={classes.mainFrame} >
|
||||
<div style={{
|
||||
overflow: "hidden",
|
||||
flex: "1 1 auto"
|
||||
}} >
|
||||
<div className={classes.heroContent}>
|
||||
<MainPage hasTinyToolBar={false} enableStructureEditing={false} />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<ZoomedUiControl
|
||||
dialogOpen={this.state.zoomedControlOpen}
|
||||
controlInfo={this.state.zoomedControlInfo}
|
||||
onDialogClose={() => { this.setState({ zoomedControlOpen: false }); }}
|
||||
onDialogClosed={() => { this.model_.zoomedUiControl.set(undefined); }
|
||||
}
|
||||
/>
|
||||
{ this.state.showStatusMonitor && (<JackStatusView />)}
|
||||
</div >
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default SnapshotEditor;
|
||||
@@ -1,13 +0,0 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
@@ -1,15 +0,0 @@
|
||||
import { ReportHandler } from 'web-vitals';
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
@@ -1,5 +0,0 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
@@ -1 +0,0 @@
|
||||
npm start
|
||||
-178
@@ -1,178 +0,0 @@
|
||||
src/PiPedalError.tsx
|
||||
src/OldDeleteIcon.tsx
|
||||
src/MaterialColors.tsx
|
||||
src/Pedalboard.tsx
|
||||
src/SystemMidiBindingView.tsx
|
||||
src/GovernorSettings.tsx
|
||||
src/ObservableProperty.tsx
|
||||
src/SnapshotEditor.tsx
|
||||
src/SelectMidiChannelsDialog.tsx
|
||||
src/AlsaMidiDeviceInfo.tsx
|
||||
src/WifiDirectConfigSettings.tsx
|
||||
src/WifiConfigSettings.tsx
|
||||
src/SnapshotButton.tsx
|
||||
src/WindowScale.tsx
|
||||
src/JsonAtom.tsx
|
||||
src/PiPedalSocket.tsx
|
||||
src/TemporaryDrawer.tsx
|
||||
src/DarkMode.tsx
|
||||
src/MidiBindingsDialog.tsx
|
||||
src/MidiBinding.tsx
|
||||
src/SnapshotDialog.tsx
|
||||
src/SettingsDialog.tsx
|
||||
src/PedalboardView.tsx
|
||||
src/reportWebVitals.ts
|
||||
src/OptionsDialog.tsx
|
||||
src/SelectChannelsDialog.tsx
|
||||
src/PluginPresetSelector.tsx
|
||||
src/VuMeter.tsx
|
||||
src/ToobFrequencyResponseView.tsx
|
||||
src/SystemMidiBindingsDialog.tsx
|
||||
src/FullScreenIME.tsx
|
||||
src/App.tsx
|
||||
src/ToobInputStageView.tsx
|
||||
src/XxxSnippet.tsx
|
||||
src/JackServerSettings.tsx
|
||||
src/ToobWaveShapeView.tsx
|
||||
src/ToobPowerStage2View.tsx
|
||||
src/PluginPreset.tsx
|
||||
src/logo.svg
|
||||
src/Banks.tsx
|
||||
src/NoChangePassword.tsx
|
||||
src/SearchFilter.tsx
|
||||
src/FilePropertyDirectorySelectDialog.tsx
|
||||
src/GxTunerView.tsx
|
||||
src/UpdateDialog.tsx
|
||||
src/svg/file_download_black_24dp.svg
|
||||
src/svg/fx_filter.svg
|
||||
src/svg/fx_pitch.svg
|
||||
src/svg/fx_distortion.svg
|
||||
src/svg/fx_split_b.svg
|
||||
src/svg/fx_chorus.svg
|
||||
src/svg/fx_limiter.svg
|
||||
src/svg/fx_phaser.svg
|
||||
src/svg/snapshot_0.svg
|
||||
src/svg/snapshot_3.svg
|
||||
src/svg/file_upload_black_24dp.svg
|
||||
src/svg/ic_bank.svg
|
||||
src/svg/fx_function.svg
|
||||
src/svg/fx_dial.svg
|
||||
src/svg/ic_settings.svg
|
||||
src/svg/fx_split_a.svg
|
||||
src/svg/fx_empty.svg
|
||||
src/svg/fx_amplifier.svg
|
||||
src/svg/fx_spectral.svg
|
||||
src/svg/fx_eq.svg
|
||||
src/svg/ic_edit_banks.svg
|
||||
src/svg/ic_help_outline.svg
|
||||
src/svg/ic_save_bank_as.svg
|
||||
src/svg/ic_pluginpreset2.svg
|
||||
src/svg/fx_flanger2.svg
|
||||
src/svg/snapshot_6.svg
|
||||
src/svg/fx_lr.svg
|
||||
src/svg/fx_generator.svg
|
||||
src/svg/snapshot_1.svg
|
||||
src/svg/fx_reverb.svg
|
||||
src/svg/fx_plugin.svg
|
||||
src/svg/snapshot_4.svg
|
||||
src/svg/drive_file_rename_outline_black_24dp.svg
|
||||
src/svg/snapshot_2.svg
|
||||
src/svg/fx_instrument.svg
|
||||
src/svg/fx_modulator.svg
|
||||
src/svg/fx_error.svg
|
||||
src/svg/fx_simulator.svg
|
||||
src/svg/fx_filter_hp.svg
|
||||
src/svg/fx_compressor.svg
|
||||
src/svg/fx_gate.svg
|
||||
src/svg/fx_terminal.svg
|
||||
src/svg/ic_midi.svg
|
||||
src/svg/fx_oscillator.svg
|
||||
src/svg/fx_analyzer.svg
|
||||
src/svg/fx_flanger.svg
|
||||
src/svg/old_delete_outline_24dp.svg
|
||||
src/svg/ic_presets.svg
|
||||
src/svg/fx_delay.svg
|
||||
src/svg/fx_parametric_eq.svg
|
||||
src/svg/fx_spatial.svg
|
||||
src/svg/fx_converter.svg
|
||||
src/svg/fx_constant.svg
|
||||
src/svg/snapshot_5.svg
|
||||
src/svg/ic_pluginpreset.svg
|
||||
src/svg/fx_mixer.svg
|
||||
src/svg/fx_utility.svg
|
||||
src/Updater.tsx
|
||||
src/UploadPresetDialog.tsx
|
||||
src/RadioSelectDialog.tsx
|
||||
src/setupTests.ts
|
||||
src/ToobCabSimView.tsx
|
||||
src/RenameDialog.tsx
|
||||
src/SplitControlView.tsx
|
||||
src/SelectHoverBackground.tsx
|
||||
src/IControlViewFactory.tsx
|
||||
src/OkCancelDialog.tsx
|
||||
src/BankDialog.tsx
|
||||
src/PluginPresetsDialog.tsx
|
||||
src/App.test.tsx
|
||||
src/ControlViewFactory.tsx
|
||||
src/DraggableGrid.tsx
|
||||
src/ZoomedDial.tsx
|
||||
src/index.css
|
||||
src/Pedal.tsx
|
||||
src/Draggable.tsx
|
||||
src/PluginIcon.tsx
|
||||
src/react-app-env.d.ts
|
||||
src/TextFieldEx.tsx
|
||||
src/FilePropertyDialog.tsx
|
||||
src/FilePropertyDirectoryTree.tsx
|
||||
src/StringBuilder.tsx
|
||||
src/MainPage.tsx
|
||||
src/PluginInfoDialog.tsx
|
||||
src/AppThemed.tsx
|
||||
src/Lv2Plugin.tsx
|
||||
src/ToobSpectrumResponseView.tsx
|
||||
src/PerformanceView.tsx
|
||||
src/JackServerSettingsDialog.tsx
|
||||
src/Utility.tsx
|
||||
src/JackStatusView.tsx
|
||||
src/ToobSpectrumAnalyzerView.tsx
|
||||
src/SvgPathBuilder.tsx
|
||||
src/PresetSelector.tsx
|
||||
src/PresetDialog.tsx
|
||||
src/WifiChannel.tsx
|
||||
src/AboutDialog.tsx
|
||||
src/GxTunerControl.tsx
|
||||
src/VirtualKeyboardHandler.tsx
|
||||
src/PluginControl.tsx
|
||||
src/LoadPluginDialog.tsx
|
||||
src/ZoomedUiControl.tsx
|
||||
src/AlsaDeviceInfo.tsx
|
||||
src/ToobToneStackView.tsx
|
||||
src/UploadFileDialog.tsx
|
||||
src/PluginOutputControl.tsx
|
||||
src/SelectThemeDialog.tsx
|
||||
src/Rect.tsx
|
||||
src/AppThemed.css
|
||||
src/ColorDropdownButton.tsx
|
||||
src/AndroidHost.tsx
|
||||
src/SnapshotPanel.tsx
|
||||
src/ToobMLView.tsx
|
||||
src/WifiConfigDialog.tsx
|
||||
src/Rectangle.tsx
|
||||
src/SnapshotColors.tsx
|
||||
src/PiPedalModel.tsx
|
||||
src/ObservableEvent.tsx
|
||||
src/JackHostStatus.tsx
|
||||
src/index.tsx
|
||||
src/FilePropertyControl.tsx
|
||||
src/WifiDirectConfigDialog.tsx
|
||||
src/MidiBindingView.tsx
|
||||
src/PluginControlView.tsx
|
||||
src/SplitUiControls.tsx
|
||||
src/PluginClass.tsx
|
||||
src/DialogEx.tsx
|
||||
src/Jack.tsx
|
||||
src/ResizeResponsiveComponent.tsx
|
||||
src/NumericInput.tsx
|
||||
src/SnapshotPropertiesDialog.tsx
|
||||
src/SearchControl.tsx
|
||||
src/Units.tsx
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext",
|
||||
"es2020"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
+11
-6
@@ -1257,7 +1257,7 @@ namespace pipedal
|
||||
{
|
||||
message =
|
||||
SS("Device " << alsa_device_name << " in use. The following applications are using your soundcard: " << apps
|
||||
<< ". Stop them as neccesary before trying to restart pipedald.");
|
||||
<< ". Stop them as neccesary before trying to pipedald.");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1657,7 +1657,7 @@ namespace pipedal
|
||||
Lv2Log::error(e.what());
|
||||
Lv2Log::error("ALSA audio thread terminated abnormally.");
|
||||
}
|
||||
this->driverHost->OnAudioStopped();
|
||||
this->driverHost->OnAlsaDriverStopped();
|
||||
|
||||
// if we terminated abnormally, pump messages until we have been terminated.
|
||||
if (!terminateAudio())
|
||||
@@ -1671,11 +1671,16 @@ namespace pipedal
|
||||
pBuffer[j] = 0;
|
||||
}
|
||||
}
|
||||
while (!terminateAudio())
|
||||
try {
|
||||
while (!terminateAudio())
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
// zero out input buffers.
|
||||
this->driverHost->OnProcess(this->bufferSize);
|
||||
}
|
||||
} catch (const std::exception &e)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
// zero out input buffers.
|
||||
this->driverHost->OnProcess(this->bufferSize);
|
||||
|
||||
}
|
||||
}
|
||||
this->driverHost->OnAudioTerminated();
|
||||
|
||||
@@ -212,7 +212,7 @@ public:
|
||||
|
||||
}
|
||||
|
||||
virtual void OnAudioStopped() {
|
||||
virtual void OnAlsaDriverStopped() {
|
||||
}
|
||||
virtual void OnProcess(size_t nFrames) {
|
||||
if (testType == TestType::NullTest) return;
|
||||
|
||||
@@ -100,8 +100,8 @@ namespace pipedal
|
||||
|
||||
long long get_l1_misses()
|
||||
{
|
||||
long long count;
|
||||
read(fd_l1, &count, sizeof(long long));
|
||||
long long count = 0;
|
||||
auto _ = read(fd_l1, &count, sizeof(long long));
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -111,8 +111,8 @@ namespace pipedal
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
long long count;
|
||||
read(fd_l2, &count, sizeof(long long));
|
||||
long long count = 0;
|
||||
auto _ = read(fd_l2, &count, sizeof(long long));
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ namespace pipedal {
|
||||
public:
|
||||
virtual void OnProcess(size_t nFrames) = 0;
|
||||
virtual void OnUnderrun() = 0;
|
||||
virtual void OnAudioStopped() = 0;
|
||||
virtual void OnAlsaDriverStopped() = 0;
|
||||
virtual void OnAudioTerminated() = 0;
|
||||
|
||||
|
||||
|
||||
+35
-37
@@ -32,6 +32,7 @@
|
||||
#include <unordered_map>
|
||||
#include "PluginHost.hpp"
|
||||
#include "PatchPropertyWriter.hpp"
|
||||
#include "CpuTemperatureMonitor.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
@@ -182,7 +183,8 @@ namespace pipedal
|
||||
reader.read(&vProperty);
|
||||
if (!vProperty.is_null())
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
vProperty = pluginHost.MapPath(vProperty);
|
||||
|
||||
// now to atom format (what we want on the rt thread0)
|
||||
@@ -193,7 +195,8 @@ namespace pipedal
|
||||
pathPatchProperty.atomBuffer.resize(atomValue->size + sizeof(LV2_Atom));
|
||||
memcpy(pathPatchProperty.atomBuffer.data(), atomValue, pathPatchProperty.atomBuffer.size());
|
||||
this->pathPatchProperties.push_back(std::move(pathPatchProperty));
|
||||
} catch (const std::exception &e)
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::info(SS("IndexedSnapshotValue: Failed to map path property " << pathProperty.first << ". " << e.what()));
|
||||
}
|
||||
@@ -206,7 +209,7 @@ namespace pipedal
|
||||
void ApplyValues(IEffect *effect)
|
||||
{
|
||||
|
||||
effect->SetBypass(this->enabled);
|
||||
effect->SetBypass(this->enabled);
|
||||
if (effect != pEffect)
|
||||
{
|
||||
throw std::runtime_error("Wrong effect");
|
||||
@@ -387,6 +390,7 @@ private:
|
||||
std::mutex atomConverterMutex;
|
||||
AtomConverter atomConverter;
|
||||
|
||||
CpuTemperatureMonitor::ptr cpuTemperatureMonitor;
|
||||
static constexpr size_t DEFERRED_MIDI_BUFFER_SIZE = 1024;
|
||||
|
||||
uint8_t deferredMidiMessages[DEFERRED_MIDI_BUFFER_SIZE];
|
||||
@@ -905,7 +909,7 @@ private:
|
||||
|
||||
bool onMidiEvent(Lv2EventBufferWriter &eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent &event)
|
||||
{
|
||||
//eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);
|
||||
// eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);
|
||||
|
||||
this->realtimeActivePedalboard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged);
|
||||
if (listenForMidiEvent)
|
||||
@@ -1098,10 +1102,12 @@ private:
|
||||
{
|
||||
return this->active && (!this->audioStopped) && !this->isDummyAudioDriver;
|
||||
}
|
||||
virtual void OnAudioStopped() override
|
||||
virtual void OnAlsaDriverStopped() override
|
||||
{
|
||||
|
||||
this->audioStopped = true;
|
||||
Lv2Log::info("Audio stopped.");
|
||||
this->realtimeWriter.AudioTerminatedAbnormally();
|
||||
}
|
||||
virtual void OnAudioTerminated() override
|
||||
{
|
||||
@@ -1211,7 +1217,7 @@ private:
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error("Fatal error while processing jack audio. (%s)", e.what());
|
||||
Lv2Log::error("Fatal error while processing realtime audio. (%s)", e.what());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -1231,6 +1237,8 @@ public:
|
||||
{
|
||||
realtimeAtomBuffer.resize(32 * 1024);
|
||||
lv2_atom_forge_init(&inputWriterForge, pHost->GetMapFeature().GetMap());
|
||||
|
||||
cpuTemperatureMonitor = CpuTemperatureMonitor::Get();
|
||||
}
|
||||
virtual ~AudioHostImpl()
|
||||
{
|
||||
@@ -1252,12 +1260,14 @@ public:
|
||||
return this->sampleRate;
|
||||
}
|
||||
|
||||
void OnAudioComplete()
|
||||
void HandleAudioTerminatedAbnormally()
|
||||
{
|
||||
// there is actually no compelling circumstance in which this should ever happen.
|
||||
|
||||
Lv2Log::error("Audio processing terminated unexpectedly.");
|
||||
realtimeWriter.AudioStopped();
|
||||
|
||||
if (pNotifyCallbacks)
|
||||
{
|
||||
pNotifyCallbacks->OnAlsaDriverTerminatedAbnormally();
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> atomBuffer;
|
||||
std::vector<uint8_t> realtimeAtomBuffer;
|
||||
@@ -1267,7 +1277,7 @@ public:
|
||||
{
|
||||
SetThreadName("rtsvc");
|
||||
SetThreadPriority(SchedulerPriority::AudioService);
|
||||
|
||||
|
||||
int underrunMessagesGiven = 0;
|
||||
try
|
||||
{
|
||||
@@ -1452,7 +1462,7 @@ public:
|
||||
{
|
||||
LV2_URID propertyUrid = ((LV2_Atom_URID *)property)->body;
|
||||
this->pNotifyCallbacks->OnPatchSetReply(instanceId, propertyUrid, value);
|
||||
//this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId);
|
||||
// this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1488,11 +1498,11 @@ public:
|
||||
hostReader.read(&buffer);
|
||||
OnPathPropertyReceived(buffer);
|
||||
}
|
||||
else if (command == RingBufferCommand::AudioStopped)
|
||||
else if (command == RingBufferCommand::AudioTerminatedAbnormally)
|
||||
{
|
||||
AudioStoppedBody body;
|
||||
hostReader.read(&body);
|
||||
OnAudioComplete();
|
||||
HandleAudioTerminatedAbnormally();
|
||||
return;
|
||||
}
|
||||
else if (command == RingBufferCommand::
|
||||
@@ -1617,7 +1627,7 @@ public:
|
||||
if (jackServerSettings.IsDummyAudioDevice())
|
||||
{
|
||||
this->isDummyAudioDriver = true;
|
||||
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this,jackServerSettings.GetAlsaInputDevice()));
|
||||
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this, jackServerSettings.GetAlsaInputDevice()));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1852,8 +1862,9 @@ public:
|
||||
VuUpdate v;
|
||||
v.instanceId_ = instanceId;
|
||||
// Display mono VUs if a stereo device is being fed identical L/R inputs.
|
||||
v.isStereoInput_ = effect->GetNumberOfInputAudioPorts() != 1 && effect->GetAudioInputBuffer(0) != effect->GetAudioInputBuffer(1);
|
||||
v.isStereoOutput_ = effect->GetNumberOfOutputAudioPorts() != 1;
|
||||
v.isStereoInput_ = effect->GetNumberOfInputAudioBuffers() >= 2
|
||||
&& effect->GetAudioInputBuffer(0) != effect->GetAudioInputBuffer(1);
|
||||
v.isStereoOutput_ = effect->GetNumberOfOutputAudioBuffers() >= 2;
|
||||
|
||||
vuConfig->vuUpdateWorkingData.push_back(v);
|
||||
vuConfig->vuUpdateResponseData.push_back(v);
|
||||
@@ -1923,7 +1934,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
//virtual bool UpdatePluginStates(Pedalboard &pedalboard) override;
|
||||
// virtual bool UpdatePluginStates(Pedalboard &pedalboard) override;
|
||||
virtual bool UpdatePluginState(PedalboardItem &pedalboardItem) override;
|
||||
|
||||
class RestartThread
|
||||
@@ -2032,21 +2043,6 @@ public:
|
||||
this->hostWriter.ParameterRequest(pParameterRequest);
|
||||
}
|
||||
|
||||
static int32_t GetRaspberryPiTemperature()
|
||||
{
|
||||
try
|
||||
{
|
||||
std::ifstream f("/sys/class/thermal/thermal_zone0/temp");
|
||||
int32_t temp;
|
||||
f >> temp;
|
||||
return temp;
|
||||
}
|
||||
catch (std::exception &)
|
||||
{
|
||||
return -1000000;
|
||||
}
|
||||
}
|
||||
|
||||
virtual JackHostStatus getJackStatus()
|
||||
{
|
||||
CleanRestartThreads(false);
|
||||
@@ -2062,7 +2058,7 @@ public:
|
||||
|
||||
result.msSinceLastUnderrun_ = (uint64_t)dt;
|
||||
|
||||
result.temperaturemC_ = GetRaspberryPiTemperature();
|
||||
result.temperaturemC_ = (int32_t)(std::round(cpuTemperatureMonitor->GetTemperatureC() * 1000));
|
||||
|
||||
result.active_ = IsAudioRunning();
|
||||
result.restarting_ = this->restarting;
|
||||
@@ -2071,12 +2067,14 @@ public:
|
||||
{
|
||||
result.cpuUsage_ = audioDriver->CpuUse();
|
||||
}
|
||||
GetCpuFrequency(&result.cpuFreqMax_, &result.cpuFreqMin_);
|
||||
GetCpuFrequency(&result.cpuFreqMin_, &result.cpuFreqMax_);
|
||||
result.hasCpuGovernor_ = HasCpuGovernor();
|
||||
if (result.hasCpuGovernor_)
|
||||
{
|
||||
result.governor_ = GetGovernor();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
result.governor_ = "";
|
||||
}
|
||||
|
||||
@@ -2121,7 +2119,7 @@ void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding> &bindin
|
||||
else if (i->symbol() == "prevBank")
|
||||
{
|
||||
this->prevBankMidiBinding.SetBinding(*i);
|
||||
}
|
||||
}
|
||||
else if (i->symbol() == "nextProgram")
|
||||
{
|
||||
this->nextPresetMidiBinding.SetBinding(*i);
|
||||
|
||||
@@ -170,6 +170,8 @@ namespace pipedal
|
||||
virtual void OnNotifyLv2RealtimeError(int64_t instanceId, const std::string &error) = 0;
|
||||
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) = 0;
|
||||
virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) = 0;
|
||||
|
||||
virtual void OnAlsaDriverTerminatedAbnormally() = 0;
|
||||
};
|
||||
|
||||
class JackHostStatus
|
||||
|
||||
@@ -181,6 +181,7 @@ void AvahiService::EntryGroupCallback(AvahiEntryGroup *g, AvahiEntryGroupState s
|
||||
char *n = avahi_alternative_service_name(avahiNameString);
|
||||
avahi_free(avahiNameString);
|
||||
avahiNameString = n;
|
||||
this->serviceName = n;
|
||||
Lv2Log::warning(SS("Service name collision, renaming service to '" << avahiNameString << "'\n"));
|
||||
/* And recreate the services */
|
||||
create_group(avahi_entry_group_get_client(g));
|
||||
@@ -253,7 +254,7 @@ void AvahiService::create_group(AvahiClient *c)
|
||||
* because it was reset previously, add our entries. */
|
||||
if (this->makeAnnouncement && avahi_entry_group_is_empty(group))
|
||||
{
|
||||
Lv2Log::debug(SS("Adding service '" << avahiNameString << "'"));
|
||||
Lv2Log::debug(SS("Adding service '" << serviceName << "'"));
|
||||
|
||||
std::string instanceTxtRecord = SS("id=" << this->instanceId);
|
||||
|
||||
|
||||
+7
-3
@@ -41,7 +41,7 @@ namespace macaron
|
||||
public:
|
||||
static std::string Encode(const std::vector<uint8_t> &data)
|
||||
{
|
||||
return Encode(data.size(),&(data[0]));
|
||||
return Encode(data.size(),data.data());
|
||||
}
|
||||
static std::string Encode(size_t size, const uint8_t *data)
|
||||
{
|
||||
@@ -55,13 +55,14 @@ namespace macaron
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
||||
'4', '5', '6', '7', '8', '9', '+', '/'};
|
||||
|
||||
|
||||
size_t in_len = size;
|
||||
size_t out_len = 4 * ((in_len + 2) / 3);
|
||||
std::string ret(out_len, '\0');
|
||||
size_t i;
|
||||
size_t i = 0;
|
||||
char *p = const_cast<char *>(ret.c_str());
|
||||
|
||||
for (i = 0; i < in_len - 2; i += 3)
|
||||
for (i = 0; i+2 < in_len; i += 3)
|
||||
{
|
||||
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
|
||||
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)];
|
||||
@@ -111,6 +112,9 @@ namespace macaron
|
||||
if (in_len % 4 != 0)
|
||||
throw std::invalid_argument("Input data size is not a multiple of 4");
|
||||
|
||||
if (in_len < 4) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
size_t out_len = in_len / 4 * 3;
|
||||
if (input[in_len - 1] == '=')
|
||||
out_len--;
|
||||
|
||||
@@ -0,0 +1,465 @@
|
||||
// Copyright (c) 2024 Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "BootConfig.hpp"
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include "ss.hpp"
|
||||
#include <algorithm>
|
||||
#include "SysExec.hpp"
|
||||
#include "util.hpp"
|
||||
#include <set>
|
||||
#include "SystemConfigFile.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
static bool IsGrubBootLoader()
|
||||
{
|
||||
const std::vector<std::string> grub_config_paths = {
|
||||
"/boot/grub/grub.cfg",
|
||||
"/boot/grub2/grub.cfg",
|
||||
};
|
||||
|
||||
for (const auto &path : grub_config_paths)
|
||||
{
|
||||
if (std::filesystem::exists(path))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::set<std::string> KNOWN_KERNEL_TYPES = {
|
||||
"PREEMPT_NONE",
|
||||
"PREEMPT_VOLUNTARY",
|
||||
"PREEMPT",
|
||||
"PREEMPT_RT",
|
||||
"PREEMPT_DYNAMIC"};
|
||||
|
||||
std::string GetKernelType()
|
||||
{
|
||||
auto result = sysExecForOutput("uname", "-a");
|
||||
if (result.exitCode != EXIT_SUCCESS)
|
||||
{
|
||||
throw std::runtime_error("Unable to execute 'uname -a'");
|
||||
}
|
||||
std::vector<std::string> items = split(result.output, ' ');
|
||||
for (std::string &item : items)
|
||||
{
|
||||
if (KNOWN_KERNEL_TYPES.contains(item))
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
std::string ReadFileLine(const fs::path &path)
|
||||
{
|
||||
if (!fs::exists(path))
|
||||
{
|
||||
throw std::runtime_error(SS("File does not exist: " << path));
|
||||
}
|
||||
|
||||
std::ifstream f(path);
|
||||
if (!f.is_open())
|
||||
{
|
||||
throw std::runtime_error(SS("Can't read file " << path));
|
||||
}
|
||||
std::string line;
|
||||
|
||||
std::getline(f, line);
|
||||
return line;
|
||||
}
|
||||
std::vector<std::string> ReadFileArgs(const fs::path &path)
|
||||
{
|
||||
std::string line = ReadFileLine(path);
|
||||
|
||||
return split(line, ' ');
|
||||
}
|
||||
|
||||
using namespace pipedal;
|
||||
BootConfig::BootConfig()
|
||||
{
|
||||
if (fs::exists("/boot/firmware/cmdline.txt"))
|
||||
{
|
||||
this->bootLoader = BootLoaderT::UBoot;
|
||||
}
|
||||
else if (IsGrubBootLoader())
|
||||
{
|
||||
this->bootLoader = BootLoaderT::Grub;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->bootLoader = BootLoaderT::Unknown;
|
||||
}
|
||||
|
||||
this->kernelType = GetKernelType();
|
||||
|
||||
if (this->kernelType == "PREEMPT_DYNAMIC")
|
||||
{
|
||||
this->dynamicScheduler = DynamicSchedulerT::Voluntary; // the default.
|
||||
}
|
||||
|
||||
std::string cmdLine = ReadFileLine("/proc/cmdline");
|
||||
std::vector<std::string> cmdLineArgs = split(cmdLine, ' ');
|
||||
|
||||
for (const auto &arg : cmdLineArgs)
|
||||
{
|
||||
if (arg == "preempt=full")
|
||||
{
|
||||
this->dynamicScheduler = DynamicSchedulerT::Full;
|
||||
}
|
||||
else if (arg == "preempt=voluntary")
|
||||
{
|
||||
this->dynamicScheduler = DynamicSchedulerT::Voluntary;
|
||||
}
|
||||
else if (arg == "preempt=none")
|
||||
{
|
||||
this->dynamicScheduler = DynamicSchedulerT::None;
|
||||
}
|
||||
else if (arg.starts_with("preempt="))
|
||||
{
|
||||
this->dynamicScheduler = DynamicSchedulerT::Unknown;
|
||||
}
|
||||
else if (arg == "threadirqs")
|
||||
{
|
||||
this->threadedIrqs = true;
|
||||
}
|
||||
}
|
||||
this->canSetThreadIrqs = this->kernelType == "PREEMPT" || this->kernelType == "PREEMPT_DYNAMIC";
|
||||
}
|
||||
|
||||
BootConfig::~BootConfig()
|
||||
{
|
||||
this->thread = nullptr; // join and clean up the tread before deleting ANYTHING else.
|
||||
}
|
||||
bool BootConfig::operator==(BootConfig &other) const
|
||||
{
|
||||
return this->bootLoader == other.bootLoader && this->kernelType == other.kernelType && this->preemptMode == other.preemptMode && this->threadedIrqs == other.threadedIrqs;
|
||||
}
|
||||
|
||||
bool BootConfig::CanWriteConfig()
|
||||
{
|
||||
if (!CanSetThreadIrqs() && dynamicScheduler == DynamicSchedulerT::NotApplicable)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return (this->bootLoader != BootLoaderT::Unknown);
|
||||
}
|
||||
|
||||
void BootConfig::WriteConfiguration(std::function<void(bool success, std::string errorMessage)> onComplete)
|
||||
{
|
||||
this->thread = nullptr; // wait for any previous operations to complete.
|
||||
if (!changed)
|
||||
{
|
||||
if (onComplete)
|
||||
onComplete(true, "");
|
||||
}
|
||||
else
|
||||
{
|
||||
changed = false;
|
||||
|
||||
if (this->bootLoader == BootLoaderT::UBoot)
|
||||
{
|
||||
try
|
||||
{
|
||||
WriteUBootConfiguration(onComplete);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
if (onComplete)
|
||||
onComplete(false, e.what());
|
||||
}
|
||||
}
|
||||
else if (this->bootLoader == BootLoaderT::Grub)
|
||||
{
|
||||
WriteGrubConfiguration(onComplete);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (onComplete)
|
||||
onComplete(false, "Unsupported bootloader.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string commandLineOption(BootConfig::DynamicSchedulerT dynamicScheduler)
|
||||
{
|
||||
switch (dynamicScheduler)
|
||||
{
|
||||
case BootConfig::DynamicSchedulerT::None:
|
||||
return "preempt=none";
|
||||
case BootConfig::DynamicSchedulerT::Voluntary:
|
||||
return "preempt=voluntary";
|
||||
case BootConfig::DynamicSchedulerT::Full:
|
||||
return "preempt=full";
|
||||
default:
|
||||
throw std::range_error("Invalid value.");
|
||||
}
|
||||
}
|
||||
void BootConfig::WriteUBootConfiguration(std::function<void(bool success, std::string errorMessage)> onComplete)
|
||||
{
|
||||
this->thread = nullptr; // blocks if already running.
|
||||
|
||||
this->thread = std::make_unique<std::jthread>(
|
||||
[this, onComplete]()
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
fs::path cmdlineFile = "/boot/firmware/cmdline.txt";
|
||||
|
||||
fs::path backupFile = "/boot/firmware/cmdline.txt.bak";
|
||||
if (!fs::exists(backupFile))
|
||||
{
|
||||
try {
|
||||
fs::copy_file(cmdlineFile,backupFile);
|
||||
} catch(...)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
std::string line = ReadFileLine(cmdlineFile);
|
||||
|
||||
std::string newLine = SetCmdlineArgs(line);
|
||||
|
||||
{
|
||||
std::ofstream f(cmdlineFile);
|
||||
if (!f.is_open())
|
||||
{
|
||||
throw std::runtime_error(SS("Can't write to " << cmdlineFile));
|
||||
}
|
||||
f << newLine << '\n'; // trailing \n is REQUIRED
|
||||
}
|
||||
|
||||
if (onComplete)
|
||||
{
|
||||
onComplete(true, "");
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
if (onComplete)
|
||||
{
|
||||
onComplete(false, e.what());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static std::string QuoteGrubArgument(const std::string &value) {
|
||||
std::ostringstream ss;
|
||||
ss << '\"';
|
||||
for (char c : value) {
|
||||
if (c == '\\' || c == '\"')
|
||||
{
|
||||
ss << '\\' << c;
|
||||
} else {
|
||||
ss << c;
|
||||
}
|
||||
}
|
||||
ss << '\"';
|
||||
return ss.str();
|
||||
}
|
||||
static std::string UnquoteGrubArgument(const std::string &value) {
|
||||
if (value.length() == 0) return value;
|
||||
std::istringstream ss(value);
|
||||
if (ss.peek() == '\"')
|
||||
{
|
||||
// c-like; \ is escape character.
|
||||
if (ss.peek() != '\"')
|
||||
{
|
||||
throw std::runtime_error("Invalid string in GRUB configuration file.");
|
||||
}
|
||||
ss.get();
|
||||
std::ostringstream result;
|
||||
while (ss.peek() != '\"' && ss.peek() != -1)
|
||||
{
|
||||
int c = ss.get();
|
||||
if (c == -1 || c == '\n') {
|
||||
throw std::runtime_error("Invalid string in GRUB configuration file.");
|
||||
}
|
||||
if (c == '\\')
|
||||
{
|
||||
int c2 = ss.get();
|
||||
if (c2 == '\n' || c2 == -1) {
|
||||
throw std::runtime_error("Invalid string in GRUB configuration file.");
|
||||
}
|
||||
result << (char)c2;
|
||||
} else {
|
||||
result << (char)c;
|
||||
}
|
||||
}
|
||||
return result.str();
|
||||
} else if (ss.peek() == '\'')
|
||||
{
|
||||
ss.get();
|
||||
std::ostringstream result;
|
||||
while (true)
|
||||
{
|
||||
int c = ss.get();
|
||||
if (c == -1 || c == '\n') {
|
||||
throw std::runtime_error("Invalid string in GRUB configuration file.");
|
||||
}
|
||||
if (c == '\'')
|
||||
{
|
||||
break;
|
||||
}
|
||||
result << (char)c;
|
||||
}
|
||||
return result.str();
|
||||
} else {
|
||||
throw std::runtime_error("Invalid string in GRUB configuration file.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::string BootConfig::SetCmdlineArgs(const std::string &cmdLine)
|
||||
{
|
||||
std::vector<std::string> bootArgs = split(cmdLine,' ');
|
||||
std::vector<std::string> newBootArgs;
|
||||
for (const auto &value : bootArgs)
|
||||
{
|
||||
if (value != "threadirqs" && !value.starts_with("preempt="))
|
||||
{
|
||||
newBootArgs.push_back(value);
|
||||
}
|
||||
}
|
||||
if (this->canSetThreadIrqs && this->threadedIrqs)
|
||||
{
|
||||
newBootArgs.push_back("threadirqs");
|
||||
}
|
||||
if (this->dynamicScheduler != DynamicSchedulerT::NotApplicable)
|
||||
{
|
||||
newBootArgs.push_back(commandLineOption(this->dynamicScheduler));
|
||||
}
|
||||
std::stringstream ss;
|
||||
bool firstArg = true;
|
||||
for (const std::string &arg : newBootArgs)
|
||||
{
|
||||
if (!firstArg)
|
||||
{
|
||||
ss << ' ';
|
||||
}
|
||||
firstArg = false;
|
||||
ss << arg;
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void BootConfig::WriteGrubConfiguration(std::function<void(bool success, std::string errorMessage)> onComplete)
|
||||
{
|
||||
this->thread = nullptr; // blocks if already running.
|
||||
|
||||
this->thread = std::make_unique<std::jthread>(
|
||||
[this, onComplete]()
|
||||
{
|
||||
try
|
||||
{
|
||||
////////// rewrite /etc/default/grub ////////////
|
||||
fs::path configPath = "/etc/default/grub";
|
||||
fs::path backupPath = "/etc/default/grub.bak";
|
||||
if (!fs::exists(backupPath))
|
||||
{
|
||||
try {
|
||||
fs::copy_file(configPath,backupPath);
|
||||
} catch(...)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
SystemConfigFile grubFile(configPath);
|
||||
|
||||
std::string linePrefix = "GRUB_CMDLINE_LINUX_DEFAULT=";
|
||||
auto line = grubFile.GetLineNumberStartingWith(linePrefix);
|
||||
if (line < 0)
|
||||
{
|
||||
throw std::runtime_error(SS("Unexpected file format: " << configPath));
|
||||
}
|
||||
std::string cmdlineDefault = grubFile.Get(line);
|
||||
|
||||
std::string cmdlineArgs = UnquoteGrubArgument(cmdlineDefault.substr(linePrefix.length()));
|
||||
|
||||
std::string newArgs = SetCmdlineArgs(cmdlineArgs);
|
||||
|
||||
std::string newLine = linePrefix + QuoteGrubArgument(newArgs);
|
||||
|
||||
grubFile.Set(line,newLine);
|
||||
|
||||
grubFile.Save(configPath);
|
||||
|
||||
/////// regenerate actual GRUB boot files /////////////
|
||||
|
||||
auto result = sysExecForOutput("update-grub","");
|
||||
if (result.exitCode != EXIT_SUCCESS)
|
||||
{
|
||||
throw std::runtime_error(SS("update-grub failed. (" << result.output.substr(0,std::min((size_t)60,result.output.length())) << ")"));
|
||||
}
|
||||
//// signal completion //////
|
||||
|
||||
if (onComplete)
|
||||
{
|
||||
onComplete(true,"");
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
if (onComplete)
|
||||
{
|
||||
onComplete(false, e.what());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void BootConfig::DynamicScheduler(DynamicSchedulerT value)
|
||||
{
|
||||
if (value != this->dynamicScheduler)
|
||||
{
|
||||
if (this->dynamicScheduler == DynamicSchedulerT::NotApplicable)
|
||||
{
|
||||
throw std::runtime_error("Illegal operation.");
|
||||
}
|
||||
this->dynamicScheduler = value;
|
||||
this->changed = true;
|
||||
}
|
||||
}
|
||||
void BootConfig::ThreadedIrqs(bool value)
|
||||
{
|
||||
if (value != threadedIrqs)
|
||||
{
|
||||
if (!CanSetThreadIrqs())
|
||||
{
|
||||
throw std::runtime_error("Illegal operation.");
|
||||
}
|
||||
this->threadedIrqs = value;
|
||||
this->changed = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2024 Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
|
||||
namespace pipedal {
|
||||
class BootConfig {
|
||||
public:
|
||||
BootConfig();
|
||||
~BootConfig();
|
||||
|
||||
enum class BootLoaderT {
|
||||
UBoot,
|
||||
Grub,
|
||||
Unknown
|
||||
};
|
||||
|
||||
enum class DynamicSchedulerT {
|
||||
None = 0,
|
||||
Voluntary = 1,
|
||||
Full = 2,
|
||||
|
||||
NotApplicable = -1,
|
||||
Unknown = -2,
|
||||
|
||||
};
|
||||
|
||||
// BEWARE: The onComplete function gets called from off the main thread on machines with GRUB bootloaders!
|
||||
void WriteConfiguration(std::function<void(bool success,std::string errorMessage)> onComplete);
|
||||
BootLoaderT BootLoader() const { return bootLoader; }
|
||||
const std::string&KernelType() const { return kernelType; }
|
||||
|
||||
bool CanSetThreadIrqs() const { return canSetThreadIrqs; }
|
||||
bool ThreadedIrqs() const { return threadedIrqs; }
|
||||
void ThreadedIrqs(bool value);
|
||||
|
||||
const DynamicSchedulerT DynamicScheduler() const { return dynamicScheduler; }
|
||||
void DynamicScheduler(DynamicSchedulerT value);
|
||||
|
||||
|
||||
bool CanWriteConfig();
|
||||
|
||||
bool Changed() const { return changed; }
|
||||
|
||||
bool operator==(BootConfig&other) const;
|
||||
private:
|
||||
std::unique_ptr<std::jthread> thread;
|
||||
std::string SetCmdlineArgs(const std::string &args);
|
||||
|
||||
void WriteUBootConfiguration(std::function<void(bool success,std::string errorMessage)> onComplete);
|
||||
void WriteGrubConfiguration(std::function<void(bool success,std::string errorMessage)> onComplete);
|
||||
|
||||
bool changed = false;
|
||||
BootLoaderT bootLoader = BootLoaderT::Unknown;
|
||||
std::string kernelType;
|
||||
std::string preemptMode;
|
||||
DynamicSchedulerT dynamicScheduler = DynamicSchedulerT::NotApplicable;
|
||||
bool canSetThreadIrqs = false;
|
||||
bool threadedIrqs = false;
|
||||
|
||||
};
|
||||
};
|
||||
+71
-17
@@ -7,15 +7,49 @@ set (USE_PCH 1)
|
||||
|
||||
set (ENABLE_BACKTRACE 1)
|
||||
|
||||
set (USE_SANITIZE true)
|
||||
set (USE_SANITIZE OFF) # seems to be broken on Ubuntu 24.10
|
||||
|
||||
set(CXX_STANDARD 20)
|
||||
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
# FOR FTXUI
|
||||
if(CMAKE_VERSION VERSION_GREATER 3.30)
|
||||
cmake_policy(SET CMP0169 OLD) # Allow use of old FETCHCONTENT macros.
|
||||
endif()
|
||||
|
||||
# Our FTXUI option takes precedence over the one in the ftxui CMakeLists.txt
|
||||
# because FTXUI uses cmake_minimum_required(VERSION 3.12)
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||
|
||||
set(FETCHCONTENT_QUIET ON)
|
||||
set(FTXUI_QUIET ON)
|
||||
|
||||
FetchContent_Declare(ftxui
|
||||
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
|
||||
GIT_TAG v5.0.0
|
||||
)
|
||||
|
||||
FetchContent_GetProperties(ftxui)
|
||||
if(NOT ftxui_POPULATED)
|
||||
FetchContent_Populate(ftxui)
|
||||
add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
message(STATUS "*** ${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR}")
|
||||
|
||||
|
||||
|
||||
include(FindPkgConfig)
|
||||
|
||||
find_package(sdbus-c++ REQUIRED)
|
||||
|
||||
|
||||
find_package(ICU REQUIRED COMPONENTS uc i18n )
|
||||
message(STATUS "ICU_LIBRARIES: ${ICU_LIBRARIES}")
|
||||
|
||||
|
||||
if(DEFINED ENV{GITHUB_ACTIONS})
|
||||
message(STATUS "Building in GitHub Actions environment")
|
||||
set(GITHUB_ACTIONS TRUE)
|
||||
@@ -115,16 +149,16 @@ add_compile_definitions(DEBIAN_ARCHITECTURE="${DEBIAN_ARCHITECTURE}")
|
||||
if (CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
message(STATUS "Debug build")
|
||||
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -DDEBUG -D_GLIBCXX_DEBUG" )
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -DDEBUG -D_GLIBCXX_DEBUG" )
|
||||
if (USE_SANITIZE)
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -static-libasan " )
|
||||
endif()
|
||||
elseif(CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)
|
||||
message(STATUS "RelWithgDebInfo build")
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG" )
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O3 -DNDEBUG" )
|
||||
else()
|
||||
message(STATUS "Release build")
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG" )
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG" )
|
||||
endif()
|
||||
|
||||
if (ENABLE_BACKTRACE)
|
||||
@@ -158,8 +192,10 @@ else()
|
||||
endif()
|
||||
|
||||
set (PIPEDAL_SOURCES
|
||||
CpuTemperatureMonitor.cpp CpuTemperatureMonitor.hpp
|
||||
SchedulerPriority.hpp SchedulerPriority.cpp
|
||||
ModFileTypes.cpp ModFileTypes.hpp
|
||||
MimeTypes.cpp MimeTypes.hpp
|
||||
PatchPropertyWriter.hpp
|
||||
PresetBundle.cpp PresetBundle.hpp
|
||||
RealtimeMidiEventType.hpp
|
||||
@@ -178,9 +214,8 @@ set (PIPEDAL_SOURCES
|
||||
FilePropertyDirectoryTree.cpp FilePropertyDirectoryTree.hpp
|
||||
FileEntry.cpp FileEntry.hpp
|
||||
atom_object.hpp atom_object.cpp
|
||||
FileBrowserFiles.h
|
||||
lv2ext/pipedal.lv2/ext/fileBrowser.h
|
||||
FileBrowserFilesFeature.hpp FileBrowserFilesFeature.cpp
|
||||
MimeTypes.cpp MimeTypes.hpp
|
||||
inverting_mutex.hpp
|
||||
DbDezipper.hpp DbDezipper.cpp
|
||||
WebServerLog.hpp
|
||||
@@ -272,16 +307,15 @@ set (PIPEDAL_INCLUDES
|
||||
.
|
||||
)
|
||||
|
||||
|
||||
set(PIPEDAL_LIBS libpipedald zip
|
||||
PiPedalCommon
|
||||
icui18n
|
||||
icuuc
|
||||
icudata
|
||||
pthread atomic stdc++fs asound avahi-common avahi-client systemd
|
||||
${VST3_LIBRARIES}
|
||||
${LILV_0_LIBRARIES}
|
||||
# ${JACK_LIBRARIES} - pending delete for JACK support.
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -299,6 +333,24 @@ if(${USE_PCH})
|
||||
endif()
|
||||
|
||||
|
||||
#################################
|
||||
|
||||
add_executable(pipedal_kconfig
|
||||
kconfigMain.cpp
|
||||
BootConfig.cpp BootConfig.hpp
|
||||
SystemConfigFile.hpp SystemConfigFile.cpp
|
||||
|
||||
|
||||
)
|
||||
|
||||
target_link_libraries(pipedal_kconfig
|
||||
PRIVATE ftxui::screen
|
||||
PRIVATE ftxui::dom
|
||||
PRIVATE ftxui::component # Not needed for this example.
|
||||
PRIVATE PiPedalCommon
|
||||
systemd
|
||||
)
|
||||
|
||||
#################################
|
||||
add_executable(pipedald
|
||||
asan_options.cpp # disable leak checking for sanitize=address.
|
||||
@@ -325,9 +377,10 @@ set_target_properties(hotspotManagerTest PROPERTIES EXCLUDE_FROM_ALL true)
|
||||
|
||||
|
||||
|
||||
add_executable(pipedaltest testMain.cpp
|
||||
add_executable(pipedaltest
|
||||
testMain.cpp
|
||||
|
||||
InvertingMutexTest.cpp
|
||||
ModFileTypesTest.cpp
|
||||
jsonTest.cpp
|
||||
UpdaterTest.cpp
|
||||
|
||||
@@ -339,6 +392,8 @@ add_executable(pipedaltest testMain.cpp
|
||||
PiPedalAlsaTest.cpp
|
||||
UnixSocketTest.cpp
|
||||
|
||||
LocaleTest.cpp
|
||||
|
||||
Lv2HostLeakTest.cpp
|
||||
|
||||
|
||||
@@ -348,7 +403,7 @@ add_executable(pipedaltest testMain.cpp
|
||||
MemDebug.cpp
|
||||
MemDebug.hpp
|
||||
)
|
||||
target_link_libraries(pipedaltest PRIVATE ${PIPEDAL_LIBS})
|
||||
target_link_libraries(pipedaltest PRIVATE ${PIPEDAL_LIBS} ${ICU_LIBRARIES})
|
||||
target_include_directories(pipedaltest PRIVATE ${PIPEDAL_INCLUDES}
|
||||
)
|
||||
|
||||
@@ -642,7 +697,9 @@ add_executable(pipedalconfig
|
||||
ConfigMain.cpp
|
||||
alsaCheck.cpp
|
||||
alsaCheck.hpp
|
||||
BootConfig.cpp BootConfig.hpp
|
||||
ModFileTypes.cpp ModFileTypes.hpp
|
||||
MimeTypes.cpp MimeTypes.hpp
|
||||
PiPedalConfiguration.hpp PiPedalConfiguration.cpp
|
||||
JackServerSettings.hpp JackServerSettings.cpp
|
||||
SystemConfigFile.hpp SystemConfigFile.cpp
|
||||
@@ -653,9 +710,6 @@ add_executable(pipedalconfig
|
||||
)
|
||||
|
||||
target_link_libraries(pipedalconfig PRIVATE PiPedalCommon pthread atomic uuid stdc++fs asound
|
||||
icui18n
|
||||
icuuc
|
||||
icudata
|
||||
|
||||
)
|
||||
|
||||
@@ -774,7 +828,7 @@ add_custom_target (
|
||||
)
|
||||
|
||||
|
||||
install (TARGETS pipedalconfig pipedal_latency_test DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
|
||||
install (TARGETS pipedalconfig pipedal_kconfig pipedal_latency_test DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
|
||||
EXPORT pipedalTargets)
|
||||
|
||||
install (TARGETS pipedald pipedaladmind pipedal_update DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
|
||||
|
||||
+185
-55
@@ -27,6 +27,8 @@
|
||||
#include "alsaCheck.hpp"
|
||||
#include "RegDb.hpp"
|
||||
#include "Locale.hpp"
|
||||
#include "Finally.hpp"
|
||||
#include "BootConfig.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <stdlib.h>
|
||||
@@ -560,38 +562,6 @@ void InstallAudioService()
|
||||
#endif
|
||||
}
|
||||
|
||||
int SudoExec(int argc, char **argv)
|
||||
{
|
||||
// re-execute with SUDO in order to prompt for SUDO credentials once only.
|
||||
std::vector<char *> args;
|
||||
std::string pkexec = "/usr/bin/sudo"; // staged because "ISO C++ forbids converting a string constant to std::vector<char*>::value_type"(!)
|
||||
args.push_back((char *)(pkexec.c_str()));
|
||||
|
||||
std::string sPath = getSelfExePath();
|
||||
args.push_back(const_cast<char *>(sPath.c_str()));
|
||||
for (int arg = 1; arg < argc; ++arg)
|
||||
{
|
||||
args.push_back(const_cast<char *>(argv[arg]));
|
||||
}
|
||||
|
||||
args.push_back(nullptr);
|
||||
|
||||
char **newArgs = &args[0];
|
||||
|
||||
int pbPid;
|
||||
int returnValue = 0;
|
||||
|
||||
if ((pbPid = fork()) == 0)
|
||||
{
|
||||
return execv(newArgs[0], newArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
waitpid(pbPid, &returnValue, 0);
|
||||
int exitStatus = WEXITSTATUS(returnValue);
|
||||
return exitStatus;
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsP2pServiceEnabled()
|
||||
{
|
||||
@@ -953,6 +923,132 @@ void InstallPgpKey()
|
||||
sysExec(cmd.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static uint32_t autoSelectPorts[] = {
|
||||
80,
|
||||
81, // unofficial alternate
|
||||
8080, // unofficial alternate
|
||||
8081, // unofficial alternate
|
||||
8008, // unofficial alternate
|
||||
83 // Not official at all.
|
||||
};
|
||||
|
||||
|
||||
|
||||
static bool isPortInUse(int port) {
|
||||
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sockfd < 0) {
|
||||
throw std::runtime_error(SS("Socket creation failed: " << strerror(errno)));
|
||||
}
|
||||
Finally ff{[sockfd]() {
|
||||
close(sockfd);
|
||||
}};
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
// Try to bind to the port
|
||||
int result = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
|
||||
|
||||
if (result < 0) {
|
||||
if (errno == EADDRINUSE) {
|
||||
return true; // Port is in use
|
||||
} else {
|
||||
cout << "Warning: Unexpected error binding socket to port " << port << ": " << strerror(errno) << endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false; // Port is free
|
||||
}
|
||||
|
||||
|
||||
static std::string AutoSelectPort() {
|
||||
constexpr size_t MAX_PORT = sizeof(autoSelectPorts)/sizeof(autoSelectPorts[0]);
|
||||
|
||||
int32_t port = -1;
|
||||
for (size_t i = 0; i < MAX_PORT; ++i)
|
||||
{
|
||||
if (!isPortInUse(autoSelectPorts[i]))
|
||||
{
|
||||
port = autoSelectPorts[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (port == -1) {
|
||||
cout << "Warning: Can't find an available HTTP port. Setting to port 80 anyway." << endl;
|
||||
port = 80;
|
||||
} else if (port != 80)
|
||||
{
|
||||
cout << "Warning: Port 80 is already in use. Using port " << port << " instead." << endl;
|
||||
}
|
||||
|
||||
return SS(port);
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool SetWebServerPort(std::string portOption)
|
||||
{
|
||||
|
||||
try {
|
||||
auto nPos = portOption.find_last_of(':');
|
||||
std::string strPort;
|
||||
if (nPos != std::string::npos)
|
||||
{
|
||||
strPort = portOption.substr(nPos+1);
|
||||
} else {
|
||||
strPort = portOption;
|
||||
}
|
||||
std::istringstream ss(strPort);
|
||||
|
||||
uint16_t iPort = 0;
|
||||
ss >> iPort;
|
||||
if (!ss)
|
||||
{
|
||||
throw std::runtime_error(SS("Invalid port number: " << strPort));
|
||||
}
|
||||
|
||||
StopService(false);
|
||||
|
||||
PrepareServiceConfigurationFile(iPort);
|
||||
|
||||
StartService(true);
|
||||
|
||||
cout << "PiPedal web server is listening on port " << iPort << endl;
|
||||
} catch (const std::exception&e)
|
||||
{
|
||||
cout << "Error: " << e.what() << endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CheckPreemptDynamicConfig()
|
||||
{
|
||||
try {
|
||||
BootConfig bootConfig;
|
||||
|
||||
if (bootConfig.KernelType() == "PREEMPT_DYNAMIC")
|
||||
{
|
||||
if (bootConfig.DynamicScheduler() != BootConfig::DynamicSchedulerT::Full)
|
||||
{
|
||||
cout << endl;
|
||||
cout << "Warning: PREEMPT_DYNAMIC kernel has not been configured for 'preempt=full'" << endl;
|
||||
cout << "Run pipedal_kconfig to configure the kernel for real-time audio." << endl;
|
||||
cout << endl;
|
||||
}
|
||||
}
|
||||
|
||||
} catch(const std::exception& /* ignore */)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Install(const fs::path &programPrefix, const std::string endpointAddress)
|
||||
{
|
||||
cout << "Configuring pipedal" << endl;
|
||||
@@ -1000,14 +1096,14 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
|
||||
{
|
||||
throw std::runtime_error("Invalid endpoint address: " + endpointAddress);
|
||||
}
|
||||
uint16_t port;
|
||||
uint16_t port = 80;
|
||||
auto strPort = endpointAddress.substr(endpos + 1);
|
||||
try
|
||||
{
|
||||
auto lport = std::stoul(strPort);
|
||||
if (lport == 0 || lport >= std::numeric_limits<uint16_t>::max())
|
||||
{
|
||||
throw std::runtime_error("out of range.");
|
||||
throw std::runtime_error("Port number is too large.");
|
||||
}
|
||||
port = (uint16_t)lport;
|
||||
std::stringstream s;
|
||||
@@ -1020,6 +1116,7 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
|
||||
s << "Invalid port number: " << strPort;
|
||||
throw std::runtime_error(s.str());
|
||||
}
|
||||
|
||||
PrepareServiceConfigurationFile(port);
|
||||
|
||||
bool authBindRequired = port < 512;
|
||||
@@ -1183,14 +1280,25 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
|
||||
|
||||
// Restart Wi-Fi Direct if neccessary.
|
||||
OnWifiReinstall();
|
||||
|
||||
CheckPreemptDynamicConfig();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
// don't allow abnormal termination, which leaves the package in a state that's
|
||||
// difficult to uninstall.
|
||||
cout << "Error: " << e.what();
|
||||
cout << "Error: " << e.what() << endl;
|
||||
cout << " Run 'pipedalconfig --install' again to complete setup of PiPedal." << endl;
|
||||
}
|
||||
const std::string ADDR_ANY = "0.0.0.0:";
|
||||
|
||||
if (endpointAddress.starts_with(ADDR_ANY))
|
||||
{
|
||||
cout << "PiPedal web server is listening on port " << endpointAddress.substr(ADDR_ANY.length()) << endl;
|
||||
} else {
|
||||
cout << "PiPedal web server is listening on port " << endpointAddress << endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static std::string GetCurrentWebServicePort()
|
||||
@@ -1199,7 +1307,7 @@ static std::string GetCurrentWebServicePort()
|
||||
config.Load("/etc/pipedal/config", "");
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << config.GetSocketServerPort();
|
||||
ss << config.GetSocketServerAddress() << ":" << config.GetSocketServerPort();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
@@ -1208,7 +1316,7 @@ static void PrintHelp()
|
||||
PrettyPrinter pp;
|
||||
pp << "pipedalconfig - Command-line post-install configuration for PiPedal"
|
||||
<< "\n"
|
||||
<< "Copyright (c) 2022-2024 Robin Davies."
|
||||
<< "Copyright (c) 2022-2024 Robin E. R. Davies."
|
||||
<< "\n"
|
||||
<< "\n"
|
||||
<< "See https://rerdavies.github.io/pipedal/Documentation.html for "
|
||||
@@ -1239,6 +1347,10 @@ static void PrintHelp()
|
||||
<< "The --port option controls which TCP/IP port the web server uses."
|
||||
<< "\n\n"
|
||||
|
||||
<< HangingIndent() << " --port <port#>\t"
|
||||
<< "Set the TCP/IP port that the web server will use."
|
||||
<< "\n"
|
||||
|
||||
<< HangingIndent() << " --uninstall\t"
|
||||
<< "Remove installed services."
|
||||
<< "\n"
|
||||
@@ -1401,6 +1513,29 @@ void ListValidCountryCodes()
|
||||
}
|
||||
}
|
||||
|
||||
static std::string ProcessPortOption(std::string portOption)
|
||||
{
|
||||
if (portOption == "" || portOption == "0") // allow testing of port autodetect.
|
||||
{
|
||||
std::string t = GetCurrentWebServicePort();
|
||||
if (portOption != "0") {// allows easy debugging of port AutoSelectPort.
|
||||
portOption = t;
|
||||
} else {
|
||||
portOption = "";
|
||||
}
|
||||
if (portOption == "")
|
||||
{
|
||||
portOption = AutoSelectPort();
|
||||
}
|
||||
}
|
||||
std::string ADDR_ANY = "0.0.0.0:";
|
||||
|
||||
if (portOption.find(':') == string::npos)
|
||||
{
|
||||
portOption = ADDR_ANY + portOption;
|
||||
}
|
||||
return portOption;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
@@ -1457,6 +1592,10 @@ int main(int argc, char **argv)
|
||||
uninstall + stop + start + enable + disable + enable_hotspot +
|
||||
disable_hotspot + restart + enable_p2p + disable_p2p + list_p2p_channels +
|
||||
fix_permissions + list_wifi_country_codes;
|
||||
if (actionCount == 0 && portOption.length() != 0)
|
||||
{
|
||||
++actionCount;
|
||||
}
|
||||
if (actionCount > 1)
|
||||
{
|
||||
throw std::runtime_error("Please provide only one action.");
|
||||
@@ -1553,12 +1692,6 @@ int main(int argc, char **argv)
|
||||
{
|
||||
return ListP2PChannels(parser.Arguments());
|
||||
}
|
||||
if (portOption.size() != 0 && !install)
|
||||
{
|
||||
cout << "Error: -port option can only be specified with the -install option."
|
||||
<< "\n";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
auto uid = getuid();
|
||||
if (uid != 0 && !nosudo)
|
||||
@@ -1568,6 +1701,12 @@ int main(int argc, char **argv)
|
||||
|
||||
try
|
||||
{
|
||||
if (portOption.length() != 0 && !install)
|
||||
{
|
||||
portOption = ProcessPortOption(portOption);
|
||||
SetWebServerPort(portOption);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (fix_permissions)
|
||||
{
|
||||
FixPermissions();
|
||||
@@ -1595,20 +1734,11 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
if (portOption == "")
|
||||
{
|
||||
portOption = GetCurrentWebServicePort();
|
||||
if (portOption == "")
|
||||
{
|
||||
portOption = "80";
|
||||
}
|
||||
}
|
||||
if (portOption.find(':') == string::npos)
|
||||
{
|
||||
portOption = "0.0.0.0:" + portOption;
|
||||
}
|
||||
portOption = ProcessPortOption(portOption);
|
||||
|
||||
Install(prefix, portOption);
|
||||
FileSystemSync();
|
||||
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
#include "CpuTemperatureMonitor.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <optional>
|
||||
#include <system_error>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using namespace pipedal;
|
||||
|
||||
|
||||
|
||||
static constexpr std::string_view THERMAL_PATH = "/sys/class/thermal/";
|
||||
|
||||
|
||||
static std::optional<std::string> readFile(const fs::path& path) {
|
||||
std::ifstream file(path);
|
||||
if (!file) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string content;
|
||||
std::getline(file, content);
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
static bool isCpuThermal(const fs::path &zone_path) {
|
||||
auto type = readFile(zone_path / "type");
|
||||
return type && (*type == "cpu-thermal") || (*type == "x86_pkg_temp");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
class CpuTemperatureMonitorImpl: public CpuTemperatureMonitor {
|
||||
private:
|
||||
std::vector<fs::path> cpuZones;
|
||||
public:
|
||||
explicit CpuTemperatureMonitorImpl(std::vector<fs::path>&&cpuZones)
|
||||
: cpuZones(std::move(cpuZones)) {
|
||||
}
|
||||
|
||||
|
||||
virtual float GetTemperatureC() override {
|
||||
float result = INVALID_TEMPERATURE*1000;
|
||||
|
||||
for (const auto&cpuZone: cpuZones)
|
||||
{
|
||||
|
||||
std::ifstream file(cpuZone / "temp");
|
||||
if (file) {
|
||||
float v;
|
||||
file >> v;
|
||||
if (file)
|
||||
{
|
||||
if (v > result)
|
||||
{
|
||||
result = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result/1000;
|
||||
}
|
||||
};
|
||||
|
||||
static std::vector<fs::path> findCpuThermalZones() {
|
||||
std::vector<fs::path> cpu_zones;
|
||||
std::error_code ec;
|
||||
|
||||
for (const auto& entry : fs::directory_iterator(THERMAL_PATH, ec)) {
|
||||
if (ec) {
|
||||
std::cerr << "Error reading directory: " << ec.message() << '\n';
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& path = entry.path();
|
||||
if (path.filename().string().find("thermal_zone") == 0) {
|
||||
if (isCpuThermal(path)) {
|
||||
cpu_zones.push_back(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cpu_zones;
|
||||
}
|
||||
|
||||
|
||||
CpuTemperatureMonitor::ptr CpuTemperatureMonitor::Get()
|
||||
{
|
||||
|
||||
std::vector<fs::path> cpuZones = findCpuThermalZones();
|
||||
|
||||
return std::make_unique<CpuTemperatureMonitorImpl>(std::move(cpuZones));
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) 2024 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
@@ -16,45 +16,28 @@
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import React from 'react';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
#pragma once
|
||||
|
||||
|
||||
import { WithStyles } from '@mui/styles';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
#include <filesystem>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
});
|
||||
class CpuTemperatureMonitor {
|
||||
protected:
|
||||
CpuTemperatureMonitor() { }
|
||||
public:
|
||||
virtual ~CpuTemperatureMonitor() {};
|
||||
|
||||
static constexpr float INVALID_TEMPERATURE = -400;
|
||||
using ptr = std::unique_ptr<CpuTemperatureMonitor>;
|
||||
|
||||
interface XxxProps extends WithStyles<typeof styles> {
|
||||
theme: Theme,
|
||||
}
|
||||
static ptr Get(); // may return empty pointer.
|
||||
|
||||
interface XxxState {
|
||||
|
||||
}
|
||||
|
||||
const Xxx = withStyles(styles, { withTheme: true })(
|
||||
|
||||
class extends React.Component<XxxProps, XxxState> {
|
||||
constructor(props: XxxProps)
|
||||
{
|
||||
super(props);
|
||||
this.state = {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div/>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default Xxx;
|
||||
// degrees Celcius * 1000;
|
||||
virtual float GetTemperatureC() = 0;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef FILEBROWSERFILES_H
|
||||
#define FILEBROWSERFILES_H
|
||||
|
||||
#define LV2_FILEBROWSER_URI "http://two-play.com/ns/ext/fileBrowser"
|
||||
#define LV2_FILEBROWSER_PREFIX LV2_FILEBROWSER_URI "#"
|
||||
#define LV2_FILEBROWSER__files LV2_FILEBROWSER_PREFIX "files" ///< http://two-play.com/ns/ext/fileBrowser#files >
|
||||
|
||||
/**
|
||||
@defgroup filedialog LV2_FileBrowser_Files
|
||||
@ingroup lv2
|
||||
|
||||
A feature for LV2 plugins to provide sample files (or other selectable files) within the plugin's resource bundle to host file selection dialogs.
|
||||
|
||||
Browser directories may contain actual files that typically have been uploaded by users; but may also contain symlinks to files
|
||||
in other locations. This feature allows plugins to publish files within the plugin's resource bundle to browser directories. For
|
||||
each published file consists of a symlink in the browser directory that links to a corresponding file in the plugins resource bundle.
|
||||
|
||||
The LV2_FileBrowser_Files.publish_resource_files() method allows plugins to publish entire directories. publish_resource_files()
|
||||
implements a versioning system, which tracks whether sample files have been deleted by the user. If the symlink for sample files that were installed
|
||||
by a previous version of the plugin have been deleted, the sample files from the current version are not reinstalled. See LV2_FileBrowser_Files.publish_resource_files.
|
||||
not re-installed.
|
||||
|
||||
See: <http://rerdavies.github.io/pipedal/TBD XXX.html> for more details.
|
||||
|
||||
@{
|
||||
*/
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
LV2_FileBrowser_Status_Success = 0,
|
||||
LV2_FileBrowser_Status_Err_Filesystem = 1,
|
||||
LV2_FileBrowser_Status_Err_InvalidParameter = 1,
|
||||
} LV2_FileBrowser_Status;
|
||||
|
||||
typedef void*
|
||||
LV2_FileBrowser_Files_Handle; ///< Opaque handle for pipedal:uploadPath feature
|
||||
|
||||
|
||||
/**
|
||||
Feature data for filedialog:files (@ref LV2_FILEBROWSER_file).
|
||||
*/
|
||||
typedef struct {
|
||||
/**
|
||||
Opaque host data.
|
||||
*/
|
||||
LV2_FileBrowser_Files_Handle handle;
|
||||
|
||||
LV2_FileBrowser_Status (*publish_resource_files)(LV2_FileBrowser_Files_Handle handle,uint32_t version,const char*resourcePath, const char*uploadDirectory);
|
||||
|
||||
/**
|
||||
Return a path in which upload files will be stored for this plugin.
|
||||
@param handle MUST be the `handle` member of this struct.
|
||||
@param path The sub-directory name in which uploaded files will be stored.
|
||||
@return The absolute path to use for the new directory.
|
||||
|
||||
This function can be used by plugins to get the path of files or directories
|
||||
that can be browsed by file dialogs. get_filebrowser_path, unlike map_path() does
|
||||
not create or modify files in any way.
|
||||
|
||||
The caller must free memory for the returned value with LV2_FileBrowser_Files.free_path().
|
||||
|
||||
Example:
|
||||
[code]
|
||||
TBD
|
||||
[/code]
|
||||
*/
|
||||
char* (*get_upload_path)(LV2_FileBrowser_Files_Handle handle, const char* path);
|
||||
|
||||
/**
|
||||
Map resource bundle filenames to upload directories.
|
||||
@param handle MUST be the `handle` member of this struct.
|
||||
@param path File path name
|
||||
@param fileBrowserDirectory relative directory of the file browser directory in which to create a link.
|
||||
@return The absolute pathname of a file in the resource directory.
|
||||
|
||||
The primary purpose of this method is to map file references to files in the plugin's bundle
|
||||
directory to corresponding files in the a file browser directory.
|
||||
|
||||
If the path is relative, the full path is resolved using the `rootResourceDirectory` path.
|
||||
|
||||
If the full path is not a child of rootResourceDirectory or if the path is relative,
|
||||
get_upload_path returns the supplied path unmodified.
|
||||
`
|
||||
If the filename is a child of rootResourceDirectory, creates a corresponding link to the file in the file browser
|
||||
directory and returns the path of that link.
|
||||
|
||||
The caller must free memory for the returned value with `LV2_FileBrowser_Files.free_path()`.
|
||||
|
||||
Example:
|
||||
*/
|
||||
char* (*map_path)(LV2_FileBrowser_Files_Handle handle, const char* path, const char*rootResourceDirectory, const char*fileBrowserDirectory);
|
||||
|
||||
|
||||
/**
|
||||
Free a path returned by LV2_FileBrowser_Files.get_upload_path() or LV2_FileBrowser_Files.map_path()
|
||||
@param handle MUST be the `handle` member of this struct.
|
||||
@param path A path previously returned by a method of `LV2_FileBrowser_Files`.
|
||||
|
||||
*/
|
||||
void (*free_path)(LV2_FileBrowser_Files_Handle handle, char* path);
|
||||
} LV2_FileBrowser_Files;
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // FILEBROWSERFILES_H
|
||||
@@ -30,9 +30,11 @@
|
||||
#include <stdexcept>
|
||||
#include "util.hpp"
|
||||
#include "ofstream_synced.hpp"
|
||||
#include "ModFileTypes.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
using namespace pipedal::implementation;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
||||
namespace pipedal::implementation {
|
||||
@@ -128,7 +130,9 @@ void FileBrowserFilesFeature::Initialize(
|
||||
lv2_log_logger_init(&this->lv2Logger,const_cast<LV2_URID_Map *>(lv2Map),const_cast<LV2_Log_Log *>(lv2Log));
|
||||
|
||||
this->bundleDirectory = bundleDirectory;
|
||||
this->browserDirectory = browserDirectory;
|
||||
this->browserRootDirectory = browserDirectory;
|
||||
this->privateDirectory = browserDirectory / this->bundleDirectory.filename();
|
||||
MakeDirectoryMap(browserDirectory);
|
||||
|
||||
|
||||
featureData.handle = (void *)this;
|
||||
@@ -155,9 +159,73 @@ LV2_FileBrowser_Status FileBrowserFilesFeature::FN_publish_resource_files(LV2_Fi
|
||||
return ((FileBrowserFilesFeature *)handle)->PublishResourceFiles(version, resourcePath, uploadDirectory);
|
||||
}
|
||||
|
||||
|
||||
const char*FileBrowserFilesFeature::GetWellKnownDirectory(const std::string&directory)
|
||||
{
|
||||
if (strcmp(directory.c_str(), "~") == 0) {
|
||||
return privateDirectory.c_str();
|
||||
}
|
||||
const auto &f = g_wellKnownDirectoryMap->find(directory);
|
||||
if (f != g_wellKnownDirectoryMap->end())
|
||||
{
|
||||
return f->second.c_str();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
using WellKnownDirectoryMap = std::map<std::string,std::string>;
|
||||
std::mutex FileBrowserFilesFeature::g_DirectoryMap_mutex;
|
||||
|
||||
std::unique_ptr<WellKnownDirectoryMap> FileBrowserFilesFeature::g_wellKnownDirectoryMap;
|
||||
|
||||
void FileBrowserFilesFeature::MakeDirectoryMap(const std::filesystem::path&rootBrowserDirectory)
|
||||
{
|
||||
std::lock_guard lock { g_DirectoryMap_mutex};
|
||||
|
||||
if (!g_wellKnownDirectoryMap)
|
||||
{
|
||||
g_wellKnownDirectoryMap = std::make_unique<WellKnownDirectoryMap>();
|
||||
for (const auto&modDirectory: ModFileTypes::ModDirectories())
|
||||
{
|
||||
(*g_wellKnownDirectoryMap)[modDirectory.modType] =
|
||||
(rootBrowserDirectory / modDirectory.pipedalPath).string();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
char *FileBrowserFilesFeature::GetUploadPath(const char *fileBrowserPath)
|
||||
{
|
||||
std::filesystem::path result = this->browserDirectory / fileBrowserPath;
|
||||
if (fileBrowserPath == nullptr | fileBrowserPath[0] == '\0')
|
||||
{
|
||||
return strdup("");
|
||||
}
|
||||
std::filesystem::path path {fileBrowserPath};
|
||||
|
||||
auto iter = path.begin();
|
||||
if (iter == path.end())
|
||||
{
|
||||
return strdup("");
|
||||
}
|
||||
std::string firstSegment = iter->string();
|
||||
|
||||
const auto f = (g_wellKnownDirectoryMap)->find(firstSegment);
|
||||
if (f == g_wellKnownDirectoryMap->end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
fs::path result = f->second;
|
||||
|
||||
++iter;
|
||||
while (iter != path.end())
|
||||
{
|
||||
result /= (*iter);
|
||||
++iter;
|
||||
}
|
||||
return strdup(result.c_str());
|
||||
}
|
||||
void FileBrowserFilesFeature::FreePath(char *path)
|
||||
@@ -224,7 +292,7 @@ char *FileBrowserFilesFeature::MapPath(const char *path,const char*resourcePathB
|
||||
} else {
|
||||
targetDirectory = fileBrowserPath;
|
||||
}
|
||||
targetDirectory = this->browserDirectory / targetDirectory;
|
||||
targetDirectory = this->browserRootDirectory / targetDirectory;
|
||||
std::filesystem::create_directories(targetDirectory);
|
||||
|
||||
std::filesystem::path targetPath = targetDirectory / relativePath;
|
||||
@@ -297,7 +365,7 @@ LV2_FileBrowser_Status FileBrowserFilesFeature::PublishResourceFiles(uint32_t ve
|
||||
static std::string VERSION_FILENAME_PREFIX = ".versionInfo.";
|
||||
|
||||
// generate a filename that will allow multiple plugins to install files into the same directory.
|
||||
std::filesystem::path versionFileName = this->browserDirectory / uploadDirectory /
|
||||
std::filesystem::path versionFileName = this->browserRootDirectory / uploadDirectory /
|
||||
(VERSION_FILENAME_PREFIX + GetPluginTag(this->bundleDirectory));
|
||||
|
||||
bool loaded = versionInfo.Load(versionFileName);
|
||||
@@ -325,8 +393,8 @@ LV2_FileBrowser_Status FileBrowserFilesFeature::PublishResourceFiles(uint32_t ve
|
||||
LogError("uploadDirectory must be relative.");
|
||||
return LV2_FileBrowser_Status::LV2_FileBrowser_Status_Err_InvalidParameter;
|
||||
}
|
||||
targetDirectory = this->browserDirectory / targetDirectory;
|
||||
PublishRecursive(versionInfo, sourcePath,sourcePath, this->browserDirectory / uploadDirectory);
|
||||
targetDirectory = this->browserRootDirectory / targetDirectory;
|
||||
PublishRecursive(versionInfo, sourcePath,sourcePath, this->browserRootDirectory / uploadDirectory);
|
||||
versionInfo.Save(versionFileName);
|
||||
} catch (const std::exception & e)
|
||||
{
|
||||
|
||||
@@ -24,10 +24,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FileBrowserFiles.h"
|
||||
#include "lv2ext/pipedal.lv2/ext/fileBrowser.h"
|
||||
#include <filesystem>
|
||||
#include <lv2/core/lv2.h>
|
||||
#include <lv2/log/logger.h>
|
||||
#include <mutex>
|
||||
#include <filesystem>
|
||||
|
||||
|
||||
|
||||
@@ -47,20 +49,32 @@ namespace pipedal {
|
||||
const std::string&browserDirectory);
|
||||
const LV2_Feature*GetFeature() { return &feature; }
|
||||
private:
|
||||
static void MakeDirectoryMap(const std::filesystem::path&rootBrowserDirectory);
|
||||
|
||||
using WellKnownDirectoryMap = std::map<std::string,std::string>;
|
||||
static std::mutex g_DirectoryMap_mutex;
|
||||
static std::unique_ptr<WellKnownDirectoryMap> g_wellKnownDirectoryMap;
|
||||
|
||||
|
||||
LV2_Feature feature;
|
||||
const LV2_URID_Map *lv2Map = nullptr;
|
||||
LV2_Log_Logger lv2Logger = {nullptr,0,0,0,0};
|
||||
|
||||
std::filesystem::path bundleDirectory;
|
||||
std::filesystem::path browserDirectory;
|
||||
std::filesystem::path browserRootDirectory;
|
||||
std::filesystem::path privateDirectory;
|
||||
LV2_FileBrowser_Files featureData;
|
||||
|
||||
std::map<std::string,std::string> wellKnownPaths;
|
||||
|
||||
|
||||
static char* FN_get_upload_path(LV2_FileBrowser_Files_Handle handle, const char* fileBrowserDirectory);
|
||||
static char* FN_map_path(LV2_FileBrowser_Files_Handle handle, const char* path, const char *resourcePathBase,const char*fileBrowserDirectory);
|
||||
static void FN_free_path(LV2_FileBrowser_Files_Handle handle, char* path);
|
||||
static LV2_FileBrowser_Status FN_publish_resource_files(LV2_FileBrowser_Files_Handle handle,uint32_t version,const char*resourcePath, const char*uploadDirectory);
|
||||
|
||||
const char*GetWellKnownDirectory(const std::string&directory);
|
||||
|
||||
void LogError(const char*message);
|
||||
char* GetUploadPath(const char* fileBrowserDirectory);
|
||||
char* MapPath(const char* path,const char*resourcePathBase,const char* fileBrowserDirectory);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "FilePropertyDirectoryTree.hpp"
|
||||
#include <filesystem>
|
||||
|
||||
using namespace pipedal;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -372,7 +372,7 @@ void HotspotManagerImpl::onStartMonitoring()
|
||||
ethernetDevice = GetDevice(NM_DEVICE_TYPE_ETHERNET);
|
||||
if (!ethernetDevice)
|
||||
{
|
||||
throw std::runtime_error("eth0 device not found.");
|
||||
throw std::runtime_error("ethernet device not found.");
|
||||
}
|
||||
this->ethernetDevice->OnStateChanged.add(
|
||||
[this](uint32_t, uint32_t, uint32_t)
|
||||
@@ -383,7 +383,7 @@ void HotspotManagerImpl::onStartMonitoring()
|
||||
wlanDevice = GetDevice(NM_DEVICE_TYPE_WIFI);
|
||||
if (!wlanDevice)
|
||||
{
|
||||
throw std::runtime_error("wlan0 device not found.");
|
||||
throw std::runtime_error("Wi-Fi device not found.");
|
||||
}
|
||||
wlanWirelessDevice = DeviceWireless::Create(dbusDispatcher, wlanDevice->getObjectPath());
|
||||
|
||||
|
||||
+6
-2
@@ -49,8 +49,11 @@ namespace pipedal {
|
||||
virtual void SetBypass(bool enable) = 0;
|
||||
virtual float GetOutputControlValue(int controlIndex) const = 0;
|
||||
|
||||
virtual int GetNumberOfInputAudioPorts() const = 0;
|
||||
virtual int GetNumberOfOutputAudioPorts() const = 0;
|
||||
virtual int GetNumberOfInputAudioPorts() const = 0; // as declared
|
||||
virtual int GetNumberOfOutputAudioPorts() const = 0; // as declared.
|
||||
|
||||
virtual int GetNumberOfInputAudioBuffers() const = 0; // may be different if plugin has zero inputs.
|
||||
virtual int GetNumberOfOutputAudioBuffers() const = 0; // may be different if plugin has zero inputs.
|
||||
virtual float *GetAudioInputBuffer(int index) const = 0;
|
||||
virtual float *GetAudioOutputBuffer(int index) const = 0;
|
||||
virtual void ResetAtomBuffers() = 0;
|
||||
@@ -58,6 +61,7 @@ namespace pipedal {
|
||||
virtual bool GetRequestStateChangedNotification() const = 0;
|
||||
virtual void SetRequestStateChangedNotification(bool value) = 0;
|
||||
|
||||
virtual void PrepareNoInputEffect(int numberOfInputs, size_t maxBufferSize) = 0;
|
||||
|
||||
virtual void SetAudioInputBuffer(int index, float *buffer) = 0;
|
||||
virtual void SetAudioOutputBuffer(int index, float*buffer) = 0;
|
||||
|
||||
+162
-51
@@ -30,10 +30,10 @@
|
||||
#include <regex>
|
||||
#include "ss.hpp"
|
||||
#include "Finally.hpp"
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
|
||||
static bool IsIpv4MappedAddress(const struct in6_addr &inetAddr6)
|
||||
{
|
||||
return IN6_IS_ADDR_V4MAPPED(&inetAddr6) != 0;
|
||||
@@ -62,6 +62,71 @@ static bool ipv6NetmaskCompare(const struct in6_addr &left, const struct in6_add
|
||||
return true;
|
||||
}
|
||||
|
||||
NetworkInterfaceType pipedal::GetNetworkInterfaceType(const char *interfaceName)
|
||||
{
|
||||
// stock linux.
|
||||
if (strncmp(interfaceName, "eth", 3) == 0)
|
||||
{
|
||||
return NetworkInterfaceType::Ethernet;
|
||||
}
|
||||
if (strncmp(interfaceName, "wlan", 4) == 0)
|
||||
{
|
||||
return NetworkInterfaceType::WiFi;
|
||||
}
|
||||
if (strncmp(interfaceName, "lo", 2) == 0)
|
||||
{
|
||||
return NetworkInterfaceType::Loopback;
|
||||
}
|
||||
|
||||
// ubuntu-style names.
|
||||
if (strncmp(interfaceName, "en", 2) == 0)
|
||||
{
|
||||
return NetworkInterfaceType::Ethernet;
|
||||
}
|
||||
if (strncmp(interfaceName, "wl", 2) == 0)
|
||||
{
|
||||
return NetworkInterfaceType::WiFi;
|
||||
}
|
||||
return NetworkInterfaceType::Other;
|
||||
}
|
||||
|
||||
std::optional<std::string> pipedal::GetWlanInterfaceName()
|
||||
{
|
||||
struct ifaddrs *ifap = nullptr;
|
||||
if (getifaddrs(&ifap) != 0)
|
||||
throw std::runtime_error("No networks available.");
|
||||
|
||||
Finally f{[ifap]()
|
||||
{
|
||||
freeifaddrs(ifap);
|
||||
}};
|
||||
|
||||
for (struct ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
|
||||
{
|
||||
if (GetNetworkInterfaceType(p->ifa_name) == NetworkInterfaceType::WiFi)
|
||||
{
|
||||
return std::string(p->ifa_name);
|
||||
}
|
||||
}
|
||||
|
||||
return std::optional<std::string>();
|
||||
}
|
||||
|
||||
std::optional<std::string> pipedal::GetWlanIpv4Address()
|
||||
{
|
||||
auto wlanAddress = GetWlanInterfaceName();
|
||||
if (wlanAddress)
|
||||
{
|
||||
auto result = GetInterfaceIpv4Address(*wlanAddress);
|
||||
if (result.length() != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return std::optional<std::string>();
|
||||
}
|
||||
|
||||
|
||||
static bool IsIpv4OnLocalSubnet(uint32_t ipv4Addres)
|
||||
{
|
||||
bool result = false;
|
||||
@@ -71,7 +136,7 @@ static bool IsIpv4OnLocalSubnet(uint32_t ipv4Addres)
|
||||
|
||||
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)
|
||||
if (p->ifa_addr && 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);
|
||||
@@ -245,7 +310,7 @@ bool pipedal::IsOnLocalSubnet(const std::string &fromAddress)
|
||||
|
||||
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
|
||||
{
|
||||
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
if (p->ifa_addr && p->ifa_addr && p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
{
|
||||
|
||||
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
|
||||
@@ -267,6 +332,44 @@ bool pipedal::IsOnLocalSubnet(const std::string &fromAddress)
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool isEthernetAddress(const char *ifName)
|
||||
{
|
||||
// either ethN (classic),
|
||||
if (strncmp(ifName, "eth", 3) == 0)
|
||||
return true;
|
||||
// or "enpNNsNN" (ubuntu)
|
||||
return (ifName[0] == 'e' && ifName[1] == 'n');
|
||||
}
|
||||
|
||||
std::vector<std::string> pipedal::GetEthernetIpv4Addresses()
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
|
||||
struct ifaddrs *ifap = nullptr;
|
||||
if (getifaddrs(&ifap) != 0)
|
||||
return result;
|
||||
|
||||
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
|
||||
{
|
||||
if (isEthernetAddress(p->ifa_name))
|
||||
{
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
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 (ifAddr & 0xFF)
|
||||
{ // has an actual bound IP address.
|
||||
std::string name = SS(
|
||||
((ifAddr >> 24) & 0xFF) << '.' << ((ifAddr >> 16) & 0xFF) << '.' << ((ifAddr >> 8) & 0xFF) << '.' << ((ifAddr) & 0xFF));
|
||||
result.push_back(std::move(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
freeifaddrs(ifap);
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string GetInterfaceForIp4Address(uint32_t ipv4Address)
|
||||
{
|
||||
struct ifaddrs *ifap = nullptr;
|
||||
@@ -275,7 +378,7 @@ static std::string GetInterfaceForIp4Address(uint32_t ipv4Address)
|
||||
std::string result;
|
||||
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)
|
||||
if (p->ifa_addr && 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);
|
||||
@@ -299,7 +402,7 @@ static std::string GetInterfaceForIp6Address(const in6_addr inetAddr6)
|
||||
std::string result;
|
||||
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
|
||||
{
|
||||
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
{ // TODO: Add support for AF_INET6
|
||||
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
|
||||
struct sockaddr_in6 *pNetMask = (struct sockaddr_in6 *)(p->ifa_netmask);
|
||||
@@ -325,9 +428,10 @@ static std::string GetNonLinkLocalAddress(const boost::asio::ip::address_v6 &ipV
|
||||
if (getifaddrs(&ifap) != 0)
|
||||
throw std::runtime_error("No networks available.");
|
||||
|
||||
Finally f{[ifap]() {
|
||||
freeifaddrs(ifap);
|
||||
}};
|
||||
Finally f{[ifap]()
|
||||
{
|
||||
freeifaddrs(ifap);
|
||||
}};
|
||||
|
||||
std::string result;
|
||||
|
||||
@@ -337,7 +441,7 @@ static std::string GetNonLinkLocalAddress(const boost::asio::ip::address_v6 &ipV
|
||||
// get the nameof the interface for this scopeId.
|
||||
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
|
||||
{
|
||||
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr)
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr)
|
||||
{ // TODO: Add support for AF_INET6
|
||||
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
|
||||
if (pAddr->sin6_scope_id == scopeId)
|
||||
@@ -355,9 +459,9 @@ static std::string GetNonLinkLocalAddress(const boost::asio::ip::address_v6 &ipV
|
||||
// an IPV4 address would be the ideal result.
|
||||
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)
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
{
|
||||
if (strcmp(p->ifa_name,targetInterfaceName) == 0)
|
||||
if (strcmp(p->ifa_name, targetInterfaceName) == 0)
|
||||
{
|
||||
constexpr int BUFSIZE = 128;
|
||||
char host[BUFSIZE];
|
||||
@@ -381,14 +485,13 @@ static std::string GetNonLinkLocalAddress(const boost::asio::ip::address_v6 &ipV
|
||||
{
|
||||
char buffer[128];
|
||||
inet_ntop(AF_INET6, &(pAddr->sin6_addr),
|
||||
buffer,sizeof(buffer));
|
||||
buffer, sizeof(buffer));
|
||||
return SS('[' << buffer << ']');
|
||||
}
|
||||
}
|
||||
}
|
||||
// probably network address settling after the hotspot comes up. :-/
|
||||
throw std::runtime_error("Not ready. Try again later.");
|
||||
|
||||
}
|
||||
|
||||
static std::string GetNonLinkLocalAddressForInterface(const std::string &name)
|
||||
@@ -400,7 +503,7 @@ static std::string GetNonLinkLocalAddressForInterface(const std::string &name)
|
||||
|
||||
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
|
||||
{
|
||||
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
{ // TODO: Add support for AF_INET6
|
||||
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
|
||||
if (!IN6_IS_ADDR_LINKLOCAL(&(pAddr->sin6_addr)))
|
||||
@@ -444,8 +547,8 @@ static std::string GetNonLinkLocalAddressForIp4Interface(const std::string &name
|
||||
std::string result = "";
|
||||
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
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
{
|
||||
struct sockaddr_in *pAddr = (struct sockaddr_in *)(p->ifa_addr);
|
||||
{
|
||||
if (name == p->ifa_name)
|
||||
@@ -543,7 +646,6 @@ static std::string GetNonLinkLocalAddressForIp4Interface(const std::string &name
|
||||
// else
|
||||
// {
|
||||
|
||||
|
||||
// std::string interfaceName = GetInterfaceForIp6Address(inetAddr6);
|
||||
// result = GetNonLinkLocalAddressForInterface(interfaceName);
|
||||
// }
|
||||
@@ -556,7 +658,6 @@ static std::string GetNonLinkLocalAddressForIp4Interface(const std::string &name
|
||||
// return result;
|
||||
// }
|
||||
|
||||
|
||||
std::string pipedal::GetNonLinkLocalAddress(const std::string &fromAddress)
|
||||
{
|
||||
std::string address = fromAddress;
|
||||
@@ -572,12 +673,11 @@ std::string pipedal::GetNonLinkLocalAddress(const std::string &fromAddress)
|
||||
throw std::invalid_argument("Bad address.");
|
||||
address = address.substr(1, address.length() - 2);
|
||||
|
||||
auto nPos = address.find('%') ;
|
||||
auto nPos = address.find('%');
|
||||
if (nPos != std::string::npos)
|
||||
{
|
||||
std::string ifName = address.substr(nPos+1);
|
||||
std::string ifName = address.substr(nPos + 1);
|
||||
return GetNonLinkLocalAddressForInterface(ifName);
|
||||
|
||||
}
|
||||
struct in6_addr inetAddr6;
|
||||
memset(&inetAddr6, 0, sizeof(inetAddr6));
|
||||
@@ -599,9 +699,8 @@ std::string pipedal::GetNonLinkLocalAddress(const std::string &fromAddress)
|
||||
else
|
||||
{
|
||||
|
||||
|
||||
std::string interfaceName = GetInterfaceForIp6Address(inetAddr6);
|
||||
result = GetNonLinkLocalAddressForInterface(interfaceName);
|
||||
result = GetNonLinkLocalAddressForInterface(interfaceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -612,12 +711,11 @@ std::string pipedal::GetNonLinkLocalAddress(const std::string &fromAddress)
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string pipedal::GetInterfaceIpv4Address(const std::string& interfaceName)
|
||||
std::string pipedal::GetInterfaceIpv4Address(const std::string &interfaceName)
|
||||
{
|
||||
return GetNonLinkLocalAddressForIp4Interface(interfaceName);
|
||||
}
|
||||
|
||||
|
||||
// std::string getNonLinkLocalAddress(const std::string&address, const std::string&interface)
|
||||
// {
|
||||
// if (!interface.empty())
|
||||
@@ -628,7 +726,7 @@ std::string pipedal::GetInterfaceIpv4Address(const std::string& interfaceName)
|
||||
|
||||
// }
|
||||
|
||||
static bool parseForLinkLocalUrl(const std::string&url,std::string*prefix, std::string *suffix)
|
||||
static bool parseForLinkLocalUrl(const std::string &url, std::string *prefix, std::string *suffix)
|
||||
{
|
||||
auto iter = url.begin();
|
||||
auto end = url.end();
|
||||
@@ -636,7 +734,8 @@ static bool parseForLinkLocalUrl(const std::string&url,std::string*prefix, std::
|
||||
// proto:
|
||||
while (true)
|
||||
{
|
||||
if (iter == end) return false;
|
||||
if (iter == end)
|
||||
return false;
|
||||
if (*iter == ':')
|
||||
{
|
||||
++iter;
|
||||
@@ -646,17 +745,20 @@ static bool parseForLinkLocalUrl(const std::string&url,std::string*prefix, std::
|
||||
}
|
||||
|
||||
// //
|
||||
if (iter == end || *iter != '/') return false;
|
||||
if (iter == end || *iter != '/')
|
||||
return false;
|
||||
++iter;
|
||||
if (iter == end || *iter != '/') return false;
|
||||
if (iter == end || *iter != '/')
|
||||
return false;
|
||||
++iter;
|
||||
|
||||
|
||||
// (user@)? of id: http://user@[::1]/
|
||||
if (*iter != '[') {
|
||||
if (*iter != '[')
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (iter == end || *iter == '/') return false;
|
||||
if (iter == end || *iter == '/')
|
||||
return false;
|
||||
if (*iter == '@')
|
||||
{
|
||||
++iter;
|
||||
@@ -674,23 +776,30 @@ static bool parseForLinkLocalUrl(const std::string&url,std::string*prefix, std::
|
||||
++iter;
|
||||
|
||||
// fe80: ...
|
||||
if (iter == end || !(*iter == 'f' || *iter == 'F')) return false;
|
||||
if (iter == end || !(*iter == 'f' || *iter == 'F'))
|
||||
return false;
|
||||
++iter;
|
||||
if (iter == end || !(*iter == 'e' || *iter == 'E')) return false;
|
||||
if (iter == end || !(*iter == 'e' || *iter == 'E'))
|
||||
return false;
|
||||
++iter;
|
||||
if (iter == end || *iter != '8' ) return false;
|
||||
if (iter == end || *iter != '8')
|
||||
return false;
|
||||
++iter;
|
||||
if (iter == end || *iter != '0' ) return false;
|
||||
if (iter == end || *iter != '0')
|
||||
return false;
|
||||
++iter;
|
||||
if (iter == end || *iter != ':' ) return false;
|
||||
if (iter == end || *iter != ':')
|
||||
return false;
|
||||
++iter;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (iter == end) break;
|
||||
if (iter == end)
|
||||
break;
|
||||
char c = *iter;
|
||||
bool valid = (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c >= '0' && c <= '9' || c == ':';
|
||||
if (!valid) break;
|
||||
if (!valid)
|
||||
break;
|
||||
++iter;
|
||||
}
|
||||
// potentially a scope ID
|
||||
@@ -698,38 +807,40 @@ static bool parseForLinkLocalUrl(const std::string&url,std::string*prefix, std::
|
||||
auto endOfAddress = iter;
|
||||
if (iter != end && *iter == '%')
|
||||
{
|
||||
while (true) {
|
||||
if (iter == end) return false;
|
||||
if (*iter == ']') break;
|
||||
while (true)
|
||||
{
|
||||
if (iter == end)
|
||||
return false;
|
||||
if (*iter == ']')
|
||||
break;
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
// made it!
|
||||
if (iter == end || *iter != ']') return false;
|
||||
if (iter == end || *iter != ']')
|
||||
return false;
|
||||
++iter;
|
||||
|
||||
auto startOfRest = iter;
|
||||
|
||||
*prefix = std::string(url.begin(),endOfPrefix);
|
||||
*suffix = std::string(startOfRest,url.end());
|
||||
*prefix = std::string(url.begin(), endOfPrefix);
|
||||
*suffix = std::string(startOfRest, url.end());
|
||||
return true;
|
||||
|
||||
}
|
||||
bool pipedal::RemapLinkLocalUrl(
|
||||
const boost::asio::ip::address &boostAddress,
|
||||
const std::string&url,std::string*outputUrl)
|
||||
const std::string &url, std::string *outputUrl)
|
||||
{
|
||||
|
||||
std::string prefix, suffix;
|
||||
if (!parseForLinkLocalUrl(url,&prefix,&suffix)) return false;
|
||||
if (!parseForLinkLocalUrl(url, &prefix, &suffix))
|
||||
return false;
|
||||
|
||||
boost::asio::ip::address_v6 addressV6 = boostAddress.to_v6();
|
||||
|
||||
std::string directAddress;
|
||||
directAddress = ::GetNonLinkLocalAddress(addressV6);
|
||||
|
||||
*outputUrl = SS(prefix << directAddress << suffix);
|
||||
*outputUrl = SS(prefix << directAddress << suffix);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user