Flutter键盘控制插件keyboard_invoker的使用

Flutter键盘控制插件keyboard_invoker的使用

keyboard_invoker 是一个 Flutter 插件,允许你录制按键并在主机操作系统上重新播放。

功能

  • 录制和重放按键:轻松录制一系列键盘按键,并在需要时重新播放。
  • 转换为MacroMap:将一系列 LogicalKeyboardKey 转换为 MacroMap,以便后续调用。

平台支持

Windows Linux MacOS
✔️ ✔️ ✔️

入门指南

要开始使用 keyboard_invoker,请按照以下步骤操作:

安装

要将 keyboard_invoker 添加到你的 Flutter 项目中,在命令提示符中打开你的项目文件夹并运行以下命令:

flutter pub add keyboard_invoker

在Linux上设置

要在Linux上开始使用,你需要安装 xdotool 并配置你的显示服务器以使用 X11

安装 xdotool

要安装 xdotool,打开终端并运行以下命令:

sudo apt-get install xdotool

配置显示服务器(适用于Ubuntu 22.04)

对于Ubuntu 22.04,你还需要调整显示服务器设置。为此,请执行以下命令:

sudo nano /etc/gdm3/custom.conf

找到 WaylandEnable=false 这一行,如果有必要则取消注释,保存更改,然后重启系统以使设置生效。

在MacOS上设置

在MacOS上设置更为简单,因为不需要安装任何额外软件。然而,当你尝试调用宏时,系统会提示你授予键盘输入监控权限。

请注意,此插件在MacOS上的调试模式下无法与Flutter应用一起工作。你需要构建一个发布版本才能使其正常运行,仅限于MacOS。

使用/示例

我们将使用 Provider 包来跟踪 KeyboardInvoker 的状态。

以下是一个简短示例:

// ChangeNotifierProvider 不需要作为应用的根组件
void main() {
  final _keyboardInvokerPlugin = KeyboardInvoker();

  runApp(
    ChangeNotifierProvider(
      create: (_) => _keyboardInvokerPlugin,
      child: const MyApp(),
    ),
  );
}

然后,你可以在构建方法中这样访问它:

[@override](/user/override)
Widget build(BuildContext context) {
  final keyboardInvokerPlugin = Provider.of<KeyboardInvoker>(context);
  ...
}

录制按键序列

ElevatedButton(
  onPressed: keyboardInvokerPlugin.isRecording
    ? () async {
      // 停止录制
      await keyboardInvokerPlugin.stopRecording();
    }
    : () async {
      // 开始录制
      await keyboardInvokerPlugin.startRecording();
    },
  child: keyboardInvokerPlugin.isRecording
    ? const Text("停止宏录制")
    : const Text("开始宏录制")
)

调用录制的按键序列

ElevatedButton(
  onPressed: () async {
    try {
      // 停止录制
      await keyboardInvokerPlugin.stopRecording();
      // 调用录制的宏
      await keyboardInvokerPlugin.invokeMacroList(
        keyboardInvokerPlugin.recordedKeys,
      );
    } catch (e) {
      String errorMessage = '';
      if (e is X11NotActiveInstalled) {
        errorMessage = e.message;
      } else if (e is XdotoolNotInstalled) {
        errorMessage = e.message;
      } else {
        errorMessage = '发生错误: ${e.toString()}';
      }
      // 使用捕获的上下文显示对话框
      showAboutDialog(
        context: context,
        children: [
          Text('调用宏时出错: $errorMessage'),
        ],
      );
    }
  },
  child: const Text("调用已录制的宏"),
)

调用 LogicalKeyList

// 用于在主机操作系统上调用的一系列 LogicalKeyboardKey
final List<LogicalKeyboardKey> keyboardKeyList = [
  LogicalKeyboardKey.shiftLeft,
  LogicalKeyboardKey.keyH,
  LogicalKeyboardKey.keyI,
];

ElevatedButton(
  onPressed: () async {
    try {
      // 停止录制
      await keyboardInvokerPlugin.stopRecording();
      // 将宏列表转换为映射列表
      List<Map<String, dynamic>> macroList =
          await keyboardInvokerPlugin
              .logicalKeyboardKeysToMacro(keyboardKeyList);
      // 调用录制的宏
      await keyboardInvokerPlugin.invokeMacroList(
        macroList,
      );
    } catch (e) {
      String errorMessage = '';
      if (e is X11NotActiveInstalled) {
        errorMessage = e.message;
      } else if (e is XdotoolNotInstalled) {
        errorMessage = e.message;
      } else {
        errorMessage = '发生错误: ${e.toString()}';
      }
      // 使用捕获的上下文显示对话框
      showAboutDialog(
        context: context,
        children: [
          Text('调用宏时出错: $errorMessage'),
        ],
      );
    }
  },
  child: const Text("调用测试宏"),
)

