@@ -385,7 +385,96 @@ result = self.request(some_query_command, timeout=5.0)
385385| ` NewGameCommand ` | 开始新游戏 | ` rows ` , ` cols ` , ` mines ` |
386386| ` MouseClickCommand ` | 模拟鼠标点击 | ` row ` , ` col ` , ` button ` , ` modifiers ` |
387387
388- ### 5.4 线程安全的 GUI 更新(重要!)
388+ ### 5.4 控制授权系统(重要!)
389+
390+ 为了防止多个插件同时发送冲突的控制指令,系统实现了** 控制授权机制** :
391+
392+ - 每个** 控制命令类型** 只能授权给** 一个插件**
393+ - 未获得授权的插件发送该命令会被拒绝
394+ - 授权变更时会通知相关插件
395+
396+ #### 声明需要的控制权限
397+
398+ 在 ` PluginInfo ` 中通过 ` required_controls ` 字段声明:
399+
400+ ``` python
401+ from shared_types.commands import NewGameCommand, MouseClickCommand
402+
403+ class MyPlugin (BasePlugin ):
404+
405+ @ classmethod
406+ def plugin_info (cls ) -> PluginInfo:
407+ return PluginInfo(
408+ name = " my_plugin" ,
409+ description = " 需要控制权限的插件" ,
410+ required_controls = [NewGameCommand], # 👈 声明需要的控制权限
411+ )
412+ ```
413+
414+ #### 检查和响应授权状态
415+
416+ ``` python
417+ class MyPlugin (BasePlugin ):
418+
419+ def on_initialized (self ) -> None :
420+ # 检查当前是否有权限
421+ has_auth = self .has_control_auth(NewGameCommand)
422+ self .logger.info(f " NewGameCommand 权限: { has_auth} " )
423+
424+ # 更新 UI 状态
425+ self .run_on_gui(self ._update_ui_auth, has_auth)
426+
427+ def on_control_auth_changed (
428+ self ,
429+ command_type : type ,
430+ granted : bool ,
431+ ) -> None :
432+ """
433+ 控制权限变更回调
434+
435+ Args:
436+ command_type: 命令类型
437+ granted: True 表示获得权限,False 表示失去权限
438+ """
439+ if command_type == NewGameCommand:
440+ if granted:
441+ self .logger.info(" 获得了 NewGameCommand 控制权限" )
442+ else :
443+ self .logger.warning(" 失去了 NewGameCommand 控制权限" )
444+ # 停止正在进行的操作
445+ self ._stop_auto_play()
446+
447+ # 更新 UI
448+ self .run_on_gui(self ._update_ui_auth, granted)
449+
450+ def _on_button_click (self ) -> None :
451+ # 发送前可以检查权限(不检查也行,无权限时 send_command 会自动拒绝)
452+ if self .has_control_auth(NewGameCommand):
453+ self .send_command(NewGameCommand(rows = 16 , cols = 30 , mines = 99 ))
454+ else :
455+ self .logger.warning(" 没有 NewGameCommand 权限" )
456+ ```
457+
458+ #### 控制授权相关方法
459+
460+ | 方法 | 说明 |
461+ | ------| ------|
462+ | ` has_control_auth(command_type) ` | 检查是否有该控制类型的权限 |
463+ | ` on_control_auth_changed(cmd_type, granted) ` | 权限变更回调(覆写) |
464+ | ` PluginInfo.required_controls ` | 声明需要的控制权限 |
465+
466+ #### 用户授权操作
467+
468+ 用户通过插件管理器工具栏的 ** "🔐 控制授权"** 按钮管理授权:
469+
470+ 1 . 点击按钮打开授权对话框
471+ 2 . 选择要授权的控制类型
472+ 3 . 从下拉列表中选择插件(只显示声明了该控制权限的插件)
473+ 4 . 确认后生效
474+
475+ 授权配置会持久化到 ` data/control_authorization.json ` 。
476+
477+ ### 5.5 线程安全的 GUI 更新(重要!)
389478
390479> ** 为什么需要跨线程机制?**
391480>
@@ -1295,6 +1384,7 @@ def on_initialized(self):
12951384from plugin_sdk import BasePlugin, PluginInfo, make_plugin_icon, WindowMode
12961385from plugin_sdk import OtherInfoBase, BoolConfig, IntConfig # 配置类型(可选)
12971386from shared_types.events import VideoSaveEvent # 按需导入
1387+ from shared_types.commands import NewGameCommand # 控制命令(可选)
12981388from plugins.services.my_service import MyService # 服务接口(可选)
12991389
13001390# ═══ 配置类定义(可选) ═══
@@ -1312,6 +1402,7 @@ class MyPlugin(BasePlugin):
13121402 window_mode = WindowMode.TAB , # TAB / DETACHED / CLOSED
13131403 icon = make_plugin_icon(" #1976D2" , " M" ),
13141404 other_info = MyConfig, # 👈 绑定配置类(可选)
1405+ required_controls = [NewGameCommand], # 👈 声明控制权限(可选)
13151406 )
13161407
13171408 def _setup_subscriptions (self ) -> None :
@@ -1336,11 +1427,19 @@ class MyPlugin(BasePlugin):
13361427 # 访问配置值(可选)
13371428 # if self.other_info:
13381429 # max_count = self.other_info.max_count
1430+
1431+ # 检查控制权限(可选)
1432+ # has_auth = self.has_control_auth(NewGameCommand)
13391433
13401434 def on_shutdown (self ): # 可选:资源清理
13411435 pass
13421436
1437+ def on_control_auth_changed (self , cmd_type : type , granted : bool ):
1438+ """ 控制权限变更回调(可选覆写)"""
1439+ pass
1440+
13431441 def _handle_event (self , event ):
13441442 self .logger.info(f " 收到事件: { event} " ) # 用 logger 不用 print
13451443 # self.run_on_gui(gui_func, *args) # GUI 更新走这
1444+ # self.send_command(NewGameCommand(...)) # 发送控制命令
13461445```
0 commit comments