/*
*Copyright(c)2016, Jeffrey Lee
*Allrightsreserved.
*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: 
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
*/

#include "task.h"
#include "frontend.h"
#include "server.h"
#include "header.h"
#include "debug.h"
#include "keys.h"
#include <oslib/os.h>
#include <oslib/wimp.h>
#include <oslib/osfile.h>
#include <oslib/osmodule.h>
#include <oslib/osfind.h>
#include <oslib/osgbpb.h>
#include <oslib/osargs.h>
#include <oslib/taskmanager.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

/* Public vars - read/written by server callbacks */
static int pollword = 0;
static clipboard_text *client_text = NULL; /* Text received from VNC client (temporary, gets moved to clip_text) */
static clipboard_text *server_text = NULL; /* Text that was last sent to the server */
static bool own_clipboard = false;
static os_t clipboard_check_time = 0;

/* Private vars - only accessed by task */
static clipboard_text *clip_text = NULL; /* Text we're hosting on the clipboard (from client_text), or text we're reading in from the clipboard (gets moved to server_text) */
static int paste_msg = 0;
static int paste_offset = 0;
static int check_msg = 0;
static int fetch_msg = 0;

static const int accepted_messages[] =
{
  message_DATA_SAVE,
  message_DATA_SAVE_ACK,
  message_DATA_LOAD,
  message_RAM_FETCH,
  message_RAM_TRANSMIT,
  message_CLAIM_ENTITY,
  message_DATA_REQUEST,
  0
};

/* Check clipboard content 25cs after last key/mouse press */
#define CLIPBOARD_CHECK_DELAY 25

#define POLLWORD_PUT 1
#define POLLWORD_CLIPBOARD_TICK 2
#define POLLWORD_RELEASE 3
#define POLLWORD_TICK 4

clipboard_text *clipboard_alloc(struct vnc_client_s *source, const char *text, int len, int remain)
{
  clipboard_text *ret;
  if (xosmodule_alloc(sizeof(clipboard_text) + len + remain + 1, (void **) &ret))
  {
    return NULL;
  }
  ret->source = source;
  ret->refcount = 1;
  ret->write_offset = len;
  ret->len = len + remain;
  if (len)
  {
    memcpy(ret->text, text, len);
  }
  ret->text[len + remain] = 0;
  return ret;
}

clipboard_text *clipboard_addref(clipboard_text **text)
{
  xos_int_off();
  clipboard_text *t = *text;
  if (t)
  {
    t->refcount++;
  }
  xos_int_on();
  return t;
}

void clipboard_deref(clipboard_text **clip)
{
  xos_int_off();
  clipboard_text *local = *clip;
  *clip = NULL;
  if (local)
  {
    local->refcount--;
  }
  xos_int_on();
  if (local && !local->refcount)
  {
    xosmodule_free(local);
  }
}

static clipboard_text *translate_alphabet_to_8859_1(clipboard_text *clip)
{
  const int *alphabet = get_alphabet();
  if (alphabet)
  {
    /* Perform in-place conversion */
    for (int i=clip->len-1; i>=0; i--)
    {
      unsigned int unicode = alphabet[(unsigned int) clip->text[i]];
      if (unicode >= 256)
      {
        /* Undefined or outside latin-1 range */
        unicode = '?';
      }
      clip->text[i] = unicode;
    }
  }
  else
  {
    /* Input is UTF-8, so output may be shorter */
    const char *read = clip->text;
    char *write = clip->text;
    const char *end = clip->text + clip->len;
    while (read < end)
    {
      int c = *read++;
      if (c < 128)
      {
        /* 1-byte sequence */
      }
      else if (((c & 0xe0) == 0xc0) && ((read[0] & 0xc0) == 0x80))
      {
        /* 2-byte sequence */
        c = ((c & 0x1f) << 6) + (read[0] & 0x3f);
        read++;
      }
      else if (((c & 0xf0) == 0xe0) && ((read[0] & 0xc0) == 0x80) && ((read[1] & 0xc0) == 0x80))
      {
        /* 3-byte sequence */
        c = ((c & 0xf) << 12) + ((read[0] & 0x3f) << 6) + (read[1] & 0x3f);
        read+=2;
      }
      else if (((c & 0xf8) == 0xf0) && ((read[0] & 0xc0) == 0x80) && ((read[1] & 0xc0) == 0x80) && ((read[2] & 0xc0) == 0x80))
      {
        /* 4-byte sequence */
        c = ((c & 0x7) << 18) + ((read[0] & 0x3f) << 12) + ((read[1] & 0x3f) << 6) + (read[2] & 0x3f);
        read+=3;
      }
      else
      {
        /* Who knows! */
        c = '?';
      }
      if (c >= 256)
      {
        c = '?';
      }
      *write++ = c;
    }
    clip->len = clip->write_offset = write - clip->text;
    *write = 0;
  }
  return clip;
}

static clipboard_text *translate_8859_1_to_alphabet(clipboard_text *clip, bool inplace)
{
  const int *alphabet = get_alphabet();
  if (alphabet)
  {
    if (!inplace)
    {
      /* Copy to new clip */
      clipboard_text *t = clipboard_alloc(clip->source, clip->text, clip->len, 0);
      if (!t)
      {
        return clip;
      }
      clipboard_deref(&clip);
      clip = t;
    }
    /* Perform in-place conversion */
    for (int i=clip->len-1; i>=0; i--)
    {
      int c = unicode_to_alphabet(clip->text[i], alphabet);
      clip->text[i] = (c == -1 ? '?' : c);
    }
    return clip;
  }
  else
  {
    /* Output may be longer - count number of top-bit-set characters */
    int num=0;
    for (int i=clip->len-1; i>=0; i--)
    {
      if (clip->text[i] >= 128)
      {
        num++;
      }
    }
    if (!num)
    {
      return clip;
    }
    /* Allocate new block */
    clipboard_text *utf = clipboard_alloc(clip->source, NULL, 0, clip->len + num);
    if (!utf)
    {
      return clip;
    }
    const char *read = clip->text;
    char *write = utf->text;
    const char *end = clip->text + clip->len;
    while (read < end)
    {
      int c = *read++;
      if (c < 128)
      {
        *write++ = c;
      }
      else
      {
        *write++ = 0xc0 + (c >> 6);
        *write++ = 0x80 + (c & 0x3f);
      }
    }
    /* 'utf' should already have correct length + terminator */
    clipboard_deref(&clip);
    return utf;
  }
}

static void not_running()
{
  taskhandle = 0;
  dprintf("task: Stopping\n");
}

static void push_to_server()
{
  if (!global_config.enable_clipboard)
  {
    /* No change */
    clipboard_deref(&clip_text);
    return;
  }

  /* Translate to ISO 8859-1 */
  clip_text = translate_alphabet_to_8859_1(clip_text);

  if (server_text && (server_text->len == clip_text->len) && !memcmp(server_text->text, clip_text->text, clip_text->len))
  {
    /* No change */
    clipboard_deref(&clip_text);
    return;
  }
  dprintf("task: Pushing new clipboard text to server\n");
  xos_int_off();
  clipboard_text *old = server_text;
  server_text = clip_text;
  clip_text = NULL;
  xos_int_on();
  clipboard_deref(&old);
}

bool task_should_run()
{
  return global_config.enable_clipboard || global_config.enable_filter;
}

