Turtle moved to UppHub

git-svn-id: svn://ultimatepp.org/upp/trunk@15649 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
cxl 2021-01-05 12:38:23 +00:00
parent c7e3f0ceb3
commit 3cc74e6760
16 changed files with 3 additions and 1976 deletions

View file

@ -1,84 +0,0 @@
template "U++ Basic Turtle application" main;
option "Create header" header;
text "Server URL" host = "localhost";
text "Connection port" port = "8888";
text "Connection limit" limit = "100";
option "Debug mode" debugmode;
option "Add GUI mode switch" guiflag;
@@<:PACKAGE:>.h
??header
#ifndef _<:PACKAGE:>_<:PACKAGE:>_h
#define _<:PACKAGE:>_<:PACKAGE:>_h
<:?guiflag:>
#ifdef flagTURTLEGUI
#include <Turtle/Turtle.h>
#else
#include <CtrlLib/CtrlLib.h>
#endif
<:/:>
#include <Turtle/Turtle.h>
<:.:>
using namespace Upp {
}
#endif
@@<:PACKAGE:>.cpp
<:? header :>#include "<:PACKAGE:>.h"<:.:><:? !header && !guiflag:>#include <Turtle/Turtle.h><:.:><:? !header && guiflag :>#ifdef flagTURTLEGUI
#include <Turtle/Turtle.h>
#else
#include <CtrlLib/CtrlLib.h>
#endif<:.:>
using namespace Upp;
void AppMainLoop()
{
// "Main" stuff should go in here...
}
<:?guiflag:>
#ifdef flagTURTLEGUI
<:.:>
CONSOLE_APP_MAIN
{
<:?debugmode:>
#ifdef _DEBUG
TurtleServer::DebugMode();
#endif
<:.:>
// MemoryLimitKb(100000000); // Can aid preventing DDoS attacks.
TurtleServer guiserver;
guiserver.Host("<:host:>");
guiserver.Port(<:port:>);
guiserver.MaxConnections(<:limit:>);
RunTurtleGui(guiserver, AppMainLoop);
}
<:?guiflag:>
#else
GUI_APP_MAIN
{
AppMainLoop();
}
#endif
<:.:>
@@<:PACKAGE:>.upp
<:?guiflag:>
uses (TURTLEGUI) Turtle;
uses (!TURTLEGUI) CtrlLib;
<:/:>
uses Turtle;
<:.:>
file<:?header:>
<:PACKAGE:>.h,<:.:>
<:PACKAGE:>.cpp;
mainconfig
"" = "TURTLEGUI"<:?guiflag:>,<:/:>;<:.:>
<:?guiflag:>"" = "GUI";<:.:>

View file

@ -1,117 +0,0 @@
template "U++ Turtle application with main window" main;
id "Main window class name" classname = PACKAGE;
select("No layouts", "Generate layout file", "Main window has layout", "Main window has OK/Cancel") "Layout" lay = 2;
option "Imagelist file" iml;
text "Server URL" host = "localhost";
text "Connection port" port = "8888";
text "Connection limit" limit = "100";
option "Debug mode" debugmode;
option "Add GUI mode switch" guiflag;
@@<:PACKAGE:>.h
#ifndef _<:PACKAGE:>_<:PACKAGE:>_h
#define _<:PACKAGE:>_<:PACKAGE:>_h
<:?guiflag:>
#ifdef flagTURTLEGUI
#include <Turtle/Turtle.h>
#else
#include <CtrlLib/CtrlLib.h>
#endif
<:/:>
#include <Turtle/Turtle.h>
<:.:>
namespace Upp {<:?lay:>
#define LAYOUTFILE <<:PACKAGE:>/<:PACKAGE:>.lay>
#include <CtrlCore/lay.h><:.:><:?iml:>
#define IMAGEFILE <<:PACKAGE:>/<:PACKAGE:>.iml>
#include <Draw/iml_header.h><:.:>
class <:classname:> : public <:lay > 1 ? "With" + classname + "Layout<TopWindow>" : "TopWindow":> {
public:
<:classname:>();
};
}
#endif
@@main.cpp
#include "<:PACKAGE:>.h"<:?iml:>
#define IMAGEFILE <<:PACKAGE:>/<:PACKAGE:>.iml>
#include <Draw/iml_source.h><:.:>
using namespace Upp;
<:classname:>::<:classname:>()
{<:?lay > 1:>
CtrlLayout<:(lay == 3 ? "OKCancel" : ""):>(*this, "Window title");<:.:>
}
void AppMainLoop()
{
// "Main" stuff should go in here...
<:classname:>().Run();
}
<:?guiflag:>
#ifdef flagTURTLEGUI
<:.:>
CONSOLE_APP_MAIN
{
<:?debugmode:>
#ifdef _DEBUG
TurtleServer::DebugMode();
#endif
<:.:>
// MemoryLimitKb(100000000); // Can aid preventing DDoS attacks.
TurtleServer guiserver;
guiserver.Host("<:host:>");
guiserver.Port(<:port:>);
guiserver.MaxConnections(<:limit:>);
RunTurtleGui(guiserver, AppMainLoop);
}
<:?guiflag:>
#else
GUI_APP_MAIN
{
AppMainLoop();
}
#endif
<:.:>
@@<:PACKAGE:>.lay
??lay
LAYOUT(<:classname:>Layout, 200, 100)<:?lay == 3:>
ITEM(Button, ok, SetLabel("OK").RightPosZ(90, 80).BottomPosZ(4, 22))
ITEM(Button, cancel, SetLabel("Cancel").RightPosZ(4, 80).BottomPosZ(4, 22))<:.:>
END_LAYOUT
@@<:PACKAGE:>.iml
??iml
@@<:PACKAGE:>.upp
<:?guiflag:>
uses (TURTLEGUI) Turtle;
uses (!TURTLEGUI) CtrlLib;
<:/:>
uses Turtle;
<:.:>
file
<:PACKAGE:>.h,
main.cpp<:?lay:>,
<:PACKAGE:>.lay<:.:><:?iml:>,
<:PACKAGE:>.iml<:.:>;
mainconfig
"" = "TURTLEGUI"<:?guiflag:>,<:/:>;<:.:>
<:?guiflag:>"" = "GUI";<:.:>

View file

