diff --git a/.gitignore b/.gitignore index 6734284..6a32990 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ bin_* .obj_* mcp/locale.* +MEMORY.md +.DS_Store diff --git a/mcc/Dispatcher.cpp b/mcc/Dispatcher.cpp index ba10e63..31064b6 100644 --- a/mcc/Dispatcher.cpp +++ b/mcc/Dispatcher.cpp @@ -97,7 +97,10 @@ HOOKPROTONHNO(DefaultLoadFunc, ULONG, struct HTMLview_LoadMsg* lmsg) { case HTMLview_Open: { - STRPTR file = lmsg->lm_Params.lm_Open.URL+7; + STRPTR url = lmsg->lm_Params.lm_Open.URL; + STRPTR file = url; + if(!strncmp(url, "file://", 7)) + file = url + 7; APTR result = lmsg->lm_Userdata = (APTR)Open(file, MODE_OLDFILE); return((ULONG)result); } diff --git a/mcc/IM_Render.cpp b/mcc/IM_Render.cpp index bb98b84..cf51baf 100644 --- a/mcc/IM_Render.cpp +++ b/mcc/IM_Render.cpp @@ -102,6 +102,11 @@ RenderEngine::RenderEngine (struct Screen *scr, struct DecoderData *data) TmpRP.Layer = NULL; TmpRP.BitMap = NULL; InitRastPort(&BMpRP); + /* AllocateFrame() uses `first = FirstFrame ? FALSE : TRUE` to detect the + initial frame and then calls StatusItem->Start(). The LastFrame self-ref + trick only works if FirstFrame starts as NULL — stack-allocated + RenderEngine instances would otherwise see garbage here. */ + FirstFrame = NULL; LastFrame = (struct PictureFrame *)&FirstFrame; } @@ -171,7 +176,7 @@ BOOL RenderEngine::AllocateFrame (ULONG width, ULONG height, ULONG animdelay, UL goto error; } - if(first) + if(first && Data->StatusItem) Data->StatusItem->Start(FirstFrame); LastFrame = LastFrame->Next; diff --git a/mcc/ImageManager.cpp b/mcc/ImageManager.cpp index 952cc57..991bef1 100644 --- a/mcc/ImageManager.cpp +++ b/mcc/ImageManager.cpp @@ -21,54 +21,85 @@ ***************************************************************************/ #include -#include -#include -#include +#include +#include + #include #include #include +#include +#include +#include #include + #include -#if defined(__amigaos4__) -#include -#elif defined(__MORPHOS__) -#include -#endif +#include "mcc_common.h" -#include "ImageManager.h" +#include "IM_Render.h" #include "IM_Output.h" #include "ImageDecoder.h" #include "Animation.h" #include "SharedData.h" +#include +#include +#include +#include +#include + +#ifndef PMODE_V43 +#define PMODE_V43 1 +#endif +#ifndef MODE_V43 +#define MODE_V43 PMODE_V43 +#endif +#ifndef PDTA_MaskPlane +#define PDTA_MaskPlane (DTA_Dummy + 258) +#endif +#ifndef PDTA_Mask +#define PDTA_Mask PDTA_MaskPlane +#endif + #include "classes/HostClass.h" #include "classes/ImgClass.h" #include "classes/SuperClass.h" -//#include "private.h" - -#include "SDI_stdarg.h" #include #include #include "private.h" -#ifdef __amigaos4__ -#define GetImageDecoderClass(base) ( (struct IClass *)(IExec->EmulateTags)(base, ET_Offset, -30, ET_RegisterA6, base, ET_SaveRegs, TRUE, TAG_DONE) ) +#if defined(__amigaos4__) +/* On OS4 DeleteFile is deprecated, use Delete instead. + The SDK will then map Delete to IDOS->Delete. */ +#define DeleteFile Delete +#endif + +#if defined(__amigaos3__) +#define GetImageDecoderClass(base) (struct IClass *)DoMethod(base, ET_Offset, -30, ET_RegisterA6, base, ET_SaveRegs, TRUE, TAG_DONE) #elif defined(__MORPHOS__) #define GetImageDecoderClass(base) (struct IClass *)({ REG_A6 = (ULONG) (base); MyEmulHandle->EmulCallDirectOS(-30); }) #elif defined(__cplusplus) -#warning FIXME #define GetImageDecoderClass(base) NULL #else -#define GetImageDecoderClass(base) ( (struct IClass *(*)(REG(a6, struct Library *))) ((UBYTE *)base-30) )(base) +#define GetImageDecoderClass(base) ( (struct IClass *(*)(REG(a6, struct Library *))) ((base)->lp_VTable[-5]) ) (base) #endif -struct DecoderThreadStartupMessage +extern struct Library *CyberGfxBase; +extern struct Library *DataTypesBase; + +#if defined(__amigaos4__) +extern struct DataTypesIFace *IDataTypes; +extern struct DOSIFace *IDOS; +#endif + +struct SignalSemaphore ImageMutex; + +CONSTRUCTOR(MutexInit, 5) { - struct Message message; - struct Args *args; -}; + memset(&ImageMutex, 0, sizeof(ImageMutex)); + InitSemaphore(&ImageMutex); +} ImageCacheItem::ImageCacheItem (STRPTR url, struct PictureFrame *pic) { @@ -100,6 +131,7 @@ ImageCache::~ImageCache () VOID ImageCache::AddImage (STRPTR url, struct PictureFrame *pic) { + ObtainSemaphore(&ImageMutex); struct ImageCacheItem *item = new (std::nothrow) struct ImageCacheItem (url, pic); if (item) { @@ -129,48 +161,49 @@ VOID ImageCache::AddImage (STRPTR url, struct PictureFrame *pic) preprev = prev; } } - if(preprev && !preprev->Next) - LastEntry = preprev; } + ReleaseSemaphore(&ImageMutex); } struct PictureFrame *ImageCache::FindImage (STRPTR url, ULONG width, ULONG height) { - struct ImageCacheItem *prev, *first = FirstEntry; + ObtainSemaphore(&ImageMutex); + struct ImageCacheItem *preprev = NULL, *prev, *first = FirstEntry; while(first) { - if(!strcmp(first->URL, url) && first->Picture->MatchSize(width, height)) + prev = first; + first = first->Next; + + if(!strcmp(url, prev->URL) && prev->Picture->MatchSize(width, height)) { - if(first != LastEntry) + if(prev != LastEntry) { - if(first == FirstEntry) - FirstEntry = first->Next; - else prev->Next = first->Next; - LastEntry = (LastEntry->Next = first); - first->Next = NULL; + if(prev == FirstEntry) + FirstEntry = first; + else preprev->Next = first; + + LastEntry = (LastEntry->Next = prev); + prev->Next = NULL; } - return(first->Picture); + ReleaseSemaphore(&ImageMutex); + return(prev->Picture); } - prev = first; - first = first->Next; + preprev = prev; } + ReleaseSemaphore(&ImageMutex); return(NULL); } -inline BOOL Match (STRPTR url, struct ImageCacheItem *item) +static BOOL Match (STRPTR url, struct ImageCacheItem *item) { BOOL res; - switch((LONG)url) + switch((ULONG)url) { - case MUIV_HTMLview_FlushImage_All: + case (ULONG)MUIV_HTMLview_FlushImage_All: res = TRUE; break; - case MUIV_HTMLview_FlushImage_Displayed: - res = item->Picture->LockCnt > 1; - break; - - case MUIV_HTMLview_FlushImage_Nondisplayed: + case (ULONG)MUIV_HTMLview_FlushImage_Displayed: res = item->Picture->LockCnt == 1; break; @@ -183,6 +216,7 @@ inline BOOL Match (STRPTR url, struct ImageCacheItem *item) VOID ImageCache::FlushCache (STRPTR url) { + ObtainSemaphore(&ImageMutex); struct ImageCacheItem *prev, *preprev = NULL, *first = FirstEntry; while(first) { @@ -209,6 +243,7 @@ VOID ImageCache::FlushCache (STRPTR url) if(!(LastEntry = preprev)) LastEntry = (struct ImageCacheItem *)&FirstEntry; + ReleaseSemaphore(&ImageMutex); } DecodeItem::DecodeItem (Object *obj, struct HTMLviewData *data, struct ImageList *image) @@ -339,20 +374,8 @@ BOOL DecodeItem::Update () return(result); } -/* This function is redundant, but keept for debugging purposes */ DecodeQueueManager::~DecodeQueueManager () { -/* struct DecodeItem *prev, *first = Queue; - while(first) - { - prev = first; - first = first->Next; - - D(DBF_ALWAYS, "Status: %ld, Y: %ld, Pass: %ld, Started: %ld", - prev->Status, prev->CurrentY, prev->CurrentPass, prev->Started); - - delete prev; - }*/ } VOID DecodeQueueManager::InsertElm (struct DecodeItem *item) { @@ -389,7 +412,6 @@ ULONG DecodeQueueManager::DumpQueue () ULONG total = 0; ObtainSemaphore(&Mutex); struct DecodeItem *prev, *first = Queue; -//BOOL result = first ? TRUE : FALSE; while(first) { prev = first; @@ -397,7 +419,6 @@ ULONG DecodeQueueManager::DumpQueue () if(prev->Update()) { total++; - //result = FALSE; } } ReleaseSemaphore(&Mutex); @@ -423,263 +444,6 @@ VOID DecodeQueueManager::InvalidateQueue (Object *obj) ReleaseSemaphore(&Mutex); } -CPPDISPATCHER(DecoderDispatcher) -{ - ULONG result = 0; - struct DecoderData *data; - - //return 0; - if (msg->MethodID!=OM_NEW) - data = (struct DecoderData *)INST_DATA(cl, obj); - - switch(msg->MethodID) - { - case OM_NEW: - { - if((obj = (Object *)DoSuperMethodA(cl, obj, (Msg)msg))) - { - struct DecoderData *data = (struct DecoderData *)INST_DATA(cl, obj); - struct opSet *nmsg = (struct opSet *)msg; - BOOL no_dither = FALSE; - ULONG width = 0, height = 0; - - struct TagItem *tag, *tags = nmsg->ops_AttrList; - while((tag = NextTagItem(&tags))) - { - LONG ti_Data = tag->ti_Data; - switch(tag->ti_Tag) - { - case IDA_Screen: - data->Scr = (struct Screen *)ti_Data; - break; - - case IDA_NoDither: - no_dither = ti_Data; - break; - - case IDA_Width: - width = ti_Data; - break; - - case IDA_Height: - height = ti_Data; - break; - - case IDA_HTMLview: - data->HTMLview = (Object *)ti_Data; - break; - - case IDA_LoadHook: - data->LoadHook = (struct Hook *)ti_Data; - break; - - case IDA_LoadMsg: - data->LoadMsg = (struct HTMLview_LoadMsg *)ti_Data; - break; - - case IDA_StatusStruct: - data->StatusItem = (struct DecodeItem *)ti_Data; - break; - - case IDA_StartBuffer: - data->StartBuffer = (UBYTE *)ti_Data; - break; - - case IDA_BytesInBuffer: - data->BytesInBuffer = ti_Data; - break; - - case IDA_Gamma: - data->Gamma = ti_Data; - break; - } - } - - struct Screen *scr; - if((scr = data->Scr)) - { - ULONG depth = GetBitMapAttr(scr->RastPort.BitMap, BMA_DEPTH); - - if(!CyberGfxBase && depth > 8) - depth = 8; - - class ScaleEngine *img; - if(depth >= 15) - img = new (std::nothrow) TrueColourEngine(scr, width, height, data); - else if(no_dither) - img = new (std::nothrow) LowColourNDEngine(scr, width, height, data); - else img = new (std::nothrow) LowColourEngine(scr, width, height, data); - - if((data->ImgObj = img)) - return((ULONG)obj); - } - - CoerceMethod(cl, obj, OM_DISPOSE); - } - } - break; - - case IDM_Decode: - { - if((result = DoSuperMethodA(cl, obj, (Msg)msg))) - data->ImgObj->FlushBuffers(); - } - break; - - case OM_DISPOSE: - { - delete data->ImgObj; - result = DoSuperMethodA(cl, obj, (Msg)msg); - } - break; - - case OM_GET: - { - struct opGet *gmsg = (struct opGet *)msg; - - switch(gmsg->opg_AttrID) - { - case IDA_Gamma: - { - if(data->Gamma) - { - *gmsg->opg_Storage = data->Gamma; - result = TRUE; - } - } - break; - - default: - result = DoSuperMethodA(cl, obj, (Msg)msg); - break; - } - } - break; - - case IDM_Read: - { - struct IDP_Read *rmsg = (struct IDP_Read *)msg; - - ULONG len = rmsg->Length; - STRPTR buf = (STRPTR)rmsg->Buffer; - if(data->BytesInBuffer) - { - result = MIN(len, data->BytesInBuffer); - memcpy(buf, data->StartBuffer, result); - buf += result; - len -= result; - data->StartBuffer += result; - data->BytesInBuffer -= result; - } - - data->LoadMsg->lm_Params.lm_Read.Buffer = buf; - data->LoadMsg->lm_Params.lm_Read.Size = len; - result += CallHookPkt(data->LoadHook, data->HTMLview, data->LoadMsg); - } - break; - - case IDM_Skip: - { - struct IDP_Skip *smsg = (struct IDP_Skip *)msg; - UBYTE skip[512]; - LONG left = smsg->Length; - - if(data->BytesInBuffer) - { - LONG size = MIN((LONG)data->BytesInBuffer, left); - left -= size; - data->BytesInBuffer -= size; - } - - while(left > 0) - { - data->LoadMsg->lm_Params.lm_Read.Buffer = (STRPTR)skip; - data->LoadMsg->lm_Params.lm_Read.Size = left > 512 ? 512 : left; - LONG sub = CallHookPkt(data->LoadHook, data->HTMLview, data->LoadMsg); - if(!sub) - break; - left -= sub; - } - } - break; - - case IDM_AllocateFrameTags: - { - struct IDP_AllocateFrameTags *smsg = (struct IDP_AllocateFrameTags *)msg; - ULONG Interlaced = InterlaceNONE, AnimDelay = 0, Disposal = DisposeNOP, LeftOfs = 0, TopOfs = 0, Transparency = TransparencyNONE; - struct RGBPixel *Background = NULL; - - struct TagItem *tag, *tags = &smsg->Tags; - while((tag = NextTagItem(&tags))) - { - LONG ti_Data = tag->ti_Data; - switch(tag->ti_Tag) - { - case AFA_Interlaced: - Interlaced = ti_Data; - break; - - case AFA_Transparency: - Transparency = ti_Data; - break; - - case AFA_LeftOfs: - LeftOfs = ti_Data; - break; - - case AFA_TopOfs: - TopOfs = ti_Data; - break; - - case AFA_AnimDelay: - AnimDelay = ti_Data; - break; - - case AFA_Disposal: - Disposal = ti_Data; - break; - - case AFA_Background: - Background = (struct RGBPixel *)&ti_Data; - break; - } - } - - result = data->ImgObj->SetDimensions(smsg->Width, smsg->Height, Interlaced, AnimDelay, Disposal, LeftOfs, TopOfs, Background, Transparency); - } - break; - - case IDM_SetDimensions: - { - struct IDP_SetDimensions *smsg = (struct IDP_SetDimensions *)msg; - result = data->ImgObj->SetDimensions(smsg->Width, smsg->Height, smsg->Interlaced, smsg->AnimDelay, smsg->Disposal, smsg->LeftOfs, smsg->TopOfs, &smsg->Background, TransparencyNONE); - } - break; - - case IDM_GetLineBuffer: - { - struct IDP_GetLineBuffer *gmsg = (struct IDP_GetLineBuffer *)msg; - result = (ULONG)data->ImgObj->GetLineBuffer(gmsg->Y, gmsg->LastPass); - } - break; - - case IDM_DrawLineBuffer: - { - struct IDP_DrawLineBuffer *dmsg = (struct IDP_DrawLineBuffer *)msg; - data->ImgObj->DrawLineBuffer(dmsg->LineBuffer, dmsg->Y, dmsg->Height); - result = data->StatusItem->Abort; - } - break; - - default: - { - result = DoSuperMethodA(cl, obj, (Msg)msg); - } - break; - } - return(result); -} - struct Args { Args (Object *obj, struct HTMLviewData *data, struct ImageList *image, struct Screen *scr, LONG sigbit, struct Task *parenttask) @@ -692,11 +456,13 @@ struct Args } Obj = obj; + App = (Object *)DoMethod(obj, MUIM_GetConfigItem, MUIC_Application, 0); Data = data; Img = image; Scr = scr; SigBit = sigbit; ParentTask = parenttask; + MainSigBit = data->SigBit; } ~Args () @@ -705,138 +471,43 @@ struct Args delete TaskName; } - Object *Obj; + Object *Obj, *App; STRPTR TaskName, Name; struct ImageList *Img; struct Screen *Scr; struct HTMLviewData *Data; - LONG SigBit; + LONG SigBit, MainSigBit; struct Task *ParentTask; }; -struct DecoderInfo -{ - CONST_STRPTR Name; - BOOL (*MatchFunc)(UBYTE *); - struct Library *Base; - struct IClass *Class; -}; - -BOOL MatchGIF (UBYTE *x) { return(*((ULONG *)x) == MAKE_ID('G','I','F','8')); } -BOOL MatchJPG (UBYTE *x) { return(*((ULONG *)x) == 0xFFD8FFE0 && (*((ULONG *)(((UBYTE *)x) + 6)) == MAKE_ID('J','F','I','F'))); } -BOOL MatchPNG (UBYTE *x) { return(*((ULONG *)x) == 0x89504e47 && ((ULONG *)x)[1] == 0x0d0a1a0a); } -BOOL MatchDT (UNUSED UBYTE *x) { return(TRUE); } -#define GIFDecoder "gif.decoder" -#define JPGDecoder "jfif.decoder" -#define PNGDecoder "png.decoder" -#define DTDecoder "datatype.decoder" - -struct DecoderInfo Decoders[] = -{ - { GIFDecoder, MatchGIF, NULL, NULL }, - { JPGDecoder, MatchJPG, NULL, NULL }, - { PNGDecoder, MatchPNG, NULL, NULL }, - { DTDecoder, MatchDT, NULL, NULL }, - { NULL, NULL, NULL, NULL } -}; - -CONST_STRPTR DecoderPaths[] = +struct DecoderThreadStartupMessage { - "MUI:Libs/MUI/HTMLview/", - "LIBS:MUI/HTMLview/", - "MUI:HTMLview/", - "PROGDIR:Decoders/", - "PROGDIR:MUI/HTMLview/", - "LIBS:Decoders/", - NULL + struct Message message; + struct Args *args; }; -struct SignalSemaphore DecoderMutex; - -static struct Library *ClassOpen(CONST_STRPTR decoder) -{ - struct Library *result; - CONST_STRPTR *path = DecoderPaths; - - do { - - char name[64]; - strcpy(name, *path++); - strcat(name, decoder); - - result = OpenLibrary(name, 0); +/* Accumulate diagnostic output in a ring-style buffer and rewrite + T:htmlview_dt.log on each call. Avoids seek/append portability issues + across OS3/OS4/MorphOS. */ +static char DTLogBuf[8192]; +static ULONG DTLogLen = 0; - } while(!result && *path); - - return(result); -} - - -Object *NewDecoderObjectA(UBYTE *buf,struct TagItem *attrs) +static void DTLog(const char *line) { - struct TagItem *tags = attrs; - struct IClass *cl = NULL; - struct DecoderInfo *decoders = Decoders; - - //return 0; - ObtainSemaphore(&DecoderMutex); - - while(decoders->Name && !cl) - { - if(decoders->MatchFunc(buf)) + ULONG need = strlen(line) + 1; + if (DTLogLen + need + 1 >= sizeof(DTLogBuf)) + return; /* silently drop if overflow */ + memcpy(DTLogBuf + DTLogLen, line, need - 1); + DTLogLen += need - 1; + DTLogBuf[DTLogLen++] = '\n'; + DTLogBuf[DTLogLen] = 0; + + BPTR f = Open((STRPTR)"T:htmlview_dt.log", MODE_NEWFILE); + if (f) { - if(!(cl = decoders->Class)) - { - if((decoders->Base = ClassOpen(decoders->Name))) - { - if((cl = MakeClass(NULL, NULL, GetImageDecoderClass(decoders->Base), sizeof(DecoderData), 0L))) - { - #if defined(__amigaos3__) - cl->cl_Dispatcher.h_SubEntry = (ULONG (*)())ENTRY(DecoderDispatcher); - cl->cl_Dispatcher.h_Entry = (ULONG (*)())HookEntry; - cl->cl_Dispatcher.h_Data = 0; - #else - cl->cl_Dispatcher.h_SubEntry = 0; - cl->cl_Dispatcher.h_Entry = (HOOKFUNC)ENTRY(DecoderDispatcher); - cl->cl_Dispatcher.h_Data = 0; - #endif - - decoders->Class = cl; - } - } - } + Write(f, DTLogBuf, DTLogLen); + Close(f); } - decoders++; - } - - ReleaseSemaphore(&DecoderMutex); - - if(cl) - { - return((Object *)NewObjectA(cl, NULL, tags)); - } - - else return(NULL); -} - -CONSTRUCTOR(PrepareDecoders, 4) -{ - memset(&DecoderMutex,0,sizeof(struct SignalSemaphore)); - InitSemaphore(&DecoderMutex); -} - -DESTRUCTOR(FlushDecoders, 4) -{ - struct DecoderInfo *decoders = Decoders; - while(decoders->Name) - { - if(decoders->Class) - FreeClass(decoders->Class); - if(decoders->Base) - CloseLibrary(decoders->Base); - - decoders++; - } } extern "C" void DecoderThread(void) @@ -848,68 +519,222 @@ extern "C" void DecoderThread(void) if((startup = (struct DecoderThreadStartupMessage *)GetMsg(&me->pr_MsgPort)) != NULL) { struct Args *args = startup->args; + struct Task *parent = args->ParentTask; + ULONG sigbit = args->SigBit; ReplyMsg((struct Message *)startup); BOOL result = FALSE; struct ImageList *image = args->Img; - ULONG width = image->Width, height = image->Height; - BOOL dither = args->Data->Share->DitherType; - ULONG gamma = args->Data->Share->GammaCorrection; struct Hook *loadhook = args->Data->ImageLoadHook; struct HTMLview_LoadMsg loadmsg; - loadmsg.lm_App = _app(args->Obj); + loadmsg.lm_App = args->App; + + { + char logbuf[256]; + sprintf(logbuf, "thread start url=%s", args->Name); + DTLog(logbuf); + } + D(DBF_STARTUP, "DecoderThread: started for %s", args->Name); struct DecodeItem *item = new (std::nothrow) struct DecodeItem(args->Obj, args->Data, image); if (item) { args->Data->Share->DecodeQueue.InsertElm(item); - Signal(args->ParentTask, 1 << args->SigBit); + Signal(parent, 1 << sigbit); loadmsg.lm_Type = HTMLview_Open; loadmsg.lm_PageID = item->PageID; loadmsg.lm_Params.lm_Open.URL = args->Name; loadmsg.lm_Params.lm_Open.Flags = MUIF_HTMLview_LoadMsg_Image; - if(CallHookPkt(loadhook, args->Obj, &loadmsg)) + + ULONG openres = CallHookPkt(loadhook, args->Obj, &loadmsg); + { + char logbuf[64]; + sprintf(logbuf, " hook-open => %lu", openres); + DTLog(logbuf); + } + if(openres) { - UBYTE buf[12]; - loadmsg.lm_Type = HTMLview_Read; - loadmsg.lm_Params.lm_Read.Buffer = (char *)buf; - loadmsg.lm_Params.lm_Read.Size = 10; - ULONG len = CallHookPkt(loadhook, args->Obj, &loadmsg); - - struct TagItem attrs[] = - {{IDA_StartBuffer, (ULONG)buf}, - {IDA_BytesInBuffer,(ULONG)len}, - {IDA_Width, (ULONG)width}, - {IDA_Height, (ULONG)height}, - {IDA_Screen, (ULONG)args->Scr}, - {IDA_HTMLview, (ULONG)args->Obj}, - {IDA_LoadHook, (ULONG)loadhook}, - {IDA_LoadMsg, (ULONG)&loadmsg}, - {IDA_StatusStruct, (ULONG)item}, - {IDA_NoDither, (ULONG)dither}, - {IDA_Gamma, (ULONG)gamma}, - {TAG_DONE,0}}; - Object *decoder = NewDecoderObjectA(buf,attrs); - - if(decoder) + D(DBF_STARTUP, "DecoderThread: hook open success"); + char tmpname[64]; + STRPTR ext = (STRPTR)""; + STRPTR url = args->Name; + if (strstr(url, ".png") || strstr(url, ".PNG")) ext = (STRPTR)".png"; + else if (strstr(url, ".gif") || strstr(url, ".GIF")) ext = (STRPTR)".gif"; + else if (strstr(url, ".jpg") || strstr(url, ".JPG") || strstr(url, ".jpeg")) ext = (STRPTR)".jpg"; + + sprintf(tmpname, "T:hv_%lx_%lx%s", (ULONG)item->PageID, (ULONG)FindTask(NULL), ext); + BPTR tmpf = Open(tmpname, MODE_NEWFILE); + { + char logbuf[128]; + sprintf(logbuf, " open(%s, NEWFILE) => %lx", tmpname, (ULONG)tmpf); + DTLog(logbuf); + } + if (tmpf) { - result = DoMethod(decoder, IDM_Decode); - DisposeObject(decoder); + char *buf = new (std::nothrow) char[8192]; + if (buf) + { + LONG rd; + ULONG total = 0; + loadmsg.lm_Type = HTMLview_Read; + loadmsg.lm_Params.lm_Read.Buffer = buf; + loadmsg.lm_Params.lm_Read.Size = 8192; + + while ((rd = CallHookPkt(loadhook, args->Obj, &loadmsg)) > 0) + { + Write(tmpf, buf, rd); + total += rd; + if (item->Abort) break; + } + delete[] buf; + { + char logbuf[64]; + sprintf(logbuf, " recv %lu bytes", total); + DTLog(logbuf); + } + D(DBF_STARTUP, "DecoderThread: wrote %lu bytes to %s", total, tmpname); + } + Close(tmpf); + + if (!item->Abort) + { + Object *dt = NewDTObject(tmpname, + DTA_GroupID, GID_PICTURE, + DTA_SourceType, DTST_FILE, + PDTA_DestMode, PMODE_V43, + PDTA_Remap, TRUE, + PDTA_Screen, args->Scr, + PDTA_UseFriendBitMap, TRUE, + TAG_DONE); + + { + char logbuf[128]; + sprintf(logbuf, " NewDTObject(%s) => %lx ioerr=%ld", + tmpname, (ULONG)dt, IoErr()); + DTLog(logbuf); + } + if (dt) + { + D(DBF_STARTUP, "DecoderThread: DT created"); + + /* Trigger layout / colour remap for the target screen. */ + DoMethod(dt, DTM_PROCLAYOUT, NULL, 1L); + + struct BitMapHeader *bmhd = NULL; + struct BitMap *srcbmp = NULL; + UBYTE *mask = NULL; + + GetDTAttrs(dt, + PDTA_BitMapHeader, (ULONG)&bmhd, + PDTA_DestBitMap, (ULONG)&srcbmp, + PDTA_MaskPlane, (ULONG)&mask, + TAG_DONE); + + /* PDTA_DestBitMap is only populated after PROCLAYOUT; + fall back to the raw source bitmap otherwise. */ + if (!srcbmp) + GetDTAttrs(dt, PDTA_BitMap, (ULONG)&srcbmp, TAG_DONE); + + ULONG width = 0, height = 0; + if (bmhd) + { + width = bmhd->bmh_Width; + height = bmhd->bmh_Height; + } + else + { + GetDTAttrs(dt, + DTA_NominalHoriz, (ULONG)&width, + DTA_NominalVert, (ULONG)&height, + TAG_DONE); + } + + { + char logbuf[160]; + sprintf(logbuf, + " DT %lux%lu srcbmp=%lx mask=%lx bmhd=%lx", + width, height, (ULONG)srcbmp, (ULONG)mask, (ULONG)bmhd); + DTLog(logbuf); + } + D(DBF_STARTUP, "DecoderThread: DT %lux%lu src=%lx mask=%lx", + width, height, (ULONG)srcbmp, (ULONG)mask); + + struct DecoderData decData; + memset(&decData, 0, sizeof(decData)); + decData.Scr = args->Scr; + decData.StatusItem = item; + decData.HTMLview = args->Obj; + decData.LoadHook = loadhook; + decData.LoadMsg = &loadmsg; + + RenderEngine render(args->Scr, &decData); + BOOL alloc_ok = FALSE; + if (srcbmp && width > 0 && height > 0) + { + alloc_ok = render.AllocateFrame(width, height, 0, DisposeNOP, 0, 0, NULL, + mask ? TransparencySINGLE : TransparencyNONE, PicFLG_Full); + } + { + char logbuf[128]; + sprintf(logbuf, " AllocateFrame => %s item->Picture=%lx", + alloc_ok ? "OK" : "FAIL", (ULONG)item->Picture); + DTLog(logbuf); + } + if (alloc_ok && item->Picture && item->Picture->BMp) + { + /* BltBitMap both src and dest are friend bitmaps + of args->Scr, so this is a straight copy with + any necessary on-the-fly format conversion. */ + BltBitMap(srcbmp, 0, 0, + item->Picture->BMp, 0, 0, + width, height, + 0xC0, 0xFF, NULL); + WaitBlit(); + + if (mask && item->Picture->Mask) + CopyMem(mask, item->Picture->Mask, RASSIZE(width, height)); + + item->Enter(); + item->CurrentY = height; + item->Started = FALSE; + item->Leave(); + result = TRUE; + DTLog(" blit OK"); + D(DBF_STARTUP, "DecoderThread: blit OK"); + } + else + { + char logbuf[160]; + sprintf(logbuf, + " decode failed: srcbmp=%lx w=%lu h=%lu alloc=%s", + (ULONG)srcbmp, width, height, alloc_ok ? "OK" : "FAIL"); + DTLog(logbuf); + D(DBF_STARTUP, "DecoderThread: no source bitmap / AllocateFrame failed"); + } + DisposeDTObject(dt); + } else { + D(DBF_STARTUP, "DecoderThread: NewDTObject failed for %s", tmpname); + } + } + DeleteFile(tmpname); } loadmsg.lm_Type = HTMLview_Close; CallHookPkt(loadhook, args->Obj, &loadmsg); + } else { + D(DBF_STARTUP, "DecoderThread: hook open failed for %s", args->Name); } - delete args; - item->Enter(); item->Status = result ? StatusDone : StatusError; item->Thread = NULL; item->Leave(); + + Signal(parent, 1 << args->MainSigBit); } + delete args; } } @@ -918,12 +743,6 @@ VOID DecodeImage (Object *obj, UNUSED struct IClass *cl, struct ImageList *image LONG sigbit = 0; if(image && (sigbit = AllocSignal(-1)) > 0) { - if(SetSignal(0L, 1 << sigbit) & (1 << sigbit)) - { - DisplayBeep(NULL); - W(DBF_STARTUP, "Signal was already pending!\n"); - } - STRPTR name = NULL; struct List *pscrs = LockPubScreenList(); for(struct Node *node = pscrs->lh_Head; node->ln_Succ; node = node->ln_Succ) @@ -942,10 +761,9 @@ VOID DecodeImage (Object *obj, UNUSED struct IClass *cl, struct ImageList *image if (!args) { UnlockPubScreen(NULL,lock); + FreeSignal(sigbit); return; } - /*char str_args[10]; - sprintf(str_args, "%lx", (ULONG)args);*/ #if defined(__PPC__) static const BOOL FBlit = FALSE; @@ -987,16 +805,15 @@ VOID DecodeImage (Object *obj, UNUSED struct IClass *cl, struct ImageList *image startup.args = args; PutMsg(&thread->pr_MsgPort, (struct Message *)&startup); - Remove((struct Node *)WaitPort(&replyPort)); + + WaitPort(&replyPort); + while (GetMsg(&replyPort)); Wait(1 << sigbit); + } else { + delete args; } FreeSignal(sigbit); } - else - { - DisplayBeep(NULL); - W(DBF_STARTUP, "No image! - %s (%ld)\n", image->ImageName, sigbit); - } } diff --git a/mcc/LibLoad_Test.c b/mcc/LibLoad_Test.c index 877dcc4..aa0df24 100644 --- a/mcc/LibLoad_Test.c +++ b/mcc/LibLoad_Test.c @@ -1,3 +1,4 @@ + #include #include #include @@ -5,135 +6,263 @@ #include #include #include - -#ifndef MUIA_HTMLview_Contents -#define MUIA_HTMLview_Contents 0xad003005 +#include +#if defined(__MORPHOS__) +#include +#elif defined(__amigaos4__) +#include #endif +#include #if defined(__amigaos4__) -struct Library *IntuitionBase; -struct Library *MUIMasterBase; +struct Library *IntuitionBase; +struct Library *MUIMasterBase; struct IntuitionIFace *IIntuition; struct MUIMasterIFace *IMUIMaster; +struct Library *SocketBase; +struct SocketIFace *ISocket; #else struct IntuitionBase *IntuitionBase; -struct DosLibrary *DOSBase; -struct Library *MUIMasterBase; +struct Library *MUIMasterBase; +struct Library *SocketBase; #endif -static void ShowError(char *msg) -{ - struct EasyStruct es = { - sizeof(struct EasyStruct), - 0, - (UBYTE *)"LibLoad_Test Error", - (UBYTE *)msg, - (UBYTE *)"OK" - }; - EasyRequestArgs(NULL, &es, 0, NULL); -} +#include "test_image_hook.h" -static void LogMsg(BPTR fh, char *msg) -{ - if (fh) VFPrintf(fh, "%s\n", (APTR)msg); -} +enum { + MSG_OPEN_HTMLVIEW = 1, + MSG_QUIT = 2, +}; -int main(int argc, char **argv) -{ - Object *app = NULL; - Object *win = NULL; - Object *html = NULL; - int result = 0; - BPTR fh = NULL; +/* Same HTML as SimpleTest so the two programs cover identical capability + surface -- the difference being when the HTMLview window materialises + (dynamic load vs. up-front creation). */ +static const char *test_html = + "" + "

