远程控制功能

This commit is contained in:
Songyang.Huo
2023-10-05 08:45:07 +08:00
parent 9a0b24e3c6
commit ef8eb5f36d
10 changed files with 764 additions and 3 deletions

View File

@@ -1679,6 +1679,38 @@
"sortId": 30001,
"usage": "一份椒盐果仁的糖皮月饼,可用于恢复理智。"
},
"ap_supply_lt_120_2023_1": {
"classifyType": "CONSUME",
"description": "可口凉爽的冰皮月饼,记得冷藏保存哦!",
"icon": "ap_supply_lt_120_2023_1.png",
"name": "“满庭光”",
"sortId": 30001,
"usage": "一份芒果椰蓉馅的冰皮月饼,可用于恢复理智。"
},
"ap_supply_lt_120_2023_2": {
"classifyType": "CONSUME",
"description": "可口凉爽的冰皮月饼,记得冷藏保存哦!",
"icon": "ap_supply_lt_120_2023_2.png",
"name": "“良宵晴”",
"sortId": 30001,
"usage": "一份肉松馅的冰皮月饼,可用于恢复理智。"
},
"ap_supply_lt_120_2023_3": {
"classifyType": "CONSUME",
"description": "香甜脆爽的糖皮月饼,食用前记得加热哦!",
"icon": "ap_supply_lt_120_2023_3.png",
"name": "“飘桂香”",
"sortId": 30001,
"usage": "一份豆沙桂花馅的糖皮月饼,可用于恢复理智。"
},
"ap_supply_lt_120_2023_4": {
"classifyType": "CONSUME",
"description": "香甜脆爽的糖皮月饼,食用前记得加热哦!",
"icon": "ap_supply_lt_120_2023_4.png",
"name": "“藏金波“",
"sortId": 30001,
"usage": "一份咸蛋黄馅的糖皮月饼,可用于恢复理智。"
},
"ap_supply_lt_60": {
"classifyType": "CONSUME",
"description": "虽然暴饮暴食可以缓解战场带来的焦虑与压力,但总归不利于博士的身体健康。本品含有一定糖分,故而外包装采取特制结构,较难拆解,请借此消耗掉多余的卡路里。",

View File

@@ -163,6 +163,11 @@ namespace MaaWpfGui.Constants
public const string DropsItemName = "MainFunction.Drops.ItemName";
public const string DropsQuantity = "MainFunction.Drops.Quantity";
public const string RemoteControlGetTaskEndpointUri = "RemoteControl.RemoteControlGetTaskEndpointUri";
public const string RemoteControlReportStatusUri = "RemoteControl.RemoteControlReportStatusUri";
public const string RemoteControlUserIdentity = "RemoteControl.RemoteControlUserIdentity";
public const string RemoteControlDeviceIdentity = "RemoteControl.RemoteControlDeviceIdentity";
public const string ExternalNotificationEnabled = "ExternalNotification.Enabled";
public const string ExternalNotificationSmtpServer = "ExternalNotification.Smtp.Server";
public const string ExternalNotificationSmtpPort = "ExternalNotification.Smtp.Port";

View File

@@ -16,6 +16,7 @@ using MaaWpfGui.Main;
using MaaWpfGui.Services;
using MaaWpfGui.Services.HotKeys;
using MaaWpfGui.Services.Managers;
using MaaWpfGui.Services.RemoteControl;
using MaaWpfGui.Services.Web;
using MaaWpfGui.ViewModels.UI;
using Stylet;
@@ -50,6 +51,8 @@ namespace MaaWpfGui.Helper
public static IMaaHotKeyActionHandler MaaHotKeyActionHandler { get; private set; }
public static RemoteControlService RemoteControlService { get; private set; }
public static IMainWindowManager MainWindowManager { get; private set; }
public static IHttpService HttpService { get; private set; }
@@ -71,6 +74,8 @@ namespace MaaWpfGui.Helper
HttpService = container.Get<HttpService>();
MaaApiService = container.Get<MaaApiService>();
RemoteControlService = container.Get<RemoteControlService>();
HotKeyManager = container.Get<HotKeyManager>();
MaaHotKeyManager = container.Get<MaaHotKeyManager>();
MaaHotKeyActionHandler = container.Get<MaaHotKeyActionHandler>();

View File

@@ -23,6 +23,7 @@ using MaaWpfGui.Helper;
using MaaWpfGui.Services;
using MaaWpfGui.Services.HotKeys;
using MaaWpfGui.Services.Managers;
using MaaWpfGui.Services.RemoteControl;
using MaaWpfGui.Services.Web;
using MaaWpfGui.ViewModels.UI;
using MaaWpfGui.Views.UI;
@@ -171,6 +172,8 @@ namespace MaaWpfGui.Main
builder.Bind<IMaaHotKeyManager>().To<MaaHotKeyManager>().InSingletonScope();
builder.Bind<IMaaHotKeyActionHandler>().To<MaaHotKeyActionHandler>().InSingletonScope();
builder.Bind<RemoteControlService>().To<RemoteControlService>().InSingletonScope();
builder.Bind<IMainWindowManager>().To<MainWindowManager>().InSingletonScope();
builder.Bind<IHttpService>().To<HttpService>().InSingletonScope();

View File

@@ -234,6 +234,13 @@
<system:String x:Key="AdditionCommand">附加命令</system:String>
<system:String x:Key="StartsWithScript">开始前脚本</system:String>
<system:String x:Key="EndsWithScript">结束后脚本</system:String>
<system:String x:Key="RemoteControlSettings">远程控制</system:String>
<system:String x:Key="RemoteControlDeviceIdentityLabel">设备标识符(只读)</system:String>
<system:String x:Key="RemoteControlConnectionTest">测试连接</system:String>
<system:String x:Key="RemoteControlRegenerateDeviceIdentity">重新生成</system:String>
<system:String x:Key="RemoteControlUserIdentityLabel">用户标识符</system:String>
<system:String x:Key="RemoteControlGetTaskEndpointLabel">获取任务端点</system:String>
<system:String x:Key="RemoteControlReportStatusEndpointLabel">汇报任务端点</system:String>
<system:String x:Key="RoguelikeTheme">肉鸽主题</system:String>
<system:String x:Key="RoguelikeThemePhantom">傀影</system:String>
<system:String x:Key="RoguelikeThemeMizuki">水月</system:String>
@@ -663,6 +670,12 @@
<system:String x:Key="GameResourceUpdated">游戏资源已更新,请重启 MAA</system:String>
<system:String x:Key="GameResourceUpdating">游戏资源正在更新,请勿关闭 MAA</system:String>
<!-- Api -->
<!-- Remote Control -->
<system:String x:Key="RemoteControlConnectionTestSuccess">连接测试成功!</system:String>
<system:String x:Key="RemoteControlConnectionTestFail">连接测试失败。</system:String>
<system:String x:Key="RemoteControlReceivedTask">收到远端任务{0}任务Id:{1}。</system:String>
<system:String x:Key="RemoteControlCompletedTask">完成远端任务{0}任务Id:{1}。</system:String>
<!-- Remote Control -->
<!-- External Notification -->
<system:String x:Key="ExternalNotificationEmailTemplateHello">博士,有新的通知哦!</system:String>
<system:String x:Key="ExternalNotificationEmailTemplateFooterLineOne">您会收到此邮件,是因为您在 MAA 中设置了 SMTP 服务器并开启了邮件通知服务。</system:String>

View File

@@ -0,0 +1,496 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using MaaWpfGui.Constants;
using MaaWpfGui.Helper;
using MaaWpfGui.States;
using MaaWpfGui.ViewModels.UI;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serilog;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
namespace MaaWpfGui.Services.RemoteControl
{
public class RemoteControlService
{
private readonly Task _pollJobTask = Task.CompletedTask;
private readonly Task _executeJobTask = Task.CompletedTask;
private readonly List<string> _executedTaskIds = new List<string>();
private readonly ConcurrentQueue<JObject> _taskQueue = new ConcurrentQueue<JObject>();
private readonly RunningState _runningState;
public RemoteControlService()
{
_pollJobTask = _pollJobTask.ContinueWith(async (_) =>
{
while (true)
{
await Task.Delay(1000);
try
{
await PollJobTaskLoop();
}
catch (Exception ex)
{
Log.Logger.Error(ex, "RemoteControl service raises unknown error.");
}
}
// ReSharper disable once FunctionNeverReturns
});
_executeJobTask = _executeJobTask.ContinueWith(async (_) =>
{
while (true)
{
await Task.Delay(1000);
try
{
await ExecuteJobLoop();
}
catch (Exception ex)
{
Log.Logger.Error(ex, "RemoteControl service raises unknown error.");
}
}
// ReSharper disable once FunctionNeverReturns
});
_runningState = RunningState.Instance;
}
#region Private Method Invoker
private static void InvokeInstanceMethod(object instance, string methodName)
{
if (instance == null)
{
throw new ArgumentNullException(nameof(instance));
}
if (string.IsNullOrEmpty(methodName))
{
throw new ArgumentNullException(nameof(methodName));
}
Type type = instance.GetType();
MethodInfo methodInfo = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
if (methodInfo == null)
{
throw new ArgumentException($"Method '{methodName}' not found in type '{type.FullName}'.");
}
methodInfo.Invoke(instance, null);
}
private static T InvokeInstanceFunction<T>(object instance, string methodName)
{
if (instance == null)
{
throw new ArgumentNullException(nameof(instance));
}
if (string.IsNullOrEmpty(methodName))
{
throw new ArgumentNullException(nameof(methodName));
}
Type type = instance.GetType();
MethodInfo methodInfo = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
if (methodInfo == null)
{
throw new ArgumentException($"Method '{methodName}' not found in type '{type.FullName}'.");
}
if (!typeof(T).IsAssignableFrom(methodInfo.ReturnType))
{
throw new ArgumentException($"Method '{methodName}' is not {typeof(T)}.");
}
return (T)methodInfo.Invoke(instance, null);
}
private static async Task<T> InvokeInstanceAsyncFunction<T>(object instance, string methodName)
{
if (instance == null) { throw new ArgumentNullException(nameof(instance)); }
if (string.IsNullOrEmpty(methodName)) { throw new ArgumentNullException(nameof(methodName)); }
Type type = instance.GetType();
MethodInfo methodInfo = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
if (methodInfo == null)
{
throw new ArgumentException($"Method '{methodName}' not found in type '{type.FullName}'.");
}
// 检查方法是否是异步方法 (返回Task或Task<T>)
if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
{
throw new ArgumentException($"Method '{methodName}' is not asynchronous.");
}
return await (Task<T>)methodInfo.Invoke(instance, null);
}
private static TResult InvokeStaticFunction<TResult>(Type staticType, string methodName)
{
if (staticType == null) { throw new ArgumentNullException(nameof(staticType)); }
if (string.IsNullOrEmpty(methodName)) { throw new ArgumentNullException(nameof(methodName)); }
MethodInfo methodInfo = staticType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static);
if (methodInfo == null)
{
throw new ArgumentException($"Method '{methodName}' not found in type '{staticType.FullName}'.");
}
return (TResult)methodInfo.Invoke(null, null);
}
#endregion
private async Task PollJobTaskLoop()
{
var endpoint = Instances.SettingsViewModel.RemoteControlGetTaskEndpointUri;
var uid = Instances.SettingsViewModel.RemoteControlUserIdentity;
var did = Instances.SettingsViewModel.RemoteControlDeviceIdentity;
var response = await Instances.HttpService.PostAsJsonAsync(new Uri(endpoint), new { user=uid, device = did});
if (response == null)
{
Log.Logger.Error("RemoteControlService endpoint failed.");
return;
}
var jsonObject = JsonConvert.DeserializeObject<JObject>(response);
// A list of task
if (jsonObject?.GetValue("tasks") is JArray tasks)
{
foreach (var task in tasks.OfType<JObject>())
{
var type = task.GetValue("type")?.Value<string>();
if (!string.IsNullOrWhiteSpace(type))
{
// It is a valid task
var id = task.GetValue("id")?.Value<string>();
if (!_executedTaskIds.Contains(id))
{
_executedTaskIds.Add(id);
_taskQueue.Enqueue(task);
}
}
}
}
}
private async Task ExecuteJobLoop()
{
if (_taskQueue.TryDequeue(out var task))
{
var type = task.GetValue("type")?.Value<string>();
var id = task.GetValue("id")?.Value<string>();
var data = task.GetValue("params")?.Value<string>();
var payload = string.Empty;
var status = "SUCCESS";
switch (type)
{
case "LinkStart":
{
// 一键长草特殊任务
await _runningState.UntilIdleAsync();
var startLogStr = string.Format(LocalizationHelper.GetString("RemoteControlReceivedTask"), type, id);
Application.Current.Dispatcher.Invoke(() =>
{
Instances.TaskQueueViewModel.AddLog(startLogStr);
Instances.TaskQueueViewModel.LinkStart();
});
await _runningState.UntilIdleAsync();
var stopLogStr = string.Format(LocalizationHelper.GetString("RemoteControlCompletedTask"), type, id);
Application.Current.Dispatcher.Invoke(() =>
{
Instances.TaskQueueViewModel.AddLog(stopLogStr);
});
break;
}
case "LinkStart-Base":
case "LinkStart-WakeUp":
case "LinkStart-Combat":
case "LinkStart-Recruiting":
case "LinkStart-Mall":
case "LinkStart-Mission":
case "LinkStart-AutoRoguelike":
case "LinkStart-ReclamationAlgorithm":
{
await LinkStart(new[] { type.Split('-')[1] });
break;
}
case "Toolbox-GachaOnce":
{
await _runningState.UntilIdleAsync();
Instances.RecognizerViewModel.GachaOnce();
while (!Instances.RecognizerViewModel.GachaDone)
{
await Task.Delay(100); // 暂停100毫秒以避免密集循环
}
break;
}
case "Toolbox-GachaTenTimes":
{
await _runningState.UntilIdleAsync();
Instances.RecognizerViewModel.GachaTenTimes();
while (!Instances.RecognizerViewModel.GachaDone)
{
await Task.Delay(100); // 暂停100毫秒以避免密集循环
}
break;
}
case "CaptureImage":
{
string errMsg = string.Empty;
bool connected = await Task.Run(() => Instances.AsstProxy.AsstConnect(ref errMsg));
if (connected)
{
var image = Instances.AsstProxy.AsstGetImage();
if (image == null)
{
status = "FAILED";
break;
}
byte[] bytes;
using (MemoryStream stream = new MemoryStream())
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(image));
encoder.Save(stream);
bytes = stream.ToArray();
}
payload = Convert.ToBase64String(bytes);
break;
}
else
{
status = "FAILED";
break;
}
}
case "Settings-ConnectAddress":
// ConfigurationHelper.SetValue(type.Split('-')[1], data);
Application.Current.Dispatcher.Invoke(() =>
{
Instances.SettingsViewModel.ConnectAddress = data;
});
break;
default:
// 未知的Type统一直接发给MAACore
// No! 未知的任务一概不处理
break;
}
var endpoint = Instances.SettingsViewModel.RemoteControlReportStatusUri;
var uid = Instances.SettingsViewModel.RemoteControlUserIdentity;
var did = Instances.SettingsViewModel.RemoteControlDeviceIdentity;
var response = await Instances.HttpService.PostAsJsonAsync(new Uri(endpoint), new
{
user = uid,
device = did,
status = status,
task = id,
payload = payload,
});
if (response == null)
{
Log.Logger.Error("RemoteControlService report task failed.");
}
}
}
/// <summary>
/// 根据"一键长草"功能进行修改的方法。
/// </summary>
/// <remarks>
/// <para>注意以下特点:</para>
/// <para>- 可以在非UI线程执行。</para>
/// <para>- 不调用StartScript。</para>
/// <para>- 不使用Model里的列表。</para>
/// <para>- 在结尾添加对RunningStatus的等待。</para>
/// <para>若"一键长草"功能在未来有更新,此方法也应进行相应的同步更新,但需确保上述特点保持不变。</para>
/// </remarks>
/// <param name="originalNames">指定的任务列表。</param>
/// <returns>异步任务,无返回结果。</returns>
public async Task LinkStart(IEnumerable<string> originalNames)
{
await _runningState.UntilIdleAsync();
_runningState.SetIdle(false);
await Application.Current.Dispatcher.Invoke(async () =>
{
// 虽然更改时已经保存过了,不过保险起见还是在点击开始之后再保存一次任务及基建列表
Instances.TaskQueueViewModel.TaskItemSelectionChanged();
Instances.SettingsViewModel.InfrastOrderSelectionChanged();
InvokeInstanceMethod(Instances.TaskQueueViewModel, "ClearLog");
/*await Task.Run(() => Instances.SettingsViewModel.RunScript("StartsWithScript"));*/
Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetString("ConnectingToEmulator"));
if (!Instances.SettingsViewModel.AdbReplaced && !Instances.SettingsViewModel.IsAdbTouchMode())
{
Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetString("AdbReplacementTips"), UiLogColor.Info);
}
// 一般是点了“停止”按钮了
if (Instances.TaskQueueViewModel.Stopping)
{
Instances.TaskQueueViewModel.SetStopped();
return;
}
if (!await InvokeInstanceAsyncFunction<bool>(Instances.TaskQueueViewModel, "ConnectToEmulator"))
{
return;
}
// 一般是点了“停止”按钮了
if (Instances.TaskQueueViewModel.Stopping)
{
Instances.TaskQueueViewModel.SetStopped();
return;
}
bool taskRet = true;
// 直接遍历TaskItemViewModels里面的内容是排序后的
int count = 0;
foreach (var item in originalNames)
{
++count;
switch (item)
{
case "Base":
taskRet &= InvokeInstanceFunction<bool>(Instances.TaskQueueViewModel, "AppendInfrast");
break;
case "WakeUp":
taskRet &= InvokeStaticFunction<bool>(Instances.TaskQueueViewModel.GetType(), "AppendStart");
break;
case "Combat":
taskRet &= InvokeInstanceFunction<bool>(Instances.TaskQueueViewModel, "AppendFight");
break;
case "Recruiting":
taskRet &= InvokeStaticFunction<bool>(Instances.TaskQueueViewModel.GetType(), "AppendRecruit");
break;
case "Mall":
taskRet &= InvokeInstanceFunction<bool>(Instances.TaskQueueViewModel, "AppendMall");
break;
case "Mission":
taskRet &= InvokeInstanceFunction<bool>(Instances.TaskQueueViewModel, "AppendAward");
break;
case "AutoRoguelike":
taskRet &= InvokeStaticFunction<bool>(Instances.TaskQueueViewModel.GetType(), "AppendRoguelike");
break;
case "ReclamationAlgorithm":
taskRet &= InvokeStaticFunction<bool>(Instances.TaskQueueViewModel.GetType(), "AppendReclamation");
break;
default:
--count;
// Instances.TaskQueueViewModel._logger.Error("Unknown task: " + item);
break;
}
if (taskRet)
{
continue;
}
Instances.TaskQueueViewModel.AddLog(item + "Error", UiLogColor.Error);
taskRet = true;
--count;
}
if (count == 0)
{
Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetString("UnselectedTask"));
_runningState.SetIdle(true);
Instances.TaskQueueViewModel.SetStopped();
return;
}
taskRet &= Instances.AsstProxy.AsstStart();
if (taskRet)
{
Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetString("Running"));
}
else
{
Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetString("UnknownErrorOccurs"));
await Instances.TaskQueueViewModel.Stop();
Instances.TaskQueueViewModel.SetStopped();
}
});
await _runningState.UntilIdleAsync();
}
public static async Task ConnectionTest()
{
var endpoint = Instances.SettingsViewModel.RemoteControlGetTaskEndpointUri;
var uid = Instances.SettingsViewModel.RemoteControlUserIdentity;
var did = Instances.SettingsViewModel.RemoteControlDeviceIdentity;
var response = await Instances.HttpService.PostAsJsonAsync(new Uri(endpoint), new { user = uid, device = did });
using var toast = new ToastNotification(
LocalizationHelper.GetString(
response != null ? "RemoteControlConnectionTestSuccess" : "RemoteControlConnectionTestFail"));
// if (response != null)
// {
// toast.AppendContentText(response);
// }
toast.Show();
}
public static void RegenerateDeviceIdentity()
{
Instances.SettingsViewModel.RemoteControlDeviceIdentity = Guid.NewGuid().ToString("N");
}
}
}

