[Scummvm-git-logs] scummvm master -> 9766d228c3f1a7a08951dfaa867b41ceb904513f

sev- noreply at scummvm.org
Thu Oct 2 23:00:42 UTC 2025


This automated email contains information about 8 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
c5701936ad DIRECTOR: Add detection for empty 0x0 BitmapCastMembers
e51ad1557a DIRECTOR: Fix detection entry + quirks for pingu1
4ba38f7c52 DIRECTOR: LINGO: Allow more than 2 arguments for b_puppetSound
bfad1db6be DIRECTOR: Remove multiple inheritance from Window class
1274e6037b DIRECTOR: Only use unsigned audio for Moa when 8-bit
b34dc9e277 DIRECTOR: Fix regression in Movie::queueEvent
b97c82b200 DIRECTOR: Defer changing the current window until the main loop
9766d228c3 DIRECTOR: Add guardrail for Movie::resolveScriptEvent


Commit: c5701936add39c9f9ff4e5a1753a7521b732483b
    https://github.com/scummvm/scummvm/commit/c5701936add39c9f9ff4e5a1753a7521b732483b
Author: Scott Percival (code at moral.net.au)
Date: 2025-10-03T01:00:34+02:00

Commit Message:
DIRECTOR: Add detection for empty 0x0 BitmapCastMembers

Fixes mountain of warnings in tunnel puzzle in pingu1.

Changed paths:
    engines/director/castmember/bitmap.cpp


diff --git a/engines/director/castmember/bitmap.cpp b/engines/director/castmember/bitmap.cpp
index 367b39ccd43..c45991e3fdc 100644
--- a/engines/director/castmember/bitmap.cpp
+++ b/engines/director/castmember/bitmap.cpp
@@ -747,6 +747,12 @@ void BitmapCastMember::load() {
 			} else {
 				warning("BitmapCastMember::load(): cannot open external picture '%s'", location.toString(Common::Path::kNativeSeparator).c_str());
 			}
+		} else if ((!pic || (pic->size() == 0)) && (_initialRect.width() == 0) && (_initialRect.height() == 0)) {
+			// If an image is 0x0, it doesn't matter if we don't have any data.
+			_picture->_surface.create(0, 0, g_director->_wm->_pixelformat);
+			delete pic;
+			_loaded = true;
+			return;
 		}
 	} else {
 		realId = imgId + _cast->_castIDoffset;


Commit: e51ad1557a1c543b7f4a0efff5ff726b03bea1b4
    https://github.com/scummvm/scummvm/commit/e51ad1557a1c543b7f4a0efff5ff726b03bea1b4
Author: Scott Percival (code at moral.net.au)
Date: 2025-10-03T01:00:34+02:00

Commit Message:
DIRECTOR: Fix detection entry + quirks for pingu1

Changed paths:
    engines/director/detection_tables.h
    engines/director/game-quirks.cpp


diff --git a/engines/director/detection_tables.h b/engines/director/detection_tables.h
index fd35f295851..b811d36775b 100644
--- a/engines/director/detection_tables.h
+++ b/engines/director/detection_tables.h
@@ -7539,8 +7539,8 @@ static const DirectorGameDescription gameDescriptions[] = {
 	// "Pingu Screen Saver Chooser", "35dbf55e70f69e00ca016e3663379970", 705445, 500
 	// "Remove Pingu", "35dbf55e70f69e00ca016e3663379970", 705445, 500
 	MACGAME1("pingu1", "",		"Pingu CD-ROM", "35dbf55e70f69e00ca016e3663379970", 705445, 500),
-	WINGAME2("pingu1", "1282A", "PINGU.EXE",	"2e62abdad839e42068afdcd0644d7dcf", 917473,
-								"PINTITLE.DXR", "2e604dfd80ce4189dc0162dbe47ca7e2", 478854, 500),
+	WINGAME2("pingu1", "1282A", "PINGU.EXE",	"t:828102166219aa43f00c7cd72c0a4fc9", 917473,
+								"PINTITLE.DXR", "d:2e604dfd80ce4189dc0162dbe47ca7e2", 478854, 500),
 	WINGAME2_l("pingu1", "",	"PINGU.EXE",	"2e62abdad839e42068afdcd0644d7dcf", 917473,
 								"PINTITLE.DXR", "e56af5ddd0750b8ec04cb4ea3d707066", 489234, Common::CA_ESP, 500),
 	WINGAME2_l("pingu1", "",	"PINGU.EXE",	"2e62abdad839e42068afdcd0644d7dcf", 917473,
diff --git a/engines/director/game-quirks.cpp b/engines/director/game-quirks.cpp
index 7d68bc2095f..121955cb820 100644
--- a/engines/director/game-quirks.cpp
+++ b/engines/director/game-quirks.cpp
@@ -139,6 +139,11 @@ struct CachedFile {
 	// Mission Code: Millennium expects the installer to have added an empty save file.
 	{"mcmillennium", Common::kPlatformWindows, "pc/players", (const byte *)"", 0},
 
+	// Pingu: A Barrel of Fun! expects a text file containing system paths to be written by InstallShield,
+	// and the placeholder text file in the archive will not work.
+	{ "pingu1", Common::kPlatformWindows, "PINGUDRV.PNG", (const byte *)"C:\\\r\nC:\\\r\nD:\\\r\n", -1},
+	{ "pingu1", Common::kPlatformWindows, "CHECKDRV.PNG", (const byte *)"D:\\\r\n", -1},
+
 	{ nullptr, Common::kPlatformUnknown, nullptr, nullptr, 0 }
 };
 
@@ -364,7 +369,11 @@ void DirectorEngine::gameQuirks(const char *target, Common::Platform platform) {
 			SearchMan.remove(kQuirksCacheArchive);
 		}
 
-		SearchMan.add(kQuirksCacheArchive, archive);
+		// The order of precedence for file loading should be:
+		// - Save system
+		// - Quirks list
+		// - File system
+		SearchMan.add(kQuirksCacheArchive, archive, 5);
 	}
 }
 


Commit: 4ba38f7c525643264b304eee4ac1e479f50e40b0
    https://github.com/scummvm/scummvm/commit/4ba38f7c525643264b304eee4ac1e479f50e40b0
Author: Scott Percival (code at moral.net.au)
Date: 2025-10-03T01:00:34+02:00

Commit Message:
DIRECTOR: LINGO: Allow more than 2 arguments for b_puppetSound

Fixes crash at start of pingu1.

Changed paths:
    engines/director/lingo/lingo-builtins.cpp


diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index 5d0c45ebd46..cf93bccd81f 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -3021,8 +3021,10 @@ void LB::b_puppetPalette(int nargs) {
 void LB::b_puppetSound(int nargs) {
 
 	if (nargs < 1 || nargs >= 3) {
-		warning("b_puppetSound(): needs 1 or 2 args");
-		return;
+		warning("b_puppetSound(): needs 1 or 2 args, got %d", nargs);
+		if (nargs < 1)
+			return;
+		g_lingo->dropStack(nargs - 2);
 	}
 
 	DirectorSound *sound = g_director->getCurrentWindow()->getSoundManager();


Commit: bfad1db6be608b1d2d5483cf652a64aa2d9cf233
    https://github.com/scummvm/scummvm/commit/bfad1db6be608b1d2d5483cf652a64aa2d9cf233
Author: Scott Percival (code at moral.net.au)
Date: 2025-10-03T01:00:34+02:00

Commit Message:
DIRECTOR: Remove multiple inheritance from Window class

Previously this was both a subclass of Object<Window> and MacWindow.
This could cause issues because the lifetime of both object types is not
the same; the MacWindow object is owned by the window manager, whereas
Object<Window> is managed with reference counting. To sort this out,
split the MacWindow object to be a pointer held by Window.

Changed paths:
    engines/director/castmember/bitmap.cpp
    engines/director/castmember/digitalvideo.cpp
    engines/director/castmember/richtext.cpp
    engines/director/castmember/text.cpp
    engines/director/director.cpp
    engines/director/events.cpp
    engines/director/lingo/lingo-object.cpp
    engines/director/lingo/lingo-the.cpp
    engines/director/lingo/xlibs/popupmenuxobj.cpp
    engines/director/lingo/xlibs/qtvr.cpp
    engines/director/lingo/xtras/qtvrxtra.cpp
    engines/director/movie.cpp
    engines/director/tests.cpp
    engines/director/transitions.cpp
    engines/director/window.cpp
    engines/director/window.h
    graphics/macgui/macwindow.cpp
    graphics/macgui/macwindow.h


diff --git a/engines/director/castmember/bitmap.cpp b/engines/director/castmember/bitmap.cpp
index c45991e3fdc..97128181a31 100644
--- a/engines/director/castmember/bitmap.cpp
+++ b/engines/director/castmember/bitmap.cpp
@@ -385,7 +385,7 @@ Graphics::MacWidget *BitmapCastMember::createWidget(Common::Rect &bbox, Channel
 		}
 	}
 
-	Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false);
+	Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow()->getMacWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false);
 
 	Graphics::Surface *srcSurface = _ditheredImg ? _ditheredImg : &_picture->_surface;
 	if ((srcSurface->w <= 0) || (srcSurface->h <= 0)) {
diff --git a/engines/director/castmember/digitalvideo.cpp b/engines/director/castmember/digitalvideo.cpp
index a725756d760..f5d55398468 100644
--- a/engines/director/castmember/digitalvideo.cpp
+++ b/engines/director/castmember/digitalvideo.cpp
@@ -390,7 +390,7 @@ Graphics::MacWidget *DigitalVideoCastMember::createWidget(Common::Rect &bbox, Ch
 		}
 	}
 
