@bitefoundry/retrieviewer 0.0.17 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ASSET-PROVENANCE.md +11 -0
- package/ASSETS-LICENSE.md +6 -0
- package/ATTRIBUTION.md +8 -0
- package/DESKTOP-PLATFORMS.md +1 -1
- package/INSTALLING.md +2 -1
- package/LICENSE +16 -16
- package/NATIVE-RUNTIME.md +89 -0
- package/PREREQUISITES.md +7 -0
- package/PUPPY-MEMORY.md +11 -0
- package/README.ko.md +10 -1
- package/README.md +11 -1
- package/RELEASING.md +19 -0
- package/THIRD-PARTY-NOTICES.md +8 -0
- package/WINDOWS-COMPANION.md +15 -6
- package/braille.mjs +2 -2
- package/integrations/native-windows/CMakeLists.txt +15 -0
- package/integrations/native-windows/THIRD-PARTY-LICENSES.txt +77 -0
- package/integrations/native-windows/application.manifest +8 -0
- package/integrations/native-windows/bin/win32-x64/retrieviewer-native.exe +0 -0
- package/integrations/native-windows/desktop.mjs +45 -0
- package/integrations/native-windows/host.cpp +84 -0
- package/integrations/native-windows/manifest.json +14 -0
- package/integrations/native-windows/native-control.hpp +41 -0
- package/integrations/native-windows/native-window.hpp +128 -0
- package/integrations/native-windows/runtime.bundle.mjs +4694 -0
- package/integrations/native-windows/runtime.mjs +55 -0
- package/integrations/native-windows/sources.json +23 -0
- package/integrations/purrviewer/ART.md +289 -0
- package/integrations/purrviewer/ASSETS-LICENSE.md +21 -0
- package/integrations/purrviewer/LICENSE +24 -0
- package/integrations/purrviewer/art/ginger.bin.gz +0 -0
- package/integrations/purrviewer/art/ginger.json +1707 -0
- package/integrations/purrviewer/art/ginger.rvz +0 -0
- package/integrations/purrviewer/src/contact.mjs +63 -0
- package/integrations/purrviewer/src/habitat.mjs +41 -0
- package/integrations/purrviewer/src/life.mjs +280 -0
- package/integrations/purrviewer/src/purrviewer-core.mjs +130 -0
- package/integrations/purrviewer/src/purrviewer.mjs +37 -0
- package/integrations/purrviewer/src/walk-cycle.mjs +3 -0
- package/integrations/purrviewer/src/world.mjs +216 -0
- package/integrations/purrviewer/vendor/retrieviewer/LICENSE-CODE +21 -0
- package/integrations/purrviewer/vendor/retrieviewer/braille.mjs +73 -0
- package/integrations/purrviewer/vendor/retrieviewer/puppy-frame-file.mjs +15 -0
- package/integrations/purrviewer/vendor/retrieviewer/puppy-frame-indexed.mjs +78 -0
- package/integrations/purrviewer/vendor/retrieviewer/puppy-frame-predictive.mjs +170 -0
- package/integrations/purrviewer/vendor/retrieviewer/puppy-frame-store.mjs +114 -0
- package/integrations/windows-overlay/desktop.mjs +11 -3
- package/integrations/windows-overlay/worker.mjs +9 -2
- package/package.json +39 -4
- package/puppy-clean.mjs +2 -2
- package/puppy-companion-sprites.mjs +2 -2
- package/puppy-frame-indexed.mjs +2 -2
- package/puppy-paper-actions.mjs +2 -2
- package/puppy-paper-behaviors.mjs +1 -1
- package/puppy-paper-contact.mjs +2 -2
- package/puppy-paper-seat.mjs +2 -2
- package/puppy-paper.mjs +12 -12
- package/retrieviewer-cli.mjs +10 -6
- package/retrieviewer.mjs +13 -4
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"platform": "win32-x64",
|
|
4
|
+
"files": {
|
|
5
|
+
"runtime.bundle.mjs": {
|
|
6
|
+
"bytes": 235608,
|
|
7
|
+
"sha256": "e9da942dbe57ac778c44ea8f300e2e868face5e73b00ffcd4399aeb7e94ee09c"
|
|
8
|
+
},
|
|
9
|
+
"bin/win32-x64/retrieviewer-native.exe": {
|
|
10
|
+
"bytes": 1551872,
|
|
11
|
+
"sha256": "317841c3a61f78ee57e56b9eb7f778d59d17ee6a3ab49e04eab37a40509799c1"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#include <atomic>
|
|
2
|
+
#include <mutex>
|
|
3
|
+
#include <thread>
|
|
4
|
+
#include <sstream>
|
|
5
|
+
#include <fstream>
|
|
6
|
+
#include <filesystem>
|
|
7
|
+
#include <sddl.h>
|
|
8
|
+
#include <psapi.h>
|
|
9
|
+
#include <functional>
|
|
10
|
+
|
|
11
|
+
static std::wstring wide(const std::string& text){if(text.empty())return L"";int n=MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,text.data(),(int)text.size(),nullptr,0);if(!n)throw std::runtime_error("Invalid UTF-8");std::wstring result(n,0);MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,text.data(),(int)text.size(),result.data(),n);return result;}
|
|
12
|
+
static std::string userSid(){HANDLE token;if(!OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY,&token))throw std::runtime_error("Cannot read process owner");DWORD n=0;GetTokenInformation(token,TokenUser,nullptr,0,&n);std::vector<uint8_t> data(n);BOOL ok=GetTokenInformation(token,TokenUser,data.data(),n,&n);CloseHandle(token);if(!ok)throw std::runtime_error("Cannot read process owner");LPWSTR sid;if(!ConvertSidToStringSidW(((TOKEN_USER*)data.data())->User.Sid,&sid))throw std::runtime_error("Cannot read process owner");std::wstring s(sid);LocalFree(sid);return std::string(s.begin(),s.end());}
|
|
13
|
+
static std::string propertyString(JSContext* ctx,JSValueConst object,const char* name,const std::string& fallback=""){JSValue value=JS_GetPropertyStr(ctx,object,name);if(JS_IsUndefined(value)||JS_IsNull(value)){JS_FreeValue(ctx,value);return fallback;}const char* text=JS_ToCString(ctx,value);if(!text){JS_FreeValue(ctx,value);throw std::runtime_error(name);}std::string out(text);JS_FreeCString(ctx,text);JS_FreeValue(ctx,value);return out;}
|
|
14
|
+
static bool propertyBool(JSContext* ctx,JSValueConst object,const char* name){JSValue value=JS_GetPropertyStr(ctx,object,name);bool out=JS_ToBool(ctx,value);JS_FreeValue(ctx,value);return out;}
|
|
15
|
+
class NativeControl {
|
|
16
|
+
std::thread worker;std::atomic<bool> alive{false};std::mutex mutex;std::string status,token,session;std::wstring name;HANDLE pipe=INVALID_HANDLE_VALUE;PSECURITY_DESCRIPTOR security=nullptr;std::function<void(const std::string&)> action;
|
|
17
|
+
void serve(){
|
|
18
|
+
while(alive){BOOL connected=ConnectNamedPipe(pipe,nullptr);if(!connected&&GetLastError()!=ERROR_PIPE_CONNECTED){if(!alive)break;continue;}
|
|
19
|
+
std::string body;const ULONGLONG deadline=GetTickCount64()+1800;
|
|
20
|
+
while(alive&&body.size()<=2048&&GetTickCount64()<deadline){DWORD available=0;if(!PeekNamedPipe(pipe,nullptr,0,nullptr,&available,nullptr))break;if(!available){Sleep(10);continue;}char bytes[2048];DWORD got=0;if(!ReadFile(pipe,bytes,std::min<DWORD>(available,sizeof(bytes)),&got,nullptr)||!got)break;body.append(bytes,got);if(body.find('\n')!=std::string::npos)break;}
|
|
21
|
+
const std::string prefix="{\"token\":\""+token+"\",\"command\":\"";std::string response;
|
|
22
|
+
std::string command;for(const auto* choice:{"stop","pet-dog","pet-cat"})if(body==prefix+choice+"\"}\n")command=choice;
|
|
23
|
+
if(body==prefix+"status\"}\n"){std::lock_guard<std::mutex> lock(mutex);response=status+"\n";}
|
|
24
|
+
else if(!command.empty())response=command=="stop"?"{\"stopped\":true}\n":"{\"accepted\":true}\n";
|
|
25
|
+
if(!response.empty()){DWORD wrote=0;WriteFile(pipe,response.data(),(DWORD)response.size(),&wrote,nullptr);if(!command.empty())action(command);FlushFileBuffers(pipe);}
|
|
26
|
+
DisconnectNamedPipe(pipe);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
public:
|
|
30
|
+
void start(const std::string& address,const std::string& secret,const std::string& file,std::function<void(const std::string&)> callback){
|
|
31
|
+
if(secret.size()!=64||secret.find_first_not_of("0123456789abcdef")!=std::string::npos||address.rfind("\\\\.\\pipe\\retrieviewer-overlay-",0)!=0)throw std::runtime_error("Invalid native control configuration");
|
|
32
|
+
token=secret;name=wide(address);session=file;action=callback;std::wstring sddl=wide("D:P(A;;GA;;;"+userSid()+")");if(!ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(),SDDL_REVISION_1,&security,nullptr))throw std::runtime_error("Cannot protect native control pipe");SECURITY_ATTRIBUTES attributes{sizeof(SECURITY_ATTRIBUTES),security,FALSE};
|
|
33
|
+
pipe=CreateNamedPipeW(name.c_str(),PIPE_ACCESS_DUPLEX|FILE_FLAG_FIRST_PIPE_INSTANCE,PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT|PIPE_REJECT_REMOTE_CLIENTS,1,8192,4096,0,&attributes);if(pipe==INVALID_HANDLE_VALUE)throw std::runtime_error("Cannot open native control pipe");alive=true;worker=std::thread([this]{serve();});
|
|
34
|
+
}
|
|
35
|
+
void update(const std::string& value){std::lock_guard<std::mutex> lock(mutex);status=value;}
|
|
36
|
+
void close(){if(alive.exchange(false)){if(worker.joinable()){CancelSynchronousIo(worker.native_handle());HANDLE wake=CreateFileW(name.c_str(),GENERIC_READ|GENERIC_WRITE,0,nullptr,OPEN_EXISTING,0,nullptr);if(wake!=INVALID_HANDLE_VALUE)CloseHandle(wake);worker.join();}if(pipe!=INVALID_HANDLE_VALUE){CloseHandle(pipe);pipe=INVALID_HANDLE_VALUE;}}
|
|
37
|
+
if(security){LocalFree(security);security=nullptr;}
|
|
38
|
+
if(!session.empty()){auto path=std::filesystem::u8path(session);DWORD attr=GetFileAttributesW(path.c_str()),parent=GetFileAttributesW(path.parent_path().c_str());if(attr!=INVALID_FILE_ATTRIBUTES&&parent!=INVALID_FILE_ATTRIBUTES&&!(attr&FILE_ATTRIBUTE_REPARSE_POINT)&&!(parent&FILE_ATTRIBUTE_REPARSE_POINT)){std::ifstream file(path,std::ios::binary);std::string bytes((std::istreambuf_iterator<char>(file)),std::istreambuf_iterator<char>());file.close();if(bytes.size()<8192&&bytes.find("\"token\":\""+token+"\"")!=std::string::npos)DeleteFileW(path.c_str());}session.clear();}
|
|
39
|
+
}
|
|
40
|
+
~NativeControl(){close();}
|
|
41
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#include <windows.h>
|
|
2
|
+
#include <gdiplus.h>
|
|
3
|
+
#include <memory>
|
|
4
|
+
#include "native-control.hpp"
|
|
5
|
+
namespace NativePet {
|
|
6
|
+
static JSContext* context;
|
|
7
|
+
static HWND target=nullptr,overlay=nullptr;
|
|
8
|
+
static bool showing=false,paused=false,failed=false;
|
|
9
|
+
static ULONGLONG startTime=0,lastTick=0,durationMs=0;
|
|
10
|
+
static int stage=0,painted=0;
|
|
11
|
+
static std::string previous;
|
|
12
|
+
static std::vector<double> timings;
|
|
13
|
+
static bool saveProof=false,verifyAtlas=false,proofSaved=false,atlasVerified=false;
|
|
14
|
+
static bool production=false,diagnostic=false,testPicker=false,visible=false,inkExplicit=false;
|
|
15
|
+
static HWND followed=nullptr,picker=nullptr,windowList=nullptr;
|
|
16
|
+
static DWORD followedPid=0;
|
|
17
|
+
static HANDLE instanceMutex=nullptr;
|
|
18
|
+
static NativeControl control;
|
|
19
|
+
static std::thread startupGuard;
|
|
20
|
+
static std::atomic<bool> detached{false};
|
|
21
|
+
static std::string petKind="dog",inkName="cream",returnStyle="auto",lastKind;
|
|
22
|
+
static bool lastAccepted=false;
|
|
23
|
+
static uint64_t frames=0;static size_t timingCursor=0;
|
|
24
|
+
static std::vector<std::pair<HWND,DWORD>> choices;
|
|
25
|
+
static POINT lastCanvas{INT_MIN,INT_MIN};static int lastWidth=0,lastHeight=0;
|
|
26
|
+
static Gdiplus::Color ink(){return inkName=="dark"?Gdiplus::Color(245,67,48,24):inkName=="gold"?(petKind=="cat"?Gdiplus::Color(245,241,182,107):Gdiplus::Color(245,231,184,103)):Gdiplus::Color(245,248,242,232);}
|
|
27
|
+
static void updateStatus();static void chooseWindow();static void layoutControls();
|
|
28
|
+
static JSValue call(const char* name,int argc=0,JSValue* args=nullptr){JSValue global=JS_GetGlobalObject(context),fn=JS_GetPropertyStr(context,global,name),out=JS_Call(context,fn,global,argc,args);JS_FreeValue(context,fn);JS_FreeValue(context,global);if(JS_IsException(out)){js_std_dump_error(context);failed=true;if(target)PostMessage(target,WM_CLOSE,0,0);}return out;}
|
|
29
|
+
static void interact(const char* kind){JSValue arg=JS_NewString(context,kind),result=call("__desktopInteract",1,&arg);bool accepted=!JS_IsException(result)&&JS_ToBool(context,result);JS_FreeValue(context,result);JS_FreeValue(context,arg);lastKind=kind;lastAccepted=accepted;if(!production||diagnostic){printf("{\"interaction\":\"%s\",\"accepted\":%s}\n",kind,accepted?"true":"false");fflush(stdout);}previous.clear();updateStatus();}
|
|
30
|
+
struct Surface {
|
|
31
|
+
HDC dc=nullptr;HBITMAP bitmap=nullptr;HGDIOBJ old=nullptr;uint8_t* bits=nullptr;int width=0,height=0;
|
|
32
|
+
~Surface(){release();}
|
|
33
|
+
void release(){if(dc){if(old)SelectObject(dc,old);if(bitmap)DeleteObject(bitmap);DeleteDC(dc);}dc=nullptr;bitmap=nullptr;old=nullptr;bits=nullptr;width=height=0;}
|
|
34
|
+
void ensure(int w,int h){if(w<=width&&h<=height)return;w=std::max(width,(w+63)/64*64);h=std::max(height,(h+63)/64*64);release();width=w;height=h;BITMAPINFO info{};info.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);info.bmiHeader.biWidth=w;info.bmiHeader.biHeight=-h;info.bmiHeader.biPlanes=1;info.bmiHeader.biBitCount=32;info.bmiHeader.biCompression=BI_RGB;HDC screen=GetDC(nullptr);dc=CreateCompatibleDC(screen);bitmap=CreateDIBSection(screen,&info,DIB_RGB_COLORS,(void**)&bits,nullptr,0);ReleaseDC(nullptr,screen);if(!bitmap||!dc)throw std::runtime_error("Native surface allocation");old=SelectObject(dc,bitmap);}
|
|
35
|
+
};
|
|
36
|
+
static Surface surface;
|
|
37
|
+
struct DotAtlas {
|
|
38
|
+
int cell=0;uint32_t color=0;std::vector<uint8_t> pixels;
|
|
39
|
+
void ensure(int value){if(cell==value&&color==ink().GetValue())return;cell=value;color=ink().GetValue();size_t length=(size_t)cell*cell*2*4;pixels.assign(length*256,0);static const int dx[]={0,0,0,1,1,1,0,1},dy[]={0,1,2,0,1,2,3,3};float step=cell/2.f,diameter=std::max(1.1f,step*.48f);
|
|
40
|
+
for(int mask=0;mask<256;mask++){Gdiplus::Bitmap bitmap(cell,cell*2,cell*4,PixelFormat32bppPARGB,pixels.data()+mask*length);Gdiplus::Graphics g(&bitmap);g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);Gdiplus::SolidBrush brush(ink());for(int bit=0;bit<8;bit++)if(mask&(1<<bit)){float x=(dx[bit]+.5f)*step,y=(dy[bit]+.5f)*step;g.FillEllipse(&brush,x-diameter/2,y-diameter/2,diameter,diameter);}}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
static DotAtlas atlas;
|
|
44
|
+
static void paint(const std::string& text,int columns,int rows,int cell,POINT canvas){
|
|
45
|
+
std::vector<uint8_t> masks((size_t)columns*rows);size_t at=0;int left=columns,top=rows,right=-1,bottom=-1;
|
|
46
|
+
for(int y=0;y<rows;y++){if(y){if(at>=text.size()||text[at++]!='\n')throw std::runtime_error("Native row separator");}for(int x=0;x<columns;x++){if(at+3>text.size())throw std::runtime_error("Native text length");const auto* p=(const unsigned char*)text.data()+at;uint32_t ch=((p[0]&15)<<12)|((p[1]&63)<<6)|(p[2]&63);at+=3;if(ch<0x2800||ch>0x28ff)throw std::runtime_error("Native glyph");uint8_t mask=(uint8_t)(ch-0x2800);masks[y*columns+x]=mask;if(mask){left=std::min(left,x);right=std::max(right,x);top=std::min(top,y);bottom=std::max(bottom,y);}}}
|
|
47
|
+
if(at!=text.size())throw std::runtime_error("Native trailing text");if(right<left){ShowWindow(overlay,SW_HIDE);return;}
|
|
48
|
+
int w=(right-left+1)*cell,h=(bottom-top+1)*cell*2;if((int64_t)w*h>8000000){ShowWindow(overlay,SW_HIDE);visible=false;return;}surface.ensure(w,h);std::fill(surface.bits,surface.bits+(size_t)surface.width*surface.height*4,0);
|
|
49
|
+
atlas.ensure(cell);size_t stampSize=(size_t)cell*cell*2*4;
|
|
50
|
+
for(int y=top;y<=bottom;y++)for(int x=left;x<=right;x++){int mask=masks[y*columns+x];if(!mask)continue;const uint8_t* stamp=atlas.pixels.data()+mask*stampSize;for(int sy=0;sy<cell*2;sy++)memcpy(surface.bits+((size_t)((y-top)*cell*2+sy)*surface.width+(x-left)*cell)*4,stamp+(size_t)sy*cell*4,cell*4);}
|
|
51
|
+
if(verifyAtlas&&!atlasVerified){std::vector<uint8_t> reference((size_t)w*h*4,0);Gdiplus::Bitmap bitmap(w,h,w*4,PixelFormat32bppPARGB,reference.data());Gdiplus::Graphics g(&bitmap);g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);Gdiplus::SolidBrush ink(NativePet::ink());static const int dx[]={0,0,0,1,1,1,0,1},dy[]={0,1,2,0,1,2,3,3};float step=cell/2.f,diameter=std::max(1.1f,step*.48f);for(int y=top;y<=bottom;y++)for(int x=left;x<=right;x++){int mask=masks[y*columns+x];for(int bit=0;bit<8;bit++)if(mask&(1<<bit)){float px=(x-left)*cell+(dx[bit]+.5f)*step,py=(y-top)*cell*2+(dy[bit]+.5f)*step;g.FillEllipse(&ink,px-diameter/2,py-diameter/2,diameter,diameter);}}g.Flush(Gdiplus::FlushIntentionSync);for(int y=0;y<h;y++)if(memcmp(reference.data()+(size_t)y*w*4,surface.bits+(size_t)y*surface.width*4,w*4)!=0)throw std::runtime_error("Dot atlas changed pixels");atlasVerified=true;printf("{\"stage\":\"dot-atlas-verified\",\"pixelExact\":true}\n");fflush(stdout);}
|
|
52
|
+
if(saveProof&&!proofSaved){Gdiplus::Bitmap source(surface.width,surface.height,surface.width*4,PixelFormat32bppPARGB,surface.bits),proof(w,h,PixelFormat32bppARGB);Gdiplus::Graphics pg(&proof);pg.Clear(Gdiplus::Color(255,0,0,0));pg.DrawImage(&source,Gdiplus::Rect(0,0,w,h),0,0,w,h,Gdiplus::UnitPixel);pg.Flush(Gdiplus::FlushIntentionSync);CLSID png={0x557cf406,0x1a04,0x11d3,{0x9a,0x73,0x00,0x00,0xf8,0x1e,0xf3,0x2e}};if(proof.Save(L"dist/native-memory/native-window-proof.png",&png,nullptr)!=Gdiplus::Ok)throw std::runtime_error("PNG proof failed");proofSaved=true;}
|
|
53
|
+
POINT destination={canvas.x+left*cell,canvas.y+top*cell*2},origin={0,0};SIZE size={w,h};BLENDFUNCTION blend={AC_SRC_OVER,0,255,AC_SRC_ALPHA};HDC screen=GetDC(nullptr);BOOL result=UpdateLayeredWindow(overlay,screen,&destination,&size,surface.dc,&origin,0,&blend,ULW_ALPHA);ReleaseDC(nullptr,screen);if(!result)throw std::runtime_error("Native layered update");SetWindowPos(overlay,HWND_TOPMOST,destination.x,destination.y,w,h,SWP_NOACTIVATE|SWP_SHOWWINDOW);painted++;
|
|
54
|
+
}
|
|
55
|
+
static void updateStatus(){
|
|
56
|
+
if(!production)return;
|
|
57
|
+
PROCESS_MEMORY_COUNTERS_EX memory{};GetProcessMemoryInfo(GetCurrentProcess(),(PROCESS_MEMORY_COUNTERS*)&memory,sizeof(memory));
|
|
58
|
+
std::ostringstream out;out<<"{\"running\":true,\"reachable\":true,\"engine\":\"quickjs-native\",\"pet\":\""<<petKind<<"\",\"ink\":\""<<inkName<<"\",\"returnStyle\":\""<<returnStyle<<"\",\"selected\":"<<(followed?"true":"false")<<",\"visible\":"<<(visible?"true":"false")<<",\"pickerVisible\":"<<(picker?"true":"false")<<",\"paused\":"<<(paused?"true":"false")<<",\"frames\":"<<frames<<",\"paintedFrames\":"<<painted<<",\"pid\":"<<GetCurrentProcessId()<<",\"painterPid\":"<<GetCurrentProcessId()<<",\"rssBytes\":"<<memory.WorkingSetSize<<",\"peakWorkingSetBytes\":"<<memory.PeakWorkingSetSize<<",\"privateBytes\":"<<memory.PrivateUsage<<",\"watch\":{\"source\":\"off\",\"state\":\"off\",\"pendingCount\":0},\"brain\":{\"provider\":\"off\",\"state\":\"off\"},\"lastInteraction\":";
|
|
59
|
+
if(lastKind.empty())out<<"null";else out<<"{\"kind\":\""<<lastKind<<"\",\"accepted\":"<<(lastAccepted?"true":"false")<<"}";out<<"}";control.update(out.str());
|
|
60
|
+
}
|
|
61
|
+
static void switchPet(const std::string& next){
|
|
62
|
+
if(next==petKind)return;JSValue arg=JS_NewString(context,next.c_str()),result=call("__desktopSwitchPet",1,&arg);bool ok=!JS_IsException(result)&&JS_ToBool(context,result);JS_FreeValue(context,arg);JS_FreeValue(context,result);if(!ok)return;petKind=next;if(!inkExplicit)inkName=next=="cat"?"gold":"cream";previous.clear();surface.release();lastCanvas={INT_MIN,INT_MIN};layoutControls();updateStatus();
|
|
63
|
+
}
|
|
64
|
+
static BOOL CALLBACK enumerate(HWND window,LPARAM){
|
|
65
|
+
if(!IsWindowVisible(window)||GetWindow(window,GW_OWNER))return TRUE;DWORD pid=0;GetWindowThreadProcessId(window,&pid);if(!pid||pid==GetCurrentProcessId())return TRUE;wchar_t title[512];if(!GetWindowText(window,title,512))return TRUE;RECT rect;if(!GetClientRect(window,&rect)||rect.right<160||rect.bottom<120)return TRUE;LRESULT index=SendMessage(windowList,LB_ADDSTRING,0,(LPARAM)title);if(index!=LB_ERR&&index!=LB_ERRSPACE){SendMessage(windowList,LB_SETITEMDATA,index,(LPARAM)choices.size());choices.emplace_back(window,pid);}return TRUE;
|
|
66
|
+
}
|
|
67
|
+
static void attachSelection(){
|
|
68
|
+
LRESULT selected=SendMessage(windowList,LB_GETCURSEL,0,0);if(selected==LB_ERR)return;LRESULT item=SendMessage(windowList,LB_GETITEMDATA,selected,0);if(item<0||(size_t)item>=choices.size())return;const auto candidate=choices[(size_t)item];DWORD pid=0;GetWindowThreadProcessId(candidate.first,&pid);if(!IsWindow(candidate.first)||pid!=candidate.second)return;followed=candidate.first;followedPid=candidate.second;HWND old=picker;picker=nullptr;DestroyWindow(old);previous.clear();SetForegroundWindow(followed);updateStatus();
|
|
69
|
+
}
|
|
70
|
+
static void addPetCombo(HWND parent){HWND combo=CreateWindow(L"COMBOBOX",L"",WS_CHILD|WS_VISIBLE|CBS_DROPDOWNLIST,8,8,114,180,parent,(HMENU)9,GetModuleHandle(nullptr),nullptr);SendMessage(combo,CB_ADDSTRING,0,(LPARAM)L"Retriever");SendMessage(combo,CB_ADDSTRING,0,(LPARAM)L"Ginger cat");SendMessage(combo,CB_SETCURSEL,petKind=="cat"?1:0,0);}
|
|
71
|
+
static LRESULT CALLBACK pickerProcedure(HWND window,UINT message,WPARAM w,LPARAM l){
|
|
72
|
+
if(message==WM_COMMAND){int id=LOWORD(w);if(id==101||(id==100&&HIWORD(w)==LBN_DBLCLK)){attachSelection();return 0;}if(id==102){SendMessage(windowList,LB_RESETCONTENT,0,0);choices.clear();EnumWindows(enumerate,0);return 0;}if(id==9&&HIWORD(w)==CBN_SELCHANGE){switchPet(SendMessage((HWND)l,CB_GETCURSEL,0,0)==1?"cat":"dog");return 0;}}
|
|
73
|
+
if(message==WM_DESTROY){if(picker==window)picker=nullptr;if(!followed&&target)PostMessage(target,WM_CLOSE,0,0);updateStatus();return 0;}return DefWindowProc(window,message,w,l);
|
|
74
|
+
}
|
|
75
|
+
static void chooseWindow(){
|
|
76
|
+
if(!production||(diagnostic&&!testPicker))return;if(picker){SetForegroundWindow(picker);return;}visible=false;ShowWindow(overlay,SW_HIDE);ShowWindow(target,SW_HIDE);previous.clear();WNDCLASS wc{};wc.lpfnWndProc=pickerProcedure;wc.hInstance=GetModuleHandle(nullptr);wc.lpszClassName=L"RetrieviewerNativePicker";wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);RegisterClass(&wc);
|
|
77
|
+
picker=CreateWindowEx(WS_EX_TOOLWINDOW,wc.lpszClassName,L"Retrieviewer - choose an app window",WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU, std::max(0,(GetSystemMetrics(SM_CXSCREEN)-650)/2),std::max(0,(GetSystemMetrics(SM_CYSCREEN)-450)/2),650,450,nullptr,nullptr,wc.hInstance,nullptr);if(!picker)throw std::runtime_error("Cannot open window picker");
|
|
78
|
+
addPetCombo(picker);CreateWindow(L"STATIC",L"Choose the app window your companion should follow.",WS_CHILD|WS_VISIBLE,136,12,490,24,picker,nullptr,wc.hInstance,nullptr);windowList=CreateWindowEx(WS_EX_CLIENTEDGE,L"LISTBOX",L"",WS_CHILD|WS_VISIBLE|WS_VSCROLL|LBS_NOTIFY,12,44,610,310,picker,(HMENU)100,wc.hInstance,nullptr);CreateWindow(L"BUTTON",L"Attach",WS_CHILD|WS_VISIBLE|BS_DEFPUSHBUTTON,434,367,90,30,picker,(HMENU)101,wc.hInstance,nullptr);CreateWindow(L"BUTTON",L"Refresh",WS_CHILD|WS_VISIBLE,532,367,90,30,picker,(HMENU)102,wc.hInstance,nullptr);choices.clear();EnumWindows(enumerate,0);ShowWindow(picker,SW_SHOW);SetWindowPos(picker,HWND_TOP,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW);SetForegroundWindow(picker);updateStatus();
|
|
79
|
+
}
|
|
80
|
+
static void layoutControls(){
|
|
81
|
+
if(!production||diagnostic||!target)return;const std::vector<int> ids=petKind=="cat"?std::vector<int>{1,2,3,6,8,4,5,7}:std::vector<int>{1,2,3,4,5,7};
|
|
82
|
+
for(int id:{1,2,3,4,5,6,7,8})ShowWindow(GetDlgItem(target,id),SW_HIDE);int x=128;
|
|
83
|
+
for(int id:ids){const wchar_t* label=id==1?(petKind=="cat"?L"Greet":L"Ball"):id==2?L"Treat":id==3?(petKind=="cat"?L"Company":L"Tug"):id==4?(paused?L"Resume":L"Pause"):id==5?L"Window":id==6?L"Tower":id==8?L"Wheel":L"Quit";HWND button=GetDlgItem(target,id);SetWindowText(button,label);SetWindowPos(button,nullptr,x,6,64,28,SWP_NOACTIVATE|SWP_NOZORDER|SWP_SHOWWINDOW);x+=68;}
|
|
84
|
+
SendMessage(GetDlgItem(target,9),CB_SETCURSEL,petKind=="cat"?1:0,0);SetWindowPos(target,nullptr,0,0,x+6,40,SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER);previous.clear();
|
|
85
|
+
}
|
|
86
|
+
static void tick(){
|
|
87
|
+
const ULONGLONG now=GetTickCount64(),elapsed=now-startTime;if((durationMs&&elapsed>=durationMs)||(production&&!detached&&elapsed>12000)){DestroyWindow(target);return;}
|
|
88
|
+
if(!showing&&!testPicker&&(!production||diagnostic)){const int next=elapsed>=120000?5:elapsed>=95000?4:elapsed>=70000?3:elapsed>=45000?2:elapsed>=1000?1:0;if(next>stage){stage=next;if(petKind=="cat")interact(stage==1?"toy":stage==2?"treat":stage==3?"company":stage==4?"tower":"wheel");else if(stage==1||stage==2)interact("ball");else if(stage==3)interact("treat");else if(stage==4)interact("tug");else if(stage==5)interact("wall");}}
|
|
89
|
+
double dt=lastTick?std::min(.1,(now-lastTick)/1000.0):0;lastTick=now;
|
|
90
|
+
HWND scene=production&&(!diagnostic||testPicker)?followed:target;
|
|
91
|
+
DWORD scenePid=0;if(scene)GetWindowThreadProcessId(scene,&scenePid);
|
|
92
|
+
if(production&&(!diagnostic||testPicker)&&scene&&(!IsWindow(scene)||scenePid!=followedPid)){followed=nullptr;chooseWindow();scene=nullptr;}
|
|
93
|
+
if(picker||!scene||IsIconic(scene)||(production&&(!diagnostic||testPicker)&&GetForegroundWindow()!=scene&&GetForegroundWindow()!=target)||(showing&&GetForegroundWindow()!=target)){
|
|
94
|
+
visible=false;ShowWindow(overlay,SW_HIDE);if(production&&(!diagnostic||testPicker))ShowWindow(target,SW_HIDE);previous.clear();updateStatus();return;
|
|
95
|
+
}
|
|
96
|
+
RECT rect;GetClientRect(scene,&rect);POINT canvas{0,0};ClientToScreen(scene,&canvas);int cell=8,columns=rect.right/cell,rows=rect.bottom/(cell*2);while(columns*rows>125000&&cell<32){cell+=2;columns=rect.right/cell;rows=rect.bottom/(cell*2);}if(columns<8||rows<4||columns*rows>125000){visible=false;ShowWindow(overlay,SW_HIDE);previous.clear();updateStatus();return;}
|
|
97
|
+
visible=true;if(canvas.x!=lastCanvas.x||canvas.y!=lastCanvas.y||rect.right!=lastWidth||rect.bottom!=lastHeight){previous.clear();lastCanvas=canvas;lastWidth=rect.right;lastHeight=rect.bottom;}
|
|
98
|
+
if(production&&(!diagnostic||testPicker)){RECT bar;GetWindowRect(target,&bar);int x=canvas.x+std::max<LONG>(0,rect.right-(bar.right-bar.left)),y=canvas.y+8;if(bar.left!=x||bar.top!=y||!IsWindowVisible(target))SetWindowPos(target,HWND_TOPMOST,x,y,0,0,SWP_NOSIZE|SWP_NOACTIVATE|SWP_SHOWWINDOW);}
|
|
99
|
+
if(paused){updateStatus();return;}
|
|
100
|
+
LARGE_INTEGER freq,t0,t1;QueryPerformanceFrequency(&freq);QueryPerformanceCounter(&t0);JSValue args[]={JS_NewFloat64(context,dt),JS_NewInt32(context,columns),JS_NewInt32(context,rows),JS_NewBool(context,previous.empty())},result=call("__desktopFrame",4,args);for(auto value:args)JS_FreeValue(context,value);if(JS_IsException(result)||JS_IsNull(result)||JS_IsUndefined(result)){JS_FreeValue(context,result);updateStatus();return;}
|
|
101
|
+
frames++;size_t length;const char* value=JS_ToCStringLen(context,&length,result);if(!value){JS_FreeValue(context,result);throw std::runtime_error("Native frame string");}std::string text(value,length);JS_FreeCString(context,value);JS_FreeValue(context,result);if(text!=previous){paint(text,columns,rows,cell,canvas);previous=std::move(text);}QueryPerformanceCounter(&t1);double cost=(t1.QuadPart-t0.QuadPart)*1000.0/freq.QuadPart;if(production&&timings.size()>=1024)timings[timingCursor++%1024]=cost;else timings.push_back(cost);updateStatus();
|
|
102
|
+
}
|
|
103
|
+
static LRESULT CALLBACK procedure(HWND window,UINT message,WPARAM w,LPARAM l){
|
|
104
|
+
if(message==WM_TIMER){try{tick();}catch(const std::exception& e){fprintf(stderr,"Native renderer: %s\n",e.what());failed=true;DestroyWindow(window);}return 0;}
|
|
105
|
+
if(message==WM_APP+1){if(w==1||w==2)switchPet(w==2?"cat":"dog");return 0;}
|
|
106
|
+
if(message==WM_COMMAND){int id=LOWORD(w);if(id==9&&HIWORD(w)==CBN_SELCHANGE)switchPet(SendMessage((HWND)l,CB_GETCURSEL,0,0)==1?"cat":"dog");else if(id==1)interact(petKind=="cat"?"greet":"ball");else if(id==2)interact("treat");else if(id==3)interact(petKind=="cat"?"company":"tug");else if(id==4){paused=!paused;previous.clear();layoutControls();updateStatus();}else if(id==5){if(production)chooseWindow();else interact("paw");}else if(id==6)interact(production?"tower":"wall");else if(id==8)interact("wheel");else if(id==7)DestroyWindow(window);return 0;}
|
|
107
|
+
if(message==WM_SIZE||message==WM_MOVE){previous.clear();return 0;}
|
|
108
|
+
if(message==WM_DESTROY){KillTimer(window,1);followed=nullptr;if(picker){HWND old=picker;picker=nullptr;SetWindowLongPtr(old,GWLP_WNDPROC,(LONG_PTR)DefWindowProc);DestroyWindow(old);}if(overlay)DestroyWindow(overlay);overlay=nullptr;PostQuitMessage(0);return 0;}
|
|
109
|
+
return DefWindowProc(window,message,w,l);
|
|
110
|
+
}
|
|
111
|
+
static int run(JSContext* ctx,int argc,char** argv){
|
|
112
|
+
context=ctx;showing=false;durationMs=150000;for(int i=1;i<argc;i++){std::string arg=argv[i];if(arg=="--production"){production=true;durationMs=0;}if(arg=="--show"){showing=true;durationMs=0;}if(arg=="--proof")saveProof=true;if(arg=="--verify-atlas")verifyAtlas=true;if(arg.rfind("--duration=",0)==0)durationMs=(ULONGLONG)(std::stod(arg.substr(11))*1000);}
|
|
113
|
+
JSValue global=JS_GetGlobalObject(ctx),config=JS_GetPropertyStr(ctx,global,"__nativeConfig");if(JS_IsObject(config)){petKind=propertyString(ctx,config,"pet","dog");inkName=propertyString(ctx,config,"ink",petKind=="cat"?"gold":"cream");returnStyle=propertyString(ctx,config,"returnStyle","auto");inkExplicit=propertyBool(ctx,config,"inkExplicit");diagnostic=propertyBool(ctx,config,"diagnostic");testPicker=diagnostic&&propertyBool(ctx,config,"testPicker");}if(petKind!="dog"&&petKind!="cat")throw std::runtime_error("Invalid native pet");if(inkName!="cream"&&inkName!="gold"&&inkName!="dark")throw std::runtime_error("Invalid native ink");
|
|
114
|
+
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);Gdiplus::GdiplusStartupInput gdip;ULONG_PTR token;if(Gdiplus::GdiplusStartup(&token,&gdip,nullptr)!=Gdiplus::Ok)throw std::runtime_error("GDI+ startup");
|
|
115
|
+
if(production){std::wstring name=wide("Local\\BiteFoundry.Retrieviewer.Overlay."+userSid()+(diagnostic?".Diagnostics."+std::to_string(GetCurrentProcessId()):""));instanceMutex=CreateMutexW(nullptr,TRUE,name.c_str());if(!instanceMutex||GetLastError()==ERROR_ALREADY_EXISTS)throw std::runtime_error("A Retrieviewer companion is already running");}
|
|
116
|
+
HINSTANCE instance=GetModuleHandle(nullptr);WNDCLASS wc{};wc.lpfnWndProc=procedure;wc.hInstance=instance;wc.lpszClassName=L"RetrieviewerNativeHost";wc.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);RegisterClass(&wc);
|
|
117
|
+
const bool attach=production&&(!diagnostic||testPicker);target=CreateWindowEx(WS_EX_TOOLWINDOW|(attach?WS_EX_NOACTIVATE:0),wc.lpszClassName,attach?L"Retrieviewer controls":L"Retrieviewer diagnostic scene",showing?WS_OVERLAPPEDWINDOW:WS_POPUP,showing?80:-12000,showing?80:-12000,attach?552:showing?1280:3840,attach?40:showing?800:2160,nullptr,nullptr,instance,nullptr);if(!target)throw std::runtime_error("Native host window");if(!attach)followed=target;
|
|
118
|
+
WNDCLASS pet{};pet.lpfnWndProc=DefWindowProc;pet.hInstance=instance;pet.lpszClassName=L"RetrieviewerNativeDots";RegisterClass(&pet);overlay=CreateWindowEx(WS_EX_LAYERED|WS_EX_TRANSPARENT|WS_EX_TOOLWINDOW|WS_EX_NOACTIVATE|WS_EX_TOPMOST,pet.lpszClassName,L"",WS_POPUP,0,0,1,1,nullptr,nullptr,instance,nullptr);if(!overlay)throw std::runtime_error("Native overlay window");
|
|
119
|
+
if(attach){addPetCombo(target);for(int id=1;id<=8;id++)CreateWindow(L"BUTTON",L"",WS_CHILD|BS_PUSHBUTTON,0,0,1,1,target,(HMENU)(INT_PTR)id,instance,nullptr);layoutControls();chooseWindow();}
|
|
120
|
+
else if(showing){const wchar_t* names[]={L"Ball",L"Treat",L"Tug",L"Pause",L"Paw",L"Wall",L"Quit"};for(int i=0;i<7;i++)CreateWindow(L"BUTTON",names[i],WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,8+i*70,8,66,28,target,(HMENU)(INT_PTR)(i+1),instance,nullptr);}
|
|
121
|
+
if(!attach)ShowWindow(target,showing?SW_SHOW:SW_SHOWNOACTIVATE);startTime=GetTickCount64();lastTick=0;timings.reserve(1024);SetTimer(target,1,33,nullptr);
|
|
122
|
+
if(production){updateStatus();control.start(propertyString(ctx,config,"pipe"),propertyString(ctx,config,"token"),propertyString(ctx,config,"sessionFile"),[](const std::string& command){PostMessage(target,command=="stop"?WM_CLOSE:WM_APP+1,command=="pet-cat"?2:command=="pet-dog"?1:0,0);});printf("{\"ready\":true,\"pid\":%lu}\n",GetCurrentProcessId());fflush(stdout);startupGuard=std::thread([]{std::string ack;bool ok=std::getline(std::cin,ack)&&ack=="DETACH";if(ok){detached=true;printf("{\"detached\":true}\n");fflush(stdout);}else{DWORD pid=0;GetWindowThreadProcessId(target,&pid);if(pid==GetCurrentProcessId())PostMessage(target,WM_CLOSE,0,0);}});}
|
|
123
|
+
else{detached=true;printf("{\"stage\":\"native-window\",\"width\":%d,\"height\":%d,\"durationMs\":%llu,\"pid\":%lu}\n",showing?1280:3840,showing?800:2160,durationMs,GetCurrentProcessId());fflush(stdout);}
|
|
124
|
+
JS_FreeValue(ctx,config);JS_FreeValue(ctx,global);MSG msg;while(GetMessage(&msg,nullptr,0,0)>0){TranslateMessage(&msg);DispatchMessage(&msg);}
|
|
125
|
+
if(startupGuard.joinable()){CancelSynchronousIo(startupGuard.native_handle());startupGuard.join();}control.close();JSValue result=call("__desktopDispose");JS_FreeValue(ctx,result);surface.release();if(instanceMutex){ReleaseMutex(instanceMutex);CloseHandle(instanceMutex);instanceMutex=nullptr;}Gdiplus::GdiplusShutdown(token);
|
|
126
|
+
std::sort(timings.begin(),timings.end());double sum=0;for(auto t:timings)sum+=t;if(!production||diagnostic){printf("{\"stage\":\"native-exit\",\"painted\":%d,\"renders\":%llu,\"meanMs\":%.3f,\"p95Ms\":%.3f,\"maxMs\":%.3f,\"failed\":%s}\n",painted,frames,timings.empty()?0:sum/timings.size(),timings.empty()?0:timings[(size_t)(timings.size()*.95)],timings.empty()?0:timings.back(),failed?"true":"false");fflush(stdout);}return failed?1:0;
|
|
127
|
+
}
|
|
128
|
+
}
|