/* Copyright 2019 Wolfgang Thaller. This file is part of Retro68. Retro68 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Retro68 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Retro68. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AppLauncher.h" #include "StatusDisplay.h" #include "AboutBox.h" #include "Preferences.h" #include #include #include #include #include "ConnectionProvider.h" #if !TARGET_API_MAC_CARBON #include "SerialConnectionProvider.h" #endif #if HAVE_MACTCP #include "TCPConnectionProvider.h" #endif #ifdef HAVE_OPENTRANSPORT #include "OpenTptConnectionProvider.h" #endif #include "SharedFileProvider.h" #include "SystemInfo.h" #include "CarbonFileCompat.h" #include enum { kMenuApple = 128, kMenuFile, kMenuEdit, kMenuConnection }; enum { kItemAbout = 1, kItemClose = 1, kItemQuit = 3, kItemChooseFolder = 14 }; #if TARGET_API_MAC_CARBON bool portsAvailable[] = { false, false, false, false, true }; #else #ifdef HAVE_OPENTRANSPORT bool portsAvailable[] = { true, true, false, false, true }; #else bool portsAvailable[] = { true, false, false, false, true }; #endif #endif bool hasIconUtils = true; bool hasColorQD = true; bool hasSys7StdFile = true; Prefs gPrefs; void WritePrefs() { short refNum; Create("\pLaunchAPPLServer Preferences", 0, 'R68L', 'LAPR'); if(OpenDF("\pLaunchAPPLServer Preferences", 0, &refNum) == noErr) { long count = sizeof(gPrefs); FSWrite(refNum, &count, &gPrefs); FSClose(refNum); } } void ReadPrefs() { short refNum; if(OpenDF("\pLaunchAPPLServer Preferences", 0, &refNum) == noErr) { long count = sizeof(gPrefs); gPrefs.version = -1; FSRead(refNum, &count, &gPrefs); if(gPrefs.version != Prefs::currentVersion) gPrefs = Prefs(); FSClose(refNum); } } bool gQuitting = false; WindowRef aboutWindow = nullptr; void ConnectionChanged(); #if TARGET_API_MAC_CARBON #define EnableItem EnableMenuItem #define DisableItem DisableMenuItem #endif void SetItemEnabled(MenuHandle m, short item, bool enabled) { if(enabled) EnableItem(m,item); else DisableItem(m,item); } void UpdateMenus() { MenuRef m = GetMenuHandle(kMenuFile); WindowRef w = FrontWindow(); m = GetMenuHandle(kMenuFile); if(w && (w == aboutWindow || GetWindowKind(w) < 0)) EnableItem(m,kItemClose); else DisableItem(m,kItemClose); m = GetMenuHandle(kMenuEdit); bool enableEditMenu = (w && GetWindowKind(w) < 0); // Desk accessory in front: Enable edit menu items // Application window or nothing in front, disable edit menu for(short i : {1,3,4,5,6}) SetItemEnabled(m,i,enableEditMenu); m = GetMenuHandle(kMenuConnection); SetItemEnabled(m, 1, portsAvailable[(int)Port::macTCP]); CheckMenuItem(m, 1, gPrefs.port == Port::macTCP); SetItemEnabled(m, 2, portsAvailable[(int)Port::openTptTCP]); CheckMenuItem(m, 2, gPrefs.port == Port::openTptTCP); SetItemEnabled(m, 3, portsAvailable[(int)Port::modemPort]); CheckMenuItem(m, 3, gPrefs.port == Port::modemPort); SetItemEnabled(m, 4, portsAvailable[(int)Port::printerPort]); CheckMenuItem(m, 4, gPrefs.port == Port::printerPort); SetItemEnabled(m, 5, portsAvailable[(int)Port::sharedFiles]); CheckMenuItem(m, 5, gPrefs.port == Port::sharedFiles); for(int i = 7; i < kItemChooseFolder; i++) { Str255 str; long baud; GetMenuItemText(m, i, str); StringToNum(str, &baud); CheckMenuItem(m, i, baud == gPrefs.baud); SetItemEnabled(m, i, gPrefs.port == Port::modemPort || gPrefs.port == Port::printerPort); } } void DoMenuCommand(long menuCommand) { Str255 str; WindowRef w; short menuID = menuCommand >> 16; short menuItem = menuCommand & 0xFFFF; if(menuID == kMenuApple) { if(menuItem == kItemAbout) AboutBox::ShowAboutBox(); #if !TARGET_API_MAC_CARBON else { GetMenuItemText(GetMenu(128), menuItem, str); OpenDeskAcc(str); } #endif } else if(menuID == kMenuFile) { switch(menuItem) { case kItemClose: w = FrontWindow(); if(w) { #if !TARGET_API_MAC_CARBON if(GetWindowKind(w) < 0) CloseDeskAcc(GetWindowKind(w)); else #endif if(w == aboutWindow) { DisposeWindow(w); aboutWindow = nullptr; } } break; case kItemQuit: gQuitting = true; break; } } else if(menuID == kMenuEdit) { #if !TARGET_API_MAC_CARBON if(!SystemEdit(menuItem - 1)) #endif { // edit command not handled by desk accessory } } else if(menuID == kMenuConnection) { switch(menuItem) { case 1: gPrefs.port = Port::macTCP; break; case 2: gPrefs.port = Port::openTptTCP; break; case 3: gPrefs.port = Port::modemPort; break; case 4: gPrefs.port = Port::printerPort; break; case 5: gPrefs.port = Port::sharedFiles; break; case kItemChooseFolder: ChooseSharedDirectory(); UnloadSeg((void*) &ChooseSharedDirectory); break; default: GetMenuItemText(GetMenuHandle(menuID), menuItem, str); StringToNum(str, &gPrefs.baud); } ConnectionChanged(); } HiliteMenu(0); } std::unique_ptr statusDisplay; std::unique_ptr connection; class LaunchServer : public StreamListener { uint32_t dataSize, rsrcSize; uint32_t remainingSize; short refNum; short outRefNum; long outSize, outSizeRemaining; std::unique_ptr appLauncher; int nullEventCounter = 0; enum class State { command, header, data, rsrc, launch, wait, respond }; State state = State::command; RemoteCommand command; bool upgrade = false; OSType type, creator; public: void onReset() { AppStatus readyState{ (int)AppStatus::readyModem + (int)gPrefs.port - (int)Port::modemPort }; statusDisplay->SetStatus(readyState, 0, 0); state = State::command; } size_t onReceive(const uint8_t* p, size_t n) { #ifdef DEBUG_CONSOLE printf("Received %d bytes in state %d.\n", (int)n, (int)state); #endif switch(state) { case State::command: { if(n < 1) return 0; command = (RemoteCommand)p[0]; if(command == RemoteCommand::launchApp || command == RemoteCommand::upgradeLauncher) state = State::header; return 1; } case State::header: { if(n < 16) return 0; type = *(const OSType*)(p+0); creator = *(const OSType*)(p+4); dataSize = *(const uint32_t*)(p+8); rsrcSize = *(const uint32_t*)(p+12); statusDisplay->SetStatus(command == RemoteCommand::upgradeLauncher ? AppStatus::upgrading : AppStatus::downloading, 0, dataSize + rsrcSize); FSDelete("\pRetro68App", 0); Create("\pRetro68App", 0, creator, type); OpenDF("\pRetro68App", 0, &refNum); FSDelete("\pout", 0); Create("\pout", 0, 'ttxt', 'TEXT'); if(dataSize) { state = State::data; remainingSize = dataSize; } else if(rsrcSize) { FSClose(refNum); OpenRF("\pRetro68App", 0, &refNum); state = State::rsrc; remainingSize = rsrcSize; } else state = State::launch; return 16; } case State::data: { long count = n < remainingSize ? n : remainingSize; FSWrite(refNum, &count, p); remainingSize -= count; statusDisplay->SetProgress(dataSize - remainingSize, dataSize + rsrcSize); if(remainingSize) return count; FSClose(refNum); OpenRF("\pRetro68App", 0, &refNum); if(rsrcSize) { state = State::rsrc; remainingSize = rsrcSize; } else state = State::launch; return count; } case State::rsrc: { long count = n < remainingSize ? n : remainingSize; FSWrite(refNum, &count, p); remainingSize -= count; statusDisplay->SetProgress(dataSize + rsrcSize - remainingSize, dataSize + rsrcSize); if(remainingSize) return count; FSClose(refNum); statusDisplay->SetStatus(AppStatus::running); state = State::launch; return count; } default: return 0; } } void idle() { ++nullEventCounter; if(connection) connection->idle(); if(state == State::launch) { if(command == RemoteCommand::upgradeLauncher) { Stream *stream = connection->getStream(); if(creator == 'R68L' && type == 'APPL') { uint32_t zero = 0; stream->write(&zero, 4); stream->write(&zero, 4); upgrade = true; } else { uint32_t error = 1; stream->write(&error, 4); } stream->flushWrite(); outRefNum = 0; outSizeRemaining = 0; outSize = 0; state = State::respond; } else { connection->suspend(); #if TARGET_CPU_68K if(void *seg = connection->segmentToUnload()) UnloadSeg(seg); #endif gPrefs.inSubLaunch = true; WritePrefs(); if(type == 'MPST') appLauncher = CreateToolLauncher(); else appLauncher = CreateAppLauncher(); bool launched = appLauncher->Launch("\pRetro68App"); gPrefs.inSubLaunch = false; WritePrefs(); if(launched) { state = State::wait; nullEventCounter = 0; statusDisplay->SetStatus(AppStatus::running, 0, 0); } else { #ifdef DEBUG_CONSOLE printf("Failed to Launch.\n"); #endif connection->resume(); onReset(); } } } else if(state == State::wait && nullEventCounter > 3) { if(!appLauncher->IsRunning("\pRetro68App")) { appLauncher.reset(); UnloadSeg((void*) &CreateAppLauncher); UnloadSeg((void*) &CreateToolLauncher); connection->resume(); StartResponding(); } } else if(state == State::respond) { Stream *stream = connection->getStream(); while(outSizeRemaining && stream->readyToWrite()) { char buf[1024]; long count = outSizeRemaining > 1024 ? 1024 : outSizeRemaining; FSRead(outRefNum, &count, buf); stream->write(buf, count); outSizeRemaining -= count; } statusDisplay->SetStatus(AppStatus::uploading, outSize - outSizeRemaining, outSize); if(outSizeRemaining == 0 && outRefNum) { FSClose(outRefNum); } if(outSizeRemaining == 0 && stream->allDataArrived()) { if(upgrade) { connection.reset(); FSDelete("\pLaunchAPPLServer.old", 0); Rename(LMGetCurApName(), 0, "\pLaunchAPPLServer.old"); Rename("\pRetro68App", 0, LMGetCurApName()); LaunchParamBlockRec lpb; memset(&lpb, 0, sizeof(lpb)); lpb.reserved1 = (unsigned long) LMGetCurApName(); lpb.reserved2 = 0; LaunchApplication(&lpb); ExitToShell(); } onReset(); } } } void StartResponding() { Stream *stream = connection->getStream(); state = State::respond; uint32_t zero = 0; stream->write(&zero, 4); OpenDF("\pout", 0, &outRefNum); GetEOF(outRefNum, &outSize); outSizeRemaining = outSize; statusDisplay->SetStatus(AppStatus::uploading, 0, outSize); stream->write(&outSize, 4); stream->flushWrite(); } }; LaunchServer server; void ConnectionChanged() { void *connectionSeg = connection ? connection->segmentToUnload() : nullptr; connection.reset(); // deallocate before we create the new provider if(connectionSeg) UnloadSeg(connectionSeg); bool first = true; for(;;) { using std::begin, std::end; if((int)gPrefs.port >= end(portsAvailable)-begin(portsAvailable)) { if(!first) return; first = false; gPrefs.port = (Port) 0; } if(portsAvailable[(int)gPrefs.port]) break; gPrefs.port = Port((int)gPrefs.port + 1); } switch(gPrefs.port) { #if !TARGET_API_MAC_CARBON #ifdef HAVE_MACTCP case Port::macTCP: connection = std::make_unique(statusDisplay.get()); break; #endif case Port::modemPort: connection = std::make_unique(0, gPrefs.baud, statusDisplay.get()); break; case Port::printerPort: connection = std::make_unique(1, gPrefs.baud, statusDisplay.get()); break; #endif #ifdef HAVE_OPENTRANSPORT case Port::openTptTCP: connection = std::make_unique(statusDisplay.get());; break; #endif case Port::sharedFiles: if(gPrefs.sharedDirectoryPath[0] == 0) { ChooseSharedDirectory(); UnloadSeg((void*) &ChooseSharedDirectory); } if(gPrefs.sharedDirectoryPath[0] != 0) connection = std::make_unique(statusDisplay.get());; break; default: ; } WritePrefs(); if(connection) { connection->setListener(&server); server.onReset(); } } pascal OSErr aeRun (const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) { return noErr; } pascal OSErr aeOpen (const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) { return noErr; } pascal OSErr aePrint (const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) { return noErr; } pascal OSErr aeQuit (const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) { gQuitting = true; return noErr; } int main() { #if !TARGET_API_MAC_CARBON // default stack size is 8KB on B&W macs // and 24 KB on Color macs. // 8KB is too little as soon as we allocate a buffer on the stack. // To allow that, increae stack size: SetApplLimit(GetApplLimit() - 8192); MaxApplZone(); InitGraf(&qd.thePort); InitFonts(); InitWindows(); InitMenus(); TEInit(); InitDialogs(NULL); #endif #if TARGET_CPU_68K && !TARGET_RT_MAC_CFM short& ROM85 = *(short*) 0x028E; Boolean is128KROM = (ROM85 > 0); Boolean hasWaitNextEvent = false; Boolean hasGestalt = false; Boolean hasAppleEvents = false; hasIconUtils = false; hasColorQD = false; if (is128KROM) { UniversalProcPtr trapUnimpl = GetToolTrapAddress(_Unimplemented); UniversalProcPtr trapWaitNextEvent = GetToolTrapAddress(_WaitNextEvent); UniversalProcPtr trapGestalt = GetOSTrapAddress(_Gestalt); hasWaitNextEvent = (trapWaitNextEvent != trapUnimpl); hasGestalt = (trapGestalt != trapUnimpl); if(hasGestalt) { long response = 0; OSErr err = Gestalt(gestaltAppleEventsAttr, &response); hasAppleEvents = err == noErr && response != 0; err = Gestalt(gestaltIconUtilitiesAttr, &response); hasIconUtils = err == noErr && response != 0; err = Gestalt(gestaltQuickdrawVersion, &response); hasColorQD = err == noErr && response != 0; err = Gestalt(gestaltStandardFileAttr, &response); hasSys7StdFile = err == noErr && (response & (1 << gestaltStandardFile58)) != 0; } } #else #if !TARGET_API_MAC_CARBON const Boolean hasWaitNextEvent = true; #endif const Boolean hasGestalt = true; const Boolean hasAppleEvents = true; #endif #if !TARGET_API_MAC_CARBON if(hasGestalt) { long resp; if(Gestalt('mtcp', &resp) == noErr) portsAvailable[(int)Port::macTCP] = true; if(Gestalt(gestaltSerialAttr, &resp) == noErr) { // gestaltPortADisabled and gestaltPortBDisabled are "recent" additions // (Universal Interfaces 3.4 only). Use the literal constants for compatibility // in case people are using older interfaces. portsAvailable[(int)Port::modemPort] = (resp & ((1 << gestaltHidePortA) | (1<< 5/*gestaltPortADisabled*/))) == 0; portsAvailable[(int)Port::printerPort] = (resp & ((1 << gestaltHidePortB) | (1<< 6/*gestaltPortBDisabled*/))) == 0; } } #endif if(hasGestalt) { long resp; if(Gestalt(gestaltOpenTpt, &resp) == noErr && resp) portsAvailable[(int)Port::openTptTCP] = true; } SetMenuBar(GetNewMBar(128)); AppendResMenu(GetMenu(128), 'DRVR'); DrawMenuBar(); InitCursor(); if(hasAppleEvents) { AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, NewAEEventHandlerUPP(&aeRun), 0, false); AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, NewAEEventHandlerUPP(&aeOpen), 0, false); AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments, NewAEEventHandlerUPP(&aePrint), 0, false); AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, NewAEEventHandlerUPP(&aeQuit), 0, false); } statusDisplay = std::make_unique(); ReadPrefs(); ConnectionChanged(); if(gPrefs.inSubLaunch) { gPrefs.inSubLaunch = false; server.StartResponding(); } while(!gQuitting) { EventRecord e; WindowRef win; Boolean hadEvent; #if !TARGET_API_MAC_CARBON if(!hasWaitNextEvent) { SystemTask(); hadEvent = GetNextEvent(everyEvent, &e); } else #endif { hadEvent = WaitNextEvent(everyEvent, &e, 1, NULL); } if(hadEvent) { switch(e.what) { case keyDown: if(e.modifiers & cmdKey) { UpdateMenus(); DoMenuCommand(MenuKey(e.message & charCodeMask)); } break; case mouseDown: switch(FindWindow(e.where, &win)) { case inGoAway: if(TrackGoAway(win, e.where)) { if(Window *winObject = reinterpret_cast(GetWRefCon(win))) { delete winObject; } } break; case inDrag: #if !TARGET_API_MAC_CARBON DragWindow(win, e.where, &qd.screenBits.bounds); #else DragWindow(win, e.where, nullptr); #endif break; case inMenuBar: UpdateMenus(); DoMenuCommand( MenuSelect(e.where) ); break; case inContent: SelectWindow(win); break; #if !TARGET_API_MAC_CARBON case inSysWindow: SystemClick(&e, win); break; #endif } break; case updateEvt: win = reinterpret_cast(e.message); if(Window *winObject = reinterpret_cast(GetWRefCon(win))) { winObject->Update(); } break; case kHighLevelEvent: if(hasAppleEvents) AEProcessAppleEvent(&e); break; } } server.idle(); statusDisplay->Idle(); } WritePrefs(); return 0; }