HTMLView MCC Test Suite

" + "

This is the dynamic-load variant. The MCC is opened only when you " + "click the button, exercising the MUI class-path lookup.

" - DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 0); - if (!DOSBase) return 20; + "

Images - Local Files

" + "

Local image (PROGDIR:test.png):

" + "

\"Local

" + "

Sized: \"Sized\"

" + "

\"Centered\"

" - fh = Open("t:libload_test.txt", MODE_NEWFILE); + "

Images - Network (HTTP)

" + "

Network images require bsdsocket.library:

" + "

\"Aminet

" + "

\"AmigaWorld

" - LogMsg(fh, "LibLoad_Test: Starting..."); + "

Text

" + "

Bold, italic, under, strike, tt

" + "

Visit Aminet or " + "OS4Depot.

" - IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 39); - if (!IntuitionBase) - { - LogMsg(fh, "Failed intuition.library"); - if (fh) Close(fh); - ShowError("Failed to open intuition.library"); - return 20; - } - LogMsg(fh, "Opened intuition.library OK"); + "

Headings

" + "

H1

H2

H3

H4

H5
H6
" + + "

Lists

" + "
  • First
  • Second with bold
" + "
  1. Step one
  2. Step two
" + "
MCC
MUI Custom Class
" + + "

Tables

