summaryrefslogtreecommitdiffstats
path: root/client/cursor_channel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'client/cursor_channel.cpp')
-rw-r--r--client/cursor_channel.cpp661
1 files changed, 661 insertions, 0 deletions
diff --git a/client/cursor_channel.cpp b/client/cursor_channel.cpp
new file mode 100644
index 00000000..a78eba35
--- /dev/null
+++ b/client/cursor_channel.cpp
@@ -0,0 +1,661 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#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<RedPixmap> _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<RedPixmap> _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<RedPixmap> _pixmap;
+ std::auto_ptr<RedPixmap> _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 responce(Application& application)
+ {
+ 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 (application.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<CursorData> _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 responce(Application& application)
+ {
+ _channel._cursor_visible = true;
+ if (application.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 responce(Application& application)
+ {
+ _channel._cursor_visible = false;
+ if (application.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 responce(Application& application)
+ {
+ _channel._cursor_visible = false;
+ _channel.clear_area();
+ if (_channel._cursor) {
+ _channel._cursor->unref();
+ _channel._cursor = NULL;
+ }
+ }
+
+private:
+ CursorChannel& _channel;
+};
+
+class CursorHandler: public MessageHandlerImp<CursorChannel, RED_CURSOR_MESSAGES_END> {
+public:
+ CursorHandler(CursorChannel& channel)
+ : MessageHandlerImp<CursorChannel, RED_CURSOR_MESSAGES_END>(channel) {}
+};
+
+class CursorModeEvent: public Event {
+public:
+ CursorModeEvent(CursorChannel& channel): _channel (channel) {}
+
+ virtual void responce(Application& application)
+ {
+ RedScreen* screen = _channel.screen();
+ if (!screen) {
+ return;
+ }
+ if (application.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<CursorModeEvent> 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<CursorHandler*>(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_events_loop().add_trigger(_cursor_trigger);
+}
+
+CursorChannel::~CursorChannel()
+{
+ ASSERT(!_cursor);
+}
+
+void CursorChannel::on_connect()
+{
+}
+
+void CursorChannel::on_disconnect()
+{
+ remove_cursor();
+ _cursor_cache.clear();
+ AutoRef<SyncEvent> 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<CursorRemoveEvent> 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<CursorData> cursor_ref(cursor);
+ AutoRef<CursorSetEvent> 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<CursorMoveEvent> event(new CursorMoveEvent(*this, move->postition.x,
+ move->postition.y));
+ get_client().push_event(*event);
+}
+
+void CursorChannel::handle_cursor_hide(RedPeer::InMessage* message)
+{
+ AutoRef<CursorHideEvent> 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;
+}
+