diff --git a/packages/hotkey_manager/pubspec.yaml b/packages/hotkey_manager/pubspec.yaml index 172c04e..dabe6ac 100644 --- a/packages/hotkey_manager/pubspec.yaml +++ b/packages/hotkey_manager/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: collection: ^1.17.1 flutter: sdk: flutter - # hotkey_manager_linux: ^0.2.0 + hotkey_manager_linux: ^0.2.0 hotkey_manager_macos: ^0.2.0 hotkey_manager_platform_interface: ^0.2.0 hotkey_manager_windows: ^0.2.0 @@ -37,8 +37,8 @@ dev_dependencies: flutter: plugin: platforms: - # linux: - # default_package: hotkey_manager_linux + linux: + default_package: hotkey_manager_linux macos: default_package: hotkey_manager_macos windows: diff --git a/packages/hotkey_manager_linux/.gitignore b/packages/hotkey_manager_linux/.gitignore new file mode 100644 index 0000000..ac5aa98 --- /dev/null +++ b/packages/hotkey_manager_linux/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/packages/hotkey_manager_linux/.metadata b/packages/hotkey_manager_linux/.metadata new file mode 100644 index 0000000..30b4ab3 --- /dev/null +++ b/packages/hotkey_manager_linux/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "ef1af02aead6fe2414f3aafa5a61087b610e1332" + channel: "stable" + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 + base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 + - platform: linux + create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 + base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/hotkey_manager_linux/CHANGELOG.md b/packages/hotkey_manager_linux/CHANGELOG.md new file mode 100644 index 0000000..2f145ce --- /dev/null +++ b/packages/hotkey_manager_linux/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.2.0 + +* First release. diff --git a/packages/hotkey_manager_linux/LICENSE b/packages/hotkey_manager_linux/LICENSE new file mode 100644 index 0000000..eea05ab --- /dev/null +++ b/packages/hotkey_manager_linux/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022-2024 LiJianying + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/hotkey_manager_linux/README.md b/packages/hotkey_manager_linux/README.md new file mode 100644 index 0000000..0a32d2c --- /dev/null +++ b/packages/hotkey_manager_linux/README.md @@ -0,0 +1,12 @@ +# hotkey_manager_linux + +[![pub version][pub-image]][pub-url] + +[pub-image]: https://img.shields.io/pub/v/hotkey_manager_linux.svg +[pub-url]: https://pub.dev/packages/hotkey_manager_linux + +The Linux implementation of [hotkey_manager](https://pub.dev/packages/hotkey_manager). + +## License + +[MIT](./LICENSE) diff --git a/packages/hotkey_manager_linux/analysis_options.yaml b/packages/hotkey_manager_linux/analysis_options.yaml new file mode 100644 index 0000000..095b1d6 --- /dev/null +++ b/packages/hotkey_manager_linux/analysis_options.yaml @@ -0,0 +1 @@ +include: package:mostly_reasonable_lints/flutter.yaml diff --git a/packages/hotkey_manager_linux/linux/CMakeLists.txt b/packages/hotkey_manager_linux/linux/CMakeLists.txt new file mode 100644 index 0000000..9edffd0 --- /dev/null +++ b/packages/hotkey_manager_linux/linux/CMakeLists.txt @@ -0,0 +1,106 @@ +# The Flutter tooling requires that developers have CMake 3.10 or later +# installed. You should not increase this version, as doing so will cause +# the plugin to fail to compile for some customers of the plugin. +cmake_minimum_required(VERSION 3.10) + +# Project-level configuration. +set(PROJECT_NAME "hotkey_manager_linux") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed. +set(PLUGIN_NAME "hotkey_manager_linux_plugin") + +# Any new source files that you add to the plugin should be added here. +list(APPEND PLUGIN_SOURCES + "hotkey_manager_linux_plugin.cc" +) + +# Define the plugin library target. Its name must not be changed (see comment +# on PLUGIN_NAME above). +add_library(${PLUGIN_NAME} SHARED + ${PLUGIN_SOURCES} +) + +# Apply a standard set of build settings that are configured in the +# application-level CMakeLists.txt. This can be removed for plugins that want +# full control over build settings. +apply_standard_settings(${PLUGIN_NAME}) + +# Symbols are hidden by default to reduce the chance of accidental conflicts +# between plugins. This should not be removed; any symbols that should be +# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) + +# Source include directories and library dependencies. Add any plugin-specific +# dependencies here. +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::KEYBINDER) + +pkg_check_modules(KEYBINDER IMPORTED_TARGET keybinder-3.0) +if(KEYBINDER_FOUND) + target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::KEYBINDER) +else() + message( + FATAL_ERROR + "\n" + "The `hotkey_manager` package requires keybinder-3.0. See https://github.com/leanflutter/hotkey_manager#linux-requirements" + ) +endif() + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(hotkey_manager_linux_bundled_libraries + "" + PARENT_SCOPE +) + +# === Tests === +# These unit tests can be run from a terminal after building the example. + +# Only enable test builds when building the example (which sets this variable) +# so that plugin clients aren't building the tests. +if (${include_${PROJECT_NAME}_tests}) +if(${CMAKE_VERSION} VERSION_LESS "3.11.0") +message("Unit tests require CMake 3.11.0 or later") +else() +set(TEST_RUNNER "${PROJECT_NAME}_test") +enable_testing() + +# Add the Google Test dependency. +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/release-1.11.0.zip +) +# Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +# Disable install commands for gtest so it doesn't end up in the bundle. +set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) + +FetchContent_MakeAvailable(googletest) + +# The plugin's exported API is not very useful for unit testing, so build the +# sources directly into the test binary rather than using the shared library. +add_executable(${TEST_RUNNER} + test/hotkey_manager_linux_plugin_test.cc + ${PLUGIN_SOURCES} +) +apply_standard_settings(${TEST_RUNNER}) +target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") +target_link_libraries(${TEST_RUNNER} PRIVATE flutter) +target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::GTK) +target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock) + +# Enable automatic test discovery. +include(GoogleTest) +gtest_discover_tests(${TEST_RUNNER}) + +endif() # CMake version check +endif() # include_${PROJECT_NAME}_tests \ No newline at end of file diff --git a/packages/hotkey_manager_linux/linux/hotkey_manager_linux_plugin.cc b/packages/hotkey_manager_linux/linux/hotkey_manager_linux_plugin.cc new file mode 100644 index 0000000..3c31763 --- /dev/null +++ b/packages/hotkey_manager_linux/linux/hotkey_manager_linux_plugin.cc @@ -0,0 +1,208 @@ +#include "include/hotkey_manager_linux/hotkey_manager_linux_plugin.h" + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +#include "hotkey_manager_linux_plugin_private.h" + +#define HOTKEY_MANAGER_LINUX_PLUGIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), hotkey_manager_linux_plugin_get_type(), \ + HotkeyManagerLinuxPlugin)) + +std::map hotkey_id_map; +FlEventChannel* event_channel; + +struct _HotkeyManagerLinuxPlugin { + GObject parent_instance; +}; + +G_DEFINE_TYPE(HotkeyManagerLinuxPlugin, + hotkey_manager_linux_plugin, + g_object_get_type()) + +void handle_key_down(const char* keystring, void* user_data) { + const char* identifier; + + std::string val = keystring; + auto result = std::find_if(hotkey_id_map.begin(), hotkey_id_map.end(), + [val](const auto& e) { return e.second == val; }); + + if (result != hotkey_id_map.end()) + identifier = result->first.c_str(); + + g_autoptr(FlValue) event_data = fl_value_new_map(); + fl_value_set_string_take(event_data, "identifier", + fl_value_new_string(identifier)); + + g_autoptr(FlValue) event = fl_value_new_map(); + fl_value_set_string_take(event, "type", fl_value_new_string("onKeyDown")); + fl_value_set_string_take(event, "data", event_data); + + fl_event_channel_send(event_channel, event, nullptr, nullptr); + + std::cout << keystring << std::endl; +} + +guint get_mods(const std::vector& modifiers) { + guint mods = 0; + for (int i = 0; i < modifiers.size(); i++) { + guint mod = 0; + if (modifiers[i] == "shiftModifier") + mod = GDK_SHIFT_MASK; + else if (modifiers[i] == "controlModifier") + mod = GDK_CONTROL_MASK; + else if (modifiers[i] == "altModifier") + mod = GDK_MOD1_MASK; + else if (modifiers[i] == "metaModifier") + mod = GDK_META_MASK; + mods = mods | mod; + } + return mods; +} + +static FlMethodResponse* hkm_register(_HotkeyManagerLinuxPlugin* self, + FlValue* args) { + FlValue* modifiers_value = fl_value_lookup_string(args, "modifiers"); + + const char* identifier = + fl_value_get_string(fl_value_lookup_string(args, "identifier")); + const int key_code = + fl_value_get_int(fl_value_lookup_string(args, "keyCode")); + std::vector modifiers; + for (gint i = 0; i < fl_value_get_length(modifiers_value); i++) { + std::string keyModifier = + fl_value_get_string(fl_value_get_list_value(modifiers_value, i)); + modifiers.push_back(keyModifier); + } + + const char* keystring = + gtk_accelerator_name(key_code, (GdkModifierType)get_mods(modifiers)); + + std::cout << keystring << std::endl; + + hotkey_id_map.insert( + std::pair(identifier, keystring)); + + keybinder_init(); + keybinder_bind(keystring, handle_key_down, NULL); + + return FL_METHOD_RESPONSE( + fl_method_success_response_new(fl_value_new_bool(true))); +} + +static FlMethodResponse* hkm_unregister(_HotkeyManagerLinuxPlugin* self, + FlValue* args) { + const char* identifier = + fl_value_get_string(fl_value_lookup_string(args, "identifier")); + const char* keystring; + + std::string val = identifier; + auto result = std::find_if(hotkey_id_map.begin(), hotkey_id_map.end(), + [val](const auto& e) { return e.first == val; }); + + if (result != hotkey_id_map.end()) + keystring = result->second.c_str(); + + keybinder_unbind(keystring, handle_key_down); + hotkey_id_map.erase(identifier); + + return FL_METHOD_RESPONSE( + fl_method_success_response_new(fl_value_new_bool(true))); +} + +static FlMethodResponse* hkm_unregister_all(_HotkeyManagerLinuxPlugin* self, + FlValue* args) { + for (std::map::iterator it = hotkey_id_map.begin(); + it != hotkey_id_map.end(); ++it) { + std::string identifier = it->first; + const char* keystring = it->second.c_str(); + keybinder_unbind(keystring, handle_key_down); + } + + hotkey_id_map.clear(); + + return FL_METHOD_RESPONSE( + fl_method_success_response_new(fl_value_new_bool(true))); +} + +// Called when a method call is received from Flutter. +static void hotkey_manager_linux_plugin_handle_method_call( + HotkeyManagerLinuxPlugin* self, + FlMethodCall* method_call) { + g_autoptr(FlMethodResponse) response = nullptr; + + const gchar* method = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + if (strcmp(method, "register") == 0) { + response = hkm_register(self, args); + } else if (strcmp(method, "unregister") == 0) { + response = hkm_unregister(self, args); + } else if (strcmp(method, "unregisterAll") == 0) { + response = hkm_unregister_all(self, args); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + } + + fl_method_call_respond(method_call, response, nullptr); +} + +FlMethodResponse* get_platform_version() { + struct utsname uname_data = {}; + uname(&uname_data); + g_autofree gchar* version = g_strdup_printf("Linux %s", uname_data.version); + g_autoptr(FlValue) result = fl_value_new_string(version); + return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); +} + +static void hotkey_manager_linux_plugin_dispose(GObject* object) { + g_clear_object(&event_channel); + G_OBJECT_CLASS(hotkey_manager_linux_plugin_parent_class)->dispose(object); +} + +static void hotkey_manager_linux_plugin_class_init( + HotkeyManagerLinuxPluginClass* klass) { + G_OBJECT_CLASS(klass)->dispose = hotkey_manager_linux_plugin_dispose; +} + +static void hotkey_manager_linux_plugin_init(HotkeyManagerLinuxPlugin* self) {} + +static void method_call_cb(FlMethodChannel* channel, + FlMethodCall* method_call, + gpointer user_data) { + HotkeyManagerLinuxPlugin* plugin = HOTKEY_MANAGER_LINUX_PLUGIN(user_data); + hotkey_manager_linux_plugin_handle_method_call(plugin, method_call); +} + +void hotkey_manager_linux_plugin_register_with_registrar( + FlPluginRegistrar* registrar) { + HotkeyManagerLinuxPlugin* plugin = HOTKEY_MANAGER_LINUX_PLUGIN( + g_object_new(hotkey_manager_linux_plugin_get_type(), nullptr)); + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(FlMethodChannel) channel = fl_method_channel_new( + fl_plugin_registrar_get_messenger(registrar), + "dev.leanflutter.plugins/hotkey_manager", FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler( + channel, method_call_cb, g_object_ref(plugin), g_object_unref); + + g_autoptr(FlStandardMethodCodec) event_codec = fl_standard_method_codec_new(); + event_channel = + fl_event_channel_new(fl_plugin_registrar_get_messenger(registrar), + "dev.leanflutter.plugins/hotkey_manager_event", + FL_METHOD_CODEC(event_codec)); + + g_object_unref(plugin); +} diff --git a/packages/hotkey_manager_linux/linux/hotkey_manager_linux_plugin_private.h b/packages/hotkey_manager_linux/linux/hotkey_manager_linux_plugin_private.h new file mode 100644 index 0000000..05131d3 --- /dev/null +++ b/packages/hotkey_manager_linux/linux/hotkey_manager_linux_plugin_private.h @@ -0,0 +1,10 @@ +#include + +#include "include/hotkey_manager_linux/hotkey_manager_linux_plugin.h" + +// This file exposes some plugin internals for unit testing. See +// https://github.com/flutter/flutter/issues/88724 for current limitations +// in the unit-testable API. + +// Handles the getPlatformVersion method call. +FlMethodResponse *get_platform_version(); diff --git a/packages/hotkey_manager_linux/linux/include/hotkey_manager_linux/hotkey_manager_linux_plugin.h b/packages/hotkey_manager_linux/linux/include/hotkey_manager_linux/hotkey_manager_linux_plugin.h new file mode 100644 index 0000000..d9e8206 --- /dev/null +++ b/packages/hotkey_manager_linux/linux/include/hotkey_manager_linux/hotkey_manager_linux_plugin.h @@ -0,0 +1,26 @@ +#ifndef FLUTTER_PLUGIN_HOTKEY_MANAGER_LINUX_PLUGIN_H_ +#define FLUTTER_PLUGIN_HOTKEY_MANAGER_LINUX_PLUGIN_H_ + +#include + +G_BEGIN_DECLS + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define FLUTTER_PLUGIN_EXPORT +#endif + +typedef struct _HotkeyManagerLinuxPlugin HotkeyManagerLinuxPlugin; +typedef struct { + GObjectClass parent_class; +} HotkeyManagerLinuxPluginClass; + +FLUTTER_PLUGIN_EXPORT GType hotkey_manager_linux_plugin_get_type(); + +FLUTTER_PLUGIN_EXPORT void hotkey_manager_linux_plugin_register_with_registrar( + FlPluginRegistrar* registrar); + +G_END_DECLS + +#endif // FLUTTER_PLUGIN_HOTKEY_MANAGER_LINUX_PLUGIN_H_ diff --git a/packages/hotkey_manager_linux/linux/test/hotkey_manager_linux_plugin_test.cc b/packages/hotkey_manager_linux/linux/test/hotkey_manager_linux_plugin_test.cc new file mode 100644 index 0000000..4b5f3fd --- /dev/null +++ b/packages/hotkey_manager_linux/linux/test/hotkey_manager_linux_plugin_test.cc @@ -0,0 +1,31 @@ +#include +#include +#include + +#include "include/hotkey_manager_linux/hotkey_manager_linux_plugin.h" +#include "hotkey_manager_linux_plugin_private.h" + +// This demonstrates a simple unit test of the C portion of this plugin's +// implementation. +// +// Once you have built the plugin's example app, you can run these tests +// from the command line. For instance, for a plugin called my_plugin +// built for x64 debug, run: +// $ build/linux/x64/debug/plugins/my_plugin/my_plugin_test + +namespace hotkey_manager_linux { +namespace test { + +TEST(HotkeyManagerLinuxPlugin, GetPlatformVersion) { + g_autoptr(FlMethodResponse) response = get_platform_version(); + ASSERT_NE(response, nullptr); + ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + FlValue* result = fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)); + ASSERT_EQ(fl_value_get_type(result), FL_VALUE_TYPE_STRING); + // The full string varies, so just validate that it has the right format. + EXPECT_THAT(fl_value_get_string(result), testing::StartsWith("Linux ")); +} + +} // namespace test +} // namespace hotkey_manager_linux diff --git a/packages/hotkey_manager_linux/pubspec.yaml b/packages/hotkey_manager_linux/pubspec.yaml new file mode 100644 index 0000000..1d99c23 --- /dev/null +++ b/packages/hotkey_manager_linux/pubspec.yaml @@ -0,0 +1,26 @@ +name: hotkey_manager_linux +description: Linux implementation of the hotkey_manager plugin. +version: 0.2.0 +repository: https://github.com/leanflutter/hotkey_manager/tree/main/packages/hotkey_manager_linux + +environment: + sdk: '>=3.0.0 <4.0.0' + flutter: '>=3.3.0' + +dependencies: + flutter: + sdk: flutter + hotkey_manager_platform_interface: ^0.2.0 + +dev_dependencies: + flutter_test: + sdk: flutter + mostly_reasonable_lints: ^0.1.1 + +flutter: + plugin: + implements: hotkey_manager + platforms: + linux: + pluginClass: HotkeyManagerLinuxPlugin +