" + "" + "" + "" + "" + "
ABC
123
4boldlink
" + "

Forms

" + "
" + "

Text:

" + "

a " + " b

" + "

yes " + " no

" + "" + "" + "

" + "
" + + "

Preformatted

" + "
int main() {\n    return 0;\n}
" + + "

Colors & Entities

" + "

Red, " + "Green, " + "Blue

" + "

© ® ™ < > &

" + + "

End of Test Suite

" + ""; + +int main(int argc, char **argv) +{ + Object *app = NULL, *mainWin = NULL, *htmlWin = NULL, *htmlObj = NULL, *btnOpen = NULL; + struct Hook loadHook; + ULONG signals = 0; + (void)argc; + (void)argv; + + SocketBase = OpenLibrary("bsdsocket.library", 4); +#if defined(__amigaos4__) + if (SocketBase) + ISocket = (struct SocketIFace *)GetInterface(SocketBase, "main", 1, NULL); +#endif + /* Socket library is optional -- we still support local files without it. */ + +#if defined(__amigaos4__) + IntuitionBase = OpenLibrary("intuition.library", 39); + if (IntuitionBase) + IIntuition = (struct IntuitionIFace *)GetInterface(IntuitionBase, "main", 1, NULL); + MUIMasterBase = OpenLibrary("muimaster.library", 19); + if (MUIMasterBase) + IMUIMaster = (struct MUIMasterIFace *)GetInterface(MUIMasterBase, "main", 1, NULL); + if (!IntuitionBase || !MUIMasterBase || !IIntuition || !IMUIMaster) +#else + IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 39); MUIMasterBase = OpenLibrary("muimaster.library", 19); - if (!MUIMasterBase) + if (!IntuitionBase || !MUIMasterBase) +#endif { - LogMsg(fh, "Failed muimaster.library"); - if (fh) Close(fh); - ShowError("Failed to open muimaster.library"); - goto cleanup_intuition; + goto cleanup; } - LogMsg(fh, "Opened muimaster.library OK"); - LogMsg(fh, "MUI will find HTMLview.mcc via its class path (MUI:Libs/mui)"); - LogMsg(fh, "Creating MUI application..."); + /* CallHookPkt on m68k and MorphOS passes args in registers (a0=hook, + a2=obj, a1=msg); our plain-C TestImageHookFunc expects stack args. + HookEntry is the amiga.lib trampoline that does the register->stack + conversion. On OS4 hooks are already called with stack args, and + HookEntry is not declared, so we assign directly. */ +#if defined(__amigaos4__) + loadHook.h_Entry = (HOOKFUNC)TestImageHookFunc; + loadHook.h_SubEntry = NULL; +#else + loadHook.h_Entry = (HOOKFUNC)HookEntry; + loadHook.h_SubEntry = (HOOKFUNC)TestImageHookFunc; +#endif + loadHook.h_Data = NULL; + app = MUI_NewObject(MUIC_Application, - MUIA_Application_Title, (ULONG)"LibLoad Test", - MUIA_Application_Version, (ULONG)"$VER: LibLoad_Test 1.0", + MUIA_Application_Title, (ULONG)"LibLoad Test", + MUIA_Application_Version, (ULONG)"$VER: LibLoad_Test 1.1 (18.4.2026)", MUIA_Application_SingleTask, TRUE, - MUIA_Application_Window, win = MUI_NewObject(MUIC_Window, - MUIA_Window_Title, (ULONG)"HTMLView Library Load Test", - MUIA_Window_RootObject, html = MUI_NewObject("HTMLview.mcc", - MUIA_Background, MUII_TextBack, + MUIA_Application_Window, mainWin = MUI_NewObject(MUIC_Window, + MUIA_Window_Title, (ULONG)"HTMLView Library Load Test", + MUIA_Window_Width, 400, + MUIA_Window_Height, 200, + MUIA_Window_RootObject, MUI_NewObject(MUIC_Group, + MUIA_Group_Child, MUI_NewObject(MUIC_Text, + MUIA_Text_Contents, (ULONG) + "\nLibLoad Test Program\n\n" + "Demonstrates loading HTMLview.mcc dynamically.\n" + "Click the button to open the test document.\n", + MUIA_Text_PreParse, (ULONG)"\33c", + TAG_DONE), + MUIA_Group_Child, btnOpen = MUI_NewObject(MUIC_Text, + MUIA_Frame, MUIV_Frame_Button, + MUIA_Text_Contents, (ULONG)"[ Open HTMLView Test ]", + MUIA_Text_PreParse, (ULONG)"\33c", + MUIA_InputMode, MUIV_InputMode_RelVerify, + TAG_DONE), TAG_DONE), TAG_DONE), TAG_DONE); - if (!app) - { - LogMsg(fh, "FAILED to create Application!"); - LogMsg(fh, "MUI could not find HTMLview.mcc in its class path."); - LogMsg(fh, "Make sure HTMLview.mcc is in MUI:Libs/mui/"); - if (fh) Close(fh); - ShowError("LibLoad_Test: Failed!\nMUI cannot find HTMLview.mcc.\nMake sure it is in MUI:Libs/mui/"); - result = 20; - goto cleanup_muimaster; - } + if (!app) goto cleanup; - LogMsg(fh, "Application created OK - MUI found HTMLview.mcc!"); + DoMethod(btnOpen, MUIM_Notify, MUIA_Selected, MUIV_EveryTime, + app, 2, MUIM_Application_ReturnID, MSG_OPEN_HTMLVIEW); - SetAttrs(html, MUIA_HTMLview_Contents, - (ULONG)"