@ -1,185 +0,0 @@
#include "Turtle.h"
#define LLOG(x) // LLOG(x)
#define LDUMP(x) // RDUMP(x)
#define LTIMING(x)
namespace Upp {
static int sHandleCount;
static Vector<int> sFreeHandles;
static TurtleServer::Draw sTurtleDraw;
static LRUCache<TurtleServer::ImageSysData, int64> sImageCache;
struct ImageSysDataMaker : LRUCache<TurtleServer::ImageSysData, int64>::Maker {
Image img;
int64 Key() const override
{
return img.GetSerialId();
}
int Make(TurtleServer::ImageSysData& object) const override
{
object.Init(img);
return img.GetLength();
}
};
static int sAllocImageHandle()
{
return sFreeHandles.GetCount() ? sFreeHandles.Pop() : sHandleCount++;
}
static void sFreeImageHandle(int handle)
{
sFreeHandles.Add(handle);
}
TurtleServer::ImageSysData::ImageSysData()
{
}
TurtleServer::ImageSysData::~ImageSysData()
{
SysImageReleased(image);
sFreeImageHandle(handle);
}
void TurtleServer::ImageSysData::Init(const Image& img)
{
image = img;
handle = sAllocImageHandle();
LLOG(Format("SetImage %`, size: %`, cache size: %",
handle, image.GetSize(), sImageCache.GetSize()));
TurtleServer::Put8(TurtleServer::SETIMAGE);
TurtleServer::Put16(handle);
TurtleServer::Put(image.GetSize());
Image um = Unmultiply(image);
const RGBA *end = ~um + um.GetLength();
for(const RGBA *s = ~um; s < end; s++) {
TurtleServer::Put8(s->r);
TurtleServer::Put8(s->g);
TurtleServer::Put8(s->b);
TurtleServer::Put8(s->a);
}
TurtleServer::stat_setimage++;
TurtleServer::stat_setimage_len += image.GetLength() * sizeof(RGBA);
SysImageRealized(image);
}
TurtleServer::Draw::Draw()
{
pos = { 0, 0 };
PaintOnly();
sysdraw.SetTarget(this);
SDraw::Init(TurtleServer::desktopsize);
}
void TurtleServer::Draw::Init(const Size& sz)
{
SDraw::Init(sz);
}
void TurtleServer::Draw::PutImage(Point p, const Image& img, const Rect& src)
{
LLOG(Format("Turtle::Draw::PutImage %`, size: %`, src: %`, id: %",
p, img.GetSize(), src, img.GetSerialId()));
TurtleServer::stat_putimage++;
ImageSysDataMaker m;
LLOG(Format("SysImage cache pixels %`, count: %",
sImageCache.GetSize(), sImageCache.GetCount()));
m.img = img;
ImageSysData& sd = sImageCache.Get(m);
if(Rect(img.GetSize()) == src) {
Point dp = p - pos;
if(abs(dp.x) < 256
&& abs(dp.y) < 256) {
TurtleServer::Put8(dp.x < 0
? dp.y < 0 ? TurtleServer::IMAGENN : TurtleServer::IMAGENP
: dp.y < 0 ? TurtleServer::IMAGEPN : TurtleServer::IMAGEPP);
TurtleServer::Put8(abs(dp.x));
TurtleServer::Put8(abs(dp.y));
TurtleServer::Put16(sd.handle);
pos = p;
sImageCache.Shrink(25000000, 5000); // Cache must be after Paint because of PaintOnly!
return;
}
}
TurtleServer::Put8(TurtleServer::IMAGE);
TurtleServer::Put16(sd.handle);
TurtleServer::Put(p);
TurtleServer::Put(src);
pos = p;
sImageCache.Shrink(25000000, 5000); // Cache must be after Paint because of PaintOnly!
}
void TurtleServer::Draw::PutRect(const Rect& r, Color color)
{
LLOG("TurtleDraw::PutRect " << r << ", color " << color);
TurtleServer::stat_putrect++;
Point p = r.TopLeft();
if(color == InvertColor()) {
TurtleServer::Put8(TurtleServer::INVERTRECT);
TurtleServer::Put(r);
}
else {
Size sz = r.GetSize();
Point dp = p - pos;
if(abs(dp.x) < 256
&& abs(dp.y) < 256
&& sz.cx < 256
&& sz.cy < 256
&& 0) { // FIXME: ?
TurtleServer::Put8(dp.x < 0
? dp.y < 0 ? TurtleServer::RECTNN : TurtleServer::RECTNP
: dp.y < 0 ? TurtleServer::RECTPN : TurtleServer::RECTPP);
TurtleServer::Put8(abs(dp.x));
TurtleServer::Put8(abs(dp.y));
TurtleServer::Put8(sz.cx);
TurtleServer::Put8(sz.cy);
}
else {
TurtleServer::Put8(TurtleServer::RECT);
TurtleServer::Put(r);
}
TurtleServer::Put8(color.GetR());
TurtleServer::Put8(color.GetG());
TurtleServer::Put8(color.GetB());
}
pos = p;
}
SystemDraw& TurtleServer::BeginDraw()
{
return sTurtleDraw.sysdraw;
}
void TurtleServer::CommitDraw()
{
if(recieved_update_serial < update_serial -1) // Falling behind, wait.
SyncClient();
Flush();
}
void TurtleServer::SetCanvasSize(const Size& sz)
{
sTurtleDraw.Init(sz);
}
void TurtleServer::ResetImageCache()
{
sImageCache.Clear();
sFreeHandles.Clear();
sHandleCount = 0;
}
}

View file

@ -1,296 +0,0 @@
#include "Turtle.h"
#define LLOG(x) // RLOG(x)
#define LDUMP(x) // RDUMP(x)
#define LTIMING(x)
namespace Upp {
static bool sQuit; // Ctrl::IsEndSession() would be much better to use had it been implemented.
static BiVector<String> sEventQueue;
Point ReadPoint(CParser& p)
{
Point pt;
pt.x = p.ReadInt();
pt.y = p.ReadInt();
return pt;
}
bool TurtleServer::ProcessEvent(bool *quit)
{
if(!IsWaitingEvent())
return false;
while(sEventQueue.GetCount() >= 2
&& *sEventQueue[0] == 'M'
&& *sEventQueue[1] == 'M')
sEventQueue.DropHead(); // MouseMove compression
String event = sEventQueue[0];
sEventQueue.DropHead();
LLOG("Processing event " << event);
CParser p(event);
try
{
if(p.Id("i"))
{
ResetImageCache();
}
else
if(p.Id("R"))
{
Resize(p);
}
else
if(p.Id("M"))
{
MouseMove(p);
}
else
if(p.Id("W"))
{
MouseWheel(p);
}
else
if(p.Id("I"))
{
mousein = true;
}
else
if(p.Id("O"))
{
mousebuttons = 0;
mousein = false;
}
else
if(p.Id("D"))
{
MouseButton(Ctrl::DOWN, p);
}
else
if(p.Id("U"))
{
MouseButton(Ctrl::UP, p);
}
else
if(p.Id("K"))
{
KeyDown(event, p);
}
else
if(p.Id("k"))
{
KeyUp(event, p);
}
else
if(p.Id("C"))
{
KeyPress(event, p);
}
}
catch(const CParser::Error& e)
{
LLOG("ProcessEvent() -> Parser error");
}
return true;
}
void TurtleServer::WaitEvent(int ms)
{
websocket.Do();
SocketWaitEvent we;
websocket.AddTo(we);
we.Wait(ms);
}
bool TurtleServer::IsWaitingEvent()
{
websocket.Do();
String s = websocket.Receive();
if(websocket.IsClosed()) {
Ctrl::EndSession();
sQuit = true; // Ugly..
return false;
}
if(s.GetCount() == 0)
return sEventQueue.GetCount();
LLOG("Received data " << s);
StringStream ss(s);
while(!ss.IsEof()) {
String s = ss.GetLine();
CParser p(s);
try
{
if(p.Id("S")) {
uint32 l = p.ReadNumber();
uint32 h = p.ReadNumber();
recieved_update_serial = MAKEQWORD(l, h);
stat_client_ms = p.ReadNumber();
}
else
sEventQueue.AddTail(s);
}
catch(const CParser::Error& e)
{
LLOG("IsWaitingEvent() -> Parser error.");
}
}
if(recieved_update_serial == serial_0) {
serial_0 = 0;
stat_roundtrip_ms = msecs() - serial_time0;
serial_time0 = Null;
}
if(websocket.IsError())
LLOG("ERROR: " << websocket.GetErrorDesc());
return sEventQueue.GetCount();
}
void TurtleServer::SyncClient()
{
while(recieved_update_serial < update_serial && !sQuit) {
Ctrl::GuiSleep(10);
IsWaitingEvent();
}
}
void TurtleServer::MouseButton(dword event, CParser& p)
{
auto MaxDistance = [](Point a, Point b) -> int
{
return IsNull(a) ? INT_MAX : max(abs(a.x - b.x), abs(a.y - b.y));
};
static int64 sMouseDownTime;
static Point sMouseDownPos;
int bt = p.ReadInt();
Point pt = ReadPoint(p);
int64 tm = p.ReadInt64();
dword down = (dword) event == Ctrl::DOWN;
dword bt2 = decode(bt, 0, (1 << 0), 2, (1 << 1), (1 << 2));
mousebuttons = (mousebuttons & ~bt2) | (-down & bt2); // Toggles button flags.
if(event == Ctrl::DOWN) {
if(MaxDistance(sMouseDownPos, pt) < GUI_DragDistance() && tm - sMouseDownTime < 800) {
event = Ctrl::DOUBLE;
sMouseDownTime = 0;
}
else {
sMouseDownPos = pt;
sMouseDownTime = tm;
}
}
ReadModifierKeys(p);
Ctrl::DoMouseFB(decode(bt, 0, Ctrl::LEFT, 2, Ctrl::RIGHT, Ctrl::MIDDLE) | event, pt, 0);
}
void TurtleServer::MouseWheel(CParser& p)
{
double w = p.ReadDouble();
Point pt = ReadPoint(p);
ReadModifierKeys(p);
Ctrl::DoMouseFB(Ctrl::MOUSEWHEEL, pt, w < 0 ? 120 : -120);
}
void TurtleServer::MouseMove(CParser& p)
{
Point pt = ReadPoint(p);
ReadModifierKeys(p);
Ctrl::DoMouseFB(Ctrl::MOUSEMOVE, pt, 0);
}
void TurtleServer::KeyDown(const String& event, CParser& p)
{
int count = 1;
int code = p.ReadInt();
int which = p.ReadInt();
for(;;) {
if(sEventQueue.GetCount()
&& sEventQueue[0] == event) { // Chrome autorepeat
sEventQueue.DropHead();
count++;
}
else
if(sEventQueue.GetCount() >= 2
&& *sEventQueue[0] == 'C'
&& sEventQueue[1] == event) { // Firefox autorepeat
String h = sEventQueue[0];
sEventQueue.DropHead();
sEventQueue.DropHead();
sEventQueue.AddHead(h);
count++;
}
else
break;
}
ReadModifierKeys(p);
Ctrl::DoKeyFB(TranslateWebKeyToK(which), count);
}
void TurtleServer::KeyUp(const String& event, CParser& p)
{
int code = p.ReadInt();
int which = p.ReadInt();
ReadModifierKeys(p);
Ctrl::DoKeyFB(TranslateWebKeyToK(which) | K_KEYUP, 1);
}
void TurtleServer::KeyPress(const String& event, CParser& p)
{
int code = p.ReadInt();
int which = p.ReadInt();
ReadModifierKeys(p);
while(sEventQueue.GetCount() && sEventQueue[0] == event) // 'K_'s are not there anymore
sEventQueue.DropHead();
if(which && !GetAlt() && !GetCtrl() && findarg(which, 0x09, 0x0D, 0x20) < 0)
Ctrl::DoKeyFB(which, 1);
}
void TurtleServer::Resize(CParser& p)
{
desktopsize = ReadPoint(p);
SetCanvasSize(desktopsize);
Ctrl::SetDesktopSize(desktopsize);
}
void TurtleServer::SetMouseCursor(const Image& image)
{
int64 q = image.GetAuxData();
if(q) {
Put8(STD_CURSORIMAGE);
Put8(clamp((int)q, 1, 16));
}
else {
Point pt = image.GetHotSpot();
String h;
h << "url('data:image/png;base64,"
<< Base64Encode(PNGEncoder().SaveString(image))
<< "') " << pt.x << ' ' << pt.y << ", default";
Put8(SETCURSORIMAGE);
Put16(0); // TODO: Cursor cache
Put(h);
Put8(MOUSECURSOR);
Put16(0); // TODO: Cursor cache
}
}
}