贡献

我们欢迎对 keyboard_invoker 的贡献。如果你遇到问题或有功能请求,请在GitHub上打开一个issue

许可证

该插件根据MIT许可证发布。


示例代码

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:keyboard_invoker/keyboard_invoker.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(
    const MyApp(),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Keyboard_invoker示例',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Keyboard_invoker示例'),
        ),
        // 使用provider
        body: ChangeNotifierProvider<KeyboardInvoker>(
          create: (context) {
            return KeyboardInvoker();
          },
          child: const KeyboardInvokerExample(),
        ),
      ),
    );
  }
}

class KeyboardInvokerExample extends StatefulWidget {
  const KeyboardInvokerExample({super.key});

  [@override](/user/override)
  State<KeyboardInvokerExample> createState() => _KeyboardInvokerExampleState();
}

class _KeyboardInvokerExampleState extends State<KeyboardInvokerExample> {
  late FocusNode _focusNode;
  final macroRecordingScrollController = ScrollController();

  [@override](/user/override)
  void initState() {
    super.initState();
    _focusNode = FocusNode();
  }

  [@override](/user/override)
  void dispose() {
    _focusNode.dispose();
    macroRecordingScrollController.dispose();
    super.dispose();
  }

  // 这是LogicalKeyboardKey列表
  List<LogicalKeyboardKey> keyboardKeyList = [
    LogicalKeyboardKey.shiftLeft,
    LogicalKeyboardKey.keyB,
    LogicalKeyboardKey.keyR,
    LogicalKeyboardKey.keyA,
    LogicalKeyboardKey.keyT,
    LogicalKeyboardKey.keyW,
    LogicalKeyboardKey.keyU,
    LogicalKeyboardKey.keyR,
    LogicalKeyboardKey.keyS,
    LogicalKeyboardKey.keyT,
    LogicalKeyboardKey.space,
    LogicalKeyboardKey.keyU,
    LogicalKeyboardKey.keyN,
    LogicalKeyboardKey.keyD,
    LogicalKeyboardKey.space,
    LogicalKeyboardKey.keyE,
    LogicalKeyboardKey.keyI,
    LogicalKeyboardKey.keyN,
    LogicalKeyboardKey.space,
    LogicalKeyboardKey.keyG,
    LogicalKeyboardKey.keyR,
    LogicalKeyboardKey.keyO,
    LogicalKeyboardKey.keyS,
    LogicalKeyboardKey.keyS,
    LogicalKeyboardKey.keyE,
    LogicalKeyboardKey.keyS,
    LogicalKeyboardKey.space,
    LogicalKeyboardKey.keyB,
    LogicalKeyboardKey.keyI,
    LogicalKeyboardKey.keyE,
    LogicalKeyboardKey.keyR,
  ];

