joeylib2/examples/wblaunch/wblaunch.c

229 lines
8 KiB
C

// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// Workbench probe (WBLAUNCH, a gate's program, not an example, and the
// Amiga's alone): a program's icon read and drawn by the ROM's icon.library,
// and the program started from it the way Workbench starts a tool.
//
// Wblaunch FOLDER NAME
//
// GetDiskObject reads FOLDER/NAME.info; its type, stack and image are
// logged, and the image is drawn at ICON_X,ICON_Y on a hires screen of
// Workbench's four colours, with the mouse pointer hidden, held HOLD_TICKS
// for a screenshot. Then
// FOLDER/NAME is started as Workbench starts a tool -- LoadSeg, CreateProc
// with the icon's stack, and a WBStartup message whose one argument is the
// folder's lock and the program's name -- from a current directory that is
// not FOLDER (AWAY_FOLDER), and the message coming back says the program
// has ended. Each step is a "WBLAUNCH:" line of LOG_NAME, beside Wblaunch.
// scripts/verify-amiga-icon.sh is the gate.
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <graphics/modeid.h>
#include <intuition/intuition.h>
#include <intuition/screens.h>
#include <workbench/startup.h>
#include <workbench/workbench.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/graphics.h>
#include <proto/icon.h>
#include <proto/intuition.h>
#define LOG_NAME "wblaunch.txt"
#define AWAY_FOLDER "RAM:"
#define PATH_BYTES 256u
#define SCREEN_W 640
#define SCREEN_H STDSCREENHEIGHT
#define SCREEN_DEPTH 2
#define ICON_X 64
#define ICON_Y 64
#define HOLD_TICKS (5 * TICKS_PER_SECOND)
#define GUN_MASK 0xFu
#define RED_SHIFT 8u
#define GREEN_SHIFT 4u
#define ARGS_WANTED 3
// A blank pointer, in chip memory: its two control words, one empty line of
// two words, and the two words that end it.
#define BLANK_POINTER_BYTES (6u * sizeof(UWORD))
#define BLANK_POINTER_H 1
#define BLANK_POINTER_W 16
static bool drawIcon(struct DiskObject *icon);
static bool launch(const char *folder, const char *name, ULONG stack);
static void logLine(const char *format, ...);
// Workbench's four colours (2.0 on), 4 bits a gun: grey, black, white and
// blue (tools/iconBake.py's AMIGA_COLOURS).
static const UWORD gWorkbenchColours[] = { 0xAAA, 0x000, 0xFFF, 0x68B };
static FILE *gLog = NULL;
// The icon's image on a screen of Workbench's colours, held for the gate's
// screenshot. A window over the whole screen, active, hides the pointer.
static bool drawIcon(struct DiskObject *icon) {
struct Screen *screen;
struct Window *window;
UWORD *blank;
UWORD i;
bool drawn;
drawn = false;
blank = (UWORD *)AllocMem(BLANK_POINTER_BYTES, MEMF_CHIP | MEMF_CLEAR);
screen = OpenScreenTags(NULL, SA_Width, SCREEN_W, SA_Height, SCREEN_H, SA_Depth, SCREEN_DEPTH, SA_DisplayID, HIRES_KEY, SA_Quiet, TRUE, SA_ShowTitle, FALSE, TAG_DONE);
window = NULL;
if (screen != NULL) {
window = OpenWindowTags(NULL, WA_CustomScreen, (ULONG)screen, WA_Backdrop, TRUE, WA_Borderless, TRUE, WA_Activate, TRUE, WA_RMBTrap, TRUE, TAG_DONE);
}
if (blank == NULL || window == NULL) {
logLine("FAIL cannot open a screen and a window");
} else {
SetPointer(window, blank, BLANK_POINTER_H, BLANK_POINTER_W, 0, 0);
for (i = 0; i < sizeof(gWorkbenchColours) / sizeof(gWorkbenchColours[0]); i++) {
SetRGB4(&screen->ViewPort, i, (gWorkbenchColours[i] >> RED_SHIFT) & GUN_MASK, (gWorkbenchColours[i] >> GREEN_SHIFT) & GUN_MASK, gWorkbenchColours[i] & GUN_MASK);
}
SetRast(window->RPort, 0);
DrawImage(window->RPort, (struct Image *)icon->do_Gadget.GadgetRender, ICON_X, ICON_Y);
logLine("drawn at %d,%d", ICON_X, ICON_Y);
Delay(HOLD_TICKS);
ClearPointer(window);
drawn = true;
}
if (window != NULL) {
CloseWindow(window);
}
if (screen != NULL) {
CloseScreen(screen);
}
if (blank != NULL) {
FreeMem(blank, BLANK_POINTER_BYTES);
}
return drawn;
}
// FOLDER/NAME started as Workbench starts a tool, from AWAY_FOLDER; true
// once its message has come back.
static bool launch(const char *folder, const char *name, ULONG stack) {
struct WBStartup startup;
struct WBArg arg;
struct MsgPort *reply;
struct MsgPort *process;
char path[PATH_BYTES];
BPTR lock;
BPTR away;
BPTR before;
BPTR segment;
strncpy(path, folder, sizeof(path) - 1u);
path[sizeof(path) - 1u] = '\0';
if (!AddPart((STRPTR)path, (STRPTR)name, sizeof(path))) {
logLine("FAIL the path is too long");
return false;
}
lock = Lock((STRPTR)folder, SHARED_LOCK);
if (lock == 0) {
logLine("FAIL cannot lock %s", folder);
return false;
}
segment = LoadSeg((STRPTR)path);
if (segment == 0) {
logLine("FAIL cannot load %s", path);
UnLock(lock);
return false;
}
reply = CreateMsgPort();
away = Lock((STRPTR)AWAY_FOLDER, SHARED_LOCK);
if (reply == NULL || away == 0) {
logLine("FAIL no reply port or no %s", AWAY_FOLDER);
UnLoadSeg(segment);
UnLock(lock);
return false;
}
before = CurrentDir(away);
process = CreateProc((STRPTR)name, 0, segment, (LONG)stack);
if (process == NULL) {
logLine("FAIL cannot start %s", path);
} else {
memset(&startup, 0, sizeof(startup));
startup.sm_Message.mn_Node.ln_Type = NT_MESSAGE;
startup.sm_Message.mn_ReplyPort = reply;
startup.sm_Message.mn_Length = sizeof(startup);
startup.sm_Process = process;
startup.sm_Segment = segment;
startup.sm_NumArgs = 1;
startup.sm_ArgList = &arg;
arg.wa_Lock = lock;
arg.wa_Name = (STRPTR)name;
logLine("started %s with %lu bytes of stack from %s", path, (unsigned long)stack, AWAY_FOLDER);
PutMsg(process, &startup.sm_Message);
WaitPort(reply);
GetMsg(reply);
}
CurrentDir(before);
UnLock(away);
DeleteMsgPort(reply);
UnLoadSeg(segment);
UnLock(lock);
return process != NULL;
}
static void logLine(const char *format, ...) {
va_list args;
fputs("WBLAUNCH: ", gLog);
va_start(args, format);
vfprintf(gLog, format, args);
va_end(args);
fputc('\n', gLog);
fflush(gLog);
}
int main(int argc, char **argv) {
struct DiskObject *icon;
struct Image *image;
char path[PATH_BYTES];
int status;
gLog = fopen(LOG_NAME, "w");
if (gLog == NULL) {
return RETURN_FAIL;
}
status = RETURN_FAIL;
if (argc != ARGS_WANTED) {
logLine("FAIL usage: Wblaunch FOLDER NAME");
} else if (IconBase == NULL) {
logLine("FAIL no icon.library");
} else {
strncpy(path, argv[1], sizeof(path) - 1u);
path[sizeof(path) - 1u] = '\0';
AddPart((STRPTR)path, (STRPTR)argv[2], sizeof(path));
icon = GetDiskObject((STRPTR)path);
if (icon == NULL) {
logLine("FAIL icon.library cannot read %s.info", path);
} else {
image = (struct Image *)icon->do_Gadget.GadgetRender;
logLine("icon type=%u stack=%ld gadget=%dx%d image=%dx%dx%d", (unsigned)icon->do_Type, (long)icon->do_StackSize, icon->do_Gadget.Width, icon->do_Gadget.Height, image->Width, image->Height, image->Depth);
if (drawIcon(icon) && launch(argv[1], argv[2], (ULONG)icon->do_StackSize)) {
logLine("returned");
status = RETURN_OK;
}
FreeDiskObject(icon);
}
}
fclose(gLog);
return status;
}