View file

@ -1,43 +0,0 @@
#include "Turtle.h"
#define LLOG(x) // LLOG(x)
#define LDUMP(x) // RDUMP(x)
#define LTIMING(x)
namespace Upp {
String TurtleServer::host = "localhost";
int TurtleServer::port = 8888;
String TurtleServer::ip = "0.0.0.0";
int TurtleServer::connection_limit = 100;
bool TurtleServer::debugmode;
WebSocket TurtleServer::websocket;
int TurtleServer::mainpid;
bool TurtleServer::quit;
bool TurtleServer::mousein;
dword TurtleServer::mousebuttons = 0;
dword TurtleServer::modifierkeys = 0;
Size TurtleServer::desktopsize = Size(1024, 1024);
int64 TurtleServer::update_serial = 0;
int64 TurtleServer::recieved_update_serial;
int TurtleServer::serial_time0 = Null;
int64 TurtleServer::serial_0 = 0;
Time TurtleServer::stat_started;
int64 TurtleServer::stat_data_send;
int TurtleServer::stat_putrect;
int TurtleServer::stat_putimage;
int TurtleServer::stat_setimage;
int64 TurtleServer::stat_setimage_len;
int TurtleServer::stat_roundtrip_ms;
int TurtleServer::stat_client_ms;
Event<int, String> TurtleServer::WhenConnect;
Event<int> TurtleServer::WhenTerminate;
Event<> TurtleServer::WhenDisconnect;
void RunTurtleGui(TurtleServer& gui, Event<> app_main)
{
if(TurtleServer::StartSession())
RunVirtualGui((VirtualGui &) gui, app_main);
}
}

View file

@ -1,137 +0,0 @@
#include "Turtle.h"
namespace Upp {
#define WEBKEY(x) x
const static VectorMap<dword, dword> sKeyCodeMap = {
{ WEBKEY(3), K_BREAK },
{ WEBKEY(8), K_BACKSPACE },
{ WEBKEY(9), K_TAB },
{ WEBKEY(13), K_RETURN },
{ WEBKEY(16), K_SHIFT_KEY },
{ WEBKEY(17), K_CTRL_KEY },
{ WEBKEY(18), K_ALT_KEY },
{ WEBKEY(20), K_CAPSLOCK },
{ WEBKEY(27), K_ESCAPE },
{ WEBKEY(32), K_SPACE },
{ WEBKEY(33), K_PAGEUP },
{ WEBKEY(34), K_PAGEDOWN },
{ WEBKEY(35), K_END },
{ WEBKEY(36), K_HOME },
{ WEBKEY(37), K_LEFT },
{ WEBKEY(38), K_UP },
{ WEBKEY(39), K_RIGHT },
{ WEBKEY(40), K_DOWN },
{ WEBKEY(45), K_INSERT },
{ WEBKEY(46), K_DELETE },
{ WEBKEY('A'), K_A },
{ WEBKEY('B'), K_B },
{ WEBKEY('C'), K_C },
{ WEBKEY('D'), K_D },
{ WEBKEY('E'), K_E },
{ WEBKEY('F'), K_F },
{ WEBKEY('G'), K_G },
{ WEBKEY('H'), K_H },
{ WEBKEY('I'), K_I },
{ WEBKEY('J'), K_J },
{ WEBKEY('K'), K_K },
{ WEBKEY('L'), K_L },
{ WEBKEY('M'), K_M },
{ WEBKEY('N'), K_N },
{ WEBKEY('O'), K_O },
{ WEBKEY('P'), K_P },
{ WEBKEY('Q'), K_Q },
{ WEBKEY('R'), K_R },
{ WEBKEY('S'), K_S },
{ WEBKEY('T'), K_T },
{ WEBKEY('U'), K_U },
{ WEBKEY('V'), K_V },
{ WEBKEY('W'), K_W },
{ WEBKEY('X'), K_X },
{ WEBKEY('Y'), K_Y },
{ WEBKEY('Z'), K_Z },
{ WEBKEY('0'), K_0 },
{ WEBKEY('1'), K_1 },
{ WEBKEY('2'), K_2 },
{ WEBKEY('3'), K_3 },
{ WEBKEY('4'), K_4 },
{ WEBKEY('5'), K_5 },
{ WEBKEY('6'), K_6 },
{ WEBKEY('7'), K_7 },
{ WEBKEY('8'), K_8 },
{ WEBKEY('9'), K_9 },
{ WEBKEY(96), K_NUMPAD0 },
{ WEBKEY(97), K_NUMPAD1 },
{ WEBKEY(98), K_NUMPAD2 },
{ WEBKEY(99), K_NUMPAD3 },
{ WEBKEY(100), K_NUMPAD4 },
{ WEBKEY(101), K_NUMPAD5 },
{ WEBKEY(102), K_NUMPAD6 },
{ WEBKEY(103), K_NUMPAD7 },
{ WEBKEY(104), K_NUMPAD8 },
{ WEBKEY(105), K_NUMPAD9 },
{ WEBKEY(106), K_MULTIPLY },
{ WEBKEY(107), K_ADD },
{ WEBKEY(108), K_SEPARATOR },
{ WEBKEY(109), K_SUBTRACT },
{ WEBKEY(110), K_DECIMAL },
{ WEBKEY(111), K_DIVIDE },
{ WEBKEY(145), K_SCROLL },
{ WEBKEY(112), K_F1 },
{ WEBKEY(113), K_F2 },
{ WEBKEY(114), K_F3 },
{ WEBKEY(115), K_F4 },
{ WEBKEY(116), K_F5 },
{ WEBKEY(117), K_F6 },
{ WEBKEY(118), K_F7 },
{ WEBKEY(119), K_F8 },
{ WEBKEY(120), K_F9 },
{ WEBKEY(121), K_F10 },
{ WEBKEY(122), K_F11 },
{ WEBKEY(123), K_F12 },
{ WEBKEY(219), K_CTRL_LBRACKET },
{ WEBKEY(221), K_CTRL_RBRACKET },
{ WEBKEY(173), K_CTRL_MINUS }, // Firefox specific.
{ WEBKEY(189), K_CTRL_MINUS },
{ WEBKEY(192), K_CTRL_GRAVE },
{ WEBKEY(191), K_CTRL_SLASH },
{ WEBKEY(220), K_CTRL_BACKSLASH },
{ WEBKEY(188), K_CTRL_COMMA },
{ WEBKEY(190), K_CTRL_PERIOD },
{ WEBKEY(59) , K_CTRL_SEMICOLON }, // Firefox specific.
{ WEBKEY(186), K_CTRL_SEMICOLON },
{ WEBKEY(61) , K_CTRL_EQUAL }, // Firefox specific.
{ WEBKEY(187), K_CTRL_EQUAL },
{ WEBKEY(222), K_CTRL_APOSTROPHE },
};
#undef WEBKEY
dword TurtleServer::TranslateWebKeyToK(dword key)
{
int i = sKeyCodeMap.Find(key);
if(i < 0)
return key | K_DELTA;
key = sKeyCodeMap[i];
if(key == K_ALT_KEY || key == K_CTRL_KEY || key == K_SHIFT_KEY)
return key;
if(GetAlt()) key |= K_ALT;
if(GetCtrl()) key |= K_CTRL;
if(GetShift()) key |= K_SHIFT;
return key;
}
void TurtleServer::ReadModifierKeys(CParser& p)
{
const char *s = p.GetPtr();
if(*s && *s++ == '1') modifierkeys |= KM_SHIFT; else modifierkeys &= ~KM_SHIFT;
if(*s && *s++ == '1') modifierkeys |= KM_CTRL; else modifierkeys &= ~KM_CTRL;
if(*s && *s++ == '1') modifierkeys |= KM_ALT; else modifierkeys &= ~KM_ALT;
}
}