View File

@@ -36,6 +36,7 @@ using MaaWpfGui.Main;
using MaaWpfGui.Models;
using MaaWpfGui.Services.HotKeys;
using MaaWpfGui.Services.Notification;
using MaaWpfGui.Services.RemoteControl;
using MaaWpfGui.States;
using MaaWpfGui.Utilities;
using MaaWpfGui.Utilities.ValueType;
@@ -106,6 +107,7 @@ namespace MaaWpfGui.ViewModels.UI
_listTitle.Add(LocalizationHelper.GetString("ConnectionSettings"));
_listTitle.Add(LocalizationHelper.GetString("StartupSettings"));
_listTitle.Add(LocalizationHelper.GetString("UISettings"));
_listTitle.Add(LocalizationHelper.GetString("RemoteControlSettings"));
_listTitle.Add(LocalizationHelper.GetString("ExternalNotificationSettings"));
_listTitle.Add(LocalizationHelper.GetString("HotKeySettings"));
_listTitle.Add(LocalizationHelper.GetString("UpdateSettings"));
@@ -150,6 +152,69 @@ namespace MaaWpfGui.ViewModels.UI
}
}
#region Remote Control
public async void RemoteControlConnectionTest()
{
await RemoteControlService.ConnectionTest();
}
public void RemoteControlRegenerateDeviceIdentity()
{
RemoteControlService.RegenerateDeviceIdentity();
}
private string _remoteControlGetTaskEndpointUri = ConfigurationHelper.GetValue(ConfigurationKeys.RemoteControlGetTaskEndpointUri, string.Empty);
public string RemoteControlGetTaskEndpointUri
{
get => _remoteControlGetTaskEndpointUri;
set
{
SetAndNotify(ref _remoteControlGetTaskEndpointUri, value);
ConfigurationHelper.SetValue(ConfigurationKeys.RemoteControlGetTaskEndpointUri, value);
}
}
private string _remoteControlReportStatusUri = ConfigurationHelper.GetValue(ConfigurationKeys.RemoteControlReportStatusUri, string.Empty);
public string RemoteControlReportStatusUri
{
get => _remoteControlReportStatusUri;
set
{
SetAndNotify(ref _remoteControlReportStatusUri, value);
ConfigurationHelper.SetValue(ConfigurationKeys.RemoteControlReportStatusUri, value);
}
}
private string _remoteControlUserIdentity = ConfigurationHelper.GetValue(ConfigurationKeys.RemoteControlUserIdentity, string.Empty);
public string RemoteControlUserIdentity
{
get => _remoteControlUserIdentity;
set
{
SetAndNotify(ref _remoteControlUserIdentity, value);
ConfigurationHelper.SetValue(ConfigurationKeys.RemoteControlUserIdentity, value);
}
}
private string _remoteControlDeviceIdentity = ConfigurationHelper.GetValue(ConfigurationKeys.RemoteControlDeviceIdentity, string.Empty);
public string RemoteControlDeviceIdentity
{
get => _remoteControlDeviceIdentity;
set
{
SetAndNotify(ref _remoteControlDeviceIdentity, value);
ConfigurationHelper.SetValue(ConfigurationKeys.RemoteControlDeviceIdentity, value);
}
}
#endregion
#region External Notifications
public void ExternalNotificationSendTest()
@@ -406,6 +471,14 @@ namespace MaaWpfGui.ViewModels.UI
new CombinedData { Display = LocalizationHelper.GetString("RoguelikeThemeSami"), Value = "Sami" },
};
RemoteControlList = new List<CombinedData>
{
new CombinedData { Display = LocalizationHelper.GetString("RemoteControlHandshakeUri"), Value = "Phantom" },
new CombinedData { Display = LocalizationHelper.GetString("RemoteControlEndpointUri"), Value = "Mizuki" },
new CombinedData { Display = LocalizationHelper.GetString("RemoteControlUserIdentity"), Value = "Sami" },
new CombinedData { Display = LocalizationHelper.GetString("RemoteControlUserIdentity"), Value = "Sami" },
};
UpdateRoguelikeSquadList();
UpdateRoguelikeCoreCharList();
@@ -1206,6 +1279,11 @@ namespace MaaWpfGui.ViewModels.UI
/// </summary>
public List<CombinedData> TouchModeList { get; set; }
/// <summary>
/// Gets or sets the list of touch modes
/// </summary>
public List<CombinedData> RemoteControlList { get; set; }
/// <summary>
/// Gets or sets the list of dark mode.
/// </summary>

View File

@@ -69,15 +69,18 @@
<userControl:GUISettingsUserControl Margin="0,20" HorizontalAlignment="Center" />
<hc:Divider Content="{Binding ListTitle[6]}" />
<userControl:ExternalNotificationSettingsUserControl Margin="0,20" HorizontalAlignment="Center" />
<userControl:RemoteControlUserControl Margin="0,20" HorizontalAlignment="Center" />
<hc:Divider Content="{Binding ListTitle[7]}" />
<userControl:HotKeySettingsUserControl Margin="0,20" HorizontalAlignment="Center" />
<userControl:ExternalNotificationSettingsUserControl Margin="0,20" HorizontalAlignment="Center" />
<hc:Divider Content="{Binding ListTitle[8]}" />
<userControl:VersionUpdateSettingsUserControl Margin="0,20" HorizontalAlignment="Center" />
<userControl:HotKeySettingsUserControl Margin="0,20" HorizontalAlignment="Center" />
<hc:Divider Content="{Binding ListTitle[9]}" />
<userControl:VersionUpdateSettingsUserControl Margin="0,20" HorizontalAlignment="Center" />
<hc:Divider Content="{Binding ListTitle[10]}" />
<userControl:AboutUserControl Margin="0,20" HorizontalAlignment="Center" />
<!--<Rectangle HorizontalAlignment="Stretch" Fill="{DynamicResource BorderBrush}" Height="1" />-->
</StackPanel>

