/* Copyright (C) 2009 Red Hat, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 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 . */ #include "common.h" #include "cursor_channel.h" #include "cursor.h" #include "red_client.h" #include "application.h" #include "debug.h" #include "utils.h" #include "screen.h" #include "red_pixmap_cairo.h" #include "rect.h" static inline uint8_t revers_bits(uint8_t byte) { uint8_t ret = 0; int i; for (i = 0; i < 4; i++) { int shift = 7 - i * 2; ret |= (byte & (1 << i)) << shift; ret |= (byte & (0x80 >> i)) >> shift; } return ret; } class NaitivCursor: public CursorOpaque { public: virtual void draw(RedDrawable& dest, int x, int y, const Rect& area) = 0; }; class AlphaCursor: public NaitivCursor { public: AlphaCursor(const CursorHeader& header, const uint8_t* data); virtual void draw(RedDrawable& dest, int x, int y, const Rect& area); private: std::auto_ptr _pixmap; }; class MonoCursor: public NaitivCursor { public: MonoCursor(const CursorHeader& header, const uint8_t* data); virtual void draw(RedDrawable& dest, int x, int y, const Rect& area); private: std::auto_ptr _pixmap; int _height; }; class UnsupportedCursor: public NaitivCursor { public: UnsupportedCursor(const CursorHeader& header); virtual void draw(RedDrawable& dest, int x, int y, const Rect& area); private: int _hot_x; int _hot_y; }; UnsupportedCursor::UnsupportedCursor(const CursorHeader& header) : _hot_x (header.hot_spot_x) , _hot_y (header.hot_spot_y) { LOG_WARN("Unsupported cursor %hu", header.type); } void UnsupportedCursor::draw(RedDrawable& dest, int x, int y, const Rect& area) { Rect dest_area; Rect rect; dest_area.left = area.left; dest_area.right = area.right; dest_area.top = area.top; dest_area.bottom = area.bottom; rect.left = x + _hot_x - 2; rect.right = rect.left + 8; rect.top = y + _hot_y - 2; rect.bottom = rect.top + 8; rect_sect(rect, dest_area); dest.fill_rect(rect, rgb32_make(0xf8, 0xf1, 0xb8)); rect.left = x + _hot_x - 1; rect.right = rect.left + 6; rect.top = y + _hot_y - 1; rect.bottom = rect.top + 6; rect_sect(rect, dest_area); dest.frame_rect(rect, rgb32_make(0, 0, 0)); } AlphaCursor::AlphaCursor(const CursorHeader& header, const uint8_t* data) : _pixmap (new RedPixmapCairo(header.width, header.height, RedPixmap::ARGB32, true, NULL, NULL)) { int stride = _pixmap->get_stride(); uint8_t* dest = _pixmap->get_data(); int line_size = header.width * sizeof(uint32_t); for (int i = 0; i < header.height; i++, data += line_size, dest += stride) { memcpy(dest, data, line_size); } } void AlphaCursor::draw(RedDrawable& dest, int x, int y, const Rect& area) { dest.blend_pixels(*_pixmap, area.left - x, area.top - y, area); } MonoCursor::MonoCursor(const CursorHeader& header, const uint8_t* data) : _pixmap (NULL) , _height (header.height) { rgb32_t pallete[2] = { rgb32_make(0x00, 0x00, 0x00), rgb32_make(0xff, 0xff, 0xff)}; _pixmap.reset(new RedPixmapCairo(header.width, _height * 2, RedPixmap::A1, true, pallete, NULL)); int dest_stride = _pixmap->get_stride(); uint8_t *dest_line = _pixmap->get_data(); int src_stride = ALIGN(header.width, 8) >> 3; const uint8_t* src_line = data; const uint8_t* end_line = src_line + _pixmap->get_height() * src_stride; if (_pixmap->is_big_endian_bits()) { for (; src_line < end_line; src_line += src_stride, dest_line += dest_stride) { memcpy(dest_line, src_line, src_stride); } } else { for (; src_line < end_line; src_line += src_stride, dest_line += dest_stride) { for (int i = 0; i < src_stride; i++) { dest_line[i] = revers_bits(src_line[i]); } } } } void MonoCursor::draw(RedDrawable& dest, int x, int y, const Rect& area) { dest.combine_pixels(*_pixmap, area.left - x, area.top - y, area, RedDrawable::OP_AND); dest.combine_pixels(*_pixmap, area.left - x, area.top - y + _height, area, RedDrawable::OP_XOR); } class ColorCursor: public NaitivCursor { public: ColorCursor(const CursorHeader& header); virtual void draw(RedDrawable& dest, int x, int y, const Rect& area); protected: void init_pixels(const CursorHeader& header, const uint8_t* _pixels, const uint8_t *and_mask); virtual uint32_t get_pixel_color(const uint8_t *data, int row, int col) = 0; private: std::auto_ptr _pixmap; std::auto_ptr _invers; }; ColorCursor::ColorCursor(const CursorHeader& header) : _pixmap (new RedPixmapCairo(header.width, header.height, RedPixmap::ARGB32, true, NULL, NULL)) , _invers (NULL) { rgb32_t pallete[2] = { rgb32_make(0x00, 0x00, 0x00), rgb32_make(0xff, 0xff, 0xff)}; _invers.reset(new RedPixmapCairo(header.width, header.height, RedPixmap::A1, true, pallete, NULL)); } void ColorCursor::init_pixels(const CursorHeader& header, const uint8_t* pixels, const uint8_t *and_mask) { int mask_stride = ALIGN(header.width, 8) / 8; int invers_stride = _invers->get_stride(); int pixmap_stride = _pixmap->get_stride(); uint8_t *_pixmap_line = _pixmap->get_data(); uint8_t* invers_line = _invers->get_data(); bool be_bits = _invers->is_big_endian_bits(); memset(invers_line, 0, header.height * invers_stride); for (int i = 0; i < header.height; i++, and_mask += mask_stride, invers_line += invers_stride, _pixmap_line += pixmap_stride) { uint32_t *line_32 = (uint32_t *)_pixmap_line; for (int j = 0; j < header.width; j++) { uint32_t pixel_val = get_pixel_color(pixels, i, j); int and_val = test_bit_be(and_mask, j); if ((pixel_val & 0x00ffffff) == 0 && and_val) { line_32[j] = 0; } else if ((pixel_val & 0x00ffffff) == 0x00ffffff && and_val) { line_32[j] = 0; if (be_bits) { set_bit_be(invers_line, j); } else { set_bit(invers_line, j); } } else { line_32[j] = pixel_val | 0xff000000; } } } } void ColorCursor::draw(RedDrawable& dest, int x, int y, const Rect& area) { dest.blend_pixels(*_pixmap, area.left - x, area.top - y, area); dest.combine_pixels(*_invers, area.left - x, area.top - y, area, RedDrawable::OP_XOR); } class ColorCursor32: public ColorCursor { public: ColorCursor32(const CursorHeader& header, const uint8_t* data) : ColorCursor(header) , _src_stride (header.width * sizeof(uint32_t)) { init_pixels(header, data, data + _src_stride * header.height); } private: uint32_t get_pixel_color(const uint8_t *data, int row, int col) { return *((uint32_t *)(data + row * _src_stride) + col); } private: int _src_stride; }; class ColorCursor16: public ColorCursor { public: ColorCursor16(const CursorHeader& header, const uint8_t* data) : ColorCursor(header) , _src_stride (header.width * sizeof(uint16_t)) { init_pixels(header, data, data + _src_stride * header.height); } private: uint32_t get_pixel_color(const uint8_t *data, int row, int col) { uint32_t pix = *((uint16_t*)(data + row * _src_stride) + col); return ((pix & 0x1f) << 3) | ((pix & 0x3e0) << 6) | ((pix & 0x7c00) << 9); } private: int _src_stride; }; class ColorCursor4: public ColorCursor { public: ColorCursor4(const CursorHeader& header, const uint8_t* data) : ColorCursor(header) , _src_stride (ALIGN(header.width, 2) >> 1) , _palette ((uint32_t*)(data + _src_stride * header.height)) { init_pixels(header, data, (uint8_t*)(_palette + 16)); } private: uint32_t get_pixel_color(const uint8_t *data, int row, int col) { data += _src_stride * row + (col >> 1); return (col & 1) ? _palette[*data & 0x0f] : _palette[*data >> 4]; } private: int _src_stride; uint32_t* _palette; }; class CursorSetEvent: public Event { public: CursorSetEvent(CursorChannel& channel, CursorData *cursor, int x, int y, bool visable) : _channel (channel) , _cursor (cursor->ref()) , _x (x) , _y (y) , _visible (visable) { } void create_cursor() { CursorData *cursor = *_cursor; CursorOpaque* native_cursor = cursor->get_opaque(); if (native_cursor) { return; } switch (cursor->header().type) { case CURSOR_TYPE_ALPHA: native_cursor = new AlphaCursor(cursor->header(), cursor->data()); break; case CURSOR_TYPE_COLOR32: native_cursor = new ColorCursor32(cursor->header(), cursor->data()); break; case CURSOR_TYPE_MONO: native_cursor = new MonoCursor(cursor->header(), cursor->data()); break; case CURSOR_TYPE_COLOR4: native_cursor = new ColorCursor4(cursor->header(), cursor->data()); break; case CURSOR_TYPE_COLOR8: native_cursor = new UnsupportedCursor(cursor->header()); break; case CURSOR_TYPE_COLOR16: native_cursor = new ColorCursor16(cursor->header(), cursor->data()); break; case CURSOR_TYPE_COLOR24: native_cursor = new UnsupportedCursor(cursor->header()); break; default: THROW("invalid curosr type"); } cursor->set_opaque(native_cursor); } virtual void response(AbstractProcessLoop& events_loop) { CursorData *cursor = *_cursor; create_cursor(); Lock lock(_channel._update_lock); _channel._hot_pos.x = _x; _channel._hot_pos.y = _y; _channel._cursor_visible = _visible; _channel._cursor_rect.left = _x - cursor->header().hot_spot_x; _channel._cursor_rect.right = _channel._cursor_rect.left + cursor->header().width; _channel._cursor_rect.top = _y - cursor->header().hot_spot_y; _channel._cursor_rect.bottom = _channel._cursor_rect.top + cursor->header().height; if (static_cast(events_loop.get_owner())->get_mouse_mode() == RED_MOUSE_MODE_CLIENT) { RedScreen* screen = _channel.screen(); ASSERT(screen); screen->set_cursor(_visible ? cursor : NULL); } else { if (_visible) { _channel.set_rect_area(_channel._cursor_rect); } else { _channel.clear_area(); } } if (_channel._cursor) { _channel._cursor->unref(); } _channel._cursor = cursor->ref(); } private: CursorChannel& _channel; AutoRef _cursor; int _x; int _y; bool _visible; }; class CursorMoveEvent: public Event { public: CursorMoveEvent(CursorChannel& channel, int x, int y) : _channel (channel) , _x (x) , _y (y) { } virtual void response(AbstractProcessLoop& events_loop) { _channel._cursor_visible = true; if (static_cast(events_loop.get_owner())->get_mouse_mode() == RED_MOUSE_MODE_CLIENT) { RedScreen* screen = _channel.screen(); ASSERT(screen); screen->set_cursor(_channel._cursor); } else { Lock lock(_channel._update_lock); int dx = _x - _channel._hot_pos.x; int dy = _y - _channel._hot_pos.y; _channel._hot_pos.x += dx; _channel._hot_pos.y += dy; _channel._cursor_rect.left += dx; _channel._cursor_rect.right += dx; _channel._cursor_rect.top += dy; _channel._cursor_rect.bottom += dy; lock.unlock(); _channel.set_rect_area(_channel._cursor_rect); } } private: CursorChannel& _channel; int _x; int _y; }; class CursorHideEvent: public Event { public: CursorHideEvent(CursorChannel& channel): _channel (channel) {} virtual void response(AbstractProcessLoop& events_loop) { _channel._cursor_visible = false; if (static_cast(events_loop.get_owner())->get_mouse_mode() == RED_MOUSE_MODE_CLIENT) { RedScreen* screen = _channel.screen(); ASSERT(screen); screen->set_cursor(NULL); } else { _channel.clear_area(); } } private: CursorChannel& _channel; }; class CursorRemoveEvent: public Event { public: CursorRemoveEvent(CursorChannel& channel): _channel (channel) {} virtual void response(AbstractProcessLoop& events_loop) { _channel._cursor_visible = false; _channel.clear_area(); if (_channel._cursor) { _channel._cursor->unref(); _channel._cursor = NULL; } } private: CursorChannel& _channel; }; class CursorHandler: public MessageHandlerImp { public: CursorHandler(CursorChannel& channel) : MessageHandlerImp(channel) {} }; class CursorModeEvent: public Event { public: CursorModeEvent(CursorChannel& channel): _channel (channel) {} virtual void response(AbstractProcessLoop& events_loop) { RedScreen* screen = _channel.screen(); if (!screen) { return; } if (static_cast(events_loop.get_owner())->get_mouse_mode() == RED_MOUSE_MODE_CLIENT) { _channel.clear_area(); screen->set_cursor(_channel._cursor_visible ? _channel._cursor : NULL); } else { if (_channel._cursor_visible && _channel._cursor) { _channel.set_rect_area(_channel._cursor_rect); } else { _channel.clear_area(); } } screen->relase_inputs(); } private: CursorChannel& _channel; }; CursorModeTrigger::CursorModeTrigger(CursorChannel& channel) : _channel (channel) { } void CursorModeTrigger::on_event() { AutoRef set_event(new CursorModeEvent(_channel)); _channel.get_client().push_event(*set_event); } CursorChannel::CursorChannel(RedClient& client, uint32_t id) : RedChannel(client, RED_CHANNEL_CURSOR, id, new CursorHandler(*this)) , ScreenLayer(SCREEN_LAYER_CURSOR, false) , _cursor (NULL) , _cursor_trigger (*this) , _cursor_visible (false) { CursorHandler* handler = static_cast(get_message_handler()); handler->set_handler(RED_MIGRATE, &CursorChannel::handle_migrate, 0); handler->set_handler(RED_SET_ACK, &CursorChannel::handle_set_ack, sizeof(RedSetAck)); handler->set_handler(RED_PING, &CursorChannel::handle_ping, sizeof(RedPing)); handler->set_handler(RED_WAIT_FOR_CHANNELS, &CursorChannel::handle_wait_for_channels, sizeof(RedWaitForChannels)); handler->set_handler(RED_DISCONNECTING, &CursorChannel::handle_disconnect, sizeof(RedDisconnect)); handler->set_handler(RED_NOTIFY, &CursorChannel::handle_notify, sizeof(RedNotify)); handler->set_handler(RED_CURSOR_INIT, &CursorChannel::handle_init, sizeof(RedCursorInit)); handler->set_handler(RED_CURSOR_RESET, &CursorChannel::handle_reset, 0); handler->set_handler(RED_CURSOR_SET, &CursorChannel::handle_cursor_set, sizeof(RedCursorSet)); handler->set_handler(RED_CURSOR_MOVE, &CursorChannel::handle_cursor_move, sizeof(RedCursorMove)); handler->set_handler(RED_CURSOR_HIDE, &CursorChannel::handle_cursor_hide, 0); handler->set_handler(RED_CURSOR_TRAIL, &CursorChannel::handle_cursor_trail, sizeof(RedCursorTrail)); handler->set_handler(RED_CURSOR_INVAL_ONE, &CursorChannel::handle_inval_one, sizeof(RedInvalOne)); handler->set_handler(RED_CURSOR_INVAL_ALL, &CursorChannel::handle_inval_all, 0); get_process_loop().add_trigger(_cursor_trigger); } CursorChannel::~CursorChannel() { ASSERT(!_cursor); } void CursorChannel::on_connect() { } void CursorChannel::on_disconnect() { remove_cursor(); _cursor_cache.clear(); AutoRef sync_event(new SyncEvent()); get_client().push_event(*sync_event); (*sync_event)->wait(); detach_from_screen(get_client().get_application()); } void CursorChannel::remove_cursor() { AutoRef event(new CursorRemoveEvent(*this)); get_client().push_event(*event); } void CursorChannel::copy_pixels(const QRegion& dest_region, RedDrawable& dest_dc) { Lock lock(_update_lock); for (int i = 0; i < (int)dest_region.num_rects; i++) { ASSERT(_cursor && _cursor->get_opaque()); ((NaitivCursor*)_cursor->get_opaque())->draw(dest_dc, _cursor_rect.left, _cursor_rect.top, dest_region.rects[i]); } } void CursorChannel::set_cursor(RedCursor& red_cursor, int data_size, int x, int y, bool visible) { CursorData *cursor; if (red_cursor.flags & RED_CURSOR_NONE) { remove_cursor(); return; } if (red_cursor.flags & RED_CURSOR_FROM_CACHE) { cursor = _cursor_cache.get(red_cursor.header.unique); } else { cursor = new CursorData(red_cursor, data_size); if (red_cursor.flags & RED_CURSOR_CACHE_ME) { ASSERT(red_cursor.header.unique); _cursor_cache.add(red_cursor.header.unique, cursor); } } AutoRef cursor_ref(cursor); AutoRef set_event(new CursorSetEvent(*this, *cursor_ref, x, y, visible)); get_client().push_event(*set_event); } void CursorChannel::handle_init(RedPeer::InMessage *message) { RedCursorInit *init = (RedCursorInit*)message->data(); attach_to_screen(get_client().get_application(), get_id()); remove_cursor(); _cursor_cache.clear(); set_cursor_mode(); set_cursor(init->cursor, message->size() - sizeof(RedCursorInit), init->position.x, init->position.y, init->visible != 0); } void CursorChannel::handle_reset(RedPeer::InMessage *message) { remove_cursor(); detach_from_screen(get_client().get_application()); _cursor_cache.clear(); } void CursorChannel::handle_cursor_set(RedPeer::InMessage* message) { RedCursorSet* set = (RedCursorSet*)message->data(); set_cursor(set->cursor, message->size() - sizeof(RedCursorSet), set->postition.x, set->postition.y, set->visible != 0); } void CursorChannel::handle_cursor_move(RedPeer::InMessage* message) { RedCursorMove* move = (RedCursorMove*)message->data(); AutoRef event(new CursorMoveEvent(*this, move->postition.x, move->postition.y)); get_client().push_event(*event); } void CursorChannel::handle_cursor_hide(RedPeer::InMessage* message) { AutoRef event(new CursorHideEvent(*this)); get_client().push_event(*event); } void CursorChannel::handle_cursor_trail(RedPeer::InMessage* message) { RedCursorTrail* trail = (RedCursorTrail*)message->data(); DBG(0, "length %u frequency %u", trail->length, trail->frequency) } void CursorChannel::handle_inval_one(RedPeer::InMessage* message) { RedInvalOne* inval = (RedInvalOne*)message->data(); _cursor_cache.remove(inval->id); } void CursorChannel::handle_inval_all(RedPeer::InMessage* message) { _cursor_cache.clear(); } void CursorChannel::set_cursor_mode() { _cursor_trigger.trigger(); } class CursorFactory: public ChannelFactory { public: CursorFactory() : ChannelFactory(RED_CHANNEL_CURSOR) {} virtual RedChannel* construct(RedClient& client, uint32_t id) { return new CursorChannel(client, id); } }; static CursorFactory factory; ChannelFactory& CursorChannel::Factory() { return factory; }