LibLoad Test

If you see this, HTMLview.mcc loads correctly!

", - TAG_DONE); + DoMethod(mainWin, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, + app, 2, MUIM_Application_ReturnID, MSG_QUIT); - LogMsg(fh, "Opening window..."); - SetAttrs(win, MUIA_Window_Open, TRUE, TAG_DONE); + SetAttrs(mainWin, MUIA_Window_Open, TRUE, TAG_DONE); - ULONG open = 0; - GetAttr(MUIA_Window_Open, win, &open); - if (open) - { - LogMsg(fh, "TEST PASSED!"); - if (fh) Close(fh); - ShowError("LibLoad_Test: TEST PASSED!\nHTMLview.mcc loaded successfully!"); - } - else + BOOL running = TRUE; + while (running) { - LogMsg(fh, "Window failed to open"); - if (fh) Close(fh); - ShowError("Window failed to open."); + ULONG id = DoMethod(app, MUIM_Application_NewInput, &signals); + + if (id == MSG_OPEN_HTMLVIEW && !htmlWin) + { + /* Create the MCC with only the hooks -- the image decoder needs + a screen, which is only known once the window is open. Setting + Contents at creation time queues image decode with no screen + and the decoded bitmaps end up unused. We mirror SimpleTest: + open the window first, THEN push Contents via SetAttrs. */ + htmlObj = MUI_NewObject("HTMLview.mcc", + MUIA_HTMLview_ImageLoadHook, (ULONG)&loadHook, + MUIA_HTMLview_LoadHook, (ULONG)&loadHook, + TAG_DONE); + + if (htmlObj) + { + htmlWin = MUI_NewObject(MUIC_Window, + MUIA_Window_Title, (ULONG)"HTMLView Test Content", + MUIA_Window_Width, 800, + MUIA_Window_Height, 600, + MUIA_Window_RootObject, MUI_NewObject(MUIC_Scrollgroup, + MUIA_Scrollgroup_FreeVert, TRUE, + MUIA_Scrollgroup_FreeHoriz, TRUE, + MUIA_Scrollgroup_Contents, htmlObj, + TAG_DONE), + TAG_DONE); + + if (htmlWin) + { + DoMethod(htmlWin, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, + app, 2, MUIM_Application_ReturnID, MSG_QUIT); + DoMethod(app, OM_ADDMEMBER, htmlWin); + SetAttrs(htmlWin, MUIA_Window_Open, TRUE, TAG_DONE); + + ULONG open = 0; + GetAttr(MUIA_Window_Open, htmlWin, &open); + if (open) + SetAttrs(htmlObj, MUIA_HTMLview_Contents, (ULONG)test_html, TAG_DONE); + } + } + } + else if (id == MSG_QUIT) + { + running = FALSE; + } + + if (running && signals) + { + ULONG got = Wait(signals | SIGBREAKF_CTRL_C); + if (got & SIGBREAKF_CTRL_C) running = FALSE; + } } - Delay(50); - SetAttrs(win, MUIA_Window_Open, FALSE, TAG_DONE); + if (htmlWin) SetAttrs(htmlWin, MUIA_Window_Open, FALSE, TAG_DONE); + if (mainWin) SetAttrs(mainWin, MUIA_Window_Open, FALSE, TAG_DONE); -cleanup_htmlview: +cleanup: if (app) MUI_DisposeObject(app); -cleanup_muimaster: +#if defined(__amigaos4__) + if (MUIMasterBase) + { + if (IMUIMaster) DropInterface((struct Interface *)IMUIMaster); + CloseLibrary(MUIMasterBase); + } + if (IntuitionBase) + { + if (IIntuition) DropInterface((struct Interface *)IIntuition); + CloseLibrary(IntuitionBase); + } + if (SocketBase) + { + if (ISocket) DropInterface((struct Interface *)ISocket); + CloseLibrary(SocketBase); + } +#else if (MUIMasterBase) CloseLibrary(MUIMasterBase); - -cleanup_intuition: if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase); - if (DOSBase) CloseLibrary((struct Library *)DOSBase); + if (SocketBase) CloseLibrary(SocketBase); +#endif - return result; + return 0; } diff --git a/mcc/Makefile b/mcc/Makefile index c05afca..f1ed190 100644 --- a/mcc/Makefile +++ b/mcc/Makefile @@ -171,7 +171,7 @@ ifeq ($(OS), os4) CFLAGS += -mcrt=$(CRT) -D__USE_INLINE__ -D__NEW_TIMEVAL_DEFINITION_USED__ \ -D__USE_CLASSIC_MINTERM__ -Wa,-mregnames -DNO_PPCINLINE_STDARG \ -fno-exceptions -fno-threadsafe-statics - LDFLAGS += -mcrt=$(CRT) -nostartfiles + LDFLAGS += -mcrt=$(CRT) -nostartfiles -Wl,-u,_startu LDLIBS += -lauto # additional object files required @@ -420,7 +420,7 @@ CATALOGS = $(LOCALE)/deutsch.catalog # # Build test programs for all platforms -all: $(BINDIR) $(OBJDIR) $(MCCTARGET) $(SIMPLETARGET) $(LIBLOADTARGET) +all: $(BINDIR) $(OBJDIR) $(MCCTARGET) $(SIMPLETARGET) $(LIBLOADTARGET) $(BINDIR)/test.png # make the object directories $(OBJDIR): @@ -477,6 +477,11 @@ $(MCCTARGET) : $(MCCOBJS) @$(CXX) $(LDFLAGS) -o $@.debug $(MCCOBJS) $(LDLIBS) -Wl,-Map,$@.map @$(STRIP) --preserve-dates -R.comment -R.sdata2 -S -o $@ $@.debug +# Copy the sample PNG used by the two test programs into the binary dir. +$(BINDIR)/test.png: testdata/test.png | $(BINDIR) + @echo " CP $@" + @cp testdata/test.png $@ + $(OBJDIR)/library.o: library.c ../include/mccinit.c \ HTMLview_mcc.h private.h rev.h diff --git a/mcc/SimpleTest.c b/mcc/SimpleTest.c index a7227be..ce4753c 100644 --- a/mcc/SimpleTest.c +++ b/mcc/SimpleTest.c @@ -1,114 +1,250 @@ - -#include -#include -#include -#include -#include -#include - -#if defined(__amigaos4__) -#define kprintf(...) ((void)0) -#else -extern void kprintf(const char *fmt, ...); -#endif - -struct Library *MUIMasterBase; -#if defined(__amigaos4__) -struct Library *IntuitionBase; -#else -struct IntuitionBase *IntuitionBase; -#endif -struct Library *UtilityBase; - -// Simple MUI macros if missing -#ifndef MUI_Set -#define MUI_Set(o,a,v) SetAttrs(o,a,v,TAG_DONE) -#endif -#ifndef MUI_GetVal -#define MUI_GetVal(o,a) ({ ULONG v; GetAttr(a,o,&v); v; }) -#endif - -int main(void) -{ - Object *app, *win, *html; - - IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 39); - MUIMasterBase = OpenLibrary("muimaster.library", 19); - - if (!IntuitionBase || !MUIMasterBase) - { - kprintf("SimpleTest: Failed to open libraries\n"); - return 20; - } - - app = MUI_NewObject(MUIC_Application, - MUIA_Application_Title , "HTMLView Simple Test", - MUIA_Application_Version , "$VER: SimpleTest 1.0 (15.12.2025)", - MUIA_Application_SingleTask , TRUE, - MUIA_Application_Window , win = MUI_NewObject(MUIC_Window, - MUIA_Window_Title, "HTMLView Test Window", - MUIA_Window_RootObject, html = MUI_NewObject("HTMLview.mcc", - MUIA_Background, MUII_TextBack, - TAG_DONE), - TAG_DONE), - TAG_DONE); - - if (!app) - { - kprintf("SimpleTest: Failed to create Application object\n"); - } - else - { - SetAttrs(win, MUIA_Window_Open, TRUE, TAG_DONE); - - static const char *test_html = - "" - "

