mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2024-11-15 09:35:27 +00:00
account: add Custom User Profiles support (#2227)
* Initial Impl * Fix names * remove useless ContentManager * Support backgrounds and improve avatar loading * Fix firmware checks * Addresses gdkchan feedback
This commit is contained in:
parent
3e61fb0268
commit
c46f6879ff
|
@ -1,52 +1,204 @@
|
||||||
using Ryujinx.Common;
|
using LibHac;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using LibHac.Fs.Shim;
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.FileSystem.Content;
|
||||||
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||||
{
|
{
|
||||||
public class AccountManager
|
public class AccountManager
|
||||||
{
|
{
|
||||||
|
public static readonly UserId DefaultUserId = new UserId("00000000000000010000000000000000");
|
||||||
|
|
||||||
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
|
private readonly AccountSaveDataManager _accountSaveDataManager;
|
||||||
|
|
||||||
private ConcurrentDictionary<string, UserProfile> _profiles;
|
private ConcurrentDictionary<string, UserProfile> _profiles;
|
||||||
|
|
||||||
public UserProfile LastOpenedUser { get; private set; }
|
public UserProfile LastOpenedUser { get; private set; }
|
||||||
|
|
||||||
public AccountManager()
|
public AccountManager(VirtualFileSystem virtualFileSystem)
|
||||||
{
|
{
|
||||||
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
|
||||||
_profiles = new ConcurrentDictionary<string, UserProfile>();
|
_profiles = new ConcurrentDictionary<string, UserProfile>();
|
||||||
|
|
||||||
UserId defaultUserId = new UserId("00000000000000010000000000000000");
|
_accountSaveDataManager = new AccountSaveDataManager(_profiles);
|
||||||
|
|
||||||
|
if (!_profiles.TryGetValue(DefaultUserId.ToString(), out _))
|
||||||
|
{
|
||||||
byte[] defaultUserImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg");
|
byte[] defaultUserImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg");
|
||||||
|
|
||||||
AddUser(defaultUserId, "Player", defaultUserImage);
|
AddUser("RyuPlayer", defaultUserImage, DefaultUserId);
|
||||||
|
|
||||||
OpenUser(defaultUserId);
|
OpenUser(DefaultUserId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OpenUser(_accountSaveDataManager.LastOpened);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddUser(UserId userId, string name, byte[] image)
|
public void AddUser(string name, byte[] image, UserId userId = new UserId())
|
||||||
{
|
{
|
||||||
|
if (userId.IsNull)
|
||||||
|
{
|
||||||
|
userId = new UserId(Guid.NewGuid().ToString().Replace("-", ""));
|
||||||
|
}
|
||||||
|
|
||||||
UserProfile profile = new UserProfile(userId, name, image);
|
UserProfile profile = new UserProfile(userId, name, image);
|
||||||
|
|
||||||
_profiles.AddOrUpdate(userId.ToString(), profile, (key, old) => profile);
|
_profiles.AddOrUpdate(userId.ToString(), profile, (key, old) => profile);
|
||||||
|
|
||||||
|
_accountSaveDataManager.Save(_profiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenUser(UserId userId)
|
public void OpenUser(UserId userId)
|
||||||
{
|
{
|
||||||
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
|
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
|
||||||
{
|
{
|
||||||
(LastOpenedUser = profile).AccountState = AccountState.Open;
|
// TODO: Support multiple open users ?
|
||||||
|
foreach (UserProfile userProfile in GetAllUsers())
|
||||||
|
{
|
||||||
|
if (userProfile == LastOpenedUser)
|
||||||
|
{
|
||||||
|
userProfile.AccountState = AccountState.Closed;
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(LastOpenedUser = profile).AccountState = AccountState.Open;
|
||||||
|
|
||||||
|
_accountSaveDataManager.LastOpened = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
_accountSaveDataManager.Save(_profiles);
|
||||||
|
}
|
||||||
|
|
||||||
public void CloseUser(UserId userId)
|
public void CloseUser(UserId userId)
|
||||||
{
|
{
|
||||||
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
|
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
|
||||||
{
|
{
|
||||||
profile.AccountState = AccountState.Closed;
|
profile.AccountState = AccountState.Closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_accountSaveDataManager.Save(_profiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetUserCount()
|
public void OpenUserOnlinePlay(UserId userId)
|
||||||
|
{
|
||||||
|
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
|
||||||
|
{
|
||||||
|
// TODO: Support multiple open online users ?
|
||||||
|
foreach (UserProfile userProfile in GetAllUsers())
|
||||||
|
{
|
||||||
|
if (userProfile == LastOpenedUser)
|
||||||
|
{
|
||||||
|
userProfile.OnlinePlayState = AccountState.Closed;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.OnlinePlayState = AccountState.Open;
|
||||||
|
}
|
||||||
|
|
||||||
|
_accountSaveDataManager.Save(_profiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseUserOnlinePlay(UserId userId)
|
||||||
|
{
|
||||||
|
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
|
||||||
|
{
|
||||||
|
profile.OnlinePlayState = AccountState.Closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
_accountSaveDataManager.Save(_profiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetUserImage(UserId userId, byte[] image)
|
||||||
|
{
|
||||||
|
foreach (UserProfile userProfile in GetAllUsers())
|
||||||
|
{
|
||||||
|
if (userProfile.UserId == userId)
|
||||||
|
{
|
||||||
|
userProfile.Image = image;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_accountSaveDataManager.Save(_profiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetUserName(UserId userId, string name)
|
||||||
|
{
|
||||||
|
foreach (UserProfile userProfile in GetAllUsers())
|
||||||
|
{
|
||||||
|
if (userProfile.UserId == userId)
|
||||||
|
{
|
||||||
|
userProfile.Name = name;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_accountSaveDataManager.Save(_profiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteUser(UserId userId)
|
||||||
|
{
|
||||||
|
DeleteSaveData(userId);
|
||||||
|
|
||||||
|
_profiles.Remove(userId.ToString(), out _);
|
||||||
|
|
||||||
|
OpenUser(DefaultUserId);
|
||||||
|
|
||||||
|
_accountSaveDataManager.Save(_profiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteSaveData(UserId userId)
|
||||||
|
{
|
||||||
|
SaveDataFilter saveDataFilter = new SaveDataFilter();
|
||||||
|
saveDataFilter.SetUserId(new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low));
|
||||||
|
|
||||||
|
Result result = _virtualFileSystem.FsClient.OpenSaveDataIterator(out SaveDataIterator saveDataIterator, SaveDataSpaceId.User, ref saveDataFilter);
|
||||||
|
if (result.IsSuccess())
|
||||||
|
{
|
||||||
|
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
saveDataIterator.ReadSaveDataInfo(out long readCount, saveDataInfo);
|
||||||
|
|
||||||
|
if (readCount == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < readCount; i++)
|
||||||
|
{
|
||||||
|
// TODO: We use Directory.Delete workaround because DeleteSaveData softlock without, due to a bug in LibHac 0.12.0.
|
||||||
|
string savePath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataInfo[i].SaveDataId:x16}");
|
||||||
|
string saveMetaPath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/saveMeta/{saveDataInfo[i].SaveDataId:x16}");
|
||||||
|
|
||||||
|
Directory.Delete(savePath, true);
|
||||||
|
Directory.Delete(saveMetaPath, true);
|
||||||
|
|
||||||
|
_virtualFileSystem.FsClient.DeleteSaveData(SaveDataSpaceId.User, saveDataInfo[i].SaveDataId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int GetUserCount()
|
||||||
{
|
{
|
||||||
return _profiles.Count;
|
return _profiles.Count;
|
||||||
}
|
}
|
||||||
|
@ -56,7 +208,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||||
return _profiles.TryGetValue(userId.ToString(), out profile);
|
return _profiles.TryGetValue(userId.ToString(), out profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal IEnumerable<UserProfile> GetAllUsers()
|
public IEnumerable<UserProfile> GetAllUsers()
|
||||||
{
|
{
|
||||||
return _profiles.Values;
|
return _profiles.Values;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||||
|
{
|
||||||
|
class AccountSaveDataManager
|
||||||
|
{
|
||||||
|
private readonly string _profilesJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "Profiles.json");
|
||||||
|
|
||||||
|
private struct ProfilesJson
|
||||||
|
{
|
||||||
|
[JsonPropertyName("profiles")]
|
||||||
|
public List<UserProfileJson> Profiles { get; set; }
|
||||||
|
[JsonPropertyName("last_opened")]
|
||||||
|
public string LastOpened { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct UserProfileJson
|
||||||
|
{
|
||||||
|
[JsonPropertyName("user_id")]
|
||||||
|
public string UserId { get; set; }
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
[JsonPropertyName("account_state")]
|
||||||
|
public AccountState AccountState { get; set; }
|
||||||
|
[JsonPropertyName("online_play_state")]
|
||||||
|
public AccountState OnlinePlayState { get; set; }
|
||||||
|
[JsonPropertyName("last_modified_timestamp")]
|
||||||
|
public long LastModifiedTimestamp { get; set; }
|
||||||
|
[JsonPropertyName("image")]
|
||||||
|
public byte[] Image { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserId LastOpened { get; set; }
|
||||||
|
|
||||||
|
public AccountSaveDataManager(ConcurrentDictionary<string, UserProfile> profiles)
|
||||||
|
{
|
||||||
|
// TODO: Use 0x8000000000000010 system savedata instead of a JSON file if needed.
|
||||||
|
|
||||||
|
if (File.Exists(_profilesJsonPath))
|
||||||
|
{
|
||||||
|
ProfilesJson profilesJson = JsonHelper.DeserializeFromFile<ProfilesJson>(_profilesJsonPath);
|
||||||
|
|
||||||
|
foreach (var profile in profilesJson.Profiles)
|
||||||
|
{
|
||||||
|
UserProfile addedProfile = new UserProfile(new UserId(profile.UserId), profile.Name, profile.Image, profile.LastModifiedTimestamp);
|
||||||
|
|
||||||
|
profiles.AddOrUpdate(profile.UserId, addedProfile, (key, old) => addedProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
LastOpened = new UserId(profilesJson.LastOpened);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LastOpened = AccountManager.DefaultUserId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(ConcurrentDictionary<string, UserProfile> profiles)
|
||||||
|
{
|
||||||
|
ProfilesJson profilesJson = new ProfilesJson()
|
||||||
|
{
|
||||||
|
Profiles = new List<UserProfileJson>(),
|
||||||
|
LastOpened = LastOpened.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var profile in profiles)
|
||||||
|
{
|
||||||
|
profilesJson.Profiles.Add(new UserProfileJson()
|
||||||
|
{
|
||||||
|
UserId = profile.Value.UserId.ToString(),
|
||||||
|
Name = profile.Value.Name,
|
||||||
|
AccountState = profile.Value.AccountState,
|
||||||
|
OnlinePlayState = profile.Value.OnlinePlayState,
|
||||||
|
LastModifiedTimestamp = profile.Value.LastModifiedTimestamp,
|
||||||
|
Image = profile.Value.Image,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(_profilesJsonPath, JsonHelper.Serialize(profilesJson, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,31 +8,80 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||||
|
|
||||||
public UserId UserId { get; }
|
public UserId UserId { get; }
|
||||||
|
|
||||||
public string Name { get; }
|
public long LastModifiedTimestamp { get; set; }
|
||||||
|
|
||||||
public byte[] Image { get; }
|
private string _name;
|
||||||
|
|
||||||
public long LastModifiedTimestamp { get; private set; }
|
public string Name
|
||||||
|
{
|
||||||
|
get => _name;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_name = value;
|
||||||
|
|
||||||
public AccountState AccountState { get; set; }
|
UpdateLastModifiedTimestamp();
|
||||||
public AccountState OnlinePlayState { get; set; }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public UserProfile(UserId userId, string name, byte[] image)
|
private byte[] _image;
|
||||||
|
|
||||||
|
public byte[] Image
|
||||||
|
{
|
||||||
|
get => _image;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_image = value;
|
||||||
|
|
||||||
|
UpdateLastModifiedTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AccountState _accountState;
|
||||||
|
|
||||||
|
public AccountState AccountState
|
||||||
|
{
|
||||||
|
get => _accountState;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_accountState = value;
|
||||||
|
|
||||||
|
UpdateLastModifiedTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountState _onlinePlayState;
|
||||||
|
|
||||||
|
public AccountState OnlinePlayState
|
||||||
|
{
|
||||||
|
get => _onlinePlayState;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_onlinePlayState = value;
|
||||||
|
|
||||||
|
UpdateLastModifiedTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserProfile(UserId userId, string name, byte[] image, long lastModifiedTimestamp = 0)
|
||||||
{
|
{
|
||||||
UserId = userId;
|
UserId = userId;
|
||||||
Name = name;
|
Name = name;
|
||||||
|
|
||||||
Image = image;
|
Image = image;
|
||||||
|
|
||||||
LastModifiedTimestamp = 0;
|
|
||||||
|
|
||||||
AccountState = AccountState.Closed;
|
AccountState = AccountState.Closed;
|
||||||
OnlinePlayState = AccountState.Closed;
|
OnlinePlayState = AccountState.Closed;
|
||||||
|
|
||||||
UpdateTimestamp();
|
if (lastModifiedTimestamp != 0)
|
||||||
|
{
|
||||||
|
LastModifiedTimestamp = lastModifiedTimestamp;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdateLastModifiedTimestamp();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateTimestamp()
|
private void UpdateLastModifiedTimestamp()
|
||||||
{
|
{
|
||||||
LastModifiedTimestamp = (long)(DateTime.Now - Epoch).TotalSeconds;
|
LastModifiedTimestamp = (long)(DateTime.Now - Epoch).TotalSeconds;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.HLE.HOS.Services.Caps.Types;
|
using Ryujinx.HLE.HOS.Services.Caps.Types;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -19,11 +18,6 @@ namespace Ryujinx.HLE.HOS.Services.Caps
|
||||||
public CaptureManager(Switch device)
|
public CaptureManager(Switch device)
|
||||||
{
|
{
|
||||||
_sdCardPath = device.FileSystem.GetSdCardPath();
|
_sdCardPath = device.FileSystem.GetSdCardPath();
|
||||||
|
|
||||||
SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()
|
|
||||||
{
|
|
||||||
Quality = 100
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultCode SetShimLibraryVersion(ServiceCtx context)
|
public ResultCode SetShimLibraryVersion(ServiceCtx context)
|
||||||
|
|
|
@ -150,12 +150,9 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
||||||
return ResultCode.InvalidArgument;
|
return ResultCode.InvalidArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.Device.System.AccountManager.TryGetUser(userId, out UserProfile profile))
|
context.Device.System.AccountManager.OpenUserOnlinePlay(userId);
|
||||||
{
|
|
||||||
profile.OnlinePlayState = AccountState.Open;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString(), profile.OnlinePlayState });
|
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -171,12 +168,9 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
||||||
return ResultCode.InvalidArgument;
|
return ResultCode.InvalidArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.Device.System.AccountManager.TryGetUser(userId, out UserProfile profile))
|
context.Device.System.AccountManager.CloseUserOnlinePlay(userId);
|
||||||
{
|
|
||||||
profile.OnlinePlayState = AccountState.Closed;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString(), profile.OnlinePlayState });
|
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ using Ryujinx.Configuration;
|
||||||
using Ryujinx.Modules;
|
using Ryujinx.Modules;
|
||||||
using Ryujinx.Ui;
|
using Ryujinx.Ui;
|
||||||
using Ryujinx.Ui.Widgets;
|
using Ryujinx.Ui.Widgets;
|
||||||
|
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
@ -97,6 +98,12 @@ namespace Ryujinx
|
||||||
// Initialize Discord integration.
|
// Initialize Discord integration.
|
||||||
DiscordIntegrationModule.Initialize();
|
DiscordIntegrationModule.Initialize();
|
||||||
|
|
||||||
|
// Sets ImageSharp Jpeg Encoder Quality.
|
||||||
|
SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()
|
||||||
|
{
|
||||||
|
Quality = 100
|
||||||
|
});
|
||||||
|
|
||||||
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
|
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
|
||||||
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json");
|
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json");
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,7 @@ namespace Ryujinx.Ui
|
||||||
[GUI] Box _footerBox;
|
[GUI] Box _footerBox;
|
||||||
[GUI] Box _statusBar;
|
[GUI] Box _statusBar;
|
||||||
[GUI] MenuItem _optionMenu;
|
[GUI] MenuItem _optionMenu;
|
||||||
|
[GUI] MenuItem _manageUserProfiles;
|
||||||
[GUI] MenuItem _actionMenu;
|
[GUI] MenuItem _actionMenu;
|
||||||
[GUI] MenuItem _stopEmulation;
|
[GUI] MenuItem _stopEmulation;
|
||||||
[GUI] MenuItem _simulateWakeUpMessage;
|
[GUI] MenuItem _simulateWakeUpMessage;
|
||||||
|
@ -140,7 +141,7 @@ namespace Ryujinx.Ui
|
||||||
// Instanciate HLE objects.
|
// Instanciate HLE objects.
|
||||||
_virtualFileSystem = VirtualFileSystem.CreateInstance();
|
_virtualFileSystem = VirtualFileSystem.CreateInstance();
|
||||||
_contentManager = new ContentManager(_virtualFileSystem);
|
_contentManager = new ContentManager(_virtualFileSystem);
|
||||||
_accountManager = new AccountManager();
|
_accountManager = new AccountManager(_virtualFileSystem);
|
||||||
_userChannelPersistence = new UserChannelPersistence();
|
_userChannelPersistence = new UserChannelPersistence();
|
||||||
|
|
||||||
// Instanciate GUI objects.
|
// Instanciate GUI objects.
|
||||||
|
@ -155,6 +156,7 @@ namespace Ryujinx.Ui
|
||||||
_applicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
|
_applicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
|
||||||
|
|
||||||
_actionMenu.StateChanged += ActionMenu_StateChanged;
|
_actionMenu.StateChanged += ActionMenu_StateChanged;
|
||||||
|
_optionMenu.StateChanged += OptionMenu_StateChanged;
|
||||||
|
|
||||||
_gameTable.ButtonReleaseEvent += Row_Clicked;
|
_gameTable.ButtonReleaseEvent += Row_Clicked;
|
||||||
_fullScreen.Activated += FullScreen_Toggled;
|
_fullScreen.Activated += FullScreen_Toggled;
|
||||||
|
@ -1192,6 +1194,11 @@ namespace Ryujinx.Ui
|
||||||
SaveConfig();
|
SaveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OptionMenu_StateChanged(object o, StateChangedArgs args)
|
||||||
|
{
|
||||||
|
_manageUserProfiles.Sensitive = _emulationContext == null;
|
||||||
|
}
|
||||||
|
|
||||||
private void Settings_Pressed(object sender, EventArgs args)
|
private void Settings_Pressed(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
SettingsWindow settingsWindow = new SettingsWindow(this, _virtualFileSystem, _contentManager);
|
SettingsWindow settingsWindow = new SettingsWindow(this, _virtualFileSystem, _contentManager);
|
||||||
|
@ -1200,6 +1207,14 @@ namespace Ryujinx.Ui
|
||||||
settingsWindow.Show();
|
settingsWindow.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ManageUserProfiles_Pressed(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
UserProfilesManagerWindow userProfilesManagerWindow = new UserProfilesManagerWindow(_accountManager, _contentManager, _virtualFileSystem);
|
||||||
|
|
||||||
|
userProfilesManagerWindow.SetSizeRequest((int)(userProfilesManagerWindow.DefaultWidth * Program.WindowScaleFactor), (int)(userProfilesManagerWindow.DefaultHeight * Program.WindowScaleFactor));
|
||||||
|
userProfilesManagerWindow.Show();
|
||||||
|
}
|
||||||
|
|
||||||
private void Simulate_WakeUp_Message_Pressed(object sender, EventArgs args)
|
private void Simulate_WakeUp_Message_Pressed(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
if (_emulationContext != null)
|
if (_emulationContext != null)
|
||||||
|
|
|
@ -248,6 +248,16 @@
|
||||||
<signal name="activate" handler="Settings_Pressed" swapped="no"/>
|
<signal name="activate" handler="Settings_Pressed" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuItem" id="_manageUserProfiles">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">Open User Profiles Manager window</property>
|
||||||
|
<property name="label" translatable="yes">Manage User Profiles</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<signal name="activate" handler="ManageUserProfiles_Pressed" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
|
@ -115,7 +115,7 @@ namespace Ryujinx.Ui.Widgets
|
||||||
Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Uid user = new Uid(1, 0); // TODO: Remove Hardcoded value.
|
Uid user = new Uid((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
|
||||||
|
|
||||||
result = EnsureApplicationSaveData(_virtualFileSystem.FsClient, out _, new LibHac.Ncm.ApplicationId(titleId), ref control, ref user);
|
result = EnsureApplicationSaveData(_virtualFileSystem.FsClient, out _, new LibHac.Ncm.ApplicationId(titleId), ref control, ref user);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using Gtk;
|
using Gtk;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ryujinx.Ui.Widgets
|
namespace Ryujinx.Ui.Widgets
|
||||||
{
|
{
|
||||||
|
@ -76,6 +77,34 @@ namespace Ryujinx.Ui.Widgets
|
||||||
return response == ResponseType.Yes;
|
return response == ResponseType.Yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static ResponseType CreateCustomDialog(string title, string mainText, string secondaryText, Dictionary<int, string> buttons, MessageType messageType = MessageType.Other)
|
||||||
|
{
|
||||||
|
GtkDialog gtkDialog = new GtkDialog(title, mainText, secondaryText, messageType, ButtonsType.None);
|
||||||
|
|
||||||
|
foreach (var button in buttons)
|
||||||
|
{
|
||||||
|
gtkDialog.AddButton(button.Value, button.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ResponseType)gtkDialog.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string CreateInputDialog(Window parent, string title, string mainText, uint inputMax)
|
||||||
|
{
|
||||||
|
GtkInputDialog gtkDialog = new GtkInputDialog(parent, title, mainText, inputMax);
|
||||||
|
ResponseType response = (ResponseType)gtkDialog.Run();
|
||||||
|
string responseText = gtkDialog.InputEntry.Text.TrimEnd();
|
||||||
|
|
||||||
|
gtkDialog.Dispose();
|
||||||
|
|
||||||
|
if (response == ResponseType.Ok)
|
||||||
|
{
|
||||||
|
return responseText;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
internal static bool CreateExitDialog()
|
internal static bool CreateExitDialog()
|
||||||
{
|
{
|
||||||
return CreateChoiceDialog("Ryujinx - Exit", "Are you sure you want to close Ryujinx?", "All unsaved data will be lost!");
|
return CreateChoiceDialog("Ryujinx - Exit", "Are you sure you want to close Ryujinx?", "All unsaved data will be lost!");
|
||||||
|
|
37
Ryujinx/Ui/Widgets/GtkInputDialog.cs
Normal file
37
Ryujinx/Ui/Widgets/GtkInputDialog.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
using Gtk;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Widgets
|
||||||
|
{
|
||||||
|
public class GtkInputDialog : MessageDialog
|
||||||
|
{
|
||||||
|
public Entry InputEntry { get; }
|
||||||
|
|
||||||
|
public GtkInputDialog(Window parent, string title, string mainText, uint inputMax) : base(parent, DialogFlags.Modal | DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.OkCancel, null)
|
||||||
|
{
|
||||||
|
SetDefaultSize(300, 0);
|
||||||
|
|
||||||
|
Title = title;
|
||||||
|
|
||||||
|
Label mainTextLabel = new Label
|
||||||
|
{
|
||||||
|
Text = mainText
|
||||||
|
};
|
||||||
|
|
||||||
|
InputEntry = new Entry
|
||||||
|
{
|
||||||
|
MaxLength = (int)inputMax
|
||||||
|
};
|
||||||
|
|
||||||
|
Label inputMaxTextLabel = new Label
|
||||||
|
{
|
||||||
|
Text = $"(Max length: {inputMax})"
|
||||||
|
};
|
||||||
|
|
||||||
|
((Box)MessageArea).PackStart(mainTextLabel, true, true, 0);
|
||||||
|
((Box)MessageArea).PackStart(InputEntry, true, true, 5);
|
||||||
|
((Box)MessageArea).PackStart(inputMaxTextLabel, true, true, 0);
|
||||||
|
|
||||||
|
ShowAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
289
Ryujinx/Ui/Windows/AvatarWindow.cs
Normal file
289
Ryujinx/Ui/Windows/AvatarWindow.cs
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
using Gtk;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using LibHac.Fs.Fsa;
|
||||||
|
using LibHac.FsSystem;
|
||||||
|
using LibHac.FsSystem.NcaUtils;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.FileSystem.Content;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Formats.Png;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using System;
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Windows
|
||||||
|
{
|
||||||
|
public class AvatarWindow : Window
|
||||||
|
{
|
||||||
|
public byte[] SelectedProfileImage;
|
||||||
|
public bool NewUser;
|
||||||
|
|
||||||
|
private static Dictionary<string, byte[]> _avatarDict = new Dictionary<string, byte[]>();
|
||||||
|
|
||||||
|
private ListStore _listStore;
|
||||||
|
private IconView _iconView;
|
||||||
|
private Button _setBackgroungColorButton;
|
||||||
|
private Gdk.RGBA _backgroundColor;
|
||||||
|
|
||||||
|
public AvatarWindow() : base($"Ryujinx {Program.Version} - Manage Accounts - Avatar")
|
||||||
|
{
|
||||||
|
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Ryujinx.png");
|
||||||
|
|
||||||
|
CanFocus = false;
|
||||||
|
Resizable = false;
|
||||||
|
Modal = true;
|
||||||
|
TypeHint = Gdk.WindowTypeHint.Dialog;
|
||||||
|
|
||||||
|
SetDefaultSize(740, 400);
|
||||||
|
SetPosition(WindowPosition.Center);
|
||||||
|
|
||||||
|
VBox vbox = new VBox(false, 0);
|
||||||
|
Add(vbox);
|
||||||
|
|
||||||
|
ScrolledWindow scrolledWindow = new ScrolledWindow
|
||||||
|
{
|
||||||
|
ShadowType = ShadowType.EtchedIn
|
||||||
|
};
|
||||||
|
scrolledWindow.SetPolicy(PolicyType.Automatic, PolicyType.Automatic);
|
||||||
|
|
||||||
|
HBox hbox = new HBox(false, 0);
|
||||||
|
|
||||||
|
Button chooseButton = new Button()
|
||||||
|
{
|
||||||
|
Label = "Choose",
|
||||||
|
CanFocus = true,
|
||||||
|
ReceivesDefault = true
|
||||||
|
};
|
||||||
|
chooseButton.Clicked += ChooseButton_Pressed;
|
||||||
|
|
||||||
|
_setBackgroungColorButton = new Button()
|
||||||
|
{
|
||||||
|
Label = "Set Background Color",
|
||||||
|
CanFocus = true
|
||||||
|
};
|
||||||
|
_setBackgroungColorButton.Clicked += SetBackgroungColorButton_Pressed;
|
||||||
|
|
||||||
|
_backgroundColor.Red = 1;
|
||||||
|
_backgroundColor.Green = 1;
|
||||||
|
_backgroundColor.Blue = 1;
|
||||||
|
_backgroundColor.Alpha = 1;
|
||||||
|
|
||||||
|
Button closeButton = new Button()
|
||||||
|
{
|
||||||
|
Label = "Close",
|
||||||
|
CanFocus = true
|
||||||
|
};
|
||||||
|
closeButton.Clicked += CloseButton_Pressed;
|
||||||
|
|
||||||
|
vbox.PackStart(scrolledWindow, true, true, 0);
|
||||||
|
hbox.PackStart(chooseButton, true, true, 0);
|
||||||
|
hbox.PackStart(_setBackgroungColorButton, true, true, 0);
|
||||||
|
hbox.PackStart(closeButton, true, true, 0);
|
||||||
|
vbox.PackStart(hbox, false, false, 0);
|
||||||
|
|
||||||
|
_listStore = new ListStore(typeof(string), typeof(Gdk.Pixbuf));
|
||||||
|
_listStore.SetSortColumnId(0, SortType.Ascending);
|
||||||
|
|
||||||
|
_iconView = new IconView(_listStore);
|
||||||
|
_iconView.ItemWidth = 64;
|
||||||
|
_iconView.ItemPadding = 10;
|
||||||
|
_iconView.PixbufColumn = 1;
|
||||||
|
|
||||||
|
_iconView.SelectionChanged += IconView_SelectionChanged;
|
||||||
|
|
||||||
|
scrolledWindow.Add(_iconView);
|
||||||
|
|
||||||
|
_iconView.GrabFocus();
|
||||||
|
|
||||||
|
ProcessAvatars();
|
||||||
|
|
||||||
|
ShowAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PreloadAvatars(ContentManager contentManager, VirtualFileSystem virtualFileSystem)
|
||||||
|
{
|
||||||
|
if (_avatarDict.Count > 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.NandSystem, NcaContentType.Data);
|
||||||
|
string avatarPath = virtualFileSystem.SwitchPathToSystemPath(contentPath);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(avatarPath))
|
||||||
|
{
|
||||||
|
using (IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open))
|
||||||
|
{
|
||||||
|
Nca nca = new Nca(virtualFileSystem.KeySet, ncaFileStream);
|
||||||
|
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
|
||||||
|
|
||||||
|
foreach (var item in romfs.EnumerateEntries())
|
||||||
|
{
|
||||||
|
// TODO: Parse DatabaseInfo.bin and table.bin files for more accuracy.
|
||||||
|
|
||||||
|
if (item.Type == DirectoryEntryType.File && item.FullPath.Contains("chara") && item.FullPath.Contains("szs"))
|
||||||
|
{
|
||||||
|
romfs.OpenFile(out IFile file, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
using (MemoryStream streamPng = new MemoryStream())
|
||||||
|
{
|
||||||
|
file.AsStream().CopyTo(stream);
|
||||||
|
|
||||||
|
stream.Position = 0;
|
||||||
|
|
||||||
|
Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256);
|
||||||
|
|
||||||
|
avatarImage.SaveAsPng(streamPng);
|
||||||
|
|
||||||
|
_avatarDict.Add(item.FullPath, streamPng.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessAvatars()
|
||||||
|
{
|
||||||
|
_listStore.Clear();
|
||||||
|
|
||||||
|
foreach (var avatar in _avatarDict)
|
||||||
|
{
|
||||||
|
_listStore.AppendValues(avatar.Key, new Gdk.Pixbuf(ProcessImage(avatar.Value), 96, 96));
|
||||||
|
}
|
||||||
|
|
||||||
|
_iconView.SelectPath(new TreePath(new int[] { 0 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] ProcessImage(byte[] data)
|
||||||
|
{
|
||||||
|
using (MemoryStream streamJpg = new MemoryStream())
|
||||||
|
{
|
||||||
|
Image avatarImage = Image.Load(data, new PngDecoder());
|
||||||
|
|
||||||
|
avatarImage.Mutate(x => x.BackgroundColor(new Rgba32((byte)(_backgroundColor.Red * 255),
|
||||||
|
(byte)(_backgroundColor.Green * 255),
|
||||||
|
(byte)(_backgroundColor.Blue * 255),
|
||||||
|
(byte)(_backgroundColor.Alpha * 255))));
|
||||||
|
avatarImage.SaveAsJpeg(streamJpg);
|
||||||
|
|
||||||
|
return streamJpg.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseButton_Pressed(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
SelectedProfileImage = null;
|
||||||
|
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IconView_SelectionChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_iconView.SelectedItems.Length > 0)
|
||||||
|
{
|
||||||
|
_listStore.GetIter(out TreeIter iter, _iconView.SelectedItems[0]);
|
||||||
|
|
||||||
|
SelectedProfileImage = ProcessImage(_avatarDict[(string)_listStore.GetValue(iter, 0)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetBackgroungColorButton_Pressed(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
using (ColorChooserDialog colorChooserDialog = new ColorChooserDialog("Set Background Color", this))
|
||||||
|
{
|
||||||
|
colorChooserDialog.UseAlpha = false;
|
||||||
|
colorChooserDialog.Rgba = _backgroundColor;
|
||||||
|
|
||||||
|
if (colorChooserDialog.Run() == (int)ResponseType.Ok)
|
||||||
|
{
|
||||||
|
_backgroundColor = colorChooserDialog.Rgba;
|
||||||
|
|
||||||
|
ProcessAvatars();
|
||||||
|
}
|
||||||
|
|
||||||
|
colorChooserDialog.Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ChooseButton_Pressed(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] DecompressYaz0(Stream stream)
|
||||||
|
{
|
||||||
|
using (BinaryReader reader = new BinaryReader(stream))
|
||||||
|
{
|
||||||
|
reader.ReadInt32(); // Magic
|
||||||
|
|
||||||
|
uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32());
|
||||||
|
|
||||||
|
reader.ReadInt64(); // Padding
|
||||||
|
|
||||||
|
byte[] input = new byte[stream.Length - stream.Position];
|
||||||
|
stream.Read(input, 0, input.Length);
|
||||||
|
|
||||||
|
long inputOffset = 0;
|
||||||
|
|
||||||
|
byte[] output = new byte[decodedLength];
|
||||||
|
long outputOffset = 0;
|
||||||
|
|
||||||
|
ushort mask = 0;
|
||||||
|
byte header = 0;
|
||||||
|
|
||||||
|
while (outputOffset < decodedLength)
|
||||||
|
{
|
||||||
|
if ((mask >>= 1) == 0)
|
||||||
|
{
|
||||||
|
header = input[inputOffset++];
|
||||||
|
mask = 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((header & mask) > 0)
|
||||||
|
{
|
||||||
|
if (outputOffset == output.Length)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
output[outputOffset++] = input[inputOffset++];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
byte byte1 = input[inputOffset++];
|
||||||
|
byte byte2 = input[inputOffset++];
|
||||||
|
|
||||||
|
int dist = ((byte1 & 0xF) << 8) | byte2;
|
||||||
|
int position = (int)outputOffset - (dist + 1);
|
||||||
|
|
||||||
|
int length = byte1 >> 4;
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
length = input[inputOffset++] + 0x12;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
length += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (length-- > 0)
|
||||||
|
{
|
||||||
|
output[outputOffset++] = output[position++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
255
Ryujinx/Ui/Windows/UserProfilesManagerWindow.Designer.cs
generated
Normal file
255
Ryujinx/Ui/Windows/UserProfilesManagerWindow.Designer.cs
generated
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
using Gtk;
|
||||||
|
using Pango;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Windows
|
||||||
|
{
|
||||||
|
public partial class UserProfilesManagerWindow : Window
|
||||||
|
{
|
||||||
|
private Box _mainBox;
|
||||||
|
private Label _selectedLabel;
|
||||||
|
private Box _selectedUserBox;
|
||||||
|
private Image _selectedUserImage;
|
||||||
|
private VBox _selectedUserInfoBox;
|
||||||
|
private Entry _selectedUserNameEntry;
|
||||||
|
private Label _selectedUserIdLabel;
|
||||||
|
private VBox _selectedUserButtonsBox;
|
||||||
|
private Button _saveProfileNameButton;
|
||||||
|
private Button _changeProfileImageButton;
|
||||||
|
private Box _usersTreeViewBox;
|
||||||
|
private Label _availableUsersLabel;
|
||||||
|
private ScrolledWindow _usersTreeViewWindow;
|
||||||
|
private ListStore _tableStore;
|
||||||
|
private TreeView _usersTreeView;
|
||||||
|
private Box _bottomBox;
|
||||||
|
private Button _addButton;
|
||||||
|
private Button _deleteButton;
|
||||||
|
private Button _closeButton;
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
|
||||||
|
#pragma warning disable CS0612
|
||||||
|
|
||||||
|
//
|
||||||
|
// UserProfilesManagerWindow
|
||||||
|
//
|
||||||
|
CanFocus = false;
|
||||||
|
Resizable = false;
|
||||||
|
Modal = true;
|
||||||
|
WindowPosition = WindowPosition.Center;
|
||||||
|
DefaultWidth = 620;
|
||||||
|
DefaultHeight = 548;
|
||||||
|
TypeHint = Gdk.WindowTypeHint.Dialog;
|
||||||
|
|
||||||
|
//
|
||||||
|
// _mainBox
|
||||||
|
//
|
||||||
|
_mainBox = new Box(Orientation.Vertical, 0);
|
||||||
|
|
||||||
|
//
|
||||||
|
// _selectedLabel
|
||||||
|
//
|
||||||
|
_selectedLabel = new Label("Selected User Profile:")
|
||||||
|
{
|
||||||
|
Margin = 15,
|
||||||
|
Attributes = new AttrList()
|
||||||
|
};
|
||||||
|
_selectedLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold));
|
||||||
|
|
||||||
|
//
|
||||||
|
// _viewBox
|
||||||
|
//
|
||||||
|
_usersTreeViewBox = new Box(Orientation.Vertical, 0);
|
||||||
|
|
||||||
|
//
|
||||||
|
// _SelectedUserBox
|
||||||
|
//
|
||||||
|
_selectedUserBox = new Box(Orientation.Horizontal, 0)
|
||||||
|
{
|
||||||
|
MarginLeft = 30
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// _selectedUserImage
|
||||||
|
//
|
||||||
|
_selectedUserImage = new Image();
|
||||||
|
|
||||||
|
//
|
||||||
|
// _selectedUserInfoBox
|
||||||
|
//
|
||||||
|
_selectedUserInfoBox = new VBox(true, 0);
|
||||||
|
|
||||||
|
//
|
||||||
|
// _selectedUserNameEntry
|
||||||
|
//
|
||||||
|
_selectedUserNameEntry = new Entry("")
|
||||||
|
{
|
||||||
|
MarginLeft = 15,
|
||||||
|
MaxLength = (int)MaxProfileNameLength
|
||||||
|
};
|
||||||
|
_selectedUserNameEntry.KeyReleaseEvent += SelectedUserNameEntry_KeyReleaseEvent;
|
||||||
|
|
||||||
|
//
|
||||||
|
// _selectedUserIdLabel
|
||||||
|
//
|
||||||
|
_selectedUserIdLabel = new Label("")
|
||||||
|
{
|
||||||
|
MarginTop = 15,
|
||||||
|
MarginLeft = 15
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// _selectedUserButtonsBox
|
||||||
|
//
|
||||||
|
_selectedUserButtonsBox = new VBox()
|
||||||
|
{
|
||||||
|
MarginRight = 30
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// _saveProfileNameButton
|
||||||
|
//
|
||||||
|
_saveProfileNameButton = new Button()
|
||||||
|
{
|
||||||
|
Label = "Save Profile Name",
|
||||||
|
CanFocus = true,
|
||||||
|
ReceivesDefault = true,
|
||||||
|
Sensitive = false
|
||||||
|
};
|
||||||
|
_saveProfileNameButton.Clicked += EditProfileNameButton_Pressed;
|
||||||
|
|
||||||
|
//
|
||||||
|
// _changeProfileImageButton
|
||||||
|
//
|
||||||
|
_changeProfileImageButton = new Button()
|
||||||
|
{
|
||||||
|
Label = "Change Profile Image",
|
||||||
|
CanFocus = true,
|
||||||
|
ReceivesDefault = true,
|
||||||
|
MarginTop = 10
|
||||||
|
};
|
||||||
|
_changeProfileImageButton.Clicked += ChangeProfileImageButton_Pressed;
|
||||||
|
|
||||||
|
//
|
||||||
|
// _availableUsersLabel
|
||||||
|
//
|
||||||
|
_availableUsersLabel = new Label("Available User Profiles:")
|
||||||
|
{
|
||||||
|
Margin = 15,
|
||||||
|
Attributes = new AttrList()
|
||||||
|
};
|
||||||
|
_availableUsersLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold));
|
||||||
|
|
||||||
|
//
|
||||||
|
// _usersTreeViewWindow
|
||||||
|
//
|
||||||
|
_usersTreeViewWindow = new ScrolledWindow()
|
||||||
|
{
|
||||||
|
ShadowType = ShadowType.In,
|
||||||
|
CanFocus = true,
|
||||||
|
Expand = true,
|
||||||
|
MarginLeft = 30,
|
||||||
|
MarginRight = 30,
|
||||||
|
MarginBottom = 15
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// _tableStore
|
||||||
|
//
|
||||||
|
_tableStore = new ListStore(typeof(bool), typeof(Gdk.Pixbuf), typeof(string), typeof(Gdk.RGBA));
|
||||||
|
|
||||||
|
//
|
||||||
|
// _usersTreeView
|
||||||
|
//
|
||||||
|
_usersTreeView = new TreeView(_tableStore)
|
||||||
|
{
|
||||||
|
HoverSelection = true,
|
||||||
|
HeadersVisible = false,
|
||||||
|
};
|
||||||
|
_usersTreeView.RowActivated += UsersTreeView_Activated;
|
||||||
|
|
||||||
|
//
|
||||||
|
// _bottomBox
|
||||||
|
//
|
||||||
|
_bottomBox = new Box(Orientation.Horizontal, 0)
|
||||||
|
{
|
||||||
|
MarginLeft = 30,
|
||||||
|
MarginRight = 30,
|
||||||
|
MarginBottom = 15
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// _addButton
|
||||||
|
//
|
||||||
|
_addButton = new Button()
|
||||||
|
{
|
||||||
|
Label = "Add New Profile",
|
||||||
|
CanFocus = true,
|
||||||
|
ReceivesDefault = true,
|
||||||
|
HeightRequest = 35
|
||||||
|
};
|
||||||
|
_addButton.Clicked += AddButton_Pressed;
|
||||||
|
|
||||||
|
//
|
||||||
|
// _deleteButton
|
||||||
|
//
|
||||||
|
_deleteButton = new Button()
|
||||||
|
{
|
||||||
|
Label = "Delete Selected Profile",
|
||||||
|
CanFocus = true,
|
||||||
|
ReceivesDefault = true,
|
||||||
|
HeightRequest = 35,
|
||||||
|
MarginLeft = 10
|
||||||
|
};
|
||||||
|
_deleteButton.Clicked += DeleteButton_Pressed;
|
||||||
|
|
||||||
|
//
|
||||||
|
// _closeButton
|
||||||
|
//
|
||||||
|
_closeButton = new Button()
|
||||||
|
{
|
||||||
|
Label = "Close",
|
||||||
|
CanFocus = true,
|
||||||
|
ReceivesDefault = true,
|
||||||
|
HeightRequest = 35,
|
||||||
|
WidthRequest = 80
|
||||||
|
};
|
||||||
|
_closeButton.Clicked += CloseButton_Pressed;
|
||||||
|
|
||||||
|
#pragma warning restore CS0612
|
||||||
|
|
||||||
|
ShowComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowComponent()
|
||||||
|
{
|
||||||
|
_usersTreeViewWindow.Add(_usersTreeView);
|
||||||
|
|
||||||
|
_usersTreeViewBox.Add(_usersTreeViewWindow);
|
||||||
|
|
||||||
|
_bottomBox.PackStart(new Gtk.Alignment(-1, 0, 0, 0) { _addButton }, false, false, 0);
|
||||||
|
_bottomBox.PackStart(new Gtk.Alignment(-1, 0, 0, 0) { _deleteButton }, false, false, 0);
|
||||||
|
_bottomBox.PackEnd(new Gtk.Alignment(1, 0, 0, 0) { _closeButton }, false, false, 0);
|
||||||
|
|
||||||
|
_selectedUserInfoBox.Add(_selectedUserNameEntry);
|
||||||
|
_selectedUserInfoBox.Add(_selectedUserIdLabel);
|
||||||
|
|
||||||
|
_selectedUserButtonsBox.Add(_saveProfileNameButton);
|
||||||
|
_selectedUserButtonsBox.Add(_changeProfileImageButton);
|
||||||
|
|
||||||
|
_selectedUserBox.Add(_selectedUserImage);
|
||||||
|
_selectedUserBox.PackStart(new Gtk.Alignment(-1, 0, 0, 0) { _selectedUserInfoBox }, true, true, 0);
|
||||||
|
_selectedUserBox.Add(_selectedUserButtonsBox);
|
||||||
|
|
||||||
|
_mainBox.PackStart(new Gtk.Alignment(-1, 0, 0, 0) { _selectedLabel }, false, false, 0);
|
||||||
|
_mainBox.PackStart(_selectedUserBox, false, true, 0);
|
||||||
|
_mainBox.PackStart(new Gtk.Alignment(-1, 0, 0, 0) { _availableUsersLabel }, false, false, 0);
|
||||||
|
_mainBox.Add(_usersTreeViewBox);
|
||||||
|
_mainBox.Add(_bottomBox);
|
||||||
|
|
||||||
|
Add(_mainBox);
|
||||||
|
|
||||||
|
ShowAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
327
Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs
Normal file
327
Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs
Normal file
|
@ -0,0 +1,327 @@
|
||||||
|
using Gtk;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.FileSystem.Content;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
using Ryujinx.Ui.Widgets;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
|
using UserId = Ryujinx.HLE.HOS.Services.Account.Acc.UserId;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Windows
|
||||||
|
{
|
||||||
|
public partial class UserProfilesManagerWindow : Window
|
||||||
|
{
|
||||||
|
private const uint MaxProfileNameLength = 0x20;
|
||||||
|
|
||||||
|
private readonly AccountManager _accountManager;
|
||||||
|
private readonly ContentManager _contentManager;
|
||||||
|
|
||||||
|
private byte[] _bufferImageProfile;
|
||||||
|
private string _tempNewProfileName;
|
||||||
|
|
||||||
|
private Gdk.RGBA _selectedColor;
|
||||||
|
|
||||||
|
private ManualResetEvent _avatarsPreloadingEvent = new ManualResetEvent(false);
|
||||||
|
|
||||||
|
public UserProfilesManagerWindow(AccountManager accountManager, ContentManager contentManager, VirtualFileSystem virtualFileSystem) : base($"Ryujinx {Program.Version} - Manage User Profiles")
|
||||||
|
{
|
||||||
|
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Ryujinx.png");
|
||||||
|
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
_selectedColor.Red = 0.212;
|
||||||
|
_selectedColor.Green = 0.843;
|
||||||
|
_selectedColor.Blue = 0.718;
|
||||||
|
_selectedColor.Alpha = 1;
|
||||||
|
|
||||||
|
_accountManager = accountManager;
|
||||||
|
_contentManager = contentManager;
|
||||||
|
|
||||||
|
CellRendererToggle userSelectedToggle = new CellRendererToggle();
|
||||||
|
userSelectedToggle.Toggled += UserSelectedToggle_Toggled;
|
||||||
|
|
||||||
|
// NOTE: Uncomment following line when multiple selection of user profiles is supported.
|
||||||
|
//_usersTreeView.AppendColumn("Selected", userSelectedToggle, "active", 0);
|
||||||
|
_usersTreeView.AppendColumn("User Icon", new CellRendererPixbuf(), "pixbuf", 1);
|
||||||
|
_usersTreeView.AppendColumn("User Info", new CellRendererText(), "text", 2, "background-rgba", 3);
|
||||||
|
|
||||||
|
_tableStore.SetSortColumnId(0, SortType.Descending);
|
||||||
|
|
||||||
|
RefreshList();
|
||||||
|
|
||||||
|
if (_contentManager.GetCurrentFirmwareVersion() != null)
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
AvatarWindow.PreloadAvatars(contentManager, virtualFileSystem);
|
||||||
|
_avatarsPreloadingEvent.Set();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshList()
|
||||||
|
{
|
||||||
|
_tableStore.Clear();
|
||||||
|
|
||||||
|
foreach (UserProfile userProfile in _accountManager.GetAllUsers())
|
||||||
|
{
|
||||||
|
_tableStore.AppendValues(userProfile.AccountState == AccountState.Open, new Gdk.Pixbuf(userProfile.Image, 96, 96), $"{userProfile.Name}\n{userProfile.UserId}", Gdk.RGBA.Zero);
|
||||||
|
|
||||||
|
if (userProfile.AccountState == AccountState.Open)
|
||||||
|
{
|
||||||
|
_selectedUserImage.Pixbuf = new Gdk.Pixbuf(userProfile.Image, 96, 96);
|
||||||
|
_selectedUserIdLabel.Text = userProfile.UserId.ToString();
|
||||||
|
_selectedUserNameEntry.Text = userProfile.Name;
|
||||||
|
|
||||||
|
_deleteButton.Sensitive = userProfile.UserId != AccountManager.DefaultUserId;
|
||||||
|
|
||||||
|
_usersTreeView.Model.GetIterFirst(out TreeIter firstIter);
|
||||||
|
_tableStore.SetValue(firstIter, 3, _selectedColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Events
|
||||||
|
//
|
||||||
|
|
||||||
|
private void UsersTreeView_Activated(object o, RowActivatedArgs args)
|
||||||
|
{
|
||||||
|
SelectUserTreeView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UserSelectedToggle_Toggled(object o, ToggledArgs args)
|
||||||
|
{
|
||||||
|
SelectUserTreeView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectUserTreeView()
|
||||||
|
{
|
||||||
|
// Get selected item informations.
|
||||||
|
_usersTreeView.Selection.GetSelected(out TreeIter selectedIter);
|
||||||
|
|
||||||
|
Gdk.Pixbuf userPicture = (Gdk.Pixbuf)_tableStore.GetValue(selectedIter, 1);
|
||||||
|
|
||||||
|
string userName = _tableStore.GetValue(selectedIter, 2).ToString().Split("\n")[0];
|
||||||
|
string userId = _tableStore.GetValue(selectedIter, 2).ToString().Split("\n")[1];
|
||||||
|
|
||||||
|
// Unselect the first user.
|
||||||
|
_usersTreeView.Model.GetIterFirst(out TreeIter firstIter);
|
||||||
|
_tableStore.SetValue(firstIter, 0, false);
|
||||||
|
_tableStore.SetValue(firstIter, 3, Gdk.RGBA.Zero);
|
||||||
|
|
||||||
|
// Set new informations.
|
||||||
|
_tableStore.SetValue(selectedIter, 0, true);
|
||||||
|
|
||||||
|
_selectedUserImage.Pixbuf = userPicture;
|
||||||
|
_selectedUserNameEntry.Text = userName;
|
||||||
|
_selectedUserIdLabel.Text = userId;
|
||||||
|
_saveProfileNameButton.Sensitive = false;
|
||||||
|
|
||||||
|
// Open the selected one.
|
||||||
|
_accountManager.OpenUser(new UserId(userId));
|
||||||
|
|
||||||
|
_deleteButton.Sensitive = userId != AccountManager.DefaultUserId.ToString();
|
||||||
|
|
||||||
|
_tableStore.SetValue(selectedIter, 3, _selectedColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectedUserNameEntry_KeyReleaseEvent(object o, KeyReleaseEventArgs args)
|
||||||
|
{
|
||||||
|
if (_saveProfileNameButton.Sensitive == false)
|
||||||
|
{
|
||||||
|
_saveProfileNameButton.Sensitive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddButton_Pressed(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_tempNewProfileName = GtkDialog.CreateInputDialog(this, "Choose the Profile Name", "Please Enter a Profile Name", MaxProfileNameLength);
|
||||||
|
|
||||||
|
if (_tempNewProfileName != "")
|
||||||
|
{
|
||||||
|
SelectProfileImage(true);
|
||||||
|
|
||||||
|
if (_bufferImageProfile != null)
|
||||||
|
{
|
||||||
|
AddUser();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteButton_Pressed(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (GtkDialog.CreateChoiceDialog("Delete User Profile", "Are you sure you want to delete the profile ?", "Deleting this profile will also delete all associated save data."))
|
||||||
|
{
|
||||||
|
_accountManager.DeleteUser(GetSelectedUserId());
|
||||||
|
|
||||||
|
RefreshList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EditProfileNameButton_Pressed(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_saveProfileNameButton.Sensitive = false;
|
||||||
|
|
||||||
|
_accountManager.SetUserName(GetSelectedUserId(), _selectedUserNameEntry.Text);
|
||||||
|
|
||||||
|
RefreshList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessProfileImage(byte[] buffer)
|
||||||
|
{
|
||||||
|
using (Image image = Image.Load(buffer))
|
||||||
|
{
|
||||||
|
image.Mutate(x => x.Resize(256, 256));
|
||||||
|
|
||||||
|
using (MemoryStream streamJpg = new MemoryStream())
|
||||||
|
{
|
||||||
|
image.SaveAsJpeg(streamJpg);
|
||||||
|
|
||||||
|
_bufferImageProfile = streamJpg.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProfileImageFileChooser()
|
||||||
|
{
|
||||||
|
FileChooserDialog fileChooser = new FileChooserDialog("Import Custom Profile Image", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Import", ResponseType.Accept)
|
||||||
|
{
|
||||||
|
SelectMultiple = false,
|
||||||
|
Filter = new FileFilter()
|
||||||
|
};
|
||||||
|
|
||||||
|
fileChooser.SetPosition(WindowPosition.Center);
|
||||||
|
fileChooser.Filter.AddPattern("*.jpg");
|
||||||
|
fileChooser.Filter.AddPattern("*.jpeg");
|
||||||
|
fileChooser.Filter.AddPattern("*.png");
|
||||||
|
fileChooser.Filter.AddPattern("*.bmp");
|
||||||
|
|
||||||
|
if (fileChooser.Run() == (int)ResponseType.Accept)
|
||||||
|
{
|
||||||
|
ProcessProfileImage(File.ReadAllBytes(fileChooser.Filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
fileChooser.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectProfileImage(bool newUser = false)
|
||||||
|
{
|
||||||
|
if (_contentManager.GetCurrentFirmwareVersion() == null)
|
||||||
|
{
|
||||||
|
ProfileImageFileChooser();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Dictionary<int, string> buttons = new Dictionary<int, string>()
|
||||||
|
{
|
||||||
|
{ 0, "Import Image File" },
|
||||||
|
{ 1, "Select Firmware Avatar" }
|
||||||
|
};
|
||||||
|
|
||||||
|
ResponseType responseDialog = GtkDialog.CreateCustomDialog("Profile Image Selection",
|
||||||
|
"Choose a Profile Image",
|
||||||
|
"You may import a custom profile image, or select an avatar from the system firmware.",
|
||||||
|
buttons, MessageType.Question);
|
||||||
|
|
||||||
|
if (responseDialog == 0)
|
||||||
|
{
|
||||||
|
ProfileImageFileChooser();
|
||||||
|
}
|
||||||
|
else if (responseDialog == (ResponseType)1)
|
||||||
|
{
|
||||||
|
AvatarWindow avatarWindow = new AvatarWindow()
|
||||||
|
{
|
||||||
|
NewUser = newUser
|
||||||
|
};
|
||||||
|
|
||||||
|
avatarWindow.DeleteEvent += AvatarWindow_DeleteEvent;
|
||||||
|
|
||||||
|
avatarWindow.SetSizeRequest((int)(avatarWindow.DefaultWidth * Program.WindowScaleFactor), (int)(avatarWindow.DefaultHeight * Program.WindowScaleFactor));
|
||||||
|
avatarWindow.Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ChangeProfileImageButton_Pressed(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_contentManager.GetCurrentFirmwareVersion() != null)
|
||||||
|
{
|
||||||
|
_avatarsPreloadingEvent.WaitOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectProfileImage();
|
||||||
|
|
||||||
|
if (_bufferImageProfile != null)
|
||||||
|
{
|
||||||
|
SetUserImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AvatarWindow_DeleteEvent(object sender, DeleteEventArgs args)
|
||||||
|
{
|
||||||
|
_bufferImageProfile = ((AvatarWindow)sender).SelectedProfileImage;
|
||||||
|
|
||||||
|
if (_bufferImageProfile != null)
|
||||||
|
{
|
||||||
|
if (((AvatarWindow)sender).NewUser)
|
||||||
|
{
|
||||||
|
AddUser();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetUserImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddUser()
|
||||||
|
{
|
||||||
|
_accountManager.AddUser(_tempNewProfileName, _bufferImageProfile);
|
||||||
|
|
||||||
|
_bufferImageProfile = null;
|
||||||
|
_tempNewProfileName = "";
|
||||||
|
|
||||||
|
RefreshList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetUserImage()
|
||||||
|
{
|
||||||
|
_accountManager.SetUserImage(GetSelectedUserId(), _bufferImageProfile);
|
||||||
|
|
||||||
|
_bufferImageProfile = null;
|
||||||
|
|
||||||
|
RefreshList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserId GetSelectedUserId()
|
||||||
|
{
|
||||||
|
if (_usersTreeView.Model.GetIterFirst(out TreeIter iter))
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if ((bool)_tableStore.GetValue(iter, 0))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (_usersTreeView.Model.IterNext(ref iter));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UserId(_tableStore.GetValue(iter, 2).ToString().Split("\n")[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseButton_Pressed(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue