LOL
This commit is contained in:
parent
cf3d4046a3
commit
6b4aabb6e3
|
@ -0,0 +1,3 @@
|
|||
node_modules/
|
||||
dialog.r
|
||||
build/
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Linux",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"../R68/toolchain/m68k-apple-macos/include"
|
||||
],
|
||||
"defines": [],
|
||||
"compilerPath": "/usr/bin/clang",
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++17",
|
||||
"intelliSenseMode": "clang-x64"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"files.associations": {
|
||||
"quickdraw.h": "c",
|
||||
"cstring": "cpp",
|
||||
"cstdio": "c"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
|
||||
add_application(Dialog
|
||||
main.c
|
||||
dialog.c
|
||||
dialog.r
|
||||
dukbridge.c
|
||||
duktape.c
|
||||
)
|
||||
|
||||
target_link_libraries(Dialog -lm)
|
||||
|
||||
# Enable -ffunction-sections and -gc-sections to make the app as small as possible
|
||||
# On 68K, also enable --mac-single to build it as a single-segment app (so that this code path doesn't rot)
|
||||
set_target_properties(Dialog PROPERTIES COMPILE_OPTIONS -ffunction-sections)
|
||||
if(CMAKE_SYSTEM_NAME MATCHES Retro68)
|
||||
set_target_properties(Dialog PROPERTIES LINK_FLAGS "-Wl,-gc-sections -Wl,--mac-single")
|
||||
|
||||
else()
|
||||
set_target_properties(Dialog PROPERTIES LINK_FLAGS "-Wl,-gc-sections")
|
||||
endif()
|
|
@ -0,0 +1,8 @@
|
|||
all:
|
||||
node makeResource.js > dialog.r
|
||||
rm -Rf build
|
||||
mkdir build
|
||||
cd build && cmake .. -DCMAKE_TOOLCHAIN_FILE=~/github/R68/toolchain/m68k-apple-macos/cmake/retro68.toolchain.cmake
|
||||
cd build && make
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
Macintosh fake-react native
|
||||
===
|
||||
|
||||
This is a porting of my afternoon experiment to the Macintosh platform
|
||||
|
||||
It's based on the work I've done for Windows 3.11 for [this twitter thread](twitter.com/kentaromiura/status/1216742960408055809)
|
||||
|
||||
|
||||
Requirements:
|
||||
===
|
||||
You need a build of https://github.com/autc04/Retro68, that includes the Apple Universal Interfaces,
|
||||
to get those download the image from one of the links, open it in Basilisk and then copy those file back to windows: I used HVFExplorer and I copied them w/out the resource partition, seems to work.
|
||||
About basilisk, I'm using the windows build, in order to enable the sharing between windows and basilisk I had to add `ignoresegv true` to the preference file as it keeps crashing otherwise in Windows 10.
|
||||
|
||||
To simplify this step you can find a pce/macplus image [linked in this article](http://www.toughdev.com/content/2018/12/developing-68k-mac-apps-with-codelite-ide-retro68-and-pce-macplus-emulator/)
|
||||
I personally did not try it but should work fine, but probably you'll need to pull the latest changes from git (recursively).
|
||||
|
||||
I used WSL2, you can get this to work fine with WSL1 probably as long as you use a windows shared folder mounted on your linux for using the `launchAPPL` utility, otherwise you'll have to use HVFExplorer to copy the executable to the mac image.
|
||||
If you use the launchAPPL I suggest to make an alias like
|
||||
```
|
||||
alias launchAPPL='/path/to/Retro68/build/build-host/LaunchAPPL/Client/LaunchAPPL -e shared --shared-directory /mnt/c/share'
|
||||
```
|
||||
If you use other emulators such as minivmac2 you can probably use the network share, it doesn't work on Basilisk though.
|
||||
|
||||
Ensure the node dependencies are installed by running yarn.
|
||||
I wrote a script to create and embed the sources as mac resources inside the executable resource partition,
|
||||
node wasn't strictly necessary for this, but I still needed babel to convert the JSX to ES3 that can be run in duktape, so that's why it's a nodejs file.
|
||||
|
||||
In the Makefile you'll need to edit the path to your Retro68 build, just change the value of `CMAKE_TOOLCHAIN_FILE` to the appropriate path.
|
||||
|
||||
How does this work?
|
||||
===
|
||||
|
||||
The `main.c` is just a simple barebone program that initialize everything needed by the mac to start up an application, then it loads [duktape](https://duktape.org/) a real stack based JS engine that can run the code that babel outputs; the dukbridge file creates 2 duktape native functions that will we use to bridge the JS world to the native world.
|
||||
|
||||
First though we load the _app/fakeReact.js_ that creates some globals that we need, `React` with its 2 functions `createElement` and `mount`, plus a `MessageBox` and a `Dialog` _native_ components (you might notice that will call some weird `React.duktape.` methods, explained later), and two implementation specific things, an empty object property named `duktape`, under the `React` global to avoid too much pollution, and a `native` property that simply maps all the native components, this is used by `React.mount` to know if it needs to call the _native_ function or continue rendering; a little note here is that I didn't implement a fully descendant reconciler, it only supports 2 levels, the one needed for the demo, sorry!
|
||||
|
||||
Anyway, at this point the `populateCtx` in _dukbridge.c_ runs and connects the JS world to the native world by adding the `showDialog` and `showMessageBox` native functions to `React.duktape`.
|
||||
|
||||
At this point the main setup is done, so the _app/index.js_ is loaded first and the `main` function is mounted.
|
|
@ -0,0 +1,48 @@
|
|||
(function(global){
|
||||
global.Dialog = function(descriptor){
|
||||
var message = descriptor.children;
|
||||
var title = descriptor.properties.title;
|
||||
var onYes = descriptor.properties.onYes;
|
||||
var onNo = descriptor.properties.onNo;
|
||||
global.React.duktape.showDialog(title, message, onYes, onNo);
|
||||
};
|
||||
global.MessageBox = function(descriptor) {
|
||||
var message = descriptor.children;
|
||||
global.React.duktape.showMessageBox(message);
|
||||
return 0;
|
||||
};
|
||||
global.React = {
|
||||
native: {
|
||||
'Dialog': Dialog,
|
||||
'MessageBox': MessageBox
|
||||
},
|
||||
duktape: {},
|
||||
createElement: function (component, properties, children) {
|
||||
return {
|
||||
component: component,
|
||||
properties: properties,
|
||||
children: children
|
||||
}
|
||||
},
|
||||
mount: function(root) {
|
||||
var handled;
|
||||
Object.keys(global.React.native).forEach(function (component) {
|
||||
if(global.React.native[component] == root.component) {
|
||||
handled = function workaroundForBug() {root.component(root)};
|
||||
}
|
||||
});
|
||||
if (handled) handled();
|
||||
if (!handled) {
|
||||
var newRoot = root.component(
|
||||
Object.assign(
|
||||
{
|
||||
children: root.children
|
||||
},
|
||||
root.properties
|
||||
)
|
||||
);
|
||||
newRoot.component(newRoot);
|
||||
}
|
||||
}
|
||||
};
|
||||
})(new Function('return this')());
|
|
@ -0,0 +1,29 @@
|
|||
let showMessageBox = message => {
|
||||
React.mount(<MessageBox>{message}</MessageBox>);
|
||||
}
|
||||
|
||||
let nop = () => {};
|
||||
|
||||
let YesNoDialog = ({
|
||||
onYes = nop,
|
||||
onNo = nop,
|
||||
message = '',
|
||||
title = ''
|
||||
}) => (
|
||||
<Dialog
|
||||
title={title}
|
||||
onYes={onYes}
|
||||
onNo={onNo}
|
||||
>
|
||||
{message}
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
let main = () => (
|
||||
<YesNoDialog
|
||||
title={'Attention!'}
|
||||
onYes={() => showMessageBox('Ok, bye then!')}
|
||||
onNo={() => showMessageBox('Alas there is not much more to do here!')}
|
||||
message={'Are you sure you want to quit?'}
|
||||
/>
|
||||
);
|
|
@ -0,0 +1,83 @@
|
|||
#include <Quickdraw.h>
|
||||
#include <Dialogs.h>
|
||||
#include <Fonts.h>
|
||||
#include <stdbool.h>
|
||||
#include "types.h"
|
||||
#include "utils.h"
|
||||
|
||||
#ifndef TARGET_API_MAC_CARBON
|
||||
/* NOTE: this is checking whether the Dialogs.h we use *knows* about Carbon,
|
||||
not whether we are actually compiling for Cabon.
|
||||
If Dialogs.h is older, we add a define to be able to use the new name
|
||||
for NewUserItemUPP, which used to be NewUserItemProc. */
|
||||
|
||||
#define NewUserItemUPP NewUserItemProc
|
||||
#endif
|
||||
|
||||
// define position in resource dialog.r (128)
|
||||
#define MessageBoxOK 1
|
||||
#define MessageBoxMessage 2
|
||||
#define MessageBoxCancel 3
|
||||
#define MessageBoxTitle 4
|
||||
|
||||
#define AlertOK 1
|
||||
#define AlertMessage 2
|
||||
|
||||
bool ShowAlert(char *message) {
|
||||
DialogItemType type;
|
||||
Handle controlHandle;
|
||||
Rect box;
|
||||
|
||||
DialogPtr adlg = GetNewDialog(129,0,(WindowPtr)-1);
|
||||
InitCursor();
|
||||
{
|
||||
GetDialogItem(adlg, AlertMessage, &type, &controlHandle, &box);
|
||||
PString _message = toPascal(message);
|
||||
SetDialogItemText(controlHandle, toConstStr255Param(_message));
|
||||
}
|
||||
ShowWindow(adlg);
|
||||
|
||||
short itemHit = NULL;
|
||||
|
||||
while (itemHit != AlertOK) {
|
||||
ModalDialog(NULL, &itemHit);
|
||||
}
|
||||
DisposeDialog(adlg);
|
||||
FreeDialog(129);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShowDialog(char *message, char *title, char *ok, char *cancel) {
|
||||
DialogItemType type;
|
||||
Handle controlHandle;
|
||||
Rect box;
|
||||
|
||||
DialogPtr dlg = GetNewDialog(128,0,(WindowPtr)-1);
|
||||
InitCursor();
|
||||
|
||||
{
|
||||
GetDialogItem(dlg, MessageBoxMessage, &type, &controlHandle, &box);
|
||||
PString _message = toPascal(message);
|
||||
SetDialogItemText(controlHandle, toConstStr255Param(_message));
|
||||
|
||||
GetDialogItem(dlg, MessageBoxTitle, &type, &controlHandle, &box);
|
||||
PString _title = toPascal(title);
|
||||
SetDialogItemText(controlHandle, toConstStr255Param(_title));
|
||||
|
||||
GetDialogItem(dlg, MessageBoxOK, &type, &controlHandle, &box);
|
||||
PString _ok = toPascal(ok);
|
||||
SetControlTitle(controlHandle, toConstStr255Param(_ok));
|
||||
|
||||
GetDialogItem(dlg, MessageBoxCancel, &type, &controlHandle, &box);
|
||||
PString _cancel = toPascal(cancel);
|
||||
SetControlTitle(controlHandle, toConstStr255Param(_cancel));
|
||||
}
|
||||
ShowWindow(dlg);
|
||||
short itemHit = NULL;
|
||||
|
||||
while (itemHit != MessageBoxOK && itemHit != MessageBoxCancel) {
|
||||
ModalDialog(NULL, &itemHit);
|
||||
}
|
||||
DisposeDialog(dlg);
|
||||
return itemHit == MessageBoxOK;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef DIALOG_H
|
||||
#define DIALOG_H
|
||||
#include <stdbool.h>
|
||||
|
||||
bool ShowAlert(char *message);
|
||||
bool ShowDialog(char *message, char *title, char *ok, char *cancel);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,31 @@
|
|||
data 'DLOG' (128) {
|
||||
$"0064 0051 012A 01B3 0003 0100 0100 0000" /*
|
||||
.d.Q.*.>........ */
|
||||
$"0000 0080 0000 280A" /* ...<2E>..(. */
|
||||
};
|
||||
|
||||
data 'DLOG' (129) {
|
||||
$"0028 0028 00F0 0118 0001 0100 0100 0000" /* .(.(. ..........
|
||||
*/
|
||||
$"0000 0081 0000 280A" /* ...<2E>..(. */
|
||||
};
|
||||
|
||||
data 'DITL' (128) {
|
||||
$"0003 0000 0000 00AA 0122 00BE 015C 0402" /* ....... .".<2E>.\..
|
||||
*/
|
||||
$"5E31 0000 0000 0028 000A 00A5 0159 8802" /*
|
||||
^1.....(...<2E>.Y<>. */
|
||||
$"5E30 0000 0000 00AA 000A 00BE 0044 0402" /* ^0.....
|
||||
...<2E>.D.. */
|
||||
$"5E32 0000 0000 000A 0073 001A 00EF 8805" /*
|
||||
^2.......s...O<>. */
|
||||
$"5469 746C 6500" /* Title. */
|
||||
};
|
||||
|
||||
data 'DITL' (129) {
|
||||
$"0001 0000 0000 00A0 005B 00B4 0095 0402" /* .......+.[.<2E>.<2E>..
|
||||
*/
|
||||
$"4F4B 0000 0000 001E 0014 008F 00DD 8807" /*
|
||||
OK.........<2E>.><3E>. */
|
||||
$"4D65 7373 6167 6500" /* Message. */
|
||||
};
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,45 @@
|
|||
// This file is almost the same used for windows 3.1 (save for LPCStrings and minor things)
|
||||
#include "dukbridge.h"
|
||||
#include "dialog.h"
|
||||
|
||||
void populateCtx(duk_context *ctx) {
|
||||
duk_get_global_string(ctx, "React"); //0
|
||||
duk_get_prop_string(ctx, -1, "duktape"); //1
|
||||
duk_push_c_lightfunc(ctx, DUK_DIALOG, 4,4,0); //2
|
||||
duk_put_prop_string(ctx, -2, "showDialog");
|
||||
|
||||
duk_push_c_lightfunc(ctx, DUK_MessageBox, 1,1,0);
|
||||
duk_put_prop_string(ctx, -2, "showMessageBox");
|
||||
}
|
||||
|
||||
void emptyStack(duk_context *ctx) {
|
||||
duk_idx_t idx_top;
|
||||
do {
|
||||
idx_top = duk_get_top_index(ctx);
|
||||
if (idx_top != DUK_INVALID_INDEX) duk_pop(ctx);
|
||||
} while(idx_top != DUK_INVALID_INDEX);
|
||||
}
|
||||
|
||||
duk_ret_t DUK_DIALOG(duk_context *ctx) {
|
||||
|
||||
duk_idx_t top = duk_get_top(ctx);
|
||||
char* title = duk_safe_to_string(ctx, 0);
|
||||
char* message = duk_safe_to_string(ctx, 1);
|
||||
|
||||
int ok = ShowDialog(message, title, "Yes", "No");
|
||||
if (ok) {
|
||||
duk_dup(ctx, 2);
|
||||
} else {
|
||||
duk_dup(ctx, 3);
|
||||
}
|
||||
duk_call(ctx, 0);
|
||||
duk_pop_n(ctx, 5);
|
||||
return 0;
|
||||
}
|
||||
|
||||
duk_ret_t DUK_MessageBox(duk_context *ctx) {
|
||||
const char* message = duk_safe_to_string(ctx, 0);
|
||||
ShowAlert(message);
|
||||
duk_pop(ctx);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef DUKBRIDGE_H
|
||||
#define DUKBRIDGE_H
|
||||
#include "duktape.h"
|
||||
duk_ret_t DUK_DIALOG(duk_context *ctx);
|
||||
duk_ret_t DUK_MessageBox(duk_context *ctx);
|
||||
|
||||
void emptyStack(duk_context *ctx);
|
||||
void populateCtx(duk_context *ctx);
|
||||
#endif
|
|
@ -0,0 +1,72 @@
|
|||
#include <Quickdraw.h>
|
||||
#include <Dialogs.h>
|
||||
#include <Fonts.h>
|
||||
#include <stdbool.h>
|
||||
#include <Resources.h>
|
||||
|
||||
// This is probably not needed to save space,
|
||||
// in case you encounter any issues aliasing builtins remove this.
|
||||
#define DUK_USE_LIGHTFUNC_BUILTINS
|
||||
|
||||
#include "duktape.h"
|
||||
#include "dukbridge.h"
|
||||
#include "dialog.h"
|
||||
|
||||
void Initialize();
|
||||
void MainLoop();
|
||||
void Terminate();
|
||||
|
||||
int main()
|
||||
{
|
||||
Initialize();
|
||||
MainLoop();
|
||||
Terminate();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
#if !TARGET_API_MAC_CARBON
|
||||
InitGraf(&qd.thePort);
|
||||
InitFonts();
|
||||
InitWindows();
|
||||
InitMenus();
|
||||
TEInit();
|
||||
InitDialogs(NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
char * readFromResource(int resourceId) {
|
||||
Handle hnd = GetResource('TEXT', resourceId);
|
||||
long size = GetResourceSizeOnDisk(hnd);
|
||||
char * out = malloc(size + 1);
|
||||
ReadPartialResource(hnd, 0, out, size);
|
||||
out[size] = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
void MainLoop()
|
||||
{
|
||||
char * fakeReact = readFromResource(129); // app/fakeReact.js
|
||||
|
||||
duk_context *ctx = duk_create_heap_default();
|
||||
duk_eval_string(ctx, fakeReact);
|
||||
free(fakeReact);
|
||||
emptyStack(ctx);
|
||||
|
||||
populateCtx(ctx);
|
||||
emptyStack(ctx);
|
||||
|
||||
char * source = readFromResource(128); // app/index.js
|
||||
duk_eval_string(ctx, source);
|
||||
free(source);
|
||||
emptyStack(ctx);
|
||||
|
||||
duk_eval_string(ctx, "React.mount(main())");
|
||||
emptyStack(ctx);
|
||||
}
|
||||
|
||||
void Terminate()
|
||||
{
|
||||
ExitToShell();
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
const fs = require('fs');
|
||||
|
||||
const babel = require('@babel/core'),
|
||||
presetEnv = require('@babel/preset-env'),
|
||||
presetReact = require('@babel/preset-react'),
|
||||
transformClasses = require('@babel/plugin-transform-classes');
|
||||
|
||||
const input = fs.readFileSync('./app/index.js', 'utf8');
|
||||
const fakeReact = fs.readFileSync('./app/fakeReact.js', 'utf8');
|
||||
|
||||
const asResource = (input, id = 128) => {
|
||||
const toHex = input => {
|
||||
const result = [];
|
||||
let next = input;
|
||||
while(next.length) {
|
||||
const next16 = next
|
||||
.slice(0,16)
|
||||
.padEnd(16, '\0')
|
||||
.split('')
|
||||
.map(x => x.charCodeAt(0).toString(16).padStart(2, '0'));
|
||||
result.push(` $"${next16[0]}${next16[1]} ${next16[2]}${next16[3]} ${next16[4]}${next16[5]} ${next16[6]}${next16[7]} ${next16[8]}${next16[9]} ${next16[10]}${next16[11]} ${next16[12]}${next16[13]} ${next16[14]}${next16[15]}"`)
|
||||
next = next.slice(16);
|
||||
}
|
||||
return result.join('\n');
|
||||
}
|
||||
return `data 'TEXT' (${id}) {
|
||||
${toHex(input)}
|
||||
};`
|
||||
}
|
||||
|
||||
const main = asResource(babel.transform(input, {
|
||||
filename: 'index.js',
|
||||
presets: [[presetEnv, {
|
||||
"targets": {
|
||||
"browsers": ["ie < 8"]
|
||||
}
|
||||
}],presetReact],
|
||||
plugins: [transformClasses],
|
||||
retainLines: true
|
||||
}).code.replace('"use strict";', ''));
|
||||
|
||||
const react = asResource(babel.transform(fakeReact, {
|
||||
filename: 'react.js',
|
||||
presets: [[presetEnv, {
|
||||
"targets": {
|
||||
"browsers": ["ie < 8"]
|
||||
}
|
||||
}],presetReact],
|
||||
plugins: [transformClasses],
|
||||
retainLines: true
|
||||
}).code.replace('"use strict";', ''), 129);
|
||||
|
||||
const template = fs.readFileSync('./dialog.tpl');
|
||||
|
||||
console.log(`${template}
|
||||
|
||||
${main}
|
||||
|
||||
${react}`);
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "NativeReactLikeMacintosh",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"author": "Cristian Carlesso <@kentaromiura>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.9.6",
|
||||
"@babel/plugin-transform-classes": "^7.9.5",
|
||||
"@babel/preset-env": "^7.9.6",
|
||||
"@babel/preset-react": "^7.9.4"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef __TYPES_H
|
||||
#define __TYPES_H
|
||||
typedef struct PascalString {
|
||||
unsigned char len;
|
||||
char content[255];
|
||||
} PString;
|
||||
#endif
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#include "types.h"
|
||||
#include <Dialogs.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#define toConstStr255Param(var) (ConstStr255Param) &(var)
|
||||
|
||||
// Probably better to return a malloc'd struct for performance,
|
||||
// for now this is fine.
|
||||
PString toPascal(char * str) {
|
||||
PString pStr;
|
||||
unsigned char len = strlen(str);
|
||||
if (len > 255) len = 255;
|
||||
strncpy(pStr.content, str, len);
|
||||
pStr.len = len;
|
||||
return pStr;
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue