MXU 特殊任务是通过 MaaFramework 的 Custom Action 机制实现的内置功能任务(如延迟/睡眠、启动程序),不依赖项目的 pipeline 资源。
现有示例:
MXU_SLEEP—— 倒计时等待指定秒数(单选项 input 示例)MXU_WAITUNTIL—— 等待到指定时间点(time 类型 input 示例)MXU_LAUNCH—— 启动外部程序(多选项 input + switch 示例)
用户操作 → 前端添加任务 → 生成 pipeline_override → MaaFramework 执行
↓
Rust custom action 回调
涉及修改的文件(按顺序):
| 步骤 | 文件 | 职责 |
|---|---|---|
| 1 | src-tauri/src/mxu_actions.rs |
实现 Rust 回调并注册 |
| 2 | src/types/specialTasks.ts |
注册任务/选项定义到 MXU_SPECIAL_TASKS |
| 3 | src/i18n/locales/*.ts |
添加所有语言的翻译文本 |
| 4 | src/utils/pipelineOverride.ts |
无需修改(自动复用 collectOptionOverrides) |
| 5 | src/stores/appStore.ts |
无需修改(通用 addMxuSpecialTask 自动处理) |
| 6 | UI 组件 | 无需修改(TaskItem/AddTaskPanel/OptionEditor 自动适配) |
在 src-tauri/src/mxu_actions.rs 中添加:
const MXU_EXAMPLE_ACTION: &str = "MXU_EXAMPLE_ACTION";命名规则:MXU_<NAME>_ACTION,与前端 MXU_SPECIAL_TASKS 中 custom_action 字段一致。
extern "C" fn mxu_example_action(
_context: *mut MaaContext,
_task_id: MaaId,
_current_task_name: *const c_char,
_custom_action_name: *const c_char,
custom_action_param: *const c_char,
_reco_id: MaaId,
_box_rect: *const MaaRect,
_trans_arg: *mut c_void,
) -> MaaBool {
let result = std::panic::catch_unwind(|| {
let param_str = if custom_action_param.is_null() {
warn!("[MXU_EXAMPLE] param is null");
"{}".to_string()
} else {
unsafe { from_cstr(custom_action_param) }
};
info!("[MXU_EXAMPLE] param: {}", param_str);
let json: serde_json::Value = serde_json::from_str(¶m_str).unwrap_or_default();
// 从 json 中读取参数并执行逻辑
// ...
1u8 // 返回 1 表示成功,0 表示失败
});
match result {
Ok(ret) => ret,
Err(e) => {
log::error!("[MXU_EXAMPLE] Panic: {:?}", e);
0
}
}
}
pub fn get_mxu_example_action() -> MaaCustomActionCallback {
Some(mxu_example_action)
}关键要求:
- 签名必须与
MaaCustomActionCallback完全匹配(8 个参数 +MaaBool返回值) - 必须用
std::panic::catch_unwind包裹,防止 panic 跨 FFI 边界 custom_action_param是 JSON 字符串,由前端pipeline_override中的custom_action_param字段传入
pub fn register_all_mxu_actions(lib: &MaaLibrary, resource: *mut MaaResource) -> Result<(), String> {
// 已有的 MXU_SLEEP 注册...
// 注册新动作
let action_name = to_cstring(MXU_EXAMPLE_ACTION);
let result = unsafe {
(lib.maa_resource_register_custom_action)(
resource,
action_name.as_ptr(),
get_mxu_example_action(),
std::ptr::null_mut(),
)
};
if result != 0 {
info!("[MXU] Custom action {} registered", MXU_EXAMPLE_ACTION);
} else {
warn!("[MXU] Failed to register {}", MXU_EXAMPLE_ACTION);
}
Ok(())
}在 src/types/specialTasks.ts 的 MXU_SPECIAL_TASKS 注册表中添加条目。
export const MXU_EXAMPLE_TASK_NAME = '__MXU_EXAMPLE__';
export const MXU_EXAMPLE_ENTRY = 'MXU_EXAMPLE';
export const MXU_EXAMPLE_ACTION = 'MXU_EXAMPLE_ACTION'; // 必须与 Rust 端一致const MXU_EXAMPLE_TASK_DEF: TaskItem = {
name: MXU_EXAMPLE_TASK_NAME,
label: 'specialTask.example.label', // i18n key
entry: MXU_EXAMPLE_ENTRY,
option: ['__MXU_EXAMPLE_OPTION__'],
pipeline_override: {
[MXU_EXAMPLE_ENTRY]: {
action: 'Custom',
custom_action: MXU_EXAMPLE_ACTION,
},
},
};说明:
entry节点不需要预先存在于 pipeline 中,pipeline_override会创建它- MaaFramework 默认
recognition: DirectHit(直接命中,无需截图识别),适用于纯逻辑任务 label使用 i18n key,会被t()翻译
根据需要选择选项类型:
input 类型(用户输入值):
const MXU_EXAMPLE_OPTION: InputOption = {
type: 'input',
label: 'specialTask.example.optionLabel',
inputs: [
{
name: 'param_name',
label: 'specialTask.example.inputLabel',
default: '默认值',
pipeline_type: 'int', // 'string' | 'int' | 'bool'
verify: '^[1-9]\\d*$', // 可选,输入校验正则
pattern_msg: 'specialTask.example.inputError', // 校验失败提示 i18n key
// MXU 扩展字段(可选):
input_type: 'file', // 'text'(默认) | 'file'(渲染文件选择器)
placeholder: 'specialTask.example.placeholder', // 占位提示文本 i18n key
},
],
pipeline_override: {
[MXU_EXAMPLE_ENTRY]: {
custom_action_param: {
param_name: '{param_name}', // 占位符,运行时替换为用户输入值
},
},
},
};MXU 扩展字段说明(InputItem 上的可选字段,不影响 PI V2 协议):
| 字段 | 类型 | 说明 |
|---|---|---|
input_type |
'text' | 'file' | 'time' |
控制 UI 渲染:'file' 渲染带浏览按钮的文件选择器;'time' 渲染 HH:MM 时间选择器 |
placeholder |
string |
输入框占位提示文本的 i18n key,通过 t() 翻译。若未设置则 fallback 到 default |
switch 类型(开关):
const MXU_EXAMPLE_SWITCH_OPTION: SwitchOption = {
type: 'switch',
label: 'specialTask.example.switchLabel',
description: 'specialTask.example.switchDescription', // 可选,显示在开关下方的说明文本
cases: [
{
name: 'Yes', // 开启时的 case,name 必须是 Yes/yes/Y/y 之一
label: 'specialTask.example.switchYes',
pipeline_override: {
[MXU_EXAMPLE_ENTRY]: {
custom_action_param: { some_flag: true },
},
},
},
{
name: 'No', // 关闭时的 case,name 必须是 No/no/N/n 之一
label: 'specialTask.example.switchNo',
pipeline_override: {
[MXU_EXAMPLE_ENTRY]: {
custom_action_param: { some_flag: false },
},
},
},
],
default_case: 'No', // 默认状态
};select 类型(下拉选择):也完全支持,参考 ProjectInterface V2 协议的 option 定义。
pipeline_type 占位符替换规则:
'string':"{param}"→"用户输入"'int':"{param}"→123(去掉引号,变为 JSON 数字)'bool':"{param}"→true/false(去掉引号,变为 JSON 布尔值)
一个任务可以引用多个选项(在 taskDef.option 数组中列出),每个选项独立贡献 pipeline_override。
关键限制:MaaFramework 对同一节点的同名字段执行完整替换而非深合并。例如,若两个 override 都设置了
custom_action_param,后者会完全覆盖前者,导致先前设置的参数丢失。
generateMxuSpecialTaskOverride 已在前端处理此问题:它会将所有 override 对象进行深合并后,作为单元素数组发送给 MaaFramework。因此,多个选项可以安全地各自设置 custom_action_param 的不同字段:
// 任务定义引用两个选项
option: ['__MXU_EXAMPLE_OPTION__', '__MXU_EXAMPLE_SWITCH_OPTION__'],
// Input 选项的 pipeline_override 贡献:
{ MXU_EXAMPLE: { custom_action_param: { program: "xxx", args: "yyy" } } }
// Switch 选项的 pipeline_override 贡献:
{ MXU_EXAMPLE: { custom_action_param: { wait_for_exit: true } } }
// 前端深合并后发送给 MaaFramework 的实际内容(单元素数组):
[{ MXU_EXAMPLE: { action: "Custom", custom_action: "...", custom_action_param: { program: "xxx", args: "yyy", wait_for_exit: true } } }]注意:
- 此深合并仅在 MXU 特殊任务中生效(
generateMxuSpecialTaskOverride),普通 PI V2 任务仍按原始数组格式发送。 - 深合并仅对对象类型递归合并;若两个 override 在同一路径下设置了非对象值(如字符串、数组),后者会覆盖前者。
- 普通 PI V2 任务的选项设计需自行避免对同一字段的重复设置,因为 MaaFramework 不会深合并。
export const MXU_SPECIAL_TASKS: Record<string, MxuSpecialTaskDefinition> = {
[MXU_SLEEP_TASK_NAME]: { /* 已有 */ },
[MXU_EXAMPLE_TASK_NAME]: {
taskName: MXU_EXAMPLE_TASK_NAME,
entry: MXU_EXAMPLE_ENTRY,
taskDef: MXU_EXAMPLE_TASK_DEF,
optionDefs: {
__MXU_EXAMPLE_OPTION__: MXU_EXAMPLE_OPTION,
},
iconName: 'Zap', // lucide-react 图标名
iconColorClass: 'text-accent/80', // Tailwind CSS 颜色类
},
};iconName 可选值:'Clock' | 'Zap' | 'Bell' | 'Timer' | 'Pause' | 'Play'(见 MxuSpecialTaskDefinition 类型定义,如需新增图标需同步修改类型和 AddTaskPanel.tsx 中的 ICON_MAP)。
在 所有 语言文件(src/i18n/locales/)的 specialTask 对象中添加:
specialTask: {
sleep: { /* 已有 */ },
example: {
label: '任务显示名称',
optionLabel: '选项组标题',
inputLabel: '输入框标签',
inputError: '输入校验失败提示',
},
},必须同步更新的文件:zh-CN.ts, en-US.ts, zh-TW.ts, ja-JP.ts, ko-KR.ts。
| 项目 | 格式 | 示例 |
|---|---|---|
| 任务名称常量 | __MXU_<NAME>__ |
__MXU_SLEEP__ |
| 入口节点 | MXU_<NAME> |
MXU_SLEEP |
| 动作名称 | MXU_<NAME>_ACTION |
MXU_SLEEP_ACTION |
| 选项 key | __MXU_<NAME>_<用途>_OPTION__ |
__MXU_LAUNCH_WAIT_OPTION__ |
| i18n 前缀 | specialTask.<name>. |
specialTask.sleep.label |
选项 key 命名说明:选项 key 只需以 __MXU_ 开头且全局唯一即可,不要求与任务名严格对应。系统通过 findMxuOptionByKey() 遍历所有注册的特殊任务来反查选项定义,因此命名可以灵活描述用途,例如 __MXU_LAUNCH_OPTION__(程序设置)和 __MXU_LAUNCH_WAIT_OPTION__(等待设置)。
以下逻辑已通用化,注册到 MXU_SPECIAL_TASKS 后自动生效:
- Pipeline Override 生成:
generateMxuSpecialTaskOverride复用collectOptionOverrides,自动处理所有选项类型(input / switch / select 及嵌套选项) - 任务添加/删除:
addMxuSpecialTask根据注册表初始化选项值 - UI 渲染:
TaskItem/AddTaskPanel/OptionEditor检测__MXU_前缀自动适配 - 选项定义查找:
OptionEditor通过findMxuOptionByKey()遍历所有特殊任务反查选项定义,无需选项 key 与任务名严格对应 - i18n 翻译:选项的
label、description、placeholder、pattern_msg等字段均通过t()翻译 - 配置持久化:
appStore中的配置恢复逻辑通过isMxuSpecialTask正确保留选项值 - 任务校验:配置加载时通过
MXU_SPECIAL_TASKSkey 集合判断任务有效性