clipshare_clipboard_listener 1.0.4 copy "clipshare_clipboard_listener: ^1.0.4" to clipboard
clipshare_clipboard_listener: ^1.0.4 copied to clipboard

Supports clipboard monitoring on Windows、Android、Linux platforms, with support for text and images. For Android 10+ devices, background clipboard monitoring is supported (requires Shizuku).

example/lib/main.dart

import 'dart:io';
import 'dart:math';

import 'package:clipshare_clipboard_listener/clipboard_manager.dart';
import 'package:clipshare_clipboard_listener/enums.dart';
import 'package:clipshare_clipboard_listener/models/clipboard_source.dart';
import 'package:clipshare_clipboard_listener/models/notification_content_config.dart';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
import 'package:permission_handler/permission_handler.dart';

void main(List<String> args) {
  var isMultiWindow = args.firstOrNull == 'multi_window';
  if (isMultiWindow) {
    runApp(MultiWindow());
  } else {
    runApp(const MyApp());
  }
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with ClipboardListener, WidgetsBindingObserver {
  String? type;
  String? content;
  ClipboardSource? source;
  EnvironmentType env = EnvironmentType.none;
  bool isGranted = false;
  ClipboardListeningWay way = ClipboardListeningWay.logs;
  bool hasAlertWindowPermission = false;
  bool hasNotificationPermission = false;
  bool hasAccessibilityPermission = false;

  @override
  void initState() {
    super.initState();
    clipboardManager.addListener(this);
    WidgetsBinding.instance.addObserver(this);
    initHotKey();
    initMultiWindowEvent();
    if (Platform.isAndroid) {
      clipboardManager.getCurrentEnvironment().then((env) {
        setState(() {
          this.env = env;
          this.isGranted = env != EnvironmentType.none;
        });
      });
      checkAndroidPermissions();
    }
  }

  Future<void> checkAndroidPermissions() async {
    hasAlertWindowPermission = await Permission.systemAlertWindow.isGranted;
    hasNotificationPermission = await Permission.notification.isGranted;
    hasAccessibilityPermission = await clipboardManager.checkAccessibility();
    setState(() {});
  }

  @override
  void dispose() {
    super.dispose();
    clipboardManager.removeListener(this);
    WidgetsBinding.instance.removeObserver(this);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Example'),
        ),
        body: Builder(builder: (context) {
          return SingleChildScrollView(
            scrollDirection: Axis.vertical,
            child: Column(
              children: [
                const SizedBox(
                  height: 10,
                ),
                //region Android
                Visibility(
                  visible: Platform.isAndroid,
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text('Current environment: ${env.name}, Status: $isGranted'),
                      Padding(
                        padding: const EdgeInsets.only(left: 10, right: 10),
                        child: Row(
                          children: [
                            GestureDetector(
                              onTap: () {
                                clipboardManager.requestPermission(EnvironmentType.shizuku);
                              },
                              child: const Chip(label: Text("Request Shizuka")),
                            ),
                            const SizedBox(
                              width: 10,
                            ),
                            GestureDetector(
                              onTap: () {
                                clipboardManager.requestPermission(EnvironmentType.root);
                              },
                              child: const Chip(label: Text("Request Root")),
                            ),
                            const SizedBox(
                              width: 10,
                            ),
                          ],
                        ),
                      ),
                      const Text('Permissions:'),
                      Padding(
                        padding: const EdgeInsets.only(left: 10, right: 10),
                        child: Wrap(
                          children: [
                            RawChip(
                              label: const Text("Alert Window"),
                              selected: hasAlertWindowPermission,
                              onSelected: (_) async {
                                if (hasAlertWindowPermission) {
                                  return;
                                }
                                final result = await Permission.systemAlertWindow.request();
                                print("result isDenied = ${result.isDenied}");
                                if (result.isGranted) {
                                  setState(() {
                                    hasAlertWindowPermission = result.isGranted;
                                  });
                                  showSnackBarSuc(context, "SystemAlertWindow granted");
                                } else {
                                  showSnackBarErr(context, "SystemAlertWindow denied");
                                }
                              },
                            ),
                            const SizedBox(width: 10),
                            RawChip(
                              label: const Text("Notification"),
                              selected: hasNotificationPermission,
                              onSelected: (_) async {
                                if (hasNotificationPermission) {
                                  return;
                                }
                                final result = await Permission.notification.request();
                                if (result.isGranted) {
                                  setState(() {
                                    hasNotificationPermission = result.isGranted;
                                  });
                                  showSnackBarSuc(context, "Notification granted");
                                } else {
                                  showSnackBarErr(context, "Notification denied");
                                }
                              },
                            ),
                            const SizedBox(width: 10),
                            RawChip(
                              label: const Text("Accessibility"),
                              selected: hasAccessibilityPermission,
                              onSelected: (_) async {
                                if (hasAccessibilityPermission) {
                                  return;
                                }
                                clipboardManager.requestAccessibility();
                              },
                            ),
                          ],
                        ),
                      ),
                      Container(
                        margin: const EdgeInsets.symmetric(vertical: 5),
                        child: Text("Options:"),
                      ),
                      Padding(
                        padding: const EdgeInsets.only(left: 10, right: 10),
                        child: IntrinsicHeight(
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              GestureDetector(
                                onTap: env == EnvironmentType.shizuku
                                    ? () {
                                        startListeningOnAndroid(
                                          context,
                                          env: EnvironmentType.shizuku,
                                          way: way,
                                        );
                                      }
                                    : null,
                                child: Chip(
                                  label: Text(
                                    "Start listening by Shizuku",
                                    style: env == EnvironmentType.shizuku ? null : const TextStyle(color: Colors.grey),
                                  ),
                                ),
                              ),
                              GestureDetector(
                                onTap: env == EnvironmentType.root
                                    ? () {
                                        startListeningOnAndroid(
                                          context,
                                          env: EnvironmentType.root,
                                          way: way,
                                        );
                                      }
                                    : null,
                                child: Chip(
                                  label: Text(
                                    "Start listening by Root",
                                    style: env == EnvironmentType.root ? null : const TextStyle(color: Colors.grey),
                                  ),
                                ),
                              ),
                              GestureDetector(
                                onTap: () {
                                  clipboardManager.stopListening();
                                  showSnackBarSuc(
                                    context,
                                    "Listening stopped",
                                  );
                                },
                                child: const Chip(label: Text("Stop listening")),
                              ),
                            ],
                          ),
                        ),
                      ),
                      Container(
                        margin: const EdgeInsets.symmetric(vertical: 5),
                        child: Text("listening way: ${way.name}"),
                      ),
                      Padding(
                        padding: const EdgeInsets.only(left: 10, right: 10),
                        child: Row(
                          children: [
                            RawChip(
                              label: const Text("Hidden API"),
                              selected: way == ClipboardListeningWay.hiddenApi,
                              onSelected: (_) async {
                                setState(() {
                                  way = ClipboardListeningWay.hiddenApi;
                                });
                                await clipboardManager.stopListening();
                                Future.delayed(const Duration(seconds: 1), () {
                                  startListeningOnAndroid(
                                    context,
                                    env: env,
                                    way: ClipboardListeningWay.hiddenApi,
                                  );
                                });
                              },
                            ),
                            const SizedBox(width: 10),
                            RawChip(
                              label: const Text("System Logs"),
                              selected: way == ClipboardListeningWay.logs,
                              onSelected: (_) async {
                                setState(() {
                                  way = ClipboardListeningWay.logs;
                                });
                                await clipboardManager.stopListening();
                                Future.delayed(const Duration(seconds: 1), () {
                                  startListeningOnAndroid(
                                    context,
                                    env: env,
                                    way: ClipboardListeningWay.logs,
                                  );
                                });
                              },
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
                //endregion

                //region Linux
                Visibility(
                  visible: !Platform.isAndroid,
                  child: Column(
                    children: [
                      GestureDetector(
                        onTap: () {
                          clipboardManager.startListening().then((res) {
                            if (res) {
                              showSnackBarSuc(
                                context,
                                "Listening started successfully",
                              );
                            } else {
                              showSnackBarErr(
                                context,
                                "Listening failed to start",
                              );
                            }
                          });
                        },
                        child: const Chip(label: Text("Start listening")),
                      ),
                      GestureDetector(
                        onTap: () {
                          clipboardManager.stopListening().then((res) {
                            showSnackBarSuc(
                              context,
                              "Listening stopped successfully",
                            );
                          }).catchError((err) {
                            showSnackBarErr(
                              context,
                              "Listening failed to stop",
                            );
                          });
                        },
                        child: const Chip(label: Text("Stop listening")),
                      ),
                    ],
                  ),
                ),
                //endregion
                Text('type: $type\n\ncontent:\n$content\n\nsource:${source?.name}\n\n'),
                const SizedBox(height: 10),
                if (source?.iconBytes != null)
                  Image.memory(
                    source!.iconBytes!,
                    height: 30,
                    width: 30,
                  ),
                const SizedBox(height: 10),
                const TextField(),
                const SizedBox(
                  height: 10,
                ),
                GestureDetector(
                  onTap: () {
                    clipboardManager.copy(ClipboardContentType.text, Random().nextInt(99999).toString());
                  },
                  child: const Chip(label: Text("Copy Random Data")),
                ),
                GestureDetector(
                  onTap: () {
                    clipboardManager.copy(ClipboardContentType.image, "/tmp/2025-01-16_22-29-42-6.png");
                  },
                  child: const Chip(label: Text("Copy Test Image(mannal set on code)")),
                ),
              ],
            ),
          );
        }),
      ),
    );
  }

  @override
  void onClipboardChanged(ClipboardContentType type, String content, ClipboardSource? source) {
    print("type: ${type.name}, content: $content");
    setState(() {
      this.type = type.name;
      this.content = content;
    });
    setState(() {
      this.source = source;
    });
    var start = DateTime.now();
    clipboardManager.getLatestWriteClipboardSource().then((source) {
      if (source == null) {
        return;
      }
      final isTimeout = source.isTimeout(2000);
      print("source time: ${source.time?.toString()}, timeout: $isTimeout");
      var end = DateTime.now();
      print("source: ${source.name}, offset: ${end.difference(start).inMilliseconds}");
      if (isTimeout) {
        return;
      }
      setState(() {
        this.source = source;
      });
    }).catchError((err) {
      print("error: $err");
    });
  }

  @override
  void onPermissionStatusChanged(EnvironmentType environment, bool isGranted) {
    debugPrint("env: ${environment.name}, granted: $isGranted");
    setState(() {
      env = environment;
      this.isGranted = isGranted;
    });
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        if (Platform.isAndroid) {
          checkAndroidPermissions();
        }
        break;
      default:
    }
  }

  Future startListeningOnAndroid(
    BuildContext context, {
    NotificationContentConfig? notificationContentConfig,
    EnvironmentType? env,
    ClipboardListeningWay? way,
  }) {
    if (!Platform.isAndroid) return Future.value();
    //Android version>= 10
    if (!hasAlertWindowPermission) {
      showSnackBarErr(context, "No Alert Window permission");
      return Future.value();
    }
    //Android version>= 10
    if (!hasNotificationPermission) {
      showSnackBarErr(context, "No Notification permission");
      return Future.value();
    }
    return clipboardManager
        .startListening(
      env: env,
      way: way,
      notificationContentConfig: notificationContentConfig,
    )
        .then((res) {
      if (res) {
        showSnackBarSuc(
          context,
          "Listening started successfully",
        );
      } else {
        showSnackBarErr(
          context,
          "Listening failed to start",
        );
      }
    });
  }

  void showSnackBar(BuildContext context, String text, Color color) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(text),
        backgroundColor: color,
      ),
    );
  }

  void showSnackBarSuc(BuildContext context, String text) {
    showSnackBar(context, text, Colors.lightBlue);
  }

  void showSnackBarErr(BuildContext context, String text) {
    showSnackBar(context, text, Colors.redAccent);
  }

  Future<void> initHotKey() async {
    await hotKeyManager.unregisterAll();
    final key = HotKey(
      key: PhysicalKeyboardKey.keyG,
      modifiers: [HotKeyModifier.control, HotKeyModifier.alt],
      scope: HotKeyScope.system,
    );
    await hotKeyManager.register(
      key,
      keyDownHandler: (hotKey) async {
        if (Platform.isWindows) {
          await clipboardManager.storeCurrentWindowHwnd();
        }
        //createWindow里面的参数必须传
        final window = await DesktopMultiWindow.createWindow('{}');
        window
          ..setFrame(const Offset(500, 500) & const Size(355.0, 630.0))
          ..setTitle('Window')
          ..show();
      },
    );
  }

  void initMultiWindowEvent() {
    DesktopMultiWindow.setMethodHandler((
      MethodCall call,
      int fromWindowId,
    ) {
      Clipboard.setData(ClipboardData(text: DateTime.now().toString()));
      clipboardManager.pasteToPreviousWindow();
      return Future.value();
    });
  }
}

class MultiWindow extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text("MultiWindow"),
        ),
        body: Column(
          children: [
            TextButton(
                onPressed: () {
                  DesktopMultiWindow.invokeMethod(
                    0,
                    "methodName",
                    "{}",
                  );
                },
                child: const Text("click me"))
          ],
        ),
      ),
    );
  }
}
1
likes
140
points
44
downloads

Publisher

unverified uploader

Weekly Downloads

Supports clipboard monitoring on Windows、Android、Linux platforms, with support for text and images. For Android 10+ devices, background clipboard monitoring is supported (requires Shizuku).

Homepage
Repository (GitHub)

Documentation

API reference

License

MIT (license)

Dependencies

flutter, path, plugin_platform_interface

More

Packages that depend on clipshare_clipboard_listener