window.cpp 11.7 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
Window::Id Window::Invalid{-1};
42
const std::map<std::string, Window::window_property> Window::property_map = {
Y
yu_qinfei 已提交
43 44
  {"喜马拉雅", Window::HIDE_MAXIMIZE},
  {"i深圳", Window::HIDE_MAXIMIZE}
45
};
46

47
Window::Observer::~Observer() {}
48

S
Simon Fels 已提交
49 50
Window::Window(const std::shared_ptr<Renderer> &renderer,
               const Id &id, const wm::Task::Id &task,
51
               const std::shared_ptr<Observer> &observer,
52
               const graphics::Rect &frame,
53 54
               const std::string &title,
               bool resizable)
55
    : wm::Window(renderer, task, frame, title),
56
      id_(id),
57
      lastClickTime(0),
58
      observer_(observer),
59
      native_display_(0){
60
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1);
61

62 63 64 65 66
  // 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.
67
  std::uint32_t flags = SDL_WINDOW_BORDERLESS;
Y
yu_qinfei 已提交
68 69 70 71 72 73 74
  auto property_itr = property_map.find(title);
  if (property_itr != property_map.end()) {
    visible_property = property_itr->second;
    if (!(visible_property & HIDE_MAXIMIZE) && resizable) {
      flags |= SDL_WINDOW_RESIZABLE;
    }
  } else if (resizable) {
75
    flags |= SDL_WINDOW_RESIZABLE;
Y
yu_qinfei 已提交
76
  }
77 78
  window_ = SDL_CreateWindow(title.c_str(),
                             frame.left(), frame.top(),
79
                             frame.width(), frame.height(),
80
                             flags);
81
  if (!window_) {
82
    const auto message = utils::string_format("Failed to create window: %s", SDL_GetError());
83 84 85
    BOOST_THROW_EXCEPTION(std::runtime_error(message));
  }

86 87 88
  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"));
89

90
  std::string strPath = SystemConfiguration::instance().resource_dir() + "/ui/logo.png";
91 92 93

  SDL_Surface *icon = IMG_Load(strPath.c_str());
  SDL_SetWindowIcon(window_, icon);
94
  SDL_FreeSurface(icon);
95

96 97 98 99
  SDL_SysWMinfo info;
  SDL_VERSION(&info.version);
  SDL_GetWindowWMInfo(window_, &info);
  switch (info.subsystem) {
D
David Heidelberg 已提交
100
#if defined(X11_SUPPORT)
101
    case SDL_SYSWM_X11:
102
      native_display_ = static_cast<EGLNativeDisplayType>(info.info.x11.display);
103
      set_native_handle(static_cast<EGLNativeWindowType>(info.info.x11.window));
104
      break;
D
David Heidelberg 已提交
105
#endif
106
#if defined(WAYLAND_SUPPORT)
107
    case SDL_SYSWM_WAYLAND:
108
      native_display_ = reinterpret_cast<EGLNativeDisplayType>(info.info.wl.display);
109
      set_native_handle(reinterpret_cast<EGLNativeWindowType>(info.info.wl.surface));
110
      break;
111
#endif
112 113 114 115
#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);
116
      set_native_handle(reinterpret_cast<EGLNativeWindowType>(mir_buffer_stream_get_egl_native_window(buffer_stream)));
117 118 119
      break;
    }
#endif
120
    default:
121
      ERROR("Unknown subsystem (%d)", info.subsystem);
S
Shengjing Zhu 已提交
122
      BOOST_THROW_EXCEPTION(std::runtime_error("SDL subsystem not supported"));
123
  }
124

125 126
  struct timeval now = (struct timeval) { 0 };
  gettimeofday(&now, NULL);
127 128
  last_update_time = USEC_PER_SEC * (now.tv_sec) + now.tv_usec;
  lastClickTime = last_update_time;
129

130
  SDL_ShowWindow(window_);
131 132
}

133
Window::~Window() {
134
  if (window_) SDL_DestroyWindow(window_);
135 136
}

137 138 139 140 141
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;
}

142 143 144 145 146 147 148
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);
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
  // 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;

176 177 178 179
    if (!platform_window->initialized.load()) {
      INFO("window initialized by resize");
      platform_window->initialized = true;
    }
180 181
    return result;
  }
182 183

  if (pt->y < top_drag_area_height) {
184 185 186 187 188
    if (!platform_window->initialized.load()) {
      INFO("window initialized by click top");
      platform_window->initialized = true;
    }

189 190 191 192 193 194 195
    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);
196
      }
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
      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)) {
225
      platform_window->switch_window_state();
226
      return SDL_HITTEST_NORMAL;
227
    } else {
228
      return SDL_HITTEST_DRAGGABLE;
229
    }
230
  }
231 232 233 234

  return SDL_HITTEST_NORMAL;
}

235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
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;
}

261 262 263 264 265 266 267 268 269 270 271 272 273
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_);
}

274
void Window::process_event(const SDL_Event &event) {
275
  switch (event.window.event) {
276
    case SDL_WINDOWEVENT_FOCUS_GAINED:
277 278
      if (observer_) observer_->window_wants_focus(id_);
      break;
279
    case SDL_WINDOWEVENT_FOCUS_LOST:
280
      break;
281 282
    // Not need to listen for SDL_WINDOWEVENT_RESIZED here as the
    // SDL_WINDOWEVENT_SIZE_CHANGED is always sent.
283
    case SDL_WINDOWEVENT_SIZE_CHANGED:
284
      if (observer_) {
285
        observer_->window_resized(id_, event.window.data1, event.window.data2);
286
      }
287
      break;
288
    case SDL_WINDOWEVENT_MOVED:
289
      if (observer_) {
290
        observer_->window_moved(id_, event.window.data1, event.window.data2);
291
      }
292
      break;
293
    case SDL_WINDOWEVENT_SHOWN:
294
      break;
295
    case SDL_WINDOWEVENT_HIDDEN:
296
      break;
297
    case SDL_WINDOWEVENT_CLOSE:
298 299 300
      if (observer_)
        observer_->window_deleted(id_);
      close();
301
      break;
T
Thomas Voß 已提交
302 303
    default:
      break;
304
  }
305 306
}

307
Window::Id Window::id() const { return id_; }
308

309
std::uint32_t Window::window_id() const { return SDL_GetWindowID(window_); }
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351

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);
  }
}
352 353 354
} // namespace sdl
} // namespace platform
} // namespace anbox