HTMLView Test

" - "

This is a bold paragraph.

" - "

This is an italic paragraph.

" - "

This is a link.

" - ""; - - // Correct ID for HTMLview contents - #define MUIA_HTMLview_Contents 0xad003005 - SetAttrs(html, MUIA_HTMLview_Contents, test_html, TAG_DONE); - - ULONG val = 0; - GetAttr(MUIA_Window_Open, win, &val); - if (val) - { - ULONG sigs = 0; - BOOL running = TRUE; - - DoMethod(win, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, - app, 2, MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit); - - while(running) - { - ULONG id = DoMethod(app, MUIM_Application_NewInput, &sigs); - - if (id == MUIV_Application_ReturnID_Quit) { - running = FALSE; - } - - if (running && sigs) { - ULONG got = Wait(sigs | SIGBREAKF_CTRL_C); - if (got & SIGBREAKF_CTRL_C) { - running = FALSE; - } - } - } - } - else - { - kprintf("SimpleTest: Window failed to open.\n"); - } - - MUI_DisposeObject(app); - } - - if (MUIMasterBase) CloseLibrary(MUIMasterBase); - if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase); - - return 0; -} + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__MORPHOS__) +#include +#elif defined(__amigaos4__) +#include +#endif + +#if defined(__amigaos4__) +struct Library *IntuitionBase; +struct Library *MUIMasterBase; +struct Interface *IntuitionIFace; +struct MUIMasterIFace *IMUIMaster; +struct Library *SocketBase; +struct SocketIFace *ISocket; +#else +struct Library *MUIMasterBase; +struct IntuitionBase *IntuitionBase; +struct Library *SocketBase; +#endif +struct Library *UtilityBase; + +#if defined(DEBUG) +extern void kprintf(const char *fmt, ...); +#else +static void kprintf(const char *fmt, ...) { (void)fmt; } +#endif + +#include "test_image_hook.h" + +static struct Hook ImageLoadHook; + +/* Keep this HTML blob self-contained; PROGDIR:test.png is copied into the + binary directory by the Makefile, and the HTTP entries exercise the + bsdsocket/chunked path in the shared hook. */ +static const char *test_html = + "" + "

