Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/placement.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Placement service
on:
pull_request:
paths:
- 'libtandywin/PLACE/**'
- '.github/workflows/placement.yml'
push:
branches: [main]
paths:
- 'libtandywin/PLACE/**'
- '.github/workflows/placement.yml'
permissions:
contents: read
jobs:
placement:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Strict actual-source host checks
run: python3 libtandywin/PLACE/TESTS/RUN.PY --out "$RUNNER_TEMP/placement"
43 changes: 39 additions & 4 deletions MANIFEST.JSON
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
"bytes": 9,
"sha256": "8b9c3028d6467ff3be7c793141927841d70f47f52d0590677438f488ca154992"
},
{
"path": ".github/workflows/placement.yml",
"bytes": 636,
"sha256": "3e3ea2fc79d55ddf7246c7508c1e4952d6bea4b494e5f1ae4a03d6e07f6a4067"
},
{
"path": ".github/workflows/rawlease.yml",
"bytes": 1866,
Expand Down Expand Up @@ -58,8 +63,8 @@
},
{
"path": "README.md",
"bytes": 3426,
"sha256": "6686b8ddd6100a6d7eaa158c15f83c17eab96d162ee7240be32f3ad5ebf5c230"
"bytes": 3653,
"sha256": "c1530858978873a45dc17843267202f1ad04a6966e0b74cd0b554e8f8e9da51d"
},
{
"path": "docs/ADOPT.TXT",
Expand All @@ -78,8 +83,8 @@
},
{
"path": "docs/BUILD.TXT",
"bytes": 4469,
"sha256": "ab371b7f8969aaf2357ba5f7e20d4a6e24df2d5499aa50f7eb95d9dc784ae756"
"bytes": 4756,
"sha256": "aa0368e67d16e57aa54d6cd5b99976510923c4d75dcd06a57edb118eae05ffe3"
},
{
"path": "docs/NAMING.TXT",
Expand Down Expand Up @@ -1626,6 +1631,36 @@
"bytes": 145,
"sha256": "765db2a718d7852e11ae349d3d93a08da832195b57f53efdac4881aa94f25a1a"
},
{
"path": "libtandywin/PLACE/README.TXT",
"bytes": 3266,
"sha256": "e2d64d6f31a4567fa0a2091abf65e21d8ae3a01a5c7dbc11baaf574b130200a2"
},
{
"path": "libtandywin/PLACE/TESTS/RUN.PY",
"bytes": 1664,
"sha256": "ae673a5376480cc0325c115f3f7df2f1d3878e152a9b593b746337f6e8c08176"
},
{
"path": "libtandywin/PLACE/TESTS/WTTEST.C",
"bytes": 6302,
"sha256": "762ac9b696754c70115ea0a99921f23e8dca5e03dbf0f9c241c582d2b1ea4a50"
},
{
"path": "libtandywin/PLACE/TESTS/windows.h",
"bytes": 1954,
"sha256": "85568a0e0199daf4cbeaf29fc200026ffa29e12207658ddc9e38f83feb17f474"
},
{
"path": "libtandywin/PLACE/WTPLACE.C",
"bytes": 6192,
"sha256": "30e822766219f1d6bea9ef5b86fc625dac2828502076adef192903f976f34f56"
},
{
"path": "libtandywin/PLACE/WTPLACE.H",
"bytes": 1843,
"sha256": "af77584f4676029813a6e39e172b4362f0ef79dcae42df5303a2c857b3564ce1"
},
{
"path": "libtandywin/README.TXT",
"bytes": 2456,
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ The canonical shared sound driver is built in
[OEMSound](https://github.com/astrobleem/oemsound-tandy/tree/main/src/win30/DRIVER).
This library contains its consumer interfaces.

* [Placement service](libtandywin/PLACE/README.TXT) supplies caller-owned normal
window rectangles and recovery for the Windows XT window-moving desktop.
It preserves minimized/maximized state without changing USER.EXE.
* `LWTANDY.H` exposes the existing USER repaint helpers.
* `LWAUDIO.H` forwards the app-local MCI/raw-player interfaces.
* [TPLAY](docs/TPLAYXT.TXT) supplies the guarded Windows XT HPXY/XRSYNC launcher
Expand Down
5 changes: 5 additions & 0 deletions docs/BUILD.TXT
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
libwintandy - build instructions

Host placement checks (strict C89, actual source with USER mocks):
python libtandywin/PLACE/TESTS/RUN.PY --out <new-output>
Native consumers link WTPLACE.C once using their existing Win16 toolchain.
See libtandywin/PLACE/README.TXT for the Windows 3.0 ABI and measured evidence.

Host UI checks:
python libtandywin/TESTS/RUN.PY
Host production format, MOD and RAWCLK checks:
Expand Down
51 changes: 51 additions & 0 deletions libtandywin/PLACE/README.TXT
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
WINDOWS 3.0 PLACEMENT SERVICE

Windows 3.0 real mode, 8086/8088. WTPLACE.C links as one object with the
caller, like LTWUI.C. Include WINDOWS.H before WTPLACE.H. Functions use
FAR PASCAL and caller-owned FAR pointers; there is no mutable global data,
allocation, enumeration, hook, timer or USER.EXE modification.

WTPlaceInit initializes one caller-owned observation record.
WTPlaceGet captures current state and the real normal rectangle, using
GetWindowRect for normal windows and existing Windows 3.0 USER exports
GETINTERNALWINDOWPOS/SETINTERNALWINDOWPOS (460/461) for saved positions.
WTPlaceAvailable reports whether both exports exist. The service resolves
them through the original KERNEL APIs; no USER files or exports are added.
An unavailable saved-position read leaves a first-observed icon/zoom's
normal rectangle explicitly unknown. It never substitutes an icon rectangle.

WTPlaceSetNormal updates the normal rectangle, preserving iconic/zoomed
and hidden state, activation and stacking. Pinned windows use SW_SHOWNA
or SW_HIDE with the saved-position setter; normal windows use SetWindowPos
with NOACTIVATE/NOZORDER. WTP_MOVE_ONLY preserves the actual normal size.
Every change is read back. An error leaves the latest observed record and
must not be counted as completed recovery. Pinned updates without usable
saved-position exports return UNSUPPORTED, without restoring the window.
HWND/task/instance changes clear remembered state.

The Windows 3.0 SDK Release 1 documents SetWindowPos and EndDeferWindowPos
as void. Newer headers may declare BOOL; those return values must never be
used as success evidence. The service and WDESK verify the resulting state.
Owned icon-title helpers may be created or raised by the saved-position
setter. Group stacking uses unrelated sibling anchors, then is repaired
without activation or geometry changes and read back. No class-number
assumption or hidden-window exclusion is needed.

This is an explicit caller-owned API, not a replacement for Win32
Get/SetWindowPlacement. A caller may retain records for its existing window
table; this module owns no second tracker. WDESK keeps its logical absolute
coordinates separately and supplies the desired physical normal rectangle.
Home, disable, backend switch and shutdown must finish recovery before
discarding the caller's records. A window already minimized is established
from USER's actual saved rectangle. The service owns no duplicated history.

Host mocks test policy and failure handling. A disposable real-mode Windows
3.0 CGA run verified saved-position ABI, canaries, state, active window and
previous sibling for minimized/maximized/hidden updates. The final source
passed a 417.62-second guarded PCjr 320x200 helper/UI/recovery run and a
37.34-second stock CGA 640x200 visible-maximized recovery run. Both exited
normally to DOS. The record measured 20 bytes natively; no heap/global tracker
or desktop bitmap is allocated. See display src/wdesk/EVIDENCE.JSN.
Canonical source is libtandywin/PLACE. The Windows XT WDESK backend is its
first consumer; its source pins and native receipts identify the exact tested
revision. Host checks: python libtandywin/PLACE/TESTS/RUN.PY --out <new-output>.
28 changes: 28 additions & 0 deletions libtandywin/PLACE/TESTS/RUN.PY
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Strict actual-source placement checks; host USER mocks, no native claim."""
from pathlib import Path
import argparse,hashlib,json,os,shlex,subprocess,sys
root=Path(__file__).resolve().parents[1]
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('--out',type=Path,required=True)
args=parser.parse_args()
args.out.mkdir(parents=True,exist_ok=False)
out=args.out.resolve()
launcher=Path(r'C:\Users\chad\Documents\Codex\toolchains\gcc-ucrt64.py')
compiler=([sys.executable,str(launcher),'--'] if os.name=='nt'
else shlex.split(os.environ.get('CC','cc')))
command=compiler+['-x','c','-std=c89','-pedantic',
'-Wall','-Wextra','-Wconversion','-Wshadow','-Wstrict-prototypes','-Werror',
'-I'+str(root/'TESTS'),'-I'+str(root),str(root/'WTPLACE.C'),
str(root/'TESTS/WTTEST.C'),'-o',str(out/'WTTEST.EXE')]
compiled=subprocess.run(command,capture_output=True)
(out/'COMPILE.LOG').write_bytes(compiled.stdout+compiled.stderr)
if compiled.returncode:raise SystemExit(compiled.returncode)
run=subprocess.run([str(out/'WTTEST.EXE')],capture_output=True)
(out/'RUN.LOG').write_bytes(run.stdout+run.stderr)
record={'evidence':'host USER mocks only','returncode':run.returncode,
'command':command,'source':{p.relative_to(root).as_posix():
{'bytes':len(p.read_bytes()),'sha256':hashlib.sha256(p.read_bytes()).hexdigest()}
for p in [root/'WTPLACE.C',root/'WTPLACE.H',root/'TESTS/windows.h',root/'TESTS/WTTEST.C']}}
(out/'RESULT.JSON').write_text(json.dumps(record,indent=2)+'\n',encoding='ascii')
print(run.stdout.decode('ascii'),end='')
raise SystemExit(run.returncode)
122 changes: 122 additions & 0 deletions libtandywin/PLACE/TESTS/WTTEST.C
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/* Actual placement service, bounded USER mocks. No native evidence here. */
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include "WTPLACE.H"
static struct {int state;WORD task,instance;RECT normal;HWND previous;} win;
static int failRect,failMove,checks,failures,saved,visible,readCalls,writeCalls,gone;
static HWND active;
static int title,raiseGroup,failStack;
static HWND titlePrevious;
#define CHECK(x) do {checks++;if(!(x)){failures++;printf("FAIL line %d: %s\n",__LINE__,#x);}}while(0)
BOOL IsWindow(HWND h){return (h==2 || h==3 || h==5 || (h==4 && title)) && !gone;}
HTASK GetWindowTask(HWND h){(void)h;return win.task;}
WORD GetWindowWord(HWND h,int i){(void)h;(void)i;return win.instance;}
BOOL IsIconic(HWND h){(void)h;return win.state==WTP_ICONIC;}
BOOL IsZoomed(HWND h){(void)h;return win.state==WTP_ZOOMED;}
BOOL IsWindowVisible(HWND h){return h==5?0:h==3?1:visible;}
LONG GetWindowLong(HWND h,int i){(void)i;return (h==4 || h==5)?0:WS_CAPTION;}
HWND GetActiveWindow(void){return active;}
void GetWindowRect(HWND h,RECT *r){
(void)h;if(failRect){gone=1;return;}
if(win.state==WTP_NORMAL)*r=win.normal;
else {r->left=1;r->top=180;r->right=31;r->bottom=200;}
}
HWND GetWindow(HWND h,int c){
if(c==GW_OWNER)return (HWND)(h==4?2:0);
CHECK(c==GW_HWNDPREV);return h==4?titlePrevious:h==5?3:h==3?0:win.previous;
}
HMODULE GetModuleHandle(LPCSTR s){CHECK(!strcmp(s,"USER"));return 1;}
static WORD getSaved(HWND h,LPRECT r,LPPOINT point){
(void)h;readCalls++;
if(r)*r=win.normal;
if(point)point->x=point->y=-1;
return (WORD)(win.state+1);
}
static void setSaved(HWND h,WORD show,LPRECT r,LPPOINT point){
(void)h;(void)point;writeCalls++;CHECK(show==(visible?SW_SHOWNA:SW_HIDE));
if(!failMove)win.normal=*r;
if(title){titlePrevious=raiseGroup?0:win.previous;win.previous=4;}
}
FARPROC GetProcAddress(HMODULE m,LPCSTR s){
CHECK(m==1);if(!saved)return 0;
if(!strcmp(s,"GETINTERNALWINDOWPOS"))return (FARPROC)getSaved;
CHECK(!strcmp(s,"SETINTERNALWINDOWPOS"));return (FARPROC)setSaved;
}
void SetWindowPos(HWND h,HWND after,int x,int y,int w,int ht,UINT f){
int oldw,oldh;(void)h;(void)after;
CHECK((f&SWP_NOACTIVATE)!=0);
if(failMove)return;
if(!(f&SWP_NOZORDER)){
if(failStack)return;
CHECK((f&(SWP_NOMOVE|SWP_NOSIZE))==(SWP_NOMOVE|SWP_NOSIZE));
if(win.previous==4)titlePrevious=after;else win.previous=after;
}
if(f&SWP_NOMOVE)return;
oldw=win.normal.right-win.normal.left;oldh=win.normal.bottom-win.normal.top;
win.normal.left=(short)x;win.normal.top=(short)y;
win.normal.right=(short)(x+((f&SWP_NOSIZE)?oldw:w));
win.normal.bottom=(short)(y+((f&SWP_NOSIZE)?oldh:ht));
}
static void reset(void){
memset(&win,0,sizeof(win));win.task=5;win.instance=6;win.previous=3;
win.normal.left=-200;win.normal.top=-100;win.normal.right=-80;win.normal.bottom=-20;
failRect=failMove=readCalls=writeCalls=gone=title=raiseGroup=failStack=0;saved=visible=1;active=3;titlePrevious=0;
}
int main(void){
WTPLACEMENT p;RECT target;int state,hidden;
for(state=WTP_NORMAL;state<=WTP_ZOOMED;state++)for(hidden=0;hidden<2;hidden++){
reset();win.state=state;visible=!hidden;
CHECK(WTPlaceAvailable());CHECK(WTPlaceInit(2,&p)==0);CHECK(p.flags==0);
CHECK(WTPlaceGet(2,&p)==0);CHECK(p.state==state && (p.flags&WTP_KNOWN));
CHECK(p.normal.left==-200 && p.normal.top==-100);
CHECK((p.flags&WTP_SAVED)==(state==WTP_NORMAL?0:WTP_SAVED));
target.left=20;target.top=30;target.right=target.bottom=0;
CHECK(WTPlaceSetNormal(2,&p,&target,WTP_MOVE_ONLY)==0);
CHECK(p.state==state && p.normal.left==20 && p.normal.top==30);
CHECK(p.normal.right==140 && p.normal.bottom==110);
CHECK(active==3 && win.previous==3 && visible==!hidden);
target.left=25;target.top=35;target.right=100;target.bottom=90;
CHECK(WTPlaceSetNormal(2,&p,&target,0)==0);
CHECK(p.normal.left==25 && p.normal.right==100 && p.normal.bottom==90);
failMove=1;target.left=30;CHECK(WTPlaceSetNormal(2,&p,&target,0)==WTP_E_MOVE);
CHECK(p.normal.left==25 && p.state==state && active==3);
win.task++;saved=0;CHECK(WTPlaceGet(2,&p)==0);
CHECK((p.flags&WTP_KNOWN)==(state==WTP_NORMAL?WTP_KNOWN:0));
}
reset();saved=0;win.state=WTP_ICONIC;CHECK(!WTPlaceAvailable());
CHECK(WTPlaceInit(2,&p)==0);CHECK(WTPlaceGet(2,&p)==0 && p.flags==0);
CHECK(WTPlaceSetNormal(2,&p,&target,WTP_MOVE_ONLY)==WTP_E_UNSUPPORTED);
CHECK(win.state==WTP_ICONIC && active==3 && !writeCalls);
win.state=WTP_NORMAL;CHECK(WTPlaceGet(2,&p)==0);
win.state=WTP_ICONIC;CHECK(WTPlaceGet(2,&p)==0);
CHECK((p.flags&WTP_KNOWN) && !(p.flags&WTP_SAVED) && p.normal.left==-200);
win.instance++;CHECK(WTPlaceGet(2,&p)==0 && p.flags==0);
reset();CHECK(WTPlaceInit(2,&p)==0);failRect=1;
CHECK(WTPlaceGet(2,&p)==WTP_E_WINDOW);
gone=0;p.size=0;CHECK(WTPlaceGet(2,&p)==WTP_E_ARG);
CHECK(WTPlaceInit(0,&p)==WTP_E_WINDOW && p.task==0 && p.instance==0);
CHECK(WTPlaceInit(2,0)==WTP_E_ARG);CHECK(WTPlaceInit(2,&p)==0);
target.left=10;target.right=10;target.top=20;target.bottom=30;
CHECK(WTPlaceSetNormal(2,&p,&target,0)==WTP_E_ARG);
CHECK(WTPlaceSetNormal(2,&p,&target,2)==WTP_E_ARG);
win.state=WTP_ICONIC;CHECK(WTPlaceGet(2,&p)==0);
target.left=32760;CHECK(WTPlaceSetNormal(2,&p,&target,WTP_MOVE_ONLY)==WTP_E_ARG);
CHECK(!writeCalls && win.normal.left==-200);
for(state=0;state<2;state++){
reset();title=1;raiseGroup=state;win.state=WTP_ZOOMED;
CHECK(WTPlaceInit(2,&p)==0);target.left=20;target.top=30;
CHECK(WTPlaceSetNormal(2,&p,&target,WTP_MOVE_ONLY)==0);
CHECK(win.previous==4 && titlePrevious==3 && p.state==WTP_ZOOMED && active==3);
}
reset();title=raiseGroup=1;win.state=WTP_ICONIC;win.previous=5;
CHECK(WTPlaceInit(2,&p)==0);target.left=20;target.top=30;
CHECK(WTPlaceSetNormal(2,&p,&target,WTP_MOVE_ONLY)==0);
CHECK(win.previous==4 && titlePrevious==5 && p.state==WTP_ICONIC && active==3);
reset();title=raiseGroup=failStack=1;win.state=WTP_ICONIC;
CHECK(WTPlaceInit(2,&p)==0);target.left=20;target.top=30;
CHECK(WTPlaceSetNormal(2,&p,&target,WTP_MOVE_ONLY)==WTP_E_MOVE);
CHECK(p.state==WTP_ICONIC && p.normal.left==20 && active==3);
printf("%d checks; %d failures; sizeof(WTPLACEMENT)=%u; host mocks only\n",checks,failures,(unsigned)sizeof(p));
return failures?1:0;
}
62 changes: 62 additions & 0 deletions libtandywin/PLACE/TESTS/windows.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#ifndef WTP_MOCK_WINDOWS_H
#define WTP_MOCK_WINDOWS_H
#define FAR
#define PASCAL
typedef unsigned short WORD;
typedef unsigned short HWND;
typedef unsigned short HTASK;
typedef unsigned short HDWP;
typedef unsigned UINT;
typedef int BOOL;
typedef long LONG;
typedef unsigned long DWORD;
typedef const char *LPCSTR;
typedef struct {short left,top,right,bottom;} RECT;
typedef struct {short x,y;} POINT;
typedef RECT *LPRECT;
typedef POINT *LPPOINT;
typedef WORD HMODULE;
typedef void (*FARPROC)(void);
#define GWW_HINSTANCE (-6)
#define GW_HWNDPREV 3
#define GW_HWNDNEXT 2
#define GW_OWNER 4
#define GW_CHILD 5
#define GWL_STYLE (-16)
#define WS_CHILD 0x40000000L
#define WS_CAPTION 0x00c00000L
#define SM_CXSCREEN 0
#define SM_CYSCREEN 1
#define SM_CYCAPTION 4
#define SW_SHOWNOACTIVATE 4
#define SW_HIDE 0
#define SW_SHOWNORMAL 1
#define SW_SHOWMINIMIZED 2
#define SW_SHOWMAXIMIZED 3
#define SW_SHOWNA 8
#define SWP_NOSIZE 1u
#define SWP_NOMOVE 2u
#define SWP_NOZORDER 4u
#define SWP_NOACTIVATE 16u
BOOL IsWindow(HWND window);
HTASK GetWindowTask(HWND window);
WORD GetWindowWord(HWND window,int index);
BOOL IsIconic(HWND window);
BOOL IsZoomed(HWND window);
void GetWindowRect(HWND window,RECT *rect);
HWND GetWindow(HWND window,int command);
BOOL ShowWindow(HWND window,int command);
void SetWindowPos(HWND window,HWND after,int x,int y,int width,int height,UINT flags);
HWND GetDesktopWindow(void);
BOOL IsWindowVisible(HWND window);
LONG GetWindowLong(HWND window,int index);
HTASK GetCurrentTask(void);
int GetClassName(HWND window,char *name,int size);
int GetSystemMetrics(int index);
HDWP BeginDeferWindowPos(int count);
HDWP DeferWindowPos(HDWP batch,HWND window,HWND after,int x,int y,int width,int height,UINT flags);
void EndDeferWindowPos(HDWP batch);
HMODULE GetModuleHandle(LPCSTR name);
FARPROC GetProcAddress(HMODULE module,LPCSTR name);
HWND GetActiveWindow(void);
#endif
Loading
Loading