Merge pull request #199 from rerdavies/BufferSizeExperiment

PiPedal v1.2.51 Release
This commit is contained in:
Robin Davies
2024-09-21 22:55:33 +01:00
committed by GitHub
132 changed files with 9983 additions and 1654 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
build/
#VS Code config files.
.vscode/
# .vscode/ Sort this out later. .gitignore-ing an already tracked file doesn't actually work.
#settings
Testing/
+1 -1
View File
@@ -3,5 +3,5 @@
url = https://github.com/rerdavies/pipedal_p2pd
[submodule "modules/websocketpp"]
path = modules/websocketpp
url = https://github.com/zaphoyd/websocketpp.git
url = https://github.com/rerdavies/websocketpp.git
branch = develop
+65 -3
View File
@@ -11,6 +11,33 @@
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Edge",
"port": 9222,
"request": "attach",
"type": "msedge",
"webRoot": "${workspaceFolder}"
},
{
"name": "(gdb) Attach",
"type": "cppdbg",
"request": "attach",
"program": "enter program name, for example ${workspaceFolder}/a.out",
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
},
@@ -40,6 +67,32 @@
}
]
},
{
"name": "(gdb) makeRelease",
"type": "cppdbg",
"request": "launch",
// Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}",
"args": [ ],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/../..",
"environment": [
{
"name": "PATH",
"value": "$PATH:${command:cmake.launchTargetDirectory}"
}
],
"externalConsole": true,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "(gdb) pipedal_nm_p2pd",
"type": "cppdbg",
@@ -96,7 +149,13 @@
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Add alsa source directories.",
"text": "directory $cd:$cwd:/usr/src/alsa-lib-1.2.8",
"ignoreFailures": true
}
]
},
@@ -179,7 +238,7 @@
// Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}",
"args": [ "Nam_Profile", "--no-profile","-w" ],
"args": [ "ToobNam_Profile", "--no-profile","-w" ],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
@@ -289,10 +348,13 @@
// Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}",
"args": [
"--prefix",
"/usr/sbin",
"--nosudo", // run without sudo (which will fail because of perms, but allow us to debug deeper into install code.)
"--prefix", "/usr",
//"--get-current-port"
"--install"
//"--get-current-port"
//"--install"
//"--enable-p2p" , "CA", "PiPedalTest","12345678","14"
//"--list-p2p-channels"
],
+2 -1
View File
@@ -96,7 +96,8 @@
"nm-device-wifi-p2p.h": "c",
"strstream": "cpp",
"p2p_i.h": "c",
"p2p.h": "c"
"p2p.h": "c",
"hash_set": "cpp"
},
"cSpell.words": [
"Guitarix",
+22 -14
View File
@@ -1,17 +1,17 @@
cmake_minimum_required(VERSION 3.16.0)
project(pipedal
VERSION 1.2.47
VERSION 1.2.51
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
)
set (DISPLAY_VERSION "PiPedal v1.2.47-Release")
set (DISPLAY_VERSION "PiPedal v1.2.51-Release")
set (PACKAGE_ARCHITECTURE "arm64")
set (CMAKE_INSTALL_PREFIX "/usr/")
include(CTest)
enable_testing()
add_subdirectory("submodules/pipedal_p2pd")
#add_subdirectory("submodules/pipedal_p2pd")
add_subdirectory("PiPedalCommon")
@@ -19,14 +19,14 @@ add_subdirectory("react")
add_subdirectory("src")
add_subdirectory("NetworkManagerP2P")
# Replaced with hotspot.
#add_subdirectory("NetworkManagerP2P")
# install (TARGETS pipedal_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
# )
install (TARGETS pipedal_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
)
install (TARGETS pipedal_nm_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
)
# install (TARGETS pipedal_nm_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
# )
# select LV2 source directory for the current build architecture
@@ -50,6 +50,13 @@ install(
install(
FILES ${PROJECT_SOURCE_DIR}/debian/copyright DESTINATION /usr/share/doc/pipedal
)
#install (
# DIRECTORY ${PROJECT_SOURCE_DIR}/src/polkit-1/rules/10-pipedal-networkmanager.rule DESTINATION /var/lib/polkit-1/localauthority/
#)
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
)
@@ -75,8 +82,9 @@ install(CODE
EXECUTABLES
${CMAKE_INSTALL_PREFIX}/sbin/pipedaladmind
${CMAKE_INSTALL_PREFIX}/sbin/pipedald
${CMAKE_INSTALL_PREFIX}/sbin/pipedal_nm_p2pd
${CMAKE_INSTALL_PREFIX}/sbin/pipedal_p2pd
${CMAKE_INSTALL_PREFIX}/sbin/pipedal_update
#${CMAKE_INSTALL_PREFIX}/sbin/pipedal_nm_p2pd
#${CMAKE_INSTALL_PREFIX}/sbin/pipedal_p2pd
${CMAKE_INSTALL_PREFIX}/bin/pipedalconfig
)
]]
@@ -84,7 +92,7 @@ install(CODE
set(CPACK_GENERATOR "DEB")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Robin Davies<rerdavies@gmail.com>") # required
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Robin E. R. Davies<rerdavies@gmail.com>") # required
set(CPACK_PACKAGE_VENDOR "Robin 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")
@@ -92,13 +100,13 @@ set(CPACK_DEBIAN_PACKAGE_SECTION sound)
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE)
#set(CPACK_DEBIAN_PACKAGE_DEPENDS "jackd2, hostapd, dhcpcd, dnsmasq" )
set(CPACK_DEBIAN_PACKAGE_DEPENDS "lv2-dev, hostapd, dhcpcd,dnsmasq, authbind, gpg" )
set(CPACK_DEBIAN_PACKAGE_DEPENDS "lv2-dev, authbind, gpg" )
#set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "arm64")
set(CPACK_PACKAGING_INSTALL_PREFIX /usr)
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
set (CPACK_STRIP_FILES ON)
set (CPACK_STRIP_FILES true)
set(CPACK_DEBIAN_PACKAGE_SIGN_ALGORITHM "detached")
set(CPACK_DEBIAN_PACKAGE_SIGN_TYPE "origin")
+5
View File
@@ -215,6 +215,11 @@ void LogTrace(const std::string&path,const char*method,const std::string&message
}
}
void SetDBusLogger(std::unique_ptr<IDBusLogger> && logger)
{
loggers.clear();
loggers.push_back(std::move(logger));
}
void SetDBusConsoleLogger()
{
+1
View File
@@ -42,6 +42,7 @@ void SetDBusConsoleLogger();
void AddDBusConsoleLogger();
void SetDBusSystemdLogger();
void SetDBusFileLogger(const std::filesystem::path &path);
void SetDBusLogger(std::unique_ptr<IDBusLogger>&&logger);
extern void LogError(const std::string&path,const char*method,const std::string&message);
extern void LogInfo(const std::string&path,const char*method,const std::string&message);
+19 -13
View File
@@ -193,7 +193,7 @@ void P2pSettings::Load()
}
static void openWithPerms(
static void openWithRestrictedPerms(
pipedal::ofstream_synced &f,
const std::filesystem::path &path,
std::filesystem::perms perms =
@@ -201,19 +201,25 @@ static void openWithPerms(
std::filesystem::perms::group_read | std::filesystem::perms::group_write)
{
auto directory = path.parent_path();
std::filesystem::create_directories(directory);
// open and close to make an existing empty file.
// close it.
if (!std::filesystem::exists(directory))
{
std::ofstream f;
f.open(path);
f.close();
std::filesystem::create_directories(directory);
// open and close to make an existing empty file.
// close it.
{
std::ofstream f;
f.open(path);
f.close();
}
try {
// set the perms.
std::filesystem::permissions(
path,
perms,
std::filesystem::perm_options::replace);
} catch (const std::exception&) {
}
}
// set the perms.
std::filesystem::permissions(
path,
perms,
std::filesystem::perm_options::replace);
// open for re3al.
f.open(path);
@@ -223,7 +229,7 @@ void P2pSettings::Save()
auto filename = config_filename();
try {
pipedal::ofstream_synced f;
openWithPerms(f,filename);
openWithRestrictedPerms(f,filename);
if (!f.is_open())
{
+29 -1
View File
@@ -41,6 +41,34 @@ message(STATUS "NMPIPEDAL CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
# Use the sdbus-c++ target in SDBusCpp namespace
add_library(PiPedalCommon STATIC
include/dbus/org.freedesktop.NetworkManager.Device.Statistics.hpp
include/dbus/org.freedesktop.NetworkManager.Settings.Connection.hpp
include/dbus/org.freedesktop.NetworkManager.IP6Config.hpp
include/dbus/org.freedesktop.NetworkManager.Connection.Active.hpp
include/dbus/org.freedesktop.NetworkManager.DnsManager.hpp
include/dbus/org.freedesktop.NetworkManager.IP4Config.hpp
include/dbus/org.freedesktop.NetworkManager.Settings.hpp
include/dbus/org.freedesktop.NetworkManager.Device.hpp
include/dbus/org.freedesktop.NetworkManager.DHCP6Config.hpp
include/dbus/org.freedesktop.NetworkManager.AccessPoint.hpp
include/dbus/org.freedesktop.NetworkManager.Device.WifiP2P.hpp
include/dbus/org.freedesktop.NetworkManager.WifiP2PPeer.hpp
include/dbus/org.freedesktop.NetworkManager.hpp
include/dbus/org.freedesktop.NetworkManager.Device.Wireless.hpp
include/dbus/org.freedesktop.NetworkManager.DHCP4Config.hpp
NetworkManagerInterfaces.cpp
include/NetworkManagerInterfaces.hpp
Lv2Log.cpp include/Lv2Log.hpp
DBusEvent.cpp
DBusVariantHelper.cpp
DBusLog.cpp
DBusDispatcher.cpp
include/DBusDispatcher.hpp
include/DBusEvent.hpp
include/DBusLog.hpp
include/DBusVariantHelper.hpp
ofstream_synced.cpp include/ofstream_synced.hpp
ConfigSerializer.cpp include/ConfigSerializer.hpp
WifiRegs.cpp include/WifiRegs.hpp
@@ -66,7 +94,7 @@ add_library(PiPedalCommon STATIC
ss.hpp
)
target_include_directories(PiPedalCommon PUBLIC "include" ${LIBNL3_INCLUDE_DIRS})
target_link_libraries(PiPedalCommon PUBLIC ${LIBNL3_LIBRARIES} )
target_link_libraries(PiPedalCommon PUBLIC ${LIBNL3_LIBRARIES} SDBusCpp::sdbus-c++ )
+2 -2
View File
@@ -53,12 +53,12 @@ static std::vector<OpClassChannels> opClasses = {
* care of removing invalid channels.
*/
// WiFi 5.
// Wi-Fi 5.
// {WifiMode::IEEE80211A, 128, 36, 177, 4, WifiBandwidth::BW80, true},
// {WifiMode::IEEE80211A, 129, 36, 177, 4, WifiBandwidth::BW160, true},
// {WifiMode::IEEE80211A, 130, 36, 177, 4, WifiBandwidth::BW80P80, true},
// WiFi 6.
// Wi-Fi 6.
// {WifiMode::IEEE80211AX, 131, 1, 233, 4, WifiBandwidth::BW20, true},
// {WifiMode::IEEE80211AX, 132, 1, 233, 8, WifiBandwidth::BW40PLUS, true},
// {WifiMode::IEEE80211AX, 133, 1, 233, 16, WifiBandwidth::BW80, true},
+275
View File
@@ -0,0 +1,275 @@
#include "DBusDispatcher.hpp"
#include "DBusLog.hpp"
#include <sdbus-c++/IConnection.h>
#include <poll.h>
#include <sys/eventfd.h>
#include "ss.hpp"
#include <chrono>
#include <cassert>
#include "util.hpp"
DBusDispatcher::DBusDispatcher(bool systemBus)
{
// SignalStop requires this to be true.
if (systemBus)
{
busConnection = sdbus::createSystemBusConnection();
}
else
{
busConnection = sdbus::createSessionBusConnection();
}
}
DBusDispatcher::~DBusDispatcher()
{
Stop();
busConnection = nullptr;
}
DBusDispatcher::PostHandle DBusDispatcher::Post(DBusDispatcher::PostCallback &&fn)
{
auto nextHandle = NextHandle();
{
std::lock_guard lock{postMutex};
this->postedEvents.push_back(CallbackEntry{std::move(fn), clock::time_point::min(), nextHandle});
}
WakeThread();
return nextHandle;
}
bool DBusDispatcher::CancelPost(DBusDispatcher::PostHandle handle)
{
{
std::lock_guard lock{postMutex};
for (auto i = postedEvents.begin(); i != postedEvents.end(); ++i)
{
if (i->handle == handle)
{
postedEvents.erase(i);
return true;
}
}
return false;
}
}
DBusDispatcher::PostHandle DBusDispatcher::PostDelayed(const clock::duration &delay, PostCallback &&fn)
{
PostHandle nextHandle = NextHandle();
{
std::lock_guard lock{postMutex};
this->postedEvents.emplace_back(
CallbackEntry{
std::move(fn),
clock::now() + delay,
nextHandle});
}
WakeThread();
return nextHandle;
}
void DBusDispatcher::Run()
{
this->eventFd = eventfd(0, 0);
this->serviceThread = std::thread(
[this]()
{ this->ThreadProc(); });
}
void DBusDispatcher::WakeThread()
{
uint64_t val = 1;
if (eventFd != -1)
{
std::ignore = write(eventFd, &val, sizeof(val));
}
}
void DBusDispatcher::ThreadProc()
{
pipedal::SetThreadName("dbusd");
try
{
while (true)
{
bool idle;
bool quitting;
while (true)
{
idle = true;
quitting = false;
while (busConnection->processPendingRequest())
{
idle = false;
}
{
// process one event.
PostCallback callback;
auto now = clock::now();
{
std::lock_guard lock{postMutex};
for (auto iter = postedEvents.begin(); iter != postedEvents.end(); ++iter)
{
if (now >= iter->time)
{
callback = std::move(iter->callback);
postedEvents.erase(iter);
idle = false;
break;
}
}
quitting = this->stopping; // transfer with the mutex held.
}
if (callback)
{
callback();
}
}
if (idle)
{
break;
}
}
if (idle && signalStopRequested)
{
signalStopRequested = false;
if (!signalStopExecuted) // only ever do this once.
{
signalStopExecuted = true;
std::atomic_thread_fence(std::memory_order::acquire);
signalStopCallback();
signalStopCallback = std::function<void(void)>();
this->stopping = true;
idle = false;
}
}
if (idle)
{
if (quitting)
{
if (postedEvents.size() == 0)
{
break;
}
}
sdbus::IConnection::PollData pollData = busConnection->getEventLoopPollData();
struct pollfd pollFds[2];
pollFds[0].fd = pollData.fd;
pollFds[0].events = pollData.events;
pollFds[0].revents = 0;
pollFds[1].fd = this->eventFd;
pollFds[1].events = POLLIN;
pollFds[1].revents = 0;
int pollTimeout = pollData.getPollTimeout();
int eventTimeout = GetEventTimeoutMs();
if (eventTimeout != -1 && eventTimeout < pollTimeout)
{
pollTimeout = eventTimeout;/*+-*/
}
if (pollTimeout > 250 || pollTimeout == -1)
{
pollTimeout = 250; // poll for quit every 250 ms.
}
if (pollTimeout != 0)
{
poll(pollFds, 2, pollTimeout);
if (pollFds[1].revents & POLLIN) // received a wakup event?
{
uint64_t counter;
std::ignore = read(pollFds[1].fd, &counter, sizeof(counter)); // reset the wakeup event.
}
}
}
}
}
catch (const std::exception &e)
{
std::string msg = SS("Abnormal termination. " << e.what());
LogError("DBusDispatcher", "ThreadProc", msg);
}
isFinished = true;
LogTrace("DBusDispatcher", "ThreadProc", "Service thread terminated.");
}
void DBusDispatcher::Wait()
{
try
{
if (!threadJoined)
{
threadJoined = true;
serviceThread.join();
}
}
catch (const std::exception &)
{
}
if (eventFd != -1)
{
close(eventFd);
eventFd = -1;
}
}
bool DBusDispatcher::IsFinished()
{
return isFinished;
}
void DBusDispatcher::Stop()
{
if (!closed)
{
closed = true;
this->stopping = true;
WakeThread();
Wait();
}
}
void DBusDispatcher::SignalStop(std::function<void(void)> &&callback)
{
this->signalStopCallback = std::move(callback);
std::atomic_thread_fence(::std::memory_order::release);
this->signalStopRequested = true;
}
int DBusDispatcher::GetEventTimeoutMs()
{
std::lock_guard lock{postMutex};
clock::time_point nextTime = clock::time_point::max();
for (const auto &event : this->postedEvents)
{
if (event.time == clock::time_point::min())
return 0;
if (event.time < nextTime)
{
nextTime = event.time;
}
}
if (nextTime == clock::time_point::max())
{
return -1;
}
auto clockDuration = nextTime - clock::now();
auto durationMs = std::chrono::duration_cast<std::chrono::milliseconds>(clockDuration);
auto ticks = durationMs.count();
if (ticks > 250)
return 250;
if (ticks < 0)
return 0;
return (int)ticks;
}
bool DBusDispatcher::IsStopping()
{
std::lock_guard lock{postMutex};
return stopping;
}
+7
View File
@@ -0,0 +1,7 @@
#include "DBusEvent.hpp"
namespace impl {
std::atomic<uint64_t> NextHandle;
}
+253
View File
@@ -0,0 +1,253 @@
#include "DBusLog.hpp"
#include <memory>
#include "ss.hpp"
#include <systemd/sd-journal.h>
#include <fstream>
#include <stdexcept>
#include "ss.hpp"
#include <chrono>
#include "ofstream_synced.hpp"
namespace impl {
std::mutex logMutex;
DBusLogLevel dbusLogLevel = DBusLogLevel::Info;
}
static std::string getDateTime()
{
auto now = std::chrono::system_clock::now();
time_t in_time_t = std::chrono::system_clock::to_time_t(now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
std::stringstream ss;
ss << std::put_time(std::localtime(&in_time_t),"%Y-%m-%d %H:%M:%S");
ss << '.' << std::setfill('0') << std::setw(3) << ms.count();
ss << " ";
return ss.str();
}
class ConsoleDBusLogger : public IDBusLogger {
public:
ConsoleDBusLogger(bool showTime = true)
:showTime(showTime)
{
}
void LogError(const std::string&message) {
std::cout << getDateTime() << "error: " << message << std::endl;
}
void LogWarning(const std::string&message){
std::cout << getDateTime() << "warning: " << message << std::endl;
}
void LogInfo(const std::string&message) {
std::cout << getDateTime() << "info: " << message << std::endl;
}
void LogDebug(const std::string&message) {
std::cout << getDateTime() << "debug: " << message << std::endl;
}
void LogTrace(const std::string&message) {
std::cout << getDateTime() << " trace: " << message << std::endl;
}
private:
std::string getDateTime() {
return (showTime) ? getDateTime(): "";
}
bool showTime;
};
class FileDBusLogger : public IDBusLogger {
public:
FileDBusLogger(const std::filesystem::path&path)
{
f.open(path);
if (!f)
{
throw std::runtime_error(SS("Can't open file " << path));
}
}
void LogError(const std::string&message) {
f << "error: " << message << std::endl;
}
void LogWarning(const std::string&message){
f << "warning: " << message << std::endl;
}
void LogInfo(const std::string&message) {
f << "info: " << message << std::endl;
}
void LogDebug(const std::string&message) {
f << "debug: " << message << std::endl;
}
void LogTrace(const std::string&message) {
f << "trace: " << message << std::endl;
}
private:
pipedal::ofstream_synced f;
};
class SystemdDBusLogger : public IDBusLogger {
public:
void LogError(const std::string&message) {
sd_journal_print(LOG_ERR,"%s",message.c_str());
}
void LogWarning(const std::string&message){
sd_journal_print(LOG_WARNING,"%s",message.c_str());
}
void LogInfo(const std::string&message) {
sd_journal_print(LOG_INFO,"%s",message.c_str());
}
void LogDebug(const std::string&message) {
sd_journal_print(LOG_DEBUG,"%s",message.c_str());
}
void LogTrace(const std::string&message) {
// no trace level. just log as debug.
sd_journal_print(LOG_DEBUG,"%s",message.c_str());
}
};
static std::vector<std::unique_ptr<IDBusLogger>> loggers;
void SetDBusLogLevel(DBusLogLevel level)
{
impl::dbusLogLevel = level;
}
void LogError(const std::string&path,const char*method,const std::string&message)
{
if (impl::dbusLogLevel > DBusLogLevel::Error) return;
std::lock_guard<std::mutex> lock{impl::logMutex};
for (auto &logger: loggers)
{
logger->LogError(SS(path<< "::" << method << ": "<< message));
}
}
void LogInfo(const std::string&path,const char*method,const std::string&message)
{
if (impl::dbusLogLevel > DBusLogLevel::Info) return;
std::lock_guard<std::mutex> lock{impl::logMutex};
for (auto &logger: loggers)
{
logger->LogInfo(SS(path<< "::" << method<< ": " << message));
}
}
void LogDebug(const std::string&path,const char*method,const std::string&message)
{
if (impl::dbusLogLevel > DBusLogLevel::Debug) return;
std::lock_guard<std::mutex> lock{impl::logMutex};
for (auto &logger: loggers)
{
logger->LogDebug(SS(path<< "::" << method << ": "<< message));
}
}
void LogDebug(const std::string&path,const char*method,const std::function<std::string ()> &text)
{
if (impl::dbusLogLevel > DBusLogLevel::Debug) return;
std::lock_guard<std::mutex> lock{impl::logMutex};
for (auto &logger: loggers)
{
logger->LogDebug(SS(path<< "::" << method << ": " << text()));
}
}
void LogTrace(const std::string&path,const char*method,const std::function<std::string ()> &text)
{
if (impl::dbusLogLevel > DBusLogLevel::Trace) return;
std::lock_guard<std::mutex> lock{impl::logMutex};
for (auto &logger: loggers)
{
logger->LogTrace(SS(path<< "::" << method << ": " << text()));
}
}
void LogWarning(const std::string&path,const char*method,const std::function<std::string ()> &text)
{
if (impl::dbusLogLevel > DBusLogLevel::Warning) return;
std::lock_guard<std::mutex> lock{impl::logMutex};
std::string message = text();
for (auto &logger: loggers)
{
logger->LogWarning(SS(path<< "::" << method << ": " << message));
}
}
void LogInfo(const std::string&path,const char*method,const std::function<std::string ()> &text)
{
if (impl::dbusLogLevel > DBusLogLevel::Info) return;
std::lock_guard<std::mutex> lock{impl::logMutex};
std::string message = text();;
for (auto &logger: loggers)
{
logger->LogInfo(SS(path<< "::" << method << ": " << message));
}
}
void LogError(const std::string&path,const char*method,const std::function<std::string ()> &text)
{
if (impl::dbusLogLevel > DBusLogLevel::Error) return;
std::lock_guard<std::mutex> lock{impl::logMutex};
for (auto &logger: loggers)
{
logger->LogError(SS(path<< "::" << method << ": " << text()));
}
}
void LogWarning(const std::string&path,const char*method,const std::string&message)
{
if (impl::dbusLogLevel > DBusLogLevel::Warning) return;
std::lock_guard<std::mutex> lock{impl::logMutex};
for (auto &logger: loggers)
{
logger->LogWarning(SS(path<< "::" << method << ": " << message));
}
}
void LogTrace(const std::string&path,const char*method,const std::string&message)
{
if (impl::dbusLogLevel > DBusLogLevel::Trace) return;
std::lock_guard<std::mutex> lock{impl::logMutex};
for (auto &logger: loggers)
{
logger->LogTrace(SS(path<< "::" << method << ": " << message));
}
}
void SetDBusLogger(std::unique_ptr<IDBusLogger> && logger)
{
loggers.clear();
loggers.push_back(std::move(logger));
}
void SetDBusConsoleLogger()
{
SetDBusLogger(std::make_unique<ConsoleDBusLogger>());
}
void AddDBusConsoleLogger()
{
loggers.push_back(std::make_unique<ConsoleDBusLogger>());
}
void SetDBusSystemdLogger()
{
SetDBusLogger(std::make_unique<SystemdDBusLogger>());
}
void SetDBusFileLogger(const std::filesystem::path &path)
{
SetDBusLogger(std::make_unique<FileDBusLogger>(path));
}
+175
View File
@@ -0,0 +1,175 @@
#include "DBusVariantHelper.hpp"
#include <sdbus-c++/Types.h>
#include <iostream>
//std::ostream &operator<<(std::ostream &s, const std::map<std::string, sdbus::Variant> &properties)
std::ostream&operator<<(std::ostream&s, const sdbus::Variant&v)
{
// clang-format off
#define HANDLE_VARIANT_TYPE(type) \
if (v.containsValueOfType<type>()) \
{ \
s << v.get<type>(); \
}
HANDLE_VARIANT_TYPE(std::string)
else if (v.containsValueOfType<uint8_t>())
{
uint8_t value = v.get<uint8_t>();
s << (int)value;
}
else HANDLE_VARIANT_TYPE(uint32_t)
else HANDLE_VARIANT_TYPE(int32_t)
else HANDLE_VARIANT_TYPE(uint64_t)
else HANDLE_VARIANT_TYPE(int64_t)
else HANDLE_VARIANT_TYPE(uint16_t)
else HANDLE_VARIANT_TYPE(int16_t)
else HANDLE_VARIANT_TYPE(bool)
else HANDLE_VARIANT_TYPE(sdbus::Variant)
else if (v.containsValueOfType<sdbus::ObjectPath>())
{
auto objectPath = v.get<sdbus::ObjectPath>();
s << '[' << objectPath.c_str() << ']';
}
else if (v.containsValueOfType<std::vector<sdbus::Variant>>())
{
auto values = v.get<std::vector<sdbus::Variant>>();
{
s << '[';
bool first = true;
for (const sdbus::ObjectPath&value: values)
{
if (!first) s << ", ";
first = false;
s << value.c_str();
}
s << ']';
}
}
else if (v.containsValueOfType<std::vector<sdbus::ObjectPath>>())
{
auto paths = v.get<std::vector<sdbus::ObjectPath> >();
{
s << '[';
bool first = true;
for (const sdbus::ObjectPath&path: paths)
{
if (!first) s << ", ";
first = false;
s << path.c_str();
}
s << ']';
}
}
else if (v.containsValueOfType<std::vector<uint8_t>>())
{
std::vector<uint8_t> bytes = v.get<std::vector<uint8_t>>();
s << '[';
bool first = true;
for (uint8_t byte: bytes)
{
if (!first) s << ", ";
first = false;
s << (int)byte;
}
s << ']';
}
else if (v.containsValueOfType<std::vector<std::vector<uint8_t>>>())
{
auto arrayOfArrays = v.get<std::vector<std::vector<uint8_t>>>();
s << '[';
bool first = true;
for (const auto& array: arrayOfArrays)
{
if (!first) s << ", ";
first = false;
bool first2 = true;
for (auto byte: array)
{
if (!first2) s << ", ";
first2 = false;
s << byte;
}
}
s << ']';
}
else if (v.containsValueOfType<std::map<std::string,sdbus::Variant>>())
{
std::map<std::string,sdbus::Variant> map = v.get<std::map<std::string,sdbus::Variant>>();
s << "{";
bool first = true;
for (const auto &[key, value]: map)
{
if (!first) s << ",";
first = false;
s << '\"' << key << "\": " << value;
}
s << "}";
} else if (v.containsValueOfType<std::map<std::string,std::vector<uint8_t>>>())
{
std::map<std::string,std::vector<uint8_t>> values
= v.get<std::map<std::string,std::vector<uint8_t>>>();
s << '{';
bool first = true;
for (const auto &[key,bytes]: values)
{
if (!first) s << ',';
first = false;
s << "{\"" << key << "\": [";
bool first2 = true;
for (auto b : bytes)
{
if (!first2) s << ',';
first2 = false;
s << b;
}
s << "]";
}
s << '}';
}
// else HANDLE_VARIANT_TYPE(float)
// else HANDLE_VARIANT_TYPE(double)
// else HANDLE_VARIANT_TYPE(uint64_t)
// else HANDLE_VARIANT_TYPE(int64_t)
// else HANDLE_VARIANT_TYPE(char)
// else HANDLE_VARIANT_TYPE(uint8_t)
// else HANDLE_VARIANT_TYPE(int8_t)
else
{
s << "UNKNOWN VALUE OF TYPE " << v.peekValueType();
}
#undef HANDLE_VARIANT_TYPE
// clang-format on
return s;
}
std::ostream&operator<<(std::ostream&s,const std::map<std::string, sdbus::Variant>& properties)
{
s << "{";
bool first = true;
for (const auto &property : properties)
{
if (!first)
{
s << ", ";
}
first = false;
s << '"' << property.first << "\": ";
const sdbus::Variant &v = property.second;
s << v;
}
s << "}";
return s;
}
@@ -17,7 +17,8 @@
// 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 "pch.h"
#include <string>
#include <vector>
#include "Lv2Log.hpp"
#include <iostream>
#include <chrono>
@@ -0,0 +1,46 @@
#include "NetworkManagerInterfaces.hpp"
#include "ss.hpp"
using namespace dbus::networkmanager;
using namespace dbus::networkmanager::nm_impl;
namespace dbus::networkmanager::nm_impl
{
std::string NetworkManagerStateToString(uint32_t state)
{
// STUB: Not clear which state enum we're getting.
std::stringstream s;
s << state;
return s.str();
}
std::string NetworkManagerDeviceStateToString(uint32_t state)
{
#define DEVICE_CASE(name, value) \
case value: \
return #name;
switch (state)
{
DEVICE_CASE(NM_DEVICE_STATE_UNKNOWN, 0)
DEVICE_CASE(NM_DEVICE_STATE_UNMANAGED, 10)
DEVICE_CASE(NM_DEVICE_STATE_UNAVAILABLE, 20)
DEVICE_CASE(NM_DEVICE_STATE_DISCONNECTED, 30)
DEVICE_CASE(NM_DEVICE_STATE_PREPARE, 40)
DEVICE_CASE(NM_DEVICE_STATE_CONFIG, 50)
DEVICE_CASE(NM_DEVICE_STATE_NEED_AUTH, 60)
DEVICE_CASE(NM_DEVICE_STATE_IP_CONFIG, 70)
DEVICE_CASE(NM_DEVICE_STATE_IP_CHECK, 80)
DEVICE_CASE(NM_DEVICE_STATE_SECONDARIES, 90)
DEVICE_CASE(NM_DEVICE_STATE_ACTIVATED, 100)
DEVICE_CASE(NM_DEVICE_STATE_DEACTIVATING, 110)
DEVICE_CASE(NM_DEVICE_STATE_FAILED, 120)
default:
{
std::stringstream s;
s << state;
return s.str();
}
}
}
}
+696 -57
View File
@@ -21,30 +21,439 @@
#include <stdexcept>
#include "ss.hpp"
#include "ChannelInfo.hpp"
#include <filesystem>
#include "ofstream_synced.hpp"
#include <stdexcept>
#include <cctype>
#include "SysExec.hpp"
#include "Lv2Log.hpp"
#include <sdbus-c++/sdbus-c++.h>
#include <iostream>
#include <memory>
#include "util.hpp"
using namespace pipedal;
using namespace std;
namespace fs = std::filesystem;
JSON_MAP_BEGIN(WifiConfigSettings)
JSON_MAP_REFERENCE(WifiConfigSettings,valid)
JSON_MAP_REFERENCE(WifiConfigSettings,wifiWarningGiven)
JSON_MAP_REFERENCE(WifiConfigSettings,rebootRequired)
JSON_MAP_REFERENCE(WifiConfigSettings,enable)
JSON_MAP_REFERENCE(WifiConfigSettings,hotspotName)
JSON_MAP_REFERENCE(WifiConfigSettings,mdnsName)
JSON_MAP_REFERENCE(WifiConfigSettings,hasPassword)
JSON_MAP_REFERENCE(WifiConfigSettings,password)
JSON_MAP_REFERENCE(WifiConfigSettings,countryCode)
JSON_MAP_REFERENCE(WifiConfigSettings,channel)
JSON_MAP_END()
static const fs::path CONFIG_PATH = "/var/pipedal/config/wifiConfig.json";
bool WifiConfigSettings::ValidateCountryCode(const std::string&value)
WifiConfigSettings::WifiConfigSettings()
{
if (value.size() < 2 || value.size() > 3) return false;
return true;
Load();
}
int32_t pipedal::ChannelToChannelNumber(const std::string&channel)
static std::string getWifiCountryCode()
{
try
{
SysExecOutput result = sysExecForOutput("iw", "reg get");
if (result.exitCode != EXIT_SUCCESS)
{
return "";
}
const std::string &output = result.output;
size_t pos = output.find("country");
if (pos != std::string::npos)
{
std::string result = output.substr(pos + 8, 2);
if (WifiConfigSettings::ValidateCountryCode(result))
{
return result;
}
}
}
catch (const std::exception &e)
{
return ""; // unset.
}
return "";
}
static void setWifiCountryCode(const std::string &countryCode)
{
if (!WifiConfigSettings::ValidateCountryCode(countryCode))
{
throw std::runtime_error("Invalid country code.");
}
SysExecOutput result = sysExecForOutput("iw", SS("reg set " << countryCode));
if (result.exitCode != EXIT_SUCCESS)
{
throw std::runtime_error(SS("Failed to set country code: " << result.output));
}
}
bool WifiConfigSettings::ValidateCountryCode(const std::string &text)
{
if (text.length() != 2)
return false;
return std::isalpha(text[0]) && std::isalpha(text[1]);
}
void WifiConfigSettings::Load()
{
try
{
if (fs::exists(CONFIG_PATH))
{
std::ifstream f(CONFIG_PATH);
if (!f)
{
throw std::runtime_error("Can't open file.");
}
json_reader reader(f);
reader.read(this);
}
this->countryCode_ = getWifiCountryCode();
this->enable_ = this->autoStartMode_ != (uint16_t)HotspotAutoStartMode::Never;
this->mdnsName_ = GetHostName();
}
catch (const std::exception &e)
{
throw std::runtime_error(SS("Can't load WifiConfigSettings. " << e.what()));
}
}
static void openWithPerms(
pipedal::ofstream_synced &f,
const std::filesystem::path &path,
std::filesystem::perms perms =
std::filesystem::perms::owner_read | std::filesystem::perms::owner_write |
std::filesystem::perms::group_read | std::filesystem::perms::group_write)
{
auto directory = path.parent_path();
if (!std::filesystem::exists(directory))
{
std::filesystem::create_directories(directory);
// open and close to make an existing empty file.
// close it.
{
std::ofstream f;
f.open(path);
f.close();
}
try {
// set the perms.
std::filesystem::permissions(
path,
perms,
std::filesystem::perm_options::replace);
} catch (const std::exception&) {
Lv2Log::warning(SS("Failed to set permissions on" << path << "."));
}
}
// open for re3al.
f.open(path);
}
void WifiConfigSettings::Save()
{
WifiConfigSettings newSettings {*this};
// sync legacy settings, just in case i don't know what.
newSettings.mdnsName_ = newSettings.hotspotName_;
newSettings.enable_ = newSettings.IsEnabled();
// fill in the password, if required.
if (!newSettings.hasPassword_)
{
WifiConfigSettings oldSettings;
oldSettings.Load();
newSettings.hasPassword_ = oldSettings.hasPassword_;
newSettings.password_ = oldSettings.password_;
}
newSettings.hasSavedPassword_ = newSettings.hasPassword_;
try
{
ofstream_synced f;
openWithPerms(f,CONFIG_PATH);
json_writer writer(f);
writer.write(&newSettings);
}
catch (const std::exception &e)
{
throw std::runtime_error(SS("Can't save WifiConfigSettings. " << e.what()));
}
}
namespace pipedal::priv
{
class Device
{
public:
Device(const std::string &path)
: m_proxy(sdbus::createProxy(
sdbus::createSystemBusConnection(),
"org.freedesktop.NetworkManager",
path))
{
}
uint32_t GetDeviceType() const
{
sdbus::Variant device_type_variant;
m_proxy->callMethod("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments("org.freedesktop.NetworkManager.Device", "DeviceType")
.storeResultsTo(device_type_variant);
return device_type_variant.get<uint32_t>();
}
std::string GetPath() const
{
return m_proxy->getObjectPath();
}
private:
std::unique_ptr<sdbus::IProxy> m_proxy;
};
class NetworkManager
{
public:
NetworkManager()
: m_proxy(sdbus::createProxy(
sdbus::createSystemBusConnection(),
"org.freedesktop.NetworkManager",
"/org/freedesktop/NetworkManager")),
m_settings_proxy(sdbus::createProxy(
sdbus::createSystemBusConnection(),
"org.freedesktop.NetworkManager",
"/org/freedesktop/NetworkManager/Settings"))
{
}
struct HotspotInfo
{
std::string connection_path;
std::string active_connection_path;
};
std::string AddConnection(const std::map<std::string, std::map<std::string, sdbus::Variant>> &connection)
{
std::string path;
m_settings_proxy->callMethod("AddConnection")
.onInterface("org.freedesktop.NetworkManager.Settings")
.withArguments(connection)
.storeResultsTo(path);
return path;
}
std::string AddConnection(const std::string &ssid, const std::string &password)
{
// Create connection
std::map<std::string, std::map<std::string, sdbus::Variant>> connection;
connection["connection"]["type"] = sdbus::Variant("802-11-wireless");
connection["connection"]["id"] = sdbus::Variant(ssid);
std::vector<uint8_t> ssid_bytes(ssid.begin(), ssid.end());
connection["802-11-wireless"]["ssid"] = sdbus::Variant(ssid_bytes);
connection["802-11-wireless"]["mode"] = sdbus::Variant(std::string("ap"));
connection["802-11-wireless-security"]["key-mgmt"] = sdbus::Variant(std::string("wpa-psk"));
connection["802-11-wireless-security"]["psk"] = sdbus::Variant(password);
connection["ipv4"]["method"] = sdbus::Variant(std::string("shared"));
connection["ipv6"]["method"] = sdbus::Variant(std::string("ignore"));
return AddConnection(connection);
}
std::string ActivateConnection(const std::string &connection_path, const std::string &device_path)
{
std::string active_connection_path;
m_proxy->callMethod("ActivateConnection")
.onInterface("org.freedesktop.NetworkManager")
.withArguments(connection_path, device_path, "/")
.storeResultsTo(active_connection_path);
return active_connection_path;
}
void DeactivateConnection(const std::string &active_connection_path)
{
m_proxy->callMethod("DeactivateConnection")
.onInterface("org.freedesktop.NetworkManager")
.withArguments(active_connection_path);
}
std::string GetWirelessDevicePath()
{
auto devices = GetAllDevices();
for (const auto &device : devices)
{
if (device.GetDeviceType() == 2)
{ // 2 is the value for Wi-Fi devices
return device.GetPath();
}
}
throw std::runtime_error("No Wi-Fi device found");
}
std::string CreateHotspot(const std::string &ssid, const std::string &password)
{
// Create connection
std::map<std::string, std::map<std::string, sdbus::Variant>> connection;
connection["connection"]["type"] = sdbus::Variant("802-11-wireless");
connection["connection"]["id"] = sdbus::Variant(ssid);
std::vector<uint8_t> ssid_bytes(ssid.begin(), ssid.end());
connection["802-11-wireless"]["ssid"] = sdbus::Variant(ssid_bytes);
connection["802-11-wireless"]["mode"] = sdbus::Variant(std::string("ap"));
connection["802-11-wireless-security"]["key-mgmt"] = sdbus::Variant(std::string("wpa-psk"));
connection["802-11-wireless-security"]["psk"] = sdbus::Variant(password);
connection["ipv4"]["method"] = sdbus::Variant(std::string("shared"));
connection["ipv6"]["method"] = sdbus::Variant(std::string("ignore"));
auto connection_path = AddConnection(connection);
return connection_path;
}
std::string EnableHotspot(const std::string &connection_path)
{
// Enable hotspot
std::string device_path = GetWirelessDevicePath();
std::string active_connection_path = ActivateConnection(connection_path, device_path);
return active_connection_path;
}
void RemoveConnection(const std::string &connection)
{
auto connection_proxy = sdbus::createProxy(
m_proxy->getConnection(),
"org.freedesktop.NetworkManager",
connection);
connection_proxy->callMethod("Delete")
.onInterface("org.freedesktop.NetworkManager.Settings.Connection");
}
std::optional<std::string> GetActiveConnection(const std::string &device_path)
{
std::vector<sdbus::ObjectPath> active_connections;
m_proxy->callMethod("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments("org.freedesktop.NetworkManager", "ActiveConnections")
.storeResultsTo(active_connections);
for (const auto &conn : active_connections)
{
auto conn_proxy = sdbus::createProxy(
m_proxy->getConnection(),
"org.freedesktop.NetworkManager",
conn);
sdbus::ObjectPath conn_device;
conn_proxy->callMethod("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments("org.freedesktop.NetworkManager.Connection.Active", "Devices")
.storeResultsTo(conn_device);
if (conn_device == device_path)
{
return conn;
}
}
return std::nullopt;
}
std::string GetHotspotConnectionPath(const std::string &ssid)
{
std::vector<sdbus::ObjectPath> connections;
m_settings_proxy->callMethod("ListConnections")
.onInterface("org.freedesktop.NetworkManager.Settings")
.storeResultsTo(connections);
for (const auto &conn_path : connections)
{
auto conn_proxy = sdbus::createProxy(sdbus::createSystemBusConnection(),
"org.freedesktop.NetworkManager",
conn_path);
sdbus::Variant settings_variant;
conn_proxy->callMethod("GetSettings")
.onInterface("org.freedesktop.NetworkManager.Settings.Connection")
.storeResultsTo(settings_variant);
auto settings = settings_variant.get<std::map<std::string, std::map<std::string, sdbus::Variant>>>();
// Check if this is a Wi-Fi connection
if (settings["connection"]["type"].get<std::string>() == "802-11-wireless")
{
// Check if it's an access point (hotspot) connection
if (settings["802-11-wireless"]["mode"].get<std::string>() == "ap")
{
// Check if the SSID matches
std::vector<uint8_t> conn_ssid = settings["802-11-wireless"]["ssid"].get<std::vector<uint8_t>>();
std::string conn_ssid_str(conn_ssid.begin(), conn_ssid.end());
if (conn_ssid_str == ssid)
{
return conn_path;
}
}
}
}
return ""; // No matching hotspot connection found
}
private:
std::vector<Device> GetAllDevices()
{
sdbus::Variant devices_variant;
m_proxy->callMethod("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments("org.freedesktop.NetworkManager", "AllDevices")
.storeResultsTo(devices_variant);
std::vector<sdbus::ObjectPath> device_paths = devices_variant.get<std::vector<sdbus::ObjectPath>>();
std::vector<Device> devices;
for (const auto &path : device_paths)
{
devices.emplace_back(path);
}
return devices;
}
std::unique_ptr<sdbus::IProxy> m_proxy;
std::unique_ptr<sdbus::IProxy> m_settings_proxy;
};
}
JSON_MAP_BEGIN(WifiConfigSettings)
// v0
JSON_MAP_REFERENCE(WifiConfigSettings, valid)
JSON_MAP_REFERENCE(WifiConfigSettings, wifiWarningGiven)
JSON_MAP_REFERENCE(WifiConfigSettings, rebootRequired)
JSON_MAP_REFERENCE(WifiConfigSettings, enable)
JSON_MAP_REFERENCE(WifiConfigSettings, hotspotName)
JSON_MAP_REFERENCE(WifiConfigSettings, mdnsName)
JSON_MAP_REFERENCE(WifiConfigSettings, hasPassword)
JSON_MAP_REFERENCE(WifiConfigSettings, password)
JSON_MAP_REFERENCE(WifiConfigSettings, countryCode)
JSON_MAP_REFERENCE(WifiConfigSettings, channel)
// v1: Auto-hotspot
JSON_MAP_REFERENCE(WifiConfigSettings, autoStartMode)
JSON_MAP_REFERENCE(WifiConfigSettings, homeNetwork)
JSON_MAP_REFERENCE(WifiConfigSettings, hasSavedPassword)
JSON_MAP_END()
int32_t pipedal::ChannelToChannelNumber(const std::string &channel)
{
std::string t = channel;
// remove deprecated band specs.
if (t.size() > 1 && t[0] == 'a' || t[0] == 'g')
{
t = t.substr(1);
}
int32_t channelNumber = 0;
std::stringstream ss(t);
ss >> channelNumber;
return channelNumber;
}
static uint32_t ParseChannel(const std::string &channel)
{
std::string t = channel;
// remove dprecated band specs.
@@ -52,24 +461,9 @@ int32_t pipedal::ChannelToChannelNumber(const std::string&channel)
{
t = t.substr(1);
}
int32_t channelNumber = 1;
std::stringstream ss(t);
ss >> channelNumber;
return channelNumber;
}
static uint32_t ParseChannel(const std::string & channel)
{
std::string t = channel;
// remove dprecated band specs.
if (t.size() > 1 && t[0] == 'a' || t[0] == 'g')
{
t = t.substr(1);
}
size_t size = t.length();
unsigned long long lChannel = std::stoull(t,&size);
unsigned long long lChannel = std::stoull(t, &size);
if (size != t.length())
{
throw invalid_argument("Expecting a number: '" + t + "'.");
@@ -92,7 +486,7 @@ uint32_t pipedal::ChannelToWifiFrequency(uint32_t channel)
// 2.4GHz.
if (channel >= 1 && channel <= 13)
{
return 2412 + 5*(channel-1);
return 2412 + 5 * (channel - 1);
}
if (channel == 14)
{
@@ -101,26 +495,25 @@ uint32_t pipedal::ChannelToWifiFrequency(uint32_t channel)
// 802.11y
if (channel >= 131 && channel < 137)
{
return 3660 + (channel-131)*5;
return 3660 + (channel - 131) * 5;
}
if (channel >= 32 && channel <= 68 && (channel & 1) == 0)
{
return 5160 + (channel-32)/2*10;
return 5160 + (channel - 32) / 2 * 10;
}
if (channel == 96) return 5480;
if (channel == 96)
return 5480;
if (channel >= 100 && channel <= 196)
{
return 5500 + (channel-100)/5;
return 5500 + (channel - 100) / 5;
}
throw invalid_argument(SS("Invalid WiFi channel: " << channel));
throw invalid_argument(SS("Invalid Wi-Fi channel: " << channel));
}
bool WifiConfigSettings::ValidateChannel(const std::string&countryCode,const std::string&value)
bool WifiConfigSettings::ValidateChannel(const std::string &countryCode, const std::string &value)
{
if (value == "0") // = "Autoselect".
if (value == "0") // = "Autoselect".
{
return true;
}
@@ -134,15 +527,17 @@ bool WifiConfigSettings::ValidateChannel(const std::string&countryCode,const std
{
throw std::invalid_argument(SS("Invalid country code: " << countryCode));
}
auto regDom = getWifiRegClass(countryCode,ParseChannel(value),40);
if (regDom == -1) {
std::vector<int32_t> valid_channels = getValidChannels(countryCode,40);
auto regDom = getWifiRegClass(countryCode, ParseChannel(value), 40);
if (regDom == -1)
{
std::vector<int32_t> valid_channels = getValidChannels(countryCode, 40);
std::stringstream ss;
ss << "Channel " << value << " is not permitted in the selected country.\n Valid channels: ";
bool first = true;
for (auto channel: valid_channels)
for (auto channel : valid_channels)
{
if (!first) ss << ", ";
if (!first)
ss << ", ";
first = false;
ss << channel;
}
@@ -153,36 +548,280 @@ bool WifiConfigSettings::ValidateChannel(const std::string&countryCode,const std
return true;
}
void WifiConfigSettings::ParseArguments(const std::vector<std::string> &argv)
{
static const char* trueValues[] {
"true",
"on",
"yes"
"1",
nullptr
};
static const char* falseValues[] {
"false",
"off",
"no",
"0",
nullptr
};
static bool Matches(const std::string&value, const char**matches)
{
while(*matches)
{
if (value == *matches) return true;
++matches;
}
return false;
}
static bool TryStringToBool(const std::string &value, bool*outputValue)
{
if (Matches(value,trueValues))
{
*outputValue = true;
return true;
}
if (Matches(value, falseValues)) {
*outputValue = false;
return true;
}
*outputValue = false;
return false;
}
static WifiConfigSettings::ssid_t readSsid(std::istream&ss)
{
using ssid_t = WifiConfigSettings::ssid_t;
char c;
ssid_t result;
while (ss.peek() == ' ')
{
ss >> c;
}
if (ss.peek() == '"')
{
ss >> c;
while (!ss.eof() && ss.peek() != '"')
{
ss >> c;
if (c == '\\')
{
if (ss.eof())
{
break;
}
ss >> c;
switch (c) {
case 'n':
result.push_back((uint8_t)'\n');
break;
case 'r':
result.push_back((uint8_t)'\r');
break;
case 't':
result.push_back((uint8_t)'\t');
break;
case 'b':
result.push_back((uint8_t)'\b');
break;
default:
result.push_back((uint8_t)c);
break;
}
} else {
result.push_back((uint8_t)c);
}
}
} else {
while (!ss.eof())
{
if (c == ':')
{
break;
}
ss >> c;
if (c == ':')
{
break;
}
result.push_back((uint8_t)c);
}
}
return result;
}
static std::vector<WifiConfigSettings::ssid_t> stringToSsidArray(const std::string&value)
{
using ssid_t = WifiConfigSettings::ssid_t;
std::istringstream ss(value);
std::vector<WifiConfigSettings::ssid_t> result;
while (true)
{
ssid_t ssid = readSsid(ss);
if (ssid.size() == 0)
{
break;
}
result.push_back(ssid);
if (ss.peek() == ':')
{
char c;
ss >> c;
}
}
return result;
}
void WifiConfigSettings::ParseArguments(
const std::vector<std::string> &argv,
HotspotAutoStartMode startMode,
const std::string homeNetworkSsid
)
{
this->valid_ = false;
if (argv.size() != 4) {
if (argv.size() != 4)
{
throw invalid_argument("Invalid number of arguments.");
}
this->enable_ = true;
WifiConfigSettings oldSettings;
oldSettings.Load();
this->valid_ = false;
this->autoStartMode_ = (int16_t)startMode;
this->enable_ = startMode != HotspotAutoStartMode::Never;
this->homeNetwork_ = homeNetworkSsid;
this->mdnsName_ = GetHostName();
this->countryCode_ = argv[0];
this->hotspotName_ = argv[1];
this->mdnsName_ = this->hotspotName_;
this->password_ = argv[2];
this->channel_ = argv[3];
this->hasPassword_ = this->password_.length() != 0;
this->hasSavedPassword_ = oldSettings.hasSavedPassword_;
if (!ValidateCountryCode(this->countryCode_))
{
throw invalid_argument("Invalid country code.");
}
if (this->hotspotName_.length() > 31) throw invalid_argument("Hotspot name is too long.");
if (this->hotspotName_.length() < 1) throw invalid_argument("Hotspot name is too short.");
if (this->password_.size() != 0 && this->password_.size() < 8) throw invalid_argument("Passphrase must be at least 8 characters long.");
if (this->hotspotName_.length() > 31)
throw invalid_argument("Hotspot name is too long.");
if (this->hotspotName_.length() < 1)
throw invalid_argument("Hotspot name is too short.");
if (this->password_.size() != 0 && this->password_.size() < 8)
throw invalid_argument("Passphrase must be at least 8 characters long.");
if (!ValidateChannel(this->countryCode_,this->channel_))
if (this->password_.size() == 0 && !this->hasSavedPassword_)
{
throw invalid_argument("Passphrase required.");
}
if (!ValidateChannel(this->countryCode_, this->channel_))
{
throw invalid_argument("Channel is not valid.");
}
// validate that the channel number is supported for the given country code.
this->valid_ = true;
}
bool WifiConfigSettings::ConfigurationChanged(const WifiConfigSettings&other) const
{
return !(
this->valid_ == other.valid_ &&
//this->rebootRequired_ == other.rebootRequired_ &&
//this->enable_ == other.enable_ &&
this->countryCode_ == other.countryCode_ &&
this->hotspotName_ == other.hotspotName_ &&
this->hasPassword_ == other.hasPassword_ &&
this->password_ == other.password_ &&
this->channel_ == other.channel_ &&
this->homeNetwork_ == other.homeNetwork_ &&
this->autoStartMode_ == other.autoStartMode_ &&
this->hasSavedPassword_ == other.hasSavedPassword_
);
}
bool WifiConfigSettings::operator==(const WifiConfigSettings&other) const
{
return !ConfigurationChanged(other) && this->wifiWarningGiven_ == other.wifiWarningGiven_;
}
static bool CanSeeHomeNetwork(const std::string&home, const std::vector<std::string>&availableNetworks)
{
for (const auto &availableNetwork: availableNetworks)
{
if (availableNetwork == home)
{
return true;
}
}
return false;
}
bool WifiConfigSettings::WantsHotspot(
bool ethernetConnected,
const std::vector<std::string> &availableRememberedNetworks,
const std::vector<std::string> &availableNetworks)
{
if ((!this->valid_))
return false;
HotspotAutoStartMode autoStartMode = (HotspotAutoStartMode)this->autoStartMode_;
switch (autoStartMode)
{
case HotspotAutoStartMode::Never:
default:
return false;
case HotspotAutoStartMode::NoEthernetConnection:
return !ethernetConnected;
case HotspotAutoStartMode::NotAtHome:
return !CanSeeHomeNetwork(this->homeNetwork_, availableNetworks);
case HotspotAutoStartMode::NoRememberedWifiConections:
return availableRememberedNetworks.size() == 0;
case HotspotAutoStartMode::Always:
return true;
}
}
std::string pipedal::ssidToString(const std::vector<uint8_t> &ssid)
{
std::stringstream s;
for (auto v: ssid)
{
if (v == 0) break; // breaks for some arguably legal ssids, but I can live with that.
s << (char)v;
}
return s.str();
}
std::vector<std::string> pipedal::ssidToStringVector(const std::vector<std::vector<uint8_t>> &ssids)
{
std::vector<std::string> result;
result.reserve(ssids.size());
for (const std::vector<uint8_t> &ssid: ssids)
{
result.push_back(ssidToString(ssid));
}
return result;
}
bool WifiConfigSettings::WantsHotspot(
bool ethernetConnected,
const std::vector<std::vector<uint8_t>> &availableRememberedNetworks, // remembered networks that are currently visible
const std::vector<std::vector<uint8_t>> &availableNetworks // all visible networks.
)
{
std::vector<std::string> sAvailableRememberedNetworks = ssidToStringVector(availableRememberedNetworks);
std::vector<std::string> sAvailableNetworks = ssidToStringVector(availableNetworks);
return WantsHotspot(ethernetConnected,sAvailableRememberedNetworks,sAvailableNetworks);
}
}
@@ -123,7 +123,7 @@ void WifiDirectConfigSettings::ParseArguments(const std::vector<std::string> &ar
if (!WifiConfigSettings::ValidateChannel(this->countryCode_,channel_))
{
throw invalid_argument(SS("Invalid WiFi channel: " << channel_));
throw invalid_argument(SS("Invalid Wi-Fi channel: " << channel_));
}
}
@@ -0,0 +1,84 @@
#pragma once
#include <memory>
#include <functional>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>
#include <atomic>
namespace sdbus {
class IConnection;
};
class DBusDispatcher {
DBusDispatcher(const DBusDispatcher&) = delete;
DBusDispatcher(DBusDispatcher&&) = delete;
DBusDispatcher & operator=(const DBusDispatcher&) = delete;
DBusDispatcher & operator=(const DBusDispatcher&&) = delete;
public:
DBusDispatcher(bool systemBus = true);
~DBusDispatcher();
void Run();
// Stop everything, shutting down gracefully.
void Stop();
void Wait();
bool IsFinished();
// Stop, but don't wait. Suitable for use in a signal handler.
// The callback is executed by the dispatcher thread, and can therefore call non-signal-safe functions.
void SignalStop(std::function<void(void)> &&callback);
bool IsStopping();
using PostHandle = uint64_t;
using PostCallback = std::function<void()>;
PostHandle Post(PostCallback&&fn);
using clock = std::chrono::steady_clock;
template<class REP,class PERIOD>
DBusDispatcher::PostHandle PostDelayed(const std::chrono::duration<REP,PERIOD>&delay,PostCallback&&fn)
{
return PostDelayed(
std::chrono::duration_cast<clock::duration,REP,PERIOD>(delay),
std::move(fn));
}
DBusDispatcher::PostHandle PostDelayed(const clock::duration&delay,PostCallback&&fn);
bool CancelPost(DBusDispatcher::PostHandle handle);
sdbus::IConnection&Connection() { return *busConnection;}
private:
std::atomic<bool> isFinished;
std::atomic<bool> signalStopRequested;
bool signalStopExecuted = false;
std::function<void(void)> signalStopCallback;
uint64_t NextHandle() {
return nextHandle.fetch_add(1);
}
int GetEventTimeoutMs();
std::atomic<PostHandle> nextHandle;
int eventFd = -1;
bool closed = false;
void ThreadProc();
void WakeThread();
std::unique_ptr<sdbus::IConnection> busConnection;
struct CallbackEntry {
PostCallback callback;
clock::time_point time;
PostHandle handle;
};
std::atomic<bool> stopping;
std::vector<CallbackEntry> postedEvents;
bool threadJoined;
std::thread serviceThread;
std::mutex postMutex;
};
+62
View File
@@ -0,0 +1,62 @@
#pragma once
#include <functional>
#include <atomic>
#include <map>
#include <vector>
#include <mutex>
#include <memory>
namespace impl {
extern std::atomic<uint64_t> NextHandle;
}
using DBusEventHandle=uint64_t;
constexpr DBusEventHandle INVALID_DBUS_EVENT_HANDLE = 0;
template <typename... ARG_TYPES>
class DBusEvent {
public:
using Callback=std::function<void (ARG_TYPES...)>;
DBusEventHandle add(Callback&&cb)
{
auto handle = impl::NextHandle.fetch_add(1,std::memory_order::acq_rel);
{
std::lock_guard<std::mutex> lock{m};
callbacks[handle] = std::make_shared<Callback>(std::move(cb));
}
return handle;
}
void remove(DBusEventHandle h)
{
std::lock_guard<std::mutex> lock{m};
callbacks.erase(h);
}
void fire(ARG_TYPES...args)
{
std::vector<std::shared_ptr<Callback>> cbs;
cbs.reserve(callbacks.size());
{
std::lock_guard<std::mutex> lock{m};
for (const auto &cb: callbacks)
{
cbs.push_back(cb.second);
}
}
for (auto&cb: cbs)
{
(*cb)(args...);
}
{
// destruct shared_ptrs with mutex held.
std::lock_guard<std::mutex> lock{m};
cbs.clear();
}
}
private:
std::map<DBusEventHandle,std::shared_ptr<Callback>> callbacks;
std::mutex m;
};
+99
View File
@@ -0,0 +1,99 @@
#pragma once
#include <string>
#include <iostream>
#include <functional>
#include <mutex>
#include <filesystem>
enum class DBusLogLevel {
Trace,
Debug,
Info,
Warning,
Error,
None
};
namespace impl {
extern std::mutex logMutex;
extern DBusLogLevel dbusLogLevel;
}
extern void SetDBusLogLevel(DBusLogLevel level);
inline DBusLogLevel GetDBusLogLevel() {
return impl::dbusLogLevel;
}
inline bool IsDBusLoggingEnabled(DBusLogLevel level) {
return level >= impl::dbusLogLevel;
}
class IDBusLogger {
public:
IDBusLogger() {}
virtual ~IDBusLogger() { }
virtual void LogError(const std::string&message) = 0;
virtual void LogWarning(const std::string&message) = 0;
virtual void LogInfo(const std::string&message) = 0;
virtual void LogDebug(const std::string&message) = 0;
virtual void LogTrace(const std::string&message) = 0;
};
void SetDBusConsoleLogger();
void AddDBusConsoleLogger();
void SetDBusSystemdLogger();
void SetDBusFileLogger(const std::filesystem::path &path);
void SetDBusLogger(std::unique_ptr<IDBusLogger>&&logger);
extern void LogError(const std::string&path,const char*method,const std::string&message);
extern void LogInfo(const std::string&path,const char*method,const std::string&message);
extern void LogDebug(const std::string&path,const char*method,const std::string&message);
extern void LogWarning(const std::string&path,const char*method,const std::string&message);
extern void LogTrace(const std::string&path,const char*method,const std::string&message);
extern void LogTrace(const std::string&path,const char*method,const std::function<std::string ()> &text);
extern void LogDebug(const std::string&path,const char*method,const std::function<std::string ()> &text);
extern void LogInfo(const std::string&path,const char*method,const std::function<std::string ()> &text);
extern void LogWarning(const std::string&path,const char*method,const std::function<std::string ()> &text);
extern void LogError(const std::string&path,const char*method,const std::function<std::string ()> &text);
class DBusLog {
protected:
DBusLog(const std::string &path)
:path(path)
{
}
protected:
void LogError(const char*tag,const std::string&message)
{
::LogError(path,tag,message);
}
void LogWarning(const char *tag, const std::string&message)
{
::LogWarning(path,tag,message);
}
void LogDebug(const char*tag, const std::string &text)
{
::LogDebug(path,tag,text);
}
void LogDebug(const char*tag, const std::function<std::string ()> &text)
{
if (impl::dbusLogLevel > DBusLogLevel::Debug) return;
::LogDebug(path,tag,text());
}
void LogTrace(const char*tag, const std::string &text)
{
::LogTrace(path,tag,text);
}
void LogTrace(const char*tag, const std::function<std::string ()> &text)
{
if (impl::dbusLogLevel > DBusLogLevel::Trace) return;
::LogTrace(path,tag,text());
}
private:
std::string path;
};
@@ -0,0 +1,11 @@
#pragma once
#include <map>
#include <string>
#include <ostream>
namespace sdbus {
class Variant;
}
extern std::ostream&operator<<(std::ostream&s, const sdbus::Variant&v);
extern std::ostream&operator<<(std::ostream&s,const std::map<std::string, sdbus::Variant>& properties);
@@ -150,7 +150,7 @@ namespace pipedal
static void error(const char *format, ...)
{
if (Lv2Log::log_level_ >= LogLevel::Warning)
if (Lv2Log::log_level_ >= LogLevel::Error)
{
va_list arglist;
va_start(arglist, format);
@@ -0,0 +1,348 @@
#pragma once
#include "DBusEvent.hpp"
#include "DBusLog.hpp"
#include "DBusDispatcher.hpp"
#include "dbus/org.freedesktop.NetworkManager.Device.WifiP2P.hpp"
#include "dbus/org.freedesktop.NetworkManager.Device.hpp"
#include "dbus/org.freedesktop.NetworkManager.Device.Wireless.hpp"
#include "dbus/org.freedesktop.NetworkManager.Settings.Connection.hpp"
#include "dbus/org.freedesktop.NetworkManager.WifiP2PPeer.hpp"
#include "dbus/org.freedesktop.NetworkManager.AccessPoint.hpp"
#include "dbus/org.freedesktop.NetworkManager.Connection.Active.hpp"
#include "dbus/org.freedesktop.NetworkManager.hpp"
#include "libnm/nm-dbus-interface.h"
namespace dbus::networkmanager {
namespace nm_impl
{
extern std::string NetworkManagerStateToString(uint32_t state);
std::string NetworkManagerDeviceStateToString(uint32_t state);
};
class NetworkManager : public sdbus::ProxyInterfaces<org::freedesktop::NetworkManager_proxy>
{
public:
using ptr = std::unique_ptr<NetworkManager>;
NetworkManager(DBusDispatcher &dispatcher)
: sdbus::ProxyInterfaces<org::freedesktop::NetworkManager_proxy>(
dispatcher.Connection(),
INTERFACE_NAME,
"/org/freedesktop/NetworkManager")
{
registerProxy();
}
virtual ~NetworkManager()
{
unregisterProxy();
}
static ptr Create(DBusDispatcher &dispatcher) { return std::make_unique<NetworkManager>(dispatcher); }
DBusEvent<> OnCheckPermissions;
DBusEvent<uint32_t> OnStateChanged;
DBusEvent<const sdbus::ObjectPath &> OnDeviceAdded;
DBusEvent<const sdbus::ObjectPath &> OnDeviceRemoved;
private:
void EventTrace(const char *method, const std::string &message)
{
LogTrace(getObjectPath(), method, message);
}
virtual void onStateChanged(const uint32_t &state)
{
EventTrace("onStateChanged", nm_impl::NetworkManagerStateToString(state));
OnStateChanged.fire(state);
}
virtual void onCheckPermissions() { OnCheckPermissions.fire(); }
virtual void onDeviceAdded(const sdbus::ObjectPath &device_path)
{
EventTrace("onDeviceAdded", device_path.c_str());
OnDeviceAdded.fire(device_path);
}
virtual void onDeviceRemoved(const sdbus::ObjectPath &device_path)
{
EventTrace("onDeviceRemoved", device_path.c_str());
OnDeviceRemoved.fire(device_path);
}
};
class Device : public sdbus::ProxyInterfaces<org::freedesktop::NetworkManager::Device_proxy>
{
public:
using ptr = std::unique_ptr<Device>;
Device(DBusDispatcher &dispatcher, const sdbus::ObjectPath &path)
: sdbus::ProxyInterfaces<org::freedesktop::NetworkManager::Device_proxy>(
dispatcher.Connection(),
"org.freedesktop.NetworkManager",
path)
{
registerProxy();
}
virtual ~Device()
{
unregisterProxy();
}
static ptr Create(DBusDispatcher &dispatcher, const sdbus::ObjectPath &path)
{
return std::make_unique<Device>(dispatcher, path);
}
DBusEvent<uint32_t, uint32_t, uint32_t> OnStateChanged;
private:
const uint32_t NM_DEVICE_TYPE_WIFI_P2P = 30; // from lbnm-dev package.
public:
bool IsP2pDevice()
{
return this->DeviceType() == NM_DEVICE_TYPE_WIFI_P2P;
}
private:
void EventTrace(const char *method, const std::string &message)
{
LogTrace(getObjectPath(), method, message);
}
virtual void onStateChanged(const uint32_t &new_state, const uint32_t &old_state, const uint32_t &reason)
{
EventTrace("onStateChanged", nm_impl::NetworkManagerDeviceStateToString(new_state));
OnStateChanged.fire(new_state, old_state, reason);
}
};
class WifiP2P : public sdbus::ProxyInterfaces<
org::freedesktop::NetworkManager::Device::WifiP2P_proxy>
{
public:
using ptr = std::unique_ptr<WifiP2P>;
using base = sdbus::ProxyInterfaces<
org::freedesktop::NetworkManager::Device::WifiP2P_proxy>;
WifiP2P(DBusDispatcher &dispatcher, const std::string &objectPath)
: base(dispatcher.Connection(), "org.freedesktop.NetworkManager", objectPath)
{
registerProxy();
}
virtual ~WifiP2P()
{
unregisterProxy();
}
static ptr Create(DBusDispatcher &dispatcher, const std::string &objectPath)
{
return std::make_unique<WifiP2P>(dispatcher, objectPath);
}
DBusEvent<const sdbus::ObjectPath &> OnPeerAdded;
DBusEvent<const sdbus::ObjectPath &> OnPeerRemoved;
DBusEvent<uint32_t> OnStateChanged;
private:
void EventTrace(const char *method, const std::string &message)
{
LogTrace(getObjectPath(), method, message);
}
virtual void onStateChanged(const uint32_t &new_state, const uint32_t &old_state, const uint32_t &reason)
{
EventTrace("onStateChanged", nm_impl::NetworkManagerStateToString(new_state));
OnStateChanged.fire(new_state);
}
virtual void onPeerAdded(const sdbus::ObjectPath &peer)
{
EventTrace("onPeerAdded", peer);
OnPeerAdded.fire(peer);
}
virtual void onPeerRemoved(const sdbus::ObjectPath &peer)
{
EventTrace("onPeerRemoved", peer);
OnPeerRemoved.fire(peer);
}
};
class Connection : public sdbus::ProxyInterfaces<org::freedesktop::NetworkManager::Settings::Connection_proxy>
{
public:
using self = Connection;
using ptr = std::unique_ptr<self>;
Connection(DBusDispatcher &dispatcher, const sdbus::ObjectPath &objectPath)
: sdbus::ProxyInterfaces<org::freedesktop::NetworkManager::Settings::Connection_proxy>(
dispatcher.Connection(), "org.freedesktop.NetworkManager", objectPath)
{
registerProxy();
}
static ptr Create(DBusDispatcher &dispatcher, const sdbus::ObjectPath &objectPath)
{
return std::make_unique<self>(dispatcher, objectPath);
}
virtual ~Connection()
{
unregisterProxy();
}
DBusEvent<> OnUpdated;
DBusEvent<> OnRemoved;
private:
virtual void onUpdated() override
{
OnUpdated.fire();
}
virtual void onRemoved() override
{
OnRemoved.fire();
}
};
class WifiP2PPeer : public sdbus::ProxyInterfaces<org::freedesktop::NetworkManager::WifiP2PPeer_proxy>
{
public:
using self = WifiP2PPeer;
using ptr = std::unique_ptr<self>;
WifiP2PPeer(DBusDispatcher &dispatcher, const sdbus::ObjectPath &objectPath)
: sdbus::ProxyInterfaces<org::freedesktop::NetworkManager::WifiP2PPeer_proxy>(
dispatcher.Connection(), "org.freedesktop.NetworkManager", objectPath)
{
registerProxy();
}
virtual ~WifiP2PPeer()
{
unregisterProxy();
}
static ptr Create(DBusDispatcher &dispatcher, const sdbus::ObjectPath &objectPath)
{
return std::make_unique<self>(dispatcher, objectPath);
}
DBusEvent<const sdbus::ObjectPath &> OnPeerAdded;
DBusEvent<const sdbus::ObjectPath &> OnPeerRemoved;
private:
virtual void onPeerAdded(const sdbus::ObjectPath &peer)
{
OnPeerAdded.fire(peer);
}
virtual void onPeerRemoved(const sdbus::ObjectPath &peer)
{
OnPeerRemoved.fire(peer);
}
};
class DeviceWireless : public sdbus::ProxyInterfaces<org::freedesktop::NetworkManager::Device::Wireless_proxy>
{
public:
using self = DeviceWireless;
using ptr = std::unique_ptr<self>;
using proxy_t = org::freedesktop::NetworkManager::Device::Wireless_proxy;
DeviceWireless(DBusDispatcher &dispatcher, const sdbus::ObjectPath &path)
: sdbus::ProxyInterfaces<proxy_t>(
dispatcher.Connection(),
"org.freedesktop.NetworkManager",
path)
{
registerProxy();
}
virtual ~DeviceWireless()
{
unregisterProxy();
}
static ptr Create(DBusDispatcher &dispatcher, const sdbus::ObjectPath &path)
{
return std::make_unique<self>(dispatcher, path);
}
DBusEvent<const sdbus::ObjectPath&> OnAccessPointAdded;
DBusEvent<const sdbus::ObjectPath&> OnAccessPointRemoved;
protected:
void EventTrace(const char *method, const std::string &message)
{
LogTrace(getObjectPath(), method, message);
}
virtual void onAccessPointAdded(const sdbus::ObjectPath& access_point) override {
EventTrace("onAccessPointAdded", access_point.c_str());
OnAccessPointAdded.fire(access_point);
}
virtual void onAccessPointRemoved(const sdbus::ObjectPath& access_point) {
EventTrace("onAccessPointRemoved",access_point.c_str());
OnAccessPointRemoved.fire(access_point);
}
};
class AccessPoint : public sdbus::ProxyInterfaces<org::freedesktop::NetworkManager::AccessPoint_proxy>
{
public:
using self = AccessPoint;
using ptr = std::unique_ptr<self>;
using proxy_t = org::freedesktop::NetworkManager::AccessPoint_proxy;
AccessPoint(DBusDispatcher &dispatcher, const sdbus::ObjectPath &path)
: sdbus::ProxyInterfaces<proxy_t>(
dispatcher.Connection(),
"org.freedesktop.NetworkManager",
path)
{
registerProxy();
}
virtual ~AccessPoint()
{
unregisterProxy();
}
static ptr Create(DBusDispatcher &dispatcher, const sdbus::ObjectPath &path)
{
return std::make_unique<self>(dispatcher, path);
}
protected:
void EventTrace(const char *method, const std::string &message)
{
LogTrace(getObjectPath(), method, message);
}
};
class ActiveConnection : public sdbus::ProxyInterfaces<org::freedesktop::NetworkManager::Connection::Active_proxy>
{
public:
using self = ActiveConnection;
using ptr = std::unique_ptr<self>;
using proxy_t = org::freedesktop::NetworkManager::Connection::Active_proxy;
ActiveConnection(DBusDispatcher &dispatcher, const sdbus::ObjectPath &path)
: sdbus::ProxyInterfaces<proxy_t>(
dispatcher.Connection(),
"org.freedesktop.NetworkManager",
path)
{
registerProxy();
}
virtual ~ActiveConnection()
{
unregisterProxy();
}
static ptr Create(DBusDispatcher &dispatcher, const sdbus::ObjectPath &path)
{
return std::make_unique<self>(dispatcher, path);
}
DBusEvent<uint32_t,uint32_t> OnStateChanged;
protected:
void EventTrace(const char *method, const std::string &message)
{
LogTrace(getObjectPath(), method, message);
}
virtual void onStateChanged(const uint32_t& state, const uint32_t& reason) {
OnStateChanged.fire(state,reason);
}
};
} // namespace
+2 -2
View File
@@ -25,7 +25,7 @@
#pragma once
#define DEVICE_GUID_FILE "/etc/pipedal/config/device_uuid"
#define DEVICE_GUID_FILE "/var/pipedal/config/device_uuid"
#define PIPEDAL_P2PD_CONF_PATH "/etc/pipedal/config/pipedal_p2pd.conf"
#define PIPEDAL_P2PD_CONF_PATH "/var/pipedal/config/pipedal_p2pd.conf"
@@ -20,30 +20,92 @@
#pragma once
#include "json.hpp"
#include <vector>
#include <string>
#ifndef NEW_WIFI_CONFIG
// 0-> Old hostapd-based hotspot. Now dead code. Works with buster only.
// 1-> NetworkManager-based hotspot (supported on bookworm or later).
#define NEW_WIFI_CONFIG 1
#endif
// Wifi P2P broken completely on Debian 12 updates on raspberry pi.
//
// Setting it to enable uses current-orphaned dead code as of PiPedal v1.2.49
//
// 0: disable.
// 1: enable.
#ifndef ENABLE_WIFI_P2P
#define ENABLE_WIFI_P2P 0
#endif
namespace pipedal {
std::string ssidToString(const std::vector<uint8_t> &ssid);
std::vector<std::string> ssidToStringVector(const std::vector<std::vector<uint8_t>> &ssids);
uint32_t ChannelToWifiFrequency(const std::string &channel);
uint32_t ChannelToWifiFrequency(uint32_t channel);
int32_t ChannelToChannelNumber(const std::string&channel);
enum class HotspotAutoStartMode {
// saved to file. Do not remove or renumber enums.
Never = 0,
NoEthernetConnection = 1,
NotAtHome = 2,
NoRememberedWifiConections = 3,
Always = 4
};
class WifiConfigSettings {
public:
bool valid_ = false;
bool wifiWarningGiven_ = false;
bool rebootRequired_ = false;
bool enable_ = false;
using ssid_t = std::vector<uint8_t>;
WifiConfigSettings();
void Load();
void Save();
int16_t autoStartMode_ = 0; // see HotspotAutoStartMode
bool hasSavedPassword_ = false;
std::string homeNetwork_;
std::string countryCode_ = "US"; // iso 3661
std::string hotspotName_ = "pipedal";
std::string mdnsName_ = "pipedal";
bool hasPassword_ = false;
std::string password_;
std::string channel_ = "g6";
std::string channel_ = "";
std::string mdnsName_ = "pipedal";
void ParseArguments(const std::vector<std::string> &arguments);
bool wifiWarningGiven_ = false; // Do not use. Present only for backward compatibility.
bool valid_ = false; // Do not use. Present only for backward compatibility.
private:
bool rebootRequired_ = false; // Do not use. Present only for backward compatibility.
bool enable_ = false; // Do not use. Present only for backward compatibility.
public:
bool IsEnabled() const { return autoStartMode_ != 0; }
// Initialize from commandline arguments (see ConfigMain.cpp)
void ParseArguments(
const std::vector<std::string> &argv,
HotspotAutoStartMode startMode,
const std::string homeNetworkSsid
);
static bool ValidateCountryCode(const std::string&value);
static bool ValidateChannel(const std::string&countryCode,const std::string&value);
bool operator==(const WifiConfigSettings&other) const;
bool ConfigurationChanged(const WifiConfigSettings&other) const;
bool WantsHotspot(
bool ethernetConnected,
const std::vector<std::string> &availableRememberedNetworks, // remembered networks that are currently visible
const std::vector<std::string> &availableNetworks // all visible networks.
);
bool WantsHotspot(
bool ethernetConnected,
const std::vector<std::vector<uint8_t>> &availableRememberedNetworks, // remembered networks that are currently visible
const std::vector<std::vector<uint8_t>> &availableNetworks // all visible networks.
);
public:
DECLARE_JSON_MAP(WifiConfigSettings);
};
@@ -37,7 +37,7 @@ namespace pipedal {
std::string hotspotName_ = "pipedal";
bool pinChanged_ = false;
std::string pin_;
std::string channel_ = "0"; // "0" -> select automatically.
std::string channel_ = "1"; // "0" -> select automatically.
std::string wlan_ = "wlan0";
void ParseArguments(const std::vector<std::string> &arguments);
@@ -0,0 +1,87 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_AccessPoint_hpp__proxy__H__
#define __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_AccessPoint_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
namespace NetworkManager {
class AccessPoint_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.AccessPoint";
protected:
AccessPoint_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
}
~AccessPoint_proxy() = default;
public:
uint32_t Flags()
{
return proxy_.getProperty("Flags").onInterface(INTERFACE_NAME);
}
uint32_t WpaFlags()
{
return proxy_.getProperty("WpaFlags").onInterface(INTERFACE_NAME);
}
uint32_t RsnFlags()
{
return proxy_.getProperty("RsnFlags").onInterface(INTERFACE_NAME);
}
std::vector<uint8_t> Ssid()
{
return proxy_.getProperty("Ssid").onInterface(INTERFACE_NAME);
}
uint32_t Frequency()
{
return proxy_.getProperty("Frequency").onInterface(INTERFACE_NAME);
}
std::string HwAddress()
{
return proxy_.getProperty("HwAddress").onInterface(INTERFACE_NAME);
}
uint32_t Mode()
{
return proxy_.getProperty("Mode").onInterface(INTERFACE_NAME);
}
uint32_t MaxBitrate()
{
return proxy_.getProperty("MaxBitrate").onInterface(INTERFACE_NAME);
}
uint8_t Strength()
{
return proxy_.getProperty("Strength").onInterface(INTERFACE_NAME);
}
int32_t LastSeen()
{
return proxy_.getProperty("LastSeen").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy& proxy_;
};
}}} // namespaces
#endif
@@ -0,0 +1,126 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_Connection_Active_hpp__proxy__H__
#define __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_Connection_Active_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
namespace NetworkManager {
namespace Connection {
class Active_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.Connection.Active";
protected:
Active_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
proxy_.uponSignal("StateChanged").onInterface(INTERFACE_NAME).call([this](const uint32_t& state, const uint32_t& reason){ this->onStateChanged(state, reason); });
}
~Active_proxy() = default;
virtual void onStateChanged(const uint32_t& state, const uint32_t& reason) = 0;
public:
sdbus::ObjectPath Connection()
{
return proxy_.getProperty("Connection").onInterface(INTERFACE_NAME);
}
sdbus::ObjectPath SpecificObject()
{
return proxy_.getProperty("SpecificObject").onInterface(INTERFACE_NAME);
}
std::string Id()
{
return proxy_.getProperty("Id").onInterface(INTERFACE_NAME);
}
std::string Uuid()
{
return proxy_.getProperty("Uuid").onInterface(INTERFACE_NAME);
}
std::string Type()
{
return proxy_.getProperty("Type").onInterface(INTERFACE_NAME);
}
std::vector<sdbus::ObjectPath> Devices()
{
return proxy_.getProperty("Devices").onInterface(INTERFACE_NAME);
}
uint32_t State()
{
return proxy_.getProperty("State").onInterface(INTERFACE_NAME);
}
uint32_t StateFlags()
{
return proxy_.getProperty("StateFlags").onInterface(INTERFACE_NAME);
}
bool Default()
{
return proxy_.getProperty("Default").onInterface(INTERFACE_NAME);
}
sdbus::ObjectPath Ip4Config()
{
return proxy_.getProperty("Ip4Config").onInterface(INTERFACE_NAME);
}
sdbus::ObjectPath Dhcp4Config()
{
return proxy_.getProperty("Dhcp4Config").onInterface(INTERFACE_NAME);
}
bool Default6()
{
return proxy_.getProperty("Default6").onInterface(INTERFACE_NAME);
}
sdbus::ObjectPath Ip6Config()
{
return proxy_.getProperty("Ip6Config").onInterface(INTERFACE_NAME);
}
sdbus::ObjectPath Dhcp6Config()
{
return proxy_.getProperty("Dhcp6Config").onInterface(INTERFACE_NAME);
}
bool Vpn()
{
return proxy_.getProperty("Vpn").onInterface(INTERFACE_NAME);
}
sdbus::ObjectPath Controller()
{
return proxy_.getProperty("Controller").onInterface(INTERFACE_NAME);
}
sdbus::ObjectPath Master()
{
return proxy_.getProperty("Master").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy& proxy_;
};
}}}} // namespaces
#endif
@@ -0,0 +1,42 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_DHCP4Config_hpp__proxy__H__
#define __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_DHCP4Config_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
namespace NetworkManager {
class DHCP4Config_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.DHCP4Config";
protected:
DHCP4Config_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
}
~DHCP4Config_proxy() = default;
public:
std::map<std::string, sdbus::Variant> Options()
{
return proxy_.getProperty("Options").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy& proxy_;
};
}}} // namespaces
#endif
@@ -0,0 +1,42 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_DHCP6Config_hpp__proxy__H__
#define __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_DHCP6Config_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
namespace NetworkManager {
class DHCP6Config_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.DHCP6Config";
protected:
DHCP6Config_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
}
~DHCP6Config_proxy() = default;
public:
std::map<std::string, sdbus::Variant> Options()
{
return proxy_.getProperty("Options").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy& proxy_;
};
}}} // namespaces
#endif
@@ -0,0 +1,58 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_Device_Statistics_hpp__proxy__H__
#define __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_Device_Statistics_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
namespace NetworkManager {
namespace Device {
class Statistics_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.Device.Statistics";
protected:
Statistics_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
}
~Statistics_proxy() = default;
public:
uint32_t RefreshRateMs()
{
return proxy_.getProperty("RefreshRateMs").onInterface(INTERFACE_NAME);
}
void RefreshRateMs(const uint32_t& value)
{
proxy_.setProperty("RefreshRateMs").onInterface(INTERFACE_NAME).toValue(value);
}
uint64_t TxBytes()
{
return proxy_.getProperty("TxBytes").onInterface(INTERFACE_NAME);
}
uint64_t RxBytes()
{
return proxy_.getProperty("RxBytes").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy& proxy_;
};
}}}} // namespaces
#endif
@@ -0,0 +1,64 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_Device_WifiP2P_hpp__proxy__H__
#define __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_Device_WifiP2P_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
namespace NetworkManager {
namespace Device {
class WifiP2P_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.Device.WifiP2P";
protected:
WifiP2P_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
proxy_.uponSignal("PeerAdded").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& peer){ this->onPeerAdded(peer); });
proxy_.uponSignal("PeerRemoved").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& peer){ this->onPeerRemoved(peer); });
}
~WifiP2P_proxy() = default;
virtual void onPeerAdded(const sdbus::ObjectPath& peer) = 0;
virtual void onPeerRemoved(const sdbus::ObjectPath& peer) = 0;
public:
void StartFind(const std::map<std::string, sdbus::Variant>& options)
{
proxy_.callMethod("StartFind").onInterface(INTERFACE_NAME).withArguments(options);
}
void StopFind()
{
proxy_.callMethod("StopFind").onInterface(INTERFACE_NAME);
}
public:
std::string HwAddress()
{
return proxy_.getProperty("HwAddress").onInterface(INTERFACE_NAME);
}
std::vector<sdbus::ObjectPath> Peers()
{
return proxy_.getProperty("Peers").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy& proxy_;
};
}}}} // namespaces
#endif
@@ -0,0 +1,103 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_Device_Wireless_hpp__proxy__H__
#define __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_Device_Wireless_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
namespace NetworkManager {
namespace Device {
class Wireless_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.Device.Wireless";
protected:
Wireless_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
proxy_.uponSignal("AccessPointAdded").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& access_point){ this->onAccessPointAdded(access_point); });
proxy_.uponSignal("AccessPointRemoved").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& access_point){ this->onAccessPointRemoved(access_point); });
}
~Wireless_proxy() = default;
virtual void onAccessPointAdded(const sdbus::ObjectPath& access_point) = 0;
virtual void onAccessPointRemoved(const sdbus::ObjectPath& access_point) = 0;
public:
std::vector<sdbus::ObjectPath> GetAccessPoints()
{
std::vector<sdbus::ObjectPath> result;
proxy_.callMethod("GetAccessPoints").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
std::vector<sdbus::ObjectPath> GetAllAccessPoints()
{
std::vector<sdbus::ObjectPath> result;
proxy_.callMethod("GetAllAccessPoints").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
void RequestScan(const std::map<std::string, sdbus::Variant>& options)
{
proxy_.callMethod("RequestScan").onInterface(INTERFACE_NAME).withArguments(options);
}
public:
std::string HwAddress()
{
return proxy_.getProperty("HwAddress").onInterface(INTERFACE_NAME);
}
std::string PermHwAddress()
{
return proxy_.getProperty("PermHwAddress").onInterface(INTERFACE_NAME);
}
uint32_t Mode()
{
return proxy_.getProperty("Mode").onInterface(INTERFACE_NAME);
}
uint32_t Bitrate()
{
return proxy_.getProperty("Bitrate").onInterface(INTERFACE_NAME);
}
std::vector<sdbus::ObjectPath> AccessPoints()
{
return proxy_.getProperty("AccessPoints").onInterface(INTERFACE_NAME);
}
sdbus::ObjectPath ActiveAccessPoint()
{
return proxy_.getProperty("ActiveAccessPoint").onInterface(INTERFACE_NAME);
}
uint32_t WirelessCapabilities()
{
return proxy_.getProperty("WirelessCapabilities").onInterface(INTERFACE_NAME);
}
int64_t LastScan()
{
return proxy_.getProperty("LastScan").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy& proxy_;
};
}}}} // namespaces
#endif
@@ -0,0 +1,233 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_Device_hpp__proxy__H__
#define __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_Device_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
namespace NetworkManager {
class Device_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.Device";
protected:
Device_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
proxy_.uponSignal("StateChanged").onInterface(INTERFACE_NAME).call([this](const uint32_t& new_state, const uint32_t& old_state, const uint32_t& reason){ this->onStateChanged(new_state, old_state, reason); });
}
~Device_proxy() = default;
virtual void onStateChanged(const uint32_t& new_state, const uint32_t& old_state, const uint32_t& reason) = 0;
public:
void Reapply(const std::map<std::string, std::map<std::string, sdbus::Variant>>& connection, const uint64_t& version_id, const uint32_t& flags)
{
proxy_.callMethod("Reapply").onInterface(INTERFACE_NAME).withArguments(connection, version_id, flags);
}
std::tuple<std::map<std::string, std::map<std::string, sdbus::Variant>>, uint64_t> GetAppliedConnection(const uint32_t& flags)
{
std::tuple<std::map<std::string, std::map<std::string, sdbus::Variant>>, uint64_t> result;
proxy_.callMethod("GetAppliedConnection").onInterface(INTERFACE_NAME).withArguments(flags).storeResultsTo(result);
return result;
}
void Disconnect()
{
proxy_.callMethod("Disconnect").onInterface(INTERFACE_NAME);
}
void Delete()
{
proxy_.callMethod("Delete").onInterface(INTERFACE_NAME);
}
public:
std::string Udi()
{
return proxy_.getProperty("Udi").onInterface(INTERFACE_NAME);
}
std::string Path()
{
return proxy_.getProperty("Path").onInterface(INTERFACE_NAME);
}
std::string Interface()
{
return proxy_.getProperty("Interface").onInterface(INTERFACE_NAME);
}
std::string IpInterface()
{
return proxy_.getProperty("IpInterface").onInterface(INTERFACE_NAME);
}
std::string Driver()
{
return proxy_.getProperty("Driver").onInterface(INTERFACE_NAME);
}
std::string DriverVersion()
{
return proxy_.getProperty("DriverVersion").onInterface(INTERFACE_NAME);
}
std::string FirmwareVersion()
{
return proxy_.getProperty("FirmwareVersion").onInterface(INTERFACE_NAME);
}
uint32_t Capabilities()
{
return proxy_.getProperty("Capabilities").onInterface(INTERFACE_NAME);
}
uint32_t Ip4Address()
{
return proxy_.getProperty("Ip4Address").onInterface(INTERFACE_NAME);
}
uint32_t State()
{
return proxy_.getProperty("State").onInterface(INTERFACE_NAME);
}
sdbus::Struct<uint32_t, uint32_t> StateReason()
{
return proxy_.getProperty("StateReason").onInterface(INTERFACE_NAME);
}
sdbus::ObjectPath ActiveConnection()
{
return proxy_.getProperty("ActiveConnection").onInterface(INTERFACE_NAME);
}
sdbus::ObjectPath Ip4Config()
{
return proxy_.getProperty("Ip4Config").onInterface(INTERFACE_NAME);
}
sdbus::ObjectPath Dhcp4Config()
{
return proxy_.getProperty("Dhcp4Config").onInterface(INTERFACE_NAME);
}
sdbus::ObjectPath Ip6Config()
{
return proxy_.getProperty("Ip6Config").onInterface(INTERFACE_NAME);
}
sdbus::ObjectPath Dhcp6Config()
{
return proxy_.getProperty("Dhcp6Config").onInterface(INTERFACE_NAME);
}
bool Managed()
{
return proxy_.getProperty("Managed").onInterface(INTERFACE_NAME);
}
void Managed(const bool& value)
{
proxy_.setProperty("Managed").onInterface(INTERFACE_NAME).toValue(value);
}
bool Autoconnect()
{
return proxy_.getProperty("Autoconnect").onInterface(INTERFACE_NAME);
}
void Autoconnect(const bool& value)
{
proxy_.setProperty("Autoconnect").onInterface(INTERFACE_NAME).toValue(value);
}
bool FirmwareMissing()
{
return proxy_.getProperty("FirmwareMissing").onInterface(INTERFACE_NAME);
}
bool NmPluginMissing()
{
return proxy_.getProperty("NmPluginMissing").onInterface(INTERFACE_NAME);
}
uint32_t DeviceType()
{
return proxy_.getProperty("DeviceType").onInterface(INTERFACE_NAME);
}
std::vector<sdbus::ObjectPath> AvailableConnections()
{
return proxy_.getProperty("AvailableConnections").onInterface(INTERFACE_NAME);
}
std::string PhysicalPortId()
{
return proxy_.getProperty("PhysicalPortId").onInterface(INTERFACE_NAME);
}
uint32_t Mtu()
{
return proxy_.getProperty("Mtu").onInterface(INTERFACE_NAME);
}
uint32_t Metered()
{
return proxy_.getProperty("Metered").onInterface(INTERFACE_NAME);
}
std::vector<std::map<std::string, sdbus::Variant>> LldpNeighbors()
{
return proxy_.getProperty("LldpNeighbors").onInterface(INTERFACE_NAME);
}
bool Real()
{
return proxy_.getProperty("Real").onInterface(INTERFACE_NAME);
}
uint32_t Ip4Connectivity()
{
return proxy_.getProperty("Ip4Connectivity").onInterface(INTERFACE_NAME);
}
uint32_t Ip6Connectivity()
{
return proxy_.getProperty("Ip6Connectivity").onInterface(INTERFACE_NAME);
}
uint32_t InterfaceFlags()
{
return proxy_.getProperty("InterfaceFlags").onInterface(INTERFACE_NAME);
}
std::string HwAddress()
{
return proxy_.getProperty("HwAddress").onInterface(INTERFACE_NAME);
}
std::vector<sdbus::ObjectPath> Ports()
{
return proxy_.getProperty("Ports").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy& proxy_;
};
}}} // namespaces
#endif
@@ -0,0 +1,52 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_DnsManager_hpp__proxy__H__
#define __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_DnsManager_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
namespace NetworkManager {
class DnsManager_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.DnsManager";
protected:
DnsManager_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
}
~DnsManager_proxy() = default;
public:
std::string Mode()
{
return proxy_.getProperty("Mode").onInterface(INTERFACE_NAME);
}
std::string RcManager()
{
return proxy_.getProperty("RcManager").onInterface(INTERFACE_NAME);
}
std::vector<std::map<std::string, sdbus::Variant>> Configuration()
{
return proxy_.getProperty("Configuration").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy& proxy_;
};
}}} // namespaces
#endif
@@ -0,0 +1,102 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_IP4Config_hpp__proxy__H__
#define __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_IP4Config_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
namespace NetworkManager {
class IP4Config_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.IP4Config";
protected:
IP4Config_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
}
~IP4Config_proxy() = default;
public:
std::vector<std::vector<uint32_t>> Addresses()
{
return proxy_.getProperty("Addresses").onInterface(INTERFACE_NAME);
}
std::vector<std::map<std::string, sdbus::Variant>> AddressData()
{
return proxy_.getProperty("AddressData").onInterface(INTERFACE_NAME);
}
std::string Gateway()
{
return proxy_.getProperty("Gateway").onInterface(INTERFACE_NAME);
}
std::vector<std::vector<uint32_t>> Routes()
{
return proxy_.getProperty("Routes").onInterface(INTERFACE_NAME);
}
std::vector<std::map<std::string, sdbus::Variant>> RouteData()
{
return proxy_.getProperty("RouteData").onInterface(INTERFACE_NAME);
}
std::vector<uint32_t> Nameservers()
{
return proxy_.getProperty("Nameservers").onInterface(INTERFACE_NAME);
}
std::vector<std::map<std::string, sdbus::Variant>> NameserverData()
{
return proxy_.getProperty("NameserverData").onInterface(INTERFACE_NAME);
}
std::vector<std::string> Domains()
{
return proxy_.getProperty("Domains").onInterface(INTERFACE_NAME);
}
std::vector<std::string> Searches()
{
return proxy_.getProperty("Searches").onInterface(INTERFACE_NAME);
}
std::vector<std::string> DnsOptions()
{
return proxy_.getProperty("DnsOptions").onInterface(INTERFACE_NAME);
}
int32_t DnsPriority()
{
return proxy_.getProperty("DnsPriority").onInterface(INTERFACE_NAME);
}
std::vector<uint32_t> WinsServers()
{
return proxy_.getProperty("WinsServers").onInterface(INTERFACE_NAME);
}
std::vector<std::string> WinsServerData()
{
return proxy_.getProperty("WinsServerData").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy& proxy_;
};
}}} // namespaces
#endif
@@ -0,0 +1,87 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_IP6Config_hpp__proxy__H__
#define __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_IP6Config_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
namespace NetworkManager {
class IP6Config_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.IP6Config";
protected:
IP6Config_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
}
~IP6Config_proxy() = default;
public:
std::vector<sdbus::Struct<std::vector<uint8_t>, uint32_t, std::vector<uint8_t>>> Addresses()
{
return proxy_.getProperty("Addresses").onInterface(INTERFACE_NAME);
}
std::vector<std::map<std::string, sdbus::Variant>> AddressData()
{
return proxy_.getProperty("AddressData").onInterface(INTERFACE_NAME);
}
std::string Gateway()
{
return proxy_.getProperty("Gateway").onInterface(INTERFACE_NAME);
}
std::vector<sdbus::Struct<std::vector<uint8_t>, uint32_t, std::vector<uint8_t>, uint32_t>> Routes()
{
return proxy_.getProperty("Routes").onInterface(INTERFACE_NAME);
}
std::vector<std::map<std::string, sdbus::Variant>> RouteData()
{
return proxy_.getProperty("RouteData").onInterface(INTERFACE_NAME);
}
std::vector<std::vector<uint8_t>> Nameservers()
{
return proxy_.getProperty("Nameservers").onInterface(INTERFACE_NAME);
}
std::vector<std::string> Domains()
{
return proxy_.getProperty("Domains").onInterface(INTERFACE_NAME);
}
std::vector<std::string> Searches()
{
return proxy_.getProperty("Searches").onInterface(INTERFACE_NAME);
}
std::vector<std::string> DnsOptions()
{
return proxy_.getProperty("DnsOptions").onInterface(INTERFACE_NAME);
}
int32_t DnsPriority()
{
return proxy_.getProperty("DnsPriority").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy& proxy_;
};
}}} // namespaces
#endif
@@ -0,0 +1,105 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_Settings_Connection_hpp__proxy__H__
#define __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_Settings_Connection_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
namespace NetworkManager {
namespace Settings {
class Connection_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.Settings.Connection";
protected:
Connection_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
proxy_.uponSignal("Updated").onInterface(INTERFACE_NAME).call([this](){ this->onUpdated(); });
proxy_.uponSignal("Removed").onInterface(INTERFACE_NAME).call([this](){ this->onRemoved(); });
}
~Connection_proxy() = default;
virtual void onUpdated() = 0;
virtual void onRemoved() = 0;
public:
void Update(const std::map<std::string, std::map<std::string, sdbus::Variant>>& properties)
{
proxy_.callMethod("Update").onInterface(INTERFACE_NAME).withArguments(properties);
}
void UpdateUnsaved(const std::map<std::string, std::map<std::string, sdbus::Variant>>& properties)
{
proxy_.callMethod("UpdateUnsaved").onInterface(INTERFACE_NAME).withArguments(properties);
}
void Delete()
{
proxy_.callMethod("Delete").onInterface(INTERFACE_NAME);
}
std::map<std::string, std::map<std::string, sdbus::Variant>> GetSettings()
{
std::map<std::string, std::map<std::string, sdbus::Variant>> result;
proxy_.callMethod("GetSettings").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
std::map<std::string, std::map<std::string, sdbus::Variant>> GetSecrets(const std::string& setting_name)
{
std::map<std::string, std::map<std::string, sdbus::Variant>> result;
proxy_.callMethod("GetSecrets").onInterface(INTERFACE_NAME).withArguments(setting_name).storeResultsTo(result);
return result;
}
void ClearSecrets()
{
proxy_.callMethod("ClearSecrets").onInterface(INTERFACE_NAME);
}
void Save()
{
proxy_.callMethod("Save").onInterface(INTERFACE_NAME);
}
std::map<std::string, sdbus::Variant> Update2(const std::map<std::string, std::map<std::string, sdbus::Variant>>& settings, const uint32_t& flags, const std::map<std::string, sdbus::Variant>& args)
{
std::map<std::string, sdbus::Variant> result;
proxy_.callMethod("Update2").onInterface(INTERFACE_NAME).withArguments(settings, flags, args).storeResultsTo(result);
return result;
}
public:
bool Unsaved()
{
return proxy_.getProperty("Unsaved").onInterface(INTERFACE_NAME);
}
uint32_t Flags()
{
return proxy_.getProperty("Flags").onInterface(INTERFACE_NAME);
}
std::string Filename()
{
return proxy_.getProperty("Filename").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy& proxy_;
};
}}}} // namespaces
#endif
@@ -0,0 +1,112 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_Settings_hpp__proxy__H__
#define __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_Settings_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
namespace NetworkManager {
class Settings_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.Settings";
protected:
Settings_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
proxy_.uponSignal("NewConnection").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& connection){ this->onNewConnection(connection); });
proxy_.uponSignal("ConnectionRemoved").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& connection){ this->onConnectionRemoved(connection); });
}
~Settings_proxy() = default;
virtual void onNewConnection(const sdbus::ObjectPath& connection) = 0;
virtual void onConnectionRemoved(const sdbus::ObjectPath& connection) = 0;
public:
std::vector<sdbus::ObjectPath> ListConnections()
{
std::vector<sdbus::ObjectPath> result;
proxy_.callMethod("ListConnections").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
sdbus::ObjectPath GetConnectionByUuid(const std::string& uuid)
{
sdbus::ObjectPath result;
proxy_.callMethod("GetConnectionByUuid").onInterface(INTERFACE_NAME).withArguments(uuid).storeResultsTo(result);
return result;
}
sdbus::ObjectPath AddConnection(const std::map<std::string, std::map<std::string, sdbus::Variant>>& connection)
{
sdbus::ObjectPath result;
proxy_.callMethod("AddConnection").onInterface(INTERFACE_NAME).withArguments(connection).storeResultsTo(result);
return result;
}
sdbus::ObjectPath AddConnectionUnsaved(const std::map<std::string, std::map<std::string, sdbus::Variant>>& connection)
{
sdbus::ObjectPath result;
proxy_.callMethod("AddConnectionUnsaved").onInterface(INTERFACE_NAME).withArguments(connection).storeResultsTo(result);
return result;
}
std::tuple<sdbus::ObjectPath, std::map<std::string, sdbus::Variant>> AddConnection2(const std::map<std::string, std::map<std::string, sdbus::Variant>>& settings, const uint32_t& flags, const std::map<std::string, sdbus::Variant>& args)
{
std::tuple<sdbus::ObjectPath, std::map<std::string, sdbus::Variant>> result;
proxy_.callMethod("AddConnection2").onInterface(INTERFACE_NAME).withArguments(settings, flags, args).storeResultsTo(result);
return result;
}
std::tuple<bool, std::vector<std::string>> LoadConnections(const std::vector<std::string>& filenames)
{
std::tuple<bool, std::vector<std::string>> result;
proxy_.callMethod("LoadConnections").onInterface(INTERFACE_NAME).withArguments(filenames).storeResultsTo(result);
return result;
}
bool ReloadConnections()
{
bool result;
proxy_.callMethod("ReloadConnections").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
void SaveHostname(const std::string& hostname)
{
proxy_.callMethod("SaveHostname").onInterface(INTERFACE_NAME).withArguments(hostname);
}
public:
std::vector<sdbus::ObjectPath> Connections()
{
return proxy_.getProperty("Connections").onInterface(INTERFACE_NAME);
}
std::string Hostname()
{
return proxy_.getProperty("Hostname").onInterface(INTERFACE_NAME);
}
bool CanModify()
{
return proxy_.getProperty("CanModify").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy& proxy_;
};
}}} // namespaces
#endif
@@ -0,0 +1,87 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_WifiP2PPeer_hpp__proxy__H__
#define __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_WifiP2PPeer_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
namespace NetworkManager {
class WifiP2PPeer_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.WifiP2PPeer";
protected:
WifiP2PPeer_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
}
~WifiP2PPeer_proxy() = default;
public:
std::string Name()
{
return proxy_.getProperty("Name").onInterface(INTERFACE_NAME);
}
uint32_t Flags()
{
return proxy_.getProperty("Flags").onInterface(INTERFACE_NAME);
}
std::string Manufacturer()
{
return proxy_.getProperty("Manufacturer").onInterface(INTERFACE_NAME);
}
std::string Model()
{
return proxy_.getProperty("Model").onInterface(INTERFACE_NAME);
}
std::string ModelNumber()
{
return proxy_.getProperty("ModelNumber").onInterface(INTERFACE_NAME);
}
std::string Serial()
{
return proxy_.getProperty("Serial").onInterface(INTERFACE_NAME);
}
std::vector<uint8_t> WfdIEs()
{
return proxy_.getProperty("WfdIEs").onInterface(INTERFACE_NAME);
}
std::string HwAddress()
{
return proxy_.getProperty("HwAddress").onInterface(INTERFACE_NAME);
}
uint8_t Strength()
{
return proxy_.getProperty("Strength").onInterface(INTERFACE_NAME);
}
int32_t LastSeen()
{
return proxy_.getProperty("LastSeen").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy& proxy_;
};
}}} // namespaces
#endif
@@ -0,0 +1,320 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_hpp__proxy__H__
#define __sdbuscpp__dbus_hpp_org_freedesktop_NetworkManager_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
class NetworkManager_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager";
protected:
NetworkManager_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
proxy_.uponSignal("CheckPermissions").onInterface(INTERFACE_NAME).call([this](){ this->onCheckPermissions(); });
proxy_.uponSignal("StateChanged").onInterface(INTERFACE_NAME).call([this](const uint32_t& state){ this->onStateChanged(state); });
proxy_.uponSignal("DeviceAdded").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& device_path){ this->onDeviceAdded(device_path); });
proxy_.uponSignal("DeviceRemoved").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& device_path){ this->onDeviceRemoved(device_path); });
}
~NetworkManager_proxy() = default;
virtual void onCheckPermissions() = 0;
virtual void onStateChanged(const uint32_t& state) = 0;
virtual void onDeviceAdded(const sdbus::ObjectPath& device_path) = 0;
virtual void onDeviceRemoved(const sdbus::ObjectPath& device_path) = 0;
public:
void Reload(const uint32_t& flags)
{
proxy_.callMethod("Reload").onInterface(INTERFACE_NAME).withArguments(flags);
}
std::vector<sdbus::ObjectPath> GetDevices()
{
std::vector<sdbus::ObjectPath> result;
proxy_.callMethod("GetDevices").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
std::vector<sdbus::ObjectPath> GetAllDevices()
{
std::vector<sdbus::ObjectPath> result;
proxy_.callMethod("GetAllDevices").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
sdbus::ObjectPath GetDeviceByIpIface(const std::string& iface)
{
sdbus::ObjectPath result;
proxy_.callMethod("GetDeviceByIpIface").onInterface(INTERFACE_NAME).withArguments(iface).storeResultsTo(result);
return result;
}
sdbus::ObjectPath ActivateConnection(const sdbus::ObjectPath& connection, const sdbus::ObjectPath& device, const sdbus::ObjectPath& specific_object)
{
sdbus::ObjectPath result;
proxy_.callMethod("ActivateConnection").onInterface(INTERFACE_NAME).withArguments(connection, device, specific_object).storeResultsTo(result);
return result;
}
std::tuple<sdbus::ObjectPath, sdbus::ObjectPath> AddAndActivateConnection(const std::map<std::string, std::map<std::string, sdbus::Variant>>& connection, const sdbus::ObjectPath& device, const sdbus::ObjectPath& specific_object)
{
std::tuple<sdbus::ObjectPath, sdbus::ObjectPath> result;
proxy_.callMethod("AddAndActivateConnection").onInterface(INTERFACE_NAME).withArguments(connection, device, specific_object).storeResultsTo(result);
return result;
}
std::tuple<sdbus::ObjectPath, sdbus::ObjectPath, std::map<std::string, sdbus::Variant>> AddAndActivateConnection2(const std::map<std::string, std::map<std::string, sdbus::Variant>>& connection, const sdbus::ObjectPath& device, const sdbus::ObjectPath& specific_object, const std::map<std::string, sdbus::Variant>& options)
{
std::tuple<sdbus::ObjectPath, sdbus::ObjectPath, std::map<std::string, sdbus::Variant>> result;
proxy_.callMethod("AddAndActivateConnection2").onInterface(INTERFACE_NAME).withArguments(connection, device, specific_object, options).storeResultsTo(result);
return result;
}
void DeactivateConnection(const sdbus::ObjectPath& active_connection)
{
proxy_.callMethod("DeactivateConnection").onInterface(INTERFACE_NAME).withArguments(active_connection);
}
void Sleep(const bool& sleep)
{
proxy_.callMethod("Sleep").onInterface(INTERFACE_NAME).withArguments(sleep);
}
void Enable(const bool& enable)
{
proxy_.callMethod("Enable").onInterface(INTERFACE_NAME).withArguments(enable);
}
std::map<std::string, std::string> GetPermissions()
{
std::map<std::string, std::string> result;
proxy_.callMethod("GetPermissions").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
void SetLogging(const std::string& level, const std::string& domains)
{
proxy_.callMethod("SetLogging").onInterface(INTERFACE_NAME).withArguments(level, domains);
}
std::tuple<std::string, std::string> GetLogging()
{
std::tuple<std::string, std::string> result;
proxy_.callMethod("GetLogging").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
uint32_t CheckConnectivity()
{
uint32_t result;
proxy_.callMethod("CheckConnectivity").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
uint32_t state()
{
uint32_t result;
proxy_.callMethod("state").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
sdbus::ObjectPath CheckpointCreate(const std::vector<sdbus::ObjectPath>& devices, const uint32_t& rollback_timeout, const uint32_t& flags)
{
sdbus::ObjectPath result;
proxy_.callMethod("CheckpointCreate").onInterface(INTERFACE_NAME).withArguments(devices, rollback_timeout, flags).storeResultsTo(result);
return result;
}
void CheckpointDestroy(const sdbus::ObjectPath& checkpoint)
{
proxy_.callMethod("CheckpointDestroy").onInterface(INTERFACE_NAME).withArguments(checkpoint);
}
std::map<std::string, uint32_t> CheckpointRollback(const sdbus::ObjectPath& checkpoint)
{
std::map<std::string, uint32_t> result;
proxy_.callMethod("CheckpointRollback").onInterface(INTERFACE_NAME).withArguments(checkpoint).storeResultsTo(result);
return result;
}
void CheckpointAdjustRollbackTimeout(const sdbus::ObjectPath& checkpoint, const uint32_t& add_timeout)
{
proxy_.callMethod("CheckpointAdjustRollbackTimeout").onInterface(INTERFACE_NAME).withArguments(checkpoint, add_timeout);
}
public:
std::vector<sdbus::ObjectPath> Devices()
{
return proxy_.getProperty("Devices").onInterface(INTERFACE_NAME);
}
std::vector<sdbus::ObjectPath> AllDevices()
{
return proxy_.getProperty("AllDevices").onInterface(INTERFACE_NAME);
}
std::vector<sdbus::ObjectPath> Checkpoints()
{
return proxy_.getProperty("Checkpoints").onInterface(INTERFACE_NAME);
}
bool NetworkingEnabled()
{
return proxy_.getProperty("NetworkingEnabled").onInterface(INTERFACE_NAME);
}
bool WirelessEnabled()
{
return proxy_.getProperty("WirelessEnabled").onInterface(INTERFACE_NAME);
}
void WirelessEnabled(const bool& value)
{
proxy_.setProperty("WirelessEnabled").onInterface(INTERFACE_NAME).toValue(value);
}
bool WirelessHardwareEnabled()
{
return proxy_.getProperty("WirelessHardwareEnabled").onInterface(INTERFACE_NAME);
}
bool WwanEnabled()
{
return proxy_.getProperty("WwanEnabled").onInterface(INTERFACE_NAME);
}
void WwanEnabled(const bool& value)
{
proxy_.setProperty("WwanEnabled").onInterface(INTERFACE_NAME).toValue(value);
}
bool WwanHardwareEnabled()
{
return proxy_.getProperty("WwanHardwareEnabled").onInterface(INTERFACE_NAME);
}
bool WimaxEnabled()
{
return proxy_.getProperty("WimaxEnabled").onInterface(INTERFACE_NAME);
}
void WimaxEnabled(const bool& value)
{
proxy_.setProperty("WimaxEnabled").onInterface(INTERFACE_NAME).toValue(value);
}
bool WimaxHardwareEnabled()
{
return proxy_.getProperty("WimaxHardwareEnabled").onInterface(INTERFACE_NAME);
}
uint32_t RadioFlags()
{
return proxy_.getProperty("RadioFlags").onInterface(INTERFACE_NAME);
}
std::vector<sdbus::ObjectPath> ActiveConnections()
{
return proxy_.getProperty("ActiveConnections").onInterface(INTERFACE_NAME);
}
sdbus::ObjectPath PrimaryConnection()
{
return proxy_.getProperty("PrimaryConnection").onInterface(INTERFACE_NAME);
}
std::string PrimaryConnectionType()
{
return proxy_.getProperty("PrimaryConnectionType").onInterface(INTERFACE_NAME);
}
uint32_t Metered()
{
return proxy_.getProperty("Metered").onInterface(INTERFACE_NAME);
}
sdbus::ObjectPath ActivatingConnection()
{
return proxy_.getProperty("ActivatingConnection").onInterface(INTERFACE_NAME);
}
bool Startup()
{
return proxy_.getProperty("Startup").onInterface(INTERFACE_NAME);
}
std::string Version()
{
return proxy_.getProperty("Version").onInterface(INTERFACE_NAME);
}
std::vector<uint32_t> VersionInfo()
{
return proxy_.getProperty("VersionInfo").onInterface(INTERFACE_NAME);
}
std::vector<uint32_t> Capabilities()
{
return proxy_.getProperty("Capabilities").onInterface(INTERFACE_NAME);
}
uint32_t State()
{
return proxy_.getProperty("State").onInterface(INTERFACE_NAME);
}
uint32_t Connectivity()
{
return proxy_.getProperty("Connectivity").onInterface(INTERFACE_NAME);
}
bool ConnectivityCheckAvailable()
{
return proxy_.getProperty("ConnectivityCheckAvailable").onInterface(INTERFACE_NAME);
}
bool ConnectivityCheckEnabled()
{
return proxy_.getProperty("ConnectivityCheckEnabled").onInterface(INTERFACE_NAME);
}
void ConnectivityCheckEnabled(const bool& value)
{
proxy_.setProperty("ConnectivityCheckEnabled").onInterface(INTERFACE_NAME).toValue(value);
}
std::string ConnectivityCheckUri()
{
return proxy_.getProperty("ConnectivityCheckUri").onInterface(INTERFACE_NAME);
}
std::map<std::string, sdbus::Variant> GlobalDnsConfiguration()
{
return proxy_.getProperty("GlobalDnsConfiguration").onInterface(INTERFACE_NAME);
}
void GlobalDnsConfiguration(const std::map<std::string, sdbus::Variant>& value)
{
proxy_.setProperty("GlobalDnsConfiguration").onInterface(INTERFACE_NAME).toValue(value);
}
private:
sdbus::IProxy& proxy_;
};
}} // namespaces
#endif
+43
View File
@@ -33,6 +33,7 @@
#include <concepts>
#include <limits>
#include <stdexcept>
#include <chrono>
#define DECLARE_JSON_MAP(CLASSNAME) \
static pipedal::json_map::storage_type<CLASSNAME> jmap
@@ -327,6 +328,22 @@ namespace pipedal
bool allowNaN() const { return allowNaN_; }
void allowNaN(bool allow) { allowNaN_ = allow; }
void write(uint8_t value)
{
os << value;
}
void write(int8_t value)
{
os << value;
}
void write(short value)
{
os << value;
}
void write(unsigned short value)
{
os << value;
}
void write(long long value)
{
os << value;
@@ -351,6 +368,7 @@ namespace pipedal
{
os << value;
}
void write (const std::chrono::system_clock::time_point &time);
private:
static void throw_encoding_error();
@@ -882,6 +900,29 @@ namespace pipedal
skip_whitespace();
*value = read_boolean();
}
void read(uint8_t*value)
{
skip_whitespace();
is_ >> *value;
if (is_.fail())
throw JsonException("Invalid format.");
}
void read(short * value)
{
skip_whitespace();
is_ >> *value;
if (is_.fail())
throw JsonException("Invalid format.");
}
void read(unsigned short * value)
{
skip_whitespace();
is_ >> *value;
if (is_.fail())
throw JsonException("Invalid format.");
}
void read(int *value)
{
skip_whitespace();
@@ -956,6 +997,8 @@ namespace pipedal
if (is_.fail())
throw JsonException("Invalid format.");
}
void read(std::chrono::system_clock::time_point *value);
template <typename T>
void read(std::vector<T> *value)
{
+3
View File
@@ -35,4 +35,7 @@ namespace pipedal {
std::u32string ToUtf32(const std::string &s);
inline bool isPathSeperator(char c) { return c == '/' || c == std::filesystem::path::preferred_separator;}
std::string GetHostName();
}
+54
View File
@@ -632,6 +632,60 @@ void json_reader::throw_format_error(const char*error)
}
static std::string timePointToISO8601(const std::chrono::system_clock::time_point &tp)
{
auto tt = std::chrono::system_clock::to_time_t(tp);
std::tm tm = *std::gmtime(&tt);
std::stringstream ss;
ss << std::put_time(&tm,"%Y-%m-%dT%H:%M:%S") << "Z";
return ss.str();
}
static std::chrono::system_clock::time_point ISO8601ToTimePoint(const std::string&timeString)
{
if (timeString.empty())
{
return std::chrono::system_clock::time_point();
}
if (timeString.back() != 'Z')
{
throw std::runtime_error("Time string is not in UTC Z timezone. Time-zones are not supported.");
}
std::tm tm = {};
std::istringstream ss(timeString);
ss >> std::get_time(&tm,"%Y-%m-%dT%H:%M:%S");
if (ss.fail())
{
throw std::runtime_error("Failed to parse ISO 8601 time string.");
}
time_t tt;
#ifdef _WIN32
tt = _mkgmtime(&tm);
#else
tt = timegm(&tm);
#endif
if (tt == -1)
{
throw std::runtime_error("Failed to conver ISO 8601 time string.");
}
return std::chrono::system_clock::from_time_t(tt);
}
void json_writer::write (const std::chrono::system_clock::time_point &time)
{
std::string timeString = timePointToISO8601(time);
write(timeString);
}
void json_reader::read(std::chrono::system_clock::time_point *value)
{
std::string timeString;
read(&timeString);
*value = ISO8601ToTimePoint(timeString);
}
// void json_writer::write(const json_variant &value)
// {
+12
View File
@@ -100,3 +100,15 @@ std::u32string pipedal::ToUtf32(const std::string &s)
}
return result.str();
}
std::string pipedal::GetHostName()
{
char buffer[1024];
if (gethostname(buffer,1024) != 0)
{
buffer[0] = '\0';
}
buffer[1023] = '\0';
return buffer;
}
+3 -3
View File
@@ -6,13 +6,13 @@
<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:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.2.47</a>
Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.2.51</a>
Website:&nbsp;[https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
Documentation:&nbsp;[https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html).
&nbsp;
#### NEW version 1.2.47 Release, providing support for Raspberry Pi OS Bookworm. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
#### NEW version 1.2.51 Release, providing support for Raspberry Pi OS Bookworm. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
&nbsp;
@@ -21,7 +21,7 @@ PiPedal running on a Raspberry Pi 4 or Pi 5 provides stable super-low-latency au
PiPedal's user interface has been specifically designed to work well on small form-factor touch devices like phones or tablets. Clip a phone or tablet on your microphone stand on stage, and you're ready to play! Or connect via a desktop browser, for a slightly more luxurious experience. The PiPedal user-interface adapts to the screen size and orientation of your device, providing easy control of your guitar effects across a broad variety devices and screen sizes.
Install the [PiPedal Remote Android app](https://play.google.com/store/apps/details?id=com.twoplay.pipedal) to get one-click access to PiPedal via Wi-Fi Direct. Or connect from PC or Laptop via a web browser. PiPedal provides a simple configuration tool that allows you to set up the Wi-Fi Direct access point on your Raspberry Pi.
Install the [PiPedal Remote Android app](https://play.google.com/store/apps/details?id=com.twoplay.pipedal) to get one-click access to PiPedal via Wi-Fi or via the PiPedal Wi-Fi hotspot. Or connect from PC or Laptop via a web browser. PiPedal provides a simple configuration tool that allows you to set up the PiPedal's Wi-Fi hotspot access point on your Raspberry Pi.
PiPedal includes a pre-installed selection of LV2 plugins from the ToobAmp collection of plugins; but it works with most LV2 Audio plugins. There are literally hundreds of free high-quality LV2 audio plugins that will work with PiPedal. Just install them on your Raspberry Pi, and they will show up in PiPedal.
Binary file not shown.
+88 -34
View File
@@ -10,7 +10,7 @@ Before using PiPedal, you will need to configure settings for the audio device t
{% include pageIconL.html %}
You will also want to configure PiPedal to provide a Wi-Fi Direct access point (hotspot) that you can connect to using your phone. It's fine to use your home Wi-Fi network to connect to PiPedal when you're at home; but don't forget that when you take PiPedal out to a gig, you will need to ensure that the Wi-Fi Direct access point is enabled before you do.
You will also want to configure PiPedal to provide a Wi-Fi auto-hotspot so that you can connect to using your using your Android phone or tablet. It's fine to use your home Wi-Fi network to connect to PiPedal when you're at home; but don't forget that when you take PiPedal out to a gig, you will need to ensure that PiPedal's Wi-Fi auto-hotspot is enabled before you do.
PiPedal uses LV2 audio plugins. There are literally thousands of freely available high-quality LV2 plugins that are suitable for use as guitar effects.
@@ -21,8 +21,31 @@ with PiPedal.
### Installing the PiPedal Remote Android app
Install [PiPedal Remote](https://play.google.com/store/apps/details?id=com.twoplay.pipedal) on your Android device. PiPepedal Remote can connect to
PiPedal on your Raspberry Pi device as long as your Android device and Raspberry Pi are connected to the same network via a normal Wi-Fi access point, or via an Ethernet connection.
If you are using an Android phone or table to connect to PiPedal, you should download and install the [PiPedal Android Client](https://play.google.com/store/apps/details?id=com.twoplay.pipedal)
The Android PiPedal client provides three feature:
1. It automatically locates running instances of PiPedal on the local Wi-Fi network using dns/Bonjour discovery. So you don't need to keep track of which IP address to connect with.
2. It provides a web view of PiPedal user interface with none of the clutter associated with a web browser. No browser address bars; no additional window clutter from the browser. Just a view of the PiPedal UI. So you get to use every precious pixel of your display to control PiPedal.
3. It happens to interoperate nicely with PiPedal's auto-hotspot feature, allowing one-click connection to PiPedal even when you're away from home and don't have access to a Wi-Fi router.
In theory, you could do all of this using Chrome, running on your phone. And, in fact, you can. But it's a fiddly procedure. The Android PiPedal Client takes care of all of those details, and allows you to connect to PiPedal from your phone or tablet with just one click to launch the app.
The PiPedal client will connect to the PiPedal server running on your Raspberry Pi automatically if they are both connected to the same Wi-Fi router. When you are away from home, you should configure PiPedal to automatically bring up a Wi-Fi hotspot when the Raspberry Pi is not able to connect to your home network.
When you do, you can easily connect to PiPedal via the Android app.
- You show up at a local venue to perform.
- You power up your Raspberry pi. The PiPedal server boots up, and decides that it is not able to connect to your home network, so it starts up a Wi-Fi hotspot.
- Your phone or tablet doesn't have a Wi-Fi connection, because you're not at home. But when it sees the PiPedal hotspot it will automatically connect.
- You launch the PiPedal client on your Android device. It searches the network associated with the current active Wi-Fi connection (the PiPedal hotspot, in this instance), and finds a PiPedal server announcing itself via mDNS/Bonjour. And having located the pipedal server, it automatically opens up a Web View and connects to the PiPedal web user interface.
None of this is particularly clever. But it does make things simple.
## First Connection
@@ -30,14 +53,16 @@ You can complete the initial configuration procedure using any of the following
1. _Log on to the Raspberry Pi device_. Launch a web browser, and connect to http:://127.0.0.1/
2. _Connect Raspberry Pi to a Wi-Fi or Ethernet network, and connect from your laptop or desktop._ Connect to http://raspberrypi.local/ (substitute the host name of your Raspberry Pi if you have changed it from the default).
2. _Connect your Raspberry Pi to a Wi-Fi or Ethernet network, and connect from your laptop or desktop._ Connect to http://raspberrypi/ (substitute the host name of your Raspberry Pi if you have changed it from the default).
3. _From your Android device._ Install PiPedal Remote on your Android device. Connect your Raspberry Pi to your home router using a Wi-Fi or Ethernet connection. Launch PiPedal Remote on your Android device.
4. _From a LINUX command prompt._ As a last resort, you can configure the Wi-Fi Direct access point using the `pipedalconfig` program. Type `pipedalconfig --help` at a command prompt on your Raspberry Pi.
4. _From a LINUX command prompt._ As a last resort, you can configure the Wi-Fi hotspotusing the `pipedalconfig` program. Type `pipedalconfig --help` at a command prompt on your Raspberry Pi.
If you already have another web server on port 80, see [*How to Change the Web Server Port*](ChangingTheWebServerPort.md).
When you connect to PiPedal for the first time, you will be presented with an Onboarding dialog, which allows you to complete the setup of things that need to be configured before you use PiPedal. From the Onboarding dialog, you can select and configure the audio device, and (optionally) configure Pipedal's Wi-Fi auto hotspot.
### Configuring Audio
Once connected, select the Settings menu item on the Hamburger menu at the top left corner of the display. Click on Audio Device Settings to select and configure the audio device you want to use.
@@ -70,47 +95,76 @@ LINUX Kernel version 5.16 includes fixes to ALSA audio that are supposed to dram
PiPedal uses the ALSA audio stack; so (unlike Jack Audio) there is no performance penalty for using 44100Hz sample rates. However, using a 48000Hz sampling rates does provide significant improvements in high-frequency audio quality when performing digital audio signal processing.
### Activating the P2P (Wi-Fi Direct) Hotspot
### Activating the Wi-Fi Auto-Hotspot
The preferred way to connect to a PiPedal host device is via Wi-Fi Direct connections. Doing so allows you to use your PiPedal device when you are away from home.
Enable PiPedal's Wi-Fi Direct hotspot connection from the Settings dialog (Click on the hamburger icon; click on *Settings*; click on *Wi-Fi Direct Hotspot*). You will need to select a PIN and enable the connection before you can use it. The PiPedal hotspot will be visible to anyone with Wi-Fi distance; so choose your PIN carefully. Do NOT use trivial PINs like 12345678, or 00000000! Note that when connecting via Wi-Fi direct, you only have to enter the PIN once on the device you are connecting from; and it will be remembered automatically for subsequent connections.
The PiPedal <b><i>Auto-Hotspot</i></b> feature allows you to connect to your Raspberry Pi even if you don't have
access to a Wi-Fi router. For example, if you are performing at a live venue, you probably will not
have access to a Wi-Fi router; but you can configure PiPedal so that your Raspberry Pi automatically
starts a Wi-Fi hotspot when you are not at home. The feature is primarily intended for use with the
PiPedal Android client, but you may find it useful for other purposes as well.
Best practice would be to generate a random PIN (using the Generate Random Pin button in the setup dialog), and then tape a label to the bottom of your Raspberry PI
(not the top) with the PIN written on it, in case you ever need to connect to PiPedal with a device that hasn't been previously set up.
Wi-Fi Direct connections differ in a couple of ways from normal Wi-Fi connections
- You only have to enter the pin once; the device you connect from will remember the PIN and connect without a PIN after that.
Raspberry Pi devices are unable to run hotspots, and have another active Wi-Fi connection at the same time; so the auto-hotspot feature
automatically turns the hotspot on, when your Raspberry Pi cannot otherwise be connected to, and can be configured to
automatically turn the PiPedal hotspot off when you do want your Raspberry Pi to connect to another Wi-Fi access point.
How you configure PiPedal's auto-hostpot depends on how you normally connect to your Raspberry Pi when you are at home.
- You can have a simultaneous connection to your Wi-Fi router when using a Wi-Fi Direct connection to your Raspberry Pi. The device you
are connecting from will continue use the Internet over your Wi-Fi router, and only use the Wi-Fi Direct connection to communication
with your Raspberry Pi.
To configure Pipedal's Auto-Hotspot, open the Auto-Hotspot configuration dialog. You can access this dialog directly from the Onboarding page; or if
you have completed the onboarding procedure, you can open the dialog from the <b><i>Auto Hotspot</i></b> item in the <b><i>PiPedal Settings</i></b> dialog.
- You can use a Wi-Fi Direct connection even when you don't have a Wi-Fi router connection. Android phones will continue to communicate
with the Internet over their
data connections, even when a Wi-Fi Direct connection is active.
The Auto-Hotspot offers you several choices as to how and PiPedal should open up a Wi-Fi hotspot. Which option you choose depends on how you connect to
PiPedal when you are at home.
- More than one device can connect to the Wi-Fi Direct connection on the Raspberry Pi when it is active/
If you normally connect to your Raspberry Pi using an ethernet connection, starting the Wi-Fi hotspot when there is <b><i>No ethernet connection</i></b> is a
good choice. The PiPedal Wi-Fi hotspot will be available whenever the ethernet cable is unplugged. <b><i>Always on</i></b> is
also a good choice, but may confuse your phone or tablet, since your Android device will now have to decide whether to auto-connect to your home Wi-Fi
router, or to the Raspberry Pi hotspot. If you use the <b><i>No ethernet connection</i></b> option, your phone or tablet will
never see the PiPedal hotspot and your Wi-Fi router at the same time.
- Your Raspberry Pi can also have simultaneous access to a Wi-Fi router access point when the Wi-Fi Direct connection is enabled.
If you normally connect to your Raspberry Pi through a Wi-Fi router, <b><i>Not at home</i></b> is a good choice. The
PiPedal hotspot will be automatically turned off whenever your home Wi-Fi router is in range, and automatically turned on
when you are out of range of your home Wi-Fi router.
- Wi-Fi Direct connections are backward compatible with Wi-Fi access points if you are using an older device. Look for the DIRECT-xx-YourDeviceName access point.
But if you use a legacy connection, the connecting device cannot have a simultanous Wi-Fi router connection.
If there are multiple locations, and multiple Wi-Fi routers you use with PiPedal on a regular basis, you can choose to start the
PiPedal Wi-Fi hotspot when <b><i>No remembered Wi-Fi connections</i></b> are visible, but this is a riskier option. The PiPedal hotspot will be automatically turned on if there are no
Wi-Fi access points in range that you have previously connected to from your Raspberry Pi, and will be automatically turned on otherwise.
The risk is that you could find yourself unable to connect to your Raspberry Pi when performing
at a local bar, after you have used your Rasberry Pi to connect to the Wi-Fi access point at the coffee shop nextdoor. (Public Wi-Fi access
points usually won't work because devices that are connected to a public access point can't connect to each other).
Will you ever do that? Probably not. But there is some risk that you might find yourself unable to connect at a live venue. Whether that's an
acceptable risk is up to you.
If you use the PiPedal Android app, the Android app will manage discovery and setting up of Wi-Fi direct connections to your Raspberry Pi device automatically; but you must complete the initial configuration using a direct web connection first.
PC support for Wi-Fi Direct connections varies dramatically. Modern PCs support Wi-Fi Direct; but you may find it easier to communicate with PiPedal via your Wi-Fi router. Configure PiPedal to use a Wi-Fi connection as well as a Wi-Fi Direct access point (or connect an ethernet cable to your Raspberry Pi). Just remember to configure your phone to use a Wi-Fi Diret connection _before_ you take PiPedal out on a gig, because you won't have a Wi-Fi router then.
Typically, when you're away from home, there's no easy way to connect to your Raspberry Pi from a laptop in order to
correct the problem. So you should carefully test that your auto-hotspot configuration works as expected before you adventure
away from home with PiPedal.
PiPedal advertises the address of the Raspberry Pi host machine via Multi-cast DNS (M-DNS). You should be able to establish web connections to the Raspberry Pi using the address `http://<hostname-of-your-pi>.local/`, no matter how you are connected.
Older devices may not support Wi-Fi Direct connections; but they will be able to use the Wi-Fi Direct connection as an ordinary Wi-Fi Hotspot. But they won't have access to the Internet over your Wi-Fi router while they have a connection with your Raspberry Pi.
### Connecting to PiPedal Using a Laptop when you are Away From Home
You _must_ select the correct country when setting up your Wi-Fi Direct connection. Regulations for use of Wi-Fi vary greatly from coutry to country, and define both the channels you are allowed to use, and the features and signal strength of Wi-Fi connections on those channels.
If your laptop has Wi-Fi support, you can use a laptop to connect to Raspberry Pi when you are away from home.
When you are at home, connect both the Raspberry Pi and your Laptop to the same Wi-Fi router. Out of the box, Raspberry Pi OS is
configured to announce itself via mDNS/Bonjour, and Window and Mac operating systems can both use mDNS/Bonjour name resolution.
Simply launch
http://raspberrypi
(or supply the actual hostname of your Raspberry Pi if you have changed the default hostname).
When you are away from home, you can use Wi-Fi hotspots to connect your laptop and your Raspberry Pi device. You can either configure
your laptop to provide a Wi-Fi hotspot, and then get your Raspberry Pi to connect to the hotspot on your laptop. Raspberry Pi OS will
automatically connect to your laptop hotspot whenever it sees it, after you have connected for the first time, and entered login
credentials on your Raspberry Pi. After the first time, all you need to do is turn on your laptop hotspot, and Raspberry Pi OS will
connect to it.
Usually, you cannect from your laptop using the same web address: http://raspberrpi (or the hostname of your raspberry pi, if you have
changed it). Unlike Android phones (where mDNS name resolution doesn't work on Android-hosted hotspots), mDNS/Bonjour name resolution usually
works on laptop-hosted hotspots when using Windows or Mac OS. If are running Linux on your laptop, you may need to install and configure the
Avahi package to get mDNS/Bonjour name resolution to work.
And if that doesn't work, you can configure PiPedal to launch an auto-hotspot, and then connect from your laptop to the PiPedal hotspot on your Raspberry Pi.
When the Raspberry Pi hosts the hotspot, mDNS discovery is definitely enabled; so you should be able to connect using http://raspberrypi. But if that doesn't work, PiPedal's IP address will always be 10.40.1, when the PiPedal Wi-Fi hotspot is running, so you can always connect using http://10.40.0.1.
For best results, you should select Wi-Fi channel 1, 6 or 11 (referred to as the "Wi-Fi Direct Social Channels"). Doing so reduces the time it takes for other devices to discover the Raspberry Pi. While it is possible to use 5Ghz channels for Wi-Fi Direct, it may take some time for connecting devices to find your PiPedal device.
Support for Apple/IOS devices: a client for Apple/IoS devices is in long-term development plans; but I don't own any Apple device on which to do development and testing. If you'd like to see an Apple/IOS client, your sponsorship would help. (The client performs automatic discovery and set up of Wi-Fi direct connections, and relies on the Web interface after that. Not difficult to implement. Just expensive.)
--------
[<< Installing PiPedal](Installing.md) | [Up](Documentation.md) | | [Choosing a USB Audio Adapter >>](ChoosingAUsbAudioAdapter.md)
+3 -2
View File
@@ -68,8 +68,9 @@ The React app will display the message "Error: Failed to connect to the server",
...
}
Setting socket_server_address to "*" configures the web app to reconnect using the host address the browser
request used to connect to the web app. (e.g. 127.0.0.1, pipedal.local, the address of the Wi-Fi Direct connection &c). If you choose to provide an explicit address, remember that it is to that address that the web browser will connect.
Setting socket_server_address to "*" configures the web app to using the host address of the current
web page. If you set it to a specific address, the web app will attempt to establish a websocket connection
son that specific address instead. (Not sure what that's useful for, but it's there if you ever need it).
The original development for this app was done with Visual Studio Code. Open the root project directory in
Visual Studio Code, and it will detect the CMake build files, and configure itself appropriately. Wait for
+3 -3
View File
@@ -13,17 +13,17 @@ page_icon: img/Install4.jpg
Download the most recent Debian (.deb) package for your platform:
- [Raspberry Pi OS bookworm (64-bit) v1.2.47](https://github.com/rerdavies/pipedal/releases/download/)
- [Raspberry Pi OS bookworm (64-bit) v1.2.51](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)
Version 1.2.47 has not yet been tested on Ubuntu or Raspberry Pi OS bullseye. On these platforms, we recommend that you use version 1.1.31.
Version 1.2.51 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
```
sudo apt update
cd ~/Downloads
sudo apt-get install pipedal_1.2.47_arm64.deb
sudo apt-get install pipedal_1.2.51_arm64.deb
```
Adjust accordingly if you have downloaded v1.1.31.
+35
View File
@@ -1,4 +1,39 @@
# Release Notes
## PiPedal 1.2.51 Release
<b>Important notice:</b> Changes to the Auto-update code prevent previous versions from automatically updating to this version. You will have to install this version by downloading the [debian package](https://rerdavies.github.io/pipedal/download.html) and installing it manually.
This release replaces Wi-Fi Direct connections with Wi-Fi hotspots. Support for Wi-Fi Direct on Linux and Android has been fragile for some time. An update to Raspberry Pi OS in early September broke Wi-Fi Direct support completely. As it turns out, Auto-hotspots work much better.
The new Auto-Hostpot feature in PiPedal allows you to configure your Raspberry Pi so it automatically starts a Wi-Fi hotspot whenever you are away from home. The updated Android PiPedal Client will automatically detect and connect to your Raspberry PI whenever it is visible on the current Wi-Fi network. An updated Android [PiPedal Remote](https://play.google.com/store/apps/details?id=com.twoplay.pipedal&hl=en_US&pli=1) app has been posted on Google Play. Make sure you are using the updated version.
New MIDI system bindings allow you to enable or disable the Wi-Fi hotspot, and to shut down or reboot your Raspberry Pi using MIDI-triggered events.
NOTICE: PiPedal 1.2.47 fixed a significant and dangerous defect that may cause loss of presets, banks or configuration data if you remove power from your Raspberry Pi within up to five minutes of last saving data instead of performing an orderly shutdown or reboot. Users of PiPedal should upgrade to at least version 1.2.47 immediately, if they have not already done so.
Bug fixes:
- PiPedal Remote client theme native pages do not match the selected PiPedal UI theme.
- Unable to configure audio buffers properly for devices with minBufferSize=256.
- Release and Beta updates not visible if the most recent release is a development build.
- Revised auto-update procedure, including a new signing procedure.
- Ongoing improvements to TooB Neural Amp Modeler (bug fixes, performance improvements)
- pipedald service hangs and/or throws exceptions on shutdown.
- Update DNS/SD service announcements when the device name is changed.
- Unannounce DNS/DS services when the pipedal services shuts down.
- Auto-uprade checks may exceed the throttling rate of Github API calls when checking for updates.
- Memory corruption when ALSA device creation fails.
- MIDI input causing audio glitches and underruns.
- Unable to connect when using IPv6 connections and mDNS name resolution.
- Default web port configuration overwritten by upgrades.
- Remove dependencies on old pre-NetworkManager network services required by Wifi-Direct code.
- Refuse to install on versions of Raspberry Pi OS prior to bookworm.
Known issues:
- TooB Neural Amp Modeler runs 5% slower than Mike Oliphant's Neural Amp Modeler, despite running virtually identical code from Steven Atkins' NeuralAmpModelerCore project. Investigation continues.
## PiPedal 1.2.47 Release
This version fixes a significant and dangerous defect that may cause loss of presets, banks or configuration data if you remove power from your Raspberry Pi within up to five minutes of last saving data instead of performing an orderly shutdown or reboot. Users of PiPedal should upgrade immediately.
+2 -4
View File
@@ -4,17 +4,15 @@
Download the most recent Debian (.deb) package for your platform:
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.2.47/pipedal_1.2.47_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.2.47</a>
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.1.31/pipedal_1.1.31_arm64.deb">Ubuntu 21.04 or Raspberry Pi OS bullseyeyeye (64-bit) v1.1.31</a>
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.2.51/pipedal_1.2.51_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.2.51</a>
v1.2.47 is does not currently support Ubuntu 21.04, or older versions of Raspberry Pi OS.
Install the package by running
```
sudo apt update
cd ~/Downloads
sudo apt-get install ./pipedal_1.2.47_arm64.deb
sudo apt-get install ./pipedal_1.2.51_arm64.deb
```
Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation.
+6 -4
View File
@@ -1,7 +1,7 @@
<img src="GithubBanner.png" width="100%"/>
<a href="Installing.html"><i>v1.2.47</i></a>
<a href="Installing.html"><i>v1.2.51</i></a>
&nbsp;
@@ -9,7 +9,7 @@ To download PiPedal, click [here](download.md).
To view PiPedal documentation, click [here](Documentation.md).
#### NEW version 1.2.47 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
#### NEW version 1.2.51 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
&nbsp;
@@ -18,9 +18,11 @@ PiPedal running on a Raspberry Pi 4 or Pi 5 provides stable super-low-latency au
{% include gallery.html %}
PiPedal can be remotely controlled via a web interface over Ethernet, or from your phone or tablet via Wi-Fi Direct. Set up and configure the Wi-Fi Direct interface via the browser interface.
PiPedal can be remotely controlled via a web interface over Ethernet, or Wi-Fi. If you don't have access to a Wi-Fi router, PiPedal can be configured to
start a Wi-Fi hotspot automatically, whenever your Raspberry Pi can't connect to your home network.
Install the [PiPedal Remote Android app](https://play.google.com/store/apps/details?id=com.twoplay.pipedal) to get one-click access to PiPedal via Wi-Fi Direct. Or connect from PC, Laptop or your Raspberry Pi via a web browser over Ethernet. You can set up and configure the Wi-Fi Direct connection from the browser interface.
Install the [PiPedal Remote Android app](https://play.google.com/store/apps/details?id=com.twoplay.pipedal) to get one-click access to PiPedal via Wi-Fi networks, or Wi-Fi hotspots. If you are using PiPedal away from home, you can configure PiPedal to automatically start a Wi-Fi hotspot on your Raspberry Pi
whenever Pipedal is unable to detect your home network. The PiPedal Client Android app will allow to connect by simply launching the app, whether you are at home, or using a Wi-Fi auto-hotspot at a gig, when away from home.
PiPedal's user interface has been specifically designed to work well on small form-factor touch devices like phones or tablets. Clip a phone or tablet on your microphone stand on stage, and you're ready to play! Or connect via a desktop browser, for a slightly more luxurious experience. The PiPedal user-interface adapts to the screen size and orientation of your device, providing easy control of your guitar effects across a broad variety devices and screen sizes.
+594
View File
@@ -0,0 +1,594 @@
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
@prefix epp: <http://lv2plug.in/ns/ext/port-props#> .
@prefix uiext: <http://lv2plug.in/ns/extensions/ui#> .
@prefix idpy: <http://harrisonconsoles.com/lv2/inlinedisplay#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix mod: <http://moddevices.com/ns/mod#> .
@prefix param: <http://lv2plug.in/ns/ext/parameters#> .
@prefix work: <http://lv2plug.in/ns/ext/worker#> .
@prefix pg: <http://lv2plug.in/ns/ext/port-groups#> .
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
@prefix plug: <http://two-play.com/plugins/toob-power-stage-2#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix state: <http://lv2plug.in/ns/ext/state#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix modgui: <http://moddevices.com/ns/modgui#> .
@prefix pstage: <http://two-play.com/plugins/toob-power-stage-2#> .
pstage:stage1
a param:ControlGroup ,
pg:InputGroup ;
lv2:name "Stage 1" ;
lv2:symbol "stage1" .
pstage:stage2
a param:ControlGroup ,
pg:InputGroup ;
lv2:name "Stage 2" ;
lv2:symbol "stage2" .
pstage:stage3
a param:ControlGroup ,
pg:InputGroup ;
lv2:name "Stage 3" ;
lv2:symbol "stage3" .
<http://two-play.com/plugins/toob-power-stage-2#uiState>
a lv2:Parameter ;
rdfs:label "uiState" ;
rdfs:range atom:Vector .
<http://two-play.com/rerdavies#me>
a foaf:Person ;
foaf:name "Robin Davies" ;
foaf:mbox <mailto:rerdavies@gmail.com> ;
foaf:homepage <https://github.com/sponsors/rerdavies> .
<http://two-play.com/plugins/toob-power-stage-2>
a lv2:Plugin ,
lv2:SimulatorPlugin ;
doap:name "TooB Power Stage";
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
doap:maintainer <http://two-play.com/rerdavies#me> ;
lv2:minorVersion 0 ;
lv2:microVersion 47 ;
mod:brand "TooB";
mod:label "Power Stage";
lv2:requiredFeature urid:map ;
lv2:optionalFeature lv2:hardRTCapable;
patch:readable
<http://two-play.com/plugins/toob-power-stage-2#uiState>;
rdfs:comment """
The TooB Power Stage provides flexible and nuanced emulations of guitar amp power stages.
The goal of TooB Power Stage is emulate the best features of real tube amps without slavishly
reproducing the undesirable features as well.
TooB Power Stage provides 3 consecutive gain stages, each of which can be overdriven to varying
degrees. Power transformer sag is emulated using the SAG and SAGD controls, which emulate the
volume and distortion effects introduced when power transformer voltage sags.
Each gain section expects an input level that is nominally in the range of 12db to 0db.
It's ok if the levels are slightly over 0db, but they should not be massively over. Use the
Trim control to adjust input signal levels.
The Gain controls use oversampled atan wave shaping to emulate the effect of overdriving
gain stages. Atan wave shaping provides rich even-order harmonics similar to those produced by a tube
amp power stage. A modest amount of gain will produce the characteristically "warm" sound of a tube
gain stage. More extreme settings produce harder overdrive.
Judicious use of the LO CUT and HI CUT controls have interesting and subtle effects on the overall sound.
You are encouraged to play with these controls to see what happens. Tightening up low and high frequencies,
in early gain sections can allow you to drive the signal much harder in later gain sections without totally
breaking up. Real amps will usually cut low frequency response at somewhere between 80Hz (Fender)
and 140Hz (Marshall).
General experience seems to suggest that gradually increasing the gain in each section allows you to drive
the signal harder without introducing undesirable effects in the output. However, there definitely interesting
and subtle differences in tone that can be acheived by not adhering to that rule of thumb.
The SAG and SAGD controls the emulation of "forgiveness", and "compression" that are provided in a real tube
amp by sagging of power supply voltage. SAG controls how much
the overall volume of the output decreases as the transform sags. It provides the subtle "compression" that
tube amps provide, which is quite different from the compression provided by compressors. SAGD controls
the amount of additional distortion that occurs as the gain stages receive reduced voltage. It provides
the "forgiveness" of a good tube amp. Because SAGD operates in a feedback loop that runs through
all three gain stages. Increasing the distortion in the gain stages increases the power use of the gain stages;
increasing the power use of the gain stages increases the effect of SAG and SAGD which has an opposing effect
on power use. The net result is that SAGD causes signals to break up more gradually
(hence the perceived "forgiveness"). The effects are non-linear, so not easy to describe completely. You
are strongly urged to experiment with SAG and SAGD settings in order to get best tone results. Use very
moderate settings to emulate tube amp output; higher settings of SAG and SAGD will cause audible compression
and pumping of the outputs which may or may not be desireable.
The TooB Power Stage design is based on an experimental design, that unexpectedly produced extraordinarily
good amp tones. We have not completely explored the sonic possibilities of the
TooB Power Stage. What's known so far: it produces exceptionally pretty clean tones; it can be driven really
hard, and seems to preserve an unusual amount of detail in the signal even when being driven hard; and it
can definitely produce signals that have many of the most prized features of tube amplifiers: warmth, detail,
subtle "compression" without coloration, and forgiving break up.
Explore!
The TooB Input Stage is part of the TooB Amp Emulation Toolkit. Typically, you would
use the following plugins in the following order.
- TooB Input Stage
- TooB Tone Stack
- Toob Power Stage
- Toob Cabinet Simulator
""";
lv2:port
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 0 ;
pg:group pstage:stage1 ;
lv2:symbol "trim1" ;
lv2:name "Trim";
lv2:default 0.0 ;
lv2:minimum -20.0 ;
lv2:maximum 20.0 ;
units:unit units:db ;
rdfs:comment "Trim input level (stage 1)" ;
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 1 ;
pg:group pstage:stage1 ;
lv2:symbol "locut1" ;
lv2:name "Lo Cut";
lv2:default 30.0 ;
lv2:minimum 30.0 ;
lv2:maximum 300.0 ;
units:unit units:hz;
lv2:designation param:cutoffFrequency ;
lv2:scalePoint [
rdfs:label "OFF" ;
rdf:value 30.0
];
rdfs:comment "Low frequency cutoff (stage 1). Set to minimum to disable." ;
], [
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 2 ;
pg:group pstage:stage1 ;
lv2:symbol "hicut1" ;
lv2:name "Hi Cut";
lv2:default 19000.0 ;
lv2:minimum 1000.0 ;
lv2:maximum 19000.0;
units:unit units:hz;
lv2:designation param:cutoffFrequency ;
lv2:scalePoint [
rdfs:label "OFF" ;
rdf:value 19000.0
];
rdfs:comment "High cutoff frequency (stage 1). Set to maximum to disable." ;
],
[
a lv2:InputPort ,
lv2:ControlPort ;
pg:group pstage:stage1 ;
lv2:index 3 ;
lv2:symbol "shape1" ;
lv2:name "Shape1";
lv2:default 0.0 ;
lv2:minimum 0.0 ;
lv2:maximum 1.0;
lv2:rangeStep 2;
lv2:portProperty lv2:enumeration ;
rdfs:comment "Wave Shape" ;
lv2:scalePoint [
rdfs:label "atan" ;
rdf:value 0.0
],
[
rdfs:label "triode" ;
rdf:value 1.0
];
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 4 ;
pg:group pstage:stage1 ;
lv2:symbol "gain1" ;
lv2:name "Gain";
lv2:default 0.0 ;
lv2:minimum 0.0 ;
lv2:maximum 1.0 ;
rdfs:comment "Gain (stage 1)"
],
[
a lv2:InputPort ,
lv2:ControlPort ;
pg:group pstage:stage1 ;
lv2:index 5 ;
lv2:symbol "bias1" ;
lv2:name "Bias";
lv2:default 0.0 ;
lv2:minimum -2.0 ;
lv2:maximum 2.0;
rdfs:comment "Waveshape bias (stage 1)"
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 6 ;
pg:group pstage:stage2 ;
lv2:symbol "trim2" ;
lv2:name "Trim";
lv2:default 0.0 ;
lv2:minimum -20.0 ;
lv2:maximum 20.0 ;
units:unit units:db ;
rdfs:comment "Trim input (stage 2)" ;
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 7 ;
pg:group pstage:stage2 ;
lv2:symbol "locut2" ;
lv2:name "Lo Cut";
lv2:default 30.0 ;
lv2:minimum 30.0 ;
lv2:maximum 300.0 ;
units:unit units:hz;
lv2:designation param:cutoffFrequency ;
lv2:scalePoint [
rdfs:label "OFF" ;
rdf:value 30.0
]
;
rdfs:comment "Low frequency cutoff (stage 2). Set to minimum to disable." ;
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 8 ;
pg:group pstage:stage2 ;
lv2:symbol "hicut2" ;
lv2:name "Hi Cut";
lv2:default 19000.0 ;
lv2:minimum 1000.0 ;
lv2:maximum 19000.0;
units:unit units:hz;
lv2:designation param:cutoffFrequency ;
lv2:scalePoint [
rdfs:label "OFF" ;
rdf:value 19000.0
];
rdfs:comment "High cutoff frequency (stage 2). Set to maximum to disable." ;
],
[
a lv2:InputPort ,
lv2:ControlPort ;
pg:group pstage:stage2 ;
lv2:index 9 ;
lv2:symbol "shape2" ;
lv2:name "Shape2";
lv2:default 0.0 ;
lv2:minimum 0.0 ;
lv2:maximum 1.0;
lv2:rangeStep 2;
lv2:portProperty lv2:enumeration ;
rdfs:comment "Wave Shape" ;
lv2:scalePoint [
rdfs:label "atan" ;
rdf:value 0.0
],
[
rdfs:label "triode" ;
rdf:value 1.0
]
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 10 ;
pg:group pstage:stage2 ;
lv2:symbol "gain2" ;
lv2:name "Gain";
lv2:default 0.0 ;
lv2:minimum 0.0 ;
lv2:maximum 1.0 ;
rdfs:comment "Gain (stage 2)"
],
[
a lv2:InputPort ,
lv2:ControlPort ;
pg:group pstage:stage2 ;
lv2:index 11 ;
lv2:symbol "bias2" ;
lv2:name "Bias";
lv2:default 0.0 ;
lv2:minimum -2.0 ;
lv2:maximum 2.0;
rdfs:comment "Waveshape bias (stage 2)"
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 12 ;
pg:group pstage:stage2 ;
lv2:symbol "gain2_enable" ;
lv2:name "Enable2";
lv2:default 1 ;
lv2:minimum 0 ;
lv2:maximum 1;
lv2:portProperty lv2:toggled ;
rdfs:comment "Enable gain stage 2" ;
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 13 ;
pg:group pstage:stage3 ;
lv2:symbol "trim3" ;
lv2:name "Trim";
lv2:default 0.0 ;
lv2:minimum -20.0 ;
lv2:maximum 20.0 ;
units:unit units:db ;
rdfs:comment "Trim input level (stage 3)." ;
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 14 ;
pg:group pstage:stage3 ;
lv2:symbol "locut3" ;
lv2:name "Lo Cut";
lv2:default 30.0 ;
lv2:minimum 30.0 ;
lv2:maximum 300.0 ;
units:unit units:hz;
lv2:designation param:cutoffFrequency ;
lv2:scalePoint [
rdfs:label "OFF" ;
rdf:value 30.0
];
rdfs:comment "Low frequency cutoff (stage 3). Set to minimum to disable." ;
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 15 ;
pg:group pstage:stage3 ;
lv2:symbol "hicut3" ;
lv2:name "Hi Cut";
lv2:default 19000.0 ;
lv2:minimum 1000.0 ;
lv2:maximum 19000.0;
units:unit units:hz;
lv2:designation param:cutoffFrequency ;
lv2:scalePoint [
rdfs:label "OFF" ;
rdf:value 19000
];
rdfs:comment "High cutoff frequency (stage 3). Set to minimum to disable." ;
],
[
a lv2:InputPort ,
lv2:ControlPort ;
pg:group pstage:stage3 ;
lv2:index 16 ;
lv2:symbol "shape3" ;
lv2:name "Shape3";
lv2:default 0.0 ;
lv2:minimum 0.0 ;
lv2:maximum 1.0;
lv2:rangeStep 2;
lv2:portProperty lv2:enumeration ;
rdfs:comment "Wave Shape" ;
lv2:scalePoint [
rdfs:label "atan" ;
rdf:value 0.0
],
[
rdfs:label "triode" ;
rdf:value 1.0
]
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 17 ;
pg:group pstage:stage3 ;
lv2:symbol "gain3" ;
lv2:name "Gain";
lv2:default 0.0 ;
lv2:minimum 0.0 ;
lv2:maximum 1.0 ;
rdfs:comment "Gain (stage 3)."
],
[
a lv2:InputPort ,
lv2:ControlPort ;
pg:group pstage:stage3 ;
lv2:index 18 ;
lv2:symbol "bias3" ;
lv2:name "Bias";
lv2:default 0.0 ;
lv2:minimum -2.0 ;
lv2:maximum 2.0;
rdfs:comment "Waveshape bias (stage 3)"
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 19 ;
pg:group pstage:stage3 ;
lv2:symbol "gain3_enable" ;
lv2:name "Enable3";
lv2:default 1 ;
lv2:minimum 0 ;
lv2:maximum 1;
lv2:portProperty lv2:toggled ;
rdfs:comment "Enable gain stage 3" ;
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 20 ;
lv2:symbol "sag" ;
lv2:name "Sag Vol";
lv2:default 0.0 ;
lv2:minimum 0.0 ;
lv2:maximum 1.0;
rdfs:comment "Transformer sag (volume)." ;
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 21 ;
lv2:symbol "sagd" ;
lv2:name "Sag Dist";
lv2:default 0.0 ;
lv2:minimum 0.0 ;
lv2:maximum 1.0;
rdfs:comment "Transformer sag (distortion)." ;
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 22 ;
lv2:symbol "master" ;
lv2:name "Master";
lv2:default -0.0 ;
lv2:minimum -60.0 ;
lv2:maximum 30.0;
units:unit units:db;
lv2:scalePoint [
rdfs:label "-60db" ;
rdf:value -60.0
] , [
rdfs:label "0db" ;
rdf:value 0.0
], [
rdfs:label "30db" ;
rdf:value -30.0
];
rdfs:comment "Master volume." ;
],
[
a lv2:AudioPort ,
lv2:InputPort ;
lv2:index 23 ;
lv2:symbol "in" ;
lv2:name "In"
] , [
a lv2:AudioPort ,
lv2:OutputPort ;
lv2:index 24 ;
lv2:symbol "out" ;
lv2:name "Out"
],[
a atom:AtomPort ,
lv2:InputPort;
atom:bufferType atom:Sequence ;
# atom:supports patch:Message;
lv2:designation lv2:control ;
lv2:index 25 ;
lv2:symbol "control" ;
lv2:name "Control" ;
rdfs:comment "Plugin to GUI communication" ;
] , [
a atom:AtomPort ,
lv2:OutputPort ;
atom:bufferType atom:Sequence ;
# atom:supports patch:Message;
lv2:designation lv2:control ;
lv2:index 26 ;
lv2:symbol "notify" ;
lv2:name "Notify" ;
rdfs:comment "Plugin to GUI communication" ;
],
[
a lv2:InputPort ,
lv2:ControlPort ;
lv2:index 27 ;
lv2:symbol "sagf" ;
lv2:name "SagF";
lv2:default 13.0 ;
lv2:minimum 5.0 ;
lv2:maximum 25.0;
rdfs:comment "Sag filter Fc." ;
]
.
-1
View File
@@ -1 +0,0 @@
ToobAmp.so.0
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
ToobAmp.so.1.1.47
Binary file not shown.
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
# profiler out files
*.txt
+2 -2
View File
@@ -1,3 +1,3 @@
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
${SCRIPT_DIR}/../build/src/profilePlugin -w ToobNam_Profile -o /tmp/ToobNam.perf &&\
google-pprof --text ${SCRIPT_DIR}/../build/src/profilePlugin --add_lib /usr/lib/lv2/ToobAmp.lv2/ToobAmp.so /tmp/ToobNam.perf >./ToobNam.txt
${SCRIPT_DIR}/../build/src/profilePlugin -s 100 -w ToobNam_Profile -o /tmp/ToobNam.perf &&\
google-pprof --text ${SCRIPT_DIR}/../build/src/profilePlugin --add_lib /usr/lib/lv2/ToobAmp.lv2/ToobAmp.so /tmp/ToobNam.perf >./ToobNam.txt
+3
View File
@@ -0,0 +1,3 @@
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
${SCRIPT_DIR}/../build/src/profilePlugin -w ToobNam_Profile -o /tmp/ToobNam.perf &&\
google-pprof --web ${SCRIPT_DIR}/../build/src/profilePlugin --add_lib /usr/lib/lv2/ToobAmp.lv2/ToobAmp.so /tmp/ToobNam.perf >./ToobNam.txt
+2 -2
View File
@@ -1,3 +1,3 @@
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
${SCRIPT_DIR}/../build/src/profilePlugin -w Nam_Profile -o /tmp/nam.perf &&\
google-pprof --text ${SCRIPT_DIR}/../build/src/profilePlugin --add_lib /usr/lib/lv2/neural_amp_modeler.lv2/neural_amp_modeler.so /tmp/nam.perf >./nam.txt
${SCRIPT_DIR}/../build/src/profilePlugin -s 100 -w Nam_Profile -o /tmp/nam.perf &&\
google-pprof --text ${SCRIPT_DIR}/../build/src/profilePlugin --add_lib /usr/lib/lv2/neural_amp_modeler.lv2/neural_amp_modeler.so /tmp/nam.perf >./nam.txt
+3
View File
@@ -0,0 +1,3 @@
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
${SCRIPT_DIR}/../build/src/profilePlugin -w Nam_Profile -o /tmp/nam.perf &&\
google-pprof --web ${SCRIPT_DIR}/../build/src/profilePlugin --add_lib /usr/lib/lv2/neural_amp_modeler.lv2/neural_amp_modeler.so /tmp/nam.perf >./nam.txt
+1
View File
@@ -2,3 +2,4 @@
rm -rf ./lv2/aarch64/*
cp -R /usr/lib/lv2/ToobAmp.lv2/ ./lv2/aarch64/
strip ./lv2/aarch64/ToobAmp.lv2/*.so
+18 -3
View File
@@ -67,10 +67,9 @@
useSystem = true;
break;
}
if (useSystem)
{
if (useSystem) {
darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}
}
if (!darkMode) {
let bgStyle = document.getElementById("bgStyle");
@@ -79,6 +78,22 @@
bgStyle.setAttribute('media', "max-width: 1px");
}
}
function removeHashOnLoad() {
if (window.location.hash) {
// Store the hash value (without the '#' symbol)
var hash = window.location.hash.substring(1);
// Replace the current URL without the hash
var newUrl = window.location.href.replace(window.location.hash, '');
// Use HTML5 history API to change the URL without reloading the page
history.replaceState(null, document.title, newUrl);
}
}
// Run the function when the window loads
window.addEventListener('load', removeHashOnLoad);
</script>
</head>
+3 -2
View File
@@ -1,8 +1,9 @@
{
"socket_server_port": 81,
"socket_server_port": 8080,
"socket_server_address": "*",
"debug": true,
"max_upload_size": 536870912,
"fakeAndroid": false,
"ui_plugins": []
"ui_plugins": [],
"enable_auto_update": true
}
+46 -22
View File
@@ -6,7 +6,7 @@ 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 ArrowBackIcon from '@mui/icons-material/ArrowBack';
import JackHostStatus from './JackHostStatus';
import { PiPedalError } from './PiPedalError';
@@ -16,12 +16,9 @@ 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 {
@@ -76,6 +73,15 @@ const styles = (theme: Theme) => createStyles({
},
secondaryItem: {
},
can_select: {
userSelect: "text",
cursor: "text"
},
no_select: {
userSelect: "none",
cursor: "text"
}
@@ -173,7 +179,7 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
this.updateNotifications();
}
componentDidUpdate(prevProps: Readonly<AboutDialogProps>, prevState: Readonly<AboutDialogState>, snapshot: any): void {
super.componentDidUpdate?.(prevProps,prevState,snapshot);
super.componentDidUpdate?.(prevProps, prevState, snapshot);
this.updateNotifications();
}
@@ -185,6 +191,11 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
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}
@@ -210,13 +221,16 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
overflowX: "hidden", overflowY: "auto", userSelect: "text"
}}
>
<div style={{ margin: 24 }}>
<Typography noWrap display="block" variant="h6" color="textPrimary">
PiPedal <span style={{ fontSize: "0.7em" }}>
{(this.model.serverVersion ? this.model.serverVersion.serverVersion : "")
+ (this.model.serverVersion?.debug ? " (Debug)" : "")}
</span>
</Typography>
<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 &#169; 2022-2024 Robin Davies.
</Typography>
@@ -235,24 +249,34 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
<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 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 dangerouslySetInnerHTML={{ __html: this.state.openSourceNotices }} style={{ fontSize: "0.8em", maxWidth: 400 }}>
</div>
</div>
+9
View File
@@ -29,6 +29,8 @@ export interface AndroidHostInterface {
chooseNewDevice() : void;
setDisconnected(isDisconnected: boolean): void;
launchExternalUrl(url:string): boolean;
setThemePreference(theme: number): void;
getThemePreference(): number;
};
export class FakeAndroidHost implements AndroidHostInterface
@@ -51,4 +53,11 @@ export class FakeAndroidHost implements AndroidHostInterface
{
return false;
}
private theme = 1;
setThemePreference(theme: number): void{
this.theme = theme;
}
getThemePreference(): number {
return this.theme;
}
}
+26
View File
@@ -56,6 +56,19 @@ const theme = createTheme(
{
components: {
MuiButton: {
styleOverrides: {
containedPrimary: {
borderRadius: '9999px',
paddingLeft: "16px", paddingRight: "16px",
textTransform: "none"
},
containedSecondary: {
borderRadius: '9999px',
paddingLeft: "16px", paddingRight: "16px",
textTransform: "none"
}
},
variants: [
{
props: { variant: 'dialogPrimary' },
@@ -89,6 +102,19 @@ const theme = createTheme(
{
components: {
MuiButton: {
styleOverrides: {
containedPrimary: {
borderRadius: '9999px',
paddingLeft: "16px", paddingRight: "16px",
textTransform: "none"
},
containedSecondary: {
borderRadius: '9999px',
paddingLeft: "16px", paddingRight: "16px",
textTransform: "none"
}
},
variants: [
{
props: { variant: 'dialogPrimary' },
+95 -30
View File
@@ -48,7 +48,7 @@ import SettingsDialog from './SettingsDialog';
import AboutDialog from './AboutDialog';
import BankDialog from './BankDialog';
import { PiPedalModelFactory, PiPedalModel, State, ZoomedControlInfo,wantsLoadingScreen } from './PiPedalModel';
import { PiPedalModelFactory, PiPedalModel, State, ZoomedControlInfo, wantsLoadingScreen } from './PiPedalModel';
import ZoomedUiControl from './ZoomedUiControl'
import MainPage from './MainPage';
import DialogContent from '@mui/material/DialogContent';
@@ -67,7 +67,7 @@ import { ReactComponent as SaveBankAsIcon } from './svg/ic_save_bank_as.svg';
import { ReactComponent as EditBanksIcon } from './svg/ic_edit_banks.svg';
import { ReactComponent as SettingsIcon } from './svg/ic_settings.svg';
import { ReactComponent as HelpOutlineIcon } from './svg/ic_help_outline.svg';
import DialogEx from './DialogEx';
import DialogEx, { DialogStackState } from './DialogEx';
const appStyles = (theme: Theme) => createStyles({
@@ -139,10 +139,11 @@ const appStyles = (theme: Theme) => createStyles({
marginTop: 0,
fontWeight: 500,
fontSize: "13pt",
maxWidth: 350,
opacity: 1,
zIndex: 2010,
paddingTop: 12,
gravity: "center",
textAlign: "center"
},
errorMessageBox: {
@@ -163,10 +164,7 @@ const appStyles = (theme: Theme) => createStyles({
loadingBox: {
position: "relative",
top: "20%",
width: "240px",
color: isDarkMode() ? theme.palette.text.secondary : "#888",
marginLeft: "auto",
marginRight: "auto",
// border: "3px solid #888",
borderRadius: "12px",
padding: "12px",
@@ -352,9 +350,40 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
this.handleCloseAlert = this.handleCloseAlert.bind(this);
this.banksChangedHandler = this.banksChangedHandler.bind(this);
this.showStatusMonitorHandler = this.showStatusMonitorHandler.bind(this);
this.onPopStateHandler = this.onPopStateHandler.bind(this);
}
showDrawer() {
if (!this.state.isDrawerOpen) {
this.setState({ isDrawerOpen: true })
this.pushOpenMenuState();
}
}
hideDrawer(loadingDialog: boolean = false) {
if (!loadingDialog && window.location.hash === "#menu") {
window.history.back();
} else {
this.setState({ isDrawerOpen: false })
}
}
pushOpenMenuState() {
let stackState = { tag: "OpenMenu", previousState: null };
window.history.replaceState(stackState, "", window.location.href);
window.history.pushState({}, "", "#menu");
}
onPopStateHandler(ev: PopStateEvent) {
let stackState: DialogStackState | null = ev.state as (DialogStackState | null);
if (stackState && stackState.tag === "OpenMenu") {
if (this.state.isDrawerOpen) {
this.setState({ isDrawerOpen: false });
}
ev.stopPropagation();
window.history.replaceState(stackState.previousState, "", window.location.href);
return true;
}
return false;
}
onOpenBank(bankId: number) {
this.model_.openBank(bankId)
@@ -525,9 +554,10 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
return undefined;
}
promptForUpdateHandler(newValue: boolean) {
if (this.state.updateDialogOpen !== newValue)
{
this.setState({updateDialogOpen: newValue});
if (this.model_.enableAutoUpdate) {
if (this.state.updateDialogOpen !== newValue) {
this.setState({ updateDialogOpen: newValue });
}
}
}
componentDidMount() {
@@ -535,6 +565,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
super.componentDidMount();
window.addEventListener("beforeunload", this.beforeUnloadListener);
window.addEventListener("unload", this.unloadListener);
window.addEventListener("popstate", this.onPopStateHandler);
this.model_.errorMessage.addOnChangedHandler(this.errorChangeHandler_);
this.model_.state.addOnChangedHandler(this.stateChangeHandler_);
@@ -544,6 +575,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
this.model_.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler);
this.model_.promptForUpdate.addOnChangedHandler(this.promptForUpdateHandler);
this.alertMessageChangedHandler();
}
@@ -566,6 +598,9 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
super.componentWillUnmount();
window.removeEventListener("beforeunload", this.beforeUnloadListener);
window.removeEventListener("popstate", this.onPopStateHandler);
this.model_.promptForUpdate.removeOnChangedHandler(this.promptForUpdateHandler);
this.model_.errorMessage.removeOnChangedHandler(this.errorChangeHandler_);
this.model_.state.removeOnChangedHandler(this.stateChangeHandler_);
@@ -632,12 +667,6 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
}
}
showDrawer() {
this.setState({ isDrawerOpen: true })
}
hideDrawer() {
this.setState({ isDrawerOpen: false })
}
shortBankList(banks: BankIndex): BankIndexEntry[] {
let n = this.state.bankDisplayItems;
let entries = banks.entries;
@@ -682,6 +711,8 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
return "Downloading update...";
case State.InstallingUpdate:
return "Installing update....";
case State.HotspotChanging:
return "Network connection changing..."
default:
return "Reconnecting...";
}
@@ -792,7 +823,11 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
{
showBankSelectDialog && (
<ListItem button key={'bankDOTDOTDOT'} selected={false}
onClick={() => this.handleDrawerSelectBank()}
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerSelectBank();
}}
>
<ListItemText primary={"..."} />
@@ -804,19 +839,34 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
</List>
<Divider />
<List>
<ListItem button key='RenameBank' onClick={() => { this.handleDrawerRenameBank() }}>
<ListItem button key='RenameBank'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerRenameBank()
}}>
<ListItemIcon >
<RenameOutlineIcon color='inherit' className={classes.menuIcon} />
</ListItemIcon>
<ListItemText primary='Rename bank' />
</ListItem>
<ListItem button key='SaveBank' onClick={() => { this.handleDrawerSaveBankAs() }} >
<ListItem button key='SaveBank'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerSaveBankAs();
}} >
<ListItemIcon>
<SaveBankAsIcon color="inherit" className={classes.menuIcon} />
</ListItemIcon>
<ListItemText primary='Save as new bank' />
</ListItem>
<ListItem button key='EditBanks' onClick={() => { this.handleDrawerManageBanks(); }}>
<ListItem button key='EditBanks'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerManageBanks();
}}>
<ListItemIcon>
<EditBanksIcon color="inherit" className={classes.menuIcon} />
</ListItemIcon>
@@ -825,19 +875,34 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
</List>
<Divider />
<List>
<ListItem button key='Settings' onClick={() => { this.handleDrawerSettingsClick() }}>
<ListItem button key='Settings'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerSettingsClick()
}}>
<ListItemIcon>
<SettingsIcon color="inherit" className={classes.menuIcon} />
</ListItemIcon>
<ListItemText primary='Settings' />
</ListItem>
<ListItem button key='About' onClick={() => { this.handleDrawerAboutClick() }}>
<ListItem button key='About'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerAboutClick();
}}>
<ListItemIcon>
<HelpOutlineIcon color="inherit" className={classes.menuIcon} />
</ListItemIcon>
<ListItemText primary='About' />
</ListItem>
<ListItem button key='Donations' onClick={() => { this.handleDrawerDonationClick() }}>
<ListItem button key='Donations'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerDonationClick();
}}>
<ListItemIcon >
<VolunteerActivismIcon className={classes.menuIcon} color="inherit" />
</ListItemIcon>
@@ -861,10 +926,10 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
</div>
</main>
<BankDialog show={this.state.bankDialogOpen} isEditDialog={this.state.editBankDialogOpen} onDialogClose={() => this.setState({ bankDialogOpen: false })} />
{ (this.state.aboutDialogOpen)&&
(
<AboutDialog open={this.state.aboutDialogOpen} onClose={() => this.setState({ aboutDialogOpen: false })} />
)}
{(this.state.aboutDialogOpen) &&
(
<AboutDialog open={this.state.aboutDialogOpen} onClose={() => this.setState({ aboutDialogOpen: false })} />
)}
<SettingsDialog
open={this.state.isSettingsDialogOpen}
onboarding={this.state.onboarding}
@@ -927,7 +992,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
<div className={classes.errorContent} style={{
display: (
wantsLoadingScreen(this.state.displayState)
? "block" : "none"
? "block" : "none"
)
}}
>
@@ -937,7 +1002,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
<div className={classes.loadingBoxItem}>
<CircularProgress color="inherit" className={classes.loadingBoxItem} />
</div>
<Typography noWrap variant="body2" className={classes.progressText}>
<Typography display="block" noWrap variant="body2" className={classes.progressText}>
{this.getReloadingMessage()}
</Typography>
</div>
@@ -957,7 +1022,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
</p>
</div>
<div style={{ paddingTop: 50, paddingLeft: 36, textAlign: "left" }}>
<Button variant='contained' color="primary" component='button'
<Button variant='contained' color="primary"
onClick={() => this.handleReload()} >
Reload
</Button>
+1 -1
View File
@@ -392,7 +392,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
getSelectedBankName() {
try {
return this.model.banks.get().getEntry(this.state.selectedItem)!.name;
return this.model.banks.get()?.getEntry(this.state.selectedItem)?.name??"";
} catch (error) {
return "";
}
+30 -7
View File
@@ -17,6 +17,8 @@
// 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 { AndroidHostInterface } from "./AndroidHost";
export enum ColorTheme {
Light,
Dark,
@@ -26,16 +28,20 @@ export enum ColorTheme {
export function getColorScheme(): ColorTheme {
const androidHosted = !!((window as any).AndroidHost);
let androidHost = (window as any).AndroidHost as AndroidHostInterface;
if (androidHost) {
switch (androidHost.getThemePreference() as number)
{
case 0: return ColorTheme.Light;
case 1: return ColorTheme.Dark;
default:
case 2: return ColorTheme.System;
}
}
switch (localStorage.getItem('colorScheme')) {
case null:
default:
if (androidHosted) {
return ColorTheme.System;
} else {
return ColorTheme.Light;
}
return ColorTheme.Light;
case "Light":
return ColorTheme.Light;
case "Dark":
@@ -45,6 +51,23 @@ export function getColorScheme(): ColorTheme {
}
}
export function setColorScheme(value: ColorTheme): void {
let androidHost = (window as any).AndroidHost as AndroidHostInterface;
if (androidHost) {
switch (value) {
case ColorTheme.Light:
androidHost.setThemePreference(0);
break;
case ColorTheme.Dark:
androidHost.setThemePreference(1);
break;
default:
case ColorTheme.System:
androidHost.setThemePreference(2);
break;
}
}
var storageValue;
switch (value) {
default:
+45 -23
View File
@@ -31,21 +31,17 @@ interface DialogExState {
}
class DialogStackEntry {
constructor(tag: string)
constructor(dialog: DialogEx)
{
this.tag = tag;
this.tag = dialog.props.tag;
this.dialog = dialog;
}
tag: string;
dialog: DialogEx;
};
let dialogStack: DialogStackEntry[] = [];
function peekDialogStack(): string {
if (dialogStack.length !== 0) {
return dialogStack[dialogStack.length-1].tag;
}
return "";
}
function popDialogStack(): void {
if (dialogStack.length !== 0)
@@ -53,12 +49,33 @@ function popDialogStack(): void {
dialogStack.splice(dialogStack.length-1,1);
}
}
function pushDialogStack(tag: string): void {
dialogStack.push(new DialogStackEntry(tag));
function pushDialogStack(dialog: DialogEx): 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.tag === tag)
{
return;
}
popDialogStack();
if (entry.dialog.props.open && entry.dialog.props.onClose)
{
entry.dialog.props.onClose({}, "backdropClick");
}
}
}
class DialogEx extends React.Component<DialogExProps,DialogExState> {
constructor(props: DialogExProps)
{
@@ -75,22 +92,28 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
hasHooks: boolean = false;
stateWasPopped: boolean = false;
handlePopState(e: any): any
handlePopState(ev: PopStateEvent): any
{
let shouldClose = (peekDialogStack() === this.props.tag);
if (shouldClose)
{
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?.(e,"backdropClick");
this.props.onClose?.(ev,"backdropClick");
}
}
window.history.replaceState(evTag.previousState,"",window.location.href);
window.removeEventListener("popstate",this.handlePopState);
e.stopPropagation();
if (window.location.hash === "#menu") // The menu popstate is next?
{
window.history.back(); // then clear off the menu popstate too.
}
ev.stopPropagation();
}
}
@@ -105,19 +128,18 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
this.stateWasPopped = false;
window.addEventListener("popstate",this.handlePopState);
pushDialogStack(this.props.tag);
let state: {tag: string} = {tag: this.props.tag};
// eslint-disable-next-line no-restricted-globals
history.pushState(
state,
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)
{
// eslint-disable-next-line no-restricted-globals
history.back();
window.history.back();
}
}
}
+200 -40
View File
@@ -42,11 +42,21 @@ import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import AlsaDeviceInfo from './AlsaDeviceInfo';
const MIN_BUFFER_SIZE = 16;
const MAX_BUFFER_SIZE = 2048;
interface BufferSetting {
bufferSize: number;
numberOfBuffers: number;
};
const INVALID_DEVICE_ID = "_invalid_";
interface JackServerSettingsDialogState {
latencyText: string;
jackServerSettings: JackServerSettings;
alsaDevices?: AlsaDeviceInfo[];
okEnabled: boolean;
}
const styles = (theme: Theme) =>
@@ -66,12 +76,150 @@ export interface JackServerSettingsDialogProps extends WithStyles<typeof styles>
onApply: (jackServerSettings: JackServerSettings) => void;
}
function getLatencyText(settings: JackServerSettings ): string {
function getLatencyText(settings?: JackServerSettings ): string {
if (!settings)
{
return "\u00A0";
}
if (!settings.valid) return "\u00A0";
let ms = settings.bufferSize * settings.numberOfBuffers / settings.sampleRate * 1000;
return ms.toFixed(1) + "ms";
}
function getValidBufferCounts(bufferSize: number, alsaDeviceInfo?: AlsaDeviceInfo): number[]
{
if (!alsaDeviceInfo)
{
return [];
}
let result: number[] = [];
if (bufferSize * 2 >= alsaDeviceInfo.minBufferSize)
{
result = [2,3,4];
} else {
let minBuffers = Math.ceil(alsaDeviceInfo.minBufferSize/bufferSize/2-0.0001);
result = [minBuffers*2,minBuffers*3, minBuffers*4];
}
for (let i = 0; i < result.length; ++i)
{
let selectedSize = result[i]*bufferSize;
if (selectedSize < alsaDeviceInfo.minBufferSize || selectedSize > alsaDeviceInfo.maxBufferSize)
{
result.splice(i,1);
--i;
}
}
return result;
}
function getValidBufferSizes(alsaDeviceInfo? : AlsaDeviceInfo): number[]
{
if (!alsaDeviceInfo )
{
return [];
}
let result: number[] = [];
for (let i = MIN_BUFFER_SIZE; i <= MAX_BUFFER_SIZE; i *= 2)
{
if (getValidBufferCounts(i,alsaDeviceInfo).length !== 0)
{
result.push(i);
}
}
return result;
}
function getBestBuffers(
alsaDeviceInfo: AlsaDeviceInfo | undefined,
bufferSize: number,
numberOfBuffers: number,
bufferSizeChanging: boolean = false
) : BufferSetting {
if (!alsaDeviceInfo)
{
return { bufferSize:bufferSize, numberOfBuffers: numberOfBuffers};
}
// If the numberOfbuffers is fine as is, don't change the number of Buffers.
// set default values. Otherwise, choose the best buffer count from available buffer counts.
let validBuffercounts = getValidBufferCounts(bufferSize,alsaDeviceInfo);
let ix = validBuffercounts.indexOf(numberOfBuffers);
if (ix !== -1) {
if (bufferSize*numberOfBuffers <= alsaDeviceInfo.maxBufferSize
&& bufferSize*numberOfBuffers >= alsaDeviceInfo.minBufferSize)
{
return {bufferSize: bufferSize, numberOfBuffers: numberOfBuffers};
}
}
// if numberOfBuffers is not valid, and the user has just selected a new buffers size,
// then choose from available buffer counts.
if (bufferSizeChanging)
{
if (validBuffercounts.length !== 0)
{
// prefer the one thats divisible by 3.
for (let i = 0; i < validBuffercounts.length; ++i)
{
if (validBuffercounts[i] % 3 === 0)
{
numberOfBuffers = validBuffercounts[i];
return {bufferSize: bufferSize, numberOfBuffers:numberOfBuffers};
}
}
numberOfBuffers = validBuffercounts[0];
return {bufferSize: bufferSize, numberOfBuffers:numberOfBuffers};
}
}
// otherwise select a sensible starting value.
// favored default: 64x3.
if (64*3 >= alsaDeviceInfo.minBufferSize && 64*3 <= alsaDeviceInfo.maxBufferSize)
{
return { bufferSize: 64, numberOfBuffers: 3};
}
// if that isn't possible then minBufferSize/2 x 4.
bufferSize = alsaDeviceInfo.minBufferSize/2;
numberOfBuffers = 4;
// otherwise, minBufferSize/2 x 2.
if (bufferSize*numberOfBuffers > alsaDeviceInfo.maxBufferSize)
{
numberOfBuffers = 2;
}
return { bufferSize: bufferSize, numberOfBuffers: numberOfBuffers};
};
function isOkEnabled(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[])
{
if (!jackServerSettings.valid) return false;
if (!alsaDevices) return false;
let alsaDevice: AlsaDeviceInfo | undefined = undefined;
for (let i = 0; i < alsaDevices.length; ++i)
{
if (alsaDevices[i].id === jackServerSettings.alsaDevice)
{
alsaDevice = alsaDevices[i];
}
}
if (!alsaDevice)
{
return false;
}
let deviceBufferSize = jackServerSettings.bufferSize * jackServerSettings.numberOfBuffers;
if (deviceBufferSize < alsaDevice.minBufferSize || deviceBufferSize > alsaDevice.maxBufferSize)
{
return false;
}
let validBufferCounts = getValidBufferCounts(jackServerSettings.bufferSize, alsaDevice);
let ix = validBufferCounts.indexOf(jackServerSettings.numberOfBuffers);
if (ix === -1)
{
return false;
}
return true;
}
const JackServerSettingsDialog = withStyles(styles)(
class extends Component<JackServerSettingsDialogProps, JackServerSettingsDialogState> {
@@ -83,8 +231,10 @@ const JackServerSettingsDialog = withStyles(styles)(
this.state = {
latencyText: "\u00A0",
jackServerSettings: props.jackServerSettings.clone() // invalid, but not nullish
latencyText: getLatencyText(props.jackServerSettings),
jackServerSettings: props.jackServerSettings.clone(), // invalid, but not nullish
alsaDevices: undefined,
okEnabled: false
};
}
mounted: boolean = false;
@@ -98,7 +248,8 @@ const JackServerSettingsDialog = withStyles(styles)(
this.setState({
alsaDevices: devices,
jackServerSettings: settings,
latencyText: getLatencyText(settings)
latencyText: getLatencyText(settings),
okEnabled: isOkEnabled(settings,devices)
});
} else {
this.setState({ alsaDevices: devices });
@@ -115,8 +266,8 @@ const JackServerSettingsDialog = withStyles(styles)(
if (!alsaDevices) {
return result;
}
result.valid = false;
if (alsaDevices.length === 0) {
result.valid = false;
result.alsaDevice = INVALID_DEVICE_ID;
return result;
}
@@ -134,27 +285,34 @@ const JackServerSettingsDialog = withStyles(styles)(
}
if (result.sampleRate === 0) result.sampleRate = 48000;
if (result.bufferSize === 0) result.bufferSize = 16;
if (result.numberOfBuffers === 0) result.numberOfBuffers = 3;
result.sampleRate = selectedDevice.closestSampleRate(result.sampleRate);
result.bufferSize = selectedDevice.closestBufferSize(result.bufferSize);
let bestBuffers = getBestBuffers(selectedDevice,result.bufferSize,result.numberOfBuffers);
result.bufferSize = bestBuffers.bufferSize;
result.numberOfBuffers = bestBuffers.numberOfBuffers;
result.valid = true;
return result;
}
componentDidMount() {
this.mounted = true;
this.requestAlsaInfo();
if (this.props.open) {
this.requestAlsaInfo();
}
}
componentDidUpdate(oldProps: JackServerSettingsDialogProps) {
if (this.props.open && !oldProps.open) {
if ((this.props.open && !oldProps.open) && this.mounted) {
let settings = this.applyAlsaDevices(this.props.jackServerSettings.clone(), this.state.alsaDevices);
this.setState({
jackServerSettings: settings,
latencyText: getLatencyText(settings)
latencyText: getLatencyText(settings),
okEnabled: isOkEnabled(settings,this.state.alsaDevices)
});
this.requestAlsaInfo();
if (!this.state.alsaDevices) {
this.requestAlsaInfo();
}
}
}
@@ -180,12 +338,12 @@ const JackServerSettingsDialog = withStyles(styles)(
if (!selectedDevice) return;
let settings = this.state.jackServerSettings.clone();
settings.alsaDevice = device;
settings.sampleRate = selectedDevice.closestSampleRate(settings.sampleRate);
settings.bufferSize = selectedDevice.closestBufferSize(settings.bufferSize);
settings = this.applyAlsaDevices(settings,this.state.alsaDevices);
this.setState({
jackServerSettings: settings,
latencyText: getLatencyText(settings)
latencyText: getLatencyText(settings),
okEnabled: isOkEnabled(settings,this.state.alsaDevices)
});
}
handleRateChanged(e: any) {
@@ -198,7 +356,8 @@ const JackServerSettingsDialog = withStyles(styles)(
this.setState({
jackServerSettings: settings,
latencyText: getLatencyText(settings)
latencyText: getLatencyText(settings),
okEnabled: isOkEnabled(settings,this.state.alsaDevices)
});
}
handleSizeChanged(e: any) {
@@ -208,10 +367,14 @@ const JackServerSettingsDialog = withStyles(styles)(
if (!selectedDevice) return;
let settings = this.state.jackServerSettings.clone();
settings.bufferSize = size;
let bestBufferSetting = getBestBuffers(selectedDevice,settings.bufferSize,settings.numberOfBuffers,true);
settings.bufferSize = bestBufferSetting.bufferSize;
settings.numberOfBuffers = bestBufferSetting.numberOfBuffers;
this.setState({
jackServerSettings: settings,
latencyText: getLatencyText(settings)
latencyText: getLatencyText(settings),
okEnabled: isOkEnabled(settings,this.state.alsaDevices)
});
}
handleNumberOfBuffersChanged(e: any) {
@@ -224,7 +387,8 @@ const JackServerSettingsDialog = withStyles(styles)(
this.setState({
jackServerSettings: settings,
latencyText: getLatencyText(settings)
latencyText: getLatencyText(settings),
okEnabled: isOkEnabled(settings,this.state.alsaDevices)
});
}
@@ -232,19 +396,6 @@ const JackServerSettingsDialog = withStyles(styles)(
this.props.onApply(this.state.jackServerSettings.clone());
};
getBufferSizes(alsaDevice: AlsaDeviceInfo): number[] {
let result: number[] = [];
let max = alsaDevice.maxBufferSize;
if (max > 2048) max = 2048;
for (let i = 16; i <= max; i *= 2) {
if (i >= alsaDevice.minBufferSize) {
result.push(i);
}
}
return result;
}
render() {
const classes = this.props.classes;
@@ -265,10 +416,10 @@ const JackServerSettingsDialog = withStyles(styles)(
}
}
}
let bufferSizes: number[] = [];
if (selectedDevice) {
bufferSizes = this.getBufferSizes(selectedDevice);
}
let bufferSizes: number[] = getValidBufferSizes(selectedDevice);
let bufferCounts = getValidBufferCounts(this.state.jackServerSettings.bufferSize,selectedDevice);
let bufferSizeDisabled = !selectedDevice;
let bufferCountDisabled = !selectedDevice;
return (
<DialogEx tag="jack" onClose={handleClose} aria-labelledby="select-channels-title" open={open}>
@@ -305,6 +456,7 @@ const JackServerSettingsDialog = withStyles(styles)(
<Select variant="standard"
onChange={(e) => this.handleRateChanged(e)}
value={this.state.jackServerSettings.sampleRate}
disabled={!selectedDevice}
inputProps={{
id: 'jsd_sampleRate',
style: {
@@ -325,6 +477,7 @@ const JackServerSettingsDialog = withStyles(styles)(
<Select variant="standard"
onChange={(e) => this.handleSizeChanged(e)}
value={this.state.jackServerSettings.bufferSize}
disabled={bufferSizeDisabled}
inputProps={{
name: 'Buffer size',
id: 'jsd_bufferSize',
@@ -343,14 +496,21 @@ const JackServerSettingsDialog = withStyles(styles)(
<Select variant="standard"
onChange={(e) => this.handleNumberOfBuffersChanged(e)}
value={this.state.jackServerSettings.numberOfBuffers}
disabled={bufferCountDisabled}
inputProps={{
name: 'Number of buffers',
id: 'jsd_bufferCount',
}}
>
<MenuItem value={2}>2</MenuItem>
<MenuItem value={3}>3</MenuItem>
<MenuItem value={4}>4</MenuItem>
{
bufferCounts.map((bufferCount)=>
{
return (
<MenuItem key={bufferCount} value={bufferCount}>{bufferCount.toString()}</MenuItem>
);
})
}
</Select>
</FormControl>
</div>
@@ -365,8 +525,8 @@ const JackServerSettingsDialog = withStyles(styles)(
<Button variant="dialogSecondary" onClick={handleClose} >
Cancel
</Button>
<Button variant="dialogPrimary" onClick={() => this.handleApply()} disabled={
(!this.state.alsaDevices) || !this.state.jackServerSettings.valid}>
<Button variant="dialogPrimary" onClick={() => this.handleApply()}
disabled={!this.state.okEnabled}>
OK
</Button>
</DialogActions>
+181 -17
View File
@@ -52,6 +52,7 @@ export enum State {
ReloadingPlugins,
DownloadingUpdate,
InstallingUpdate,
HotspotChanging,
};
class UpdatedError extends Error {
@@ -65,6 +66,7 @@ export enum ReconnectReason {
LoadingSettings,
ReloadingPlugins,
Updating,
HotspotChanging
};
export type ControlValueChangedHandler = (key: string, value: number) => void;
@@ -366,7 +368,8 @@ export class PiPedalModel //implements PiPedalModel
clientId: number = -1;
serverVersion?: PiPedalVersion;
countryCodes: Object = {};
countryCodes: {[Name: string]: string} = {};
socketServerUrl: string = "";
varServerUrl: string = "";
lv2Path: string = "";
@@ -465,6 +468,10 @@ export class PiPedalModel //implements PiPedalModel
case ReconnectReason.Updating:
this.setState(State.InstallingUpdate);
break;
case ReconnectReason.HotspotChanging:
this.setState(State.HotspotChanging);
this.startHotspotReconnectTimer();
break;
}
return true;
@@ -642,9 +649,13 @@ export class PiPedalModel //implements PiPedalModel
} else if (message === "onUpdateStatusChanged") {
let updateStatus = new UpdateStatus().deserialize(body);
this.onUpdateStatusChanged(updateStatus);
} else if (message === "onNetworkChanging")
{
this.onNetworkChanging(body as boolean);
}
}
private updateLaterTimeout?: NodeJS.Timeout = undefined;
private clearPromptForUpdateTimer() {
@@ -676,6 +687,10 @@ export class PiPedalModel //implements PiPedalModel
private lastCanUpdateNow: boolean = false;
private updatePromptForUpdate() {
this.clearPromptForUpdateTimer();
if (!this.enableAutoUpdate)
{
return;
}
let stateEnabled = true; // must be present to accept alerts. this.state.get() === State.Ready;
let timeEnabled = false;
@@ -778,16 +793,46 @@ export class PiPedalModel //implements PiPedalModel
return this.webSocket;
}
androidReconnectTimeout?: NodeJS.Timeout = undefined;
cancelAndroidReconnectTimer()
{
if (this.androidReconnectTimeout) {
clearTimeout(this.androidReconnectTimeout);
this.androidReconnectTimeout = undefined;
}
}
startAndroidReconnectTimer()
{
this.cancelAndroidReconnectTimer();
this.androidReconnectTimeout = setTimeout(()=>{
this.androidReconnectTimeout = undefined;
this.androidHost?.setDisconnected(true);
},20*1000);
}
onSocketConnectionLost() {
// remove all the events and subscriptions we have.
if (this.isClosed)
{
return; // page unloading. do NOT change the UI.
}
this.vuSubscriptions = [];
this.monitorPatchPropertyListeners = [];
if (this.isAndroidHosted()) {
this.androidHost?.setDisconnected(true);
// if unexpected, go back to the device browser immediately.
if (this.reconnectReason === ReconnectReason.Disconnected)
{
this.androidHost?.setDisconnected(true);
} else {
this.startAndroidReconnectTimer();
}
}
}
onSocketReconnected() {
this.cancelOnNetworkChanging();
this.cancelAndroidReconnectTimer();
if (this.isAndroidHosted()) {
this.androidHost?.setDisconnected(false);
@@ -898,7 +943,7 @@ export class PiPedalModel //implements PiPedalModel
maxFileUploadSize: number = 512 * 1024 * 1024;
maxPresetUploadSize: number = 1024 * 1024;
debug: boolean = false;
enableAutoUpdate: boolean = false;
requestConfig(): Promise<boolean> {
const myRequest = new Request(this.varRequest('config.json'));
@@ -909,6 +954,7 @@ export class PiPedalModel //implements PiPedalModel
}
)
.then(data => {
this.enableAutoUpdate = !!data.enable_auto_update;
if (data.max_upload_size) {
this.maxPresetUploadSize = data.max_upload_size;
}
@@ -942,7 +988,7 @@ export class PiPedalModel //implements PiPedalModel
return this.webSocket.connect();
})
.catch((error) => {
this.setError("Failed to connect to server.");
this.setError("Failed to connect to server. (" + this.socketServerUrl + ")");
return false;
})
.then(() => {
@@ -956,7 +1002,7 @@ export class PiPedalModel //implements PiPedalModel
return response.json();
})
.then((countryCodes) => {
this.countryCodes = countryCodes as Object;
this.countryCodes = countryCodes as {[Name: string]: string};
return this.getWebSocket().request<number>("hello");
})
@@ -2556,27 +2602,33 @@ export class PiPedalModel //implements PiPedalModel
let oldSettings = this.wifiConfigSettings.get();
wifiConfigSettings = wifiConfigSettings.clone();
if ((!oldSettings.enable) && (!wifiConfigSettings.enable)) {
if ((!oldSettings.isEnabled()) && (!wifiConfigSettings.isEnabled())) {
// no effective change.
resolve();
return;
}
if (!wifiConfigSettings.enable) {
if (!wifiConfigSettings.isEnabled()) {
wifiConfigSettings.hasPassword = false;
wifiConfigSettings.hotspotName = oldSettings.hotspotName;
wifiConfigSettings.password = "";
} else {
if (wifiConfigSettings.countryCode === oldSettings.countryCode
&& wifiConfigSettings.channel === oldSettings.channel
&& wifiConfigSettings.hotspotName === oldSettings.hotspotName) {
if (!wifiConfigSettings.hasPassword) {
// no effective change.
resolve();
return;
if (wifiConfigSettings.hasPassword)
{
wifiConfigSettings.hasSavedPassword = true;
}
}
}
// save a version for the server (potentially carrying a password)
let serverConfigSettings = wifiConfigSettings.clone();
let serverConfigSettings: WifiConfigSettings;
if (wifiConfigSettings.isEnabled()) {
serverConfigSettings = wifiConfigSettings.clone();
} else {
// avoid leaking edits to the server
serverConfigSettings = oldSettings.clone();
serverConfigSettings.autoStartMode = wifiConfigSettings.autoStartMode;
wifiConfigSettings = oldSettings.clone();
wifiConfigSettings.autoStartMode = 0;
}
wifiConfigSettings.hasPassword = false;
wifiConfigSettings.password = "";
this.wifiConfigSettings.set(wifiConfigSettings);
@@ -2661,6 +2713,24 @@ export class PiPedalModel //implements PiPedalModel
return result;
}
getKnownWifiNetworks() : Promise<string[]> {
let result = new Promise<string[]>((resolve, reject) => {
if (!this.webSocket) {
reject("Connection closed.");
return;
}
this.webSocket.request<string[]>("getKnownWifiNetworks")
.then((data) => {
resolve(data);
})
.catch((err) => reject(err));
});
return result;
}
getWifiChannels(countryIso3661: string): Promise<WifiChannel[]> {
let result = new Promise<WifiChannel[]>((resolve, reject) => {
if (!this.webSocket) {
@@ -2869,6 +2939,100 @@ export class PiPedalModel //implements PiPedalModel
window.location.href = url;
//window.location.reload();
}
private networkChanging: boolean = false;
private networkChanging_expectHotspot = false;
private expectNetworkChangeTimeout?: NodeJS.Timeout = undefined;
private hotspotReconnectTimer?: NodeJS.Timeout = undefined;
async detectServer(address: string)
{
let port = window.location.port;
let newUrl = new URL("http://" + address + ":" + port + "/manifest.json");
try {
let response = await fetch(newUrl);
if (response.ok)
{
return "http://" + address +":"+ port;
}
return "";
} catch (error: any)
{
return "";
}
}
async pollForLiveServer() {
let wifiConfigSettings = this.wifiConfigSettings.get();
if (wifiConfigSettings.mdnsName.length !== 0)
{
let newUrl = await this.detectServer(wifiConfigSettings.mdnsName);
if (newUrl.length !== 0)
{
return newUrl;
}
}
if (this.networkChanging_expectHotspot)
{
let newUrl = await this.detectServer("10.40.0.1");
if (newUrl.length !== 0)
{
return newUrl;
}
}
return "";
}
cancelHotspotReconnectTimer()
{
if (this.hotspotReconnectTimer)
{
clearTimeout(this.hotspotReconnectTimer);
}
}
startHotspotReconnectTimer()
{
// poll for access to a running pipedal server
this.hotspotReconnectTimer = setTimeout(
async () => {
let newUrl = await this.pollForLiveServer();
if (newUrl.length === 0)
{
this.startHotspotReconnectTimer();
} else {
this.cancelOnNetworkChanging();
window.location.replace(newUrl);
}
},
5*1000);
}
cancelOnNetworkChanging()
{
this.cancelHotspotReconnectTimer();
if (this.expectNetworkChangeTimeout)
{
clearTimeout(this.expectNetworkChangeTimeout);
this.expectNetworkChangeTimeout = undefined;
}
this.networkChanging = false;
this.expectDisconnect(ReconnectReason.Disconnected);
}
onNetworkChanging(hotspotConnected: boolean)
{
this.cancelOnNetworkChanging();
this.networkChanging = true;
this.networkChanging_expectHotspot = hotspotConnected;
this.expectDisconnect(ReconnectReason.HotspotChanging);
this.expectNetworkChangeTimeout = setTimeout(
() => {
this.cancelOnNetworkChanging();
},
30*1000);
}
};
let instance: PiPedalModel | undefined = undefined;
+48 -52
View File
@@ -26,7 +26,7 @@ export type ReconnectHandler = () => void;
export type ReconnectingHandler = (retry: number, maxRetries: number) => void;
const MAX_RETRIES = 6;
const MAX_RETRY_TIME = 90*1000;
const MAX_RETRY_TIME = 90 * 1000;
export type PiPedalMessageHeader = {
replyTo?: number;
@@ -37,10 +37,10 @@ type ReplyHandler = (header: PiPedalMessageHeader, body: any | null) => void;
export interface PiPedalSocketListener {
onMessageReceived : (header: PiPedalMessageHeader, body: any | null) => void;
onMessageReceived: (header: PiPedalMessageHeader, body: any | null) => void;
onError: (message: string, exception?: Error) => void;
onConnectionLost: () => void;
onReconnect: () => void;
onReconnect: () => void;
onReconnecting: (retry: number, maxRetries: number) => boolean;
};
@@ -60,7 +60,7 @@ class PiPedalSocket {
constructor(
url: string,
listener: PiPedalSocketListener
) {
) {
this.url = url;
this.listener = listener;
}
@@ -103,8 +103,7 @@ class PiPedalSocket {
_replyMap: Map<number, ReplyHandler> = new Map<number, ReplyHandler>();
_discardReplyReservations()
{
_discardReplyReservations() {
// it's ok. All pending reservations disappear into the GC.
this._replyMap = new Map<number, ReplyHandler>();
}
@@ -193,8 +192,7 @@ class PiPedalSocket {
return;
}
if (this.canReconnect)
{
if (this.canReconnect) {
this.listener.onConnectionLost();
this._reconnect();
}
@@ -212,26 +210,23 @@ class PiPedalSocket {
isBackground: boolean = false;
enterBackgroundState()
{
enterBackgroundState() {
this.isBackground = true;
this.close();
}
exitBackgroundState()
{
exitBackgroundState() {
this.isBackground = false;
this._reconnect();
}
reconnect() {
if (this.socket)
{
if (this.socket) {
this.close();
}
if (!this.listener.onReconnecting(this.retryCount,MAX_RETRIES)) {
if (!this.listener.onReconnecting(this.retryCount, MAX_RETRIES)) {
return;
}
++this.retryCount;
@@ -243,8 +238,7 @@ class PiPedalSocket {
this.listener.onReconnect();
})
.catch(error => {
if (this.totalRetryDelay === MAX_RETRY_TIME)
{
if (this.totalRetryDelay >= MAX_RETRY_TIME) {
this.listener.onError("Server connection lost.");
return;
} else {
@@ -258,8 +252,7 @@ class PiPedalSocket {
close(): void {
try {
if (this.socket)
{
if (this.socket) {
this.socket.onclose = null;
this.socket.onerror = null;
this.socket.onmessage = null;
@@ -267,50 +260,53 @@ class PiPedalSocket {
this.socket.close();
this.socket = undefined;
}
} catch (ignored)
{
} catch (ignored) {
}
this.socket = undefined;
}
connectInternal_(): Promise<WebSocket> {
return new Promise<WebSocket>((resolve, reject) => {
let ws = new WebSocket(this.url);
try {
let ws = new WebSocket(this.url);
let self = this;
let self = this;
ws.onmessage = this.handleMessage.bind(this);
ws.onclose = (event: Event) => {
ws.onclose = null;
ws.onerror = null;
reject("Connection closed unexpectedly.");
};
ws.onerror = (event: Event) => {
ws.onclose = null;
ws.onerror = null;
reject("Failed to connect.");
};
ws.onopen = (event: Event) => {
ws.onerror = self.handleError.bind(self);
ws.onclose = self.handleClose.bind(self);
ws.onopen = null;
resolve(ws);
ws.onmessage = this.handleMessage.bind(this);
ws.onclose = (event: Event) => {
ws.onclose = null;
ws.onerror = null;
reject("Connection closed unexpectedly.");
};
ws.onerror = (evt: Event) => {
ws.onclose = null;
ws.onerror = null;
reject("Failed to connect.");
};
ws.onopen = (event: Event) => {
ws.onerror = self.handleError.bind(self);
ws.onclose = self.handleClose.bind(self);
ws.onopen = null;
resolve(ws);
};
} catch (e: any){
reject("Failed to connect. " + e.toString());
};
});
}
connect(): Promise<void> {
return new Promise<void>((resolve, reject) => {
}
connect(): Promise < void> {
return new Promise<void>((resolve, reject) => {
this.connectInternal_()
.then((socket) => {
this.socket = socket;
resolve();
})
.catch((reason) => {
reject(reason);
});
});
}
this.connectInternal_()
.then((socket) => {
this.socket = socket;
resolve();
})
.catch((reason) => {
reject(reason);
});
});
}
}
+50 -51
View File
@@ -24,7 +24,7 @@ import ListSelectDialog from './ListSelectDialog';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
import {ColorTheme} from './DarkMode';
import { ColorTheme } from './DarkMode';
import ButtonBase from "@mui/material/ButtonBase";
import AppBar from '@mui/material/AppBar';
import Button from '@mui/material/Button';
@@ -394,11 +394,10 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
});
}
handleCheckForUpdates()
{
handleCheckForUpdates() {
this.model.showUpdateDialog();
}
handleMidiMessageSettings() {
this.setState({
showSystemMidiBindingsDialog: true
@@ -572,7 +571,8 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
(
<div>
<Typography display="block" variant="body1" color="textSecondary" style={{ paddingLeft: 24, paddingBottom: 8 }}>
Select and configure an audio device. You may optionally configure MIDI inputs, and set up a Wi-Fi Direct Hotspot now as well.
Select and configure an audio device. You may optionally configure MIDI inputs, and configure up a Wi-Fi Auto-Hotspot as well.
The Auto-Hotspot feature allows you to connect to Pipedal even if you don't have access to a Wi-Fi router.
</Typography>
<Typography display="block" variant="body1" color="textSecondary" style={{ paddingLeft: 24, paddingBottom: 8 }}>
Access and modify these settings later by selecting the <i>Settings</i> menu item on the main menu.
@@ -682,6 +682,20 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
<div >
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">CONNECTION</Typography>
<ButtonBase
className={classes.setting} disabled={!this.state.wifiConfigSettings.valid}
onClick={() => this.handleShowWifiConfigDialog()} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
Wi-Fi auto-hotspot</Typography>
<Typography display="block" variant="caption" noWrap color="textSecondary">
{this.state.wifiConfigSettings.getSummaryText()}
</Typography>
</div>
</ButtonBase>
{
this.state.isAndroidHosted &&
(
@@ -700,33 +714,6 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
)
}
<ButtonBase
className={classes.setting} disabled={!this.state.wifiConfigSettings.valid}
onClick={() => this.handleShowWifiDirectConfigDialog()} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
Configure Wi-Fi Direct hotspot</Typography>
<Typography display="block" variant="caption" noWrap color="textSecondary">
{this.state.wifiDirectConfigSettings.getSummaryText()}
</Typography>
</div>
</ButtonBase>
<ButtonBase className={classes.setting} disabled={!this.state.wifiConfigSettings.valid}
style={{ display: "none" }}
onClick={() => this.handleShowWifiConfigDialog()} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
Configure Wi-Fi hotspot</Typography>
<Typography display="block" variant="caption" noWrap color="textSecondary">
{this.state.wifiConfigSettings.getSummaryText()}
</Typography>
</div>
</ButtonBase>
</div>
{(!this.props.onboarding) ? (
<div >
@@ -756,8 +743,8 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
Color theme</Typography>
<Typography className={classes.secondaryItem} display="block" variant="caption" color="textSecondary" noWrap>
{ this.model.getTheme() === ColorTheme.Dark ? "Dark" :
(this.model.getTheme() === ColorTheme.Light ? "Light": "System")}
{this.model.getTheme() === ColorTheme.Dark ? "Dark" :
(this.model.getTheme() === ColorTheme.Light ? "Light" : "System")}
</Typography>
</div>
</ButtonBase>
@@ -765,9 +752,9 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
<ButtonBase
className={classes.setting}
onClick={() => {
onClick={() => {
this.model.setShowStatusMonitor(!this.state.showStatusMonitor)
}} >
}} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<div style={{
@@ -792,15 +779,21 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
</div>
</ButtonBase>
<ButtonBase
className={classes.setting}
onClick={() => { this.handleCheckForUpdates(); }} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
Check for updates...</Typography>
</div>
</ButtonBase>
{
this.model.enableAutoUpdate && (
<ButtonBase
className={classes.setting}
onClick={() => { this.handleCheckForUpdates(); }} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
Check for updates...</Typography>
</div>
</ButtonBase>
)
}
@@ -900,20 +893,26 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
(this.state.showThemeSelectDialog) &&
(
<SelectThemeDialog open={this.state.showThemeSelectDialog}
onClose={()=> { this.setState({showThemeSelectDialog: false});} }
onClose={() => { this.setState({ showThemeSelectDialog: false }); }}
onOk={(selectedTheme: ColorTheme) => {
this.model.setTheme(selectedTheme);
this.setState({showThemeSelectDialog: false});
this.setState({ showThemeSelectDialog: false });
}}
defaultTheme={this.model.getTheme()}
/>
)
}
<WifiConfigDialog wifiConfigSettings={this.state.wifiConfigSettings} open={this.state.showWifiConfigDialog}
onClose={() => this.setState({ showWifiConfigDialog: false })}
onOk={(wifiConfigSettings) => this.handleApplyWifiConfig(wifiConfigSettings)}
/>
{
(this.state.showWifiConfigDialog) &&
(
<WifiConfigDialog wifiConfigSettings={this.state.wifiConfigSettings} open={this.state.showWifiConfigDialog}
onClose={() => this.setState({ showWifiConfigDialog: false })}
onOk={(wifiConfigSettings) => this.handleApplyWifiConfig(wifiConfigSettings)}
/>
)
}
<WifiDirectConfigDialog wifiDirectConfigSettings={this.state.wifiDirectConfigSettings} open={this.state.showWifiDirectConfigDialog}
onClose={() => this.setState({ showWifiDirectConfigDialog: false })}
onOk={(wifiDirectConfigSettings: WifiDirectConfigSettings) => this.handleApplyWifiDirectConfig(wifiDirectConfigSettings)}
+17
View File
@@ -131,7 +131,24 @@ export const SystemMidiBindingDialog =
{
displayName = "Next Preset";
instanceId = 2;
} else if (item.symbol === "startHotspot")
{
displayName = "Enable Hotspot";
instanceId = 3;
} else if (item.symbol === "stopHotspot")
{
displayName = "Disable Hotspot";
instanceId = 4;
} else if (item.symbol === "shutdown")
{
displayName = "Shutdown";
instanceId = 5;
} else if (item.symbol === "reboot")
{
displayName = "Reboot";
instanceId = 6;
}
if (instanceId !== -1)
{
result.push(new BindingEntry(displayName,instanceId,item));
+557 -269
View File
@@ -18,17 +18,19 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import React from 'react';
import IconButton from '@mui/material/IconButton';
import VisibilityIcon from '@mui/icons-material/Visibility';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import FormHelperText from '@mui/material/FormHelperText';
import Button from '@mui/material/Button';
import Autocomplete from '@mui/material/Autocomplete';
import TextField from '@mui/material/TextField';
import DialogEx from './DialogEx';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import Switch from '@mui/material/Switch';
import FormControlLabel from '@mui/material/FormControlLabel';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import WifiConfigSettings from './WifiConfigSettings';
import NoChangePassword from './NoChangePassword';
import Typography from '@mui/material/Typography';
import { Theme } from '@mui/material/styles';
import { WithStyles } from '@mui/styles';
@@ -39,7 +41,8 @@ import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import WifiChannel from './WifiChannel';
import {PiPedalModel, PiPedalModelFactory} from './PiPedalModel';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
const styles = (theme: Theme) => createStyles({
pgraph: {
@@ -57,319 +60,604 @@ export interface WifiConfigProps extends WithStyles<typeof styles> {
}
export interface WifiConfigState {
autoStartMode: number;
showPassword: boolean;
fullScreen: boolean;
compactWidth: boolean;
compactHeight: boolean;
showWifiWarningDialog: boolean;
showHelpDialog: boolean;
wifiWarningGiven: boolean;
enabled: boolean;
name: string;
newPassword: string;
hasPassword: boolean;
nameError: boolean;
nameErrorMessage: string;
passwordError: boolean;
passwordErrorMessage: string;
homeNetworkSsid: string;
homeNetworkError: boolean;
homeNetworkErrorMessage: string;
countryCode: string;
channel: string;
knownWifiNetworks: string[];
wifiChannels: WifiChannel[];
}
let gCountryCodeOptions: { id: string, label: string }[] | undefined = undefined;
const WifiConfigDialog = withStyles(styles, { withTheme: true })(
const WifiConfigDialog = withStyles(styles, { withTheme: true })(
class extends ResizeResponsiveComponent<WifiConfigProps, WifiConfigState> {
refName: React.RefObject<HTMLInputElement>;
refPassword: React.RefObject<HTMLInputElement>;
model: PiPedalModel;
refName: React.RefObject<HTMLInputElement>;
refPassword: React.RefObject<HTMLInputElement>;
model: PiPedalModel;
constructor(props: WifiConfigProps) {
super(props);
this.model = PiPedalModelFactory.getInstance();
this.state = {
fullScreen: false,
enabled: this.props.wifiConfigSettings.enable,
name: this.props.wifiConfigSettings.hotspotName,
newPassword: "",
nameError: false,
nameErrorMessage: NBSP,
passwordError: false,
showWifiWarningDialog: false,
wifiWarningGiven: this.props.wifiConfigSettings.wifiWarningGiven,
passwordErrorMessage: NBSP,
countryCode: this.props.wifiConfigSettings.countryCode,
channel: this.props.wifiConfigSettings.channel,
wifiChannels: []
};
this.model.getWifiChannels(this.props.wifiConfigSettings.countryCode)
.then((wifiChannels_) => {
this.setState({wifiChannels: wifiChannels_});
}).catch((err)=> {});
this.refName = React.createRef<HTMLInputElement>();
this.refPassword = React.createRef<HTMLInputElement>();
}
mounted: boolean = false;
handleEnableChanged(e: any) {
this.setState({ enabled: e.target.checked });
}
onWindowSizeChanged(width: number, height: number): void {
this.setState({ fullScreen: height < 200 })
}
componentDidMount() {
super.componentDidMount();
this.mounted = true;
}
componentWillUnmount() {
super.componentWillUnmount();
this.mounted = false;
}
componentDidUpdate(prevProps: WifiConfigProps) {
if (this.props.open && !prevProps.open)
{
this.setState({
enabled: this.props.wifiConfigSettings.enable,
constructor(props: WifiConfigProps) {
super(props);
this.model = PiPedalModelFactory.getInstance();
this.state = {
showPassword: false,
fullScreen: false,
compactWidth: false,
compactHeight: false,
autoStartMode: this.props.wifiConfigSettings.autoStartMode,
name: this.props.wifiConfigSettings.hotspotName,
newPassword: "",
hasPassword: false,
nameError: false,
nameErrorMessage: NBSP,
passwordError: false,
homeNetworkSsid: this.props.wifiConfigSettings.homeNetwork,
homeNetworkError: false,
homeNetworkErrorMessage: NBSP,
showWifiWarningDialog: false,
showHelpDialog: false,
wifiWarningGiven: this.props.wifiConfigSettings.wifiWarningGiven,
passwordErrorMessage: NBSP,
countryCode: this.props.wifiConfigSettings.countryCode,
channel: this.props.wifiConfigSettings.channel
});
channel: this.props.wifiConfigSettings.channel,
knownWifiNetworks: [],
wifiChannels: []
};
this.requestWifiChannels(this.props.wifiConfigSettings.countryCode);
this.requestKnownWifiNetworks();
if (this.props.wifiConfigSettings.countryCode !== this.state.countryCode)
{
this.model.getWifiChannels(this.props.wifiConfigSettings.countryCode)
.then((wifiChannels) => {
this.setState({wifiChannels: wifiChannels});
})
.catch((error) => {});
this.refName = React.createRef<HTMLInputElement>();
this.refPassword = React.createRef<HTMLInputElement>();
}
mounted: boolean = false;
}
onWindowSizeChanged(width: number, height: number): void {
this.setState({ fullScreen: height < 200, compactWidth: width < 600, compactHeight: height < 450 })
}
}
handleOk(wifiWarningGiven: boolean) {
let hasError = false;
if (this.state.enabled)
{
let name = this.state.name;
if (name.length === 0) {
this.setState({nameError: true, nameErrorMessage: "* Required"});
hasError = true;
} else if (name.length > 31)
{
this.setState({nameError: true, nameErrorMessage: "> 31 characters"});
componentDidMount() {
super.componentDidMount();
this.mounted = true;
this.applyDeferredWifiChannels();
this.applyDeferredWifiNetworks();
}
componentWillUnmount() {
super.componentWillUnmount();
this.mounted = false;
}
private deferredWifiNetworks?: string[] = undefined;
applyDeferredWifiNetworks() {
if (this.mounted && this.deferredWifiNetworks) {
this.setState({ knownWifiNetworks: this.deferredWifiNetworks });
this.deferredWifiNetworks = undefined;
}
}
requestKnownWifiNetworks() {
this.model.getKnownWifiNetworks()
.then((networks: string[]) => {
if (this.mounted) {
this.setState({ knownWifiNetworks: networks });
} else {
this.deferredWifiNetworks = networks;
}
}).catch(() => {
});
}
private lastChannelRequestCountryCode = "";
private deferredWifiChannels?: WifiChannel[] = undefined;
applyDeferredWifiChannels() {
if (this.mounted && this.deferredWifiChannels) {
this.setState({ wifiChannels: this.deferredWifiChannels });
this.deferredWifiChannels = undefined;
}
}
requestWifiChannels(countryCode: string) {
if (countryCode === this.lastChannelRequestCountryCode) {
return;
}
this.lastChannelRequestCountryCode = countryCode;
this.model.getWifiChannels(countryCode)
.then((wifiChannels) => {
if (this.mounted) {
this.setState({ wifiChannels: wifiChannels });
} else {
}
})
.catch((error) => {
this.lastChannelRequestCountryCode = ""; // try again later. :-/
});
}
componentDidUpdate(prevProps: WifiConfigProps) {
if (this.props.open && !prevProps.open) {
this.setState({
name: this.props.wifiConfigSettings.hotspotName,
newPassword: "",
autoStartMode: this.props.wifiConfigSettings.autoStartMode,
homeNetworkSsid: this.props.wifiConfigSettings.homeNetwork,
wifiWarningGiven: this.props.wifiConfigSettings.wifiWarningGiven,
countryCode: this.props.wifiConfigSettings.countryCode,
channel: this.props.wifiConfigSettings.channel
});
this.requestKnownWifiNetworks();
this.requestWifiChannels(this.props.wifiConfigSettings.countryCode);
}
this.applyDeferredWifiChannels();
}
getDefaultPasswordValue(): string {
return (this.state.showPassword && this.state.newPassword === "") ?
"password" :
this.state.newPassword;
}
handleOk(wifiWarningGiven: boolean) {
let hasError = false;
if (this.state.autoStartMode !== 0) {
let name = this.state.name;
if (name.length === 0) {
this.setState({ nameError: true, nameErrorMessage: "* Required" });
hasError = true;
} else if (name.length > 31) {
this.setState({ nameError: true, nameErrorMessage: "> 31 characters" });
hasError = true;
}
let password = this.state.newPassword;
if (!this.props.wifiConfigSettings.hasSavedPassword && password.length === 0) {
this.setState({ passwordError: true, passwordErrorMessage: "* required" });
} else if (password.length > 0) {
if (password.length < 8) {
this.setState({ passwordError: true, passwordErrorMessage: "Less than 8 characters" });
hasError = true;
} else if (password.length > 63) {
this.setState({ passwordError: true, passwordErrorMessage: "> 63 characters" });
hasError = true;
}
}
if (this.state.autoStartMode === 2 && this.state.homeNetworkSsid.length === 0) {
this.setState({ homeNetworkError: true, homeNetworkErrorMessage: "* Required" });
hasError = true;
}
}
if (this.state.autoStartMode !== 0 && (!this.props.wifiConfigSettings.hasSavedPassword) && this.state.newPassword.length === 0) {
this.setState({ passwordError: true, passwordErrorMessage: "* Required" });
hasError = true;
}
let password = this.state.newPassword;
if (password.length > 0) {
if (password.length < 8) {
this.setState({passwordError: true, passwordErrorMessage: "Less than 8 characters"});
hasError = true;
if (!hasError) {
let wifiConfigSettings = new WifiConfigSettings();
wifiConfigSettings.valid = true;
} else if (password.length > 63) {
this.setState({passwordError: true, passwordErrorMessage: "> 63 characters"});
hasError = true;
wifiConfigSettings.autoStartMode = this.state.autoStartMode;
wifiConfigSettings.homeNetwork = this.state.homeNetworkSsid;
wifiConfigSettings.hotspotName = this.state.name;
wifiConfigSettings.countryCode = this.state.countryCode;
wifiConfigSettings.channel = this.state.channel;
if (this.state.newPassword.length === 0 || this.state.autoStartMode === 0) {
wifiConfigSettings.hasPassword = false;
} else {
wifiConfigSettings.hasPassword = true;
wifiConfigSettings.password = this.state.newPassword;
}
}
}
if (this.state.enabled && (!this.props.wifiConfigSettings.hasPassword) && this.state.newPassword.length === 0)
{
this.setState({passwordError: true, passwordErrorMessage: "* Required"});
hasError = true;
}
if (!hasError)
{
let wifiConfigSettings = new WifiConfigSettings();
wifiConfigSettings.valid = true;
wifiConfigSettings.enable = this.state.enabled;
wifiConfigSettings.hotspotName = this.state.name;
wifiConfigSettings.countryCode = this.state.countryCode;
wifiConfigSettings.channel = this.state.channel;
if (this.state.newPassword.length === 0 || !this.state.enabled)
{
wifiConfigSettings.hasPassword = false;
} else {
wifiConfigSettings.hasPassword = true;
wifiConfigSettings.password = this.state.newPassword;
}
if (!this.props.wifiConfigSettings.wifiWarningGiven && !wifiWarningGiven) {
this.setState({showWifiWarningDialog: true});
} else {
wifiConfigSettings.wifiWarningGiven = true;
this.preventPasswordPrompt();
// let preventPasswordPrompt changes settle (it's HARD to prevent a password prompt)
setTimeout(()=> {
if (!this.props.wifiConfigSettings.wifiWarningGiven && !wifiWarningGiven) {
this.setState({ showWifiWarningDialog: true });
} else {
wifiConfigSettings.wifiWarningGiven = true;
this.props.onOk(wifiConfigSettings);
},100);
}
}
}
}
preventPasswordPrompt()
{
let passwordInput = this.refPassword.current;
if (passwordInput)
{
passwordInput.type = "text";
passwordInput.value = "";
handleChannelChange(e: any) {
let value = e.target.value as string;
this.setState({ channel: value });
}
this.setState({newPassword: ""});
}
handleChannelChange(e: any)
{
let value = e.target.value as string;
this.setState({channel: value });
}
handleCountryChanged(e: any)
{
let value = e.target.value as string;
handleCountryChanged(e: any) {
let value = e.target.value as string;
this.setState({countryCode: value});
this.model.getWifiChannels(value)
.then(wifiChannels => {
this.setState({wifiChannels: wifiChannels});
})
.catch((error)=> { });
}
this.setState({ countryCode: value });
this.requestWifiChannels(value);
}
handleTogglePasswordVisibility() {
this.setState({ hasPassword: true, showPassword: !this.state.showPassword });
}
render() {
let props = this.props;
let classes = this.props.classes;
let { open, onClose} = props;
private homeNetworkSsidElement?: HTMLInputElement = undefined;
const handleClose = () => {
this.preventPasswordPrompt();
// let preventPasswordPrompt changes settle (it's HARD to prevent a password prompt)
setTimeout(()=> {
getCountryCodeValue(countryCode: string) {
let value = this.model.countryCodes[countryCode];
if (!value) return undefined;
return { label: value, id: countryCode };
}
getCountryCodeOptions(): { id: string, label: string }[] {
if (gCountryCodeOptions) {
return gCountryCodeOptions;
}
let result: { label: string, id: string }[] = [];
for (let key in this.model.countryCodes) {
let value = this.model.countryCodes[key];
result.push({ id: key, label: value });
}
result.sort((left, right) => {
return left.label.localeCompare(right.label);
});
if (result.length !== 0) {
gCountryCodeOptions = result;
}
return result;
}
render() {
let props = this.props;
let classes = this.props.classes;
let { open, onClose } = props;
const handleClose = () => {
onClose();
},100);
};
return (
<DialogEx tag="wifiConfig" open={open} fullWidth onClose={handleClose} style={{userSelect: "none"}}
fullScreen={this.state.fullScreen}
>
{ this.state.fullScreen && (
<DialogTitle>Wi-fi Hotspot</DialogTitle>
)}
<DialogContent>
<FormControlLabel
control={(
<Switch
checked={this.state.enabled}
onChange={(e: any) => this.handleEnableChanged(e)}
color="primary"
/>
)}
label="Enable"
/>
<TextField style={{ marginBottom: 16, marginTop: 16 }}
autoComplete="off"
id="name"
spellCheck="false"
label="SSID"
type="text"
fullWidth
error={this.state.nameError}
helperText={this.state.nameErrorMessage}
value={this.state.name}
onChange={(e) => this.setState({name: e.target.value, nameError: false, nameErrorMessage: NBSP})}
inputRef={this.refName}
disabled={!this.state.enabled}
/>
<div style={{ marginBottom: 16 }}>
<input type="password" style={{display:"none"}}/>
<NoChangePassword
inputRef={this.refPassword}
onPasswordChange={(text): void => { this.setState({ newPassword: text, passwordError: false, passwordErrorMessage: NBSP }); }}
hasPassword={this.props.wifiConfigSettings.hasPassword}
label="WEP Passphrase"
error={this.state.passwordError}
helperText={this.state.passwordErrorMessage}
defaultValue={this.state.newPassword}
disabled={!this.state.enabled}
/>
</div>
<div style={{ marginBottom: 16}}>
<FormControl>
<InputLabel htmlFor="countryCodeSelect">Country</InputLabel>
<Select variant="standard" id="countryCodeSelect" value={this.state.countryCode} style={{width: 220}}
onChange={(event)=>this.handleCountryChanged(event)} >
{Object.entries(this.model.countryCodes).map(([key,value])=> {
return (
<MenuItem value={key}>{value}</MenuItem>
);
})}
</Select>
</FormControl>
</div>
<div style={{ marginBottom: 24}}>
<FormControl>
<InputLabel htmlFor="channelSelect">Channel</InputLabel>
<Select variant="standard" id="channelSelect" value={this.state.channel} style={{width: 220}} onChange={(e)=>{
this.handleChannelChange(e);
}}>
{this.state.wifiChannels.map((channel)=> {
return (
<MenuItem value={channel.channelId}>{channel.channelName}</MenuItem>
)
})}
</Select>
</FormControl>
</div>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} variant="dialogSecondary" style={{ width: 120 }}>
Cancel
</Button>
<Button onClick={()=> this.handleOk(false)} variant="dialogPrimary" style={{ width: 120 }} >
OK
</Button>
</DialogActions>
<DialogEx open={this.state.showWifiWarningDialog} tag="wifiConfirm"
style={{userSelect: "none"}}>
};
let enabled = this.state.autoStartMode !== 0;
return (
<DialogEx tag="wifiConfig" open={open} fullWidth onClose={handleClose} style={{ userSelect: "none" }}
fullScreen={this.state.fullScreen}
>
{(this.state.fullScreen || !this.state.compactHeight) && (
<DialogTitle>Wi-fi Auto-Hotspot</DialogTitle>
)}
<DialogContent>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
Enabling the Wi-Fi hotspot will disable regular Wi-Fi network access on the PiPedal device. Once
enabled, connect to the hotspot and launch http://172.23.0.2 or http://{this.state.name}.local to access the PiPedal web app again.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary" gutterBottom>
Are you sure you want to proceed?
</Typography>
<div style={
!this.state.compactWidth
? { display: "flex", gap: 16, flexDirection: "column", flexFlow: "nowrap", alignItems: "start" }
: { display: "block", gap: 16, flexDirection: "row", flexFlow: "nowrap" }
}
>
<div style={{
display: "flex", flexGrow: 1, flexBasis: 100,
gap: 16, flexDirection: "column", flexFlow: "nowrap", alignItems: "start"
}}
>
<FormControl variant="standard" style={{ flexGrow: 1, flexBasis: 1 }} >
<InputLabel htmlFor="behavior">Auto-start hotspot when</InputLabel>
<Select id="behavior" value={this.state.autoStartMode}
onChange={(el) => {
let value = el.target.value as number
this.setState({
autoStartMode: value
});
if (value === 2 && this.state.homeNetworkSsid.length === 0 && this.state.knownWifiNetworks.length !== 0) {
this.setState({
homeNetworkSsid: this.state.knownWifiNetworks[0]
});
if (this.homeNetworkSsidElement) {
this.homeNetworkSsidElement.value = this.state.knownWifiNetworks[0];
}
}
}}
>
<MenuItem value={0}>Never</MenuItem>
<MenuItem value={1}>No ethernet connection</MenuItem>
<MenuItem value={2}>Not at home</MenuItem>
<MenuItem value={3}>No remembered Wi-Fi connections</MenuItem>
<MenuItem value={4}>Always</MenuItem>
</Select>
<FormHelperText>{NBSP}</FormHelperText>
</FormControl>
{(this.state.compactWidth || this.state.autoStartMode !== 2) && (
<IconButton style={{ flexGrow: 0, flexShrink: 0, marginTop: 8 }}
onClick={() => { this.setState({ showHelpDialog: true }); }}
>
<HelpOutlineIcon />
</IconButton>
)}
</div>
<div style={{
display: this.state.autoStartMode === 2 ? "flex" : "none", gap: 16, flexGrow: 1, flexBasis: 100, flexDirection: "column", flexFlow: "nowrap", alignItems: "center",
}}>
<Autocomplete options={this.state.knownWifiNetworks}
freeSolo autoSelect={false} forcePopupIcon={true}
style={{
flexGrow: 1, marginLeft: this.state.compactWidth ? 32 : 0
}}
value={this.state.homeNetworkSsid}
onChange={(event, value): void => {
this.setState({ homeNetworkSsid: value?.toString() ?? "", homeNetworkError: false, homeNetworkErrorMessage: NBSP });
}
}
renderInput={
(params) =>
<TextField {...params} variant="standard" label="Home Network SSID"
autoComplete="off"
spellCheck="false"
error={this.state.homeNetworkError}
onChange={
(event) => {
this.setState({ homeNetworkSsid: event.target.value, homeNetworkError: false, homeNetworkErrorMessage: NBSP });
}
}
helperText={this.state.homeNetworkErrorMessage}
InputLabelProps={{
shrink: true
}}
/>}
/>
<IconButton style={{ visibility: this.state.compactWidth ? "hidden" : "visible", flexGrow: 0, flexShrink: 0 }}
onClick={() => { this.setState({ showHelpDialog: true }); }}
>
<HelpOutlineIcon />
</IconButton>
</div>
</div>
<div style={
!this.state.compactWidth
? { display: "flex", flexFlow: "row nowrap", gap: 24, alignItems: "start" }
: { display: "block" }
}>
<TextField variant="standard" style={{ marginBottom: 8, flexGrow: 1, flexBasis: 1 }}
autoComplete="off"
spellCheck="false"
error={this.state.nameError}
id="name"
label="SSID"
type="text"
fullWidth
helperText={this.state.nameErrorMessage}
value={this.state.name}
onChange={(e) => this.setState({ name: e.target.value, nameError: false, nameErrorMessage: NBSP })}
inputRef={this.refName}
InputLabelProps={{
shrink: true
}}
/>
<div style={{ marginBottom: 8, flexGrow: 1, flexBasis: 1 }}>
<form autoComplete='off' onSubmit={() => false}> {/*Prevents chrome from saving passwords */}
<TextField label="WEP passphrase" variant="standard"
autoComplete="off"
spellCheck="false"
fullWidth
error={this.state.passwordError}
onFocus={() => { this.setState({ hasPassword: true }) }}
type={this.state.showPassword ? "text" : "password"}
onChange={(event): void => {
this.setState({ hasPassword: true, newPassword: event.target.value.toString(), passwordError: false, passwordErrorMessage: NBSP });
}
}
helperText={this.state.passwordErrorMessage}
defaultValue={
this.getDefaultPasswordValue()
}
disabled={!enabled}
InputLabelProps={{
shrink: true
}}
InputProps={{
startAdornment:
!this.state.hasPassword && !this.state.showPassword ?
(this.props.wifiConfigSettings.hasSavedPassword? "(Unchanged)" : "(Required)")
: ""
,
endAdornment: (
<IconButton size="small"
aria-label="toggle password visibility"
onClick={() => { this.handleTogglePasswordVisibility(); }}
>
{
(this.state.showPassword) ?
(
<VisibilityIcon fontSize="small" />
) :
(
<VisibilityOffIcon fontSize="small" />
)
}
</IconButton>
)
}}
/>
</form>
</div>
</div>
<div style={
!this.state.compactWidth
? { display: "flex", flexFlow: "row nowrap", gap: 24, alignItems: "start" }
: { display: "block" }
}>
<div style={{ display: "flex", marginBottom: 8, flexGrow: 1, flexBasis: 1 }}>
<Autocomplete fullWidth
defaultValue={this.getCountryCodeValue(this.state.countryCode)}
disableClearable={true}
onChange={(event, value) => { if (value) { this.setState({ countryCode: value.id }) } }}
options={this.getCountryCodeOptions()}
renderInput={(params) => (<TextField {...params} variant="standard" label="Regulatory Domain" />)}
disabled={!enabled}
/>
{/*
<Select variant="standard" label="Regulatory Domain" id="countryCodeSelect"
fullWidth value={this.state.countryCode} style={{}}
onChange={(event) => this.handleCountryChanged(event)} disabled={!enabled} >
{Object.entries(this.model.countryCodes).map(([key, value]) => {
return (
<MenuItem value={key}>{value}</MenuItem>
);
})}
</Select>
*/}
</div>
<div style={{ display: "flex", flexFlow: "column nowrap", marginBottom: 8, flexGrow: 1, flexBasis: 1 }}>
<FormControl style={{ flexGrow: 1 }} >
<InputLabel htmlFor="channelSelect">Channel</InputLabel>
<Select variant="standard" id="channelSelect" value={this.state.channel}
fullWidth
disabled={!enabled}
onChange={(e) => {
this.handleChannelChange(e);
}}>
{this.state.wifiChannels.map((channel) => {
return (
<MenuItem value={channel.channelId}>{channel.channelName}</MenuItem>
)
})}
</Select>
</FormControl>
</div>
</div>
</DialogContent>
<DialogActions>
<Button onClick={()=> this.setState({showWifiWarningDialog: false})} variant="dialogSecondary" style={{ width: 120 }}>
<Button onClick={handleClose} variant="dialogSecondary" style={{ width: 120 }}>
Cancel
</Button>
<Button onClick={()=> {
this.setState({showWifiWarningDialog: false});
this.handleOk(true);
}} variant="dialogPrimary" style={{ width: 120 }} >
PROCEED
<Button onClick={() => this.handleOk(false)} variant="dialogPrimary" style={{ width: 120 }} >
OK
</Button>
</DialogActions>
</DialogEx>
</DialogEx>
);
}
});
{this.state.showHelpDialog && (
<DialogEx open={this.state.showHelpDialog} tag="wifiHelp"
style={{ userSelect: "none" }}>
<DialogContent>
<Typography className={classes.pgraph} variant="h6" color="textPrimary">
PiPedal Auto-Hotspot
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
The PiPedal <b><i>Auto-Hotspot</i></b> feature allows you to connect to your Raspberry Pi even if you don't have
access to a Wi-Fi router. For example, if you are performing at a live venue, you probably will not
have access to a Wi-Fi router; but you can configure PiPedal so that your Raspberry Pi automatically
starts a Wi-Fi hotspot when you are not at home. The feature is primarily intended for use with the
PiPedal Android client, but you may find it useful for other purposes as well.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
Raspberry Pi's are unable to run hotspots, and have another active Wi-Fi connection at the same time; so the auto-hotspot feature
automatically turns the hotspot on, when your Raspberry Pi cannot otherwise be connected to, and can be configured to
automatically turn the PiPedal hotspot off when you do want your Raspberry Pi to connect to another Wi-Fi access point.
Which auto-start option you should select depends on how you normally connect to your Raspberry Pi when you are at home.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
If you normally connect to your Raspberry Pi using an ethernet connection, the <b><i>No ethernet connection</i></b> is a
good choice. The PiPedal Wi-Fi hotspot will be available whenever the ethernet cable is unplugged. <b><i>Always on</i></b> is
also a good choice, but may confuse your phone or tablet, since your Android device will now have to decide whether to auto-connect to your home Wi-Fi
router, or to the Raspberry Pi hotspot. If you use the <b><i>No ethernet connection</i></b> option, your phone or tablet will
never see the PiPedal hotspot and your Wi-Fi router at the same time.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
If you normally connect to your Raspberry Pi through a Wi-Fi router, <b><i>Not at home</i></b> is a good choice. The
PiPedal hotspot will be automatically turned off whenever your home Wi-Fi router is in range, and automatically turned on
when you are out of range of your home Wi-Fi router.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
If there are multiple locations, and multiple Wi-Fi routers you use with PiPedal on a regular basis, you can select
the <b><i>No remembered Wi-Fi connections</i></b> option, but this is a riskier option. The PiPedal hotspot will be automatically turned on if there are no
Wi-Fi access points in range that you have previously connected to from your Raspberry Pi, and will be automatically turned on otherwise.
The risk is that you could find yourself unable to connect to your Raspberry Pi when performing
at a local bar, after you have used your Rasberry Pi to connect to the Wi-Fi access point at the coffee shop nextdoor. (Public Wi-Fi access
points usually won't work because devices that are connected to a public access point can't connect to each other).
Will you ever do that? Probably not. But there is some risk that you might find yourself unable to connect at a live venue. Whether that's an
acceptable risk is up to you.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
Typically, when you're away from home, there's no easy way to connect to your Raspberry Pi from a laptop in order to
correct the problem. So you should carefully test that your auto-hotspot configuration works as expected before you adventure
away from home with PiPedal.
</Typography>
</DialogContent>
<DialogActions>
<Button
onClick={
() => {
this.setState({ showHelpDialog: false });
}}
variant="dialogSecondary" >
Ok
</Button>
</DialogActions>
</DialogEx>
)}
{this.state.showWifiWarningDialog && (
<DialogEx open={this.state.showWifiWarningDialog} tag="wifiConfirm"
style={{ userSelect: "none" }}>
<DialogContent>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
Enabling the Wi-Fi hotspot will disable regular Wi-Fi network access on your Raspberry Pi when the PiPedal hotspot is active.
If you are relying on Wi-Fi access to connect to your Raspberry Pi, consider carefully whether your autostart options
will allow you to connect to your Raspberry Pi once applied. PiPedal
<a href="https://rerdavies.github.io/pipedal/Configuring.html" target="_blank" rel="noreferrer">
online documentation</a> provides a discussion of how to choose safe hotspot auto-start options.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
When you are connected to the PiPedal hotspot, you can connect to the PiPedal web server at http://10.48.0.1.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary" gutterBottom>
Are you sure you want to continue?
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ showWifiWarningDialog: false })} variant="dialogSecondary" style={{ width: 120 }}>
Cancel
</Button>
<Button onClick={() => {
this.setState({ showWifiWarningDialog: false });
this.handleOk(true);
}} variant="dialogPrimary" style={{ width: 120 }} >
PROCEED
</Button>
</DialogActions>
</DialogEx>
)}
</DialogEx >
);
}
});
export default WifiConfigDialog;
+13 -11
View File
@@ -21,11 +21,14 @@
export default class WifiConfigSettings {
deserialize(input: any) : WifiConfigSettings{
this.autoStartMode = input.autoStartMode;
this.hasSavedPassword = input.hasSavedPassword;
this.homeNetwork = input.homeNetwork;
this.valid = input.valid;
this.wifiWarningGiven = input.wifiWarningGiven;
this.rebootRequired = input.rebootRequired;
this.enable = input.enable;
this.hotspotName = input.hotspotName;
this.mdnsName = input.mdnsName;
this.hasPassword = input.hasPassword;
this.password = input.password;
this.countryCode = input.countryCode;
@@ -35,29 +38,28 @@ export default class WifiConfigSettings {
clone() : WifiConfigSettings {
return new WifiConfigSettings().deserialize(this);
}
valid: boolean = true;
autoStartMode: number = 0;
hasSavedPassword: boolean = false;
homeNetwork: string = "";
mdnsName: string = "";
wifiWarningGiven: boolean = false;
enable: boolean = true;
hasPassword: boolean = false;
rebootRequired = false;
valid: boolean = false;
hotspotName: string = "pipedal";
password: string = "";
countryCode: string = "US";
channel: string = "g6";
channel: string = "0";
isEnabled() { return this.autoStartMode !== 0;}
getSummaryText() {
let result: string;
if (!this.valid) {
result = "Not available.";
} else if (!this.enable) {
} else if (this.autoStartMode === 0) {
result = "Disabled.";
} else {
result = this.hotspotName;
}
if (this.rebootRequired)
{
result += " (Restart required)";
}
return result;
}
+13 -9
View File
@@ -1,10 +1,14 @@
#!/bin/bash
#sign a package with rerdavies@gmail.com's private key.
cd build
for filename in *.deb; do
echo gpg --armor --output "$filename".asc -b "$filename"
gpg --armor --output "$filename".asc -b "$filename"
done
cd ..
# Sign the built .deb package with the private key used in the Auto-update process.
#
# Won't work for you, because you don't have MY private key.
#
# Auto-update requires that .deb files be distributed from the GitHub rerdavies/pipedal project.
# And auto-updates WILL update installs of your forked binary unless you take steps to prevent that.
#
# If you are distributing a fork, you should see the file UpdaterSecurity.hpp for details on how to
# modify pipedal to perform updates from an alternate github site. Or you could modify your fork
# to NOT do auto-updating (probably best). The build doesn't currently have a procedure for
# knocking out Auto-Update from UpdaterSecurity.hpp. But I would happily accept a pull request
# to bring such a feature back into mainline if you do it.
build/src/makeRelease
+88 -52
View File
@@ -202,8 +202,16 @@ namespace pipedal
{
Close();
#ifdef ALSADRIVER_CONFIG_DBG
snd_output_close(snd_output);
snd_pcm_status_free(snd_status);
if (snd_output)
{
snd_output_close(snd_output);
snd_output = nullptr;
}
if (snd_status)
{
snd_pcm_status_free(snd_status);
snd_status = nullptr;
}
#endif
}
@@ -248,17 +256,15 @@ namespace pipedal
std::mutex terminateSync;
bool terminateAudio_ = false;
std::atomic<bool> terminateAudio_ = false;
void terminateAudio(bool terminate)
{
std::lock_guard lock{terminateSync};
this->terminateAudio_ = terminate;
}
bool terminateAudio()
{
std::lock_guard lock{terminateSync};
return this->terminateAudio_;
}
@@ -1289,10 +1295,10 @@ namespace pipedal
FillOutputBuffer();
err = snd_pcm_start(handle);
// if (err < 0)
// {
// throw PiPedalStateException(SS("Can't recover from ALSA underrun. (" << snd_strerror(err) << ")"));
// }
if (err < 0)
{
throw PiPedalStateException(SS("Can't recover from ALSA underrun. (" << snd_strerror(err) << ")"));
}
return;
}
else if (err == -ESTRPIPE)
@@ -1312,10 +1318,10 @@ namespace pipedal
}
return;
}
throw PiPedalStateException(SS("ALSA error:" << snd_strerror(err)));
throw PiPedalStateException(SS("ALSA error: " << snd_strerror(err)));
}
std::jthread *audioThread;
std::jthread *audioThread = nullptr;
bool audioRunning;
bool block = false;
@@ -1422,45 +1428,58 @@ namespace pipedal
}
// snd_pcm_wait(captureHandle, 1);
ssize_t framesRead;
if ((framesRead = ReadBuffer(captureHandle, this->rawCaptureBuffer, bufferSize)) < 0)
ssize_t framesToRead = bufferSize;
ssize_t framesRead = 0;
bool xrun = false;
while (framesToRead != 0)
{
this->driverHost->OnUnderrun();
auto state = snd_pcm_state(playbackHandle);
XrunRecoverInputUnderrun(captureHandle, framesRead);
continue;
}
else
{
cpuUse.AddSample(ProfileCategory::Read);
if (framesRead == 0)
continue;
if (framesRead != bufferSize)
{
throw PiPedalStateException("Invalid read.");
}
(this->*copyInputFn)(framesRead);
cpuUse.AddSample(ProfileCategory::Driver);
this->driverHost->OnProcess(framesRead);
cpuUse.AddSample(ProfileCategory::Execute);
(this->*copyOutputFn)(framesRead);
cpuUse.AddSample(ProfileCategory::Driver);
// process.
ssize_t err = WriteBuffer(playbackHandle, rawPlaybackBuffer, framesRead);
if (err < 0)
ssize_t thisTime = framesToRead;
ssize_t nFrames;
if ((nFrames = ReadBuffer(
captureHandle,
this->rawCaptureBuffer + this->captureFrameSize*framesRead,
bufferSize)) < 0)
{
this->driverHost->OnUnderrun();
XrunRecoverOutputOverrun(playbackHandle, err);
auto state = snd_pcm_state(playbackHandle);
XrunRecoverInputUnderrun(captureHandle, nFrames);
xrun = true;
break;
}
cpuUse.AddSample(ProfileCategory::Write);
framesRead += nFrames;
framesToRead -= nFrames;
}
if (xrun)
{
continue;
}
cpuUse.AddSample(ProfileCategory::Read);
if (framesRead == 0)
continue;
if (framesRead != bufferSize)
{
throw PiPedalStateException("Invalid read.");
}
(this->*copyInputFn)(framesRead);
cpuUse.AddSample(ProfileCategory::Driver);
this->driverHost->OnProcess(framesRead);
cpuUse.AddSample(ProfileCategory::Execute);
(this->*copyOutputFn)(framesRead);
cpuUse.AddSample(ProfileCategory::Driver);
// process.
ssize_t err = WriteBuffer(playbackHandle, rawPlaybackBuffer, framesRead);
if (err < 0)
{
this->driverHost->OnUnderrun();
XrunRecoverOutputOverrun(playbackHandle, err);
}
cpuUse.AddSample(ProfileCategory::Write);
}
}
catch (const std::exception &e)
@@ -1593,8 +1612,8 @@ namespace pipedal
int dataLength = 0;
int dataIndex = 0;
size_t statusBytesRemaining = 0;
size_t data0;
size_t data1;
size_t data0 =0;
size_t data1 = 0;
size_t eventCount = 0;
MidiEvent events[MAX_MIDI_EVENT];
@@ -1862,14 +1881,21 @@ namespace pipedal
{
const auto &devices = channelSelection.GetInputMidiDevices();
midiStates.resize(devices.size());
midiStates.reserve(devices.size());
for (size_t i = 0; i < devices.size(); ++i)
{
const auto &device = devices[i];
MidiState *state = new MidiState();
midiStates[i] = state;
state->Open(device);
MidiState *midiState = nullptr;
try {
midiState = new MidiState();
midiState->Open(device);
midiStates.push_back(midiState);
} catch (const std::exception &e)
{
// logged already.
delete midiState;
}
}
}
@@ -2085,16 +2111,26 @@ namespace pipedal
throw;
}
if (playbackHwParams)
{
snd_pcm_hw_params_free(playbackHwParams);
if (captureHwParams)
playbackHwParams = nullptr;
}
if (captureHwParams) {
snd_pcm_hw_params_free(captureHwParams);
captureHwParams = nullptr;
}
if (playbackHandle)
{
snd_pcm_close(playbackHandle);
playbackHandle = nullptr;
}
if (captureHandle)
{
snd_pcm_close(captureHandle);
captureHandle = nullptr;
}
return result;
}
+1 -1
View File
@@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2022 Robin E. R. 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
+61 -10
View File
@@ -296,6 +296,10 @@ private:
SystemMidiBinding nextMidiBinding;
SystemMidiBinding prevMidiBinding;
SystemMidiBinding startHotspotMidiBinding;
SystemMidiBinding stopHotspotMidiBinding;
SystemMidiBinding rebootMidiBinding;
SystemMidiBinding shutdownMidiBinding;
JackChannelSelection channelSelection;
std::atomic<bool> active = false;
@@ -345,17 +349,14 @@ private:
virtual void Close()
{
std::lock_guard guard{mutex};
if (!isOpen)
return;
isOpen = false;
if (realtimeMonitorPortSubscriptions != nullptr)
{
delete realtimeMonitorPortSubscriptions;
realtimeMonitorPortSubscriptions = nullptr;
std::lock_guard guard{mutex};
if (!isOpen)
return;
isOpen = false;
}
if (active)
{
audioDriver->Deactivate();
@@ -367,6 +368,7 @@ private:
StopReaderThread();
// release any pdealboards owned by the process thread.
this->activePedalboards.resize(0);
this->realtimeActivePedalboard = nullptr;
@@ -742,6 +744,34 @@ private:
this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId), -1);
}
}
else if (this->shutdownMidiBinding.IsMatch(event))
{
if (shutdownMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::Shutdown);
}
}
else if (this->rebootMidiBinding.IsMatch(event))
{
if (rebootMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::Reboot);
}
}
else if (this->startHotspotMidiBinding.IsMatch(event))
{
if (startHotspotMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::StartHotspot);
}
}
else if (this->stopHotspotMidiBinding.IsMatch(event))
{
if (stopHotspotMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::StopHotspot);
}
}
else if (midiProgramChangePending)
{
// defer the message for processing after the program change has completed.
@@ -783,7 +813,7 @@ private:
virtual void OnAudioTerminated() override
{
this->active = false;
Lv2Log::info("Audio stopped.");
Lv2Log::info("Audio thread terminated.");
}
@@ -1182,6 +1212,12 @@ public:
hostReader.read(&request);
pNotifyCallbacks->OnNotifyNextMidiProgram(request);
}
else if (command == RingBufferCommand::RealtimeMidiEvent)
{
RealtimeMidiEventRequest request;
hostReader.read(&request);
pNotifyCallbacks->OnNotifyMidiRealtimeEvent(request.eventType);
}
else if (command == RingBufferCommand::Lv2ErrorMessage)
{
size_t size;
@@ -1710,6 +1746,7 @@ void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding> &bindin
{
for (auto i = bindings.begin(); i != bindings.end(); ++i)
{
if (i->symbol() == "nextProgram")
{
this->nextMidiBinding.SetBinding(*i);
@@ -1717,6 +1754,20 @@ void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding> &bindin
else if (i->symbol() == "prevProgram")
{
this->prevMidiBinding.SetBinding(*i);
} else if (i->symbol() == "startHotspot")
{
this->startHotspotMidiBinding.SetBinding(*i);
} else if (i->symbol() == "stopHotspot")
{
this->stopHotspotMidiBinding.SetBinding(*i);
} else if (i->symbol() == "reboot")
{
this->rebootMidiBinding.SetBinding(*i);
} else if (i->symbol() == "shutdown")
{
this->shutdownMidiBinding.SetBinding(*i);
} else {
Lv2Log::error(SS("Invalid system midi binding: " << i->symbol()));
}
}
}
+2
View File
@@ -30,6 +30,7 @@
#include "PiPedalAlsa.hpp"
#include "Promise.hpp"
#include "json_variant.hpp"
#include "RealtimeMidiEventType.hpp"
namespace pipedal {
@@ -160,6 +161,7 @@ public:
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest) = 0;
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) = 0;
virtual void OnNotifyLv2RealtimeError(int64_t instanceId,const std::string &error) = 0;
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) = 0;
};
+300 -124
View File
@@ -1,18 +1,18 @@
/*
* MIT License
*
*
* 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
* 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
@@ -24,6 +24,7 @@
#include "AvahiService.hpp"
#include <string.h>
#include <thread>
#include <avahi-client/client.h>
#include <avahi-client/publish.h>
#include <avahi-common/alternative.h>
@@ -35,6 +36,7 @@
#include <unistd.h>
#include "Lv2Log.hpp"
#include "ss.hpp"
#include <chrono>
using namespace pipedal;
@@ -43,37 +45,122 @@ void AvahiService::Announce(
const std::string &name,
const std::string &instanceId,
const std::string &mdnsName,
bool addTestGroup)
bool wait)
{
Unannounce();
this->portNumber = portNumber;
this->instanceId = instanceId;
this->mdnsName = mdnsName;
this->addTestGroup = addTestGroup;
this->name = avahi_strdup(name.c_str());
Start();
}
void AvahiService::Unannounce()
{
Stop();
if (name)
if (terminated)
return;
// We should update an existing group if the name doesn't change.
// but in practice, the name is the only thing that's going to change without restarting the pipedal server.
if (this->serviceName == name)
{
avahi_free(name);
this->name = nullptr;
return;
}
// this->name = avahi_strdup(name.c_str());
if (!started)
{
this->portNumber = portNumber;
this->instanceId = instanceId;
this->mdnsName = mdnsName;
this->serviceName = name;
started = true;
makeAnnouncement = true;
createPending = true;
Start();
if (wait) {
Wait();
}
}
else
{
// already started, so we have to use poll_lock instead.
avahi_threaded_poll_lock(threadedPoll);
// all state that we have to protect with the lock now that the avahi serv3ce is running.
this->portNumber = portNumber;
this->instanceId = instanceId;
this->mdnsName = mdnsName;
this->serviceName = name;
this->makeAnnouncement = true;
// we've requested a start but have not created the group.
if (this->createPending)
{
// the first create will use our freshly updated parameters.
// no action required.
}
else
{
// otherwise we have to use a lock to effect the update.
if (group)
{
avahi_entry_group_reset(group); // unannounce the previous
SetState(ServiceState::Reset);
create_group(client);
}
else
{
Lv2Log::error("Failed to update mDNS service because of a synch problem.");
}
}
avahi_threaded_poll_unlock(threadedPoll);
if (wait)
{
Wait();
}
}
this->group = nullptr;
this->client = nullptr;
this->threadedPoll = nullptr;
}
void AvahiService::Unannounce(bool wait)
{
if (terminated)
return;
if (this->threadedPoll)
{
avahi_threaded_poll_lock(threadedPoll);
if (started)
{
if (this->createPending)
{
// if service is starting up, and we haven't yet made our first announcement,
// just signal that we don't want to announc.
this->makeAnnouncement = false;
}
else
{
this->makeAnnouncement = false;
// we have previously made a successful announcement. Retract it.
if (group)
{
int rc = avahi_entry_group_reset(group);
if (rc < 0)
{
Lv2Log::error("Avahi: failed to un-announce.");
}
}
}
}
avahi_threaded_poll_unlock(threadedPoll);
if (wait)
{
WaitForUnannounce();
}
}
}
void AvahiService::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userData)
{
((AvahiService *)userData)->EntryGroupCallback(g, state);
}
void AvahiService::SetState(ServiceState serviceState)
{
this->serviceState = serviceState;
}
void AvahiService::EntryGroupCallback(AvahiEntryGroup *g, AvahiEntryGroupState state)
{
group = g;
@@ -81,29 +168,39 @@ void AvahiService::EntryGroupCallback(AvahiEntryGroup *g, AvahiEntryGroupState s
switch (state)
{
case AVAHI_ENTRY_GROUP_ESTABLISHED:
SetState(ServiceState::Established);
Lv2Log::info(SS("DNS/SD group established."));
/* The entry group has been established successfully */
Lv2Log::debug(SS("Service " << name << " successfully established."));
break;
case AVAHI_ENTRY_GROUP_COLLISION:
{
char *n;
/* A service name collision with a remote service
* happened. Let's pick a new name */
n = avahi_alternative_service_name(name);
avahi_free(name);
name = n;
Lv2Log::error(SS("Service name collision, renaming service to '" << name << "'\n"));
* happened. Let's pick a new name */
SetState(ServiceState::Collision);
char *n = avahi_alternative_service_name(avahiNameString);
avahi_free(avahiNameString);
avahiNameString = n;
Lv2Log::warning(SS("Service name collision, renaming service to '" << avahiNameString << "'\n"));
/* And recreate the services */
create_group(avahi_entry_group_get_client(g));
break;
}
case AVAHI_ENTRY_GROUP_FAILURE:
Lv2Log::error(SS("Entry group failure: " << avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))) << "\n"));
Lv2Log::error(SS("DNS/SD service shutting down."));
/* Some kind of failure happened while we were registering our services */
terminated = true;
avahi_threaded_poll_quit(threadedPoll);
SetState(ServiceState::Failed);
break;
case AVAHI_ENTRY_GROUP_UNCOMMITED:
SetState(ServiceState::Uncommited);
break;
case AVAHI_ENTRY_GROUP_REGISTERING:;
SetState(ServiceState::Registering);
break;
}
}
@@ -136,11 +233,14 @@ static int toRawDns(char *rawResult, size_t size, const std::string &name)
}
void AvahiService::create_group(AvahiClient *c)
{
this->createPending = false;
char *n;
int ret;
assert(c);
/* If this is the first time we're called, let's create a new
* entry group if necessary */
SetState(ServiceState::Requested);
if (!group)
{
if (!(group = avahi_entry_group_new(c, entry_group_callback, (void *)this)))
@@ -151,20 +251,26 @@ void AvahiService::create_group(AvahiClient *c)
}
/* If the group is empty (either because it was just created, or
* because it was reset previously, add our entries. */
if (avahi_entry_group_is_empty(group))
if (this->makeAnnouncement && avahi_entry_group_is_empty(group))
{
Lv2Log::debug(SS("Adding service '" << name << "'"));
Lv2Log::debug(SS("Adding service '" << avahiNameString << "'"));
std::string instanceTxtRecord = SS("id=" << this->instanceId);
#define PIPEDAL_SERVICE_TYPE "_pipedal._tcp"
if (avahiNameString)
{
avahi_free(avahiNameString);
avahiNameString = nullptr;
}
avahiNameString = avahi_strdup(serviceName.c_str());
if ((ret = avahi_entry_group_add_service(
group,
AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC, // IPv4 for now, until we figure out why dnsqmasq borks IPv6 routing.)
AVAHI_PROTO_INET, // IPv4 for now, until we figure out why dnsqmasq borks IPv6 routing.)
(AvahiPublishFlags)0,
name,
avahiNameString,
PIPEDAL_SERVICE_TYPE,
NULL,
NULL,
@@ -180,55 +286,31 @@ void AvahiService::create_group(AvahiClient *c)
goto fail;
}
if (this->addTestGroup)
{
Lv2Log::info("Added tests DNS/SD service.");
std::string instanceTxtRecord = SS("id=" << "0a6045b0-1753-4104-b3e4-b9713b9cc360");
if ((ret = avahi_entry_group_add_service(
group,
AVAHI_IF_UNSPEC,
AVAHI_PROTO_INET, // IPv4 for now, until we figure out why dnsqmasq borks IPv6 routing.)
(AvahiPublishFlags)0,
"Ed's PiPedal",
PIPEDAL_SERVICE_TYPE,
NULL,
NULL,
portNumber,
// txt records.
instanceTxtRecord.c_str(),
NULL)) < 0)
{
if (ret == AVAHI_ERR_COLLISION)
goto collision;
Lv2Log::error(SS("Failed to add _pipedal._tcp service: " << avahi_strerror(ret)));
goto fail;
}
}
/* Tell the server to register the service */
if ((ret = avahi_entry_group_commit(group)) < 0)
{
Lv2Log::error(SS("Failed to commit entry group: " << avahi_strerror(ret)));
goto fail;
}
Lv2Log::info(SS("DNS/SD service announced."));
Lv2Log::info(SS("DNS/SD service announced. (" << this->avahiNameString << ")"));
}
return;
collision:
/* A service name collision with a local service happened. Let's
* pick a new name */
n = avahi_alternative_service_name(name);
avahi_free(name);
name = n;
Lv2Log::warning(SS("Service name collision, renaming service to '" << name << "'"));
* pick a new avahiNameString */
n = avahi_alternative_service_name(avahiNameString);
avahi_free(avahiNameString);
avahiNameString = n;
Lv2Log::warning(SS("Service name collision, renaming service to '" << avahiNameString << "'"));
avahi_entry_group_reset(group);
create_group(c);
return;
fail:
terminated = true;
avahi_threaded_poll_quit(threadedPoll);
Lv2Log::error("DNS/SD service shutting down.");
SetState(ServiceState::Failed);
}
void AvahiService::client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void *userdata)
@@ -244,7 +326,7 @@ void AvahiService::ClientCallback(AvahiClient *c, AvahiClientState state)
{
case AVAHI_CLIENT_S_RUNNING:
/* The server has startup successfully and registered its host
* name on the network, so it's time to create our services */
* name on the network, so it's time to create our services */
create_group(c);
break;
case AVAHI_CLIENT_FAILURE:
@@ -253,11 +335,11 @@ void AvahiService::ClientCallback(AvahiClient *c, AvahiClientState state)
if (this->clientErrno == AVAHI_ERR_DISCONNECTED)
{
// tear the client down and restart it
Lv2Log::info("Avahi connection lost. Reconnecting.");
Lv2Log::info("DNS/SD connection lost. Reconnecting.");
if (group)
{
avahi_entry_group_reset(group);
avahi_entry_group_free(group);
group = nullptr;
}
@@ -272,28 +354,35 @@ void AvahiService::ClientCallback(AvahiClient *c, AvahiClientState state)
if (!client)
{
Lv2Log::error(SS("Failed to create client: " << avahi_strerror(error)));
terminated = true;
avahi_threaded_poll_quit(threadedPoll);
SetState(ServiceState::Failed);
}
}
else
{
Lv2Log::error(SS("Client failure: " << avahi_strerror(avahi_client_errno(c))));
terminated = true;
Lv2Log::error(SS("DNS/SD client failure: " << avahi_strerror(avahi_client_errno(c))));
avahi_threaded_poll_quit(threadedPoll);
SetState(ServiceState::Failed);
}
break;
case AVAHI_CLIENT_S_COLLISION:
/* Let's drop our registered services. When the server is back
* in AVAHI_SERVER_RUNNING state we will register them
* again with the new host name. */
* in AVAHI_SERVER_RUNNING state we will register them
* again with the new host name. */
case AVAHI_CLIENT_S_REGISTERING:
/* The server records are now being established. This
* might be caused by a host name change. We need to wait
* for our own records to register until the host name is
* properly esatblished. */
* might be caused by a host name change. We need to wait
* for our own records to register until the host name is
* properly esatblished. */
if (group)
avahi_entry_group_reset(group);
SetState(ServiceState::Settling);
break;
case AVAHI_CLIENT_CONNECTING:
SetState(ServiceState::Settling);
break;
case AVAHI_CLIENT_CONNECTING:;
}
}
@@ -304,72 +393,159 @@ void AvahiService::Start()
int ret = 1;
struct timeval tv;
this->clientErrno = 0;
while (true)
SetState(ServiceState::Initializing);
for (int retry = 0; retry < 3; ++retry)
{
/* Allocate main loop object */
if (!(this->threadedPoll = avahi_threaded_poll_new()))
if (!this->threadedPoll)
{
Lv2Log::error("Failed to create Avahi poll object.");
goto fail;
if (!(this->threadedPoll = avahi_threaded_poll_new()))
{
Lv2Log::error("Failed to create Avahi poll object.");
goto fail;
}
avahi_threaded_poll_start(threadedPoll);
}
/* Allocate a new client */
client = avahi_client_new(avahi_threaded_poll_get(threadedPoll), AvahiClientFlags::AVAHI_CLIENT_NO_FAIL, client_callback, (void *)this, &error);
/* Check wether creating the client object succeeded */
if (!client)
{
Lv2Log::error(SS("Failed to create client: " << avahi_strerror(error)));
goto fail;
/* Allocate a new client */
client = avahi_client_new(avahi_threaded_poll_get(threadedPoll), AvahiClientFlags::AVAHI_CLIENT_NO_FAIL, client_callback, (void *)this, &error);
/* Check wether creating the client object succeeded */
if (!client)
{
Lv2Log::error(SS("Failed to create client: " << avahi_strerror(error)));
goto fail;
}
}
avahi_threaded_poll_start(threadedPoll);
ret = 0;
return;
fail:
/* Cleanup things */
if (client)
{
avahi_client_free(client);
client = nullptr;
}
if (group)
{
avahi_entry_group_reset(group);
group = nullptr;
}
if (threadedPoll)
{
avahi_threaded_poll_free(threadedPoll);
threadedPoll = nullptr;
}
Stop();
sleep(1);
SetState(ServiceState::Failed);
}
return;
}
void AvahiService::Stop()
{
if (stopped)
return;
this->stopped = true;
Unannounce(true);
sleep(1); // let traffic setting. avahi doens't seem to shut down cleany otherwise.
if (threadedPoll)
{
avahi_threaded_poll_lock(threadedPoll);
if (group)
{
avahi_entry_group_free(group);
group = nullptr;
}
avahi_threaded_poll_unlock(threadedPoll);
avahi_threaded_poll_lock(threadedPoll);
if (client)
{
avahi_client_free(client);
client = nullptr;
}
avahi_threaded_poll_unlock(threadedPoll);
avahi_threaded_poll_stop(threadedPoll);
}
/* Cleanup things */
// client owns the group?
// if (group)
// {
// avahi_entry_group_reset(group);
// group = nullptr;
// }`
if (client)
{
avahi_client_free(client);
client = nullptr;
}
if (threadedPoll)
{
avahi_threaded_poll_free(threadedPoll);
SetState(ServiceState::Closed);
threadedPoll = nullptr;
}
group = nullptr;
/* Cleanup things */
if (avahiNameString)
{
avahi_free(avahiNameString);
avahiNameString = nullptr;
}
Lv2Log::info("DNS/SD service stopped.");
}
void AvahiService::Wait()
{
using clock = std::chrono::steady_clock;
auto start = clock::now();
auto waitTime = start + std::chrono::duration_cast<clock::duration>(std::chrono::seconds(5));
while (true)
{
bool done;
switch (serviceState)
{
default:
case ServiceState::Unitialized:
throw std::runtime_error("Invalid state");
case ServiceState::Initializing:
case ServiceState::Settling:
case ServiceState::Requested:
case ServiceState::Uncommited:
case ServiceState::Registering:
case ServiceState::Collision:
done = false;
break;
case ServiceState::Reset:
case ServiceState::Established:
case ServiceState::Failed:
case ServiceState::Closed:
done = true;
break;
};
if (done) break;
if (clock::now() > waitTime)
{
Lv2Log::error("DNS/SD announcement timed out.");
}
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
}
void AvahiService::WaitForUnannounce()
{
using clock = std::chrono::steady_clock;
auto start = clock::now();
auto waitTime = start + std::chrono::duration_cast<clock::duration>(std::chrono::seconds(5));
while (true)
{
bool done;
switch (serviceState)
{
default:
throw std::runtime_error("Invalid state");
case ServiceState::Initializing:
case ServiceState::Settling:
case ServiceState::Requested:
case ServiceState::Registering:
case ServiceState::Collision:
done = false;
break;
case ServiceState::Uncommited:
case ServiceState::Unitialized:
case ServiceState::Reset:
case ServiceState::Established:
case ServiceState::Failed:
case ServiceState::Closed:
done = true;
break;
};
if (done) break;
if (clock::now() > waitTime)
{
Lv2Log::error("DNS/SD unannounce timed out.");
}
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
}
+32 -8
View File
@@ -26,10 +26,12 @@
#include <thread>
#include <string>
#include <atomic>
// forward declarations.
class AvahiEntryGroup;
class AvahiThreadedPoll;
#include <atomic>
#include <avahi-client/client.h>
@@ -38,13 +40,37 @@ namespace pipedal {
class AvahiService {
public:
~AvahiService() { Unannounce(); }
~AvahiService() { Stop(); }
void Announce(
int portNumber, const std::string &name, const std::string &instanceId, const std::string&mdnsName,bool addTestGroup = false);
void Unannounce();
int portNumber, const std::string &name, const std::string &instanceId, const std::string&mdnsName, bool wait);
void Unannounce(bool wait);
private:
enum class ServiceState {
Unitialized,
Initializing,
Settling,
Requested,
Uncommited,
Registering,
Collision,
Reset,
Established,
Failed,
Closed
};
std::atomic<ServiceState> serviceState;
void Wait();
void WaitForUnannounce();
void SetState(ServiceState serviceState);
std::atomic<bool> terminated = false;
bool stopped = false;
bool started = false;
bool createPending = false;
bool makeAnnouncement = false;
void Start();
void Stop();
static void entry_group_callback(AvahiEntryGroup*g, AvahiEntryGroupState state, void *userData);
@@ -56,19 +82,17 @@ namespace pipedal {
void create_group(AvahiClient *c);
void threadProc();
int clientErrno = 0;
int portNumber = -1;
std::string instanceId;
std::string serviceName;
std::string mdnsName;
bool addTestGroup = false;
AvahiClient *client = NULL;
AvahiEntryGroup *group = NULL;
AvahiThreadedPoll *threadedPoll = NULL;
char*name = NULL;
char*avahiNameString = NULL;
};
+49 -17
View File
@@ -9,6 +9,8 @@ set(CXX_STANDARD 20)
include(FindPkgConfig)
find_package(sdbus-c++ REQUIRED)
if(DEFINED ENV{GITHUB_ACTIONS})
message(STATUS "Building in GitHub Actions environment")
@@ -94,7 +96,7 @@ endif()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set (USE_SANITIZE False)
set (USE_SANITIZE true)
if (!ENABLE_VST3)
@@ -144,9 +146,12 @@ else()
endif()
set (PIPEDAL_SOURCES
RealtimeMidiEventType.hpp
DBusToLv2Log.cpp DBusToLv2Log.hpp
HotspotManager.cpp HotspotManager.hpp
UpdateResults.cpp UpdateResults.hpp
UpdaterSecurity.hpp
Updater.cpp Updater.hpp
Updater.cpp Updater.hpp UpdaterStatus.hpp
Lv2PluginChangeMonitor.cpp Lv2PluginChangeMonitor.hpp
WebServerConfig.cpp WebServerConfig.hpp
Locale.hpp Locale.cpp
@@ -190,7 +195,6 @@ set (PIPEDAL_SOURCES
RequestHandler.hpp
Scratch.cpp PluginHost.hpp PluginHost.cpp
PluginType.hpp PluginType.cpp
Lv2Log.hpp Lv2Log.cpp
PiPedalSocket.hpp PiPedalSocket.cpp
PiPedalVersion.hpp PiPedalVersion.cpp
PiPedalModel.hpp PiPedalModel.cpp
@@ -252,6 +256,7 @@ set (PIPEDAL_INCLUDES
)
set(PIPEDAL_LIBS libpipedald zip
PiPedalCommon
icui18n
icuuc
icudata
@@ -290,6 +295,16 @@ target_link_libraries(pipedald PRIVATE PiPedalCommon
)
#################################
add_executable(hotspotManagerTest
hotspotManagerTestMain.cpp
HotspotManager.cpp HotspotManager.hpp)
target_link_libraries(hotspotManagerTest PRIVATE ${PIPEDAL_LIBS})
set_target_properties(hotspotManagerTest PROPERTIES EXCLUDE_FROM_ALL true)
add_executable(pipedaltest testMain.cpp
InvertingMutexTest.cpp
@@ -313,10 +328,12 @@ add_executable(pipedaltest testMain.cpp
MemDebug.cpp
MemDebug.hpp
)
target_link_libraries(pipedaltest PRIVATE PiPedalCommon)
target_link_libraries(pipedaltest PRIVATE ${PIPEDAL_LIBS})
target_include_directories(pipedaltest PRIVATE ${PIPEDAL_INCLUDES}
)
set_target_properties(pipedaltest PROPERTIES EXCLUDE_FROM_ALL true)
if (NOT GITHUB_ACTIONS) # Google perftools are not available on Github action servers.
@@ -327,6 +344,9 @@ if (NOT GITHUB_ACTIONS) # Google perftools are not available on Github action se
target_include_directories(profilePlugin PRIVATE ${PIPEDAL_INCLUDES}
)
set_target_properties(profilePlugin PROPERTIES EXCLUDE_FROM_ALL true)
endif()
add_executable(jsonTest
testMain.cpp
@@ -344,7 +364,12 @@ target_link_libraries(jsonTest PRIVATE PiPedalCommon)
target_include_directories(jsonTest PRIVATE ${PIPEDAL_INCLUDES}
)
set_target_properties(jsonTest PROPERTIES EXCLUDE_FROM_ALL true)
target_link_libraries(jsonTest PRIVATE pthread)
add_test(NAME jsonTest COMMAND jsonTest)
@@ -549,18 +574,18 @@ if(ENABLE_VST3)
)
add_executable(vst3test Vst3test.cpp
# add_executable(vst3test Vst3test.cpp
MemDebug.cpp
MemDebug.hpp
# MemDebug.cpp
# MemDebug.hpp
Vst3SdkRepro.cpp
# Vst3SdkRepro.cpp
${VST3_FILES}
# ${VST3_FILES}
#vst3/Vst3PresetFile.hpp Vst3PresetFile.cpp
asan_options.cpp
)
# #vst3/Vst3PresetFile.hpp Vst3PresetFile.cpp
# asan_options.cpp
# )
target_include_directories(vst3test PRIVATE ${PIPEDAL_INCLUDES}
)
@@ -596,7 +621,6 @@ add_executable(pipedalconfig
SystemConfigFile.hpp SystemConfigFile.cpp
WifiChannelSelectors.cpp WifiChannelSelectors.hpp
asan_options.cpp
Lv2Log.cpp Lv2Log.hpp
)
@@ -609,7 +633,6 @@ add_executable(pipedal_latency_test
PiLatencyMain.cpp
PiPedalAlsa.hpp PiPedalAlsa.cpp
asan_options.cpp
Lv2Log.cpp Lv2Log.hpp
AlsaDriver.cpp AlsaDriver.hpp
JackConfiguration.hpp JackConfiguration.cpp
JackServerSettings.hpp JackServerSettings.cpp
@@ -632,13 +655,23 @@ target_include_directories(capturepresets PRIVATE ${PIPEDAL_INCLUDES})
target_link_libraries(capturepresets ${PIPEDAL_LIBS})
add_executable(makeRelease
makeReleaseMain.cpp
CommandLineParser.hpp
)
target_include_directories(makeRelease PRIVATE ${PIPEDAL_INCLUDES})
target_link_libraries(makeRelease ${PIPEDAL_LIBS})
add_executable(pipedal_update
UpdateMain.cpp
UpdateResults.cpp UpdateResults.hpp
Lv2Log.hpp Lv2Log.cpp
Lv2SystemdLogger.cpp Lv2SystemdLogger.hpp
UpdateResults.cpp UpdateResults.hpp
TemporaryFile.cpp TemporaryFile.hpp
AdminInstallUpdate.cpp AdminInstallUpdate.hpp
)
@@ -652,7 +685,6 @@ add_executable(pipedaladmind AdminMain.cpp CommandLineParser.hpp
JackServerSettings.hpp JackServerSettings.cpp
Lv2SystemdLogger.hpp Lv2SystemdLogger.cpp
Lv2Log.cpp Lv2Log.hpp
SystemConfigFile.hpp SystemConfigFile.cpp
CpuGovernor.cpp CpuGovernor.hpp
asan_options.cpp
+19
View File
@@ -1,3 +1,22 @@
// Copyright (c) 2022-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.
#include "Storage.hpp"
#include "CommandLineParser.hpp"
#include <filesystem>
+312 -140
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// Copyright (c) 2022-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
@@ -22,6 +22,8 @@
#include <stdlib.h>
#include <unistd.h>
#include "CommandLineParser.hpp"
#include "SystemConfigFile.hpp"
#include <filesystem>
#include <stdlib.h>
#include "WriteTemplateFile.hpp"
@@ -42,6 +44,7 @@
#include <grp.h>
#include "ofstream_synced.hpp"
#define P2PD_DISABLED
#if JACK_HOST
#define INSTALL_JACK_SERVICE 1
@@ -64,6 +67,7 @@ namespace fs = std::filesystem;
#define JACK_SERVICE_ACCOUNT_NAME "jack"
#define AUDIO_SERVICE_GROUP_NAME "audio"
#define JACK_SERVICE_GROUP_NAME AUDIO_SERVICE_GROUP_NAME
#define NETDEV_GROUP_NAME "netdev"
#define SYSTEMCTL_BIN "/usr/bin/systemctl"
#define GROUPADD_BIN "/usr/sbin/groupadd"
@@ -151,6 +155,86 @@ void DisableService()
#endif
}
static void RestartAvahiService()
{
silentSysExec(SS(SYSTEMCTL_BIN << " restart avahi-daemon.service").c_str());
}
static void AvahiInstall()
{
// disable IPv6 mdns broadcasts. Avahi broadcasts link-local IPV6 addresses which are unusually difficult to deal with.
try
{
std::filesystem::path avahiConfig("/etc/avahi/avahi-daemon.conf");
SystemConfigFile avahi(avahiConfig);
bool changed = avahi.RemoveUndoableActions();
int line = avahi.GetLineThatStartsWith("use-ipv6=yes");
if (line != -1)
{
avahi.UndoableReplaceLine(line, "use-ipv6=no");
changed = true;
}
else
{
if (avahi.GetLineThatStartsWith("use-ipv6=no") == -1)
{
line = avahi.GetLineThatStartsWith("[server]");
if (line == 1)
{
throw std::runtime_error("Unable to find [server] section.");
}
{
// increment to end of section.
while (line < avahi.GetLineCount())
{
const auto &txt = avahi.Get(line);
if (txt.empty())
{
break;
}
if (txt.starts_with("[")) // start of next section.
{
break;
}
++line;
}
}
avahi.UndoableAddLine(avahi.GetLineCount(), "use-ipv6=no");
changed = true;
}
}
if (changed)
{
avahi.Save();
RestartAvahiService();
}
}
catch (const std::exception &e)
{
cout << "Warning: Unabled to disable Ipv6 mDNS announcements. " << e.what() << endl;
}
}
static void AvahiUninstall()
{
try
{
std::filesystem::path avahiConfig("/etc/avahi/avahi-daemon.conf");
SystemConfigFile avahi(avahiConfig);
if (avahi.RemoveUndoableActions())
{
avahi.Save();
RestartAvahiService();
}
}
catch (const std::exception &e)
{
cout << " Warning: Unable to restore Avahi Daemon configuration. " << e.what() << endl;
}
}
void StopService(bool excludeShutdownService = false)
{
if (sysExec(SYSTEMCTL_BIN " stop " PIPEDALD_SERVICE ".service") != EXIT_SUCCESS)
@@ -468,6 +552,11 @@ static bool IsP2pServiceEnabled()
void Uninstall()
{
// if NetworkManager isn't installed, Install will have failed.
// Do cleanup as if the Isntall had not failed.
PretendNetworkManagerIsInstalled();
try
{
OnWifiUninstall(true);
@@ -526,6 +615,8 @@ void Uninstall()
{
}
UninstallPamEnv();
AvahiUninstall();
// UninstallLimits();
sysExec(SYSTEMCTL_BIN " daemon-reload");
}
@@ -676,7 +767,8 @@ static void FixPermissions()
struct passwd *passwd;
if ((passwd = getpwnam("pipedal_d")) == nullptr)
{
cout << "Error: " << "User 'pipedal_d' does not exist." << endl;
cout << "Error: "
<< "User 'pipedal_d' does not exist." << endl;
return;
}
uid = passwd->pw_uid;
@@ -771,7 +863,8 @@ void DeployVarConfig()
void InstallPgpKey()
{
fs::path homeDir = "/var/pipedal/config/gpg";
fs::path keyPath = "/etc/pipedal/config/updatekey.gpg";
fs::path keyPath = "/etc/pipedal/config/updatekey.asc";
fs::path keyPath2 = "/etc/pipedal/config/updatekey2.asc";
fs::create_directories(homeDir);
std::ignore = chmod(homeDir.c_str(), 0700);
@@ -781,15 +874,24 @@ void InstallPgpKey()
s << (CHOWN_BIN " " SERVICE_GROUP_NAME ":" SERVICE_GROUP_NAME " ") << homeDir.c_str();
sysExec(s.str().c_str());
}
std::ostringstream ss;
ss << "/usr/bin/gpg --homedir " << homeDir.c_str() << " --import " << keyPath.c_str();
int rc = silentSysExec(ss.str().c_str());
if (rc != EXIT_SUCCESS)
{
cout << "Error: Failed to create update keyring." << endl;
std::ostringstream ss;
ss << "/usr/bin/gpg --homedir " << homeDir.c_str() << " --import " << keyPath.c_str();
int rc = silentSysExec(ss.str().c_str());
if (rc != EXIT_SUCCESS)
{
cout << "Error: Failed to create update keyring." << endl;
}
}
{
std::ostringstream ss;
ss << "/usr/bin/gpg --homedir " << homeDir.c_str() << " --import " << keyPath2.c_str();
int rc = silentSysExec(ss.str().c_str());
if (rc != EXIT_SUCCESS)
{
cout << "Error: Failed to add update key." << endl;
}
}
{
std::stringstream ss;
ss << (CHOWN_BIN " -R " SERVICE_GROUP_NAME ":" SERVICE_GROUP_NAME " ") << homeDir.c_str();
@@ -800,8 +902,16 @@ void InstallPgpKey()
void Install(const fs::path &programPrefix, const std::string endpointAddress)
{
cout << "Configuring pipedal" << endl;
if (!UsingNetworkManager())
{
throw std::runtime_error("The current OS is not using NetworkManager. Services not configured.");
}
try
{
// apply policy changes we dropped into the polkit configuration files (allows pipedal_d to use NetworkManager dbus apis).
silentSysExec(SYSTEMCTL_BIN " restart polkit");
DeployVarConfig();
if (sysExec(GROUPADD_BIN " -f " AUDIO_SERVICE_GROUP_NAME) != EXIT_SUCCESS)
@@ -813,6 +923,7 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
throw std::runtime_error("Failed to create pipedald service group.");
}
// defensively disable wifi p2p if some leftover config file left it enabled.
#ifdef P2PD_DISABLED
try
{
if (IsP2pServiceEnabled())
@@ -826,6 +937,7 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
catch (const std::exception &)
{
}
#endif
InstallAudioService();
auto endpos = endpointAddress.find_last_of(':');
@@ -876,6 +988,9 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
// Add to audio groups.
sysExec(USERMOD_BIN " -a -G " AUDIO_SERVICE_GROUP_NAME " " SERVICE_ACCOUNT_NAME);
// add to netdev group
sysExec(USERMOD_BIN " -a -G " NETDEV_GROUP_NAME " " SERVICE_ACCOUNT_NAME);
// create and configure /var directory.
@@ -951,7 +1066,7 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
}
s
<< (programPrefix / "sbin" / PIPEDALD_SERVICE).string()
<< " /etc/pipedal/config /etc/pipedal/react -port " << endpointAddress << " -systemd";
<< " /etc/pipedal/config /etc/pipedal/react -systemd";
map["COMMAND"] = s.str();
}
@@ -995,12 +1110,16 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
sysExec(SYSTEMCTL_BIN " daemon-reload");
FixPermissions();
RestartService(false);
StopService(false);
AvahiInstall();
InstallPgpKey();
StartService(false);
EnableService();
// Restart WiFi Direct if neccessary.
// Restart Wi-Fi Direct if neccessary.
OnWifiReinstall();
InstallPgpKey();
}
catch (const std::exception &e)
{
@@ -1026,7 +1145,7 @@ static void PrintHelp()
PrettyPrinter pp;
pp << "pipedalconfig - Command-line post-install configuration for PiPedal"
<< "\n"
<< "Copyright (c) 2022 Robin Davies. All rights reserved."
<< "Copyright (c) 2022-2024 Robin Davies."
<< "\n"
<< "\n"
<< "See https://rerdavies.github.io/pipedal/Documentation.html for "
@@ -1051,10 +1170,10 @@ static void PrintHelp()
<< "\n"
<< HangingIndent() << " --install [--port <port#>]\t"
<< "Install or re-install services and service accounts."
<< "Install or re-install PiPedal services and service accounts."
<< "\n"
<< "\n"
<< "The --port option controls which port the web server uses."
<< "The --port option controls which TCP/IP port the web server uses."
<< "\n\n"
<< HangingIndent() << " --uninstall\t"
@@ -1063,68 +1182,54 @@ static void PrintHelp()
<< "\n"
<< HangingIndent() << " --enable\t"
<< "Start the pipedal service at boot time."
<< "Start PiPedal services at boot time."
<< "\n"
<< "\n"
<< HangingIndent() << " --disable\tDo not start the pipedal service at boot time."
<< HangingIndent() << " --disable\tDo not start PiPedal services at boot time."
<< "\n"
<< "\n"
<< HangingIndent() << " --start\tStart the pipedal services."
<< HangingIndent() << " --start\tStart PiPedal services."
<< "\n"
<< "\n"
<< HangingIndent() << " --stop\tStop the pipedal services."
<< HangingIndent() << " --stop\tStop PiPedal services."
<< "\n"
<< "\n"
<< HangingIndent() << " --restart\tRestart the pipedal services."
<< HangingIndent() << " --restart\tRestart PiPedal services."
<< "\n"
<< "\n"
<< HangingIndent() << " --enable-p2p [<country_code> <ssid> [[<pin>] <channel>] ]\t"
<< "Enable the P2P (Wi-Fi Direct) hotspot."
<< HangingIndent() << " --enable-hotspot\t <country_code> <ssid> <wep_password> <channel> [<hotspot-option>]"
<< "\n\nEnable Wi-Fi hotspot."
<< "\n\n"
<< "With no additional arguments, the P2P channel is enabled with most-recent settings."
<< "PiPedal's Wi-Fi hotspot allows you to connect to your Raspberry Pi when you don't have a Wi-Fi router, for example, when you are "
<< "performing away from home. It is most particularly useful when using the PiPedal Android client. Consult PiPedal's online documentation "
<< "for guidance on configuring a Wi-Fi hotspot."
<< "\n\n"
<< "<country_code> is the 2-letter ISO-3166 country code for "
"the country you are in. see below for further notes."
<< "One of the following hotspot options can be specifed. If no hotspot option is given, the hotspot will always be enabled. "
<< "If one of the hotspot options are given, the PiPedal server will turn the hotspot on or off automatically, as conditions change."
<< "\n\n"
<< "--home-network <wifi-ssid>\n"
<< AddIndent(4) << "Hotspot is disabled if the specificed Wi-Fi network is detected.\n"
<< AddIndent(-4)
<< "--no-ethernet\n"
<< AddIndent(4) << "Hotspot is disabled if an ethernet network is connected.\n"
<< AddIndent(-4)
<< "--no-wifi\n"
<< AddIndent(4) << "Hotspot is disabled if a remembered Wi-Fi network is detected.\n"
<< AddIndent(-4)
<< "\n"
<< "Caution: Wi-Fi connections are disabled when the hotspot is activated. If you currently access your Raspberry Pi using "
<< "Wi-Fi, choose your hotspot options carefully, to ensure that you can still access your Raspberry Pi."
<< "\n\n"
<< "<ssid> is the name you see when connecting. "
<< "country_code is the 2-letter ISO-3166 country code for "
<< "the country you are in. see below for further notes."
<< "\n\n"
<< "<pin> is an exactly-eight-digit pin number that you must "
<< "enter when connecting to the hotspot. If you don't "
<< "provide a pin, pipedalconfig will generate and "
<< "display a random pin for you. The pin is a "
<< "so-called \"label\" pin, which is the same every "
<< "time you are asked to enter it (unlike a keypad pin "
<< "which changes every time you need to enter it."
<< "\n\n"
<< "Consider attaching a label to the bottom of your device "
<< "so you can can remember the pin if you wan't to connect a new "
<< "device to PiPedal. (It's also available on the Settings page of PiPedal, if you have access to PiPedal UI on another device.)"
<< "\n\n"
<< "For best performance, the channel number should be 1, 6, or 11 (the Wifi Direct \"social\" channels). "
<< "Channel number defaults to 1."
<< "If the Wi-Fi channel is not specified, a Wi-Fi channel will be automatically selected."
<< "\n\n"
<< HangingIndent() << " --disable-p2p\tDisable Wi-Fi Direct access."
<< HangingIndent() << " --disable-hotspot\tDisabled the Wi-Fi hotspot."
<< "\n\n"
<< HangingIndent() << " --list-p2p-channels [<country_code>] \tList valid p2p channels for the current/specified country."
<< "\n\n"
// << HangingIndent() << " --enable-legacy-ap\t <country_code> <ssid> <wep_password> <channel>\tEnable a legacy Wi-Fi access point."
// << "\n\n"
// << "Enable a legacy Wi-Fi access point. \n\n"
// << "country_code is the 2-letter ISO-3166 country code for "
// << "the country you are in. see below for further notes."
// << "\n\n"
// << "See below for an explanation of when you might want to use a legacy Wi-Fi access point instead of Wifi-Direct access."
// << "an explanation of when you might want to use a legacy Access Point instead of "
// << "a P2P (Wi-Fi Direct) connection. Generally, you should prefer a P2p connection "
// << "to an ordinary Hotspot connection."
// << "\n\n"
<< HangingIndent() << " --disable-legacy-ap\tDisabled the legacy Wi-Fi access point."
<< HangingIndent() << " --list-wifi-channels [<country_code>] \tList valid Wifi channels for the current/specified country."
<< "\n\n"
<< Indent(0) << "Country codes:"
@@ -1138,28 +1243,11 @@ static void PrintHelp()
<< "with reduced amplitude and feature sets."
<< "\n\n"
<< "For the most part, Wi-Fi country codes are taken from the list of ISO 3661 "
<< "2-letter country codes; although there are a handful of exceptions for small "
<< "2-letter country codes; although there are a handful of exceptions for small "
<< "countries and islands. See the Alpha-2 code column of "
<< "\n\n"
<< Indent(8) << "https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes."
<< "\n\n"
<< Indent(0) << "Legacy Wi-Fi Access Points:"
<< "\n\n"
<< Indent(4)
<< "Some older devices may not be able to connect to PiPedal using Wi-Fi Direct connections. "
"Old Apple devices, for example, do not support Wi-Fi Direct. In theory, Wi-Fi Direct should "
"allow legacy Wi-Fi devices to connect to a Wi-Fi Direct access points as if it were an "
"ordinary Access Point. If this turns out "
"not to be the case, you can configure PiPedal to provide a legacy Wi-Fi access point instead. "
"\n\n"
"Unlike Wi-Fi Direct connections, legacy Access Points will prevent both the connecting device "
"and the PiPedal host machine from connecting to the Internet over a simultanous Wi-Fi connection Access "
"Point. On a connecting Android device, you won't be able to use the data connection either when a legacy "
"Wi-Fi connection is active."
"\n\n"
"Wi-Fi Direct connections are "
"therefore preferrable under almost all circumstances.\n\n";
<< "\n\n";
}
static int ListP2PChannels(const std::vector<std::string> &arguments)
@@ -1200,6 +1288,14 @@ static int ListP2PChannels(const std::vector<std::string> &arguments)
return EXIT_SUCCESS;
}
void RequireNetworkManager()
{
if (!UsingNetworkManager())
{
throw std::runtime_error("The current OS is not using NetworkManager.");
}
}
int main(int argc, char **argv)
{
CommandLineParser parser;
@@ -1209,7 +1305,7 @@ int main(int argc, char **argv)
bool helpError = false;
bool stop = false, start = false;
bool enable = false, disable = false, restart = false;
bool enable_ap = false, disable_ap = false;
bool enable_hotspot = false, disable_hotspot = false;
bool enable_p2p = false, disable_p2p = false;
bool list_p2p_channels = false;
bool get_current_port = false;
@@ -1218,6 +1314,9 @@ int main(int argc, char **argv)
bool excludeShutdownService = false;
std::string prefixOption;
std::string portOption;
std::string homeNetwork;
bool noEthernet = false;
bool noWifi = false;
parser.AddOption("--nosudo", &nosudo); // strictly a debugging aid. Run without sudo, until we fail because of permissions.
parser.AddOption("--install", &install);
@@ -1231,22 +1330,17 @@ int main(int argc, char **argv)
parser.AddOption("--help", &help);
parser.AddOption("--prefix", &prefixOption);
parser.AddOption("--port", &portOption);
// parser.AddOption("--enable-legacy-ap", &enable_ap);
parser.AddOption("--disable-ap", &disable_ap);
parser.AddOption("--enable-p2p", &enable_p2p);
parser.AddOption("--disable-p2p", &disable_p2p);
parser.AddOption("--list-p2p-channels", &list_p2p_channels);
parser.AddOption("--fix-permissions", &fix_permissions);
parser.AddOption("--get-current-port", &get_current_port); // private. For debug use only.
parser.AddOption("--excludeShutdownService", &excludeShutdownService); // private (unstable) option used by shutdown service.
parser.AddOption("--enable-hotspot", &enable_hotspot);
parser.AddOption("--disable-hotspot", &disable_hotspot);
parser.AddOption("--home-network",&homeNetwork);
parser.AddOption("--no-ethernet",&noEthernet);
parser.AddOption("--no-wifi",&noWifi);
try
{
parser.Parse(argc, (const char **)argv);
int actionCount =
help + get_current_port + install + uninstall + stop + start + enable + disable + enable_ap + disable_ap + restart + enable_p2p + disable_p2p + list_p2p_channels + fix_permissions;
help + get_current_port + install + uninstall + stop + start + enable + disable + enable_hotspot + disable_hotspot + restart + enable_p2p + disable_p2p + list_p2p_channels + fix_permissions;
if (actionCount > 1)
{
throw std::runtime_error("Please provide only one action.");
@@ -1260,7 +1354,7 @@ int main(int argc, char **argv)
throw std::runtime_error("No action provided.");
}
if ((!enable_p2p) && (!enable_ap) && (!list_p2p_channels))
if ((!enable_p2p) && (!enable_hotspot) && (!list_p2p_channels))
{
if (parser.Arguments().size() != 0)
{
@@ -1268,6 +1362,23 @@ int main(int argc, char **argv)
helpError = true;
}
}
int hotspotOptionCount =
(homeNetwork.length() != 0 ? 1: 0)
+ noEthernet
+ noWifi;
if (enable_hotspot)
{
if (hotspotOptionCount > 1)
{
throw std::runtime_error("Only one hotspot option at a time can be specified.");
}
} else {
if (hotspotOptionCount > 0)
{
throw std::runtime_error("Hotspot options only only valid when the --enable-hotspot option has been supplied.");
}
}
}
catch (const std::exception &e)
{
@@ -1277,7 +1388,7 @@ int main(int argc, char **argv)
}
if (helpError)
{
cout << "\n";
cout << endl;
return EXIT_FAILURE; // don't scroll the error off the screen.
}
@@ -1317,104 +1428,165 @@ int main(int argc, char **argv)
}
if (install)
{
fs::path prefix;
if (prefixOption.length() != 0)
{
prefix = fs::path(prefixOption);
}
else
{
prefix = fs::path(argv[0]).parent_path().parent_path();
fs::path pipedalPath = prefix / "sbin" / "pipedald";
if (!fs::exists(pipedalPath))
try {
fs::path prefix;
if (prefixOption.length() != 0)
{
std::stringstream s;
s << "Can't find pipedald executable at " << pipedalPath << ". Try again using the -prefix option.";
throw std::runtime_error(s.str());
prefix = fs::path(prefixOption);
}
else
{
prefix = fs::path(argv[0]).parent_path().parent_path();
fs::path pipedalPath = prefix / "sbin" / "pipedald";
if (!fs::exists(pipedalPath))
{
std::stringstream s;
s << "Can't find pipedald executable at " << pipedalPath << ". Try again using the -prefix option.";
throw std::runtime_error(s.str());
}
}
}
if (portOption == "")
{
portOption = GetCurrentWebServicePort();
if (portOption == "")
{
portOption = "80";
portOption = GetCurrentWebServicePort();
if (portOption == "")
{
portOption = "80";
}
}
}
if (portOption.find(':') == string::npos)
if (portOption.find(':') == string::npos)
{
portOption = "0.0.0.0:" + portOption;
}
Install(prefix, portOption);
FileSystemSync();
} catch (const std::exception&e)
{
portOption = "0.0.0.0:" + portOption;
cout << "ERROR: " << e.what() << endl;
FileSystemSync();
return EXIT_SUCCESS; // say we succeeded so we don't put APT into a hellish state.
}
Install(prefix, portOption);
FileSystemSync();
}
else if (uninstall)
{
Uninstall();
FileSystemSync();
try {
Uninstall();
FileSystemSync();
} catch (const std::exception &e)
{
cout << "ERROR: " << e.what() << endl;
FileSystemSync();
return EXIT_SUCCESS; // Say we succeeds so that we don't put APT into a hellish state.
}
}
else if (stop)
{
RequireNetworkManager();
StopService();
}
else if (start)
{
RequireNetworkManager();
StartService();
}
else if (restart)
{
RequireNetworkManager();
RestartService(excludeShutdownService);
}
else if (enable)
{
RequireNetworkManager();
EnableService();
FileSystemSync();
}
else if (disable)
{
RequireNetworkManager();
DisableService();
FileSystemSync();
}
else if (enable_p2p)
{
try
{
auto argv = parser.Arguments();
WifiDirectConfigSettings settings;
settings.ParseArguments(argv);
settings.valid_ = true;
settings.enable_ = true;
SetWifiDirectConfig(settings);
RestartService(true); // also have to retart web service so that it gets the correct device name.
}
catch (const std::exception &e)
{
cout << "ERROR: " << e.what() << endl;
return EXIT_FAILURE;
}
throw std::runtime_error("Wi-Fi p2p connections are no longer supported. Use hotspots instead.");
// try
// {
// auto argv = parser.Arguments();
// WifiDirectConfigSettings settings;
// settings.ParseArguments(argv);
// settings.valid_ = true;
// settings.enable_ = true;
// SetWifiDirectConfig(settings);
// RestartService(true); // also have to retart web service so that it gets the correct device name.
// }
// catch (const std::exception &e)
// {
// cout << "ERROR: " << e.what() << endl;
// return EXIT_FAILURE;
// }
}
else if (disable_p2p)
{
RequireNetworkManager();
WifiDirectConfigSettings settings;
settings.valid_ = true;
settings.Load();
settings.enable_ = false;
SetWifiDirectConfig(settings);
RestartService(true);
return EXIT_SUCCESS;
}
else if (enable_ap)
else if (enable_hotspot)
{
RequireNetworkManager();
auto argv = parser.Arguments();
WifiConfigSettings settings;
settings.ParseArguments(argv);
HotspotAutoStartMode startMode = HotspotAutoStartMode::Always;
if (homeNetwork.length() != 0)
{
startMode = HotspotAutoStartMode::NotAtHome;
} else if (noEthernet)
{
startMode = HotspotAutoStartMode::NoEthernetConnection;
} else if (noWifi)
{
startMode = HotspotAutoStartMode::NoRememberedWifiConections;
}
settings.ParseArguments(argv,startMode,homeNetwork);
if (settings.hasPassword_)
{
settings.hasSavedPassword_ = true;
}
SetWifiConfig(settings);
if (silentSysExec(SYSTEMCTL_BIN " restart " PIPEDALD_SERVICE ".service") != EXIT_SUCCESS)
{
throw std::runtime_error("Failed to restart the " PIPEDALD_SERVICE " service.");
}
FileSystemSync();
}
else if (disable_ap)
else if (disable_hotspot)
{
RequireNetworkManager();
WifiConfigSettings settings;
settings.valid_ = true;
settings.enable_ = false;
settings.autoStartMode_ = (uint16_t)HotspotAutoStartMode::Never;
SetWifiConfig(settings);
if (silentSysExec(SYSTEMCTL_BIN " restart " PIPEDALD_SERVICE ".service") != EXIT_SUCCESS)
{
throw std::runtime_error("Failed to restart the " PIPEDALD_SERVICE " service.");
}
FileSystemSync();
}
}
catch (const std::exception &e)
+55
View File
@@ -0,0 +1,55 @@
// 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.
#include "DBusToLv2Log.hpp"
#include "DBusLog.hpp"
#include "Lv2Log.hpp"
using namespace pipedal;
class DBus2ToLv2Logger : public IDBusLogger
{
public:
virtual void LogError(const std::string &message) override
{
Lv2Log::error(message);
}
virtual void LogWarning(const std::string &message) override
{
Lv2Log::warning(message);
}
virtual void LogInfo(const std::string &message) override
{
Lv2Log::info(message);
}
virtual void LogDebug(const std::string &message) override
{
Lv2Log::debug(message);
}
virtual void LogTrace(const std::string &message) override
{
Lv2Log::debug(message);
}
};
void pipedal::DbusLogToLv2Log()
{
SetDBusLogger(std::make_unique<DBus2ToLv2Logger>());
}
+25
View File
@@ -0,0 +1,25 @@
// 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.
#pragma once
namespace pipedal {
void DbusLogToLv2Log();
}
+989
View File
@@ -0,0 +1,989 @@
// 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 "HotspotManager.hpp"
#include <thread>
#include "Lv2Log.hpp"
#include "DBusLog.hpp"
#include "DBusEvent.hpp"
#include "DBusDispatcher.hpp"
#include "NetworkManagerInterfaces.hpp"
#include "ss.hpp"
#include "WifiConfigSettings.hpp"
#include "ServiceConfiguration.hpp"
#include <unordered_set>
#include <mutex>
#include <algorithm>
using namespace pipedal;
using namespace dbus::networkmanager;
namespace pipedal::impl
{
constexpr std::string PIPEDAL_HOTSPOT_NAME = "PiPedal Hotspot";
class HotspotManagerImpl : public HotspotManager
{
public:
using ssid_t = WifiConfigSettings::ssid_t;
HotspotManagerImpl();
virtual ~HotspotManagerImpl() noexcept;
virtual void Open() override;
virtual void Reload() override;
virtual void Close() override;
std::vector<std::string> GetKnownWifiNetworks();
virtual PostHandle Post(PostCallback &&fn) override;
virtual PostHandle PostDelayed(const clock::duration &delay, PostCallback &&fn) override;
virtual bool CancelPost(PostHandle handle) override;
virtual void SetNetworkChangingListener(NetworkChangingListener &&listener) override;
private:
enum class State
{
Initial,
WaitingForNetworkManager,
Monitoring,
HotspotConnecting,
HotspotConnected,
Error,
Closed
};
void SetState(State state);
State state = State::Initial;
void onClose();
void onError(const std::string &message);
void onInitialize();
void onDevicesChanged();
void onDisconnect();
void onReload();
void DisableHotspot();
void EnableHotspot();
void StartHotspot();
void StopHotspot();
void onStartMonitoring();
void onAccessPointChanged();
void onAccessPointsChanged();
void CancelAccessPointsChangedTimer();
void WaitForNetworkManager();
void StartWaitForNetworkManagerTimer();
void CancelWaitForNetworkManagerTimer();
void UpdateNetworkManagerStatus();
void CancelDeviceChangedTimer();
void ReleaseNetworkManager();
void OnEthernetStateChanged(uint32_t state);
void OnWlanStateChanged(uint32_t state);
std::vector<AccessPoint::ptr> GetAllAccessPoints();
std::vector<ssid_t> GetAllAutoConnectSsids();
std::vector<ssid_t> GetKnownVisibleAccessPoints(const std::vector<ssid_t> &allAccessPoints);
void UpdateKnownNetworks(
std::vector<ssid_t> &knownSsids,
std::vector<AccessPoint::ptr> &allAccessPoints);
void FireNetworkChanging();
void MaybeStartHotspot();
void StartScanTimer();
void StopScanTimer();
void ScanNow();
Connection::ptr FindExistingConnection();
Device::ptr GetDevice(uint32_t nmDeviceType);
std::atomic<bool> closed;
DBusDispatcher dbusDispatcher;
std::unique_ptr<std::thread> thread;
void ThreadProc();
WifiConfigSettings wifiConfigSettings;
NetworkManager::ptr networkManager;
Device::ptr ethernetDevice;
Device::ptr wlanDevice;
DeviceWireless::ptr wlanWirelessDevice;
ActiveConnection::ptr activeConnection;
std::recursive_mutex networkChangingListenerMutex;
NetworkChangingListener networkChangingListener;
std::mutex knownWifiNetworksMutex;
std::vector<std::string> knownWifiNetworks;
bool ethernetConnected = true;
bool wlanConnected = true;
DBusEventHandle onDeviceAddedHandle = INVALID_DBUS_EVENT_HANDLE;
DBusEventHandle onDeviceRemovedHandle = INVALID_DBUS_EVENT_HANDLE;
DBusDispatcher::PostHandle devicesChangedTimerHandle = 0;
DBusDispatcher::PostHandle networkManagerTimerHandle = 0;
DBusDispatcher::PostHandle accessPointsChangedTimerHandle = 0;
DBusDispatcher::PostHandle scanTimerHandle = 0;
};
}
using namespace pipedal::impl;
HotspotManagerImpl::HotspotManagerImpl()
{
}
void HotspotManagerImpl::Open()
{
dbusDispatcher.Run();
dbusDispatcher.Post(
[this]()
{
UpdateNetworkManagerStatus(); // waits for valid network manager state, and calls onInitialize when ready.
});
}
void HotspotManagerImpl::onClose()
{
this->closed = true; // avoids a memory barrier probelm.
CancelDeviceChangedTimer();
CancelWaitForNetworkManagerTimer();
CancelAccessPointsChangedTimer();
if (networkManager && activeConnection)
{
try {
networkManager->DeactivateConnection(activeConnection->getObjectPath());
activeConnection = nullptr;
} catch (const std::exception&e)
{
// nothrow.
}
}
ReleaseNetworkManager();
Lv2Log::debug("HotspotManager: state=Closed");
SetState(State::Closed);
}
void HotspotManagerImpl::onError(const std::string &message)
{
Lv2Log::error(message);
ReleaseNetworkManager();
Lv2Log::debug("HotspotManager: state=Error");
SetState(State::Error);
}
void HotspotManagerImpl::SetState(State state)
{
this->state = state;
}
void HotspotManagerImpl::onInitialize()
{
if (closed)
return;
if (state != State::Initial && state != State::WaitingForNetworkManager)
{
onError("Invalid state (onInitialize).");
return;
}
try
{
wifiConfigSettings.Load();
onStartMonitoring();
}
catch (const std::exception &e)
{
onError(SS("HotspotManager: " << e.what()));
}
}
void HotspotManagerImpl::onDisconnect()
{
if (state != State::Initial && state != State::WaitingForNetworkManager)
{
WaitForNetworkManager();
}
}
void HotspotManagerImpl::UpdateNetworkManagerStatus()
{
try
{
if (!networkManager)
{
networkManager = NetworkManager::Create(dbusDispatcher);
}
// turn on wifi if required.
auto ethernetDevice = GetDevice(NM_DEVICE_TYPE_ETHERNET);
auto wlanDevice = GetDevice(NM_DEVICE_TYPE_WIFI);
if (ethernetDevice && wlanDevice)
{
if (state == State::Initial || state == State::WaitingForNetworkManager)
{
CancelWaitForNetworkManagerTimer();
this->onInitialize();
}
return;
}
// check to see whether we have BOTH an eth0 device and a wlan0 device.
}
catch (const std::exception &e)
{
}
if (state != State::WaitingForNetworkManager)
{
this->WaitForNetworkManager();
}
else
{
this->StartWaitForNetworkManagerTimer();
}
}
void HotspotManagerImpl::CancelDeviceChangedTimer()
{
if (devicesChangedTimerHandle)
{
dbusDispatcher.CancelPost(devicesChangedTimerHandle);
devicesChangedTimerHandle = 0;
}
}
void HotspotManagerImpl::ReleaseNetworkManager()
{
StopScanTimer();
CancelAccessPointsChangedTimer();
CancelWaitForNetworkManagerTimer();
CancelDeviceChangedTimer();
DisableHotspot();
ethernetDevice = nullptr;
wlanDevice = nullptr;
wlanWirelessDevice = nullptr;
networkManager = nullptr;
this->onDeviceRemovedHandle = INVALID_DBUS_EVENT_HANDLE;
this->onDeviceAddedHandle = INVALID_DBUS_EVENT_HANDLE;
}
void HotspotManagerImpl::onDevicesChanged()
{
UpdateNetworkManagerStatus();
}
Device::ptr HotspotManagerImpl::GetDevice(uint32_t nmDeviceType)
{
const auto &allDevices = networkManager->GetAllDevices();
for (const auto &devicePath : allDevices)
{
auto device = Device::Create(dbusDispatcher, devicePath);
if (device->DeviceType() == nmDeviceType)
{
return device;
}
}
return nullptr;
}
void HotspotManagerImpl::OnEthernetStateChanged(uint32_t state)
{
bool newValue = (state == 100);
if (newValue != this->ethernetConnected)
{
this->ethernetConnected = newValue;
Lv2Log::debug(SS("HotspotMonitor: ethernetConnected=" << ethernetConnected));
FireNetworkChanging();
MaybeStartHotspot();
}
}
void HotspotManagerImpl::OnWlanStateChanged(uint32_t state)
{
this->wlanConnected = (state == 100);
Lv2Log::debug(SS("HotspotMonitor: OnWlanStateChanged"));
MaybeStartHotspot();
}
void HotspotManagerImpl::onStartMonitoring()
{
try
{
ReleaseNetworkManager();
this->networkManager = NetworkManager::Create(dbusDispatcher);
if (!networkManager->WirelessEnabled())
{
networkManager->WirelessEnabled(true);
}
this->onDeviceAddedHandle = this->networkManager->OnDeviceAdded.add(
[this](const sdbus::ObjectPath &)
{
onDevicesChanged();
});
this->onDeviceRemovedHandle = this->networkManager->OnDeviceRemoved.add(
[this](const sdbus::ObjectPath &objectPath)
{
if (this->ethernetDevice && this->ethernetDevice->getObjectPath() == objectPath)
{
onDisconnect();
}
else if (this->wlanDevice && this->wlanDevice->getObjectPath() == objectPath)
{
onDisconnect();
}
});
ethernetDevice = GetDevice(NM_DEVICE_TYPE_ETHERNET);
if (!ethernetDevice)
{
throw std::runtime_error("eth0 device not found.");
}
this->ethernetDevice->OnStateChanged.add(
[this](uint32_t, uint32_t, uint32_t)
{
OnEthernetStateChanged(ethernetDevice->State());
});
wlanDevice = GetDevice(NM_DEVICE_TYPE_WIFI);
if (!wlanDevice)
{
throw std::runtime_error("wlan0 device not found.");
}
wlanWirelessDevice = DeviceWireless::Create(dbusDispatcher, wlanDevice->getObjectPath());
wlanWirelessDevice->OnAccessPointAdded.add(
[this](const sdbus::ObjectPath &accessPoint)
{
onAccessPointChanged();
});
wlanWirelessDevice->OnAccessPointRemoved.add(
[this](const sdbus::ObjectPath &accessPoint)
{
onAccessPointChanged();
});
this->wlanDevice->OnStateChanged.add(
[this](uint32_t, uint32_t, uint32_t)
{
OnWlanStateChanged(ethernetDevice->State());
});
Lv2Log::debug("HotspotManager: state=Monitoring");
SetState(State::Monitoring);
Lv2Log::info("HotspotManager: Monitoring network status.");
OnEthernetStateChanged(ethernetDevice->State());
OnWlanStateChanged(wlanDevice->State());
StartScanTimer();
onAccessPointChanged();
}
catch (const std::exception &e)
{
Lv2Log::error(SS("HotspotManager: " << e.what()));
WaitForNetworkManager();
}
}
void HotspotManagerImpl::StartWaitForNetworkManagerTimer()
{
CancelWaitForNetworkManagerTimer();
networkManagerTimerHandle = dbusDispatcher.PostDelayed(
std::chrono::seconds(5),
[this]()
{
networkManagerTimerHandle = 0;
this->UpdateNetworkManagerStatus();
});
}
void HotspotManagerImpl::CancelWaitForNetworkManagerTimer()
{
if (networkManagerTimerHandle)
{
dbusDispatcher.CancelPost(networkManagerTimerHandle);
networkManagerTimerHandle = 0;
}
}
void HotspotManagerImpl::WaitForNetworkManager()
{
Lv2Log::debug("HotspotManager: state=WaitingForNetworkManager");
SetState(State::WaitingForNetworkManager);
ReleaseNetworkManager();
StartWaitForNetworkManagerTimer();
}
void HotspotManagerImpl::onReload()
{
if (closed)
return;
WifiConfigSettings oldSettings = this->wifiConfigSettings;
;
wifiConfigSettings.Load();
switch (state)
{
case State::Initial:
case State::Error:
// ignore.
return;
default:
MaybeStartHotspot();
return;
}
}
HotspotManagerImpl::~HotspotManagerImpl()
{
Close();
}
void HotspotManagerImpl::ThreadProc()
{
try
{
}
catch (const std::exception &e)
{
Lv2Log::error(SS("Hotspot Manager thread terminated. " << e.what()));
}
}
void HotspotManagerImpl::Reload()
{
dbusDispatcher.Post([this]()
{ onReload(); });
}
void HotspotManagerImpl::Close()
{
if (!closed)
{
closed = true;
dbusDispatcher.Post([this]
{ onClose(); });
dbusDispatcher.Stop();
}
}
HotspotManager::ptr HotspotManager::Create()
{
return std::make_unique<HotspotManagerImpl>();
}
template <typename T>
class VectorHash
{
public:
std::size_t operator()(const std::vector<T> &vec) const
{
std::size_t hash = vec.size();
for (auto &i : vec)
{
hash += static_cast<size_t>(i);
hash = ((hash >> 32) ^ hash) * 0x119de1F3;
}
return hash;
}
};
struct NetworkSortRecord
{
std::string ssid;
bool connected = false;
bool knownNetwork = false;
bool visibleNetwork = false;
uint8_t strength;
};
void HotspotManagerImpl::UpdateKnownNetworks(
std::vector<ssid_t> &knownSsids,
std::vector<AccessPoint::ptr> &allAccessPoints)
{
std::map<std::string, NetworkSortRecord> map;
for (auto &knownSsid : knownSsids)
{
std::string ssid = ssidToString(knownSsid);
if (ssid.length() != 0)
{
NetworkSortRecord &record = map[ssid];
record.ssid = ssid;
record.knownNetwork = true;
}
}
for (auto &accessPoint : allAccessPoints)
{
try {
uint8_t strength = accessPoint->Strength();
auto vSsid = accessPoint->Ssid();
std::string ssid = ssidToString(vSsid);
if (ssid.length() != 0)
{
NetworkSortRecord &record = map[ssid];
record.ssid = ssid;
record.visibleNetwork = true;
record.strength = strength;
}
} catch (const std::exception&ignored)
{
// race to get the info before it changes. np.
}
}
if (this->wlanDevice)
{
auto activeConnectionPath = this->wlanDevice->ActiveConnection();
if (activeConnectionPath.length() > 2) // "/" -> no connection. Be paranoid about "".
{
auto activeConnection = ActiveConnection::Create(dbusDispatcher, activeConnectionPath);
std::string activeSsid = activeConnection->Id();
auto it = map.find(activeSsid);
if (it != map.end())
{
it->second.connected = true;
}
}
}
std::vector<NetworkSortRecord> records;
records.reserve(map.size());
for (auto &mapEntry : map)
{
records.push_back(mapEntry.second);
}
std::sort(records.begin(), records.end(), [](const NetworkSortRecord &left, const NetworkSortRecord &right)
{
if (left.connected != right.connected)
{
return left.connected > right.connected;
}
if (left.knownNetwork != right.knownNetwork)
{
return left.knownNetwork > right.knownNetwork;
}
if (left.visibleNetwork != right.visibleNetwork)
{
return left.visibleNetwork > right.visibleNetwork;
}
if (left.strength != right.strength)
{
return left.strength > right.strength;
}
return false; });
if (records.size() > 10)
{
records.resize(10);
}
std::vector<std::string> result;
result.reserve(records.size());
for (auto &record : records)
{
result.push_back(std::move(record.ssid));
}
{
std::lock_guard lock{this->knownWifiNetworksMutex};
this->knownWifiNetworks = std::move(result);
}
}
std::vector<AccessPoint::ptr> HotspotManagerImpl::GetAllAccessPoints()
{
std::vector<AccessPoint::ptr> accessPoints;
for (const auto &accessPointPath : wlanWirelessDevice->AccessPoints())
{
auto accessPoint = AccessPoint::Create(dbusDispatcher, accessPointPath);
accessPoints.push_back(std::move(accessPoint));
}
return accessPoints;
}
std::vector<std::vector<uint8_t>> HotspotManagerImpl::GetKnownVisibleAccessPoints(const std::vector<ssid_t> &allAccessPoints)
{
std::vector<std::vector<uint8_t>> knownSsids = this->GetAllAutoConnectSsids();
std::unordered_set<std::vector<uint8_t>, VectorHash<uint8_t>> index{knownSsids.begin(), knownSsids.end()};
std::vector<ssid_t> result;
std::vector<AccessPoint::ptr> accessPoints;
for (const auto &accessPoint : allAccessPoints)
{
if (index.contains(accessPoint))
{
result.push_back(accessPoint);
}
}
return result;
}
std::vector<std::vector<uint8_t>> HotspotManagerImpl::GetAllAutoConnectSsids()
{
auto availableConnections = wlanDevice->AvailableConnections();
std::vector<std::vector<uint8_t>> ssids;
for (const auto &connectionPath : availableConnections)
{
auto connection = Connection::Create(dbusDispatcher, connectionPath);
try
{
auto settings = connection->GetSettings();
bool autoConnect = true;
if (settings["connect"].count("autoconnect") > 0)
{
autoConnect = settings["connect"]["autoconnect"];
}
bool isInfrastructure =
settings["802-11-wireless"].count("mode") > 0 && settings["802-11-wireless"]["mode"].get<std::string>() == "infrastructure";
if (isInfrastructure && autoConnect)
{
std::vector<uint8_t> ssid =
settings["802-11-wireless"]["ssid"];
ssids.push_back(std::move(ssid));
}
}
catch (const std::exception &ignored)
{
// not totally sure of structure of all connection types.
}
}
return ssids;
}
void HotspotManagerImpl::onAccessPointsChanged()
{
MaybeStartHotspot();
}
void HotspotManagerImpl::CancelAccessPointsChangedTimer()
{
if (accessPointsChangedTimerHandle)
{
dbusDispatcher.CancelPost(accessPointsChangedTimerHandle);
accessPointsChangedTimerHandle = 0;
}
}
void HotspotManagerImpl::onAccessPointChanged()
{
// coalesce large bursts of AccessPointChanged calls using a timer.
if (!accessPointsChangedTimerHandle)
{
accessPointsChangedTimerHandle = dbusDispatcher.PostDelayed(
std::chrono::milliseconds(512),
[this]()
{
accessPointsChangedTimerHandle = 0;
onAccessPointsChanged(); // coalesced handling of one or more changes.
});
}
}
static std::vector<std::vector<uint8_t>> GetAccessPointSsids(std::vector<AccessPoint::ptr> &accessPoints)
{
std::vector<std::vector<uint8_t>> result;
for (const auto &accessPoint : accessPoints)
{
try
{
result.push_back(accessPoint->Ssid());
}
catch (const std::exception &ignored)
{
// race to get a disappearing ssid. Ignore the error.
}
}
return result;
}
void HotspotManagerImpl::MaybeStartHotspot()
{
if (this->state == State::Error)
return;
if (this->closed)
return;
if (!wlanDevice || !wlanWirelessDevice)
{
// devices are transitioning. Do nothing.
return;
}
std::vector<AccessPoint::ptr> allAccessPoints = GetAllAccessPoints();
std::vector<std::vector<uint8_t>> allAccessPointSsids = GetAccessPointSsids(allAccessPoints);
std::vector<std::vector<uint8_t>> connectableSsids = GetKnownVisibleAccessPoints(allAccessPointSsids); // all the ssids currently visible for which we have credentials.
this->UpdateKnownNetworks(connectableSsids, allAccessPoints);
bool wantsHotspot = this->wifiConfigSettings.WantsHotspot(
this->ethernetConnected, connectableSsids, allAccessPointSsids);
if (this->state == State::Monitoring && wantsHotspot)
{
StartHotspot();
}
else
{
if ((!wantsHotspot) && (this->state == State::HotspotConnected || this->state == State::HotspotConnecting))
{
StopHotspot();
}
}
}
void HotspotManagerImpl::EnableHotspot()
{
}
void HotspotManagerImpl::DisableHotspot()
{
StopHotspot();
}
Connection::ptr HotspotManagerImpl::FindExistingConnection()
{
if (wlanDevice)
{
for (const auto &connectionPath : wlanDevice->AvailableConnections())
{
auto connection = Connection::Create(dbusDispatcher, connectionPath);
auto settings = connection->GetSettings();
bool isHotspot =
settings["802-11-wireless"].count("mode") > 0 && settings["802-11-wireless"]["mode"].get<std::string>() == "ap";
if (isHotspot)
{
std::string id;
if (settings["connection"].count("id") > 0)
{
id = settings["connection"]["id"].get<std::string>();
}
if (id.starts_with(PIPEDAL_HOTSPOT_NAME))
{
return connection;
}
}
}
}
return nullptr;
}
void HotspotManagerImpl::StartHotspot()
{
if (this->state == State::Monitoring)
{
try
{
ServiceConfiguration serviceConfiguration;
serviceConfiguration.Load();
Lv2Log::debug("HotspotManager: state=HotspotConnecting");
SetState(State::HotspotConnecting);
Lv2Log::info("HotspotManager: Enabling PiPedal hotspot.");
// do it ONLY if we're in monitoring state.
// Create a proxy for NetworkManager
// Create connection settings for the hotspot
std::map<std::string, std::map<std::string, sdbus::Variant>> settings;
std::map<std::string, sdbus::Variant> &connection = settings["connection"];
connection["type"] = "802-11-wireless";
connection["autoconnect"] = false;
connection["id"] = PIPEDAL_HOTSPOT_NAME;
connection["interface-name"] = wlanDevice->Interface();
connection["uuid"] = serviceConfiguration.uuid;
std::map<std::string, sdbus::Variant> &wireless = settings["802-11-wireless"];
std::string ssid = this->wifiConfigSettings.hotspotName_;
std::vector<uint8_t> vSsid{ssid.begin(), ssid.end()};
wireless["ssid"] = vSsid;
wireless["mode"] = "ap";
wireless["band"] = "a";
wireless["band"] = "bg";
uint32_t iChannel = 0;
auto channel = this->wifiConfigSettings.channel_;
if (channel.length() != 0)
{
std::stringstream ss{channel};
ss >> iChannel;
}
if (iChannel != 0)
{
wireless["channel"] = iChannel;
}
std::map<std::string, sdbus::Variant> &wirelessSecurity = settings["802-11-wireless-security"];
wirelessSecurity["key-mgmt"] = "wpa-psk";
wirelessSecurity["psk"] = wifiConfigSettings.password_;
settings["ipv4"]["method"] = "shared";
settings["ipv6"]["method"] = "shared";
// settings["ipv6"]["addr-gen-mode"] = "stable-privacy";
std::map<std::string, sdbus::Variant> options;
options["persist"] = "disk";
Connection::ptr existingConnection = FindExistingConnection();
if (existingConnection)
{
existingConnection->Update(settings);
auto activeConnectionPath = networkManager->ActivateConnection(
existingConnection->getObjectPath(),
wlanDevice->getObjectPath(),
"/");
this->activeConnection = ActiveConnection::Create(dbusDispatcher, activeConnectionPath);
}
else
{
// Call AddAndActivateConnection2 method to create and activate the hotspot
sdbus::ObjectPath nullPath("/");
auto result = networkManager->AddAndActivateConnection2(
settings, wlanDevice->getObjectPath(), "/", options
);
auto connectionPath = std::get<0>(result);
auto activeConnectionPath = std::get<1>(result);
// auto resultArgs = std::get<2>(result);
this->activeConnection = ActiveConnection::Create(dbusDispatcher, activeConnectionPath);
}
SetState(State::HotspotConnected);
Lv2Log::info("HotspotManager: Hotspot activated.");
FireNetworkChanging();
}
catch (const std::exception &e)
{
onError(SS("HotspotManager: Activation failed: " << e.what()));
}
}
else
{
onError("HotspotManager: Illegal state (StartHotspot)");
}
}
void HotspotManagerImpl::StopHotspot()
{
// do it regardless of state.
try
{
if (activeConnection)
{
FireNetworkChanging();
networkManager->DeactivateConnection(activeConnection->getObjectPath());
activeConnection = nullptr;
}
}
catch (const std::exception &e)
{
Lv2Log::error(SS("HotspotManager: Failed to deactivate hotspot. " << e.what()));
activeConnection = nullptr;
}
if (this->state == State::HotspotConnected || this->state == State::HotspotConnecting)
{
Lv2Log::debug("HotspotManager: state=HotspotMonitoring");
SetState(State::Monitoring);
Lv2Log::info("HotspotManager: PiPedal hotspot disabled.");
}
}
static const std::chrono::seconds scanInterval{60};
void HotspotManagerImpl::StartScanTimer()
{
StopScanTimer();
ScanNow();
}
void HotspotManagerImpl::StopScanTimer()
{
if (this->scanTimerHandle)
{
dbusDispatcher.CancelPost(this->scanTimerHandle);
this->scanTimerHandle = 0;
}
}
void HotspotManagerImpl::ScanNow()
{
this->scanTimerHandle = 0;
if (wlanWirelessDevice)
{
std::map<std::string, sdbus::Variant> options;
try
{
Lv2Log::debug("Scanning");
wlanWirelessDevice->RequestScan(options);
}
catch (const std::exception &e)
{
Lv2Log::error(SS("HotspotMonitor: Wi-Fi RequestScan failed." << e.what()));
return;
}
this->scanTimerHandle = this->dbusDispatcher.PostDelayed(
std::chrono::duration_cast<std::chrono::steady_clock::duration>(scanInterval),
[this]()
{
ScanNow();
});
}
}
HotspotManagerImpl::PostHandle HotspotManagerImpl::Post(PostCallback &&fn)
{
return dbusDispatcher.Post(std::move(fn));
}
HotspotManagerImpl::PostHandle HotspotManagerImpl::PostDelayed(const clock::duration &delay, PostCallback &&fn)
{
return dbusDispatcher.PostDelayed(delay, std::move(fn));
}
bool HotspotManagerImpl::CancelPost(PostHandle handle)
{
return dbusDispatcher.CancelPost(handle);
}
void HotspotManagerImpl::SetNetworkChangingListener(NetworkChangingListener &&listener)
{
std::lock_guard<std::recursive_mutex> lock{this->networkChangingListenerMutex};
this->networkChangingListener = std::move(listener);
}
void HotspotManagerImpl::FireNetworkChanging()
{
std::lock_guard<std::recursive_mutex> lock{this->networkChangingListenerMutex};
if (this->networkChangingListener)
{
this->networkChangingListener(this->ethernetConnected, !!activeConnection);
}
}
std::vector<std::string> HotspotManagerImpl::GetKnownWifiNetworks()
{
std::lock_guard lock(knownWifiNetworksMutex); // pushed off the service thread.
return this->knownWifiNetworks;
}
+76
View File
@@ -0,0 +1,76 @@
// 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.
#pragma once
#include <memory>
#include <functional>
#include <chrono>
namespace pipedal {
class HotspotManager {
// no move, no delete.
HotspotManager(const HotspotManager&) = delete;
HotspotManager(HotspotManager&&) = delete;
HotspotManager & operator=(const HotspotManager&) = delete;
HotspotManager & operator=(const HotspotManager&&) = delete;
protected:
HotspotManager() {} // use Create().
public:
using clock = std::chrono::steady_clock;
using ptr = std::unique_ptr<HotspotManager>;
static ptr Create();
virtual ~HotspotManager() noexcept { }
virtual void Open() = 0;
virtual void Reload() = 0;
virtual void Close() = 0;
virtual std::vector<std::string> GetKnownWifiNetworks() = 0;
using NetworkChangingListener = std::function<void(bool ethernetConnected,bool hotspotEnabled)>;
virtual void SetNetworkChangingListener(NetworkChangingListener &&listener) = 0;
using PostHandle = uint64_t;
using PostCallback = std::function<void()>;
virtual PostHandle Post(PostCallback&&fn) = 0;
virtual PostHandle PostDelayed(const clock::duration&delay,PostCallback&&fn) = 0;
virtual bool CancelPost(PostHandle handle) = 0;
template<class REP,class PERIOD>
PostHandle PostDelayed(const std::chrono::duration<REP,PERIOD>&delay,PostCallback&&fn)
{
return PostDelayed(
std::chrono::duration_cast<clock::duration,REP,PERIOD>(delay),
std::move(fn));
}
};
}
+21 -37
View File
@@ -30,7 +30,7 @@
using namespace pipedal;
std::string pipedal::GetLinkLocalAddress(const std::string fromAddress);
std::string pipedal::GetNonLinkLocalAddress(const std::string fromAddress);
static bool IsIpv4MappedAddress(const struct in6_addr &inetAddr6)
{
@@ -302,6 +302,7 @@ static std::string GetInterfaceForIp6Address(const in6_addr inetAddr6)
{ // 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);
if (ipv6NetmaskCompare(inetAddr6, pAddr->sin6_addr, pNetMask->sin6_addr))
{
result = p->ifa_name;
@@ -313,7 +314,7 @@ static std::string GetInterfaceForIp6Address(const in6_addr inetAddr6)
return result;
}
static std::string GetLinkLocalAddressForInterface(const std::string &name)
static std::string GetNonLinkLocalAddressForInterface(const std::string &name)
{
struct ifaddrs *ifap = nullptr;
if (getifaddrs(&ifap) != 0)
@@ -324,7 +325,7 @@ static std::string GetLinkLocalAddressForInterface(const std::string &name)
if (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)))
if (!IN6_IS_ADDR_LINKLOCAL(&(pAddr->sin6_addr)))
{
if (name == p->ifa_name)
{
@@ -353,7 +354,7 @@ static std::string GetLinkLocalAddressForInterface(const std::string &name)
freeifaddrs(ifap);
return result;
}
static std::string GetLinkLocalAddressForIp4Interface(const std::string &name)
static std::string GetNonLinkLocalAddressForIp4Interface(const std::string &name)
{
struct ifaddrs *ifap = nullptr;
if (getifaddrs(&ifap) != 0)
@@ -384,23 +385,13 @@ static std::string GetLinkLocalAddressForIp4Interface(const std::string &name)
return result;
}
std::string pipedal::GetLinkLocalAddress(const std::string fromAddress)
std::string pipedal::GetNonLinkLocalAddress(const std::string fromAddress)
{
std::string address = StripPortNumber(fromAddress);
std::string address = fromAddress;
std::string result;
if (address[0] != '[')
{
// ipv4
struct in_addr inetAddr;
memset(&inetAddr, 0, sizeof(inetAddr));
if (inet_pton(AF_INET, address.c_str(), &inetAddr) == 1)
{
uint32_t remoteAddress = htonl(inetAddr.s_addr);
std::string interfaceName = GetInterfaceForIp4Address(remoteAddress);
result = GetLinkLocalAddressForIp4Interface(interfaceName);
}
return address;
}
else
{
@@ -408,13 +399,14 @@ std::string pipedal::GetLinkLocalAddress(const std::string fromAddress)
if (address[0] != '[' || address[address.length() - 1] != ']')
throw std::invalid_argument("Bad address.");
address = address.substr(1, address.length() - 2);
// strip scope if neccessary.
auto pos = address.find_last_of('%');
if (pos != std::string::npos)
{
address = address.substr(0, pos);
}
auto nPos = address.find('%') ;
if (nPos != std::string::npos)
{
std::string ifName = address.substr(nPos+1);
return GetNonLinkLocalAddressForInterface(ifName);
}
struct in6_addr inetAddr6;
memset(&inetAddr6, 0, sizeof(inetAddr6));
if (inet_pton(AF_INET6, address.c_str(), &inetAddr6) == 1)
@@ -430,33 +422,25 @@ std::string pipedal::GetLinkLocalAddress(const std::string fromAddress)
int8_t *pAddr = (int8_t *)&inetAddr6;
uint32_t remoteAddress = htonl(*(int32_t *)(pAddr + 12));
std::string interfaceName = GetInterfaceForIp4Address(remoteAddress);
result = GetLinkLocalAddressForIp4Interface(interfaceName);
result = GetNonLinkLocalAddressForIp4Interface(interfaceName);
}
else
{
std::string interfaceName;
if (IsIpv4MappedAddress(inetAddr6))
{
uint32_t remoteAddress = GetIpv4MappedAddress(inetAddr6);
interfaceName = GetInterfaceForIp4Address(remoteAddress);
}
else
{
interfaceName = GetInterfaceForIp6Address(inetAddr6);
}
result = GetLinkLocalAddressForInterface(interfaceName);
std::string interfaceName = GetInterfaceForIp6Address(inetAddr6);
result = GetNonLinkLocalAddressForInterface(interfaceName);
}
}
}
if (result == "")
{
result = GetLinkLocalAddressForInterface("");
result = GetNonLinkLocalAddressForInterface("");
}
return result;
}
std::string pipedal::GetInterfaceIpv4Address(const std::string& interfaceName)
{
return GetLinkLocalAddressForIp4Interface(interfaceName);
return GetNonLinkLocalAddressForIp4Interface(interfaceName);
}
+1 -1
View File
@@ -27,7 +27,7 @@ namespace pipedal {
std::string GetInterfaceIpv4Address(const std::string& interfaceName);
std::string GetLinkLocalAddress(const std::string fromAddress);
std::string GetNonLinkLocalAddress(const std::string fromAddress);
bool IsOnLocalSubnet(const std::string&fromAddress);

Some files were not shown because too many files have changed in this diff Show More