HTMLView MCC Test Suite

" + "

This is a comprehensive test demonstrating HTMLview features.

" + + "

Images - Local Files

" + "

Local image (PROGDIR:test.png):

" + "

\"Local

" + "

Same image sized to 64x64:

" + "

\"Sized

" + "

Centered with border:

" + "

\"Centered\"

" + + "

Images - Network (HTTP)

" + "

These need bsdsocket.library and a working network stack:

" + "

\"Aminet

" + "

\"AmigaWorld

" + + "

Text Formatting

" + "

Bold text, italic text, " + "underlined text, strikethrough text, " + "monospace text

" + "

Combination: Bold Italic Underlined

" + + "

Links

" + "

Visit Aminet or " + "OS4Depot.

" + "

Links can have bold text inside like this.

" + + "

Headings

" + "

H1

H2

H3

" + "

H4

H5
H6
" + + "

Lists

" + "
  • First
  • Second
  • Third with bold
" + "
  1. Step one
  2. Step two
  3. Step three
" + "
" + "
HTML
HyperText Markup Language
" + "
CSS
Cascading Style Sheets
" + "
" + + "

Tables

" + "" + "" + "" + "" + "
Column 1Column 2Column 3
Row 1Cell 2Cell 3
Row 2boldlink
" + + "

Forms

" + "
" + "

Text:

" + "

Pass:

" + "

Option A " + " Option B

" + "

Yes " + " No

" + "" + "" + "

" + "

" + "
" + + "

Preformatted

" + "

