window.cpp 12.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * Copyright (C) 2016 Simon Fels <morphis@gravedo.de>
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 3, as published
 * by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranties of
 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

18
#include "anbox/platform/sdl/window.h"
19
#include "anbox/wm/window_state.h"
20 21
#include "anbox/graphics/density.h"
#include "anbox/logger.h"
22
#include "anbox/system_configuration.h"
23 24

#include <boost/throw_exception.hpp>
25
#include <SDL2/SDL_image.h>
26

27 28 29 30
#if defined(MIR_SUPPORT)
#include <mir_toolkit/mir_client_library.h>
#endif

31
namespace {
32 33 34 35
constexpr const int window_resize_border{3};
constexpr const int button_size{42};
constexpr const int button_margin{0};
constexpr const int button_padding{0};
36 37
}

38
namespace anbox {
39 40
namespace platform {
namespace sdl {
41 42 43 44 45 46

static const std::uint32_t HIDE_BACK     = 0x01;
static const std::uint32_t HIDE_MINIMIZE = 0x02;
static const std::uint32_t HIDE_MAXIMIZE = 0x04;
static const std::uint32_t HIDE_CLOSE    = 0x08;
static const std::uint32_t SHOW_ALL      = 0x00;
47 48 49
static const std::uint32_t MINI_WIDTH    = 540;
static const std::uint32_t MINI_HEIGHT   = 700;
static const std::uint32_t WX_MINI_WIDTH = 730;
50 51 52 53

const std::map<std::string, std::uint32_t> Window::property_map = {
  {"喜马拉雅", HIDE_MAXIMIZE},
  {"i深圳", HIDE_MAXIMIZE}
54
};
55

56 57 58 59
const std::map<std::string, Window::mini_size>Window::custom_window_map = {
  {"微信", {WX_MINI_WIDTH, MINI_HEIGHT}}
};

60 61
Window::Id Window::Invalid{-1};

62
Window::Observer::~Observer() {}
63

S
Simon Fels 已提交
64 65
Window::Window(const std::shared_ptr<Renderer> &renderer,
               const Id &id, const wm::Task::Id &task,
66
               const std::shared_ptr<Observer> &observer,
67
               const graphics::Rect &frame,
68 69
               const std::string &title,
               bool resizable)
70
    : wm::Window(renderer, task, frame, title),
71
      id_(id),
72
      lastClickTime(0),
73
      observer_(observer),
74 75
      native_display_(0),
      visible_property(SHOW_ALL) {
76
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1);
77

78 79 80 81 82
  // NOTE: We don't furce GL initialization of the window as this will
  // be take care of by the Renderer when we attach to it. On EGL
  // initializing GL here will cause a surface to be created and the
  // renderer will attempt to create one too which will not work as
  // only a single surface per EGLNativeWindowType is supported.
83
  std::uint32_t flags = SDL_WINDOW_BORDERLESS;
Y
yu_qinfei 已提交
84 85 86
  auto property_itr = property_map.find(title);
  if (property_itr != property_map.end()) {
    visible_property = property_itr->second;
87 88
  }
  if (!(visible_property & HIDE_MAXIMIZE) && resizable) {
89
    flags |= SDL_WINDOW_RESIZABLE;
Y
yu_qinfei 已提交
90
  }
91

92 93
  window_ = SDL_CreateWindow(title.c_str(),
                             frame.left(), frame.top(),
94
                             frame.width(), frame.height(),
95
                             flags);
96
  if (!window_) {
97
    const auto message = utils::string_format("Failed to create window: %s", SDL_GetError());
98 99 100
    BOOST_THROW_EXCEPTION(std::runtime_error(message));
  }

101 102 103
  if (utils::get_env_value("ANBOX_NO_SDL_WINDOW_HIT_TEST", "false") == "false")
    if (SDL_SetWindowHitTest(window_, &Window::on_window_hit, this) < 0)
      BOOST_THROW_EXCEPTION(std::runtime_error("Failed to register for window hit test"));
104

105
  std::string strPath = SystemConfiguration::instance().resource_dir() + "/ui/logo.png";
106 107 108

