diff --git a/Aaru.Devices/Linux/ListDevices.cs b/Aaru.Devices/Linux/ListDevices.cs
index d6835212f..1c1124888 100644
--- a/Aaru.Devices/Linux/ListDevices.cs
+++ b/Aaru.Devices/Linux/ListDevices.cs
@@ -39,13 +39,13 @@ using Sentry;
namespace Aaru.Devices.Linux;
[SupportedOSPlatform("linux")]
-static class ListDevices
+public static class ListDevices
{
const string PATH_SYS_DEVBLOCK = "/sys/block/";
/// Gets a list of all known storage devices on Linux
/// List of devices
- internal static DeviceInfo[] GetList()
+ public static DeviceInfo[] GetList()
{
string[] sysdevs = Directory.GetFileSystemEntries(PATH_SYS_DEVBLOCK, "*", SearchOption.TopDirectoryOnly);
@@ -66,7 +66,7 @@ static class ListDevices
hasUdev = false;
}
- for(int i = 0; i < sysdevs.Length; i++)
+ for(var i = 0; i < sysdevs.Length; i++)
{
devices[i] = new DeviceInfo
{
diff --git a/Aaru.Devices/Windows/ListDevices.cs b/Aaru.Devices/Windows/ListDevices.cs
index 390b65310..e0043e43c 100644
--- a/Aaru.Devices/Windows/ListDevices.cs
+++ b/Aaru.Devices/Windows/ListDevices.cs
@@ -44,7 +44,7 @@ using Marshal = System.Runtime.InteropServices.Marshal;
namespace Aaru.Devices.Windows;
[SupportedOSPlatform("windows")]
-static class ListDevices
+public static class ListDevices
{
/// Converts a hex dump string to the ASCII string it represents
/// Hex dump
@@ -63,7 +63,7 @@ static class ListDevices
/// Gets a list of all known storage devices on Windows
/// List of devices
[SuppressMessage("ReSharper", "RedundantCatchClause")]
- internal static DeviceInfo[] GetList()
+ public static DeviceInfo[] GetList()
{
var deviceIDs = new List();
diff --git a/Aaru.Gui/Aaru.Gui.csproj b/Aaru.Gui/Aaru.Gui.csproj
index 0b80491e5..594360b37 100644
--- a/Aaru.Gui/Aaru.Gui.csproj
+++ b/Aaru.Gui/Aaru.Gui.csproj
@@ -315,7 +315,6 @@
-
%(Filename)
diff --git a/Aaru.Gui/Models/DeviceModel.cs b/Aaru.Gui/Models/DeviceModel.cs
new file mode 100644
index 000000000..2becd4e94
--- /dev/null
+++ b/Aaru.Gui/Models/DeviceModel.cs
@@ -0,0 +1,52 @@
+// /***************************************************************************
+// Aaru Data Preservation Suite
+// ----------------------------------------------------------------------------
+//
+// Filename : DeviceModel.cs
+// Author(s) : Natalia Portillo
+//
+// Component : GUI data models.
+//
+// --[ Description ] ----------------------------------------------------------
+//
+// Contains information about a device.
+//
+// --[ License ] --------------------------------------------------------------
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General public License for more details.
+//
+// You should have received a copy of the GNU General public License
+// along with this program. If not, see .
+//
+// ----------------------------------------------------------------------------
+// Copyright © 2011-2025 Natalia Portillo
+// ****************************************************************************/
+
+namespace Aaru.Gui.Models;
+
+public class DeviceModel
+{
+ /// Device path
+ public string Path { get; set; }
+ /// Device vendor or manufacturer
+ public string Vendor { get; set; }
+ /// Device model or product name
+ public string Model { get; set; }
+ /// Device serial number
+ public string Serial { get; set; }
+ /// Bus the device is attached to
+ public string Bus { get; set; }
+ ///
+ /// Set to true if Aaru can send commands to the device in the current machine or remote, false
+ /// otherwise
+ ///
+ public bool Supported { get; set; }
+}
\ No newline at end of file
diff --git a/Aaru.Gui/ViewModels/Windows/DeviceListViewModel.cs b/Aaru.Gui/ViewModels/Windows/DeviceListViewModel.cs
new file mode 100644
index 000000000..9486dd951
--- /dev/null
+++ b/Aaru.Gui/ViewModels/Windows/DeviceListViewModel.cs
@@ -0,0 +1,216 @@
+// /***************************************************************************
+// Aaru Data Preservation Suite
+// ----------------------------------------------------------------------------
+//
+// Filename : DeviceListViewModel.cs
+// Author(s) : Natalia Portillo
+//
+// Component : GUI view models.
+//
+// --[ Description ] ----------------------------------------------------------
+//
+// View model for device list.
+//
+// --[ License ] --------------------------------------------------------------
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General public License for more details.
+//
+// You should have received a copy of the GNU General public License
+// along with this program. If not, see .
+//
+// ----------------------------------------------------------------------------
+// Copyright © 2011-2025 Natalia Portillo
+// ****************************************************************************/
+
+using System;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Net.Sockets;
+using Aaru.Devices;
+using Aaru.Devices.Remote;
+using Aaru.Devices.Windows;
+using Aaru.Gui.Models;
+using Aaru.Gui.Views.Windows;
+using Aaru.Localization;
+using Aaru.Logging;
+using CommunityToolkit.Mvvm.ComponentModel;
+using JetBrains.Annotations;
+using MsBox.Avalonia;
+using MsBox.Avalonia.Base;
+using MsBox.Avalonia.Enums;
+using Sentry;
+using Spectre.Console;
+
+namespace Aaru.Gui.ViewModels.Windows;
+
+public partial class DeviceListViewModel : ViewModelBase
+{
+ readonly DeviceList _window;
+ [ObservableProperty]
+ ObservableCollection _devices;
+ [ObservableProperty]
+ string _remotePath;
+
+ public DeviceListViewModel(DeviceList window) => _window = window;
+
+ public DeviceListViewModel(DeviceList window, [NotNull] string remotePath)
+ {
+ _window = window;
+ RemotePath = remotePath;
+ }
+
+ public void LoadData()
+ {
+#pragma warning disable MVVMTK0034
+ if(_remotePath != null)
+#pragma warning restore MVVMTK0034
+ {
+ LoadRemote();
+
+ return;
+ }
+
+ DeviceInfo[] devices = null;
+
+ if(OperatingSystem.IsWindows()) devices = ListDevices.GetList();
+
+ if(OperatingSystem.IsLinux()) devices = ListDevices.GetList();
+
+ if((OperatingSystem.IsWindows() || OperatingSystem.IsLinux()) && devices != null)
+ {
+ Devices = [];
+
+ foreach(DeviceInfo device in devices)
+ {
+ Devices.Add(new DeviceModel
+ {
+ Bus = device.Bus,
+ Model = device.Model,
+ Path = device.Path,
+ Serial = device.Serial,
+ Supported = device.Supported,
+ Vendor = device.Vendor
+ });
+ }
+
+ return;
+ }
+
+ AaruLogging.Error(UI.Devices_are_not_supported_on_this_platform);
+
+ IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error,
+ UI
+ .Devices_are_not_supported_on_this_platform,
+ ButtonEnum.Ok,
+ Icon.Error);
+
+ _ = msbox.ShowWindowDialogAsync(_window);
+ }
+
+ public void LoadRemote()
+ {
+ try
+ {
+ var aaruUri = new Uri(RemotePath);
+
+ if(aaruUri.Scheme != "aaru" && aaruUri.Scheme != "dic")
+ {
+ AaruLogging.Error(UI.Invalid_remote_protocol);
+
+ IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error,
+ UI.Invalid_remote_protocol,
+ ButtonEnum.Ok,
+ Icon.Error);
+
+ _ = msbox.ShowWindowDialogAsync(_window);
+ }
+
+ using var remote = new Remote(aaruUri);
+
+ DeviceInfo[] devices = remote.ListDevices();
+
+ Devices = [];
+
+ foreach(DeviceInfo device in devices)
+ {
+ Devices.Add(new DeviceModel
+ {
+ Bus = device.Bus,
+ Model = device.Model,
+ Path = device.Path,
+ Serial = device.Serial,
+ Supported = device.Supported,
+ Vendor = device.Vendor
+ });
+ }
+ }
+ catch(SocketException ex)
+ {
+ if(ex.SocketErrorCode == SocketError.HostNotFound)
+ {
+ IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error,
+ UI.Host_not_found,
+ ButtonEnum.Ok,
+ Icon.Error);
+
+ _ = msbox.ShowWindowDialogAsync(_window);
+ }
+ else
+ {
+ SentrySdk.CaptureException(ex);
+
+ AaruLogging.Exception(ex, UI.Error_connecting_to_host);
+ AaruLogging.Error(UI.Error_connecting_to_host);
+
+ IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error,
+ Markup.Remove(UI.Error_connecting_to_host),
+ ButtonEnum.Ok,
+ Icon.Error);
+
+ _ = msbox.ShowWindowDialogAsync(_window);
+ }
+ }
+
+ // ReSharper disable once UncatchableException
+ catch(ArgumentException)
+ {
+ IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error,
+ UI.Server_sent_invalid_data,
+ ButtonEnum.Ok,
+ Icon.Error);
+
+ _ = msbox.ShowWindowDialogAsync(_window);
+ }
+ catch(IOException)
+ {
+ IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error,
+ UI.Unknown_network_error,
+ ButtonEnum.Ok,
+ Icon.Error);
+
+ _ = msbox.ShowWindowDialogAsync(_window);
+ }
+ catch(Exception ex)
+ {
+ SentrySdk.CaptureException(ex);
+
+ AaruLogging.Exception(ex, UI.Error_connecting_to_host);
+ AaruLogging.Error(UI.Error_connecting_to_host);
+
+ IMsBox msbox = MessageBoxManager.GetMessageBoxStandard(UI.Title_Error,
+ Markup.Remove(UI.Error_connecting_to_host),
+ ButtonEnum.Ok,
+ Icon.Error);
+
+ _ = msbox.ShowWindowDialogAsync(_window);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Gui/ViewModels/Windows/MainWindowViewModel.cs b/Aaru.Gui/ViewModels/Windows/MainWindowViewModel.cs
index d0d4ce84c..2c384c3ac 100644
--- a/Aaru.Gui/ViewModels/Windows/MainWindowViewModel.cs
+++ b/Aaru.Gui/ViewModels/Windows/MainWindowViewModel.cs
@@ -63,7 +63,9 @@ using CommunityToolkit.Mvvm.Input;
using JetBrains.Annotations;
using MsBox.Avalonia;
using MsBox.Avalonia.Base;
+using MsBox.Avalonia.Dto;
using MsBox.Avalonia.Enums;
+using MsBox.Avalonia.Models;
using Spectre.Console;
using Console = Aaru.Gui.Views.Dialogs.Console;
using FileSystem = Aaru.CommonTypes.AaruMetadata.FileSystem;
@@ -115,6 +117,8 @@ public partial class MainWindowViewModel : ViewModelBase
DecodeImageMediaTagsCommand = new RelayCommand(DecodeImageMediaTags);
OpenMhddLogCommand = new AsyncRelayCommand(OpenMhddLogAsync);
OpenIbgLogCommand = new AsyncRelayCommand(OpenIbgLogAsync);
+ ConnectToRemoteCommand = new AsyncRelayCommand(ConnectToRemoteAsync);
+ OpenDeviceCommand = new RelayCommand(OpenDevice);
_genericHddIcon =
new Bitmap(AssetLoader.Open(new Uri("avares://Aaru.Gui/Assets/Icons/oxygen/32x32/drive-harddisk.png")));
@@ -133,7 +137,6 @@ public partial class MainWindowViewModel : ViewModelBase
{
case PlatformID.Win32NT:
case PlatformID.Linux:
- case PlatformID.FreeBSD:
DevicesSupported = true;
break;
@@ -168,6 +171,8 @@ public partial class MainWindowViewModel : ViewModelBase
public ICommand DecodeImageMediaTagsCommand { get; }
public ICommand OpenMhddLogCommand { get; }
public ICommand OpenIbgLogCommand { get; }
+ public ICommand ConnectToRemoteCommand { get; }
+ public ICommand OpenDeviceCommand { get; }
public bool NativeMenuSupported
{
@@ -225,6 +230,56 @@ public partial class MainWindowViewModel : ViewModelBase
}
}
+ void OpenDevice()
+ {
+ var deviceListWindow = new DeviceList();
+
+ deviceListWindow.DataContext = new DeviceListViewModel(deviceListWindow);
+
+ deviceListWindow.Show();
+ }
+
+ async Task ConnectToRemoteAsync()
+ {
+ IMsBox msbox = MessageBoxManager.GetMessageBoxCustom(new MessageBoxCustomParams
+ {
+ ContentTitle = UI.Connect_to_AaruRemote,
+ ButtonDefinitions =
+ [
+ new ButtonDefinition
+ {
+ Name = UI.ButtonLabel_Connect,
+ IsDefault = true
+ },
+ new ButtonDefinition
+ {
+ Name = UI.ButtonLabel_Cancel,
+ IsCancel = true
+ }
+ ],
+ CanResize = false,
+ CloseOnClickAway = false,
+ InputParams = new InputParams
+ {
+ Label = UI.Address_IP_or_hostname,
+ DefaultValue = "",
+ Multiline = false
+ },
+ ContentMessage = UI.Introduce_AaruRemote_server_address,
+ MinWidth = 400
+ });
+
+ string result = await msbox.ShowWindowDialogAsync(_view);
+
+ if(result != UI.ButtonLabel_Connect) return;
+
+ var deviceListWindow = new DeviceList();
+
+ deviceListWindow.DataContext = new DeviceListViewModel(deviceListWindow, msbox.InputValue);
+
+ deviceListWindow.Show();
+ }
+
async Task OpenMhddLogAsync()
{
IReadOnlyList result = await _view.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
@@ -619,7 +674,7 @@ public partial class MainWindowViewModel : ViewModelBase
await dialog.ShowDialog(_view);
}
- Task SettingsAsync()
+ internal Task SettingsAsync()
{
var dialog = new SettingsDialog();
dialog.DataContext = new SettingsViewModel(dialog, false);
@@ -627,7 +682,8 @@ public partial class MainWindowViewModel : ViewModelBase
return dialog.ShowDialog(_view);
}
- void Exit() => (Application.Current?.ApplicationLifetime as ClassicDesktopStyleApplicationLifetime)?.Shutdown();
+ internal void Exit() =>
+ (Application.Current?.ApplicationLifetime as ClassicDesktopStyleApplicationLifetime)?.Shutdown();
void Console()
{
diff --git a/Aaru.Gui/Views/Windows/DeviceList.axaml b/Aaru.Gui/Views/Windows/DeviceList.axaml
new file mode 100644
index 000000000..1efae94b2
--- /dev/null
+++ b/Aaru.Gui/Views/Windows/DeviceList.axaml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Aaru.Gui/Views/Windows/DeviceList.axaml.cs b/Aaru.Gui/Views/Windows/DeviceList.axaml.cs
new file mode 100644
index 000000000..908024160
--- /dev/null
+++ b/Aaru.Gui/Views/Windows/DeviceList.axaml.cs
@@ -0,0 +1,57 @@
+// /***************************************************************************
+// Aaru Data Preservation Suite
+// ----------------------------------------------------------------------------
+//
+// Filename : DeviceList.axaml.cs
+// Author(s) : Natalia Portillo
+//
+// Component : GUI views.
+//
+// --[ Description ] ----------------------------------------------------------
+//
+// View for device list.
+//
+// --[ License ] --------------------------------------------------------------
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General public License for more details.
+//
+// You should have received a copy of the GNU General public License
+// along with this program. If not, see .
+//
+// ----------------------------------------------------------------------------
+// Copyright © 2011-2025 Natalia Portillo
+// ****************************************************************************/
+
+using System;
+using Aaru.Gui.ViewModels.Windows;
+using Avalonia;
+using Avalonia.Controls;
+
+namespace Aaru.Gui.Views.Windows;
+
+public partial class DeviceList : Window
+{
+ public DeviceList()
+ {
+ InitializeComponent();
+#if DEBUG
+ this.AttachDevTools();
+#endif
+ }
+
+ ///
+ protected override void OnDataContextChanged(EventArgs e)
+ {
+ base.OnDataContextChanged(e);
+
+ if(DataContext is DeviceListViewModel vm) vm?.LoadData();
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Gui/Views/Windows/MainWindow.axaml b/Aaru.Gui/Views/Windows/MainWindow.axaml
index 37ecd4d49..7a93c0e35 100644
--- a/Aaru.Gui/Views/Windows/MainWindow.axaml
+++ b/Aaru.Gui/Views/Windows/MainWindow.axaml
@@ -23,6 +23,11 @@
Command="{Binding OpenMhddLogCommand, Mode=OneWay}" />
+
+
-
_Consola
-
-
- _Dispositivos
_Codificaciones
@@ -3221,4 +3218,49 @@ Probadores:
Abrir registro _IMGBurn
+
+ Conectar a AaruRemote
+
+
+ Conectar
+
+
+ Dirección (IP o servidor):
+
+
+ Introduzca la dirección del servidor AaruRemote.
+
+
+ Conectar a Aaru_Remote
+
+
+ Abrir _dispositivo...
+
+
+ Lista de dispositivos
+
+
+ [bold][slateblue1]Servidor AaruRemote[/][/]
+
+
+ [bold][slateblue1]Ruta[/][/]
+
+
+ [bold][slateblue1]Soportado[/][/]
+
+
+ Los dispositivos no están soportados en esta plataforma.
+
+
+ Protocolo remoto inválido.
+
+
+ Servidor no encontrado.
+
+
+ El servidor envió datos inválidos.
+
+
+ Error de red desconocido.
+
\ No newline at end of file
diff --git a/Aaru.Localization/UI.resx b/Aaru.Localization/UI.resx
index dc9afc90d..e0a0f53b5 100644
--- a/Aaru.Localization/UI.resx
+++ b/Aaru.Localization/UI.resx
@@ -2828,10 +2828,6 @@ Do you want to continue?
E_xit
'_' is the character that will be used for the keyboard accelerator
-
-
- _Devices
- '_' is the character that will be used for the keyboard accelerator
_Refresh
@@ -3297,4 +3293,49 @@ Do you want to continue?
Open _IMGBurn log
+
+ Connect to AaruRemote
+
+
+ Connect
+
+
+ Address (IP or hostname):
+
+
+ Introduce AaruRemote server address.
+
+
+ Connect to AaruRemote
+
+
+ Open _device...
+
+
+ Device list
+
+
+ [bold][slateblue1]AaruRemote server[/][/]
+
+
+ [bold][slateblue1]Path[/][/]
+
+
+ [bold][slateblue1]Supported[/][/]
+
+
+ Devices are not supported on this platform.
+
+
+ Invalid remote protocol.
+
+
+ Host not found.
+
+
+ Server sent invalid data.
+
+
+ Unknown network error.
+
\ No newline at end of file