View file

@ -1,116 +0,0 @@
#include "Turtle.h"
#include "Turtle.brc"
#ifdef PLATFORM_POSIX
#include <sys/wait.h>
#endif
#define LLOG(x) // RLOG(x)
#define LDUMP(x) // RDUMP(x)
#define LTIMING(x)
namespace Upp {
static Vector<int> sChildPids;
static bool sSendTurtleHtml(TcpSocket& s, const String& host, int port)
{
HttpHeader h;
if(!h.Read(s))
return false;
LLOG("Sending Turtle HTML");
String html = String(turtle_html, turtle_html_length);
html.Replace("%%host%%", Format("ws://%s:%d", host, port));
return HttpResponse(s, h.scgi, 200, "OK", "text/html", html);
}
static void sUpdateChildList()
{
#ifdef PLATFORM_POSIX
int i = 0;
while(i < sChildPids.GetCount()) {
if(sChildPids[i] && waitpid(sChildPids[i], 0, WNOHANG | WUNTRACED) > 0) {
TurtleServer::WhenTerminate(sChildPids[i]);
sChildPids.Remove(i);
}
else ++i;
}
#endif
}
void TurtleServer::Broadcast(int signal)
{
#ifdef PLATFORM_POSIX
if(getpid() == mainpid)
for(int i = 0; i < sChildPids.GetCount(); i++)
kill(sChildPids[i], signal);
#endif
}
bool TurtleServer::StartSession()
{
// TODO: See if we can add secure websocket (wss://) support.
LLOG("Connect");
#ifdef PLATFORM_POSIX
mainpid = getpid();
#endif
IpAddrInfo ipinfo;
ipinfo.Execute(ip, port, IpAddrInfo::FAMILY_IPV4);
TcpSocket server;
#ifdef _DEBUG
int cnt = 0;
while(!server.Listen(ipinfo, port, 5, false, true)) {
LLOG("Trying to start listening (other process using the same port?) " << ++cnt);
Sleep(1000);
}
#else
if(!server.Listen(ipinfo, port, 5, false, true)) {
LLOG("Cannot open server socket for listening!");
Exit(1);
}
#endif
LLOG("Starting to listen on " << port << ", pid: " << getpid());
for(;;) {
sUpdateChildList();
if(server.IsError())
server.ClearError();
TcpSocket socket;
if(!socket.Accept(server))
continue;
if(!sSendTurtleHtml(socket, host, port))
continue;
websocket.NonBlocking();
while(!websocket.Accept(server))
Sleep(20);
LLOG("Websocket connection accepted. IP: " << websocket.GetPeerAddr());
#ifdef PLATFORM_POSIX
if(sChildPids.GetCount() >= connection_limit)
continue;
if(debugmode)
break;
int newpid = fork();
if(!newpid)
break;
else {
LLOG("Process forked. Pid: " << newpid);
sChildPids.Add(newpid);
WhenConnect(newpid, websocket.GetPeerAddr());
continue;
}
#else
break;
#endif
}
server.Close();
stat_started = GetSysTime();
return true;
}
}

View file

@ -1,105 +0,0 @@
#include "Turtle.h"
#define LLOG(x) // LLOG(x)
#define LDUMP(x) // RDUMP(x)
#define LTIMING(x)
namespace Upp {
static TurtleServer::Stream sTurtleStream;
TurtleServer::Stream::Stream()
{
Reset();
}
void TurtleServer::Stream::Out(const void *data, dword size)
{
zlib.Put(data, (int) size);
}
String TurtleServer::Stream::FlushStream()
{
Flush();
zlib.End();
String s = zlib.Get();
Reset();
return s;
}
void TurtleServer::Stream::Reset()
{
zlib.Clear();
zlib.Compress();
hasdata = false;
}
void TurtleServer::Put8(int x)
{
sTurtleStream.hasdata = true;
sTurtleStream.Put(x);
}
void TurtleServer::Put16(int x)
{
Put8(LOBYTE(x));
Put8(HIBYTE(x));
}
void TurtleServer::Put32(int x)
{
Put16(LOWORD(x));
Put16(HIWORD(x));
}
void TurtleServer::Put(Point p)
{
// TODO: Clamp?
Put16(p.x);
Put16(p.y);
}
void TurtleServer::Put(Size sz)
{
Put((Point)sz);
}
void TurtleServer::Put(const Rect& r)
{
Put(r.TopLeft());
Put(r.GetSize());
}
void TurtleServer::Put(const String& s)
{
Put32(s.GetLength());
sTurtleStream.hasdata = true;
sTurtleStream.Put(s);
}
//void Turtle_PutLink(const String& link) // FIXME
//{
// TurtleServer::Put8(OPENLINK);
// TurtleServer::Put(link);
//}
void TurtleServer::Flush()
{
if(!sTurtleStream.hasdata || websocket.IsClosed())
return;
websocket.SendBinary(ZCompress(String(DISABLESENDING, 1))); // Do not send events until data transfered and processed
int64 x = ++update_serial;
if(IsNull(serial_time0)) {
serial_time0 = msecs();
serial_0 = update_serial;
}
Put8(UPDATESERIAL);
Put32(LODWORD(x));
Put32(HIDWORD(x));
String s = sTurtleStream.FlushStream();
stat_data_send += s.GetCount();
LLOG("Sending " << s.GetLength());
websocket.SendBinary(s);
}
}

View file

@ -1 +0,0 @@
BINARY(turtle_html, "Turtle.html")

View file

