diff --git a/.github/workflows/placement.yml b/.github/workflows/placement.yml new file mode 100644 index 0000000..51496a1 --- /dev/null +++ b/.github/workflows/placement.yml @@ -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" diff --git a/MANIFEST.JSON b/MANIFEST.JSON index 6174c9a..9aa01ef 100644 --- a/MANIFEST.JSON +++ b/MANIFEST.JSON @@ -11,6 +11,11 @@ "bytes": 9, "sha256": "8b9c3028d6467ff3be7c793141927841d70f47f52d0590677438f488ca154992" }, + { + "path": ".github/workflows/placement.yml", + "bytes": 636, + "sha256": "3e3ea2fc79d55ddf7246c7508c1e4952d6bea4b494e5f1ae4a03d6e07f6a4067" + }, { "path": ".github/workflows/rawlease.yml", "bytes": 1866, @@ -58,8 +63,8 @@ }, { "path": "README.md", - "bytes": 3426, - "sha256": "6686b8ddd6100a6d7eaa158c15f83c17eab96d162ee7240be32f3ad5ebf5c230" + "bytes": 3653, + "sha256": "c1530858978873a45dc17843267202f1ad04a6966e0b74cd0b554e8f8e9da51d" }, { "path": "docs/ADOPT.TXT", @@ -78,8 +83,8 @@ }, { "path": "docs/BUILD.TXT", - "bytes": 4469, - "sha256": "ab371b7f8969aaf2357ba5f7e20d4a6e24df2d5499aa50f7eb95d9dc784ae756" + "bytes": 4756, + "sha256": "aa0368e67d16e57aa54d6cd5b99976510923c4d75dcd06a57edb118eae05ffe3" }, { "path": "docs/NAMING.TXT", @@ -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, diff --git a/README.md b/README.md index 48f7a05..9d3db1c 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/BUILD.TXT b/docs/BUILD.TXT index 62eabbb..fcc2067 100644 --- a/docs/BUILD.TXT +++ b/docs/BUILD.TXT @@ -1,5 +1,10 @@ libwintandy - build instructions +Host placement checks (strict C89, actual source with USER mocks): + python libtandywin/PLACE/TESTS/RUN.PY --out +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: diff --git a/libtandywin/PLACE/README.TXT b/libtandywin/PLACE/README.TXT new file mode 100644 index 0000000..c9acd66 --- /dev/null +++ b/libtandywin/PLACE/README.TXT @@ -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 . diff --git a/libtandywin/PLACE/TESTS/RUN.PY b/libtandywin/PLACE/TESTS/RUN.PY new file mode 100644 index 0000000..ff941a0 --- /dev/null +++ b/libtandywin/PLACE/TESTS/RUN.PY @@ -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) diff --git a/libtandywin/PLACE/TESTS/WTTEST.C b/libtandywin/PLACE/TESTS/WTTEST.C new file mode 100644 index 0000000..0757743 --- /dev/null +++ b/libtandywin/PLACE/TESTS/WTTEST.C @@ -0,0 +1,122 @@ +/* Actual placement service, bounded USER mocks. No native evidence here. */ +#include +#include +#include +#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; +} diff --git a/libtandywin/PLACE/TESTS/windows.h b/libtandywin/PLACE/TESTS/windows.h new file mode 100644 index 0000000..29759ab --- /dev/null +++ b/libtandywin/PLACE/TESTS/windows.h @@ -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 diff --git a/libtandywin/PLACE/WTPLACE.C b/libtandywin/PLACE/WTPLACE.C new file mode 100644 index 0000000..8289b68 --- /dev/null +++ b/libtandywin/PLACE/WTPLACE.C @@ -0,0 +1,158 @@ +#include +#include "WTPLACE.H" + +/* Windows 3.0 USER exports 460/461. The exact ABI and SW_SHOWNA/SW_HIDE + * saved-position behavior were exercised on real USER in a disposable guest. + * These are private implementation details behind our own explicit API. */ +typedef WORD (FAR PASCAL *WTPGET)(HWND,LPRECT,LPPOINT); +typedef void (FAR PASCAL *WTPSET)(HWND,WORD,LPRECT,LPPOINT); +static WTPGET wtp_get(void) +{ + return (WTPGET)GetProcAddress(GetModuleHandle("USER"), + "GETINTERNALWINDOWPOS"); +} +static WTPSET wtp_set(void) +{ + return (WTPSET)GetProcAddress(GetModuleHandle("USER"), + "SETINTERNALWINDOWPOS"); +} +/* USER can create or raise a window's owned icon-title helper while updating + * its saved position. Anchor the whole owned group against unrelated siblings, + * not against one of the target's own transient helper HWNDs. */ +static int wtp_owned(HWND window, HWND owner) +{ + unsigned n; + for (n = 0; window && n < 32; n++) { + if (window == owner) return 1; + window = GetWindow(window, GW_OWNER); + } + return 0; +} +static HWND wtp_previous(HWND window) +{ + HWND before = GetWindow(window, GW_HWNDPREV); + while (before && wtp_owned(before, window)) + before = GetWindow(before, GW_HWNDPREV); + return before; +} +int FAR PASCAL WTPlaceAvailable(void) +{ + return wtp_get() != 0 && wtp_set() != 0; +} + +int FAR PASCAL WTPlaceInit(HWND window, WTPLACEMENT FAR *p) +{ + if (!p) return WTP_E_ARG; + p->size = (WORD)sizeof(*p); + p->window = window; + p->flags = 0; + p->state = WTP_NORMAL; + p->task = 0; + p->instance = 0; + p->normal.left = p->normal.top = 0; + p->normal.right = p->normal.bottom = 0; + if (!IsWindow(window)) return WTP_E_WINDOW; + p->task = GetWindowTask(window); + p->instance = GetWindowWord(window, GWW_HINSTANCE); + return WTP_OK; +} + +int FAR PASCAL WTPlaceGet(HWND window, WTPLACEMENT FAR *p) +{ + RECT r; + int result; + WTPGET get; + WORD show; + if (!p || p->size != sizeof(*p)) return WTP_E_ARG; + if (!IsWindow(window)) return WTP_E_WINDOW; + if (p->window != window || p->task != GetWindowTask(window) || + p->instance != GetWindowWord(window, GWW_HINSTANCE)) { + result = WTPlaceInit(window, p); + if (result != WTP_OK) return result; + } + p->state = IsIconic(window) ? WTP_ICONIC : + IsZoomed(window) ? WTP_ZOOMED : WTP_NORMAL; + p->flags = (WORD)(p->flags & (WORD)~WTP_SAVED); + if (p->state == WTP_NORMAL) { + GetWindowRect(window, &r); + if (!IsWindow(window)) return WTP_E_WINDOW; + p->normal = r; + p->flags |= WTP_KNOWN; + } else { + get = wtp_get(); + if (get) { + show = get(window, &r, 0); + if (!IsWindow(window)) return WTP_E_WINDOW; + if ((show == SW_SHOWMINIMIZED || show == SW_SHOWMAXIMIZED) && + r.right > r.left && r.bottom > r.top) { + p->normal = r; + p->flags |= WTP_KNOWN | WTP_SAVED; + } + } + } + return WTP_OK; +} + +int FAR PASCAL WTPlaceSetNormal(HWND window, WTPLACEMENT FAR *p, + const RECT FAR *normal, WORD flags) +{ + RECT wanted; + long width, height; + int result; + UINT swp; + WORD state; + int visible; + HWND active, previous; + WTPSET put; + if (!normal || (flags & ~WTP_MOVE_ONLY)) return WTP_E_ARG; + wanted = *normal; + width = (long)wanted.right - wanted.left; + height = (long)wanted.bottom - wanted.top; + if (!(flags & WTP_MOVE_ONLY) && + (width < 1 || width > 16384L || height < 1 || height > 16384L)) + return WTP_E_ARG; + if (flags & WTP_MOVE_ONLY) width = height = 0; + result = WTPlaceGet(window, p); + if (result != WTP_OK) return result; + if ((flags & WTP_MOVE_ONLY) && (p->flags & WTP_KNOWN)) { + width = (long)wanted.left + p->normal.right - p->normal.left; + height = (long)wanted.top + p->normal.bottom - p->normal.top; + if (width < -32768L || width > 32767L || + height < -32768L || height > 32767L) return WTP_E_ARG; + wanted.right = (short)width; wanted.bottom = (short)height; + width = height = 0; + } + if (p->state != WTP_NORMAL) { + put = wtp_set(); + if (!put || !(p->flags & WTP_SAVED)) return WTP_E_UNSUPPORTED; + state = p->state; visible = IsWindowVisible(window); + active = GetActiveWindow(); previous = wtp_previous(window); + put(window, visible ? SW_SHOWNA : SW_HIDE, &wanted, 0); + result = WTPlaceGet(window, p); + if (result != WTP_OK) return result; + if (wtp_previous(window) != previous) { + if (previous && !IsWindow(previous)) return WTP_E_MOVE; + /* Windows 3.0 specifies no SetWindowPos return value. Some + * newer headers declare BOOL: only observed state is evidence. */ + (void)SetWindowPos(window, previous, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + result = WTPlaceGet(window, p); + if (result != WTP_OK) return result; + } + return p->state == state && (p->flags & WTP_SAVED) && + p->normal.left == wanted.left && p->normal.top == wanted.top && + p->normal.right == wanted.right && p->normal.bottom == wanted.bottom && + IsWindowVisible(window) == visible && GetActiveWindow() == active && + wtp_previous(window) == previous ? WTP_OK : WTP_E_MOVE; + } + swp = SWP_NOZORDER | SWP_NOACTIVATE; + if (flags & WTP_MOVE_ONLY) swp |= SWP_NOSIZE; + (void)SetWindowPos(window, 0, wanted.left, wanted.top, + (int)width, (int)height, swp); + if (WTPlaceGet(window, p) != WTP_OK) return WTP_E_WINDOW; + if (p->state != WTP_NORMAL || p->normal.left != wanted.left || + p->normal.top != wanted.top) return WTP_E_MOVE; + if (p->normal.right != wanted.right || p->normal.bottom != wanted.bottom) + return WTP_E_MOVE; + return WTP_OK; +} diff --git a/libtandywin/PLACE/WTPLACE.H b/libtandywin/PLACE/WTPLACE.H new file mode 100644 index 0000000..546f31f --- /dev/null +++ b/libtandywin/PLACE/WTPLACE.H @@ -0,0 +1,41 @@ +/* Caller-owned Windows 3.0 placement observations. Include WINDOWS.H first. + * No global tracker, hook, timer, USER replacement or placement backport. */ +#ifndef WTPLACE_H +#define WTPLACE_H +#define WTP_NORMAL 0 +#define WTP_ICONIC 1 +#define WTP_ZOOMED 2 +#define WTP_KNOWN 1u +#define WTP_SAVED 2u /* actual USER saved normal rectangle */ +#define WTP_MOVE_ONLY 1u +#define WTP_OK 0 +#define WTP_E_ARG (-1) +#define WTP_E_WINDOW (-2) +#define WTP_E_MOVE (-4) +#define WTP_E_UNSUPPORTED (-5) +typedef struct { + WORD size; + HWND window; + HTASK task; + WORD instance; + WORD state; + WORD flags; + RECT normal; +} WTPLACEMENT; +/* Existing Windows 3.0 USER saved-position exports are resolved dynamically. + * No exports or third-party imports are changed. Available means both exist. */ +int FAR PASCAL WTPlaceAvailable(void); +/* Init invents no coordinates. Get observes normal coordinates directly or + * reads USER's saved normal rectangle for a pinned window (WTP_SAVED). + * If unavailable, retained observations remain known; an initially pinned + * window stays unknown. Reused identities reset the caller-owned record. */ +int FAR PASCAL WTPlaceInit(HWND window, WTPLACEMENT FAR *place); +int FAR PASCAL WTPlaceGet(HWND window, WTPLACEMENT FAR *place); +/* Set a normal or saved normal rectangle, preserving minimized/maximized and + * hidden state, activation and z-order. MOVE_ONLY preserves actual normal + * size. Pinned updates need the USER saved-position exports; otherwise return + * UNSUPPORTED without restoring or inventing a rectangle. Verify the result. + * These functions deliberately do not promise a Win32 WINDOWPLACEMENT ABI. */ +int FAR PASCAL WTPlaceSetNormal(HWND window, WTPLACEMENT FAR *place, + const RECT FAR *normal, WORD flags); +#endif