  [@override](/user/override)
  Widget build(BuildContext context) {
    final keyboardInvokerPlugin = Provider.of<KeyboardInvoker>(context);

    return Padding(
      padding: const EdgeInsets.all(15),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              if (Platform.isLinux) ...[
                Text("是否为X11: ${keyboardInvokerPlugin.isX11}"),
                Text(
                    "是否安装了xdotool: ${keyboardInvokerPlugin.isXdotoolInstalled}"),
              ],
              Text("正在录制: ${keyboardInvokerPlugin.isRecording}"),
            ],
          ),
          TextField(
            focusNode: _focusNode,
            decoration: const InputDecoration(
              border: OutlineInputBorder(),
              labelText: '测试字段',
            ),
          ),
          Expanded(
            child: SingleChildScrollView(
              controller: macroRecordingScrollController,
              child: Column(
                children: keyboardInvokerPlugin.recordedKeys
                    .map((e) => Text(
                          "键: ${e["keyLabel"]} 代码: ${e["keyCode"]} 事件: ${e["event"]} 修饰符: ${e["modifiers"]}",
                          style: const TextStyle(fontSize: 20),
                        ))
                    .toList(),
              ),
            ),
          ),
          // 测试按钮
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              ElevatedButton(
                onPressed: keyboardInvokerPlugin.isRecording
                    ? () async {
                        // 停止录制
                        await keyboardInvokerPlugin.stopRecording();
                      }
                    : () async {
                        // 开始录制
                        await keyboardInvokerPlugin.startRecording();
                      },
                child: keyboardInvokerPlugin.isRecording
                    ? const Text("停止宏录制")
                    : const Text("开始宏录制"),
              ),
              ElevatedButton(
                onPressed: () {
                  // 清除录制的宏
                  keyboardInvokerPlugin.recordedKeys = [];
                },
                child: const Text("清除已录制的宏"),
              ),
              ElevatedButton(
                onPressed: () async {
                  // 焦点移到文本字段
                  _focusNode.requestFocus();

                  try {
                    // 停止录制
                    await keyboardInvokerPlugin.stopRecording();

                    // 调用录制的宏
                    await keyboardInvokerPlugin.invokeMacroList(
                      keyboardInvokerPlugin.recordedKeys,
                    );
                  } catch (e) {
                    String errorMessage = '';

                    if (e is X11NotActiveInstalled) {
                      errorMessage = e.message;
                    } else if (e is XdotoolNotInstalled) {
                      errorMessage = e.message;
                    } else {
                      errorMessage = '发生错误: ${e.toString()}';
                    }
                    // 使用捕获的上下文显示对话框
                    showAboutDialog(
                      context: context,
                      children: [
                        Text('调用宏时出错: $errorMessage'),
                      ],
                    );
                  }
                },
                child: const Text("调用已录制的宏"),
              ),
              ElevatedButton(
                onPressed: () async {
                  // 焦点移到文本字段
                  _focusNode.requestFocus();
                  try {
                    // 停止录制
                    await keyboardInvokerPlugin.stopRecording();

                    // 将宏列表转换为映射列表
                    List<Map<String, dynamic>> macroList =
                        await keyboardInvokerPlugin
                            .logicalKeyboardKeysToMacro(keyboardKeyList);

                    // 调用录制的宏
                    await keyboardInvokerPlugin.invokeMacroList(
                      macroList,
                    );
                  } catch (e) {
                    String errorMessage = '';

                    if (e is X11NotActiveInstalled) {
                      errorMessage = e.message;
                    } else if (e is XdotoolNotInstalled) {
                      errorMessage = e.message;
                    } else {
                      errorMessage = '发生错误: ${e.toString()}';
                    }
                    // 使用捕获的上下文显示对话框
                    showAboutDialog(
                      context: context,
                      children: [
                        Text('调用宏时出错: $errorMessage'),
                      ],
                    );
                  }
                },
                child: const Text("调用测试宏"),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

更多关于Flutter键盘控制插件keyboard_invoker的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter键盘控制插件keyboard_invoker的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


keyboard_invoker 是一个 Flutter 插件,用于在 Flutter 应用中实现键盘快捷键的功能。通过这个插件,你可以为你的应用添加快捷键支持,使用户能够通过键盘操作与应用进行交互。

安装插件

首先,你需要在 pubspec.yaml 文件中添加 keyboard_invoker 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  keyboard_invoker: ^0.0.1  # 请检查最新版本

然后运行 flutter pub get 来安装插件。

使用 keyboard_invoker

1. 导入插件

在你的 Dart 文件中导入 keyboard_invoker 插件:

import 'package:keyboard_invoker/keyboard_invoker.dart';

2. 添加快捷键

你可以使用 KeyboardInvoker 类来添加快捷键。以下是一个简单的示例,展示了如何在按下 Ctrl + S 时执行一个操作:

import 'package:flutter/material.dart';
import 'package:keyboard_invoker/keyboard_invoker.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: KeyboardInvoker(
        bindings: {
          LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyS): () {
            print('Ctrl + S pressed!');
          },
        },
        child: Scaffold(
          appBar: AppBar(
            title: Text('Keyboard Invoker Example'),
          ),
          body: Center(
            child: Text('Press Ctrl + S to see the effect.'),
          ),
        ),
      ),
    );
  }
}

3. 解释代码

  • KeyboardInvoker 是一个小部件,它包裹了你的应用内容,并监听键盘事件。
  • bindings 参数是一个 Map<LogicalKeySet, VoidCallback>,用于定义快捷键和执行的操作。
  • LogicalKeySet 用于表示一组按键组合。例如,LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyS) 表示 Ctrl + S

4. 处理更多的快捷键

你可以为不同的按键组合定义不同的操作。例如:

KeyboardInvoker(
  bindings: {
    LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyS): () {
      print('Ctrl + S pressed!');
    },
    LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.keyA): () {
      print('Alt + A pressed!');
    },
  },
  child: Scaffold(
    appBar: AppBar(
      title: Text('Keyboard Invoker Example'),
    ),
    body: Center(
      child: Text('Press Ctrl + S or Alt + A to see the effect.'),
    ),
  ),
)
回到顶部