View File

@@ -0,0 +1,98 @@
<UserControl
x:Class="MaaWpfGui.Views.UserControl.RemoteControlUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:MaaWpfGui.ViewModels.UI"
xmlns:controls="clr-namespace:MaaWpfGui.Styles.Controls"
xmlns:calcBinding="clr-namespace:CalcBinding;assembly=CalcBinding"
xmlns:s="https://github.com/canton7/Stylet"
d:DataContext="{d:DesignInstance {x:Type ui:SettingsViewModel}}"
d:DesignHeight="300"
d:DesignWidth="550"
mc:Ignorable="d">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" MinWidth="250"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Control Endpoint Input -->
<controls:TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="10"
Block.TextAlignment="Center"
Text="{DynamicResource RemoteControlGetTaskEndpointLabel}"
TextWrapping="Wrap" />
<TextBox
Grid.Row="0"
Grid.Column="1"
Margin="0,5,0,5"
Text="{Binding RemoteControlGetTaskEndpointUri}" Grid.ColumnSpan="2"/>
<!-- Handshake Endpoint Input -->
<controls:TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="10"
Block.TextAlignment="Center"
Text="{DynamicResource RemoteControlReportStatusEndpointLabel}"
TextWrapping="Wrap" />
<TextBox
Grid.Row="1"
Grid.Column="1"
Margin="0,5,0,5"
Text="{Binding RemoteControlReportStatusUri}" Grid.ColumnSpan="2"/>
<!-- User Identity Input -->
<controls:TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="10"
Block.TextAlignment="Center"
Text="{DynamicResource RemoteControlUserIdentityLabel}"
TextWrapping="Wrap" />
<TextBox
Grid.Row="2"
Grid.Column="1"
Margin="0,5,0,5"
Text="{Binding RemoteControlUserIdentity}"/>
<Button
Grid.Row="2"
Grid.Column="2"
Margin="5"
Command="{s:Action RemoteControlConnectionTest}"
Content="{DynamicResource RemoteControlConnectionTest}"/>
<!-- Remote ID (Read-only & Copyable) -->
<controls:TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="10"
Block.TextAlignment="Center"
Text="{DynamicResource RemoteControlDeviceIdentityLabel}"
TextWrapping="Wrap" />
<TextBox
Grid.Row="3"
Grid.Column="1"
Margin="0,5,0,5"
Text="{Binding RemoteControlDeviceIdentity}"
IsReadOnly="True"
ContextMenuService.IsEnabled="False"/>
<Button
Grid.Row="3"
Grid.Column="2"
Margin="5"
Command="{s:Action RemoteControlRegenerateDeviceIdentity}"
Content="{DynamicResource RemoteControlRegenerateDeviceIdentity}"/>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace MaaWpfGui.Views.UserControl
{
/// <summary>
/// RemoteControlUserControl.xaml 的交互逻辑
/// </summary>
public partial class RemoteControlUserControl
{
public RemoteControlUserControl()
{
InitializeComponent();
}
}
}