/* Copyright (C) 2009 Red Hat, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include "common.h" #include "screen.h" #include "application.h" #include "screen_layer.h" #include "utils.h" #include "debug.h" #include "monitor.h" #include "red_pixmap_sw.h" #include "resource.h" #include "icon.h" class UpdateEvent: public Event { public: UpdateEvent(int screen) : _screen (screen) {} virtual void response(AbstractProcessLoop& events_loop) { Application* app = static_cast(events_loop.get_owner()); RedScreen* screen = app->find_screen(_screen); if (screen) { screen->update(); } } private: int _screen; }; class LayerChangedEvent: public Event { public: LayerChangedEvent (int screen) : _screen (screen) {} virtual void response(AbstractProcessLoop& events_loop) { Application* app = static_cast(events_loop.get_owner()); RedScreen* screen = app->find_screen(_screen); if (screen) { Lock lock(screen->_layer_changed_lock); screen->_active_layer_change_event = false; lock.unlock(); if (screen->_pointer_on_screen) { screen->update_pointer_layer(); } } } private: int _screen; }; void UpdateTimer::response(AbstractProcessLoop& events_loop) { _screen->periodic_update(); } RedScreen::RedScreen(Application& owner, int id, const std::string& name, int width, int height) : _owner (owner) , _id (id) , _refs (1) , _window (*this) , _active (false) , _full_screen (false) , _out_of_sync (false) , _frame_area (false) , _periodic_update (false) , _key_interception (false) , _update_by_timer (true) , _size_locked (false) , _menu_needs_update (false) , _force_update_timer (0) , _update_timer (new UpdateTimer(this)) , _composit_area (NULL) , _update_mark (1) , _monitor (NULL) , _default_cursor (NULL) , _inactive_cursor (NULL) , _pixel_format_index (0) , _update_interrupt_trigger (NULL) , _pointer_layer (NULL) , _mouse_captured (false) , _active_layer_change_event (false) , _pointer_on_screen (false) { region_init(&_dirty_region); set_name(name); _size.x = width; _size.y = height; _origin.x = _origin.y = 0; create_composit_area(); _window.resize(_size.x, _size.y); save_position(); if ((_default_cursor = Platform::create_default_cursor()) == NULL) { THROW("create default cursor failed"); } if ((_inactive_cursor = Platform::create_inactive_cursor()) == NULL) { THROW("create inactive cursor failed"); } _window.set_cursor(_default_cursor); update_menu(); AutoRef icon(Platform::load_icon(RED_ICON_RES_ID)); _window.set_icon(*icon); _window.start_key_interception(); } RedScreen::~RedScreen() { bool captured = is_mouse_captured(); _window.stop_key_interception(); relase_mouse(); destroy_composit_area(); _owner.deactivate_interval_timer(*_update_timer); _owner.on_screen_destroyed(_id, captured); region_destroy(&_dirty_region); if (_default_cursor) { _default_cursor->unref(); } if (_inactive_cursor) { _inactive_cursor->unref(); } } void RedScreen::show(bool activate, RedScreen* pos) { _window.position_after((pos) ? &pos->_window : NULL); show(); if (activate) { _window.activate(); } } RedScreen* RedScreen::ref() { ++_refs; return this; } void RedScreen::unref() { if (!--_refs) { delete this; } } void RedScreen::destroy_composit_area() { if (_composit_area) { delete _composit_area; _composit_area = NULL; } } void RedScreen::create_composit_area() { destroy_composit_area(); _composit_area = new RedPixmapSw(_size.x, _size.y, _window.get_format(), false, &_window); } void RedScreen::adjust_window_rect(int x, int y) { _window.move_and_resize(x, y, _size.x, _size.y); } void RedScreen::resize(int width, int height) { RecurciveLock lock(_update_lock); _size.x = width; _size.y = height; create_composit_area(); if (_full_screen) { __show_full_screen(); } else { bool cuptur = is_mouse_captured(); if (cuptur) { relase_mouse(); } _window.resize(_size.x, _size.y); if (_active && cuptur) { capture_mouse(); } } notify_new_size(); } void RedScreen::lock_size() { ASSERT(!_size_locked); _size_locked = true; } void RedScreen::unlock_size() { _size_locked = false; _owner.on_screen_unlocked(*this); } void RedScreen::set_name(const std::string& name) { if (!name.empty()) { string_printf(_name, name.c_str(), _id); } _window.set_title(_name); } void RedScreen::on_layer_changed(ScreenLayer& layer) { Lock lock(_layer_changed_lock); if (_active_layer_change_event) { return; } _active_layer_change_event = true; AutoRef change_event(new LayerChangedEvent(_id)); _owner.push_event(*change_event); } void RedScreen::attach_layer(ScreenLayer& layer) { RecurciveLock lock(_update_lock); int order = layer.z_order(); if ((int)_layers.size() < order + 1) { _layers.resize(order + 1); } if (_layers[order]) { THROW("layer in use"); } layer.set_screen(this); _layers[order] = &layer; ref(); lock.unlock(); layer.invalidate(); if (_pointer_on_screen) { update_pointer_layer(); } } void RedScreen::detach_layer(ScreenLayer& layer) { bool need_pointer_layer_update = false; if (_pointer_layer == &layer) { _pointer_layer->on_pointer_leave(); _pointer_layer = NULL; need_pointer_layer_update = true; } RecurciveLock lock(_update_lock); int order = layer.z_order(); if ((int)_layers.size() < order + 1 || _layers[order] != &layer) { THROW("not found"); } QRegion layer_area; region_clone(&layer_area, &layer.area()); _layers[order]->set_screen(NULL); _layers[order] = NULL; lock.unlock(); invalidate(layer_area); region_destroy(&layer_area); unref(); if (need_pointer_layer_update && !update_pointer_layer()) { _window.set_cursor(_inactive_cursor); } } void RedScreen::composit_to_screen(RedDrawable& win_dc, const QRegion& region) { pixman_box32_t *rects; int num_rects; rects = pixman_region32_rectangles((pixman_region32_t *)®ion, &num_rects); for (int i = 0; i < num_rects; i++) { SpiceRect r; r.left = rects[i].x1; r.top = rects[i].y1; r.right = rects[i].x2; r.bottom = rects[i].y2; win_dc.copy_pixels(*_composit_area, r.left, r.top, r); } } void RedScreen::notify_new_size() { for (int i = 0; i < (int)_layers.size(); i++) { if (!_layers[i]) { continue; } _layers[i]->on_size_changed(); } } inline void RedScreen::begin_update(QRegion& direct_rgn, QRegion& composit_rgn, QRegion& frame_rgn) { region_init(&composit_rgn); RecurciveLock lock(_update_lock); region_clone(&direct_rgn, &_dirty_region); region_clear(&_dirty_region); _update_mark++; lock.unlock(); QRegion rect_rgn; SpiceRect r; r.top = r.left = 0; r.right = _size.x; r.bottom = _size.y; region_init(&rect_rgn); region_add(&rect_rgn, &r); if (_frame_area) { region_clone(&frame_rgn, &direct_rgn); region_exclude(&frame_rgn, &rect_rgn); } region_and(&direct_rgn, &rect_rgn); region_destroy(&rect_rgn); for (int i = _layers.size() - 1; i >= 0; i--) { ScreenLayer* layer; if (!(layer = _layers[i])) { continue; } layer->begin_update(direct_rgn, composit_rgn); } } inline void RedScreen::update_done() { for (unsigned int i = 0; i < _layers.size(); i++) { ScreenLayer* layer; if (!(layer = _layers[i])) { continue; } layer->on_update_completion(_update_mark - 1); } } inline void RedScreen::update_composit(QRegion& composit_rgn) { erase_background(*_composit_area, composit_rgn); for (int i = 0; i < (int)_layers.size(); i++) { ScreenLayer* layer; if (!(layer = _layers[i])) { continue; } QRegion& dest_region = layer->composit_area(); region_or(&composit_rgn, &dest_region); layer->copy_pixels(dest_region, *_composit_area); } } inline void RedScreen::draw_direct(RedDrawable& win_dc, QRegion& direct_rgn, QRegion& composit_rgn, QRegion& frame_rgn) { erase_background(win_dc, direct_rgn); if (_frame_area) { erase_background(win_dc, frame_rgn); region_destroy(&frame_rgn); } for (int i = 0; i < (int)_layers.size(); i++) { ScreenLayer* layer; if (!(layer = _layers[i])) { continue; } QRegion& dest_region = layer->direct_area(); layer->copy_pixels(dest_region, win_dc); } } void RedScreen::periodic_update() { bool need_update; RecurciveLock lock(_update_lock); if (is_dirty()) { need_update = true; } else { if (!_force_update_timer) { _owner.deactivate_interval_timer(*_update_timer); _periodic_update = false; } need_update = false; } lock.unlock(); if (need_update) { if (update_by_interrupt()) { interrupt_update(); } else { update(); } } } void RedScreen::activate_timer() { RecurciveLock lock(_update_lock); if (_periodic_update) { return; } _periodic_update = true; lock.unlock(); _owner.activate_interval_timer(*_update_timer, 1000 / 30); } void RedScreen::update() { if (is_out_of_sync()) { return; } QRegion direct_rgn; QRegion composit_rgn; QRegion frame_rgn; begin_update(direct_rgn, composit_rgn, frame_rgn); update_composit(composit_rgn); draw_direct(_window, direct_rgn, composit_rgn, frame_rgn); composit_to_screen(_window, composit_rgn); update_done(); region_destroy(&direct_rgn); region_destroy(&composit_rgn); if (_update_by_timer) { activate_timer(); } } bool RedScreen::_invalidate(const SpiceRect& rect, bool urgent, uint64_t& update_mark) { RecurciveLock lock(_update_lock); bool update_triger = !is_dirty() && (urgent || !_periodic_update); region_add(&_dirty_region, &rect); update_mark = _update_mark; return update_triger; } uint64_t RedScreen::invalidate(const SpiceRect& rect, bool urgent) { uint64_t update_mark; if (_invalidate(rect, urgent, update_mark)) { if (!urgent && _update_by_timer) { activate_timer(); } else { if (update_by_interrupt()) { interrupt_update(); } else { AutoRef update_event(new UpdateEvent(_id)); _owner.push_event(*update_event); } } } return update_mark; } void RedScreen::invalidate(const QRegion ®ion) { pixman_box32_t *rects, *end; int num_rects; rects = pixman_region32_rectangles((pixman_region32_t *)®ion, &num_rects); end = rects + num_rects; while (rects != end) { SpiceRect r; r.left = rects->x1; r.top = rects->y1; r.right = rects->x2; r.bottom = rects->y2; rects++; invalidate(r, false); } } inline void RedScreen::erase_background(RedDrawable& dc, const QRegion& composit_rgn) { pixman_box32_t *rects; int num_rects; rects = pixman_region32_rectangles((pixman_region32_t *)&composit_rgn, &num_rects); for (int i = 0; i < num_rects; i++) { SpiceRect r; r.left = rects[i].x1; r.top = rects[i].y1; r.right = rects[i].x2; r.bottom = rects[i].y2; dc.fill_rect(r, 0); } } void RedScreen::reset_mouse_pos() { _window.set_mouse_position(_mouse_anchor_point.x, _mouse_anchor_point.y); } void RedScreen::capture_mouse() { if (_mouse_captured || !_window.get_mouse_anchor_point(_mouse_anchor_point)) { return; } if (_pointer_layer) { _pointer_layer->on_pointer_leave(); _pointer_layer = NULL; } _pointer_on_screen = false; _mouse_captured = true; _window.hide_cursor(); reset_mouse_pos(); _window.capture_mouse(); } void RedScreen::relase_mouse() { if (!_mouse_captured) { return; } _mouse_captured = false; _window.release_mouse(); update_pointer_layer(); } void RedScreen::set_cursor(LocalCursor* cursor) { if (_mouse_captured) { return; } _window.set_cursor(cursor); } void RedScreen::hide_cursor() { _window.hide_cursor(); } ScreenLayer* RedScreen::find_pointer_layer() { for (int i = _layers.size() - 1; i >= 0; i--) { ScreenLayer* layer; if (!(layer = _layers[i])) { continue; } if (layer->pointer_test(_pointer_pos.x, _pointer_pos.y)) { return layer; } } return NULL; } bool RedScreen::update_pointer_layer() { ASSERT(!_mouse_captured); ScreenLayer* now = find_pointer_layer(); if (now == _pointer_layer) { return false; } if (_pointer_layer) { _pointer_layer->on_pointer_leave(); } _pointer_layer = find_pointer_layer(); if (_pointer_layer) { _pointer_layer->on_pointer_enter(_pointer_pos.x, _pointer_pos.y, _mouse_botton_state); } else { set_cursor(_inactive_cursor); } return true; } void RedScreen::on_pointer_enter(int x, int y, unsigned int buttons_state) { if (_mouse_captured) { return; } _pointer_on_screen = true; _pointer_pos.x = x; _pointer_pos.y = y; _mouse_botton_state = buttons_state; ScreenLayer* layer = find_pointer_layer(); if (!layer) { set_cursor(_inactive_cursor); return; } _pointer_layer = layer; _pointer_layer->on_pointer_enter(_pointer_pos.x, _pointer_pos.y, buttons_state); if (_full_screen) { /* allowing enterance to key interception mode without requiring the user to press the window */ activate(); } } void RedScreen::on_mouse_motion(int x, int y, unsigned int buttons_state) { if (x != _mouse_anchor_point.x || y != _mouse_anchor_point.y) { _owner.on_mouse_motion(x - _mouse_anchor_point.x, y - _mouse_anchor_point.y, buttons_state); reset_mouse_pos(); } } void RedScreen::on_pointer_motion(int x, int y, unsigned int buttons_state) { if (_mouse_captured) { on_mouse_motion(x, y, buttons_state); return; } _pointer_pos.x = x; _pointer_pos.y = y; _mouse_botton_state = buttons_state; if (update_pointer_layer() || !_pointer_layer) { return; } _pointer_layer->on_pointer_motion(x, y, buttons_state); } void RedScreen::on_mouse_button_press(SpiceMouseButton button, unsigned int buttons_state) { if (_mouse_captured) { _owner.on_mouse_down(button, buttons_state); return; } if (!_pointer_layer) { return; } _pointer_layer->on_mouse_button_press(button, buttons_state); } void RedScreen::on_mouse_button_release(SpiceMouseButton button, unsigned int buttons_state) { if (_mouse_captured) { _owner.on_mouse_up(button, buttons_state); return; } if (!_pointer_layer) { return; } _pointer_layer->on_mouse_button_release(button, buttons_state); } void RedScreen::on_pointer_leave() { // ASSERT(!_mouse_captured); if (_pointer_layer) { _pointer_layer->on_pointer_leave(); _pointer_layer = NULL; } _pointer_on_screen = false; } void RedScreen::on_key_press(RedKey key) { _owner.on_key_down(key); } void RedScreen::on_key_release(RedKey key) { _owner.on_key_up(key); } void RedScreen::on_char(uint32_t ch) { _owner.on_char(ch); } void RedScreen::on_deactivate() { relase_mouse(); _active = false; _owner.on_deactivate_screen(this); } void RedScreen::on_activate() { _active = true; _owner.on_activate_screen(this); } void RedScreen::on_start_key_interception() { _key_interception = true; _owner.on_start_screen_key_interception(this); } void RedScreen::on_stop_key_interception() { _key_interception = false; _owner.on_stop_screen_key_interception(this); } void RedScreen::enter_modal_loop() { _force_update_timer++; activate_timer(); } void RedScreen::exit_modal_loop() { ASSERT(_force_update_timer > 0) _force_update_timer--; } void RedScreen::pre_migrate() { for (int i = 0; i < (int)_layers.size(); i++) { if (!_layers[i]) { continue; } _layers[i]->pre_migrate(); } } void RedScreen::post_migrate() { for (int i = 0; i < (int)_layers.size(); i++) { if (!_layers[i]) { continue; } _layers[i]->post_migrate(); } } void RedScreen::exit_full_screen() { if (!_full_screen) { return; } RecurciveLock lock(_update_lock); _window.hide(); region_clear(&_dirty_region); _window.set_type(RedWindow::TYPE_NORMAL); adjust_window_rect(_save_pos.x, _save_pos.y); _origin.x = _origin.y = 0; _window.set_origin(0, 0); show(); if (_menu_needs_update) { update_menu(); } _full_screen = false; _out_of_sync = false; _frame_area = false; } void RedScreen::save_position() { _save_pos = _window.get_position(); } void RedScreen::__show_full_screen() { if (!_monitor) { hide(); return; } SpicePoint position = _monitor->get_position(); SpicePoint monitor_size = _monitor->get_size(); _frame_area = false; region_clear(&_dirty_region); _window.set_type(RedWindow::TYPE_FULLSCREEN); _window.move_and_resize(position.x, position.y, monitor_size.x, monitor_size.y); if (!(_out_of_sync = _monitor->is_out_of_sync())) { ASSERT(monitor_size.x >= _size.x); ASSERT(monitor_size.y >= _size.y); _origin.x = (monitor_size.x - _size.x) / 2; _origin.y = (monitor_size.y - _size.y) / 2; _frame_area = monitor_size.x != _size.x || monitor_size.y != _size.y; } else { _origin.x = _origin.y = 0; } _window.set_origin(_origin.x, _origin.y); show(); } void RedScreen::show_full_screen() { if (_full_screen) { return; } RecurciveLock lock(_update_lock); #ifndef WIN32 /* performing hide during resolution changes resulted in missing WM_KEYUP events */ hide(); #endif save_position(); _full_screen = true; __show_full_screen(); } void RedScreen::minimize() { _window.minimize(); } void RedScreen::position_full_screen(const SpicePoint& position) { if (!_full_screen) { return; } _window.move(position.x, position.y); } void RedScreen::hide() { _window.hide(); } void RedScreen::show() { RecurciveLock lock(_update_lock); _window.show(_monitor ? _monitor->get_screen_id() : 0); } void RedScreen::activate() { _window.activate(); } void RedScreen::external_show() { DBG(0, "Entry"); _window.external_show(); } void RedScreen::update_menu() { AutoRef menu(_owner.get_app_menu()); int ret = _window.set_menu(*menu); _menu_needs_update = (ret != 0); /* try again if menu update failed */ } void RedScreen::on_exposed_rect(const SpiceRect& area) { if (is_out_of_sync()) { _window.fill_rect(area, rgb32_make(0xff, 0xff, 0xff)); return; } invalidate(area, false); } int RedScreen::get_screen_id() { return _monitor ? _monitor->get_screen_id() : 0; } #ifdef USE_OPENGL void RedScreen::untouch_context() { _window.untouch_context(); } bool RedScreen::need_recreate_context_gl() { if (_full_screen) { return true; } return false; } #endif void RedScreen::set_update_interrupt_trigger(EventSources::Trigger *trigger) { _update_interrupt_trigger = trigger; } bool RedScreen::update_by_interrupt() { return _update_interrupt_trigger != NULL; } void RedScreen::interrupt_update() { _update_interrupt_trigger->trigger(); } #ifdef USE_OPENGL void RedScreen::set_type_gl() { _window.set_type_gl(); } void RedScreen::unset_type_gl() { _window.unset_type_gl(); } #endif // USE_OPENGL