int main(int argc, char **argv)
{
  atexit(not_running);

  pollword = 0;
  own_clipboard = false;
  check_msg = 0;
  fetch_msg = 0;

  if (!task_should_run())
  {
    return 0;
  }

  dprintf("task: Starting\n");

  wimp_version_no version;
  taskhandle = wimp_initialise(wimp_VERSION_RO3, Module_Title, (wimp_message_list const *) accepted_messages, &version);

  /* Shrink our wimpslot to minimum */
  wimp_slot_size(1, -1, NULL, NULL, NULL);

  clipboard_check_time = os_read_monotonic_time() + CLIPBOARD_CHECK_DELAY;

  while(task_should_run())
  {
    wimp_block block;
    wimp_event_no event;
    if (own_clipboard || !clipboard_check_time || !global_config.enable_clipboard)
    {
      event = wimp_poll(wimp_MASK_NULL | wimp_GIVEN_POLLWORD | wimp_SAVE_FP, &block, &pollword);
    }
    else
    {
      /* Wake up at indicated time to check clipboard contents */
      event = wimp_poll_idle(wimp_GIVEN_POLLWORD | wimp_SAVE_FP, &block, clipboard_check_time, &pollword);
    }
    if (!task_should_run())
    {
      return 0;
    }
    switch (event)
    {
    case wimp_NULL_REASON_CODE:
      if (!own_clipboard)
      {
        os_t delta = os_read_monotonic_time() - clipboard_check_time;
        if (delta >= 0)
        {
          clipboard_check_time = 0;
          if (global_config.enable_clipboard)
          {
            dprintf("task: Checking clipboard content\n");
            clipboard_deref(&clip_text);
            block.message.size = sizeof(wimp_full_message_data_request) - sizeof(block.message.data.data_request.file_types) + 8;
            block.message.your_ref = 0;
            block.message.action = message_DATA_REQUEST;
            block.message.data.data_request.w = wimp_NO_ICON;
            block.message.data.data_request.i = 0;
            block.message.data.data_request.pos.x = 0;
            block.message.data.data_request.pos.y = 0;
            block.message.data.data_request.flags = wimp_DATA_REQUEST_CLIPBOARD;
            block.message.data.data_request.file_types[0] = 0xfff;
            block.message.data.data_request.file_types[1] = -1;
            if (xwimp_send_message(wimp_USER_MESSAGE_RECORDED, &block.message, wimp_BROADCAST))
            {
              dprintf("task: Data request failed\n");
            }
            else
            {
              check_msg = block.message.my_ref;
            }
          }
        }
      }
      break;
    case wimp_POLLWORD_NON_ZERO:
      {
        xos_int_off();
        int poll = pollword;
        pollword = 0;
        switch(poll)
        {
        case POLLWORD_CLIPBOARD_TICK:
          /* Just the server kicking us so that we check clipboard_check_time */
          xos_int_on();
          break;
        case POLLWORD_RELEASE:
          /* Release any claim we have on the clipboard */
          xos_int_on();
          if (own_clipboard)
          {
            dprintf("task: Releasing clipboard via pollword\n");
            own_clipboard = false;
            clipboard_deref(&clip_text);
          }
          break;
        case POLLWORD_PUT:
          {
            /* Claim the block */
            clipboard_text *old = clip_text;
            clip_text = client_text;
            client_text = NULL;
            xos_int_on();
            clipboard_deref(&old);
            if (clip_text)
            {
              paste_msg = 0; /* Abort any current paste op */
              check_msg = 0;
              fetch_msg = 0;
              /* Translate from ISO 8859-1 */
              clip_text = translate_8859_1_to_alphabet(clip_text, !global_config.broadcast_clipboard);
              if (!own_clipboard)
              {
                /* Claim the clipboard */
                dprintf("task: Client text received, claiming clipboard...\n");
                block.message.size = sizeof(wimp_full_message_claim_entity);
                block.message.your_ref = 0;
                block.message.action = message_CLAIM_ENTITY;
                block.message.data.claim_entity.flags = wimp_CLAIM_CLIPBOARD;
                own_clipboard = true;
                if (xwimp_send_message(wimp_USER_MESSAGE, &block.message, wimp_BROADCAST))
                {
                  dprintf("task: Claim failed!\n");
                  own_clipboard = false;
                }
              }
              else
              {
                dprintf("task: Client text received, clipboard already owned\n");
              }
            }
            else
            {
              xos_int_on();
              dprintf("task: Pollword non-zero but no client text to receive\n");
            }
          }
          break;
        case POLLWORD_TICK:
          /* Server requesting a framebuffer update callback */
          xos_int_on();
          xos_add_call_back(tick_callback,private_word); /* Should fire immediately */
          break;
        default:
          xos_int_on();
          break;
        }
      }
      break;
    case wimp_USER_MESSAGE:
    case wimp_USER_MESSAGE_RECORDED:
    case wimp_USER_MESSAGE_ACKNOWLEDGE:
      {
        switch (block.message.action)
        {
        case message_QUIT:
          {
            return 0;
          }
        case message_CLAIM_ENTITY:
          {
            if ((block.message.sender != taskhandle) && (block.message.data.claim_entity.flags & wimp_CLAIM_CLIPBOARD))
            {
              dprintf("task: Task %08x now owns clipboard\n", block.message.sender);
              if (own_clipboard)
              {
                clipboard_deref(&clip_text);
                own_clipboard = false;
              }
              /* Schedule a clipboard check for the future */
              task_clipboard_tick();
            }
            else
            {
//              dprintf("task: Ignoring message_CLAIM_ENTITY %08x %08x\n",block.message.sender, block.message.data.claim_entity.flags);
            }
          }
          break;

        /* Messages for when we own the clipboard */
        case message_DATA_REQUEST:
          {
            if (!(block.message.data.data_request.flags & wimp_DATA_REQUEST_CLIPBOARD))
            {
              dprintf("task: Ignoring unknown message_DATA_REQUEST flags %08x\n", block.message.data.data_request.flags);
              break;
            }
            else if (!own_clipboard)
            {
//              dprintf("task: Received data request but don't own clipboard (sender %08x)\n", block.message.sender);
            }
            else if (!clip_text)
            {
              dprintf("task: Received data request but don't have any clip text!\n");
            }
            else
            {
              wimp_t target = block.message.sender;
              dprintf("task: Received data request from %08x, offering data save\n", target);
              sprintf(block.message.data.data_xfer.file_name, "VNC-Client");
              block.message.size = sizeof(wimp_full_message_data_xfer) - sizeof(block.message.data.data_xfer.file_name) + ((strlen(block.message.data.data_xfer.file_name)+4)&~3);
              block.message.your_ref = block.message.my_ref;
              block.message.action = message_DATA_SAVE;
              /* window, icon, pos preserved */
              block.message.data.data_xfer.est_size = clip_text->len;
              block.message.data.data_xfer.file_type = 0xfff;
              if (xwimp_send_message(wimp_USER_MESSAGE, &block.message, target))
              {
                dprintf("task: message_DATA_SAVE failed!\n");
              }
              else
              {
                dprintf("task: message_DATA_SAVE my_ref = %08x\n",block.message.my_ref);
                paste_msg = block.message.my_ref;
                paste_offset = 0;
              }
            }
          }
          break;
        case message_DATA_SAVE_ACK:
          {
            dprintf("task: message_DATA_SAVE_ACK, your_ref = %08x, filename = \"%s\"\n",block.message.your_ref,block.message.data.data_xfer.file_name);
            if (block.message.your_ref == paste_msg)
            {
              paste_msg = 0;
              if (xosfile_save_stamped(block.message.data.data_xfer.file_name, block.message.data.data_xfer.file_type, clip_text->text, clip_text->text + clip_text->len))
              {
                dprintf("task: Save failed!\n");
              }
              else
              {
                dprintf("task: Save OK, sending load message\n");
                block.message.your_ref = block.message.my_ref;
                block.message.action = message_DATA_LOAD;
                xwimp_send_message(wimp_USER_MESSAGE_RECORDED, &block.message, block.message.sender);
              }
            }
          }
          break;
        case message_RAM_FETCH:
          {
            dprintf("task: message_DATA_RAM_FETCH, your_ref = %08x\n",block.message.your_ref);
            if (block.message.your_ref == paste_msg)
            {
              /* We're attempting to paste text */
              int len = block.message.data.ram_xfer.size;
              if (len >= clip_text->len - paste_offset)
              {
                len = clip_text->len - paste_offset;
              }
              if (len && xwimp_transfer_block(taskhandle, clip_text->text + paste_offset, block.message.sender, block.message.data.ram_xfer.addr, len))
              {
                dprintf("task: RAM transfer failed!\n");
                paste_msg = 0;
              }
              else
              {
                dprintf("task: RAM transfer OK, sending ACK message\n");
                block.message.your_ref = block.message.my_ref;
                block.message.action = message_RAM_TRANSMIT;
                wimp_event_no evt = (block.message.data.ram_xfer.size == len ? wimp_USER_MESSAGE_RECORDED : wimp_USER_MESSAGE); /* Send recorded messages until we transfer less data than was requested */
                block.message.data.ram_xfer.size = len;
                xwimp_send_message(evt, &block.message, block.message.sender);
                paste_offset += len;
                paste_msg = (evt == wimp_USER_MESSAGE ? 0 : block.message.my_ref);
              }
            }
            else if (block.message.your_ref == fetch_msg)
            {
              /* We're attempting to get text */
              dprintf("task: RAM fetch bounced, trying data save instead\n");
              fetch_msg = 0;

              sprintf(block.message.data.data_xfer.file_name, "<Wimp$Scrap>");
              block.message.size = sizeof(wimp_full_message_data_xfer) - sizeof(block.message.data.data_xfer.file_name) + ((strlen(block.message.data.data_xfer.file_name)+4)&~3);
              block.message.your_ref = check_msg;
              block.message.action = message_DATA_SAVE_ACK;
              block.message.data.data_xfer.w = wimp_NO_ICON;
              block.message.data.data_xfer.i = 0;
              block.message.data.data_xfer.pos.x = 0;
              block.message.data.data_xfer.pos.y = 0;              
              block.message.data.data_xfer.est_size = clip_text->len;
              block.message.data.data_xfer.file_type = 0xfff;
              if (xwimp_send_message(wimp_USER_MESSAGE_RECORDED, &block.message, block.message.sender))
              {
                dprintf("task: message_DATA_SAVE_ACK failed!\n");
                check_msg = 0;
              }
              else
              {
                /* Wait for the data load */
                check_msg = block.message.my_ref;
              }              
            }
          }
          break;

        /* Messages for when we're polling the clipboard */
        case message_DATA_SAVE:
          {
            if (block.message.your_ref == check_msg)
            {
              dprintf("task: Task %08x owns clipboard data of type %03x size %d\n", block.message.sender, block.message.data.data_xfer.file_type, block.message.data.data_xfer.est_size);
              check_msg = 0;
              if ((block.message.data.data_xfer.file_type == 0xfff) && (block.message.data.data_xfer.est_size > 0))
              {
                clipboard_deref(&clip_text);
                clip_text = clipboard_alloc(NULL, NULL, 0, block.message.data.data_xfer.est_size);
                if (clip_text)
                {
                  check_msg = block.message.my_ref;
                  dprintf("task: Trying RAM fetch\n");
                  block.message.size = sizeof(wimp_full_message_ram_xfer);
                  block.message.your_ref = block.message.my_ref;
                  block.message.action = message_RAM_FETCH;
                  block.message.data.ram_xfer.addr = clip_text->text;
                  block.message.data.ram_xfer.size = clip_text->len + 1; /* RAM transmit terminates when we receive less than we requested, so always request one more byte than necessary (which, if received, will overwrite our null terminator) */
                  if (xwimp_send_message(wimp_USER_MESSAGE_RECORDED, &block.message, block.message.sender))
                  {
                    dprintf("task: message_RAM_FETCH failed!\n");
                    check_msg = 0;
                  }
                  else
                  {
                    fetch_msg = block.message.my_ref;
                  }
                }
              }
            }
          }
          break;
        case message_RAM_TRANSMIT:
          {
            if (block.message.your_ref == fetch_msg)
            {
              dprintf("task: Received %d/%d bytes via RAM transfer\n", block.message.data.ram_xfer.size, clip_text->len - clip_text->write_offset);
              check_msg = 0;
              clip_text->write_offset += block.message.data.ram_xfer.size;
              if (clip_text->write_offset > clip_text->len)
              {
                /* Oh dear, the estimated length was bogus
                   Just reset our position and go round again
                   (will result in us only getting the end of the clipboard,
                   but it will avoid the RAM transfer protocol stalling) */
                clip_text->write_offset = 0;
                block.message.size = sizeof(wimp_full_message_ram_xfer);
                block.message.your_ref = block.message.my_ref;
                block.message.action = message_RAM_FETCH;
                block.message.data.ram_xfer.addr = clip_text->text;
                block.message.data.ram_xfer.size = clip_text->len + 1;
                if (xwimp_send_message(wimp_USER_MESSAGE_RECORDED, &block.message, block.message.sender))
                {
                  dprintf("task: message_RAM_FETCH failed!\n");
                  fetch_msg = 0;
                }
                else
                {
                  fetch_msg = block.message.my_ref;
                }
              }
              else
              {
                /* Transaction complete, push to server */
                fetch_msg = 0;
                clip_text->len = clip_text->write_offset; /* Just in case it was less than we were originall told */
                clip_text->text[clip_text->len] = 0; /* And make sure we put the terminator back, in case it was overwritten */
                push_to_server();
              }
            }
            else if (block.message.your_ref == paste_msg)
            {
              dprintf("task: message_RAM_TRANSMIT failed!\n");
              /* Just give up then */
              paste_msg = 0;
            }
          }
          break;
        case message_DATA_LOAD:
          {
            if (block.message.your_ref == check_msg)
            {
              dprintf("task: Data save worked OK, file '%s'\n", block.message.data.data_xfer.file_name);
              check_msg = 0;
              os_fw f = 0;
              xosfind_openinw(osfind_NO_PATH, block.message.data.data_xfer.file_name, NULL, &f);
              bool ok = (f != 0);
              int unread;
              if (ok && xosgbpb_readw(f, clip_text->text, clip_text->len, &unread)) ok = false;
              if (ok && unread) ok = false;
              osbool eof;
              if (ok && xosargs_read_eof_statusw(f, &eof)) ok = false;
              if (ok && !eof) ok = false;
              if (f) xosfind_closew(f);
              if (ok)
              {
                dprintf("task: Data read OK\n");

                /* Ack the message */
                block.message.your_ref = block.message.my_ref;
                block.message.action = message_DATA_LOAD_ACK;
                xwimp_send_message(wimp_USER_MESSAGE, &block.message, block.message.sender);

                push_to_server();
              }
              else
              {
                dprintf("task: Data load failed!\n");
              }
            }
          }
          break;

        default:
          dprintf("task: Unhandled wimp message %d %08x\n",event,block.message.action);
          break;
        }
        break;
      }
    }
  }
  return 0;
}

