Merge pull request #175 from rerdavies/dev

v1,2,41 Release
This commit is contained in:
Robin Davies
2024-08-21 21:25:09 -04:00
committed by GitHub
10 changed files with 351 additions and 341 deletions
+2 -2
View File
@@ -1,10 +1,10 @@
cmake_minimum_required(VERSION 3.16.0) cmake_minimum_required(VERSION 3.16.0)
project(pipedal project(pipedal
VERSION 1.2.40 VERSION 1.2.41
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi" DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
HOMEPAGE_URL "https://rerdavies.github.io/pipedal" HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
) )
set (DISPLAY_VERSION "v1.2.40Beta6") set (DISPLAY_VERSION "v1.2.41")
set (CMAKE_INSTALL_PREFIX "/usr/") set (CMAKE_INSTALL_PREFIX "/usr/")
+2 -2
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://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> <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.40 Beta6</a> Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.2.41</a>
Website:&nbsp;[https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal). 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). Documentation:&nbsp;[https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html).
&nbsp; &nbsp;
#### NEW version 1.2.40 Beta6 release, providing support for Raspberry Pi OS Bookworm. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. #### NEW version 1.2.41 Release, providing support for Raspberry Pi OS Bookworm. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
&nbsp; &nbsp;
+3 -3
View File
@@ -13,17 +13,17 @@ page_icon: img/Install4.jpg
Download the most recent Debian (.deb) package for your platform: Download the most recent Debian (.deb) package for your platform:
- [Raspberry Pi OS bookworm (64-bit) v1.2.40 Beta6](https://github.com/rerdavies/pipedal/releases/download/) - [Raspberry Pi OS bookworm (64-bit) v1.2.41](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) - [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.40 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.41 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 Install the package by running
``` ```
sudo apt update sudo apt update
cd ~/Downloads cd ~/Downloads
sudo apt-get install pipedal_1.2.40_arm64.deb sudo apt-get install pipedal_1.2.41_arm64.deb
``` ```
Adjust accordingly if you have downloaded v1.1.31. Adjust accordingly if you have downloaded v1.1.31.
+54 -1
View File
@@ -1,9 +1,62 @@
# Release Notes # Release Notes
## PiPedal 1.2.41 Release
This version includes the following new features:
- Supports Raspberry Pi 5.
- Supports Rasberry Pi OS Bookworm.
- TooB ML allows uploading of models. See below for further details.
- TooBML support for large models (e.g. GuitarML Proteus models)
- Upload .zip file bundles to all File plugin controls.
- Uploads can be organized into sub-directories.
- Various minor improvements to TooB plugin user interfaces.
You can now upload new models into TooB ML. TooB ML models are compatible with models for the GuitarML Proteus plugin. Currently, the GuitarML Proteus model download pages are the best source for new models to use with TooB ML. To install
new models in TooB ML, follow these instructions:
- Download Proteus models from https://guitarml.com/tonelibrary/tonelib-pro.html to your local computer or Android
device. TooB ML supports both Proteus .json models, and also allows you to install model collections contained in .zip files.
- Load the TooB ML plugin.
- click on the Model control.
- click on the Upload button in the lower left corner of the Model selection dialog.
- Upload the .json model file or .zip model collection that you downloaded from The GuitarML Tone Library.
- PiPedal now monitors LV2 plugin directories for changes, and automatically updates available plugins in the
PiPedal UI, without requiring a restart.
Big fixes:
- Wi-Fi is restarted properly without requiring a reboot when the PiPedal Wi-Fi Direct connection is disabled.
- Dialog button colors follow Android UI conventions.
- The web server supports uploading of large files (limited by default to 512MB).
- Failure to open audio devices upon reboot for devices that initialize slowly.
- Misbehavior with preset names after renaming a preset.
- Dialog button colors follow Android UI conventions.
- Toggle and on/off controls now display in purple instead of red/pink.
- automatic provisioning of resource files in .LV2 plugins.
- Gain control for ToobML is enabled or disabled depending on whether the model has a gain control input (Thanks to @Foolhen)
- The control view for split controls now displays properly.
- Stereo connectors in dark mode now render properly.
- Permissions are correctly set on existing files and folders in /var/pipedal at install time. `pipedalconfig1 has a new
`--fix-permissions` option, which corrects permissions on files in /var/pipedal.
- Bypass MIDI bindings now work properly. Thanks to @FoolHen.
- GxTuner now correctly displays note pitches. Thanks to @FoolHen.
- Fixed glitch in on-boarding process when changing Wi-Fi direct settings.
- Fixed incorrect display of banks in dark mode Thanks to @FoolHen.
- pidedald service shuts down cleanly. With help from @FoolHen.
- Fixed missing dependency in build procedure documentation. Thanks to @FoolHen
- Documentation edits and corrections. With help from @FoolHen.
- automatic provisioning of resource files in .LV2 plugins (devolopment feature)
- uninstall.sh now removes all files installed by ./install.sh (used only during development).
## Pipedal 1.2.40 Beta6 ## Pipedal 1.2.40 Beta6
- PiPedal now monitors lv2 directories in order to automatically add newly-installed LV2 plugins. - PiPedal now monitors lv2 directories in order to automatically add newly-installed LV2 plugins.
- Toggle and on/off switch colors changed from red/pink to purple. - Toggle and on/off switch colors changed from red/pink to purple.
- MIDI bindings can be created for split controls.
## Pipedal 1.2.38 Beta6 ## Pipedal 1.2.38 Beta5
Fixes: Fixes:
- PiPedal won't run on non-en-US locales. - PiPedal won't run on non-en-US locales.
+3 -3
View File
@@ -4,17 +4,17 @@
Download the most recent Debian (.deb) package for your platform: Download the most recent Debian (.deb) package for your platform:
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.2.40/pipedal_1.2.40_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.2.40 Beta6</a> - <a href="https://github.com/rerdavies/pipedal/releases/download/v1.2.41/pipedal_1.2.41_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.2.41</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.1.31/pipedal_1.1.31_arm64.deb">Ubuntu 21.04 or Raspberry Pi OS bullseyeyeye (64-bit) v1.1.31</a>
v1.2.40 Beta6 is does not currently support Ubuntu 21.04, or older versions of Raspberry Pi OS. v1.2.41 is does not currently support Ubuntu 21.04, or older versions of Raspberry Pi OS.
Install the package by running Install the package by running
``` ```
sudo apt update sudo apt update
cd ~/Downloads cd ~/Downloads
sudo apt-get install ./pipedal_1.2.40_arm64.deb sudo apt-get install ./pipedal_1.2.41_arm64.deb
``` ```
Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation. Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation.
+3 -2
View File
@@ -1,7 +1,7 @@
<img src="GithubBanner.png" width="100%"/> <img src="GithubBanner.png" width="100%"/>
<a href="Installing.html"><i>v1.2.40</i></a> <a href="Installing.html"><i>v1.2.41</i></a>
&nbsp; &nbsp;
@@ -9,8 +9,9 @@ To download PiPedal, click [here](download.md).
To view PiPedal documentation, click [here](Documentation.md). To view PiPedal documentation, click [here](Documentation.md).
> NEW version 1.2.40 Beta6 release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. #### NEW version 1.2.41 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
&nbsp;
Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet. Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet.
PiPedal running on a Raspberry Pi 4 or Pi 5 provides stable super-low-latency audio via external USB audio devices, or internal Raspberry Pi audio hats. PiPedal running on a Raspberry Pi 4 or Pi 5 provides stable super-low-latency audio via external USB audio devices, or internal Raspberry Pi audio hats.
+5 -3
View File
@@ -370,7 +370,7 @@ export default withStyles(styles, { withTheme: true })(
<Link underline="hover" <Link underline="hover"
color="inherit" color="inherit"
key="1" onClick={() => { this.navigate(""); }} key="1" onClick={() => { this.navigate(""); }}
sx={{ display: 'flex', alignItems: 'center' }} sx={{ display: 'flex', alignItems: 'center',cursor: "default" }}
> >
<HomeIcon sx={{ mr: 0.6 }} fontSize="inherit" /> <HomeIcon sx={{ mr: 0.6 }} fontSize="inherit" />
Home Home
@@ -383,7 +383,7 @@ export default withStyles(styles, { withTheme: true })(
target = pathConcat(target, directories[i]); target = pathConcat(target, directories[i]);
let myTarget = target; let myTarget = target;
breadcrumbs.push(( breadcrumbs.push((
<Link underline="hover" key={(i + 1).toString()} <Link underline="hover" key={(i + 1).toString()} style={{cursor: "default"}}
color="inherit" onClick={() => { this.navigate(myTarget) }}> color="inherit" onClick={() => { this.navigate(myTarget) }}>
{directories[i]} {directories[i]}
</Link> </Link>
@@ -392,7 +392,9 @@ export default withStyles(styles, { withTheme: true })(
{ {
let lastdirectory = directories[directories.length - 1]; let lastdirectory = directories[directories.length - 1];
breadcrumbs.push(( breadcrumbs.push((
<Typography noWrap key={directories.length.toString()} color="text.secondary"> {lastdirectory}</Typography> <Typography noWrap key={directories.length.toString()}
style={{cursor: "default"}}
color="text.secondary"> {lastdirectory}</Typography>
)); ));
} }
+261 -309
View File
@@ -26,7 +26,7 @@ interface Deserializable<T> {
} }
export class Port implements Deserializable<Port> { export class Port implements Deserializable<Port> {
deserialize(input: any): Port { deserialize(input: any): Port {
this.port_index = input.port_index; this.port_index = input.port_index;
this.symbol = input.symbol; this.symbol = input.symbol;
@@ -54,28 +54,27 @@ export class Port implements Deserializable<Port> {
static deserialize_array(input: any): Port[] { static deserialize_array(input: any): Port[] {
let result: Port[] = []; let result: Port[] = [];
for (let i = 0; i < input.length; ++i) for (let i = 0; i < input.length; ++i) {
{
result[i] = new Port().deserialize(input[i]); result[i] = new Port().deserialize(input[i]);
} }
return result; return result;
} }
port_index: number = -1; port_index: number = -1;
symbol: string = ""; symbol: string = "";
name: string = ""; name: string = "";
min_value: number = 0; min_value: number = 0;
max_value: number = 1; max_value: number = 1;
default_value: number = 0.5; default_value: number = 0.5;
scale_points: ScalePoint[] = []; scale_points: ScalePoint[] = [];
is_input: boolean = false; is_input: boolean = false;
is_output: boolean = false is_output: boolean = false
is_control_port: boolean = false; is_control_port: boolean = false;
is_audio_port: boolean = false; is_audio_port: boolean = false;
is_atom_port: boolean = false; is_atom_port: boolean = false;
is_valid: boolean = false; is_valid: boolean = false;
supports_midi: boolean = false; supports_midi: boolean = false;
supports_time_position: boolean = false; supports_time_position: boolean = false;
port_group: string = ""; port_group: string = "";
comment: string = ""; comment: string = "";
is_bypass: boolean = false; is_bypass: boolean = false;
is_program_controller: boolean = false; is_program_controller: boolean = false;
@@ -91,10 +90,9 @@ export class PortGroup {
return this; return this;
} }
static deserialize_array(input: any) : PortGroup[] { static deserialize_array(input: any): PortGroup[] {
let result: PortGroup[] = []; let result: PortGroup[] = [];
for (let i = 0; i < input.length; ++i) for (let i = 0; i < input.length; ++i) {
{
result.push(new PortGroup().deserialize(input[i])); result.push(new PortGroup().deserialize(input[i]));
} }
return result; return result;
@@ -114,67 +112,71 @@ export class UiFileType {
this.mimeType = input.mimeType; this.mimeType = input.mimeType;
return this; return this;
} }
static deserialize_array(input: any): UiFileType[] static deserialize_array(input: any): UiFileType[] {
{
let result: UiFileType[] = []; let result: UiFileType[] = [];
for (let i = 0; i < input.length; ++i) for (let i = 0; i < input.length; ++i) {
{
result[i] = new UiFileType().deserialize(input[i]); result[i] = new UiFileType().deserialize(input[i]);
} }
return result; return result;
} }
private static IsAndroid() : boolean private static IsAndroid(): boolean {
{
return /Android/i.test(navigator.userAgent); return /Android/i.test(navigator.userAgent);
} }
static MergeMimeTypes(fileTypes: UiFileType[]): string static MergeMimeTypes(fileTypes: UiFileType[]): string {
{
if (fileTypes.length === 0) { if (fileTypes.length === 0) {
return ""; return "";
} }
let result = fileTypes[0].mimeType; if (this.IsAndroid()) {
for (let i = 1; i < fileTypes.length; ++i) return ""; // allow selection of .zip files, plus whatever. We'll catch it later.
{
let fileType = fileTypes[i];
if (fileType.mimeType !== result)
{
if (result.startsWith("audio/") && fileType.mimeType.startsWith("audio/"))
{
result = "audio/*";
} else if (result.startsWith("video/") && fileType.mimeType.startsWith("video/"))
{
result = "video/*";
} else if (result.startsWith("text/") && fileType.mimeType.startsWith("text/"))
{
result = "text/*";
} else {
result = "application/octet-stream";
}
}
}
if (this.IsAndroid())
{
if (result.startsWith("audio/")) result = "audio/*";
if (result.startsWith("video/")) result = "video/*";
if (result.startsWith("text/")) result = "text/*";
} else { } else {
// chrome desktop thinks "application/octet-stream" is .exe, .com, or .bat. // chrome desktop thinks "application/octet-stream" is .exe, .com, or .bat.
// Feed it file extensions isntead. // Feed it file extensions isntead.
if (result === "application/octet-stream") let result = "";
{ for (let i = 0; i < fileTypes.length; ++i) {
result = ""; let fileType = fileTypes[i];
for (let i = 0; i < fileTypes.length; ++i) if (fileType.fileExtension !== "") {
{ if (result.length !== 0) // prefer file extensions.
if (i !== 0)
{ {
result += ','; result += ",";
result += fileType.fileExtension;
}
} else {
if (fileType.mimeType !== result) {
let t = "";
if (result.startsWith("audio/") && fileType.mimeType.startsWith("audio/")) {
t = "audio/*";
} else if (result.startsWith("video/") && fileType.mimeType.startsWith("video/")) {
t = "video/*";
} else if (result.startsWith("text/") && fileType.mimeType.startsWith("text/")) {
t = "text/*";
}
if (t.length !== 0) {
if (result.length !== 0) {
result += ",";
}
result += t;
}
} }
result += fileTypes[i].fileExtension;
} }
} }
let hasZip = false;
let types = result.split(",");
for (let thisType of types) {
if (thisType === ".zip") {
hasZip = true;
break;
}
}
// add .zip files
if (!hasZip) {
if (result.length !== 0) {
result += ",.zip";
}
}
return result;
} }
return result;
} }
name: string = ""; name: string = "";
fileExtension: string = ""; fileExtension: string = "";
@@ -182,19 +184,16 @@ export class UiFileType {
} }
export class UiPropertyNotification { export class UiPropertyNotification {
deserialize(input: any): UiPropertyNotification deserialize(input: any): UiPropertyNotification {
{
this.portIndex = input.portIndex; this.portIndex = input.portIndex;
this.symbol = input.symbol; this.symbol = input.symbol;
this.plugin = input.plugin; this.plugin = input.plugin;
this.protocol = input.protocol; this.protocol = input.protocol;
return this; return this;
} }
static deserialize_array(input: any): UiPropertyNotification[] static deserialize_array(input: any): UiPropertyNotification[] {
{
let result: UiPropertyNotification[] = []; let result: UiPropertyNotification[] = [];
for (let i = 0; i < input.length; ++i) for (let i = 0; i < input.length; ++i) {
{
result[i] = new UiPropertyNotification().deserialize(input[i]); result[i] = new UiPropertyNotification().deserialize(input[i]);
} }
return result; return result;
@@ -207,8 +206,7 @@ export class UiPropertyNotification {
}; };
export class UiFrequencyPlot { export class UiFrequencyPlot {
deserialize(input: any): UiFrequencyPlot deserialize(input: any): UiFrequencyPlot {
{
this.patchProperty = input.patchProperty; this.patchProperty = input.patchProperty;
this.index = input.index; this.index = input.index;
this.portGroup = input.portGroup; this.portGroup = input.portGroup;
@@ -220,11 +218,9 @@ export class UiFrequencyPlot {
this.width = input.width; this.width = input.width;
return this; return this;
} }
static deserialize_array(input: any): UiFrequencyPlot[] static deserialize_array(input: any): UiFrequencyPlot[] {
{
let result: UiFrequencyPlot[] = []; let result: UiFrequencyPlot[] = [];
for (let i = 0; i < input.length; ++i) for (let i = 0; i < input.length; ++i) {
{
result[i] = new UiFrequencyPlot().deserialize(input[i]); result[i] = new UiFrequencyPlot().deserialize(input[i]);
} }
return result; return result;
@@ -242,78 +238,73 @@ export class UiFrequencyPlot {
}; };
export class UiFileProperty { export class UiFileProperty {
deserialize(input: any): UiFileProperty deserialize(input: any): UiFileProperty {
{ this.label = input.label;
this.label = input.label; this.fileTypes = UiFileType.deserialize_array(input.fileTypes);
this.fileTypes = UiFileType.deserialize_array(input.fileTypes); this.patchProperty = input.patchProperty;
this.patchProperty = input.patchProperty; this.directory = input.directory;
this.directory = input.directory; this.index = input.index;
this.index = input.index; this.portGroup = input.portGroup;
this.portGroup = input.portGroup; this.resourceDirectory = input.resourceDirectory ?? "";
this.resourceDirectory = input.resourceDirectory??""; return this;
return this; }
} static deserialize_array(input: any): UiFileProperty[] {
static deserialize_array(input: any): UiFileProperty[] let result: UiFileProperty[] = [];
{ for (let i = 0; i < input.length; ++i) {
let result: UiFileProperty[] = []; result[i] = new UiFileProperty().deserialize(input[i]);
for (let i = 0; i < input.length; ++i)
{
result[i] = new UiFileProperty().deserialize(input[i]);
}
return result;
} }
return result;
}
private static getFileExtension(name: string): string { private static getFileExtension(name: string): string {
let pos = name.lastIndexOf('.'); let pos = name.lastIndexOf('.');
let filenamePos = name.lastIndexOf('/') +1; let filenamePos = name.lastIndexOf('/') + 1;
filenamePos = Math.max(name.lastIndexOf('\\')+1); filenamePos = Math.max(name.lastIndexOf('\\') + 1);
filenamePos = Math.max(name.lastIndexOf(':')+1); filenamePos = Math.max(name.lastIndexOf(':') + 1);
if (pos < filenamePos) if (pos < filenamePos) {
{
return "";
}
if (pos !== -1) {
return name.substring(pos);
}
return ""; return "";
} }
wantsFile(filename: string) : boolean { if (pos !== -1) {
if (this.fileTypes.length === 0) { return name.substring(pos);
}
return "";
}
wantsFile(filename: string): boolean {
if (this.fileTypes.length === 0) {
return true;
}
let extension = UiFileProperty.getFileExtension(filename);
if (extension === ".zip") {
return true;
}
for (let fileType of this.fileTypes) {
if (fileType.fileExtension === extension) {
return true; return true;
} }
let extension = UiFileProperty.getFileExtension(filename); if (fileType.fileExtension === "*" || fileType.fileExtension === "") {
for (let fileType of this.fileTypes) return true;
{
if (fileType.fileExtension === extension )
{
return true;
}
if (fileType.fileExtension === "*" || fileType.fileExtension === "")
{
return true;
}
} }
return false;
} }
return false;
}
label: string = ""; label: string = "";
fileTypes: UiFileType[] = []; fileTypes: UiFileType[] = [];
patchProperty: string = ""; patchProperty: string = "";
directory: string = ""; directory: string = "";
index: number = -1; index: number = -1;
portGroup: string = ""; portGroup: string = "";
resourceDirectory: string = ""; resourceDirectory: string = "";
}; };
export class Lv2Plugin implements Deserializable<Lv2Plugin> { export class Lv2Plugin implements Deserializable<Lv2Plugin> {
deserialize(input: any): Lv2Plugin deserialize(input: any): Lv2Plugin {
{
this.uri = input.uri; this.uri = input.uri;
this.name = input.name; this.name = input.name;
this.brand = input.name? input.name: ""; this.brand = input.name ? input.name : "";
this.label = input.label? input.label: this.name; this.label = input.label ? input.label : this.name;
this.plugin_class = input.plugin_class; this.plugin_class = input.plugin_class;
this.supported_features = input.supported_features; this.supported_features = input.supported_features;
this.required_features = input.required_features; this.required_features = input.required_features;
@@ -321,23 +312,20 @@ export class Lv2Plugin implements Deserializable<Lv2Plugin> {
this.author_name = input.author_name; this.author_name = input.author_name;
this.author_homepage = input.author_homepage; this.author_homepage = input.author_homepage;
this.comment = input.comment; this.comment = input.comment;
this.ports= Port.deserialize_array(input.ports); this.ports = Port.deserialize_array(input.ports);
this.port_groups = PortGroup.deserialize_array(input.port_groups); this.port_groups = PortGroup.deserialize_array(input.port_groups);
if (input.fileProperties) if (input.fileProperties) {
{
this.fileProperties = UiFileProperty.deserialize_array(input.fileProperties) this.fileProperties = UiFileProperty.deserialize_array(input.fileProperties)
} else { } else {
this.fileProperties = []; this.fileProperties = [];
} }
if (input.frequencyPlots) if (input.frequencyPlots) {
{
this.frequencyPlots = UiFrequencyPlot.deserialize_array(input.frequencyPlots) this.frequencyPlots = UiFrequencyPlot.deserialize_array(input.frequencyPlots)
} else { } else {
this.frequencyPlots = []; this.frequencyPlots = [];
} }
if (input.uiPortNotifications) if (input.uiPortNotifications) {
{
this.uiPortNotifications = UiPropertyNotification.deserialize_array(input.uiPortNotifications); this.uiPortNotifications = UiPropertyNotification.deserialize_array(input.uiPortNotifications);
} else { } else {
this.uiPortNotifications = []; this.uiPortNotifications = [];
@@ -346,36 +334,34 @@ export class Lv2Plugin implements Deserializable<Lv2Plugin> {
} }
static EmptyFeatures: string[] = []; static EmptyFeatures: string[] = [];
uri: string = ""; uri: string = "";
name: string = ""; name: string = "";
brand: string = ""; brand: string = "";
label: string = ""; label: string = "";
plugin_class: string = ""; plugin_class: string = "";
supported_features: string[] = Lv2Plugin.EmptyFeatures; supported_features: string[] = Lv2Plugin.EmptyFeatures;
required_features: string[] = Lv2Plugin.EmptyFeatures; required_features: string[] = Lv2Plugin.EmptyFeatures;
optional_features: string[] = Lv2Plugin.EmptyFeatures; optional_features: string[] = Lv2Plugin.EmptyFeatures;
author_name: string = ""; author_name: string = "";
author_homepage: string = ""; author_homepage: string = "";
comment: string = ""; comment: string = "";
ports: Port[] = Port.EmptyPorts; ports: Port[] = Port.EmptyPorts;
port_groups: PortGroup[] = []; port_groups: PortGroup[] = [];
fileProperties: UiFileProperty[] = []; fileProperties: UiFileProperty[] = [];
frequencyPlots: UiFrequencyPlot[] = []; frequencyPlots: UiFrequencyPlot[] = [];
uiPortNotifications: UiPropertyNotification[] = []; uiPortNotifications: UiPropertyNotification[] = [];
} }
export class ScalePoint implements Deserializable<ScalePoint> { export class ScalePoint implements Deserializable<ScalePoint> {
deserialize(input: any): ScalePoint { deserialize(input: any): ScalePoint {
this.value = input.value; this.value = input.value;
this.label = input.label; this.label = input.label;
return this; return this;
} }
static deserialize_array(input: any): ScalePoint[] static deserialize_array(input: any): ScalePoint[] {
{
let result: ScalePoint[] = []; let result: ScalePoint[] = [];
for (let i = 0; i < input.length; ++i) for (let i = 0; i < input.length; ++i) {
{
result[i] = new ScalePoint().deserialize(input[i]); result[i] = new ScalePoint().deserialize(input[i]);
} }
return result; return result;
@@ -386,8 +372,8 @@ export class ScalePoint implements Deserializable<ScalePoint> {
export enum PluginType { export enum PluginType {
// Reserved types used in pedalboards. // Reserved types used in pedalboards.
None="", None = "",
InvalidPlugin= "InvalidPlugin", InvalidPlugin = "InvalidPlugin",
Plugin = "Plugin", Plugin = "Plugin",
AllpassPlugin = "AllpassPlugin", AllpassPlugin = "AllpassPlugin",
@@ -428,7 +414,7 @@ export enum PluginType {
UtilityPlugin = "UtilityPlugin", UtilityPlugin = "UtilityPlugin",
WaveshaperPlugin = "WaveshaperPlugin", WaveshaperPlugin = "WaveshaperPlugin",
// psuedo plugin type for the Amps node of the filter dialog. // psuedo plugin type for the Amps node of the filter dialog.
PiPedalAmpsNode = "PiPedalAmpsNode", PiPedalAmpsNode = "PiPedalAmpsNode",
// pseudo plugin types for splitter. // pseudo plugin types for splitter.
@@ -457,9 +443,8 @@ export enum ControlType {
OutputSelect OutputSelect
} }
export class UiControl implements Deserializable<UiControl> { export class UiControl implements Deserializable<UiControl> {
deserialize(input: any): UiControl deserialize(input: any): UiControl {
{
this.symbol = input.symbol; this.symbol = input.symbol;
this.name = input.name; this.name = input.name;
this.index = input.index; this.index = input.index;
@@ -480,27 +465,23 @@ export class UiControl implements Deserializable<UiControl> {
this.units = input.units as Units; this.units = input.units as Units;
this.comment = input.comment ?? ""; this.comment = input.comment ?? "";
this.is_bypass = input.is_bypass ? true: false; this.is_bypass = input.is_bypass ? true : false;
this.is_program_controller = input.is_program_controller? true: false; this.is_program_controller = input.is_program_controller ? true : false;
this.custom_units = input.custom_units ?? ""; this.custom_units = input.custom_units ?? "";
this.connection_optional = input.connection_optional ? true: false; this.connection_optional = input.connection_optional ? true : false;
if (this.is_bypass) if (this.is_bypass) {
{
this.not_on_gui = true; this.not_on_gui = true;
} }
this.controlType = ControlType.Dial; this.controlType = ControlType.Dial;
if (!this.is_input) if (!this.is_input) {
{ if (this.units === Units.midiNote) {
if (this.units === Units.midiNote)
{
this.controlType = ControlType.Tuner; this.controlType = ControlType.Tuner;
} else if (this.units === Units.db) } else if (this.units === Units.db) {
{
this.controlType = ControlType.DbVu; this.controlType = ControlType.DbVu;
} else if (this.enumeration_property) { } else if (this.enumeration_property) {
this.controlType = ControlType.OutputSelect; this.controlType = ControlType.OutputSelect;
@@ -508,45 +489,36 @@ export class UiControl implements Deserializable<UiControl> {
this.controlType = ControlType.Vu; this.controlType = ControlType.Vu;
} }
} }
if (this.isValidEnumeration()) if (this.isValidEnumeration()) {
{
this.controlType = ControlType.Select; this.controlType = ControlType.Select;
if (this.scale_points.length === 2) if (this.scale_points.length === 2) {
{
this.controlType = ControlType.ABSwitch; this.controlType = ControlType.ABSwitch;
} }
} else { } else {
if (this.toggled_property || (this.integer_property && this.min_value === 0 && this.max_value === 1)) if (this.toggled_property || (this.integer_property && this.min_value === 0 && this.max_value === 1)) {
{
this.controlType = ControlType.OnOffSwitch; this.controlType = ControlType.OnOffSwitch;
} }
} }
return this; return this;
} }
applyProperties(properties: Partial<UiControl>): UiControl applyProperties(properties: Partial<UiControl>): UiControl {
{ return { ...this, ...properties };
return {...this,...properties};
} }
private hasScalePoint(value: number): boolean { private hasScalePoint(value: number): boolean {
for (let scale_point of this.scale_points) for (let scale_point of this.scale_points) {
{
if (scale_point.value === value) return true; if (scale_point.value === value) return true;
} }
return false; return false;
} }
private isValidEnumeration() : boolean { private isValidEnumeration(): boolean {
if (this.enumeration_property) return true; if (this.enumeration_property) return true;
if (this.toggled_property && this.min_value === 0 && this.max_value === 1) if (this.toggled_property && this.min_value === 0 && this.max_value === 1) {
{ if (this.hasScalePoint(this.min_value) && this.hasScalePoint(this.max_value)) {
if (this.hasScalePoint(this.min_value) && this.hasScalePoint(this.max_value))
{
return true; return true;
} }
} }
if (this.integer_property && this.min_value === 0 && this.max_value === 1) if (this.integer_property && this.min_value === 0 && this.max_value === 1) {
{ if (this.hasScalePoint(this.min_value) && this.hasScalePoint(this.max_value)) {
if (this.hasScalePoint(this.min_value) && this.hasScalePoint(this.max_value))
{
return true; return true;
} }
} }
@@ -557,23 +529,22 @@ export class UiControl implements Deserializable<UiControl> {
static deserialize_array(input: any): UiControl[] { static deserialize_array(input: any): UiControl[] {
let result: UiControl[] = []; let result: UiControl[] = [];
for (let i = 0; i < input.length; ++i) for (let i = 0; i < input.length; ++i) {
{
result[i] = new UiControl().deserialize(input[i]); result[i] = new UiControl().deserialize(input[i]);
} }
return result; return result;
} }
symbol: string = ""; symbol: string = "";
name: string = ""; name: string = "";
index: number = -1; index: number = -1;
is_input: boolean = true; is_input: boolean = true;
min_value: number = 0; min_value: number = 0;
max_value: number = 1; max_value: number = 1;
default_value:number = 0.5; default_value: number = 0.5;
is_logarithmic: boolean = false; is_logarithmic: boolean = false;
display_priority: number = -1; display_priority: number = -1;
range_steps: number = 0; range_steps: number = 0;
integer_property:boolean = false; integer_property: boolean = false;
enumeration_property: boolean = false; enumeration_property: boolean = false;
trigger: boolean = false; trigger: boolean = false;
not_on_gui: boolean = false; not_on_gui: boolean = false;
@@ -589,16 +560,13 @@ export class UiControl implements Deserializable<UiControl> {
// Return the value of the closest scale_point. // Return the value of the closest scale_point.
clampSelectValue(value: number): number{ clampSelectValue(value: number): number {
if (this.scale_points.length !== 0) if (this.scale_points.length !== 0) {
{
let minError = 1.0E100; let minError = 1.0E100;
let bestValue = value; let bestValue = value;
for (let i = 0; i < this.scale_points.length; ++i) for (let i = 0; i < this.scale_points.length; ++i) {
{ let error = Math.abs(this.scale_points[i].value - value);
let error = Math.abs(this.scale_points[i].value-value); if (error < minError) {
if (error < minError)
{
minError = error; minError = error;
bestValue = this.scale_points[i].value; bestValue = this.scale_points[i].value;
} }
@@ -609,20 +577,20 @@ export class UiControl implements Deserializable<UiControl> {
} }
} }
isHidden() : boolean { isHidden(): boolean {
return this.not_on_gui || (this.connection_optional && !this.is_input); return this.not_on_gui || (this.connection_optional && !this.is_input);
} }
isOnOffSwitch() : boolean { isOnOffSwitch(): boolean {
return this.controlType === ControlType.OnOffSwitch; return this.controlType === ControlType.OnOffSwitch;
} }
isAbToggle(): boolean { isAbToggle(): boolean {
return this.controlType === ControlType.ABSwitch; return this.controlType === ControlType.ABSwitch;
} }
isSelect() : boolean { isSelect(): boolean {
return this.controlType === ControlType.Select; return this.controlType === ControlType.Select;
} }
isOutputSelect() : boolean { isOutputSelect(): boolean {
return !this.is_input && this.controlType === ControlType.OutputSelect; return !this.is_input && this.controlType === ControlType.OutputSelect;
} }
@@ -630,23 +598,23 @@ export class UiControl implements Deserializable<UiControl> {
isLamp(): boolean { isLamp(): boolean {
return this.toggled_property && this.scale_points.length === 0 && !this.is_input; return this.toggled_property && this.scale_points.length === 0 && !this.is_input;
} }
isDial() : boolean { isDial(): boolean {
return this.controlType === ControlType.Dial; return this.controlType === ControlType.Dial;
} }
isTuner() : boolean { isTuner(): boolean {
return this.controlType === ControlType.Tuner; return this.controlType === ControlType.Tuner;
} }
isVu() : boolean { isVu(): boolean {
return this.controlType === ControlType.Vu; return this.controlType === ControlType.Vu;
} }
isDbVu() : boolean { isDbVu(): boolean {
return this.controlType === ControlType.DbVu; return this.controlType === ControlType.DbVu;
} }
valueToRange(value: number): number { valueToRange(value: number): number {
if (this.toggled_property) return value === 0 ? 0: 1; if (this.toggled_property) return value === 0 ? 0 : 1;
if (this.integer_property || this.enumeration_property) { if (this.integer_property || this.enumeration_property) {
value = Math.round(value); value = Math.round(value);
@@ -659,11 +627,11 @@ export class UiControl implements Deserializable<UiControl> {
return range; return range;
} }
rangeToValue(range: number) : number { rangeToValue(range: number): number {
if (range < 0) range = 0; if (range < 0) range = 0;
if (range > 1) range = 1; if (range > 1) range = 1;
if (this.toggled_property) return range === 0? 0: 1; if (this.toggled_property) return range === 0 ? 0 : 1;
let value = range * (this.max_value - this.min_value) + this.min_value; let value = range * (this.max_value - this.min_value) + this.min_value;
if (this.integer_property || this.enumeration_property) { if (this.integer_property || this.enumeration_property) {
@@ -680,11 +648,9 @@ export class UiControl implements Deserializable<UiControl> {
value = Math.round(value); value = Math.round(value);
} }
for (let i = 0; i < this.scale_points.length; ++i) for (let i = 0; i < this.scale_points.length; ++i) {
{
let scalePoint = this.scale_points[i]; let scalePoint = this.scale_points[i];
if (scalePoint.value === value) if (scalePoint.value === value) {
{
return scalePoint.label; return scalePoint.label;
} }
} }
@@ -738,8 +704,7 @@ export class UiControl implements Deserializable<UiControl> {
} }
return text; return text;
} }
formatShortValue(value: number): string formatShortValue(value: number): string {
{
if (this.enumeration_property) { if (this.enumeration_property) {
for (let i = 0; i < this.scale_points.length; ++i) { for (let i = 0; i < this.scale_points.length; ++i) {
let scale_point = this.scale_points[i]; let scale_point = this.scale_points[i];
@@ -766,12 +731,11 @@ export class UiControl implements Deserializable<UiControl> {
export class UiPlugin implements Deserializable<UiPlugin> { export class UiPlugin implements Deserializable<UiPlugin> {
deserialize(input: any): UiPlugin deserialize(input: any): UiPlugin {
{
this.uri = input.uri; this.uri = input.uri;
this.name = input.name; this.name = input.name;
this.brand = input.brand ? input.brand: ""; this.brand = input.brand ? input.brand : "";
this.label = input.label? input.label: this.name; this.label = input.label ? input.label : this.name;
this.plugin_type = input.plugin_type as PluginType; this.plugin_type = input.plugin_type as PluginType;
this.plugin_display_type = input.plugin_display_type; this.plugin_display_type = input.plugin_display_type;
this.author_name = input.author_name; this.author_name = input.author_name;
@@ -783,14 +747,12 @@ export class UiPlugin implements Deserializable<UiPlugin> {
this.description = input.description; this.description = input.description;
this.controls = UiControl.deserialize_array(input.controls); this.controls = UiControl.deserialize_array(input.controls);
this.port_groups = PortGroup.deserialize_array(input.port_groups); this.port_groups = PortGroup.deserialize_array(input.port_groups);
if (input.fileProperties) if (input.fileProperties) {
{
this.fileProperties = UiFileProperty.deserialize_array(input.fileProperties) this.fileProperties = UiFileProperty.deserialize_array(input.fileProperties)
} else { } else {
this.fileProperties = []; this.fileProperties = [];
} }
if (input.frequencyPlots) if (input.frequencyPlots) {
{
this.frequencyPlots = UiFrequencyPlot.deserialize_array(input.frequencyPlots) this.frequencyPlots = UiFrequencyPlot.deserialize_array(input.frequencyPlots)
} else { } else {
this.frequencyPlots = []; this.frequencyPlots = [];
@@ -802,8 +764,7 @@ export class UiPlugin implements Deserializable<UiPlugin> {
} }
static deserialize_array(input: any): UiPlugin[] { static deserialize_array(input: any): UiPlugin[] {
let result: UiPlugin[] = []; let result: UiPlugin[] = [];
for (let i = 0; i < input.length; ++i) for (let i = 0; i < input.length; ++i) {
{
result[i] = new UiPlugin().deserialize(input[i]); result[i] = new UiPlugin().deserialize(input[i]);
} }
return result; return result;
@@ -814,190 +775,181 @@ export class UiPlugin implements Deserializable<UiPlugin> {
} }
getControl(key: string): UiControl | undefined { getControl(key: string): UiControl | undefined {
for (let i = 0; i < this.controls.length; ++i) for (let i = 0; i < this.controls.length; ++i) {
{
let control = this.controls[i]; let control = this.controls[i];
if (control.symbol === key) if (control.symbol === key) {
{
return control; return control;
} }
} }
return undefined; return undefined;
} }
getPortGroupByUri(uri: string) :PortGroup | null getPortGroupByUri(uri: string): PortGroup | null {
{ for (let i = 0; i < this.port_groups.length; ++i) {
for (let i = 0; i < this.port_groups.length; ++i)
{
let port_group = this.port_groups[i]; let port_group = this.port_groups[i];
if (port_group.uri === uri) if (port_group.uri === uri) {
{
return port_group; return port_group;
} }
} }
return null; return null;
} }
getPortGroupBySymbol(symbol: string): PortGroup | null getPortGroupBySymbol(symbol: string): PortGroup | null {
{ for (let i = 0; i < this.port_groups.length; ++i) {
for (let i = 0; i < this.port_groups.length; ++i)
{
let port_group = this.port_groups[i]; let port_group = this.port_groups[i];
if (port_group.symbol === symbol) if (port_group.symbol === symbol) {
{
return port_group; return port_group;
} }
} }
return null; return null;
} }
uri: string = ""; uri: string = "";
name: string = ""; name: string = "";
brand: string = ""; brand: string = "";
label: string = ""; label: string = "";
plugin_type: PluginType = PluginType.InvalidPlugin; plugin_type: PluginType = PluginType.InvalidPlugin;
plugin_display_type: string = ""; plugin_display_type: string = "";
author_name: string = ""; author_name: string = "";
author_homepage: string = ""; author_homepage: string = "";
audio_inputs: number = 0; audio_inputs: number = 0;
audio_outputs: number = 0; audio_outputs: number = 0;
has_midi_input: number = 0; has_midi_input: number = 0;
has_midi_output: number = 0; has_midi_output: number = 0;
description: string = ""; description: string = "";
controls: UiControl[] = []; controls: UiControl[] = [];
port_groups: PortGroup[] = []; port_groups: PortGroup[] = [];
fileProperties: UiFileProperty[] = []; fileProperties: UiFileProperty[] = [];
frequencyPlots: UiFrequencyPlot[] = []; frequencyPlots: UiFrequencyPlot[] = [];
is_vst3 : boolean = false; is_vst3: boolean = false;
} }
export function makeSplitUiPlugin(): UiPlugin export function makeSplitUiPlugin(): UiPlugin {
{
return new UiPlugin().deserialize({ return new UiPlugin().deserialize({
uri: "uri://two-play/pipedal/pedalboard#Split", uri: "uri://two-play/pipedal/pedalboard#Split",
name: "Split", name: "Split",
brand: "", brand: "",
label: "", label: "",
plugin_type: PluginType.SplitA, plugin_type: PluginType.SplitA,
plugin_display_type: "Split", plugin_display_type: "Split",
author_name: "", author_name: "",
author_homepage: "", author_homepage: "",
audio_inputs: 1, audio_inputs: 1,
audio_outputs: 1, audio_outputs: 1,
has_midi_input: 0, has_midi_input: 0,
has_midi_output: 0, has_midi_output: 0,
description: "", description: "",
controls: [ controls: [
new UiControl().applyProperties({ new UiControl().applyProperties({
symbol: "splitType", symbol: "splitType",
name: "Type", name: "Type",
index: 0, index: 0,
is_input: true, is_input: true,
min_value: 0.0, min_value: 0.0,
max_value: 2.0, max_value: 2.0,
enumeration_property: true, enumeration_property: true,
scale_points: [ scale_points: [
new ScalePoint().deserialize({value: 0, label: "A/B"}), new ScalePoint().deserialize({ value: 0, label: "A/B" }),
new ScalePoint().deserialize({value: 1, label: "mix"}), new ScalePoint().deserialize({ value: 1, label: "mix" }),
new ScalePoint().deserialize({value: 1, label: "L/R"}), new ScalePoint().deserialize({ value: 1, label: "L/R" }),
], ],
is_bypass: false, is_bypass: false,
is_program_controller: false, is_program_controller: false,
custom_units: "", custom_units: "",
connection_optional: false, connection_optional: false,
}) , }),
new UiControl().applyProperties({ new UiControl().applyProperties({
symbol: "select", symbol: "select",
name: "Select", name: "Select",
index: 1, index: 1,
is_input: true, is_input: true,
min_value: 0.0, min_value: 0.0,
max_value: 1.0, max_value: 1.0,
enumeration_property: true, enumeration_property: true,
scale_points: [ scale_points: [
new ScalePoint().deserialize({value: 0, label: "A"}), new ScalePoint().deserialize({ value: 0, label: "A" }),
new ScalePoint().deserialize({value: 1, label: "B"}), new ScalePoint().deserialize({ value: 1, label: "B" }),
], ],
is_bypass: false, is_bypass: false,
is_program_controller: false, is_program_controller: false,
custom_units: "", custom_units: "",
connection_optional: false, connection_optional: false,
}) , }),
new UiControl().applyProperties({ new UiControl().applyProperties({
symbol: "mix", symbol: "mix",
name: "Mix", name: "Mix",
index: 2, index: 2,
is_input: true, is_input: true,
min_value: -1.0, min_value: -1.0,
max_value: 1.0, max_value: 1.0,
is_bypass: false, is_bypass: false,
is_program_controller: false, is_program_controller: false,
custom_units: "", custom_units: "",
connection_optional: false, connection_optional: false,
}) , }),
new UiControl().applyProperties({ new UiControl().applyProperties({
symbol: "panL", symbol: "panL",
name: "Pan Top", name: "Pan Top",
index: 3, index: 3,
is_input: true, is_input: true,
min_value: -1.0, min_value: -1.0,
max_value: 1.0, max_value: 1.0,
is_bypass: false, is_bypass: false,
is_program_controller: false, is_program_controller: false,
custom_units: "", custom_units: "",
connection_optional: false, connection_optional: false,
}) , }),
new UiControl().applyProperties({ new UiControl().applyProperties({
symbol: "volL", symbol: "volL",
name: "Vol Top", name: "Vol Top",
index: 4, index: 4,
is_input: true, is_input: true,
min_value: -60.0, min_value: -60.0,
max_value: 12.0, max_value: 12.0,
is_bypass: false, is_bypass: false,
is_program_controller: false, is_program_controller: false,
custom_units: "", custom_units: "",
connection_optional: false, connection_optional: false,
}) , }),
new UiControl().applyProperties({ new UiControl().applyProperties({
symbol: "panR", symbol: "panR",
name: "Pan Bottom", name: "Pan Bottom",
index: 5, index: 5,
is_input: true, is_input: true,
min_value: -1.0, min_value: -1.0,
max_value: 1.0, max_value: 1.0,
is_bypass: false, is_bypass: false,
is_program_controller: false, is_program_controller: false,
custom_units: "", custom_units: "",
connection_optional: false, connection_optional: false,
}) , }),
new UiControl().applyProperties({ new UiControl().applyProperties({
symbol: "volR", symbol: "volR",
name: "Vol Bottom", name: "Vol Bottom",
index: 6, index: 6,
is_input: true, is_input: true,
min_value: -60.0, min_value: -60.0,
max_value: 12.0, max_value: 12.0,
is_bypass: false, is_bypass: false,
is_program_controller: false, is_program_controller: false,
custom_units: "", custom_units: "",
connection_optional: false, connection_optional: false,
}) })
], ],
port_groups: [], port_groups: [],
fileProperties: [], fileProperties: [],
frequencyPlots: [], frequencyPlots: [],
is_vst3 : false, is_vst3: false,
} }
); );
+4
View File
@@ -315,6 +315,8 @@ void pipedal::SetWifiConfig(const WifiConfigSettings &settings)
{ {
apdConfig.Load(path); apdConfig.Load(path);
} }
if (!UsingNetworkManager())
{}
apdConfig.Set("interface", "wlan0"); apdConfig.Set("interface", "wlan0");
apdConfig.Set("driver", "nl80211"); apdConfig.Set("driver", "nl80211");
apdConfig.Set("country_code", settings.countryCode_); apdConfig.Set("country_code", settings.countryCode_);
@@ -553,6 +555,8 @@ void UninstallP2p()
wifiDirectConfigSettings.Load(); wifiDirectConfigSettings.Load();
wifiDirectConfigSettings.enable_ = false; wifiDirectConfigSettings.enable_ = false;
wifiDirectConfigSettings.Save(); wifiDirectConfigSettings.Save();
sysExec(SYSTEMCTL_BIN " restart NetworkManager"); // bring up wlan0 wifi.
} }
} }
-2
View File
@@ -1,5 +1,3 @@
- change icons on gallery in index.html
- provide direct link to docs on both landing pages.
- feature: redo ./makepackage so that it runs without sudo. - feature: redo ./makepackage so that it runs without sudo.
- Bug: text Cursor hovering over the breadcrumb bar in file property dialog. - Bug: text Cursor hovering over the breadcrumb bar in file property dialog.
- Bug: Turning off wifi-direct sould re-enable NetworkManager. - Bug: Turning off wifi-direct sould re-enable NetworkManager.