[GUI] Add network interface dropdown (#4597)
* Add network adapter dropdown from LDN build * Ava: Add NetworkInterfaces to SettingsNetworkTab * Add headless network interface option * Add network interface dropdown to Avalonia * Fix handling network interfaces without a gateway address * gtk: Actually save selected network interface to config * Increment config version
This commit is contained in:
parent
40e87c634e
commit
69b6ef7a4a
15 changed files with 385 additions and 90 deletions
|
@ -177,6 +177,8 @@ namespace Ryujinx.Ava
|
||||||
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
|
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
|
||||||
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
|
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
|
||||||
|
|
||||||
|
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
|
||||||
|
|
||||||
_gpuCancellationTokenSource = new CancellationTokenSource();
|
_gpuCancellationTokenSource = new CancellationTokenSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,6 +385,11 @@ namespace Ryujinx.Ava
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs<string> e)
|
||||||
|
{
|
||||||
|
Device.Configuration.MultiplayerLanInterfaceId = e.NewValue;
|
||||||
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
_isActive = false;
|
_isActive = false;
|
||||||
|
@ -739,7 +746,8 @@ namespace Ryujinx.Ava
|
||||||
ConfigurationState.Instance.System.IgnoreMissingServices,
|
ConfigurationState.Instance.System.IgnoreMissingServices,
|
||||||
ConfigurationState.Instance.Graphics.AspectRatio,
|
ConfigurationState.Instance.Graphics.AspectRatio,
|
||||||
ConfigurationState.Instance.System.AudioVolume,
|
ConfigurationState.Instance.System.AudioVolume,
|
||||||
ConfigurationState.Instance.System.UseHypervisor);
|
ConfigurationState.Instance.System.UseHypervisor,
|
||||||
|
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
|
||||||
|
|
||||||
Device = new Switch(configuration);
|
Device = new Switch(configuration);
|
||||||
}
|
}
|
||||||
|
|
|
@ -638,5 +638,8 @@
|
||||||
"SmaaHigh": "SMAA High",
|
"SmaaHigh": "SMAA High",
|
||||||
"SmaaUltra": "SMAA Ultra",
|
"SmaaUltra": "SMAA Ultra",
|
||||||
"UserEditorTitle" : "Edit User",
|
"UserEditorTitle" : "Edit User",
|
||||||
"UserEditorTitleCreate" : "Create User"
|
"UserEditorTitleCreate" : "Create User",
|
||||||
|
"SettingsTabNetworkInterface": "Network Interface:",
|
||||||
|
"NetworkInterfaceTooltip": "The network interface used for LAN features",
|
||||||
|
"NetworkInterfaceDefault": "Default"
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
@ -35,6 +36,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
private readonly List<string> _validTzRegions;
|
private readonly List<string> _validTzRegions;
|
||||||
|
|
||||||
|
private readonly Dictionary<string, string> _networkInterfaces;
|
||||||
|
|
||||||
private float _customResolutionScale;
|
private float _customResolutionScale;
|
||||||
private int _resolutionScale;
|
private int _resolutionScale;
|
||||||
private int _graphicsBackendMultithreadingIndex;
|
private int _graphicsBackendMultithreadingIndex;
|
||||||
|
@ -50,6 +53,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
public event Action CloseWindow;
|
public event Action CloseWindow;
|
||||||
public event Action SaveSettingsEvent;
|
public event Action SaveSettingsEvent;
|
||||||
|
private int _networkInterfaceIndex;
|
||||||
|
|
||||||
public int ResolutionScale
|
public int ResolutionScale
|
||||||
{
|
{
|
||||||
|
@ -240,6 +244,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
public AvaloniaList<string> GameDirectories { get; set; }
|
public AvaloniaList<string> GameDirectories { get; set; }
|
||||||
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
|
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
|
||||||
|
|
||||||
|
public AvaloniaList<string> NetworkInterfaceList
|
||||||
|
{
|
||||||
|
get => new AvaloniaList<string>(_networkInterfaces.Keys);
|
||||||
|
}
|
||||||
|
|
||||||
public KeyboardHotkeys KeyboardHotkeys
|
public KeyboardHotkeys KeyboardHotkeys
|
||||||
{
|
{
|
||||||
get => _keyboardHotkeys;
|
get => _keyboardHotkeys;
|
||||||
|
@ -251,6 +260,16 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int NetworkInterfaceIndex
|
||||||
|
{
|
||||||
|
get => _networkInterfaceIndex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_networkInterfaceIndex = value != -1 ? value : 0;
|
||||||
|
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[_networkInterfaceIndex]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
|
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
@ -267,8 +286,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
TimeZones = new AvaloniaList<TimeZone>();
|
TimeZones = new AvaloniaList<TimeZone>();
|
||||||
AvailableGpus = new ObservableCollection<ComboBoxItem>();
|
AvailableGpus = new ObservableCollection<ComboBoxItem>();
|
||||||
_validTzRegions = new List<string>();
|
_validTzRegions = new List<string>();
|
||||||
|
_networkInterfaces = new Dictionary<string, string>();
|
||||||
|
|
||||||
CheckSoundBackends();
|
CheckSoundBackends();
|
||||||
|
PopulateNetworkInterfaces();
|
||||||
|
|
||||||
if (Program.PreviewerDetached)
|
if (Program.PreviewerDetached)
|
||||||
{
|
{
|
||||||
|
@ -327,6 +348,17 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PopulateNetworkInterfaces()
|
||||||
|
{
|
||||||
|
_networkInterfaces.Clear();
|
||||||
|
_networkInterfaces.Add(LocaleManager.Instance[LocaleKeys.NetworkInterfaceDefault], "0");
|
||||||
|
|
||||||
|
foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces())
|
||||||
|
{
|
||||||
|
_networkInterfaces.Add(networkInterface.Name, networkInterface.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void ValidateAndSetTimeZone(string location)
|
public void ValidateAndSetTimeZone(string location)
|
||||||
{
|
{
|
||||||
if (_validTzRegions.Contains(location))
|
if (_validTzRegions.Contains(location))
|
||||||
|
@ -414,6 +446,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
||||||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||||
|
|
||||||
|
NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(config.Multiplayer.LanInterfaceId.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveSettings()
|
public void SaveSettings()
|
||||||
|
@ -515,6 +549,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||||
|
|
||||||
|
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
|
||||||
|
|
||||||
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||||
|
|
||||||
MainWindow.UpdateGraphicsConfig();
|
MainWindow.UpdateGraphicsConfig();
|
||||||
|
|
|
@ -29,6 +29,17 @@
|
||||||
<TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}"
|
<TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}"
|
||||||
ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
|
ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
|
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
Text="{locale:Locale SettingsTabNetworkInterface}"
|
||||||
|
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
|
||||||
|
Width="200" />
|
||||||
|
<ComboBox SelectedIndex="{Binding NetworkInterfaceIndex}"
|
||||||
|
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
Items="{Binding NetworkInterfaceList}"
|
||||||
|
Width="250" />
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
66
Ryujinx.Common/Utilities/NetworkHelpers.cs
Normal file
66
Ryujinx.Common/Utilities/NetworkHelpers.cs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Utilities
|
||||||
|
{
|
||||||
|
public static class NetworkHelpers
|
||||||
|
{
|
||||||
|
private static (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(NetworkInterface adapter, bool isPreferred)
|
||||||
|
{
|
||||||
|
IPInterfaceProperties properties = adapter.GetIPProperties();
|
||||||
|
|
||||||
|
if (isPreferred || (properties.GatewayAddresses.Count > 0 && properties.DnsAddresses.Count > 0))
|
||||||
|
{
|
||||||
|
foreach (UnicastIPAddressInformation info in properties.UnicastAddresses)
|
||||||
|
{
|
||||||
|
// Only accept an IPv4 address
|
||||||
|
if (info.Address.GetAddressBytes().Length == 4)
|
||||||
|
{
|
||||||
|
return (properties, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(string lanInterfaceId = "0")
|
||||||
|
{
|
||||||
|
if (!NetworkInterface.GetIsNetworkAvailable())
|
||||||
|
{
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPInterfaceProperties targetProperties = null;
|
||||||
|
UnicastIPAddressInformation targetAddressInfo = null;
|
||||||
|
|
||||||
|
NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
|
||||||
|
|
||||||
|
string guid = lanInterfaceId;
|
||||||
|
bool hasPreference = guid != "0";
|
||||||
|
|
||||||
|
foreach (NetworkInterface adapter in interfaces)
|
||||||
|
{
|
||||||
|
bool isPreferred = adapter.Id == guid;
|
||||||
|
|
||||||
|
// Ignore loopback and non IPv4 capable interface.
|
||||||
|
if (isPreferred || (targetProperties == null && adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && adapter.Supports(NetworkInterfaceComponent.IPv4)))
|
||||||
|
{
|
||||||
|
(IPInterfaceProperties properties, UnicastIPAddressInformation info) = GetLocalInterface(adapter, isPreferred);
|
||||||
|
|
||||||
|
if (properties != null)
|
||||||
|
{
|
||||||
|
targetProperties = properties;
|
||||||
|
targetAddressInfo = info;
|
||||||
|
|
||||||
|
if (isPreferred || !hasPreference)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (targetProperties, targetAddressInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -153,6 +153,11 @@ namespace Ryujinx.HLE
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal readonly bool UseHypervisor;
|
internal readonly bool UseHypervisor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Multiplayer LAN Interface ID (device GUID)
|
||||||
|
/// </summary>
|
||||||
|
public string MultiplayerLanInterfaceId { internal get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An action called when HLE force a refresh of output after docked mode changed.
|
/// An action called when HLE force a refresh of output after docked mode changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -181,7 +186,8 @@ namespace Ryujinx.HLE
|
||||||
bool ignoreMissingServices,
|
bool ignoreMissingServices,
|
||||||
AspectRatio aspectRatio,
|
AspectRatio aspectRatio,
|
||||||
float audioVolume,
|
float audioVolume,
|
||||||
bool useHypervisor)
|
bool useHypervisor,
|
||||||
|
string multiplayerLanInterfaceId)
|
||||||
{
|
{
|
||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
LibHacHorizonManager = libHacHorizonManager;
|
LibHacHorizonManager = libHacHorizonManager;
|
||||||
|
@ -207,6 +213,7 @@ namespace Ryujinx.HLE
|
||||||
AspectRatio = aspectRatio;
|
AspectRatio = aspectRatio;
|
||||||
AudioVolume = audioVolume;
|
AudioVolume = audioVolume;
|
||||||
UseHypervisor = useHypervisor;
|
UseHypervisor = useHypervisor;
|
||||||
|
MultiplayerLanInterfaceId = multiplayerLanInterfaceId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,7 +6,6 @@ using Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types;
|
||||||
using System;
|
using System;
|
||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
||||||
{
|
{
|
||||||
|
@ -16,6 +15,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
||||||
|
|
||||||
private IPInterfaceProperties _targetPropertiesCache = null;
|
private IPInterfaceProperties _targetPropertiesCache = null;
|
||||||
private UnicastIPAddressInformation _targetAddressInfoCache = null;
|
private UnicastIPAddressInformation _targetAddressInfoCache = null;
|
||||||
|
private string _cacheChosenInterface = null;
|
||||||
|
|
||||||
public IGeneralService()
|
public IGeneralService()
|
||||||
{
|
{
|
||||||
|
@ -65,7 +65,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
||||||
{
|
{
|
||||||
ulong networkProfileDataPosition = context.Request.RecvListBuff[0].Position;
|
ulong networkProfileDataPosition = context.Request.RecvListBuff[0].Position;
|
||||||
|
|
||||||
(IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface();
|
(IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context);
|
||||||
|
|
||||||
if (interfaceProperties == null || unicastAddress == null)
|
if (interfaceProperties == null || unicastAddress == null)
|
||||||
{
|
{
|
||||||
|
@ -95,7 +95,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
||||||
// GetCurrentIpAddress() -> nn::nifm::IpV4Address
|
// GetCurrentIpAddress() -> nn::nifm::IpV4Address
|
||||||
public ResultCode GetCurrentIpAddress(ServiceCtx context)
|
public ResultCode GetCurrentIpAddress(ServiceCtx context)
|
||||||
{
|
{
|
||||||
(_, UnicastIPAddressInformation unicastAddress) = GetLocalInterface();
|
(_, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context);
|
||||||
|
|
||||||
if (unicastAddress == null)
|
if (unicastAddress == null)
|
||||||
{
|
{
|
||||||
|
@ -113,7 +113,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
||||||
// GetCurrentIpConfigInfo() -> (nn::nifm::IpAddressSetting, nn::nifm::DnsSetting)
|
// GetCurrentIpConfigInfo() -> (nn::nifm::IpAddressSetting, nn::nifm::DnsSetting)
|
||||||
public ResultCode GetCurrentIpConfigInfo(ServiceCtx context)
|
public ResultCode GetCurrentIpConfigInfo(ServiceCtx context)
|
||||||
{
|
{
|
||||||
(IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface();
|
(IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context);
|
||||||
|
|
||||||
if (interfaceProperties == null || unicastAddress == null)
|
if (interfaceProperties == null || unicastAddress == null)
|
||||||
{
|
{
|
||||||
|
@ -163,53 +163,25 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface()
|
private (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(ServiceCtx context)
|
||||||
{
|
{
|
||||||
if (!NetworkInterface.GetIsNetworkAvailable())
|
if (!NetworkInterface.GetIsNetworkAvailable())
|
||||||
{
|
{
|
||||||
return (null, null);
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_targetPropertiesCache != null && _targetAddressInfoCache != null)
|
string chosenInterface = context.Device.Configuration.MultiplayerLanInterfaceId;
|
||||||
|
|
||||||
|
if (_targetPropertiesCache == null || _targetAddressInfoCache == null || _cacheChosenInterface != chosenInterface)
|
||||||
{
|
{
|
||||||
|
_cacheChosenInterface = chosenInterface;
|
||||||
|
|
||||||
|
(_targetPropertiesCache, _targetAddressInfoCache) = NetworkHelpers.GetLocalInterface(chosenInterface);
|
||||||
|
}
|
||||||
|
|
||||||
return (_targetPropertiesCache, _targetAddressInfoCache);
|
return (_targetPropertiesCache, _targetAddressInfoCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
IPInterfaceProperties targetProperties = null;
|
|
||||||
UnicastIPAddressInformation targetAddressInfo = null;
|
|
||||||
|
|
||||||
NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
|
|
||||||
|
|
||||||
foreach (NetworkInterface adapter in interfaces)
|
|
||||||
{
|
|
||||||
// Ignore loopback and non IPv4 capable interface.
|
|
||||||
if (targetProperties == null && adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && adapter.Supports(NetworkInterfaceComponent.IPv4))
|
|
||||||
{
|
|
||||||
IPInterfaceProperties properties = adapter.GetIPProperties();
|
|
||||||
|
|
||||||
if (properties.GatewayAddresses.Count > 0 && properties.DnsAddresses.Count > 0)
|
|
||||||
{
|
|
||||||
foreach (UnicastIPAddressInformation info in properties.UnicastAddresses)
|
|
||||||
{
|
|
||||||
// Only accept an IPv4 address
|
|
||||||
if (info.Address.GetAddressBytes().Length == 4)
|
|
||||||
{
|
|
||||||
targetProperties = properties;
|
|
||||||
targetAddressInfo = info;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_targetPropertiesCache = targetProperties;
|
|
||||||
_targetAddressInfoCache = targetAddressInfo;
|
|
||||||
|
|
||||||
return (targetProperties, targetAddressInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LocalInterfaceCacheHandler(object sender, EventArgs e)
|
private void LocalInterfaceCacheHandler(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.ServiceNifm, $"NetworkAddress changed, invalidating cached data.");
|
Logger.Info?.Print(LogClass.ServiceNifm, $"NetworkAddress changed, invalidating cached data.");
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types
|
||||||
IsDhcpEnabled = OperatingSystem.IsMacOS() || interfaceProperties.DhcpServerAddresses.Count != 0;
|
IsDhcpEnabled = OperatingSystem.IsMacOS() || interfaceProperties.DhcpServerAddresses.Count != 0;
|
||||||
Address = new IpV4Address(unicastIPAddressInformation.Address);
|
Address = new IpV4Address(unicastIPAddressInformation.Address);
|
||||||
IPv4Mask = new IpV4Address(unicastIPAddressInformation.IPv4Mask);
|
IPv4Mask = new IpV4Address(unicastIPAddressInformation.IPv4Mask);
|
||||||
GatewayAddress = new IpV4Address(interfaceProperties.GatewayAddresses[0].Address);
|
GatewayAddress = (interfaceProperties.GatewayAddresses.Count == 0) ? new IpV4Address() : new IpV4Address(interfaceProperties.GatewayAddresses[0].Address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -132,6 +132,9 @@ namespace Ryujinx.Headless.SDL2
|
||||||
[Option("use-hypervisor", Required = false, Default = true, HelpText = "Uses Hypervisor over JIT if available.")]
|
[Option("use-hypervisor", Required = false, Default = true, HelpText = "Uses Hypervisor over JIT if available.")]
|
||||||
public bool UseHypervisor { get; set; }
|
public bool UseHypervisor { get; set; }
|
||||||
|
|
||||||
|
[Option("lan-interface-id", Required = false, Default = "0", HelpText = "GUID for the network interface used by LAN.")]
|
||||||
|
public string MultiplayerLanInterfaceId { get; set; }
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
|
|
||||||
[Option("disable-file-logging", Required = false, Default = false, HelpText = "Disables logging to a file on disk.")]
|
[Option("disable-file-logging", Required = false, Default = false, HelpText = "Disables logging to a file on disk.")]
|
||||||
|
|
|
@ -548,7 +548,8 @@ namespace Ryujinx.Headless.SDL2
|
||||||
options.IgnoreMissingServices,
|
options.IgnoreMissingServices,
|
||||||
options.AspectRatio,
|
options.AspectRatio,
|
||||||
options.AudioVolume,
|
options.AudioVolume,
|
||||||
options.UseHypervisor);
|
options.UseHypervisor,
|
||||||
|
options.MultiplayerLanInterfaceId);
|
||||||
|
|
||||||
return new Switch(configuration);
|
return new Switch(configuration);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current version of the file format
|
/// The current version of the file format
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int CurrentVersion = 45;
|
public const int CurrentVersion = 46;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version of the configuration file format
|
/// Version of the configuration file format
|
||||||
|
@ -350,6 +350,11 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string PreferredGpu { get; set; }
|
public string PreferredGpu { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GUID for the network interface used by LAN (or 0 for default)
|
||||||
|
/// </summary>
|
||||||
|
public string MultiplayerLanInterfaceId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Uses Hypervisor over JIT if available
|
/// Uses Hypervisor over JIT if available
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -517,6 +517,22 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Multiplayer configuration section
|
||||||
|
/// </summary>
|
||||||
|
public class MultiplayerSection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// GUID for the network interface used by LAN (or 0 for default)
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<string> LanInterfaceId { get; private set; }
|
||||||
|
|
||||||
|
public MultiplayerSection()
|
||||||
|
{
|
||||||
|
LanInterfaceId = new ReactiveObject<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default configuration instance
|
/// The default configuration instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -547,6 +563,11 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public HidSection Hid { get; private set; }
|
public HidSection Hid { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Multiplayer section
|
||||||
|
/// </summary>
|
||||||
|
public MultiplayerSection Multiplayer { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables or disables Discord Rich Presence
|
/// Enables or disables Discord Rich Presence
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -574,6 +595,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||||
System = new SystemSection();
|
System = new SystemSection();
|
||||||
Graphics = new GraphicsSection();
|
Graphics = new GraphicsSection();
|
||||||
Hid = new HidSection();
|
Hid = new HidSection();
|
||||||
|
Multiplayer = new MultiplayerSection();
|
||||||
EnableDiscordIntegration = new ReactiveObject<bool>();
|
EnableDiscordIntegration = new ReactiveObject<bool>();
|
||||||
CheckUpdatesOnStart = new ReactiveObject<bool>();
|
CheckUpdatesOnStart = new ReactiveObject<bool>();
|
||||||
ShowConfirmExit = new ReactiveObject<bool>();
|
ShowConfirmExit = new ReactiveObject<bool>();
|
||||||
|
@ -674,7 +696,8 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||||
ControllerConfig = new List<JsonObject>(),
|
ControllerConfig = new List<JsonObject>(),
|
||||||
InputConfig = Hid.InputConfig,
|
InputConfig = Hid.InputConfig,
|
||||||
GraphicsBackend = Graphics.GraphicsBackend,
|
GraphicsBackend = Graphics.GraphicsBackend,
|
||||||
PreferredGpu = Graphics.PreferredGpu
|
PreferredGpu = Graphics.PreferredGpu,
|
||||||
|
MultiplayerLanInterfaceId = Multiplayer.LanInterfaceId
|
||||||
};
|
};
|
||||||
|
|
||||||
return configurationFile;
|
return configurationFile;
|
||||||
|
@ -727,6 +750,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||||
System.ExpandRam.Value = false;
|
System.ExpandRam.Value = false;
|
||||||
System.IgnoreMissingServices.Value = false;
|
System.IgnoreMissingServices.Value = false;
|
||||||
System.UseHypervisor.Value = true;
|
System.UseHypervisor.Value = true;
|
||||||
|
Multiplayer.LanInterfaceId.Value = "0";
|
||||||
Ui.GuiColumns.FavColumn.Value = true;
|
Ui.GuiColumns.FavColumn.Value = true;
|
||||||
Ui.GuiColumns.IconColumn.Value = true;
|
Ui.GuiColumns.IconColumn.Value = true;
|
||||||
Ui.GuiColumns.AppColumn.Value = true;
|
Ui.GuiColumns.AppColumn.Value = true;
|
||||||
|
@ -1308,6 +1332,15 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||||
configurationFileUpdated = true;
|
configurationFileUpdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (configurationFileFormat.Version < 46)
|
||||||
|
{
|
||||||
|
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 45.");
|
||||||
|
|
||||||
|
configurationFileFormat.MultiplayerLanInterfaceId = "0";
|
||||||
|
|
||||||
|
configurationFileUpdated = true;
|
||||||
|
}
|
||||||
|
|
||||||
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
|
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
|
||||||
Graphics.ResScale.Value = configurationFileFormat.ResScale;
|
Graphics.ResScale.Value = configurationFileFormat.ResScale;
|
||||||
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
|
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
|
||||||
|
@ -1393,6 +1426,8 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||||
Hid.InputConfig.Value = new List<InputConfig>();
|
Hid.InputConfig.Value = new List<InputConfig>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Multiplayer.LanInterfaceId.Value = configurationFileFormat.MultiplayerLanInterfaceId;
|
||||||
|
|
||||||
if (configurationFileUpdated)
|
if (configurationFileUpdated)
|
||||||
{
|
{
|
||||||
ToFileFormat().SaveConfig(configurationFilePath);
|
ToFileFormat().SaveConfig(configurationFilePath);
|
||||||
|
|
|
@ -598,7 +598,8 @@ namespace Ryujinx.Ui
|
||||||
ConfigurationState.Instance.System.IgnoreMissingServices,
|
ConfigurationState.Instance.System.IgnoreMissingServices,
|
||||||
ConfigurationState.Instance.Graphics.AspectRatio,
|
ConfigurationState.Instance.Graphics.AspectRatio,
|
||||||
ConfigurationState.Instance.System.AudioVolume,
|
ConfigurationState.Instance.System.AudioVolume,
|
||||||
ConfigurationState.Instance.System.UseHypervisor);
|
ConfigurationState.Instance.System.UseHypervisor,
|
||||||
|
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
|
||||||
|
|
||||||
_emulationContext = new HLE.Switch(configuration);
|
_emulationContext = new HLE.Switch(configuration);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GUI = Gtk.Builder.ObjectAttribute;
|
using GUI = Gtk.Builder.ObjectAttribute;
|
||||||
|
@ -84,6 +85,7 @@ namespace Ryujinx.Ui.Windows
|
||||||
[GUI] Adjustment _systemTimeDaySpinAdjustment;
|
[GUI] Adjustment _systemTimeDaySpinAdjustment;
|
||||||
[GUI] Adjustment _systemTimeHourSpinAdjustment;
|
[GUI] Adjustment _systemTimeHourSpinAdjustment;
|
||||||
[GUI] Adjustment _systemTimeMinuteSpinAdjustment;
|
[GUI] Adjustment _systemTimeMinuteSpinAdjustment;
|
||||||
|
[GUI] ComboBoxText _multiLanSelect;
|
||||||
[GUI] CheckButton _custThemeToggle;
|
[GUI] CheckButton _custThemeToggle;
|
||||||
[GUI] Entry _custThemePath;
|
[GUI] Entry _custThemePath;
|
||||||
[GUI] ToggleButton _browseThemePath;
|
[GUI] ToggleButton _browseThemePath;
|
||||||
|
@ -348,6 +350,8 @@ namespace Ryujinx.Ui.Windows
|
||||||
UpdatePreferredGpuComboBox();
|
UpdatePreferredGpuComboBox();
|
||||||
|
|
||||||
_graphicsBackend.Changed += (sender, e) => UpdatePreferredGpuComboBox();
|
_graphicsBackend.Changed += (sender, e) => UpdatePreferredGpuComboBox();
|
||||||
|
PopulateNetworkInterfaces();
|
||||||
|
_multiLanSelect.SetActiveId(ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
|
||||||
|
|
||||||
_custThemePath.Buffer.Text = ConfigurationState.Instance.Ui.CustomThemePath;
|
_custThemePath.Buffer.Text = ConfigurationState.Instance.Ui.CustomThemePath;
|
||||||
_resScaleText.Buffer.Text = ConfigurationState.Instance.Graphics.ResScaleCustom.Value.ToString();
|
_resScaleText.Buffer.Text = ConfigurationState.Instance.Graphics.ResScaleCustom.Value.ToString();
|
||||||
|
@ -490,6 +494,19 @@ namespace Ryujinx.Ui.Windows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PopulateNetworkInterfaces()
|
||||||
|
{
|
||||||
|
NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
|
||||||
|
|
||||||
|
foreach (NetworkInterface nif in interfaces)
|
||||||
|
{
|
||||||
|
string guid = nif.Id;
|
||||||
|
string name = nif.Name;
|
||||||
|
|
||||||
|
_multiLanSelect.Append(guid, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateSystemTimeSpinners()
|
private void UpdateSystemTimeSpinners()
|
||||||
{
|
{
|
||||||
//Bind system time events
|
//Bind system time events
|
||||||
|
@ -616,6 +633,7 @@ namespace Ryujinx.Ui.Windows
|
||||||
ConfigurationState.Instance.Graphics.AntiAliasing.Value = Enum.Parse<AntiAliasing>(_antiAliasing.ActiveId);
|
ConfigurationState.Instance.Graphics.AntiAliasing.Value = Enum.Parse<AntiAliasing>(_antiAliasing.ActiveId);
|
||||||
ConfigurationState.Instance.Graphics.ScalingFilter.Value = Enum.Parse<ScalingFilter>(_scalingFilter.ActiveId);
|
ConfigurationState.Instance.Graphics.ScalingFilter.Value = Enum.Parse<ScalingFilter>(_scalingFilter.ActiveId);
|
||||||
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value = (int)_scalingFilterLevel.Value;
|
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value = (int)_scalingFilterLevel.Value;
|
||||||
|
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _multiLanSelect.ActiveId;
|
||||||
|
|
||||||
_previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value;
|
_previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- Generated with glade 3.38.2 -->
|
<!-- Generated with glade 3.40.0 -->
|
||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk+" version="3.20"/>
|
<requires lib="gtk+" version="3.20"/>
|
||||||
<object class="GtkAdjustment" id="_fsLogSpinAdjustment">
|
<object class="GtkAdjustment" id="_fsLogSpinAdjustment">
|
||||||
|
@ -7,6 +7,12 @@
|
||||||
<property name="step-increment">1</property>
|
<property name="step-increment">1</property>
|
||||||
<property name="page-increment">10</property>
|
<property name="page-increment">10</property>
|
||||||
</object>
|
</object>
|
||||||
|
<object class="GtkAdjustment" id="_scalingFilterLevel">
|
||||||
|
<property name="upper">101</property>
|
||||||
|
<property name="step-increment">1</property>
|
||||||
|
<property name="page-increment">5</property>
|
||||||
|
<property name="page-size">1</property>
|
||||||
|
</object>
|
||||||
<object class="GtkAdjustment" id="_systemTimeDaySpinAdjustment">
|
<object class="GtkAdjustment" id="_systemTimeDaySpinAdjustment">
|
||||||
<property name="lower">1</property>
|
<property name="lower">1</property>
|
||||||
<property name="upper">31</property>
|
<property name="upper">31</property>
|
||||||
|
@ -40,13 +46,6 @@
|
||||||
<property name="inline-completion">True</property>
|
<property name="inline-completion">True</property>
|
||||||
<property name="inline-selection">True</property>
|
<property name="inline-selection">True</property>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkAdjustment" id="_scalingFilterLevel">
|
|
||||||
<property name="lower">0</property>
|
|
||||||
<property name="upper">101</property>
|
|
||||||
<property name="step-increment">1</property>
|
|
||||||
<property name="page-increment">5</property>
|
|
||||||
<property name="page-size">1</property>
|
|
||||||
</object>
|
|
||||||
<object class="GtkWindow" id="_settingsWin">
|
<object class="GtkWindow" id="_settingsWin">
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="title" translatable="yes">Ryujinx - Settings</property>
|
<property name="title" translatable="yes">Ryujinx - Settings</property>
|
||||||
|
@ -2862,6 +2861,136 @@
|
||||||
<property name="tab-fill">False</property>
|
<property name="tab-fill">False</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="TabMultiplayer">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="margin-left">5</property>
|
||||||
|
<property name="margin-right">10</property>
|
||||||
|
<property name="margin-top">5</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="CatLAN">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="valign">start</property>
|
||||||
|
<property name="margin-left">5</property>
|
||||||
|
<property name="margin-right">5</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="margin-bottom">5</property>
|
||||||
|
<property name="label" translatable="yes">LAN Mode</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="weight" value="bold"/>
|
||||||
|
</attributes>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="LANOptions">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="valign">start</property>
|
||||||
|
<property name="margin-left">10</property>
|
||||||
|
<property name="margin-right">10</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="NetworkInterfaceBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="tooltip-text" translatable="yes">The network interface used for LAN features</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="label" translatable="yes">Network Interface:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="padding">5</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkComboBoxText" id="_multiLanSelect">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="tooltip-text" translatable="yes">The network interface used for LAN features</property>
|
||||||
|
<property name="active-id">0</property>
|
||||||
|
<items>
|
||||||
|
<item id="0" translatable="yes">Default</item>
|
||||||
|
</items>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="padding">5</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="margin-bottom">5</property>
|
||||||
|
<property name="label" translatable="yes">To use LAN functionality in games, Enable Guest Internet Access must be checked in System.</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="padding">5</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">5</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child type="tab">
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="label" translatable="yes">Multiplayer</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">5</property>
|
||||||
|
<property name="tab-fill">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
Reference in a new issue