void task_clipboard_put(clipboard_text **clip, struct vnc_client_s *source, const char *text, int len, int remain)
{
  if (!global_config.enable_clipboard)
  {
    /* Throw away any partially-received data. This will protect against garbage data being copied to the local clipboard if clipboard support is disabled during the middle of a receive operation (and then re-enabled later on)
       However, note that you'll still end up with incorrect clipboard data if support is enabled in the middle of a receive operation (the start of the data will be missing) */
    clipboard_deref(clip);
    return;
  }
  if (!len)
  {
    return;
  }
  /* Copy the text to the RMA */
  if (!*clip || ((*clip)->write_offset + len + remain != (*clip)->len))
  {
    /* New block needed */
    clipboard_deref(clip);
    *clip = clipboard_alloc(source, text, len, remain);
  }
  else
  {
    /* Append to existing block */
    memcpy((*clip)->text + (*clip)->write_offset, text, len);
    (*clip)->write_offset += len;
  }
  if (!remain)
  {
    /* Data fully received, transfer block ownership to the Wimp task */
    clipboard_deref(&client_text);
    client_text = *clip;
    *clip = NULL;
    /* Set pollword to wake task and get it to talk to the Wimp */
    pollword = POLLWORD_PUT;

    /* Also broadcast the received data to other clients */
    if (global_config.broadcast_clipboard)
    {
      if (!server_text || (server_text->len != client_text->len) || memcmp(server_text->text, client_text->text, client_text->len))
      {
        /* It's new data */
        dprintf("task: Broadcasting received clipboard text to other clients\n");
        clipboard_deref(&server_text);
        server_text = clipboard_addref(&client_text);
      }
    }
  }
}