Inline: printf(\"hi\");

" + "
function example() {\n    return 42;\n}
" + "

Line 1
Line 2
Line 3

" + + "

Font Styles

" + "

Big, small, " + "strong, em

" + "

Red, " + "Green, " + "Blue

" + + "

Entities

" + "

© ® ™ < > & ← →

" + + "

End of Test Suite

" + ""; + +int main(void) +{ + Object *app, *win, *html; + +#if defined(__amigaos4__) + IntuitionBase = OpenLibrary("intuition.library", 39); + if (IntuitionBase) + IntuitionIFace = GetInterface(IntuitionBase, "main", 1, NULL); + MUIMasterBase = OpenLibrary("muimaster.library", 19); + if (MUIMasterBase) + IMUIMaster = (struct MUIMasterIFace *)GetInterface(MUIMasterBase, "main", 1, NULL); + if (!IntuitionBase || !MUIMasterBase || !IntuitionIFace || !IMUIMaster) +#else + IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 39); + MUIMasterBase = OpenLibrary("muimaster.library", 19); + if (!IntuitionBase || !MUIMasterBase) +#endif + { + kprintf("SimpleTest: Failed to open libraries\n"); + return 20; + } + + SocketBase = OpenLibrary("bsdsocket.library", 4); +#if defined(__amigaos4__) + if (SocketBase) + ISocket = (struct SocketIFace *)GetInterface(SocketBase, "main", 1, NULL); +#endif + + /* CallHookPkt on m68k and MorphOS passes args in registers (a0=hook, + a2=obj, a1=msg); our plain-C TestImageHookFunc expects stack args. + HookEntry is the amiga.lib trampoline that does the register->stack + conversion. On OS4 hooks are already called with stack args, and + HookEntry is not declared, so we assign directly. */ +#if defined(__amigaos4__) + ImageLoadHook.h_Entry = (HOOKFUNC)TestImageHookFunc; + ImageLoadHook.h_SubEntry = NULL; +#else + ImageLoadHook.h_Entry = (HOOKFUNC)HookEntry; + ImageLoadHook.h_SubEntry = (HOOKFUNC)TestImageHookFunc; +#endif + ImageLoadHook.h_Data = NULL; + + app = MUI_NewObject(MUIC_Application, + MUIA_Application_Title , "HTMLView Simple Test", + MUIA_Application_Version , (ULONG)"$VER: SimpleTest 1.2 (18.4.2026)", + MUIA_Application_SingleTask , TRUE, + MUIA_Application_Window , win = MUI_NewObject(MUIC_Window, + MUIA_Window_Title, (ULONG)"HTMLView Test Window", + MUIA_Window_Width, 640, + MUIA_Window_Height, 480, + MUIA_Window_RootObject, MUI_NewObject(MUIC_Scrollgroup, + MUIA_Scrollgroup_FreeVert, TRUE, + MUIA_Scrollgroup_FreeHoriz, TRUE, + MUIA_Scrollgroup_Contents, html = MUI_NewObject("HTMLview.mcc", + MUIA_Background, MUII_TextBack, + MUIA_HTMLview_ImageLoadHook, (ULONG)&ImageLoadHook, + MUIA_HTMLview_LoadHook, (ULONG)&ImageLoadHook, + TAG_DONE), + TAG_DONE), + TAG_DONE), + TAG_DONE); + + if (!app) + { + kprintf("SimpleTest: Failed to create Application object\n"); + } + else + { + SetAttrs(win, MUIA_Window_Open, TRUE, TAG_DONE); + + SetAttrs(html, MUIA_HTMLview_Contents, (ULONG)test_html, TAG_DONE); + + ULONG open = 0; + GetAttr(MUIA_Window_Open, win, &open); + if (open) + { + DoMethod(win, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, + app, 2, MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit); + + ULONG sigs = 0; + BOOL running = TRUE; + while (running) + { + ULONG id = DoMethod(app, MUIM_Application_NewInput, &sigs); + if (id == MUIV_Application_ReturnID_Quit) running = FALSE; + + if (running && sigs) + { + ULONG got = Wait(sigs | SIGBREAKF_CTRL_C); + if (got & SIGBREAKF_CTRL_C) running = FALSE; + } + } + } + else + { + kprintf("SimpleTest: Window failed to open.\n"); + } + + MUI_DisposeObject(app); + } + + if (SocketBase) + { +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + } + if (MUIMasterBase) + { +#if defined(__amigaos4__) + if (IMUIMaster) DropInterface((struct Interface *)IMUIMaster); +#endif + CloseLibrary(MUIMasterBase); + } + if (IntuitionBase) + { +#if defined(__amigaos4__) + if (IntuitionIFace) DropInterface(IntuitionIFace); + CloseLibrary(IntuitionBase); +#else + CloseLibrary((struct Library *)IntuitionBase); +#endif + } + + return 0; +} diff --git a/mcc/test_image_hook.h b/mcc/test_image_hook.h new file mode 100644 index 0000000..52e1cfb --- /dev/null +++ b/mcc/test_image_hook.h @@ -0,0 +1,577 @@ +/* + * test_image_hook.h -- Shared image-load hook used by SimpleTest and + * LibLoad_Test to feed picture data to HTMLview.mcc. + * + * The hook resolves three kinds of URLs: + * - PROGDIR:, DH0:, etc. (direct dos Open) + * - file:// (stripped and dos Open) + * - http://host[:port]/p (bsdsocket.library GET, chunked aware) + * + * https:// URLs return failure (no TLS in the stock Amiga bsdsocket). + * + * The header is intentionally single-include, single-translation-unit. + * Include it from exactly one .c file in a test program; the program owns + * the SocketBase / ISocket globals (so it can close them on exit). + * + * Threading notes: the hook runs in the HTMLview decoder thread, not the + * main task. On m68k every task that uses bsdsocket must OpenLibrary the + * library itself -- the library base opened by main() isn't valid for our + * thread. We therefore open a task-local SocketBase in the hook and shadow + * the file-scope globals so the proto/socket.h inline macros dispatch + * through the local base. + */ + +#ifndef HTMLVIEW_TEST_IMAGE_HOOK_H +#define HTMLVIEW_TEST_IMAGE_HOOK_H + +#include +#include +#include +#include +#include +#include +#include +#include "HTMLview_mcc.h" + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +/* The test programs carry their own globals for this; only used by the + main task. The hook uses its own task-local bases instead (see below). */ +#if defined(__amigaos4__) +extern struct Library *SocketBase; +extern struct SocketIFace *ISocket; +#else +extern struct Library *SocketBase; +#endif + +/* Per-transfer state stored in lm_Userdata. */ +struct THL_State +{ + BPTR file; /* file handle if this is a local file */ + LONG socket; /* bsdsocket handle, -1 if none */ + UBYTE *buffer; /* stashed body bytes from the initial HTTP recv */ + ULONG bufpos; + ULONG buflen; + int chunked; /* non-zero => Transfer-Encoding: chunked */ + ULONG chunk_left; + + /* Task-local bsdsocket bases -- opened by the decoder thread so it can + actually call socket/recv/etc.; closed on hook-Close. */ + struct Library *SBase; +#if defined(__amigaos4__) + struct SocketIFace *SIFace; +#endif +}; + +/* --- logging ----------------------------------------------------------- */ + +/* Accumulating-buffer + rewrite pattern (same as ImageManager DTLog) so the + log file stays coherent across OS3 and OS4 without relying on Seek. */ +static char THL_LogBuf[8192]; +static ULONG THL_LogLen = 0; + +static void THL_Log(const char *line) +{ + ULONG need = strlen(line) + 1; + if (THL_LogLen + need + 1 >= sizeof(THL_LogBuf)) return; + memcpy(THL_LogBuf + THL_LogLen, line, need - 1); + THL_LogLen += need - 1; + THL_LogBuf[THL_LogLen++] = '\n'; + THL_LogBuf[THL_LogLen] = 0; + BPTR f = Open((STRPTR)"T:htmlview_hook.log", MODE_NEWFILE); + if (f) { Write(f, THL_LogBuf, THL_LogLen); Close(f); } +} + +/* --- low-level helpers ------------------------------------------------- */ + +static LONG THL_ReadChunked(struct THL_State *st, UBYTE *out, LONG want); + +static LONG THL_RecvLine(struct THL_State *st, char *buf, LONG maxlen) +{ + /* Shadow file-scope bases with our task-local ones for proto/socket.h. */ + struct Library *SocketBase = st->SBase; +#if defined(__amigaos4__) + struct SocketIFace *ISocket = st->SIFace; +#endif + + LONG i = 0; + while (i < maxlen - 1) + { + UBYTE c; + LONG got = recv(st->socket, (APTR)&c, 1, 0); + if (got <= 0) return -1; + if (c == '\n') { buf[i] = 0; return i; } + if (c != '\r') buf[i++] = c; + } + buf[i] = 0; + return i; +} + +/* Reads up to `want` bytes, unwrapping chunked transfer encoding. */ +static LONG THL_ReadChunked(struct THL_State *st, UBYTE *out, LONG want) +{ + struct Library *SocketBase = st->SBase; +#if defined(__amigaos4__) + struct SocketIFace *ISocket = st->SIFace; +#endif + + LONG produced = 0; + while (produced < want) + { + if (st->chunk_left == 0) + { + char line[32]; + if (THL_RecvLine(st, line, sizeof(line)) < 0) + return produced; + if (line[0] == 0) + { + if (THL_RecvLine(st, line, sizeof(line)) < 0) + return produced; + } + ULONG size = strtoul(line, NULL, 16); + if (size == 0) return produced; + st->chunk_left = size; + } + + LONG take = want - produced; + if ((ULONG)take > st->chunk_left) take = (LONG)st->chunk_left; + + LONG got = recv(st->socket, (APTR)(out + produced), take, 0); + if (got <= 0) return produced; + produced += got; + st->chunk_left -= got; + } + return produced; +} + +/* Parses "http://host[:port]/path" into components (buffers owned by caller). */ +static int THL_ParseHttp(CONST_STRPTR url, char *host, ULONG hlen, + char *path, ULONG plen, ULONG *portp) +{ + const char *p = url; + if (strncmp(p, "http://", 7) != 0) return 0; + p += 7; + + const char *hs = p; + while (*p && *p != '/' && *p != ':') p++; + + ULONG n = (ULONG)(p - hs); + if (n == 0 || n >= hlen) return 0; + memcpy(host, hs, n); + host[n] = 0; + + ULONG port = 80; + if (*p == ':') + { + p++; + port = 0; + while (*p >= '0' && *p <= '9') { port = port * 10 + (*p - '0'); p++; } + if (port == 0 || port > 65535) return 0; + } + *portp = port; + + if (*p == 0) { strncpy(path, "/", plen); path[plen-1] = 0; return 1; } + + strncpy(path, p, plen - 1); + path[plen - 1] = 0; + return 1; +} + +/* Opens an HTTP connection, reads and parses response headers, stashes any + body bytes that came with the header recv. Returns 1 on success. */ +static LONG THL_HttpOpen(CONST_STRPTR url, struct THL_State *st) +{ + char logbuf[320]; + + char host[256], path[1024]; + ULONG port = 80; + if (!THL_ParseHttp(url, host, sizeof(host), path, sizeof(path), &port)) + { + THL_Log("http: parse URL failed"); + return 0; + } + + sprintf(logbuf, "http: host=%s port=%lu path=%s", host, port, path); + THL_Log(logbuf); + + /* Open bsdsocket fresh for THIS task -- the decoder thread. On m68k the + library is opened per-task; the main task's SocketBase is not usable + from us. */ + struct Library *SocketBase = OpenLibrary((STRPTR)"bsdsocket.library", 4); +#if defined(__amigaos4__) + struct SocketIFace *ISocket = SocketBase + ? (struct SocketIFace *)GetInterface(SocketBase, "main", 1, NULL) + : NULL; + if (!SocketBase || !ISocket) + { + if (SocketBase && !ISocket) CloseLibrary(SocketBase); + THL_Log("http: OpenLibrary bsdsocket.library failed"); + return 0; + } +#else + if (!SocketBase) + { + THL_Log("http: OpenLibrary bsdsocket.library failed"); + return 0; + } +#endif + + struct hostent *he; +#if defined(__amigaos4__) + he = (struct hostent *)gethostbyname((STRPTR)host); +#else + he = gethostbyname((STRPTR)host); +#endif + if (!he) + { + sprintf(logbuf, "http: gethostbyname(%s) failed", host); + THL_Log(logbuf); +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + return 0; + } + + LONG s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) + { + THL_Log("http: socket() failed"); +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + return 0; + } + + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons((UWORD)port); + sin.sin_addr.s_addr = *((ULONG *)he->h_addr_list[0]); + + if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) + { + THL_Log("http: connect() failed"); + CloseSocket(s); +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + return 0; + } + + char request[2048]; + int rlen = sprintf(request, + "GET %s HTTP/1.1\r\n" + "Host: %s\r\n" + "User-Agent: HTMLview-TestHook/1.1\r\n" + "Accept: */*\r\n" + "Connection: close\r\n" + "\r\n", + path, host); + + if (send(s, request, rlen, 0) < 0) + { + THL_Log("http: send() failed"); + CloseSocket(s); +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + return 0; + } + + /* Read a moderate chunk so we're very likely to have all headers plus + some body in a single recv. */ + UBYTE *hdr = (UBYTE *)malloc(16384); + if (!hdr) + { + CloseSocket(s); +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + return 0; + } + + LONG have = 0; + char *hdrend = NULL; + while (have < 16384 - 1) + { + LONG got = recv(s, (APTR)(hdr + have), 16384 - 1 - have, 0); + if (got <= 0) break; + have += got; + hdr[have] = 0; + if ((hdrend = strstr((char *)hdr, "\r\n\r\n")) != NULL) break; + if ((hdrend = strstr((char *)hdr, "\n\n")) != NULL) break; + } + + if (!hdrend) + { + THL_Log("http: no header terminator in reply"); + free(hdr); + CloseSocket(s); +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + return 0; + } + + /* hdrend points at the first byte of the header/body separator. + Separator is either "\r\n\r\n" (4 bytes) or "\n\n" (2 bytes). + Compute body offset BEFORE null-terminating. */ + ULONG header_len = (*hdrend == '\r') + ? (ULONG)(hdrend - (char *)hdr) + 4 + : (ULONG)(hdrend - (char *)hdr) + 2; + + /* Null-terminate the header block so strstr on header values is safe. + Writing at hdrend clobbers only the first byte of the separator, + which we no longer need -- NOT the first body byte. */ + *hdrend = 0; + + { + /* Log the HTTP status line so we can spot 301/302 redirects, 404s, + etc. The header block also tells us whether the server returned + HTML instead of an image. */ + char status[120]; + ULONG n = 0; + while (n < sizeof(status) - 1 && n < header_len && + hdr[n] != '\r' && hdr[n] != '\n') + { + status[n] = (char)hdr[n]; + n++; + } + status[n] = 0; + sprintf(logbuf, "http: status=%s", status); + THL_Log(logbuf); + + /* Bail on non-200 responses -- redirects, 404s and 5xx must not + reach the decoder as "image body". Parse the numeric code. */ + int code = 0; + const char *sp = strchr(status, ' '); + if (sp) + { + while (*sp == ' ') sp++; + while (*sp >= '0' && *sp <= '9') { code = code*10 + (*sp - '0'); sp++; } + } + if (code != 200) + { + sprintf(logbuf, "http: aborting (status=%d)", code); + THL_Log(logbuf); + free(hdr); + CloseSocket(s); +#if defined(__amigaos4__) + if (ISocket) DropInterface((struct Interface *)ISocket); +#endif + CloseLibrary(SocketBase); + return 0; + } + + /* Helper: dig out a header value by name, log as "http: =". */ + const char *hdrs[] = { "Location:", "location:", + "Content-Type:", "content-type:", + "Content-Length:", "content-length:", + "Content-Encoding:", "content-encoding:" }; + const char *tags[] = { "location", "location", + "content-type", "content-type", + "content-length", "content-length", + "content-encoding", "content-encoding" }; + for (ULONG i = 0; i < sizeof(hdrs)/sizeof(hdrs[0]); i++) + { + const char *v = strstr((char *)hdr, hdrs[i]); + if (!v) continue; + v += strlen(hdrs[i]); + while (*v == ' ' || *v == '\t') v++; + const char *e = v; + while (*e && *e != '\r' && *e != '\n') e++; + ULONG L = (ULONG)(e - v); + if (L > sizeof(status) - 1) L = sizeof(status) - 1; + memcpy(status, v, L); + status[L] = 0; + sprintf(logbuf, "http: %s=%s", tags[i], status); + THL_Log(logbuf); + } + } + + if (strstr((char *)hdr, "Transfer-Encoding: chunked") || + strstr((char *)hdr, "transfer-encoding: chunked")) + st->chunked = 1; + + ULONG body_have = (ULONG)have - header_len; + if (body_have > 0) + { + st->buffer = (UBYTE *)malloc(body_have); + if (st->buffer) + { + memcpy(st->buffer, hdr + header_len, body_have); + st->buflen = body_have; + st->bufpos = 0; + } + } + + sprintf(logbuf, "http: OK header_len=%lu body_have=%lu chunked=%d", + header_len, body_have, st->chunked); + THL_Log(logbuf); + + /* Log the first 16 body bytes in hex -- lets us see the magic number + of the actual payload so we can tell PNG / GIF / HTML / gzip apart + at a glance. */ + if (body_have > 0) + { + UBYTE *bp = hdr + header_len; + ULONG show = body_have < 16 ? body_have : 16; + char hex[80]; + int off = sprintf(hex, "http: body[0..%lu]=", show); + for (ULONG i = 0; i < show; i++) + off += sprintf(hex + off, "%02x ", bp[i]); + THL_Log(hex); + } + + st->socket = s; + st->SBase = SocketBase; +#if defined(__amigaos4__) + st->SIFace = ISocket; +#endif + free(hdr); + return 1; +} + +/* --- the hook function ------------------------------------------------- */ + +static ULONG TestImageHookFunc(struct Hook *hook, APTR obj, + struct HTMLview_LoadMsg *msg) +{ + (void)hook; + (void)obj; + if (!msg) return 0; + + switch (msg->lm_Type) + { + case HTMLview_Open: + { + CONST_STRPTR url = msg->lm_Params.lm_Open.URL; + if (!url) return 0; + + struct THL_State *st = (struct THL_State *)calloc(1, sizeof(*st)); + if (!st) return 0; + st->socket = -1; + msg->lm_Userdata = st; + + if (strncmp(url, "https://", 8) == 0) + { + THL_Log("hook: https:// unsupported"); + free(st); msg->lm_Userdata = NULL; return 0; + } + + if (strncmp(url, "http://", 7) == 0) + { + if (!THL_HttpOpen(url, st)) + { + free(st); msg->lm_Userdata = NULL; return 0; + } + return 1; + } + + if (strncmp(url, "file://", 7) == 0) url += 7; + + st->file = Open((STRPTR)url, MODE_OLDFILE); + if (st->file) return 1; + + free(st); msg->lm_Userdata = NULL; + return 0; + } + + case HTMLview_Read: + { + struct THL_State *st = (struct THL_State *)msg->lm_Userdata; + if (!st) return 0; + + STRPTR out = msg->lm_Params.lm_Read.Buffer; + LONG len = msg->lm_Params.lm_Read.Size; + if (!out || len <= 0) return 0; + + /* Replay stashed body bytes first. */ + if (st->buffer && st->bufpos < st->buflen) + { + ULONG remain = st->buflen - st->bufpos; + ULONG n = (remain < (ULONG)len) ? remain : (ULONG)len; + memcpy(out, st->buffer + st->bufpos, n); + st->bufpos += n; + if (st->bufpos >= st->buflen) + { + free(st->buffer); + st->buffer = NULL; + } + return n; + } + + if (st->file) + { + LONG rd = Read(st->file, out, len); + return rd > 0 ? rd : 0; + } + + if (st->socket >= 0) + { + struct Library *SocketBase = st->SBase; +#if defined(__amigaos4__) + struct SocketIFace *ISocket = st->SIFace; +#endif + LONG rd = st->chunked + ? THL_ReadChunked(st, (UBYTE *)out, len) + : recv(st->socket, out, len, 0); + { + char logbuf[96]; + sprintf(logbuf, "read: recv(sock=%ld want=%ld) => %ld%s", + st->socket, len, rd, st->chunked ? " (chunked)" : ""); + THL_Log(logbuf); + } + return rd > 0 ? rd : 0; + } + + return 0; + } + + case HTMLview_Write: + return 0; + + case HTMLview_Close: + { + struct THL_State *st = (struct THL_State *)msg->lm_Userdata; + if (st) + { + if (st->file) Close(st->file); + + if (st->socket >= 0 && st->SBase) + { + struct Library *SocketBase = st->SBase; +#if defined(__amigaos4__) + struct SocketIFace *ISocket = st->SIFace; +#endif + CloseSocket(st->socket); + } + +#if defined(__amigaos4__) + if (st->SIFace) + DropInterface((struct Interface *)st->SIFace); +#endif + if (st->SBase) CloseLibrary(st->SBase); + + if (st->buffer) free(st->buffer); + free(st); + msg->lm_Userdata = NULL; + } + return 1; + } + } + return 0; +} + +#endif /* HTMLVIEW_TEST_IMAGE_HOOK_H */ diff --git a/mcc/testdata/test.png b/mcc/testdata/test.png new file mode 100644 index 0000000..5391345 Binary files /dev/null and b/mcc/testdata/test.png differ