2 * Copyright (C) 2016 Simon Fels <morphis@gravedo.de>
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 #pragma GCC diagnostic push
19 #pragma GCC diagnostic ignored "-Wswitch-default"
20 #include "anbox/platform/sdl/platform.h"
21 #include "anbox/input/device.h"
22 #include "anbox/input/manager.h"
23 #include "anbox/logger.h"
24 #include "anbox/platform/sdl/keycode_converter.h"
25 #include "anbox/platform/sdl/window.h"
26 #include "anbox/platform/sdl/audio_sink.h"
27 #include "anbox/wm/manager.h"
29 #include <boost/throw_exception.hpp>
32 #include <sys/types.h>
33 #pragma GCC diagnostic pop
39 const std::shared_ptr<input::Manager> &input_manager,
40 const Configuration &config)
41 : input_manager_(input_manager),
42 event_thread_running_(false),
45 // Don't block the screensaver from kicking in. It will be blocked
46 // by the desktop shell already and we don't have to do this again.
47 // If we would leave this enabled it will prevent systems from
48 // suspending correctly.
49 SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
51 #ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
52 // Don't disable compositing
53 // Available since SDL 2.0.8
54 SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
57 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS) < 0) {
58 const auto message = utils::string_format("Failed to initialize SDL: %s", SDL_GetError());
59 BOOST_THROW_EXCEPTION(std::runtime_error(message));
62 auto display_frame = graphics::Rect::Invalid;
63 if (config_.display_frame == graphics::Rect::Invalid) {
64 for (auto n = 0; n < SDL_GetNumVideoDisplays(); n++) {
66 if (SDL_GetDisplayBounds(n, &r) != 0) continue;
68 graphics::Rect frame{r.x, r.y, r.x + r.w, r.y + r.h};
70 if (display_frame == graphics::Rect::Invalid)
71 display_frame = frame;
73 display_frame.merge(frame);
76 if (display_frame == graphics::Rect::Invalid)
77 BOOST_THROW_EXCEPTION(
78 std::runtime_error("No valid display configuration found"));
80 display_frame = config_.display_frame;
81 window_size_immutable_ = true;
84 graphics::emugl::DisplayInfo::get()->set_resolution(display_frame.width(), display_frame.height());
85 display_frame_ = display_frame;
87 pointer_ = input_manager->create_device();
88 pointer_->set_name("anbox-pointer");
89 pointer_->set_driver_version(1);
90 pointer_->set_input_id({BUS_VIRTUAL, 2, 2, 2});
91 pointer_->set_physical_location("none");
92 pointer_->set_key_bit(BTN_MOUSE);
93 // NOTE: We don't use REL_X/REL_Y in reality but have to specify them here
94 // to allow InputFlinger to detect we're a cursor device.
95 pointer_->set_rel_bit(REL_X);
96 pointer_->set_rel_bit(REL_Y);
97 pointer_->set_rel_bit(REL_HWHEEL);
98 pointer_->set_rel_bit(REL_WHEEL);
99 pointer_->set_prop_bit(INPUT_PROP_POINTER);
101 keyboard_ = input_manager->create_device();
102 keyboard_->set_name("anbox-keyboard");
103 keyboard_->set_driver_version(1);
104 keyboard_->set_input_id({BUS_VIRTUAL, 3, 3, 3});
105 keyboard_->set_physical_location("none");
106 keyboard_->set_key_bit(BTN_MISC);
107 keyboard_->set_key_bit(KEY_OK);
109 touch_ = input_manager->create_device();
110 touch_->set_name("anbox-touch");
111 touch_->set_driver_version(1);
112 touch_->set_input_id({BUS_VIRTUAL, 4, 4, 4});
113 touch_->set_physical_location("none");
114 touch_->set_abs_bit(ABS_MT_SLOT);
115 touch_->set_abs_max(ABS_MT_SLOT, 10);
116 touch_->set_abs_bit(ABS_MT_TOUCH_MAJOR);
117 touch_->set_abs_max(ABS_MT_TOUCH_MAJOR, 127);
118 touch_->set_abs_bit(ABS_MT_TOUCH_MINOR);
119 touch_->set_abs_max(ABS_MT_TOUCH_MINOR, 127);
120 touch_->set_abs_bit(ABS_MT_POSITION_X);
121 touch_->set_abs_max(ABS_MT_POSITION_X, display_frame.width());
122 touch_->set_abs_bit(ABS_MT_POSITION_Y);
123 touch_->set_abs_max(ABS_MT_POSITION_Y, display_frame.height());
124 touch_->set_abs_bit(ABS_MT_TRACKING_ID);
125 touch_->set_abs_max(ABS_MT_TRACKING_ID, MAX_TRACKING_ID);
126 touch_->set_prop_bit(INPUT_PROP_DIRECT);
128 for (int i = 0; i < MAX_FINGERS; i++)
131 event_thread_ = std::thread(&Platform::process_events, this);
134 Platform::~Platform() {
135 if (event_thread_running_) {
136 event_thread_running_ = false;
137 event_thread_.join();
141 void Platform::set_renderer(const std::shared_ptr<Renderer> &renderer) {
142 renderer_ = renderer;
145 void Platform::set_window_manager(const std::shared_ptr<wm::Manager> &window_manager) {
146 window_manager_ = window_manager;
149 void Platform::process_events() {
150 event_thread_running_ = true;
152 while (event_thread_running_) {
154 while (SDL_WaitEventTimeout(&event, 100)) {
155 switch (event.type) {
158 case SDL_WINDOWEVENT:
159 for (auto &iter : windows_) {
160 if (auto w = iter.second.lock()) {
161 if (w->window_id() == event.window.windowID) {
162 w->process_event(event);
171 process_input_event(event);
173 case SDL_MOUSEMOTION:
174 case SDL_MOUSEBUTTONDOWN:
175 case SDL_MOUSEBUTTONUP:
179 case SDL_FINGERMOTION:
180 process_input_event(event);
189 void Platform::process_input_event(const SDL_Event &event) {
190 std::vector<input::Event> mouse_events;
191 std::vector<input::Event> keyboard_events;
192 std::vector<input::Event> touch_events;
197 switch (event.type) {
199 case SDL_MOUSEBUTTONDOWN:
200 if (config_.no_touch_emulation) {
201 mouse_events.push_back({EV_KEY, BTN_LEFT, 1});
205 if (!adjust_coordinates(x, y))
207 push_finger_down(x, y, emulated_touch_id_, touch_events);
210 case SDL_MOUSEBUTTONUP:
211 if (config_.no_touch_emulation) {
212 mouse_events.push_back({EV_KEY, BTN_LEFT, 0});
214 push_finger_up(emulated_touch_id_, touch_events);
217 case SDL_MOUSEMOTION:
220 if (!adjust_coordinates(x, y))
223 if (config_.no_touch_emulation) {
224 // NOTE: Sending relative move events doesn't really work and we have
225 // changes in libinputflinger to take ABS_X/ABS_Y instead for absolute
227 mouse_events.push_back({EV_ABS, ABS_X, x});
228 mouse_events.push_back({EV_ABS, ABS_Y, y});
229 // We're sending relative position updates here too but they will be only
230 // used by the Android side EventHub/InputReader to determine if the cursor
231 // was moved. They are not used to find out the exact position.
232 mouse_events.push_back({EV_REL, REL_X, event.motion.xrel});
233 mouse_events.push_back({EV_REL, REL_Y, event.motion.yrel});
235 push_finger_motion(x, y, emulated_touch_id_, touch_events);
239 if (!config_.no_touch_emulation) {
240 SDL_GetMouseState(&x, &y);
241 if (!adjust_coordinates(x, y))
244 mouse_events.push_back({EV_ABS, ABS_X, x});
245 mouse_events.push_back({EV_ABS, ABS_Y, y});
247 mouse_events.push_back(
248 {EV_REL, REL_WHEEL, static_cast<std::int32_t>(event.wheel.y)});
252 const auto code = KeycodeConverter::convert(event.key.keysym.scancode);
253 if (code == KEY_RESERVED) break;
254 keyboard_events.push_back({EV_KEY, code, 1});
258 const auto code = KeycodeConverter::convert(event.key.keysym.scancode);
259 if (code == KEY_RESERVED) break;
260 keyboard_events.push_back({EV_KEY, code, 0});
264 case SDL_FINGERDOWN: {
265 if (!calculate_touch_coordinates(event, x, y))
267 push_finger_down(x, y, event.tfinger.fingerId, touch_events);
272 push_finger_up(event.tfinger.fingerId, touch_events);
275 case SDL_FINGERMOTION: {
277 if (!calculate_touch_coordinates(event, x, y))
279 push_finger_motion(x, y, event.tfinger.fingerId, touch_events);
286 if (mouse_events.size() > 0) {
287 mouse_events.push_back({EV_SYN, SYN_REPORT, 0});
288 pointer_->send_events(mouse_events);
291 if (keyboard_events.size() > 0)
292 keyboard_->send_events(keyboard_events);
294 if (touch_events.size() > 0)
295 touch_->send_events(touch_events);
298 int Platform::find_touch_slot(int id){
299 for (int i = 0; i < MAX_FINGERS; i++) {
300 if (touch_slots[i] == id)
306 void Platform::push_slot(std::vector<input::Event> &touch_events, int slot){
307 if (last_slot != slot) {
308 touch_events.push_back({EV_ABS, ABS_MT_SLOT, slot});
313 void Platform::push_finger_down(int x, int y, int finger_id, std::vector<input::Event> &touch_events){
314 int slot = find_touch_slot(-1);
316 DEBUG("no free slot!");
319 touch_slots[slot] = finger_id;
320 push_slot(touch_events, slot);
321 touch_events.push_back({EV_ABS, ABS_MT_TRACKING_ID, static_cast<std::int32_t>(finger_id % MAX_TRACKING_ID + 1)});
322 touch_events.push_back({EV_ABS, ABS_MT_POSITION_X, x});
323 touch_events.push_back({EV_ABS, ABS_MT_POSITION_Y, y});
324 touch_events.push_back({EV_SYN, SYN_REPORT, 0});
327 void Platform::push_finger_up(int finger_id, std::vector<input::Event> &touch_events){
328 int slot = find_touch_slot(finger_id);
331 push_slot(touch_events, slot);
332 touch_events.push_back({EV_ABS, ABS_MT_TRACKING_ID, -1});
333 touch_events.push_back({EV_SYN, SYN_REPORT, 0});
334 touch_slots[slot] = -1;
337 void Platform::push_finger_motion(int x, int y, int finger_id, std::vector<input::Event> &touch_events){
338 int slot = find_touch_slot(finger_id);
341 push_slot(touch_events, slot);
342 touch_events.push_back({EV_ABS, ABS_MT_POSITION_X, x});
343 touch_events.push_back({EV_ABS, ABS_MT_POSITION_Y, y});
344 touch_events.push_back({EV_SYN, SYN_REPORT, 0});
348 bool Platform::adjust_coordinates(std::int32_t &x, std::int32_t &y) {
349 SDL_Window *window = nullptr;
351 if (!config_.single_window) {
352 window = SDL_GetWindowFromID(focused_sdl_window_id_);
353 return adjust_coordinates(window, x, y);
355 // When running the whole Android system in a single window we don't
356 // need to reacalculate and the pointer position as they are already
357 // relative to our window.
362 bool Platform::adjust_coordinates(SDL_Window *window, std::int32_t &x, std::int32_t &y) {
363 std::int32_t rel_x = 0;
364 std::int32_t rel_y = 0;
369 // As we get only absolute coordindates relative to our window we have to
370 // calculate the correct position based on the current focused window
371 SDL_GetWindowPosition(window, &rel_x, &rel_y);
377 bool Platform::calculate_touch_coordinates(const SDL_Event &event,
380 SDL_Window *window = nullptr;
382 window = SDL_GetWindowFromID(focused_sdl_window_id_);
383 // before SDL 2.0.7 on X11 tfinger coordinates are not normalized
384 if (!SDL_VERSION_ATLEAST(2,0,7) && (event.tfinger.x > 1 || event.tfinger.y > 1)) {
389 SDL_GetWindowSize(window, &x, &y);
390 x *= event.tfinger.x;
391 y *= event.tfinger.y;
393 x = display_frame_.width() * event.tfinger.x;
394 y = display_frame_.height() * event.tfinger.y;
398 if (config_.single_window) {
399 // When running the whole Android system in a single window we don't
400 // need to reacalculate and the pointer position as they are already
401 // relative to our window.
404 return adjust_coordinates(window, x, y);
408 Window::Id Platform::next_window_id() {
409 static Window::Id next_id = 0;
413 std::shared_ptr<wm::Window> Platform::create_window(
414 const anbox::wm::Task::Id &task, const anbox::graphics::Rect &frame, const std::string &title) {
416 ERROR("Can't create window without a renderer set");
420 auto id = next_window_id();
421 auto w = std::make_shared<Window>(renderer_, id, task, shared_from_this(), frame, title,
422 !window_size_immutable_, !config_.server_side_decoration);
423 focused_sdl_window_id_ = w->window_id();
424 windows_.insert({id, w});
428 void Platform::window_deleted(const Window::Id &id) {
429 auto w = windows_.find(id);
430 if (w == windows_.end()) {
431 WARNING("Got window removed event for unknown window (id %d)", id);
434 if (auto window = w->second.lock())
435 window_manager_->remove_task(window->task());
439 void Platform::window_wants_focus(const Window::Id &id) {
440 auto w = windows_.find(id);
441 if (w == windows_.end()) return;
443 if (auto window = w->second.lock()) {
444 focused_sdl_window_id_ = window->window_id();
445 window_manager_->set_focused_task(window->task());
449 void Platform::window_moved(const Window::Id &id, const std::int32_t &x,
450 const std::int32_t &y) {
451 auto w = windows_.find(id);
452 if (w == windows_.end()) return;
454 if (auto window = w->second.lock()) {
455 auto new_frame = window->frame();
456 new_frame.translate(x, y);
457 window->update_frame(new_frame);
458 window_manager_->resize_task(window->task(), new_frame, 3);
462 void Platform::window_resized(const Window::Id &id,
463 const std::int32_t &width,
464 const std::int32_t &height) {
465 auto w = windows_.find(id);
466 if (w == windows_.end()) return;
468 if (auto window = w->second.lock()) {
469 auto new_frame = window->frame();
470 new_frame.resize(width, height);
471 // We need to update the window frame in advance here as otherwise we may
472 // get a movement event before we got an update of the actual layer
473 // representing this window and then we're back to the original size of
475 window->update_frame(new_frame);
476 window_manager_->resize_task(window->task(), new_frame, 3);
480 void Platform::set_clipboard_data(const ClipboardData &data) {
481 if (data.text.empty())
483 SDL_SetClipboardText(data.text.c_str());
486 Platform::ClipboardData Platform::get_clipboard_data() {
487 if (!SDL_HasClipboardText())
488 return ClipboardData{};
490 auto text = SDL_GetClipboardText();
492 return ClipboardData{};
494 auto data = ClipboardData{text};
499 std::shared_ptr<audio::Sink> Platform::create_audio_sink() {
500 return std::make_shared<AudioSink>();
503 std::shared_ptr<audio::Source> Platform::create_audio_source() {
504 ERROR("Not implemented");
508 bool Platform::supports_multi_window() const {
512 } // namespace platform