diff --git a/lib/utils/media_key_handler.dart b/lib/utils/media_key_handler.dart index 8e7ea10..326f239 100644 --- a/lib/utils/media_key_handler.dart +++ b/lib/utils/media_key_handler.dart @@ -25,7 +25,7 @@ class MediaKeyHandler { _smtc?.disableSmtc(); } else { mediaKeyDetector.setIsPlaying(isPlaying: false); - mediaKeyDetector.removeListener(_onMediaKeyDetectedListener); + mediaKeyDetector.removeListenerWithDevice(_onMediaKeyDetectedListenerWithDevice); } } else { FlutterVolumeController.addListener( @@ -80,7 +80,7 @@ class MediaKeyHandler { ); _smtc!.buttonPressStream.listen(_onMediaKeyPressedListener); } else { - mediaKeyDetector.addListener(_onMediaKeyDetectedListener); + mediaKeyDetector.addListenerWithDevice(_onMediaKeyDetectedListenerWithDevice); mediaKeyDetector.setIsPlaying(isPlaying: true); } } @@ -88,7 +88,11 @@ class MediaKeyHandler { } bool _onMediaKeyDetectedListener(MediaKey mediaKey) { - final hidDevice = HidDevice('HID Device'); + return _onMediaKeyDetectedListenerWithDevice(mediaKey, 'HID Device'); + } + + bool _onMediaKeyDetectedListenerWithDevice(MediaKey mediaKey, String deviceId) { + final hidDevice = HidDevice(deviceId); var availableDevice = core.connection.controllerDevices.firstOrNullWhere( (e) => e.toString() == hidDevice.toString(), diff --git a/media_key_detector/media_key_detector/lib/src/media_key_detector.dart b/media_key_detector/media_key_detector/lib/src/media_key_detector.dart index eb48472..265b5d8 100644 --- a/media_key_detector/media_key_detector/lib/src/media_key_detector.dart +++ b/media_key_detector/media_key_detector/lib/src/media_key_detector.dart @@ -21,12 +21,24 @@ class MediaKeyDetector { _platform.addListener(listener); } + /// Listen for the media key event with device information + void addListenerWithDevice(void Function(MediaKey mediaKey, String deviceId) listener) { + _lazilyInitialize(); + _platform.addListenerWithDevice(listener); + } + /// Remove the previously registered listener void removeListener(void Function(MediaKey mediaKey) listener) { _lazilyInitialize(); _platform.removeListener(listener); } + /// Remove the previously registered listener with device information + void removeListenerWithDevice(void Function(MediaKey mediaKey, String deviceId) listener) { + _lazilyInitialize(); + _platform.removeListenerWithDevice(listener); + } + void _lazilyInitialize() { if (!_initialized) { _platform.initialize(); diff --git a/media_key_detector/media_key_detector_platform_interface/lib/media_key_detector_platform_interface.dart b/media_key_detector/media_key_detector_platform_interface/lib/media_key_detector_platform_interface.dart index a6d5572..b97acba 100644 --- a/media_key_detector/media_key_detector_platform_interface/lib/media_key_detector_platform_interface.dart +++ b/media_key_detector/media_key_detector_platform_interface/lib/media_key_detector_platform_interface.dart @@ -50,6 +50,7 @@ abstract class MediaKeyDetectorPlatform extends PlatformInterface { void initialize(); final List _listeners = []; + final List _listenersWithDevice = []; /// Listen for the media key event void addListener(void Function(MediaKey mediaKey) listener) { @@ -58,16 +59,33 @@ abstract class MediaKeyDetectorPlatform extends PlatformInterface { } } + /// Listen for the media key event with device information + void addListenerWithDevice(void Function(MediaKey mediaKey, String deviceId) listener) { + if (!_listenersWithDevice.contains(listener)) { + _listenersWithDevice.add(listener); + } + } + /// Remove the previously registered listener void removeListener(void Function(MediaKey mediaKey) listener) { _listeners.remove(listener); } + /// Remove the previously registered listener with device information + void removeListenerWithDevice(void Function(MediaKey mediaKey, String deviceId) listener) { + _listenersWithDevice.remove(listener); + } + /// Trigger all listeners to indicate that the specified media key was pressed - void triggerListeners(MediaKey mediaKey) { + void triggerListeners(MediaKey mediaKey, [String? deviceId]) { for (final l in _listeners) { l(mediaKey); } + if (deviceId != null) { + for (final l in _listenersWithDevice) { + l(mediaKey, deviceId); + } + } } final Map _keyMap = { diff --git a/media_key_detector/media_key_detector_platform_interface/lib/src/exports.dart b/media_key_detector/media_key_detector_platform_interface/lib/src/exports.dart index 4ce730a..17207ab 100644 --- a/media_key_detector/media_key_detector_platform_interface/lib/src/exports.dart +++ b/media_key_detector/media_key_detector_platform_interface/lib/src/exports.dart @@ -1,3 +1,4 @@ export './media_key.dart' show MediaKey; +export './media_key_event.dart' show MediaKeyEvent; export './method_channel_media_key_detector.dart' show MethodChannelMediaKeyDetector; diff --git a/media_key_detector/media_key_detector_platform_interface/lib/src/media_key_event.dart b/media_key_detector/media_key_detector_platform_interface/lib/src/media_key_event.dart new file mode 100644 index 0000000..861faa5 --- /dev/null +++ b/media_key_detector/media_key_detector_platform_interface/lib/src/media_key_event.dart @@ -0,0 +1,28 @@ +/// Represents a media key event with device information +class MediaKeyEvent { + /// Creates a media key event + const MediaKeyEvent({ + required this.key, + required this.deviceId, + }); + + /// The media key that was pressed + final String key; + + /// The unique identifier of the device that sent the event + final String deviceId; + + @override + String toString() => 'MediaKeyEvent(key: $key, deviceId: $deviceId)'; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is MediaKeyEvent && + runtimeType == other.runtimeType && + key == key && + deviceId == deviceId; + + @override + int get hashCode => key.hashCode ^ deviceId.hashCode; +} diff --git a/media_key_detector/media_key_detector_windows/lib/media_key_detector_windows.dart b/media_key_detector/media_key_detector_windows/lib/media_key_detector_windows.dart index e856823..2e8111d 100644 --- a/media_key_detector/media_key_detector_windows/lib/media_key_detector_windows.dart +++ b/media_key_detector/media_key_detector_windows/lib/media_key_detector_windows.dart @@ -19,13 +19,26 @@ class MediaKeyDetectorWindows extends MediaKeyDetectorPlatform { @override void initialize() { _eventChannel.receiveBroadcastStream().listen((event) { - final keyIdx = event as int; MediaKey? key; - if (keyIdx > -1 && keyIdx < MediaKey.values.length) { - key = MediaKey.values[keyIdx]; + String? deviceId; + + // Check if event is a map (new format with device info) + if (event is Map) { + final keyIdx = event['key'] as int?; + deviceId = event['device'] as String?; + + if (keyIdx != null && keyIdx > -1 && keyIdx < MediaKey.values.length) { + key = MediaKey.values[keyIdx]; + } + } else if (event is int) { + // Backward compatibility: old format with just key index + if (event > -1 && event < MediaKey.values.length) { + key = MediaKey.values[event]; + } } + if (key != null) { - triggerListeners(key); + triggerListeners(key, deviceId); } }); } diff --git a/media_key_detector/media_key_detector_windows/windows/media_key_detector_windows_plugin.cpp b/media_key_detector/media_key_detector_windows/windows/media_key_detector_windows_plugin.cpp index a1e9938..d3f225c 100644 --- a/media_key_detector/media_key_detector_windows/windows/media_key_detector_windows_plugin.cpp +++ b/media_key_detector/media_key_detector_windows/windows/media_key_detector_windows_plugin.cpp @@ -12,6 +12,10 @@ #include #include #include +#include +#include +#include +#include namespace { @@ -44,6 +48,15 @@ class MediaKeyDetectorWindows : public flutter::Plugin { // Unregister global hotkeys void UnregisterHotkeys(); + // Register for raw input from keyboard devices + void RegisterRawInput(HWND hwnd); + + // Unregister raw input + void UnregisterRawInput(HWND hwnd); + + // Get device identifier from device handle + std::string GetDeviceIdentifier(HANDLE hDevice); + // Handle Windows messages std::optional HandleWindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); @@ -52,6 +65,10 @@ class MediaKeyDetectorWindows : public flutter::Plugin { std::atomic is_playing_{false}; int window_proc_id_ = -1; bool hotkeys_registered_ = false; + bool raw_input_registered_ = false; + + // Cache for device identifiers + std::map device_cache_; }; // static @@ -104,6 +121,8 @@ MediaKeyDetectorWindows::MediaKeyDetectorWindows(flutter::PluginRegistrarWindows } MediaKeyDetectorWindows::~MediaKeyDetectorWindows() { + HWND hwnd = registrar_->GetView()->GetNativeWindow(); + UnregisterRawInput(hwnd); UnregisterHotkeys(); if (window_proc_id_ != -1) { registrar_->UnregisterTopLevelWindowProcDelegate(window_proc_id_); @@ -124,10 +143,13 @@ void MediaKeyDetectorWindows::HandleMethodCall( if (is_playing_it != arguments->end()) { if (auto* is_playing = std::get_if(&is_playing_it->second)) { is_playing_.store(*is_playing); + HWND hwnd = registrar_->GetView()->GetNativeWindow(); if (*is_playing) { RegisterHotkeys(); + RegisterRawInput(hwnd); } else { UnregisterHotkeys(); + UnregisterRawInput(hwnd); } result->Success(); return; @@ -185,8 +207,130 @@ void MediaKeyDetectorWindows::UnregisterHotkeys() { hotkeys_registered_ = false; } +void MediaKeyDetectorWindows::RegisterRawInput(HWND hwnd) { + if (raw_input_registered_) { + return; + } + + // Register for raw input from keyboard devices + RAWINPUTDEVICE rid[1]; + + // Keyboard devices + rid[0].usUsagePage = 0x01; // Generic Desktop Controls + rid[0].usUsage = 0x06; // Keyboard + rid[0].dwFlags = RIDEV_INPUTSINK; // Receive input even when not in foreground + rid[0].hwndTarget = hwnd; + + if (RegisterRawInputDevices(rid, 1, sizeof(rid[0]))) { + raw_input_registered_ = true; + } +} + +void MediaKeyDetectorWindows::UnregisterRawInput(HWND hwnd) { + if (!raw_input_registered_) { + return; + } + + // Unregister raw input + RAWINPUTDEVICE rid[1]; + + rid[0].usUsagePage = 0x01; + rid[0].usUsage = 0x06; + rid[0].dwFlags = RIDEV_REMOVE; + rid[0].hwndTarget = nullptr; + + RegisterRawInputDevices(rid, 1, sizeof(rid[0])); + raw_input_registered_ = false; + device_cache_.clear(); +} + +std::string MediaKeyDetectorWindows::GetDeviceIdentifier(HANDLE hDevice) { + // Check cache first + auto it = device_cache_.find(hDevice); + if (it != device_cache_.end()) { + return it->second; + } + + // Get device name + UINT size = 0; + GetRawInputDeviceInfoA(hDevice, RIDI_DEVICENAME, nullptr, &size); + + if (size == 0) { + return "Unknown Device"; + } + + std::vector name(size); + if (GetRawInputDeviceInfoA(hDevice, RIDI_DEVICENAME, name.data(), &size) == static_cast(-1)) { + return "Unknown Device"; + } + + std::string deviceName(name.data()); + + // Cache the result + device_cache_[hDevice] = deviceName; + + return deviceName; +} + std::optional MediaKeyDetectorWindows::HandleWindowProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { + + // Handle raw input messages for device-specific detection + if (message == WM_INPUT && event_sink_) { + UINT dwSize; + GetRawInputData((HRAWINPUT)lparam, RID_INPUT, nullptr, &dwSize, sizeof(RAWINPUTHEADER)); + + std::vector buffer(dwSize); + if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, buffer.data(), &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) { + return std::nullopt; + } + + RAWINPUT* raw = (RAWINPUT*)buffer.data(); + + if (raw->header.dwType == RIM_TYPEKEYBOARD) { + RAWKEYBOARD& keyboard = raw->data.keyboard; + + // Check for media keys + int key_index = -1; + + // Media keys have VKey codes + if (keyboard.Flags == RI_KEY_MAKE || keyboard.Flags == 0) { // Key down event + switch (keyboard.VKey) { + case VK_MEDIA_PLAY_PAUSE: + key_index = 0; // MediaKey.playPause + break; + case VK_MEDIA_PREV_TRACK: + key_index = 1; // MediaKey.rewind + break; + case VK_MEDIA_NEXT_TRACK: + key_index = 2; // MediaKey.fastForward + break; + case VK_VOLUME_UP: + key_index = 3; // MediaKey.volumeUp + break; + case VK_VOLUME_DOWN: + key_index = 4; // MediaKey.volumeDown + break; + } + + if (key_index >= 0) { + // Get device identifier + std::string deviceId = GetDeviceIdentifier(raw->header.hDevice); + + // Send event with both key index and device identifier + flutter::EncodableMap event_data; + event_data[EncodableValue("key")] = EncodableValue(key_index); + event_data[EncodableValue("device")] = EncodableValue(deviceId); + + event_sink_->Success(EncodableValue(event_data)); + + return 0; + } + } + } + } + + // Fallback to hotkey messages (for compatibility) if (message == WM_HOTKEY && event_sink_) { int key_index = -1; @@ -210,7 +354,12 @@ std::optional MediaKeyDetectorWindows::HandleWindowProc( } if (key_index >= 0) { - event_sink_->Success(EncodableValue(key_index)); + // Send event with key index only (no device info for hotkey) + flutter::EncodableMap event_data; + event_data[EncodableValue("key")] = EncodableValue(key_index); + event_data[EncodableValue("device")] = EncodableValue("HID Device"); + + event_sink_->Success(EncodableValue(event_data)); } return 0;