@ -1,169 +0,0 @@
#ifndef _VirtualGui_Turtle_h
#define _VirtualGui_Turtle_h
#include <CtrlLib/CtrlLib.h>
namespace Upp {
class TurtleServer : public VirtualGui {
public:
TurtleServer() {}
TurtleServer(const String& host, int port) { Host(host).Port(port); }
TurtleServer(const String& ip, String& host, int port) { Bind(ip).Host(host).Port(port); }
TurtleServer& Bind(const String& addr) { TurtleServer::ip = addr; return *this; }
TurtleServer& Host(const String& host) { TurtleServer::host = host; return *this; }
TurtleServer& Port(int port) { TurtleServer::port = port; return *this; }
TurtleServer& MaxConnections(int limit) { TurtleServer::connection_limit = max(1, limit); return *this; }
static void DebugMode(bool b = true) { TurtleServer::debugmode = b; }
static Event<int, String> WhenConnect;
static Event<int> WhenTerminate;
static Event<> WhenDisconnect;
private:
virtual dword GetOptions() { return GUI_SETMOUSECURSOR; }
virtual Size GetSize() { return desktopsize; }
virtual dword GetMouseButtons() { return mousebuttons; }
virtual dword GetModKeys() { return modifierkeys; }
virtual bool IsMouseIn() { return mousein; }
virtual bool ProcessEvent(bool *quit);
virtual void WaitEvent(int ms);
virtual bool IsWaitingEvent();
virtual void SetMouseCursor(const Image& image);
virtual void SetCaret(const Rect& caret) {}
virtual SystemDraw& BeginDraw();
virtual void CommitDraw();
virtual void WakeUpGuiThread() {}
virtual void Quit() { WhenDisconnect(); }
private:
void MouseButton(dword event, CParser& p);
void MouseWheel(CParser& p);
void MouseMove(CParser& p);
void KeyDown(const String& event, CParser& p);
void KeyUp(const String& event, CParser& p);
void KeyPress(const String& event, CParser& p);
void Resize(CParser& p);
void ReadModifierKeys(CParser& p);
dword TranslateWebKeyToK(dword key);
static void Broadcast(int signal);
void SyncClient();
public:
struct Stream : OutStream
{
Stream();
void Out(const void *data, dword size) final;
String FlushStream();
void Reset();
Zlib zlib;
bool hasdata;
};
struct ImageSysData
{
ImageSysData();
~ImageSysData();
void Init(const Image& img);
Image image;
int handle;
};
struct Draw : SDraw
{
Draw();
void Init(const Size& sz);
void PutImage(Point p, const Image& img, const Rect& src) final;
void PutRect(const Rect& r, Color color) final;
Point pos;
SystemDraw sysdraw;
};
friend struct TurtleServer::Draw;
friend struct TurtleServer::ImageSysData;
public:
enum Commands {
RECT = 0,
IMAGE = 1,
SETIMAGE = 2,
INVERTRECT = 3,
STD_CURSORIMAGE = 4,
SETCURSORIMAGE = 5,
MOUSECURSOR = 6,
DISABLESENDING = 7,
UPDATESERIAL = 8,
IMAGEPP = 9,
IMAGENP = 10,
IMAGEPN = 11,
IMAGENN = 12,
RECTPP = 13,
RECTNP = 14,
RECTPN = 15,
RECTNN = 16,
SETCARET = 17,
HORZDRAGLINE = 18,
VERTDRAGLINE = 19,
OPENLINK = 20,
};
private:
static void Put8(int x);
static void Put16(int x);
static void Put32(int x);
static void Put(Point p);
static void Put(Size sz);
static void Put(const Rect& r);
static void Put(const String& s);
static void Flush();
static void SetCanvasSize(const Size& sz);
static void ResetImageCache();
private:
static WebSocket websocket;
static dword mousebuttons;
static dword modifierkeys;
static Size desktopsize;
static int mainpid;
static int64 update_serial;
static int64 recieved_update_serial;
static int64 serial_0;
static int serial_time0;
static bool quit;
static String host;
static int port;
static String ip;
static int connection_limit;
static bool debugmode;
static bool mousein;
public:
// Statistics.
static Time stat_started;
static int64 stat_data_send;
static int stat_putrect;
static int stat_putimage;
static int stat_setimage;
static int64 stat_setimage_len;
static int stat_roundtrip_ms;
static int stat_client_ms;
private:
static bool StartSession();
friend void RunTurtleGui(TurtleServer&, Event<>);
};
void RunTurtleGui(TurtleServer& gui, Event<> app_main);
}
#endif

View file

@ -1,587 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
html, body {
width: 100%;
height: 100%;
margin: 0px;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="1000" height="1000" style="border:0px" tabindex="1" oncontextmenu="return false;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
// zlib.js for inflate
/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */(function() {'use strict';function m(b){throw b;}var n=void 0,r=this;function s(b,d){var a=b.split("."),c=r;!(a[0]in c)&&c.execScript&&c.execScript("var "+a[0]);for(var f;a.length&&(f=a.shift());)!a.length&&d!==n?c[f]=d:c=c[f]?c[f]:c[f]={}};var u="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array;function v(b){var d=b.length,a=0,c=Number.POSITIVE_INFINITY,f,e,g,h,k,l,q,p,t;for(p=0;p<d;++p)b[p]>a&&(a=b[p]),b[p]<c&&(c=b[p]);f=1<<a;e=new (u?Uint32Array:Array)(f);g=1;h=0;for(k=2;g<=a;){for(p=0;p<d;++p)if(b[p]===g){l=0;q=h;for(t=0;t<g;++t)l=l<<1|q&1,q>>=1;for(t=l;t<f;t+=k)e[t]=g<<16|p;++h}++g;h<<=1;k<<=1}return[e,a,c]};function w(b,d){this.g=[];this.h=32768;this.d=this.f=this.a=this.l=0;this.input=u?new Uint8Array(b):b;this.m=!1;this.i=x;this.r=!1;if(d||!(d={}))d.index&&(this.a=d.index),d.bufferSize&&(this.h=d.bufferSize),d.bufferType&&(this.i=d.bufferType),d.resize&&(this.r=d.resize);switch(this.i){case y:this.b=32768;this.c=new (u?Uint8Array:Array)(32768+this.h+258);break;case x:this.b=0;this.c=new (u?Uint8Array:Array)(this.h);this.e=this.z;this.n=this.v;this.j=this.w;break;default:m(Error("invalid inflate mode"))}}
var y=0,x=1,z={t:y,s:x};
w.prototype.k=function(){for(;!this.m;){var b=A(this,3);b&1&&(this.m=!0);b>>>=1;switch(b){case 0:var d=this.input,a=this.a,c=this.c,f=this.b,e=n,g=n,h=n,k=c.length,l=n;this.d=this.f=0;e=d[a++];e===n&&m(Error("invalid uncompressed block header: LEN (first byte)"));g=e;e=d[a++];e===n&&m(Error("invalid uncompressed block header: LEN (second byte)"));g|=e<<8;e=d[a++];e===n&&m(Error("invalid uncompressed block header: NLEN (first byte)"));h=e;e=d[a++];e===n&&m(Error("invalid uncompressed block header: NLEN (second byte)"));h|=
e<<8;g===~h&&m(Error("invalid uncompressed block header: length verify"));a+g>d.length&&m(Error("input buffer is broken"));switch(this.i){case y:for(;f+g>c.length;){l=k-f;g-=l;if(u)c.set(d.subarray(a,a+l),f),f+=l,a+=l;else for(;l--;)c[f++]=d[a++];this.b=f;c=this.e();f=this.b}break;case x:for(;f+g>c.length;)c=this.e({p:2});break;default:m(Error("invalid inflate mode"))}if(u)c.set(d.subarray(a,a+g),f),f+=g,a+=g;else for(;g--;)c[f++]=d[a++];this.a=a;this.b=f;this.c=c;break;case 1:this.j(B,C);break;case 2:aa(this);
break;default:m(Error("unknown BTYPE: "+b))}}return this.n()};
var D=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],E=u?new Uint16Array(D):D,F=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,258,258],G=u?new Uint16Array(F):F,H=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0],I=u?new Uint8Array(H):H,J=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],K=u?new Uint16Array(J):J,L=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,
13],M=u?new Uint8Array(L):L,N=new (u?Uint8Array:Array)(288),O,P;O=0;for(P=N.length;O<P;++O)N[O]=143>=O?8:255>=O?9:279>=O?7:8;var B=v(N),Q=new (u?Uint8Array:Array)(30),R,S;R=0;for(S=Q.length;R<S;++R)Q[R]=5;var C=v(Q);function A(b,d){for(var a=b.f,c=b.d,f=b.input,e=b.a,g;c<d;)g=f[e++],g===n&&m(Error("input buffer is broken")),a|=g<<c,c+=8;g=a&(1<<d)-1;b.f=a>>>d;b.d=c-d;b.a=e;return g}
function T(b,d){for(var a=b.f,c=b.d,f=b.input,e=b.a,g=d[0],h=d[1],k,l,q;c<h;){k=f[e++];if(k===n)break;a|=k<<c;c+=8}l=g[a&(1<<h)-1];q=l>>>16;b.f=a>>q;b.d=c-q;b.a=e;return l&65535}
function aa(b){function d(a,b,c){var d,e,f,g;for(g=0;g<a;)switch(d=T(this,b),d){case 16:for(f=3+A(this,2);f--;)c[g++]=e;break;case 17:for(f=3+A(this,3);f--;)c[g++]=0;e=0;break;case 18:for(f=11+A(this,7);f--;)c[g++]=0;e=0;break;default:e=c[g++]=d}return c}var a=A(b,5)+257,c=A(b,5)+1,f=A(b,4)+4,e=new (u?Uint8Array:Array)(E.length),g,h,k,l;for(l=0;l<f;++l)e[E[l]]=A(b,3);g=v(e);h=new (u?Uint8Array:Array)(a);k=new (u?Uint8Array:Array)(c);b.j(v(d.call(b,a,g,h)),v(d.call(b,c,g,k)))}
w.prototype.j=function(b,d){var a=this.c,c=this.b;this.o=b;for(var f=a.length-258,e,g,h,k;256!==(e=T(this,b));)if(256>e)c>=f&&(this.b=c,a=this.e(),c=this.b),a[c++]=e;else{g=e-257;k=G[g];0<I[g]&&(k+=A(this,I[g]));e=T(this,d);h=K[e];0<M[e]&&(h+=A(this,M[e]));c>=f&&(this.b=c,a=this.e(),c=this.b);for(;k--;)a[c]=a[c++-h]}for(;8<=this.d;)this.d-=8,this.a--;this.b=c};
w.prototype.w=function(b,d){var a=this.c,c=this.b;this.o=b;for(var f=a.length,e,g,h,k;256!==(e=T(this,b));)if(256>e)c>=f&&(a=this.e(),f=a.length),a[c++]=e;else{g=e-257;k=G[g];0<I[g]&&(k+=A(this,I[g]));e=T(this,d);h=K[e];0<M[e]&&(h+=A(this,M[e]));c+k>f&&(a=this.e(),f=a.length);for(;k--;)a[c]=a[c++-h]}for(;8<=this.d;)this.d-=8,this.a--;this.b=c};
w.prototype.e=function(){var b=new (u?Uint8Array:Array)(this.b-32768),d=this.b-32768,a,c,f=this.c;if(u)b.set(f.subarray(32768,b.length));else{a=0;for(c=b.length;a<c;++a)b[a]=f[a+32768]}this.g.push(b);this.l+=b.length;if(u)f.set(f.subarray(d,d+32768));else for(a=0;32768>a;++a)f[a]=f[d+a];this.b=32768;return f};
w.prototype.z=function(b){var d,a=this.input.length/this.a+1|0,c,f,e,g=this.input,h=this.c;b&&("number"===typeof b.p&&(a=b.p),"number"===typeof b.u&&(a+=b.u));2>a?(c=(g.length-this.a)/this.o[2],e=258*(c/2)|0,f=e<h.length?h.length+e:h.length<<1):f=h.length*a;u?(d=new Uint8Array(f),d.set(h)):d=h;return this.c=d};
w.prototype.n=function(){var b=0,d=this.c,a=this.g,c,f=new (u?Uint8Array:Array)(this.l+(this.b-32768)),e,g,h,k;if(0===a.length)return u?this.c.subarray(32768,this.b):this.c.slice(32768,this.b);e=0;for(g=a.length;e<g;++e){c=a[e];h=0;for(k=c.length;h<k;++h)f[b++]=c[h]}e=32768;for(g=this.b;e<g;++e)f[b++]=d[e];this.g=[];return this.buffer=f};
w.prototype.v=function(){var b,d=this.b;u?this.r?(b=new Uint8Array(d),b.set(this.c.subarray(0,d))):b=this.c.subarray(0,d):(this.c.length>d&&(this.c.length=d),b=this.c);return this.buffer=b};function U(b,d){var a,c;this.input=b;this.a=0;if(d||!(d={}))d.index&&(this.a=d.index),d.verify&&(this.A=d.verify);a=b[this.a++];c=b[this.a++];switch(a&15){case V:this.method=V;break;default:m(Error("unsupported compression method"))}0!==((a<<8)+c)%31&&m(Error("invalid fcheck flag:"+((a<<8)+c)%31));c&32&&m(Error("fdict flag is not supported"));this.q=new w(b,{index:this.a,bufferSize:d.bufferSize,bufferType:d.bufferType,resize:d.resize})}
U.prototype.k=function(){var b=this.input,d,a;d=this.q.k();this.a=this.q.a;if(this.A){a=(b[this.a++]<<24|b[this.a++]<<16|b[this.a++]<<8|b[this.a++])>>>0;var c=d;if("string"===typeof c){var f=c.split(""),e,g;e=0;for(g=f.length;e<g;e++)f[e]=(f[e].charCodeAt(0)&255)>>>0;c=f}for(var h=1,k=0,l=c.length,q,p=0;0<l;){q=1024<l?1024:l;l-=q;do h+=c[p++],k+=h;while(--q);h%=65521;k%=65521}a!==(k<<16|h)>>>0&&m(Error("invalid adler-32 checksum"))}return d};var V=8;s("Zlib.Inflate",U);s("Zlib.Inflate.prototype.decompress",U.prototype.k);var W={ADAPTIVE:z.s,BLOCK:z.t},X,Y,Z,$;if(Object.keys)X=Object.keys(W);else for(Y in X=[],Z=0,W)X[Z++]=Y;Z=0;for($=X.length;Z<$;++Z)Y=X[Z],s("Zlib.Inflate.BufferType."+Y,W[Y]);}).call(this);
function Log(msg)
{
if (window.console && console.log)
console.log(msg); //for firebug
}
var lastCommand;
function Char(p, ch)
{
if(p.pos < p.data.length && (255 & p.data[p.pos]) == ch) {
lastCommand = ch;
p.pos++;
return true;
}
return false;
}
function Get8(p)
{
return p.pos < p.data.length ? (255 & p.data[p.pos++]) : 0;
}
function Get16(p)
{
var l = Get8(p);
var h = Get8(p);
return (h << 8) | l;
}
function Get32(p)
{
var l = Get16(p);
var h = Get16(p);
return (h << 16) | l;
}
var lastLen; // TODO: remove
var lastString;
function GetString(p)
{
var n = Get32(p);
lastLen = n;
var s = "";
for(var i = 0; i < n; i++)
s += String.fromCharCode(Get8(p));
lastString = s;
return s;
}
var SendingEnabled = true;
var posx, posy;
var ctx;
var caretx = 0, carety = 0, caretcx = 0, caretcy = 0, caretshown = false;
var caretTimerID;
function InvertRect(x, y, cx, cy)
{
if(cx <= 0 || cy <= 0)
return;
var imageData = ctx.getImageData(x, y, cx, cy);
var data = imageData.data;
for(var i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i];
data[i + 1] = 255 - data[i + 1];
data[i + 2] = 255 - data[i + 2];
}
ctx.putImageData(imageData, x, y);
}
function DrawDragLine(x, y, cx, cy, anim)
{
if(cx <= 0 || cy <= 0)
return;
var imageData = ctx.getImageData(x, y, cx, cy);
var data = imageData.data;
for(var i = 0; i < data.length; i += 4) {
if(((i / 4 + anim) / 4) & 1) {
data[i] = 255 - data[i];
data[i + 1] = 255 - data[i + 1];
data[i + 2] = 255 - data[i + 2];
}
}
ctx.putImageData(imageData, x, y);
}
function InvertCaret()
{
// Log("InvertCaret " + caretx + " " + carety + " " + caretcx + " " + caretcy);
if(caretcx >= 0 || caretcy >= 0)
InvertRect(caretx, carety, caretcx, caretcy);
}
function ClearCaretTimer()
{
if(caretTimerID != undefined)
clearTimeout(caretTimerID);
}
function CaretTimer()
{
ClearCaretTimer();
caretTimerID = setTimeout(DoCaret, 500);
}
function DoCaret()
{
if(caretcx || caretcy) {
InvertCaret();
caretshown = !caretshown;
CaretTimer();
}
}
function InvertShownCaret()
{
if(caretshown)
InvertCaret();
}
function HideCaret(x, y, cx, cy)
{
return;
if(caretHidden)
return;
if(Math.max(x, caretx) < Math.min(x + cx, caretx + caretcx) &&
Math.max(y, carety) < Math.min(y + cy, carety + caretcy)) {
// Log("Hiding caret");
InvertShownCaret();
caretHidden = true;
}
}
var lastR, lastG, lastB;
function SetFillColor(r, g, b)
{
if(r == lastR && g == lastG && b == lastB)
return;
lastR = r;
lastG = g;
lastB = b;
var c = "rgb(" + r + "," + g + "," + b + ")";
ctx.fillStyle = c;
}
function ProcessDraw(s)
{
// console.clear();
// Log("Painting started " + s.length);
// window.document.title = "Processing draw";
var time0 = (new Date).getTime();
var p = new Object;
p.data = s;
p.pos = 0;
var canvas = document.getElementById("myCanvas");
ctx = canvas.getContext("2d");
SendingEnabled = true;
posx = 0;
posy = 0;
InvertShownCaret();
while(p.pos < p.data.length) {
// Log(p.pos + ": " + p.data[p.pos]);
if(Char(p, 0)) { // RECT
var x = Get16(p);
var y = Get16(p);
var cx = Get16(p);
var cy = Get16(p);
var r = Get8(p);
var g = Get8(p);
var b = Get8(p);
SetFillColor(r, g, b);
// Log("color: " + c);
HideCaret(x, y, cx, cy);
ctx.fillRect(x, y, cx, cy);
posx = x;
posy = y;
}
else
if(Char(p, 3)) { // INVERTRECT
var x = Get16(p);
var y = Get16(p);
var cx = Get16(p);
var cy = Get16(p);
// Log("irect: " + x + ", " + y + ", " + cx + ", " + cy);
HideCaret(x, y, cx, cy);
InvertRect(x, y, cx, cy);
posx = x;
posy = y;
}
else
if(Char(p, 2)) { // SETIMAGE
var r = Get16(p);
var cx = Get16(p);
var cy = Get16(p);
var n = cx * cy * 4;
var imgData = ctx.createImageData(cx, cy);
for(i = 0; i < n; i++)
imgData.data[i] = Get8(p);
var img = document.createElement('canvas');
img.width = cx;
img.height = cy;
img.getContext("2d").putImageData(imgData, 0, 0);
window.img_cache[r] = img;
// Log("Set image: " + r);
if(img == undefined) // TODO: Remove after debugging this
alert("Undefined in set: " + n);
if(window.img_cache[r] == undefined)
alert("Undefined 2: " + n);
}
else
if(Char(p, 1)) { // IMAGE
var n = Get16(p);
var px = Get16(p);
var py = Get16(p);
var x = Get16(p);
var y = Get16(p);
var cx = Get16(p);
var cy = Get16(p);
// Log("Draw image: " + n);
if(window.img_cache[n] == undefined)
alert("Undefined image: " + n);
HideCaret(x, y, cx, cy);
ctx.drawImage(window.img_cache[n], x, y, cx, cy, px, py, cx, cy);
posx = px;
posy = py;
}
else
if(Char(p, 4)) { // STD_CURSORIMAGE
canvas.style.cursor = [
"default", // should not happen
"default", // Arrow
"wait", // Wait
"text", // IBeam
"not-allowed", // No
"move", // SizeAll
"ew-resize", // SizeHorz
"ns-resize", // SizeVert
"nw-resize", // SizeTopLeft
"n-resize", // SizeTop
"ne-resize", // SizeTopRight
"w-resize", // SizeLeft
"e-resize", // SizeRight
"sw-resize", // SizeBottomLeft
"s-resize", // SizeBottom
"se-resize", // SizeBottomRight
"pointer" // Hand
][Get8(p)];
}
else
if(Char(p, 5)) { // SETCURSORIMAGE
i = Get16(p);
window.cursor_cache[i] = GetString(p);
}
else
if(Char(p, 6)) { // CURSORIMAGE
i = Get16(p);
canvas.style.cursor = window.cursor_cache[i];
// Log(cursor_cache[i]);
}
else
if(Char(p, 7)) { // DISABLESENDING
SendingEnabled = true;
}
else
if(Char(p, 8)) { // UPDATESERIAL
update_serial_l = Get32(p);
update_serial_h = Get32(p);
ws.send("S " + update_serial_l + " " + update_serial_h + " " + ((new Date).getTime() - time0) + "\n");
}
else
if(Char(p, 9)) // IMAGEPP
SImage(p, 1, 1);
else
if(Char(p, 10)) // IMAGENP
SImage(p, -1, 1);
else
if(Char(p, 11)) // IMAGEPN
SImage(p, 1, -1);
else
if(Char(p, 12)) // IMAGENN
SImage(p, -1, -1);
else
if(Char(p, 13)) // RECTPP
SRect(p, 1, 1);
else
if(Char(p, 14)) // RECTNP
SRect(p, -1, 1);
else
if(Char(p, 15)) // RECTPN
SRect(p, 1, -1);
else
if(Char(p, 16)) // RECTNN
SRect(p, -1, -1);
else
if(Char(p, 17)) { // SETCARET
caretx = Get16(p);
carety = Get16(p);
caretcx = Get16(p);
caretcy = Get16(p);
caretshown = true;
CaretTimer();
// Log("SET CARET " + caretx + " " + carety + " " + caretcx + " " + caretcy);
}
else
if(Char(p, 18)) { // HORZDRAGLINE,
var x = Get16(p);
var y = Get16(p);
var l = Get16(p);
var anim = Get16(p);
DrawDragLine(x, y, l, 1, anim);
}
else
if(Char(p, 19)) { // VERTDRAGLINE,
var x = Get16(p);
var y = Get16(p);
var l = Get16(p);
var anim = Get16(p);
DrawDragLine(x, y, 1, l, anim);
}
else
if(Char(p, 20)) { // OPENLINK,
var s = GetString(p);
window.open(s);
}
else {
alert("Unrecognized code: " + p.data[p.pos] + " after " + lastCommand + " lastLen " + lastLen + " lastString " + lastString);
break;
}
}
InvertShownCaret();
// window.document.title = "OK";
if(SendingEnabled && event_queue.length)
ScheduleSend();
// Log("Painting finished");
}
function SImage(p, sx, sy)
{
var px = posx + sx * Get8(p);
var py = posy + sy * Get8(p);
var n = Get16(p);
// Log("Draw opt image: " + px + " " + py + " " + n);
var img = window.img_cache[n];
if(img == undefined)
alert("Undefined image: " + n);
HideCaret(px, py, img.width, img.height);
ctx.drawImage(window.img_cache[n], px, py);
posx = px;
posy = py;
}
function SRect(p, sx, sy)
{
var px = posx + sx * Get8(p);
var py = posy + sy * Get8(p);
var cx = Get8(p);
var cy = Get8(p);
var r = Get8(p);
var g = Get8(p);
var b = Get8(p);
// Log("rect: " + x + ", " + y + ", " + cx + ", " + cy);
SetFillColor(r, g, b);
// Log("color: " + c);
HideCaret(x, y, cx, cy);
ctx.fillRect(x, y, cx, cy);
posx = px;
posy = py;
}
window.img_cache = {};
window.event_queue = "i\n";
window.cursor_cache = {};
window.update_serial_l = 0;
window.update_serial_h = 0;
var canvas = document.getElementById("myCanvas");
function key_flags(event)
{
return " " + 1*event.shiftKey + 1*event.ctrlKey + 1*event.altKey + "\n";
}
function mouse_event(event)
{
return " " + event.clientX + " " + event.clientY + " " + (new Date).getTime() + key_flags(event);
}
canvas.onmousemove = function(event)
{
event_queue += "M" + mouse_event(event);
ScheduleSend();
event.preventDefault();
}
canvas.onmousedown = function(event)
{
event_queue += "D " + event.button + mouse_event(event);
ScheduleSend();
event.preventDefault();
}
canvas.onmouseout = function(event)
{
event_queue += "O\n";
ScheduleSend();
event.preventDefault();
}
canvas.onmouseover = function(event)
{
event_queue += "I\n";
ScheduleSend();
event.preventDefault();
}
canvas.onmouseup = function(event)
{
event_queue += "U " + event.button + mouse_event(event);
ScheduleSend();
event.preventDefault();
}
function MouseWheel(event)
{
event_queue += "W " + event.deltaY + mouse_event(event);
}
canvas.addEventListener("wheel", MouseWheel); // IE9 needs addEventListener...
document.onkeydown = function(event)
{
event_queue += "K " + event.keyCode + " " + event.which + key_flags(event);
ScheduleSend();
if(event.ctrlKey || event.altKey || event.keyCode == 8 || event.keyCode == 9) {
event.stopPropagation();
event.preventDefault();
}
}
document.onkeypress = function(event)
{
event_queue += "C " + event.keyCode + " " + event.which + key_flags(event);
event.preventDefault();
ScheduleSend();
event.stopPropagation();
event.preventDefault();
}
document.onkeyup = function(event)
{
event_queue += "k " + event.keyCode + " " + event.which + key_flags(event);
event.preventDefault();
ScheduleSend();
if(event.ctrlKey || event.altKey) {
event.stopPropagation();
event.preventDefault();
}
}
function ResizeCanvas()
{
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
event_queue += "R " + canvas.width + ' ' + canvas.height + "\n";
ScheduleSend();
}
window.onresize = ResizeCanvas;
var timerID;
function ConnectWebsocket()
{
return new WebSocket("%%host%%");
}
var ws = ConnectWebsocket();
ws.binaryType = "arraybuffer";
function ScheduleSend()
{
if(SendingEnabled) {
if(timerID != undefined)
clearTimeout(timerID);
timerID = setTimeout(SendEvents, 0);
}
}
function SendEvents()
{
if(SendingEnabled) {
if(event_queue.length) {
// Log(event_queue);
ws.send(event_queue);
}
else
ws.send("PING");
event_queue = "";
if(timerID != undefined)
clearTimeout(timerID);
timerID = setTimeout(SendEvents, 20);
}
}
ws.onopen = function()
{
// Log("websocket connection opened");
ResizeCanvas();
};
ws.onmessage = function(event)
{
// Log("onmessage");
if(event.data instanceof ArrayBuffer) {
var inflate = new Zlib.Inflate(new Uint8Array(event.data));
ProcessDraw(inflate.decompress());
}
};
ws.onclose = function(ev)
{
ClearCaretTimer();
alert("Connection closed.");
};
</script>
</body>
</html>

View file

@ -1,23 +0,0 @@
description "HTML5 thin client backend\377B128,0,255";
uses
CtrlLib,
VirtualGui;
uses(WIN32 & MSC) plugin/DroidFonts;
file
Turtle.h,
Init.cpp,
Stream.cpp,
Server.cpp,
Keys.cpp,
Event.cpp,
Draw.cpp,
Info readonly separator,
Turtle.brc,
Turtle.html,
Docs readonly separator,
src.tpp,
issues;

View file

@ -1,24 +0,0 @@
Known Issues
------------
1) LaunchWebBrowser function doesn't work, at the moment.
Hopefully, this is a temporary regression.
Maybe it should be handled generally for VirtualGUi-based stuff, using a callback?
2) uppsrc contains various proprocessor definitons for Turtle.
They are obsolete now and better be removed. (VIRTUALGUI is sufficient)
3) The VirtualGui-based Turtle package inherits the problems of the old Turtle package.
One such major problem is that on windows platform, closing the remote app throws an
exception on the server side. This requires further investigation.
4) KeyboardEvent.KeyCode and KeyboardEvent.which properties are deprecated and are going
to be emoved from the standard.
See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/which
Recommended alternatives: KeyboardEvent.key and KeyboardEvent.code.
See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code
See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key

View file

@ -1,88 +0,0 @@
topic "TurtleServer";
[i448;a25;kKO9;2 $$1,0#37138531426314131252341829483380:class]
[l288;2 $$2,2#27521748481378242620020725143825:desc]
[0 $$3,0#96390100711032703541132217272105:end]
[H6;0 $$4,0#05600065144404261032431302351956:begin]
[i448;a25;kKO9;2 $$5,0#37138531426314131252341829483370:item]
[l288;a4;*@5;1 $$6,6#70004532496200323422659154056402:requirement]
[l288;i1121;b17;O9;~~~.1408;2 $$7,0#10431211400427159095818037425705:param]
[i448;b42;O9;2 $$8,8#61672508125594000341940100500538:tparam]
[b42;2 $$9,9#13035079074754324216151401829390:normal]
[2 $$0,0#00000000000000000000000000000000:Default]
[{_}
[ {{10000@(113.42.0) [s0;%% [*@7;4 TurtleServer]]}}&]
[s2; &]
[s1;:Upp`:`:TurtleServer`:`:class: [@(0.0.255)3 class][3 _][*3 TurtleServer][3 _:_][@(0.0.255)3 p
ublic][3 _][*@3;3 VirtualGui]&]
[s2;#%% This class implements a remote gui virtualization server
for U`+`+ applications. By utilizing the modern web technologies
such as HTML`-5 canvas and websockets, TurtleServer allows U`+`+
gui applications to be accessed remotely via modern web browsers,
or, possibly, via specialized client software that understands
the Turtle wire protocol.&]
[s3;%% &]
[ {{10000F(128)G(128)@1 [s0;%% [* Public Method List]]}}&]
[s3; &]
[s5;:Upp`:`:TurtleServer`:`:Bind`(const Upp`:`:String`&`): [_^Upp`:`:TurtleServer^ Turt
leServer][@(0.0.255) `&]_[* Bind]([@(0.0.255) const]_[_^Upp`:`:String^ String][@(0.0.255) `&
]_[*@3 addr])&]
[s2;%% Sets the server bind address to [%-*@3 addr]. Default is `"0.0.0.0`".
Returns `*this for method chaining.&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:TurtleServer`:`:Host`(const Upp`:`:String`&`): [_^Upp`:`:TurtleServer^ Turt
leServer][@(0.0.255) `&]_[* Host]([@(0.0.255) const]_[_^Upp`:`:String^ String][@(0.0.255) `&
]_[*@3 host])&]
[s2;%% Sets the host URL to [%-*@3 host]. Default URL is `"localhost`".
Returns `*this for method chaining.&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:TurtleServer`:`:Port`(int`): [_^Upp`:`:TurtleServer^ TurtleServer][@(0.0.255) `&
]_[* Port]([@(0.0.255) int]_[*@3 port])&]
[s2;%% Sets the connection port number. Default port number is 8888.
Returns `*this for method chaining.&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:TurtleServer`:`:MaxConnections`(int`): [_^Upp`:`:TurtleServer^ TurtleServer
][@(0.0.255) `&]_[* MaxConnections]([@(0.0.255) int]_[*@3 limit])&]
[s2;%% Sets a limit to the maximum number of concurrent client conntections.
Default max. connection limit is 100. Returns `*this for method
chaining.&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:TurtleServer`:`:DebugMode`(bool`): [@(0.0.255) static]
[@(0.0.255) void]_[* DebugMode]([@(0.0.255) bool]_[*@3 b]_`=_[@(0.0.255) true])&]
[s6;%% POSIX only&]
[s2;%% If true, the server will not spawn child processes (no forking).
Useful for debugging purposes.&]
[s3;%% &]
[ {{10000F(128)G(128)@1 [s0;%% [* Constructor detail]]}}&]
[s3; &]
[s5;:Upp`:`:TurtleServer`:`:TurtleServer`(`): [* TurtleServer]()&]
[s2;%% Default constructor. Initializes the server bind address to
`"0.0.0.0`", the host URL to `"localhost`", and the connection
port number to 8888.&]
[s3; &]
[s4; &]
[s5;:Upp`:`:TurtleServer`:`:TurtleServer`(const Upp`:`:String`&`,int`): [* TurtleServer
]([@(0.0.255) const]_[_^Upp`:`:String^ String][@(0.0.255) `&]_[*@3 host],
[@(0.0.255) int]_[*@3 port])&]
[s2;%% Constructor overload. Initializes the host URL and the connection
port number to provided values.&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:TurtleServer`:`:TurtleServer`(const Upp`:`:String`&`,Upp`:`:String`&`,int`): [* T
urtleServer]([@(0.0.255) const]_[_^Upp`:`:String^ String][@(0.0.255) `&]_[*@3 ip],
[_^Upp`:`:String^ String][@(0.0.255) `&]_[*@3 host], [@(0.0.255) int]_[*@3 port])&]
[s2;%% Constructor overload. Initializes the server bind adrress,
the host URL and the connection port number to provided values.&]
[s3;%% &]
[ {{10000F(128)G(128)@1 [s0;%% [* Function List]]}}&]
[s3;%% &]
[s5;:Upp`:`:RunTurtleGui`(Upp`:`:TurtleServer`&`,Upp`:`:Event`<`>`): [@(0.0.255) void]_
[* RunTurtleGui]([_^Upp`:`:TurtleServer^ TurtleServer][@(0.0.255) `&]_[*@3 gui],
[_^Upp`:`:Event^ Event]<>_[*@3 app`_main])&]
[s2;%% Starts the Turtle GUI virtualization server and runs a U`+`+
GUI application over it.&]
[s3;%% &]
[s0;%% ]]

View file

@ -94,7 +94,7 @@ struct GetPasskeyDlg : WithGetPasskeyLayout<TopWindow> {
GetPasskeyDlg::GetPasskeyDlg()
{
CtrlLayoutOKCancel(*this, "Passkey");
CtrlLayoutOK(*this, "Passkey");
show_passkey << [=] { Sync(); };

View file

@ -1,5 +1,7 @@
description "FreeType based Draw FONTSYS replacement\3770,128,0";
options(BUILDER_OPTION) NOWARNINGS;
uses
Draw;