  SDL_Surface *icon = IMG_Load(strPath.c_str());
  SDL_SetWindowIcon(window_, icon);
109
  SDL_FreeSurface(icon);
110

111 112 113 114
  SDL_SysWMinfo info;
  SDL_VERSION(&info.version);
  SDL_GetWindowWMInfo(window_, &info);
  switch (info.subsystem) {
D
David Heidelberg 已提交
115
#if defined(X11_SUPPORT)
116
    case SDL_SYSWM_X11:
117
      native_display_ = static_cast<EGLNativeDisplayType>(info.info.x11.display);
118
      set_native_handle(static_cast<EGLNativeWindowType>(info.info.x11.window));
119
      break;
D
David Heidelberg 已提交
120
#endif
121
#if defined(WAYLAND_SUPPORT)
122
    case SDL_SYSWM_WAYLAND:
123
      native_display_ = reinterpret_cast<EGLNativeDisplayType>(info.info.wl.display);
124
      set_native_handle(reinterpret_cast<EGLNativeWindowType>(info.info.wl.surface));
125
      break;
126
#endif
127 128 129 130
#if defined(MIR_SUPPORT)
    case SDL_SYSWM_MIR: {
      native_display_ = static_cast<EGLNativeDisplayType>(mir_connection_get_egl_native_display(info.info.mir.connection));
      auto buffer_stream = mir_surface_get_buffer_stream(info.info.mir.surface);
131
      set_native_handle(reinterpret_cast<EGLNativeWindowType>(mir_buffer_stream_get_egl_native_window(buffer_stream)));
132 133 134
      break;
    }
#endif
135
    default:
136
      ERROR("Unknown subsystem (%d)", info.subsystem);
S
Shengjing Zhu 已提交
137
      BOOST_THROW_EXCEPTION(std::runtime_error("SDL subsystem not supported"));
138
  }
139

140 141
  struct timeval now = (struct timeval) { 0 };
  gettimeofday(&now, NULL);
142 143
  last_update_time = USEC_PER_SEC * (now.tv_sec) + now.tv_usec;
  lastClickTime = last_update_time;
144 145 146 147 148 149
  auto window_size_ptr = custom_window_map.find(title);
  if (window_size_ptr != custom_window_map.end()) {
    SDL_SetWindowMinimumSize(window_, window_size_ptr->second.minimum_width, window_size_ptr->second.minimum_height);
  } else {
    SDL_SetWindowMinimumSize(window_, MINI_WIDTH, MINI_HEIGHT);
  }
150
  SDL_ShowWindow(window_);
151 152
}

153
Window::~Window() {
154
  if (window_) SDL_DestroyWindow(window_);
155 156
}

157 158 159 160 161
bool Window::title_event_filter(int point_y) {
  const auto top_drag_area_height = graphics::dp_to_pixel(button_size + (button_margin << 1));
  return point_y < top_drag_area_height;
}

162 163 164 165 166 167 168
SDL_HitTestResult Window::on_window_hit(SDL_Window *window, const SDL_Point *pt, void *data) {
  auto platform_window = reinterpret_cast<Window*>(data);

  int w = 0, h = 0;
  SDL_GetWindowSize(window, &w, &h);

  const auto border_size = graphics::dp_to_pixel(window_resize_border);
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
  // top and bottom, two margins
  const auto top_drag_area_height = graphics::dp_to_pixel(button_size + (button_margin << 1));
  // left and right, two margins
  const auto button_area_width = graphics::dp_to_pixel(button_size + (button_margin << 1));

  SDL_HitTestResult result = SDL_HITTEST_NORMAL;

  while (!(platform_window->GetWindowFlags() & SDL_WINDOW_MAXIMIZED)) {
    if (pt->x < border_size && pt->y < border_size)
      result = SDL_HITTEST_RESIZE_TOPLEFT;
    else if (pt->x > window_resize_border && pt->x < w - border_size && pt->y < border_size)
      result = SDL_HITTEST_RESIZE_TOP;
    else if (pt->x > w - border_size && pt->y < border_size)
      result = SDL_HITTEST_RESIZE_TOPRIGHT;
    else if (pt->x > w - border_size && pt->y > border_size && pt->y < h - border_size)
      result = SDL_HITTEST_RESIZE_RIGHT;
    else if (pt->x > w - border_size && pt->y > h - border_size)
      result = SDL_HITTEST_RESIZE_BOTTOMRIGHT;
    else if (pt->x < w - border_size && pt->x > border_size && pt->y > h - border_size)
      result = SDL_HITTEST_RESIZE_BOTTOM;
    else if (pt->x < border_size && pt->y > h - border_size)
      result = SDL_HITTEST_RESIZE_BOTTOMLEFT;
    else if (pt->x < border_size && pt->y < h - border_size && pt->y > border_size)
      result = SDL_HITTEST_RESIZE_LEFT;
    else
      break;

196 197 198 199
    if (!platform_window->initialized.load()) {
      INFO("window initialized by resize");
      platform_window->initialized = true;
    }
200 201
    return result;
  }
202 203

  if (pt->y < top_drag_area_height) {
204 205 206 207 208
    if (!platform_window->initialized.load()) {
      INFO("window initialized by click top");
      platform_window->initialized = true;
    }

209 210 211 212 213 214 215
    if (pt->x > 0 && pt->x < button_area_width &&
            ((platform_window->get_property() & HIDE_BACK) != HIDE_BACK)) {
      std::shared_ptr<anbox::platform::sdl::Window::Observer> observer = platform_window->observer_;
      if (observer ) {
        observer->window_wants_focus(platform_window->id());
        observer->input_key_event(SDL_SCANCODE_AC_BACK, 1);
        observer->input_key_event(SDL_SCANCODE_AC_BACK, 0);
216
      }
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
      return SDL_HITTEST_NORMAL;
    }
    int btn_cnt = 1;  //button count from right of the titlebar
    if ((platform_window->get_property() & HIDE_CLOSE) != HIDE_CLOSE) {
      if (pt->x > w - button_area_width * btn_cnt &&
              pt->x < w - button_area_width * (btn_cnt - 1)) {
        platform_window->close();
        return SDL_HITTEST_NORMAL;
      }
      ++btn_cnt;
    }
    if ((platform_window->get_property() & HIDE_MAXIMIZE) != HIDE_MAXIMIZE) {
      if (pt->x > w - button_area_width * btn_cnt &&
              pt->x < w - button_area_width * (btn_cnt - 1)) {
        platform_window->switch_window_state();
        return SDL_HITTEST_NORMAL;
      }
      ++btn_cnt;
    }
    if ((platform_window->get_property() & HIDE_MINIMIZE) != HIDE_MINIMIZE) {
      if (pt->x > w - button_area_width * btn_cnt &&
              pt->x < w - button_area_width * (btn_cnt - 1)) {
        SDL_MinimizeWindow(platform_window->window_);
        return SDL_HITTEST_NORMAL;
      }
    }
    if (((platform_window->get_property() & HIDE_MAXIMIZE) != HIDE_MAXIMIZE) &&
               platform_window->check_db_clicked(pt->x, pt->y)) {
245
      platform_window->switch_window_state();
246
      return SDL_HITTEST_NORMAL;
247
    } else {
248
      return SDL_HITTEST_DRAGGABLE;
249
    }
250
  }
251 252 253 254

  return SDL_HITTEST_NORMAL;
}

255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
bool Window::check_db_clicked(int x, int y) {
  bool result = false;
  int wnd_x = 0;
  int wnd_y = 0;
  SDL_GetWindowPosition(window_, &wnd_x, &wnd_y);
  struct timeval now;
  gettimeofday(&now, NULL);
  long long current_time = USEC_PER_SEC * (now.tv_sec) + now.tv_usec;
  if (x == last_point_x && y == last_point_y && 
      wnd_x == last_wnd_x && wnd_y == last_wnd_y) {
    if (current_time - lastClickTime <= timespan_db_click) {
      lastClickTime = current_time - timespan_db_click;
      result = true;
    }
  }
  if (!result){
    lastClickTime = current_time;
  }

  last_wnd_x = wnd_x;
  last_wnd_y = wnd_y;
  last_point_x = x;
  last_point_y = y;
  return result;
}

281 282 283 284 285 286 287 288 289 290 291 292 293
void Window::close() {
  if (observer_)
    observer_->window_deleted(id_);
}

void Window::switch_window_state() {
  const auto flags = SDL_GetWindowFlags(window_);
  if (flags & SDL_WINDOW_MAXIMIZED)
    SDL_RestoreWindow(window_);
  else
    SDL_MaximizeWindow(window_);
}

294
void Window::process_event(const SDL_Event &event) {
295
  switch (event.window.event) {
296
    case SDL_WINDOWEVENT_FOCUS_GAINED:
297 298
      if (observer_) observer_->window_wants_focus(id_);
      break;
299
    case SDL_WINDOWEVENT_FOCUS_LOST:
300
      break;
301 302
    // Not need to listen for SDL_WINDOWEVENT_RESIZED here as the
    // SDL_WINDOWEVENT_SIZE_CHANGED is always sent.
303
    case SDL_WINDOWEVENT_SIZE_CHANGED:
304
      if (observer_) {
305
        observer_->window_resized(id_, event.window.data1, event.window.data2);
306
      }
307
      break;
308
    case SDL_WINDOWEVENT_MOVED:
309
      if (observer_) {
310
        observer_->window_moved(id_, event.window.data1, event.window.data2);
311
      }
312
      break;
313
    case SDL_WINDOWEVENT_SHOWN:
314
      break;
315
    case SDL_WINDOWEVENT_HIDDEN:
316
      break;
317
    case SDL_WINDOWEVENT_CLOSE:
318 319 320
      if (observer_)
        observer_->window_deleted(id_);
      close();
321
      break;
T
Thomas Voß 已提交
322 323
    default:
      break;
324
  }
325 326
}

327
Window::Id Window::id() const { return id_; }
328

329
std::uint32_t Window::window_id() const { return SDL_GetWindowID(window_); }
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371

void Window::update_state(const wm::WindowState::List &states) {
  if (!initialized.load() && !states.empty()) {
    int w = 0;
    int h = 0;
    int x = 0;
    int y = 0;
    SDL_GetWindowSize(window_, &w, &h);
    SDL_GetWindowPosition(window_, &x, &y);

    graphics::Rect rect;
    int area = w * h;
    for (auto ws : states)
    {
      int temp = ws.frame().width() * ws.frame().height();
      if (temp >= area) {
        rect = ws.frame();
      }
    }

    if (w == rect.width() &&
        h == rect.height() &&
        x == rect.left() &&
        y == rect.top()) {
      return;
    }

    struct timeval now = (struct timeval) { 0 };
    gettimeofday(&now, NULL);
    long long current_time = USEC_PER_SEC * (now.tv_sec) + now.tv_usec;
    if (current_time - last_update_time >= APP_START_MAX_TIME) {
      INFO("window initialized by timeout");
      initialized = true;
      return;
    }

    last_update_time = current_time;
    SDL_SetWindowSize(window_, rect.width(), rect.height());
    SDL_SetWindowPosition(window_, rect.left(), rect.top());
    update_frame(rect);
  }
}
372 373 374
} // namespace sdl
} // namespace platform
} // namespace anbox