-	Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false);
+	Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow()->getMacWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false);
 
 	_channel = channel;
 
diff --git a/engines/director/castmember/richtext.cpp b/engines/director/castmember/richtext.cpp
index 0b6d8308aa6..b43ff96fbfe 100644
--- a/engines/director/castmember/richtext.cpp
+++ b/engines/director/castmember/richtext.cpp
@@ -170,7 +170,7 @@ Graphics::MacWidget *RichTextCastMember::createWidget(Common::Rect &bbox, Channe
 	// Check if we need to dither the image
 	int dstBpp = g_director->_wm->_pixelformat.bytesPerPixel;
 
-	Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false);
+	Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow()->getMacWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false);
 
 	Graphics::Surface *dithered = nullptr;
 
diff --git a/engines/director/castmember/text.cpp b/engines/director/castmember/text.cpp
index 54c8978e13e..f398a895a61 100644
--- a/engines/director/castmember/text.cpp
+++ b/engines/director/castmember/text.cpp
@@ -293,7 +293,7 @@ bool textWindowCallback(Graphics::WindowClick click, Common::Event &event, void
 Graphics::MacWidget *TextCastMember::createWindowOrWidget(Common::Rect &bbox, Common::Rect dims, Graphics::MacFont *macFont) {
 	Graphics::MacText *widget = nullptr;
 
-	widget = new Graphics::MacText(g_director->getCurrentWindow(), bbox.left, bbox.top, dims.width(), dims.height(), g_director->_wm, _ftext, macFont, getForeColor(), getBackColor(), _initialRect.width(), getAlignment(), _lineSpacing, _borderSize, _gutterSize, _boxShadow, _textShadow, _textType == kTextTypeFixed || _textType == kTextTypeScrolling, _textType == kTextTypeScrolling);
+	widget = new Graphics::MacText(g_director->getCurrentWindow()->getMacWindow(), bbox.left, bbox.top, dims.width(), dims.height(), g_director->_wm, _ftext, macFont, getForeColor(), getBackColor(), _initialRect.width(), getAlignment(), _lineSpacing, _borderSize, _gutterSize, _boxShadow, _textShadow, _textType == kTextTypeFixed || _textType == kTextTypeScrolling, _textType == kTextTypeScrolling);
 	widget->setSelRange(g_director->getCurrentMovie()->_selStart, g_director->getCurrentMovie()->_selEnd);
 	widget->draw();
 
@@ -342,7 +342,7 @@ Graphics::MacWidget *TextCastMember::createWidget(Common::Rect &bbox, Channel *c
 	case kCastButton:
 		// note that we use _initialRect for the dimensions of the button;
 		// the values provided in the sprite bounding box are ignored
-		widget = new Graphics::MacButton(Graphics::MacButtonType(buttonType), getAlignment(), g_director->getCurrentWindow(), bbox.left, bbox.top, _initialRect.width(), _initialRect.height(), g_director->_wm, _ftext, macFont, getForeColor(), getBackColor());
+		widget = new Graphics::MacButton(Graphics::MacButtonType(buttonType), getAlignment(), g_director->getCurrentWindow()->getMacWindow(), bbox.left, bbox.top, _initialRect.width(), _initialRect.height(), g_director->_wm, _ftext, macFont, getForeColor(), getBackColor());
 		widget->_focusable = true;
 
 		((Graphics::MacButton *)widget)->setHilite(_hilite);
diff --git a/engines/director/director.cpp b/engines/director/director.cpp
index 42432aca776..34ef5c1717d 100644
--- a/engines/director/director.cpp
+++ b/engines/director/director.cpp
@@ -210,7 +210,7 @@ Window *DirectorEngine::getOrCreateWindow(Common::String &name) {
 	window->setVisible(false, true);
 	window->move(0, 0);
 	window->incRefCount();
-	_wm->addWindowInitialized(window);
+	_wm->addWindowInitialized(window->getMacWindow());
 	_windowList.push_back(window);
 	return window;
 }
@@ -294,14 +294,14 @@ Common::Error DirectorEngine::run() {
 	*_stage->_refCount += 1;
 
 	// Set this as background so it doesn't come to foreground when multiple windows present
-	_wm->setBackgroundWindow(_stage);
+	_wm->setBackgroundWindow(_stage->getMacWindow());
 
 	if (!desktopEnabled())
 		_stage->disableBorder();
 
 	_surface = new Graphics::ManagedSurface(1, 1);
 	_wm->setScreen(_surface);
-	_wm->addWindowInitialized(_stage);
+	_wm->addWindowInitialized(_stage->getMacWindow());
 	_wm->setActiveWindow(_stage->getId());
 	setPalette(CastMemberID(kClutSystemMac, -1));
 
@@ -368,6 +368,7 @@ Common::Error DirectorEngine::run() {
 			for (size_t i = 0; i < _windowList.size(); i++) {
 				if (_windowList[i] == window) {
 					_windowList.remove_at(i);
+					// FIXME: force window to be removed from WM
 					window->decRefCount();
 					break;
 				}
diff --git a/engines/director/events.cpp b/engines/director/events.cpp
index 1e70fa298c7..eb07a5d8af7 100644
--- a/engines/director/events.cpp
+++ b/engines/director/events.cpp
@@ -113,7 +113,7 @@ void DirectorEngine::processEventQUIT() {
 }
 
 bool Window::processEvent(Common::Event &event) {
-	bool flag = MacWindow::processEvent(event);
+	bool flag = false;
 
 	if (_currentMovie && _currentMovie->processEvent(event))
 		flag = true;
@@ -325,48 +325,55 @@ bool Movie::processEvent(Common::Event &event) {
 }
 
 bool Window::processWMEvent(Graphics::WindowClick click, Common::Event &event) {
+	bool flag = false;
 	switch (click) {
 	case Graphics::kBorderCloseButton:
 		if (_currentMovie && event.type == Common::EVENT_LBUTTONUP) {
 			_currentMovie->processEvent(kEventCloseWindow, 0);
 			setVisible(false);
 
-			return true;
+			flag = true;
 		}
 		break;
 
 	case Graphics::kBorderActivate:
 		sendWindowEvent(kEventActivateWindow);
-		return true;
+		flag = true;
+		break;
 
 	case Graphics::kBorderDeactivate:
 		sendWindowEvent(kEventDeactivateWindow);
-		return true;
+		flag = true;
+		break;
 
 	case Graphics::kBorderDragged:
 		sendWindowEvent(kEventMoveWindow);
-		return true;
+		flag = true;
+		break;
 
 	case Graphics::kBorderResized:
 		sendWindowEvent(kEventResizeWindow);
-		return true;
+		flag = true;
+		break;
 
 	case Graphics::kBorderMaximizeButton:
 		if (event.type == Common::EVENT_LBUTTONUP) {
 			sendWindowEvent(kEventZoomWindow);
 
-			return true;
+			flag = true;
+			break;
 		}
 		break;
 	default:
 		break;
 	}
 
-	return false;
+	flag |= processEvent(event);
+	return flag;
 }
 
 void Window::sendWindowEvent(LEvent event) {
-	if (_currentMovie && _visible && !_isStage) {
+	if (_currentMovie && _window->isVisible() && !_isStage) {
 		// We cannot call processEvent here directly because it might
 		// be called from within another event processing (like 'on startMovie'	)
 		// which would mess up the Lingo state.
diff --git a/engines/director/lingo/lingo-object.cpp b/engines/director/lingo/lingo-object.cpp
index 6d7f44bef17..e2faf85fb85 100644
--- a/engines/director/lingo/lingo-object.cpp
+++ b/engines/director/lingo/lingo-object.cpp
@@ -785,11 +785,11 @@ bool Window::hasField(int field) {
 Datum Window::getField(int field) {
 	switch (field) {
 	case kTheTitle:
-		return getTitle();
+		return _window->getTitle();
 	case kTheTitleVisible:
-		return isTitleVisible();
+		return _window->isTitleVisible();
 	case kTheVisible:
-		return isVisible();
+		return _window->isVisible();
 	case kTheWindowType:
 		return getWindowType();
 	case kTheRect:
@@ -889,7 +889,7 @@ void LM::m_forget(int nargs) {
 
 void LM::m_open(int nargs) {
 	Window *me = static_cast<Window *>(g_lingo->_state->me.u.obj);
-	bool wasVisible = me->isVisible();
+	bool wasVisible = me->_window->isVisible();
 	me->setVisible(true);
 
 	if (!wasVisible)
diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp
index eba251f9b75..29225eca2ff 100644
--- a/engines/director/lingo/lingo-the.cpp
+++ b/engines/director/lingo/lingo-the.cpp
@@ -1032,7 +1032,7 @@ Datum Lingo::getTheEntity(int entity, Datum &id, int field) {
 	case kTheStageBottom:
 		{
 			Window *window = _vm->getCurrentWindow();
-			d = window->getInnerDimensions().bottom;
+			d = window->_window->getInnerDimensions().bottom;
 		}
 		break;
 	case kTheStageColor:
@@ -1042,19 +1042,19 @@ Datum Lingo::getTheEntity(int entity, Datum &id, int field) {
 	case kTheStageLeft:
 		{
 			Window *window = _vm->getCurrentWindow();
-			d = window->getInnerDimensions().left;
+			d = window->_window->getInnerDimensions().left;
 		}
 		break;
 	case kTheStageRight:
 		{
 			Window *window = _vm->getCurrentWindow();
-			d = window->getInnerDimensions().right;
+			d = window->_window->getInnerDimensions().right;
 		}
 		break;
 	case kTheStageTop:
 		{
 			Window *window = _vm->getCurrentWindow();
-			d = window->getInnerDimensions().top;
+			d = window->_window->getInnerDimensions().top;
 		}
 		break;
 	case kTheStillDown:
diff --git a/engines/director/lingo/xlibs/popupmenuxobj.cpp b/engines/director/lingo/xlibs/popupmenuxobj.cpp
index a6dfc1c8ec5..1914ec46e53 100644
--- a/engines/director/lingo/xlibs/popupmenuxobj.cpp
+++ b/engines/director/lingo/xlibs/popupmenuxobj.cpp
@@ -176,7 +176,7 @@ void PopUpMenuXObj::m_popNum(int nargs) {
 	int left = g_lingo->pop().asInt();
 
 	// Convert window coordinates to screen coordinates
-	Common::Rect windowRect = g_director->getCurrentWindow()->getInnerDimensions();
+	Common::Rect windowRect = g_director->getCurrentWindow()->getMacWindow()->getInnerDimensions();
 	int screenTop = top + windowRect.top - 1;
 	int screenLeft = left + windowRect.left - 1;
 
@@ -193,7 +193,7 @@ void PopUpMenuXObj::m_popText(int nargs) {
 	int left = g_lingo->pop().asInt();
 
 	// Convert window coordinates to screen coordinates
-	Common::Rect windowRect = g_director->getCurrentWindow()->getInnerDimensions();
+	Common::Rect windowRect = g_director->getCurrentWindow()->getMacWindow()->getInnerDimensions();
 	int screenTop = top + windowRect.top - 1;
 	int screenLeft = left + windowRect.left - 1;
 
diff --git a/engines/director/lingo/xlibs/qtvr.cpp b/engines/director/lingo/xlibs/qtvr.cpp
index c171eff4a82..1f1fe78957b 100644
--- a/engines/director/lingo/xlibs/qtvr.cpp
+++ b/engines/director/lingo/xlibs/qtvr.cpp
@@ -345,7 +345,7 @@ void QTVR::m_openMovie(int nargs) {
 	me->_rect = Common::Rect(left, top, left + me->_video->getWidth(), top + me->_video->getHeight());
 
 
-	me->_widget = new QtvrWidget(me, g_director->getCurrentWindow(),
+	me->_widget = new QtvrWidget(me, g_director->getCurrentWindow()->getMacWindow(),
 			me->_rect.left, me->_rect.top, me->_rect.width(), me->_rect.height(),
 			g_director->getMacWindowManager());
 
diff --git a/engines/director/lingo/xtras/qtvrxtra.cpp b/engines/director/lingo/xtras/qtvrxtra.cpp
index d2281dcfcd8..9cb342b33aa 100644
--- a/engines/director/lingo/xtras/qtvrxtra.cpp
+++ b/engines/director/lingo/xtras/qtvrxtra.cpp
@@ -386,7 +386,7 @@ void QtvrxtraXtra::m_QTVROpen(int nargs) {
 	me->_video->setTargetSize(me->_rect.width(), me->_rect.height());
 	me->_video->setOrigin(me->_rect.left, me->_rect.top);
 
-	me->_widget = new QtvrxtraWidget(me, g_director->getCurrentWindow(),
+	me->_widget = new QtvrxtraWidget(me, g_director->getCurrentWindow()->getMacWindow(),
 			me->_rect.left, me->_rect.top, me->_rect.width(), me->_rect.height(),
 			g_director->getMacWindowManager());
 
diff --git a/engines/director/movie.cpp b/engines/director/movie.cpp
index 17b4b5bedee..2bdf3e672d0 100644
--- a/engines/director/movie.cpp
+++ b/engines/director/movie.cpp
@@ -46,7 +46,7 @@ Movie::Movie(Window *window) {
 	_lingo = _vm->getLingo();
 
 	_flags = 0;
-	_stageColor = _window->_wm->_colorWhite;
+	_stageColor = _vm->_wm->_colorWhite;
 
 	_lastClickedSpriteId = 0;
 	_currentSpriteNum = 0;
diff --git a/engines/director/tests.cpp b/engines/director/tests.cpp
index ae36ecec978..cfb6d5aacab 100644
--- a/engines/director/tests.cpp
+++ b/engines/director/tests.cpp
@@ -56,12 +56,12 @@ void Window::testFontScaling() {
 
 	Graphics::ManagedSurface surface;
 
-	surface.create(w, h, _wm->_pixelformat);
-	surface.clear(_wm->_colorWhite);
+	surface.create(w, h, _vm->_wm->_pixelformat);
+	surface.clear(_vm->_wm->_colorWhite);
 
 	Graphics::MacFont origFont(Graphics::kMacFontNewYork, 18);
 
-	const Graphics::MacFONTFont *font1 = (const Graphics::MacFONTFont *)_wm->_fontMan->getFont(origFont);
+	const Graphics::MacFONTFont *font1 = (const Graphics::MacFONTFont *)_vm->_wm->_fontMan->getFont(origFont);
 
 	Graphics::MacFONTFont::testBlit(font1, &surface, 0xff, x, y + 200, 500);
 
diff --git a/engines/director/transitions.cpp b/engines/director/transitions.cpp
index 7fac342853c..d8168e519a8 100644
--- a/engines/director/transitions.cpp
+++ b/engines/director/transitions.cpp
@@ -21,6 +21,7 @@
 
 #include "common/system.h"
 
+#include "graphics/managed_surface.h"
 #include "graphics/primitives.h"
 #include "graphics/macgui/macwindowmanager.h"
 
@@ -134,12 +135,13 @@ struct {
 };
 
 void Window::exitTransition(TransParams &t, Graphics::ManagedSurface *nextFrame, Common::Rect clipRect) {
-	_composeSurface->blitFrom(*nextFrame, clipRect, Common::Point(clipRect.left, clipRect.top));
+	Graphics::ManagedSurface *composeSurface = _window->getSurface();
+	composeSurface->blitFrom(*nextFrame, clipRect, Common::Point(clipRect.left, clipRect.top));
 	stepTransition(t, t.steps);
 }
 
 void Window::stepTransition(TransParams &t, int step) {
-	_contentIsDirty = true;
+	_window->setDirty(true);
 
 	if (t.sourcePal != t.targetPal) {
 		for (int i = 0; i < 768; i++) {
@@ -189,28 +191,24 @@ void Window::playTransition(uint frame, RenderMode mode, uint16 transDuration, u
 	}
 
 	// Cache a copy of the frame before the transition.
-	Graphics::ManagedSurface currentFrame(Graphics::ManagedSurface(_composeSurface->w, _composeSurface->h, g_director->_pixelformat));
-	currentFrame.copyFrom(*_composeSurface);
+	Graphics::ManagedSurface *composeSurface = _window->getSurface();
+	Graphics::ManagedSurface currentFrame(Graphics::ManagedSurface(composeSurface->w, composeSurface->h, g_director->_pixelformat));
+	currentFrame.copyFrom(*composeSurface);
 
 	// If a transition is being played, render the frame after the transition.
-	Graphics::ManagedSurface nextFrame(Graphics::ManagedSurface(_composeSurface->w, _composeSurface->h, g_director->_pixelformat));
+	Graphics::ManagedSurface nextFrame(Graphics::ManagedSurface(composeSurface->w, composeSurface->h, g_director->_pixelformat));
 
 	Common::Rect clipRect;
+	Common::Rect innerDims = _window->getInnerDimensions();
 	Score *score = g_director->getCurrentMovie()->getScore();
 	if (t.area) {
 		// Changed area transition
 		score->updateSprites(mode);
 
-		if (_dirtyRects.size() == 0)
-			return;
-
-		clipRect = *_dirtyRects.begin();
-
-		for (auto &i : _dirtyRects)
-			clipRect.extend(i);
+		clipRect = _window->getDirtyRectBounds();
 
 		// Ensure we redraw any other sprites intersecting the non-clip area.
-		_dirtyRects.clear();
+		_window->clearDirtyRects();
 
 		// Some transitions depend upon an even clipRect size
 		if (clipRect.width() % 2 == 1)
@@ -219,8 +217,8 @@ void Window::playTransition(uint frame, RenderMode mode, uint16 transDuration, u
 		if (clipRect.height() % 2 == 1)
 			clipRect.bottom += 1;
 
-		clipRect.clip(Common::Rect(_innerDims.width(), _innerDims.height()));
-		_dirtyRects.push_back(clipRect);
+		clipRect.clip(Common::Rect(innerDims.width(), innerDims.height()));
+		_window->addDirtyRect(clipRect);
 
 		render(false, &nextFrame);
 	} else {
@@ -228,7 +226,7 @@ void Window::playTransition(uint frame, RenderMode mode, uint16 transDuration, u
 		score->updateSprites(mode);
 		render(true, &nextFrame);
 
-		clipRect = _innerDims;
+		clipRect = innerDims;
 		clipRect.moveTo(0, 0);
 	}
 
@@ -293,7 +291,7 @@ void Window::playTransition(uint frame, RenderMode mode, uint16 transDuration, u
 
 		if (transProps[t.type].algo == kTransAlgoReveal ||
  				transProps[t.type].algo == kTransAlgoEdgesIn) {
-			_composeSurface->copyRectToSurface(nextFrame, clipRect.left, clipRect.top, clipRect);
+			composeSurface->copyRectToSurface(nextFrame, clipRect.left, clipRect.top, clipRect);
 		}
 
 		switch (t.type) {
@@ -372,7 +370,7 @@ void Window::playTransition(uint frame, RenderMode mode, uint16 transDuration, u
 			rto.translate(w - t.xStepSize * i / TSTEP_FRAC, 0);
 			rfrom.right -= w - clipRect.findIntersectingRect(rto).width();
 			rto.clip(clipRect);
-			_composeSurface->blitFrom(nextFrame, rfrom, Common::Point(rto.left, rto.top));
+			composeSurface->blitFrom(nextFrame, rfrom, Common::Point(rto.left, rto.top));
 
 			rfrom.translate(t.xStepSize * i / TSTEP_FRAC, 0);
 			rfrom.setWidth(MAX((int16)0, (int16)(w - t.xStepSize * i / TSTEP_FRAC)));
@@ -382,7 +380,7 @@ void Window::playTransition(uint frame, RenderMode mode, uint16 transDuration, u
 		case kTransPushRight:								// 12
 			rfrom.translate(w - t.xStepSize * i / TSTEP_FRAC, 0);
 			rfrom.setWidth(MAX((int16)0, (int16)(t.xStepSize * i / TSTEP_FRAC)));
-			_composeSurface->blitFrom(nextFrame, rfrom, Common::Point(rto.left, rto.top));
+			composeSurface->blitFrom(nextFrame, rfrom, Common::Point(rto.left, rto.top));
 
 			rto.setWidth(MAX((int16)0, (int16)(w - t.xStepSize * i / TSTEP_FRAC)));
 			rto.translate(t.xStepSize * i / TSTEP_FRAC, 0);
@@ -393,7 +391,7 @@ void Window::playTransition(uint frame, RenderMode mode, uint16 transDuration, u
 		case kTransPushDown:								// 13
 			rfrom.translate(0, h - t.yStepSize * i / TSTEP_FRAC);
 			rfrom.setHeight(MAX((int16)0, (int16)(t.yStepSize * i / TSTEP_FRAC)));
-			_composeSurface->blitFrom(nextFrame, rfrom, Common::Point(rto.left, rto.top));
+			composeSurface->blitFrom(nextFrame, rfrom, Common::Point(rto.left, rto.top));
 
 			rto.setHeight(MAX((int16)0, (int16)(h - t.yStepSize * i / TSTEP_FRAC)));
 			rto.translate(0, t.yStepSize * i / TSTEP_FRAC);
@@ -405,7 +403,7 @@ void Window::playTransition(uint frame, RenderMode mode, uint16 transDuration, u
 			rto.translate(0, h - t.yStepSize * i / TSTEP_FRAC);
 			rfrom.bottom -= h - clipRect.findIntersectingRect(rto).height();
 			rto.clip(clipRect);
-			_composeSurface->blitFrom(nextFrame, rfrom, Common::Point(rto.left, rto.top));
+			composeSurface->blitFrom(nextFrame, rfrom, Common::Point(rto.left, rto.top));
 
 			rfrom.translate(0, t.yStepSize * i / TSTEP_FRAC);
 			rfrom.setHeight(MAX((int16)0, (int16)(h - t.yStepSize * i / TSTEP_FRAC)));
@@ -566,7 +564,7 @@ void Window::playTransition(uint frame, RenderMode mode, uint16 transDuration, u
 		if (stop)
 			break;
 
-		_composeSurface->blitFrom(*blitFrom, rfrom, Common::Point(rto.left, rto.top));
+		composeSurface->blitFrom(*blitFrom, rfrom, Common::Point(rto.left, rto.top));
 
 		if (_vm->processEvents(true)) {
 			exitTransition(t, &nextFrame, clipRect);
@@ -615,11 +613,11 @@ static uint32 randomSeed[33] = {
 };
 
 void Window::dissolveTrans(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *nextFrame) {
+	Graphics::ManagedSurface *composeSurface = _window->getSurface();
 	uint w = clipRect.width();
 	uint h = clipRect.height();
 	uint realw = w, realh = h;
 	byte pixmask[8];
-
 	memset(pixmask, 0, 8);
 
 	// This method treats xStepSize and yStepSize as pixel values, without TSTEP_FRAC.
@@ -746,7 +744,7 @@ void Window::dissolveTrans(TransParams &t, Common::Rect &clipRect, Graphics::Man
 							r.clip(clipRect);
 
 							if (!r.isEmpty())
-								_composeSurface->copyRectToSurface(*nextFrame, x, y, r);
+								composeSurface->copyRectToSurface(*nextFrame, x, y, r);
 						}
 					} else {
 						mask = pixmask[x % -t.xStepSize];
@@ -756,12 +754,12 @@ void Window::dissolveTrans(TransParams &t, Common::Rect &clipRect, Graphics::Man
 						y += clipRect.top;
 
 						if (g_director->_pixelformat.bytesPerPixel == 1) {
-							byte *dst = (byte *)_composeSurface->getBasePtr(x, y);
+							byte *dst = (byte *)composeSurface->getBasePtr(x, y);
 							byte *src = (byte *)nextFrame->getBasePtr(x, y);
 
 							*dst = ((*dst & ~mask) | (*src & mask)) & 0xff;
 						} else {
-							uint32 *dst = (uint32 *)_composeSurface->getBasePtr(x, y);
+							uint32 *dst = (uint32 *)composeSurface->getBasePtr(x, y);
 							uint32 *src = (uint32 *)nextFrame->getBasePtr(x, y);
 
 							*dst = ((*dst & ~mask) | (*src & mask)) & 0xff;
@@ -863,6 +861,7 @@ static byte dissolvePatterns[][8] = {
 };
 
 void Window::dissolvePatternsTrans(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *nextFrame) {
+	Graphics::ManagedSurface *composeSurface = _window->getSurface();
 	int patternSteps = 64;
 
 	for (int i = 0; i < t.steps; i++) {
@@ -872,7 +871,7 @@ void Window::dissolvePatternsTrans(TransParams &t, Common::Rect &clipRect, Graph
 			byte pat = dissolvePatterns[patternIndex][y % 8];
 			if (g_director->_pixelformat.bytesPerPixel == 1) {
 
-				byte *dst = (byte *)_composeSurface->getBasePtr(clipRect.left, y);
+				byte *dst = (byte *)composeSurface->getBasePtr(clipRect.left, y);
 				byte *src = (byte *)nextFrame->getBasePtr(clipRect.left, y);
 
 				for (int x = clipRect.left; x < clipRect.right;) {
@@ -887,7 +886,7 @@ void Window::dissolvePatternsTrans(TransParams &t, Common::Rect &clipRect, Graph
 					}
 				}
 			} else {
-				uint32 *dst = (uint32 *)_composeSurface->getBasePtr(clipRect.left, y);
+				uint32 *dst = (uint32 *)composeSurface->getBasePtr(clipRect.left, y);
 				uint32 *src = (uint32 *)nextFrame->getBasePtr(clipRect.left, y);
 
 				for (int x = clipRect.left; x < clipRect.right;) {
@@ -921,6 +920,7 @@ void Window::dissolvePatternsTrans(TransParams &t, Common::Rect &clipRect, Graph
 }
 
 void Window::transMultiPass(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *nextFrame) {
+	Graphics::ManagedSurface *composeSurface = _window->getSurface();
 	Common::Rect rto;
 	uint w = clipRect.width();
 	uint h = clipRect.height();
@@ -1079,7 +1079,7 @@ void Window::transMultiPass(TransParams &t, Common::Rect &clipRect, Graphics::Ma
 			rto.clip(clipRect);
 
 			if (rto.height() > 0 && rto.width() > 0) {
-				_composeSurface->blitFrom(*nextFrame, rto, Common::Point(rto.left, rto.top));
+				composeSurface->blitFrom(*nextFrame, rto, Common::Point(rto.left, rto.top));
 			}
 		}
 		stepTransition(t, i);
@@ -1101,6 +1101,7 @@ void Window::transMultiPass(TransParams &t, Common::Rect &clipRect, Graphics::Ma
 }
 
 void Window::transZoom(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *currentFrame, Graphics::ManagedSurface *nextFrame) {
+	Graphics::ManagedSurface *composeSurface = _window->getSurface();
 	Common::Rect r = clipRect;
 	uint w = clipRect.width();
 	uint h = clipRect.height();
@@ -1111,7 +1112,7 @@ void Window::transZoom(TransParams &t, Common::Rect &clipRect, Graphics::Managed
 
 	DirectorPlotData pd(g_director, kLineTopBottomSprite, kInkTypeReverse, 0, _wm->_colorWhite, _wm->_colorBlack);
 	pd.destRect = clipRect;
-	pd.dst = _composeSurface;
+	pd.dst = composeSurface;
 
 	Graphics::Primitives *primitives = g_director->getInkPrimitives();
 
@@ -1119,7 +1120,7 @@ void Window::transZoom(TransParams &t, Common::Rect &clipRect, Graphics::Managed
 		uint32 startTime = g_system->getMillis();
 
 		// FIXME: figure out the bounding box of the drawn bits
-		_composeSurface->copyRectToSurface(*currentFrame, clipRect.left, clipRect.top, clipRect);
+		composeSurface->copyRectToSurface(*currentFrame, clipRect.left, clipRect.top, clipRect);
 
 		for (int s = 2; s >= 0; s--) {
 			if (i - s < 0 || i - s > t.steps - 2)
@@ -1160,8 +1161,8 @@ void Window::transZoom(TransParams &t, Common::Rect &clipRect, Graphics::Managed
 		g_lingo->executePerFrameHook(t.frame, i, false);
 	}
 
-	render(true, _composeSurface);
-	_contentIsDirty = true;
+	render(true, composeSurface);
+	_window->setDirty(true);
 	g_director->draw();
 }
 
diff --git a/engines/director/window.cpp b/engines/director/window.cpp
index 52d2dc62f0a..9c0f6264c9d 100644
--- a/engines/director/window.cpp
+++ b/engines/director/window.cpp
@@ -39,6 +39,7 @@
 #include "director/sprite.h"
 #include "director/castmember/castmember.h"
 #include "director/debugger/debugtools.h"
+#include "graphics/managed_surface.h"
 
 namespace Director {
 
@@ -48,8 +49,9 @@ bool commandsWindowCallback(Graphics::WindowClick click, Common::Event &event, v
 }
 
 Window::Window(int id, bool scrollable, bool resizable, bool editable, Graphics::MacWindowManager *wm, DirectorEngine *vm, bool isStage)
-	: MacWindow(id, scrollable, resizable, editable, wm), Object<Window>("Window") {
+: Object<Window>("Window") {
 	_vm = vm;
+	_wm = wm;
 	_isStage = isStage;
 	_stageColor = _wm->_colorBlack;
 	_puppetTransition = nullptr;
@@ -68,11 +70,13 @@ Window::Window(int id, bool scrollable, bool resizable, bool editable, Graphics:
 	_isModal = false;
 	_skipFrameAdvance = false;
 
-	updateBorderType();
+	// Owned by the window manager
+	_window = new Graphics::MacWindow(id, scrollable, resizable, editable, wm);
+	_window->setDraggable(!_isStage);
 
-	_draggable = !_isStage;
+	_window->setCallback(commandsWindowCallback, this);
 
-	setCallback(commandsWindowCallback, this);
+	updateBorderType();
 }
 
 Window::~Window() {
@@ -90,7 +94,7 @@ Window::~Window() {
 void Window::decRefCount() {
 	*_refCount -= 1;
 	if (*_refCount <= 0) {
-		g_director->_wm->removeWindow(this);
+		g_director->_wm->removeWindow(_window);
 		g_director->_wm->removeMarked();
 	}
 }
@@ -111,9 +115,11 @@ void Window::invertChannel(Channel *channel, const Common::Rect &destRect) {
 	int xoff = srcRect.left - channel->getBbox().left;
 	int yoff = srcRect.top - channel->getBbox().top;
 
+	Graphics::ManagedSurface *composeSurface = _window->getSurface();
+
 	if (_wm->_pixelformat.bytesPerPixel == 1) {
 		for (int i = 0; i < srcRect.height(); i++) {
-			byte *src = (byte *)_composeSurface->getBasePtr(srcRect.left, srcRect.top + i);
+			byte *src = (byte *)composeSurface->getBasePtr(srcRect.left, srcRect.top + i);
 			const byte *msk = mask ? (const byte *)mask->getBasePtr(xoff, yoff + i) : nullptr;
 
 			for (int j = 0; j < srcRect.width(); j++, src++)
@@ -123,7 +129,7 @@ void Window::invertChannel(Channel *channel, const Common::Rect &destRect) {
 	} else {
 
 		for (int i = 0; i < srcRect.height(); i++) {
-			uint32 *src = (uint32 *)_composeSurface->getBasePtr(srcRect.left, srcRect.top + i);
+			uint32 *src = (uint32 *)composeSurface->getBasePtr(srcRect.left, srcRect.top + i);
 			const byte *msk = mask ? (const byte *)mask->getBasePtr(xoff, yoff + i) : nullptr;
 
 			for (int j = 0; j < srcRect.width(); j++, src++)
@@ -161,36 +167,38 @@ bool Window::render(bool forceRedraw, Graphics::ManagedSurface *blitTo) {
 		return false;
 
 	if (!blitTo)
-		blitTo = _composeSurface;
+		blitTo = _window->getSurface();
+
+	Common::List<Common::Rect> &dirtyRects = _window->getDirtyRectList();
 
 	if (forceRedraw) {
 		blitTo->clear(_stageColor);
-		markAllDirty();
+		_window->markAllDirty();
 	} else {
-		if (_dirtyRects.size() == 0 && _currentMovie->_videoPlayback == false) {
+		if (dirtyRects.size() == 0 && _currentMovie->_videoPlayback == false) {
 			if (g_director->_debugDraw & kDebugDrawFrame) {
 				drawFrameCounter(blitTo);
 
-				_contentIsDirty = true;
+				_window->setContentDirty(true);
 			}
 
 			return false;
 		}
 
-		mergeDirtyRects();
+		_window->mergeDirtyRects();
 	}
 
 	Channel *hiliteChannel = _currentMovie->getScore()->getChannelById(_currentMovie->_currentHiliteChannelId);
 
 	uint32 renderStartTime = g_system->getMillis();
-	debugC(7, kDebugImages, "Window::render(): Updating %d rects", _dirtyRects.size());
+	debugC(7, kDebugImages, "Window::render(): Updating %d rects", dirtyRects.size());
 
-	for (auto &i : _dirtyRects) {
+	for (auto &i : dirtyRects) {
 		Common::Rect r = i;
 		// The inner dimensions are relative to the virtual desktop while
 		// r isn't, so we need to move the window to be relative to the
 		// same sapce.
-		Common::Rect windowRect = getInnerDimensions();
+		Common::Rect windowRect = _window->getInnerDimensions();
 		windowRect.moveTo(r.left, r.top);
 		r.clip(windowRect);
 
@@ -264,8 +272,8 @@ bool Window::render(bool forceRedraw, Graphics::ManagedSurface *blitTo) {
 	if (g_director->_debugDraw & kDebugDrawFrame)
 		drawFrameCounter(blitTo);
 
-	_dirtyRects.clear();
-	_contentIsDirty = true;
+	dirtyRects.clear();
+	_window->setContentDirty(true);
 	debugC(7, kDebugImages, "Window::render(): Draw finished in %d ms",  g_system->getMillis() - renderStartTime);
 
 	return true;
@@ -275,19 +283,59 @@ void Window::setStageColor(uint32 stageColor, bool forceReset) {
 	if (stageColor != _stageColor || forceReset) {
 		_stageColor = stageColor;
 		reset();
-		markAllDirty();
+		_window->markAllDirty();
 	}
 }
 
 void Window::setTitleVisible(bool titleVisible) {
-	MacWindow::setTitleVisible(titleVisible);
+	_window->setTitleVisible(titleVisible);
 	updateBorderType();
 }
 
+Graphics::ManagedSurface *Window::getSurface() {
+	return _window->getSurface();
+}
+
+void Window::addDirtyRect(const Common::Rect &r) {
+	_window->addDirtyRect(r);
+}
+
+void Window::resizeInner(int w, int h) {
+	_window->resizeInner(w, h);
+}
+
+int Window::getId() {
+	return _window->getId();
+}
+
+void Window::setDirty(bool dirty) {
+	_window->setDirty(dirty);
+}
+
+void Window::disableBorder() {
+	_window->disableBorder();
+}
+
+void Window::center(bool toCenter) {
+	_window->center(toCenter);
+}
+
+Common::Point Window::getAbsolutePos() {
+	return _window->getAbsolutePos();
+}
+
+void Window::setTitle(const Common::String &title) {
+	_window->setTitle(title);
+}
+
+void Window::move(int x, int y) {
+	_window->move(x, y);
+}
+
 Datum Window::getStageRect() {
 	ensureMovieIsLoaded();
 
-	Common::Rect rect = getInnerDimensions();
+	Common::Rect rect = _window->getInnerDimensions();
 	Datum d;
 	d.type = RECT;
 	d.u.farr = new FArray;
@@ -308,7 +356,7 @@ void Window::setStageRect(Datum datum) {
 	// Unpack rect from datum
 	Common::Rect rect = Common::Rect(datum.u.farr->arr[0].asInt(), datum.u.farr->arr[1].asInt(), datum.u.farr->arr[2].asInt(), datum.u.farr->arr[3].asInt());
 
-	setInnerDimensions(rect);
+	_window->setInnerDimensions(rect);
 }
 
 void Window::setModal(bool modal) {
@@ -316,7 +364,7 @@ void Window::setModal(bool modal) {
 		_wm->setLockedWidget(nullptr);
 		_isModal = false;
 	} else if (!_isModal && modal) {
-		_wm->setLockedWidget(this);
+		_wm->setLockedWidget(this->_window);
 		_isModal = true;
 	}
 }
@@ -327,8 +375,9 @@ void Window::setFileName(Common::String filename) {
 }
 
 void Window::reset() {
-	resizeInner(_composeSurface->w, _composeSurface->h);
-	_contentIsDirty = true;
+	Graphics::ManagedSurface *composeSurface = _window->getSurface();
+	resizeInner(composeSurface->w, composeSurface->h);
+	_window->setContentDirty(true);
 }
 
 void Window::inkBlitFrom(Channel *channel, Common::Rect destRect, Graphics::ManagedSurface *blitTo) {
@@ -369,7 +418,8 @@ void Window::inkBlitFrom(Channel *channel, Common::Rect destRect, Graphics::Mana
 }
 
 Common::Point Window::getMousePos() {
-	return g_system->getEventManager()->getMousePos() - Common::Point(_innerDims.left, _innerDims.top);
+	Common::Rect innerDims = _window->getInnerDimensions();
+	return g_system->getEventManager()->getMousePos() - Common::Point(innerDims.left, innerDims.top);
 }
 
 void Window::setVisible(bool visible, bool silent) {
@@ -377,10 +427,10 @@ void Window::setVisible(bool visible, bool silent) {
 	if (!_currentMovie && !silent)
 		ensureMovieIsLoaded();
 
-	BaseMacWindow::setVisible(visible);
+	_window->setVisible(visible);
 
 	if (visible)
-		_wm->setActiveWindow(_id);
+		_wm->setActiveWindow(getId());
 }
 
 void Window::ensureMovieIsLoaded() {
@@ -435,11 +485,11 @@ bool Window::setNextMovie(Common::String &movieFilenameRaw) {
 
 void Window::updateBorderType() {
 	if (_isStage) {
-		setBorderType(3);
-	} else if (!isTitleVisible()) {
-		setBorderType(2);
+		_window->setBorderType(3);
+	} else if (!_window->isTitleVisible()) {
+		_window->setBorderType(2);
 	} else {
-		setBorderType(MAX(0, MIN(_windowType, 16)));
+		_window->setBorderType(MAX(0, MIN(_windowType, 16)));
 	}
 }
 
@@ -778,12 +828,14 @@ uint32 Window::frozenLingoRecursionCount() {
 }
 
 Common::String Window::formatWindowInfo() {
+	Common::Rect dims = _window->getDimensions();
+	Common::Rect innerDims = _window->getInnerDimensions();
 	return Common::String::format(
 			"name: \"%s\", movie: \"%s\", currentPath: \"%s\", dims: (%d,%d) %dx%d, innerDims: (%d, %d) %dx%d, visible: %d",
 			_name.c_str(), _currentMovie->getMacName().c_str(), _currentPath.c_str(),
-			_dims.left, _dims.top, _dims.width(), _dims.height(),
-			_innerDims.left, _innerDims.top, _innerDims.width(), _innerDims.height(),
-			_visible
+			dims.left, dims.top, dims.width(), dims.height(),
+			innerDims.left, innerDims.top, innerDims.width(), innerDims.height(),
+			_window->isVisible()
 	);
 }
 
diff --git a/engines/director/window.h b/engines/director/window.h
index c3120f7231c..0de1e663977 100644
--- a/engines/director/window.h
+++ b/engines/director/window.h
@@ -22,6 +22,8 @@
 #ifndef DIRECTOR_STAGE_H
 #define DIRECTOR_STAGE_H
 
+#include "graphics/macgui/macwindow.h"
+
 #include "director/lingo/lingo-object.h"
 
 namespace Common {
@@ -38,6 +40,7 @@ namespace Director {
 
 class Channel;
 class MacArchive;
+class Window;
 struct MacShape;
 struct LingoState;
 
@@ -100,7 +103,7 @@ struct TransParams {
 	}
 };
 
-class Window : public Graphics::MacWindow, public Object<Window> {
+class Window : public Object<Window> {
 public:
 	Window(int id, bool scrollable, bool resizable, bool editable, Graphics::MacWindowManager *wm, DirectorEngine *vm, bool isStage);
 	~Window();
@@ -129,18 +132,30 @@ public:
 	Common::Point getMousePos();
 
 	DirectorEngine *getVM() const { return _vm; }
+	Graphics::MacWindow *getMacWindow() const { return _window; }
 	Movie *getCurrentMovie() const { return _currentMovie; }
 	Common::String getCurrentPath() const { return _currentPath; }
 	DirectorSound *getSoundManager() const { return _soundManager; }
 
-	void setVisible(bool visible, bool silent = false) override;
+	void setVisible(bool visible, bool silent = false);
 	bool setNextMovie(Common::String &movieFilenameRaw);
 
 	void ensureMovieIsLoaded();
 
 	void setWindowType(int type) { _windowType = type; updateBorderType(); }
 	int getWindowType() const { return _windowType; }
-	void setTitleVisible(bool titleVisible) override;
+	void setTitleVisible(bool titleVisible);
+	Graphics::ManagedSurface *getSurface();
+	void addDirtyRect(const Common::Rect &r);
+	void resizeInner(int w, int h);
+	int getId();
+	void setDirty(bool dirty);
+	void disableBorder();
+	void center(bool toCenter = true);
+	Common::Point getAbsolutePos();
+	void setTitle(const Common::String &title);
+	void move(int x, int y);
+
 	Datum getStageRect();
 	void setStageRect(Datum datum);
 	void setModal(bool modal);
@@ -172,7 +187,7 @@ public:
 	static void inkBlitFrom(Channel *channel, Common::Rect destRect, Graphics::ManagedSurface *blitTo = nullptr);
 
 	// events.cpp
-	bool processEvent(Common::Event &event) override;
+	bool processEvent(Common::Event &event);
 	bool processWMEvent(Graphics::WindowClick click, Common::Event &event);
 	void sendWindowEvent(LEvent event);
 
@@ -203,6 +218,9 @@ public:
 	Common::Path _fileName;
 
 public:
+	Graphics::MacWindow *_window;
+	Graphics::MacWindowManager *_wm;
+
 	Common::List<Channel *> _dirtyChannels;
 	TransParams *_puppetTransition;
 
diff --git a/graphics/macgui/macwindow.cpp b/graphics/macgui/macwindow.cpp
index 047b9a70331..10bf2302fcf 100644
--- a/graphics/macgui/macwindow.cpp
+++ b/graphics/macgui/macwindow.cpp
@@ -531,6 +531,8 @@ WindowClick MacWindow::isInScroll(int x, int y) const {
 bool MacWindow::processEvent(Common::Event &event) {
 	WindowClick click = isInBorder(event.mouse.x, event.mouse.y);
 
+	bool result = false;
+
 	switch (event.type) {
 	case Common::EVENT_MOUSEMOVE:
 		if (_wm->_mouseDown && _wm->_hoveredWidget && !_wm->_hoveredWidget->_dims.contains(event.mouse.x - _dims.left, event.mouse.y - _dims.top)) {
@@ -611,21 +613,30 @@ bool MacWindow::processEvent(Common::Event &event) {
 		break;
 
 	case Common::EVENT_KEYDOWN:
+		if (_callback)
+			result = _callback(kBorderNone, event, _dataPtr);
+
 		if (!_editable && !(_wm->getActiveWidget() && _wm->getActiveWidget()->isEditable()))
-			return false;
+			return result;
 
 		if (_wm->getActiveWidget())
-			return _wm->getActiveWidget()->processEvent(event);
+			return _wm->getActiveWidget()->processEvent(event) || result;
 
-		return false;
+		return result;
 
 	case Common::EVENT_WHEELUP:
 	case Common::EVENT_WHEELDOWN:
+		if (_callback)
+			result = _callback(kBorderNone, event, _dataPtr);
+
 		if (_wm->getActiveWidget() && _wm->getActiveWidget()->processEvent(event))
 			return true;
-		return false;
+		return result;
 
 	default:
+		if (_callback)
+			return _callback(kBorderNone, event, _dataPtr);
+
 		return false;
 	}
 
@@ -637,13 +648,12 @@ bool MacWindow::processEvent(Common::Event &event) {
 		_wm->_hoveredWidget = w;
 
 		if (w->processEvent(event))
-			return true;
+			result = true;
 	}
 
 	if (_callback)
-		return (*_callback)(click, event, _dataPtr);
-	else
-		return false;
+		result = (*_callback)(click, event, _dataPtr) || result;
+	return result;
 }
 
 void MacWindow::setBorderType(int borderType) {
@@ -697,4 +707,15 @@ void MacWindow::mergeDirtyRects() {
 	}
 }
 
+Common::Rect MacWindow::getDirtyRectBounds() {
+	Common::Rect result;
+	if (_dirtyRects.size() == 0)
+		return result;
+	result = Common::Rect(_dirtyRects.front());
+	for (auto &r : _dirtyRects) {
+		result.extend(r);
+	}
+	return result;
+}
+
 } // End of namespace Graphics
diff --git a/graphics/macgui/macwindow.h b/graphics/macgui/macwindow.h
index ce366c966ec..04a7af6757d 100644
--- a/graphics/macgui/macwindow.h
+++ b/graphics/macgui/macwindow.h
@@ -176,6 +176,12 @@ public:
 	 */
 	void setCallback(bool (*callback)(WindowClick, Common::Event &, void *), void *data) { _callback = callback; _dataPtr = data; }
 
+	/**
+	 * Mutator to change the draggable state of the window.
+	 * @param draggable Target state.
+	 */
+	void setDraggable(bool draggable) { _draggable = draggable; }
+
 protected:
 	int _id;
 	WindowType _type;
@@ -380,10 +386,14 @@ public:
 	void addDirtyRect(const Common::Rect &r);
 	void markAllDirty();
 	void mergeDirtyRects();
+	Common::Rect getDirtyRectBounds();
+	void clearDirtyRects() { _dirtyRects.clear(); }
+	Common::List<Common::Rect> &getDirtyRectList() { return _dirtyRects; }
 
 	bool isDirty() override { return _borderIsDirty || _contentIsDirty; }
 
 	void setBorderDirty(bool dirty) { _borderIsDirty = true; }
+	void setContentDirty(bool dirty) { _contentIsDirty = true; }
 	void resizeBorderSurface();
 
 	void setMode(uint32 mode) { _mode = mode; }


Commit: 1274e6037ba053b06283a2d4cc8b4ea17a3d642a
    https://github.com/scummvm/scummvm/commit/1274e6037ba053b06283a2d4cc8b4ea17a3d642a
Author: Scott Percival (code at moral.net.au)
Date: 2025-10-03T01:00:34+02:00

Commit Message:
DIRECTOR: Only use unsigned audio for Moa when 8-bit

Changed paths:
    engines/director/sound.cpp


diff --git a/engines/director/sound.cpp b/engines/director/sound.cpp
index 7909926444f..ef55dbe3a16 100644
--- a/engines/director/sound.cpp
+++ b/engines/director/sound.cpp
@@ -1044,7 +1044,12 @@ Audio::AudioStream *MoaSoundFormatDecoder::getAudioStream(bool looping, bool for
 	byte *buffer = (byte *)malloc(_size);
 	memcpy(buffer, _data, _size);
 
-	Audio::SeekableAudioStream *stream = Audio::makeRawStream(buffer, _size, _format.frameRate, ((_format.bitsPerSample == 16) ? Audio::RawFlags::FLAG_16BITS : 0) | ((_format.numChannels == 2) ? Audio::RawFlags::FLAG_STEREO : 0) | Audio::RawFlags::FLAG_UNSIGNED, disposeAfterUse);
+	Audio::SeekableAudioStream *stream = Audio::makeRawStream(buffer,
+			_size, _format.frameRate,
+			((_format.bitsPerSample == 16) ? Audio::RawFlags::FLAG_16BITS : 0) |
+			((_format.numChannels == 2) ? Audio::RawFlags::FLAG_STEREO : 0) |
+			((_format.bitsPerSample == 8) ? Audio::RawFlags::FLAG_UNSIGNED : 0),
+			disposeAfterUse);
 
 	if (looping) {
 		if (_format.loopEndFrame < _format.loopStartFrame) {


Commit: b34dc9e277f316c66b6aff72a554c509b25a3b63
    https://github.com/scummvm/scummvm/commit/b34dc9e277f316c66b6aff72a554c509b25a3b63
Author: Scott Percival (code at moral.net.au)
Date: 2025-10-03T01:00:34+02:00

Commit Message:
DIRECTOR: Fix regression in Movie::queueEvent

Fixes main menu buttons in Mean City.

Changed paths:
    engines/director/lingo/lingo-events.cpp


diff --git a/engines/director/lingo/lingo-events.cpp b/engines/director/lingo/lingo-events.cpp
index 246d73c1ad0..3bb7cc2f42a 100644
--- a/engines/director/lingo/lingo-events.cpp
+++ b/engines/director/lingo/lingo-events.cpp
@@ -539,14 +539,14 @@ void Movie::queueEvent(Common::Queue<LingoEvent> &queue, LEvent event, int targe
 		case kEventMouseWithin:		// D6+
 			if (_vm->getVersion() >= 600) {
 				if (pointedSpriteId != 0) {
-					Channel *channel = _score->_channels[channelId];
+					Channel *channel = _score->getChannelById(pointedSpriteId);
 
 					// Generate event for each behavior, and pass through for all but the last one.
 					// This is to allow multiple behaviors on a single sprite to each have a
 					// chance to handle the event.
 					for (uint i = 0; i < channel->_scriptInstanceList.size(); i++) {
 						bool passThrough = (i != channel->_scriptInstanceList.size() - 1);
-						queue.push(LingoEvent(event, eventId, kSpriteHandler, passThrough, pos, channelId, i));
+						queue.push(LingoEvent(event, eventId, kSpriteHandler, passThrough, pos, pointedSpriteId, i));
 					}
 
 					if (event == kEventBeginSprite || event == kEventEndSprite || event == kEventMouseUpOutSide) {


Commit: b97c82b200a8d2dd81fa9089d83cf5025ba73717
    https://github.com/scummvm/scummvm/commit/b97c82b200a8d2dd81fa9089d83cf5025ba73717
Author: Scott Percival (code at moral.net.au)
Date: 2025-10-03T01:00:34+02:00

Commit Message:
DIRECTOR: Defer changing the current window until the main loop

Changing the current window should only be done outside of Lingo
execution, or in specific context which support it like b_play.

Fixes sporadic crashes in Mean City.

Changed paths:
    engines/director/director.cpp
    engines/director/director.h
    engines/director/lingo/lingo-events.cpp


diff --git a/engines/director/director.cpp b/engines/director/director.cpp
index 34ef5c1717d..e028d276526 100644
--- a/engines/director/director.cpp
+++ b/engines/director/director.cpp
@@ -86,6 +86,7 @@ DirectorEngine::DirectorEngine(OSystem *syst, const DirectorGameDescription *gam
 	_mainArchive = nullptr;
 	_currentWindow = nullptr;
 	_cursorWindow = nullptr;
+	_windowToBeActive = nullptr;
 	_lingo = nullptr;
 	_clipBoard = nullptr;
 	_fixStageSize = false;
@@ -170,6 +171,10 @@ DirectorEngine::~DirectorEngine() {
 	for (auto &it : _windowList) {
 		it->decRefCount();
 	}
+	if (_windowToBeActive) {
+		_windowToBeActive->decRefCount();
+		_windowToBeActive = nullptr;
+	}
 	delete _lingo;
 	delete _wm;
 	delete _surface;
@@ -233,6 +238,18 @@ void DirectorEngine::setCurrentWindow(Window *window) {
 	_currentWindow->incRefCount();
 }
 
+void DirectorEngine::setWindowToBeActive(Window *window) {
+	if (_windowToBeActive == window)
+		return;
+	if (window)
+		window->incRefCount();
+	if (_windowToBeActive) {
+		_windowToBeActive->decRefCount();
+		_windowToBeActive = nullptr;
+	}
+	_windowToBeActive = window;
+}
+
 void DirectorEngine::setVersion(uint16 version) {
 	if (version == _version)
 		return;
@@ -362,6 +379,13 @@ Common::Error DirectorEngine::run() {
 		}
 
 		draw();
+
+		if (_windowToBeActive) {
+			setCurrentWindow(_windowToBeActive);
+			_windowToBeActive->decRefCount();
+			_windowToBeActive = nullptr;
+		}
+
 		while (!_windowsToForget.empty()) {
 			Window *window = _windowsToForget.back();
 			_windowsToForget.pop_back();
diff --git a/engines/director/director.h b/engines/director/director.h
index 120e68900df..1187b6d73c8 100644
--- a/engines/director/director.h
+++ b/engines/director/director.h
@@ -181,6 +181,7 @@ public:
 	Window *getOrCreateWindow(Common::String &name);
 	void forgetWindow(Window *window);
 	void setCurrentWindow(Window *window);
+	void setWindowToBeActive(Window *window);
 	Window *getCursorWindow() const { return _cursorWindow; }
 	void setCursorWindow(Window *window) { _cursorWindow = window; }
 	Movie *getCurrentMovie() const;
@@ -318,6 +319,7 @@ private:
 	Common::Array<Window *> _windowsToForget;
 	Window *_currentWindow;
 	Window *_cursorWindow;
+	Window *_windowToBeActive;
 
 	Graphics::MacPatterns _director3Patterns;
 	Graphics::MacPatterns _director3QuickDrawPatterns;
diff --git a/engines/director/lingo/lingo-events.cpp b/engines/director/lingo/lingo-events.cpp
index 3bb7cc2f42a..5354517988e 100644
--- a/engines/director/lingo/lingo-events.cpp
+++ b/engines/director/lingo/lingo-events.cpp
@@ -603,7 +603,7 @@ void Movie::queueInputEvent(LEvent event, int targetId, Common::Point pos) {
 void Movie::processEvent(LEvent event, int targetId) {
 	Common::Queue<LingoEvent> queue;
 	queueEvent(queue, event, targetId);
-	_vm->setCurrentWindow(this->getWindow());
+	_vm->setWindowToBeActive(this->getWindow());
 	_lingo->processEvents(queue, false);
 }
 


Commit: 9766d228c3f1a7a08951dfaa867b41ceb904513f
    https://github.com/scummvm/scummvm/commit/9766d228c3f1a7a08951dfaa867b41ceb904513f
Author: Scott Percival (code at moral.net.au)
Date: 2025-10-03T01:00:34+02:00

Commit Message:
DIRECTOR: Add guardrail for Movie::resolveScriptEvent

Changed paths:
    engines/director/lingo/lingo-events.cpp


diff --git a/engines/director/lingo/lingo-events.cpp b/engines/director/lingo/lingo-events.cpp
index 5354517988e..bc6874e757c 100644
--- a/engines/director/lingo/lingo-events.cpp
+++ b/engines/director/lingo/lingo-events.cpp
@@ -247,8 +247,12 @@ void Movie::resolveScriptEvent(LingoEvent &event) {
 
 				if (_vm->getVersion() >= 600) {
 					if (event.behaviorIndex >= 0) {
-						scriptId = sprite->_behaviors[event.behaviorIndex].memberID;
-						initializerParams = sprite->_behaviors[event.behaviorIndex].initializerParams;
+						if (event.behaviorIndex >= sprite->_behaviors.size()) {
+							warning("Movie::resolveScriptEvent: invalid behavior index %d, ignoring", event.behaviorIndex);
+						} else {
+							scriptId = sprite->_behaviors[event.behaviorIndex].memberID;
+							initializerParams = sprite->_behaviors[event.behaviorIndex].initializerParams;
+						}
 					} else {
 						_lastClickedSpriteId = 0;
 						return;




More information about the Scummvm-git-logs mailing list