void task_start()
{
  if (!task_should_run())
  {
    return;
  }
  if (taskhandle == 0)
  {
    taskhandle = -1;
    if (xtaskmanager_start_task("vncserv_start_task"))
    {
      taskhandle = 0;
    }
  }
}

os_error *task_shutdown()
{
  if ((taskhandle != 0) && (taskhandle != -1))
  {
    os_error *err = xwimp_close_down(taskhandle);
    if (err)
    {
      return err;
    }
    dprintf("task: Killed\n");
    taskhandle = 0;
  }
  /* Release any RMA blocks */
  clipboard_deref(&client_text);
  clipboard_deref(&clip_text);
  clipboard_deref(&server_text);
  return NULL;
}

void task_clipboard_tick()
{
  if (!global_config.enable_clipboard)
  {
    return;
  }
  if (own_clipboard)
  {
    return;
  }
  /* Check clipboard in the future */
  if (!xos_read_monotonic_time(&clipboard_check_time))
  {
    clipboard_check_time += CLIPBOARD_CHECK_DELAY;
    if (!pollword) /* All we really care about is waking up the task, so don't clobber any existing pollword (especially POLLWORD_PUT!) */
    {
      pollword = POLLWORD_CLIPBOARD_TICK;
    }
  }
}

clipboard_text *task_clipboard_get()
{
  if (!global_config.enable_clipboard)
  {
    return NULL;
  }
  return server_text;
}

void task_clipboard_release(struct vnc_client_s *source)
{
  if (client_text && (!source || (client_text->source == source)))
  {
    clipboard_deref(&client_text);
  }
  /* TODO only release if from the above source */
  pollword = POLLWORD_RELEASE;
}

void task_tick()
{
  if (!pollword)
  {
    pollword = POLLWORD_TICK;
  }
}
