// Copyright (c) 2016 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #include "shell/browser/lib/bluetooth_chooser.h" #include "shell/common/gin_converters/callback_converter.h" #include "shell/common/gin_helper/dictionary.h" namespace gin { template <> struct Converter { static v8::Local ToV8( v8::Isolate* isolate, const electron::BluetoothChooser::DeviceInfo& val) { auto dict = gin_helper::Dictionary::CreateEmpty(isolate); dict.Set("deviceName", val.device_name); dict.Set("deviceId", val.device_id); return gin::ConvertToV8(isolate, dict); } }; } // namespace gin namespace electron { namespace { void OnDeviceChosen(const content::BluetoothChooser::EventHandler& handler, const std::string& device_id) { if (device_id.empty()) { handler.Run(content::BluetoothChooserEvent::CANCELLED, device_id); } else { handler.Run(content::BluetoothChooserEvent::SELECTED, device_id); } } } // namespace BluetoothChooser::BluetoothChooser(api::WebContents* contents, const EventHandler& event_handler) : api_web_contents_(contents), event_handler_(event_handler) {} BluetoothChooser::~BluetoothChooser() { event_handler_.Reset(); } void BluetoothChooser::SetAdapterPresence(AdapterPresence presence) { switch (presence) { case AdapterPresence::ABSENT: case AdapterPresence::POWERED_OFF: // Chrome currently directs the user to system preferences // to grant bluetooth permission for this case, should we // do something similar ? // https://chromium-review.googlesource.com/c/chromium/src/+/2617129 case AdapterPresence::UNAUTHORIZED: event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, ""); break; case AdapterPresence::POWERED_ON: rescan_ = true; break; } } void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) { bool idle_state = false; switch (state) { case DiscoveryState::FAILED_TO_START: refreshing_ = false; event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, ""); return; case DiscoveryState::IDLE: refreshing_ = false; idle_state = true; break; case DiscoveryState::DISCOVERING: // The first time this state fires is due to a rescan triggering so set a // flag to ignore devices if (rescan_ && !refreshing_) { refreshing_ = true; } else { // The second time this state fires we are now safe to pick a device refreshing_ = false; } break; } bool prevent_default = api_web_contents_->Emit("select-bluetooth-device", GetDeviceList(), base::BindOnce(&OnDeviceChosen, event_handler_)); if (!prevent_default && idle_state) { if (device_map_.empty()) { event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, ""); } else { auto it = device_map_.begin(); auto device_id = it->first; event_handler_.Run(content::BluetoothChooserEvent::SELECTED, device_id); } } } void BluetoothChooser::AddOrUpdateDevice(const std::string& device_id, bool should_update_name, const std::u16string& device_name, bool is_gatt_connected, bool is_paired, int signal_strength_level) { if (refreshing_) { // If the list of bluetooth devices is currently being generated don't fire // an event return; } auto [iter, changed] = device_map_.try_emplace(device_id, device_name); if (!changed && should_update_name) { iter->second = device_name; changed = true; } if (changed) { // Emit a select-bluetooth-device handler to allow for user to listen for // bluetooth device found. bool prevent_default = api_web_contents_->Emit( "select-bluetooth-device", GetDeviceList(), base::BindOnce(&OnDeviceChosen, event_handler_)); // If emit not implemented select first device that matches the filters // provided. if (!prevent_default) { event_handler_.Run(content::BluetoothChooserEvent::SELECTED, device_id); } } } std::vector BluetoothChooser::GetDeviceList() { std::vector vec; vec.reserve(device_map_.size()); for (const auto& [device_id, device_name] : device_map_) vec.emplace_back(device_id, device_name); return vec; } } // namespace electron