2018-04-23 19:32:43 +00:00
|
|
|
/*
|
2019-01-23 18:41:12 +00:00
|
|
|
Copyright 2019 Wolfgang Thaller.
|
2018-04-23 19:32:43 +00:00
|
|
|
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <Quickdraw.h>
|
|
|
|
#include <Windows.h>
|
|
|
|
#include <Menus.h>
|
|
|
|
#include <Fonts.h>
|
|
|
|
#include <Resources.h>
|
|
|
|
#include <TextEdit.h>
|
|
|
|
#include <TextUtils.h>
|
|
|
|
#include <Dialogs.h>
|
|
|
|
#include <Devices.h>
|
2018-05-01 23:47:33 +00:00
|
|
|
#include <Traps.h>
|
2018-05-05 21:45:59 +00:00
|
|
|
#include <LowMem.h>
|
2018-05-06 10:40:26 +00:00
|
|
|
#include <SegLoad.h>
|
|
|
|
#include <Gestalt.h>
|
2018-04-23 19:32:43 +00:00
|
|
|
|
2018-04-27 00:06:54 +00:00
|
|
|
#include "AppLauncher.h"
|
2018-05-06 16:49:43 +00:00
|
|
|
#include "StatusDisplay.h"
|
2018-05-19 12:55:27 +00:00
|
|
|
#include "AboutBox.h"
|
2019-01-23 18:41:12 +00:00
|
|
|
#include "Preferences.h"
|
2018-04-27 00:06:54 +00:00
|
|
|
|
2018-05-06 16:49:43 +00:00
|
|
|
#include <ServerProtocol.h>
|
2018-04-23 19:32:43 +00:00
|
|
|
#include <Processes.h>
|
|
|
|
#include <string.h>
|
2018-04-26 23:36:33 +00:00
|
|
|
#include <memory>
|
2018-04-23 19:32:43 +00:00
|
|
|
|
2018-05-08 07:43:02 +00:00
|
|
|
#include "ConnectionProvider.h"
|
2018-05-14 21:58:11 +00:00
|
|
|
#if !TARGET_API_MAC_CARBON
|
2018-05-08 07:43:02 +00:00
|
|
|
#include "SerialConnectionProvider.h"
|
2019-09-09 20:13:28 +00:00
|
|
|
#endif
|
|
|
|
#if HAVE_MACTCP
|
2018-05-10 05:59:35 +00:00
|
|
|
#include "TCPConnectionProvider.h"
|
2018-05-14 21:58:11 +00:00
|
|
|
#endif
|
2019-08-17 21:49:17 +00:00
|
|
|
#ifdef HAVE_OPENTRANSPORT
|
2018-05-16 00:04:18 +00:00
|
|
|
#include "OpenTptConnectionProvider.h"
|
2019-08-17 21:49:17 +00:00
|
|
|
#endif
|
2019-01-23 18:41:12 +00:00
|
|
|
#include "SharedFileProvider.h"
|
2018-05-16 00:04:18 +00:00
|
|
|
|
2018-05-19 12:55:27 +00:00
|
|
|
#include "SystemInfo.h"
|
2018-05-14 21:58:11 +00:00
|
|
|
|
|
|
|
#include "CarbonFileCompat.h"
|
|
|
|
|
2018-05-08 07:43:02 +00:00
|
|
|
#include <Stream.h>
|
2018-04-23 19:32:43 +00:00
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
kMenuApple = 128,
|
|
|
|
kMenuFile,
|
2018-04-24 20:06:32 +00:00
|
|
|
kMenuEdit,
|
2018-05-07 21:51:47 +00:00
|
|
|
kMenuConnection
|
2018-04-23 19:32:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
kItemAbout = 1,
|
|
|
|
|
2018-05-16 19:44:37 +00:00
|
|
|
kItemClose = 1,
|
2019-01-23 18:41:12 +00:00
|
|
|
kItemQuit = 3,
|
2018-04-23 19:32:43 +00:00
|
|
|
|
2019-01-23 18:41:12 +00:00
|
|
|
kItemChooseFolder = 14
|
2018-05-10 05:59:35 +00:00
|
|
|
};
|
2018-05-14 21:58:11 +00:00
|
|
|
|
|
|
|
#if TARGET_API_MAC_CARBON
|
2019-01-23 18:41:12 +00:00
|
|
|
bool portsAvailable[] = { false, false, false, false, true };
|
2018-05-14 21:58:11 +00:00
|
|
|
#else
|
2019-08-17 21:49:17 +00:00
|
|
|
#ifdef HAVE_OPENTRANSPORT
|
2019-01-23 18:41:12 +00:00
|
|
|
bool portsAvailable[] = { true, true, false, false, true };
|
2019-08-17 21:49:17 +00:00
|
|
|
#else
|
|
|
|
bool portsAvailable[] = { true, false, false, false, true };
|
|
|
|
#endif
|
2018-05-14 21:58:11 +00:00
|
|
|
#endif
|
2018-05-19 12:55:27 +00:00
|
|
|
bool hasIconUtils = true;
|
|
|
|
bool hasColorQD = true;
|
2019-01-23 18:41:12 +00:00
|
|
|
bool hasSys7StdFile = true;
|
2018-04-27 07:00:33 +00:00
|
|
|
|
|
|
|
Prefs gPrefs;
|
2018-05-08 00:15:05 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-27 07:00:33 +00:00
|
|
|
bool gQuitting = false;
|
2018-05-16 19:44:37 +00:00
|
|
|
WindowRef aboutWindow = nullptr;
|
2018-05-08 00:15:05 +00:00
|
|
|
void ConnectionChanged();
|
2018-04-23 19:32:43 +00:00
|
|
|
|
|
|
|
|
2018-05-16 19:44:37 +00:00
|
|
|
#if TARGET_API_MAC_CARBON
|
|
|
|
#define EnableItem EnableMenuItem
|
|
|
|
#define DisableItem DisableMenuItem
|
|
|
|
#endif
|
2018-05-10 05:59:35 +00:00
|
|
|
void SetItemEnabled(MenuHandle m, short item, bool enabled)
|
|
|
|
{
|
|
|
|
if(enabled)
|
|
|
|
EnableItem(m,item);
|
|
|
|
else
|
|
|
|
DisableItem(m,item);
|
|
|
|
}
|
|
|
|
|
2018-04-23 19:32:43 +00:00
|
|
|
void UpdateMenus()
|
|
|
|
{
|
2018-05-14 21:58:11 +00:00
|
|
|
MenuRef m = GetMenuHandle(kMenuFile);
|
2018-04-23 19:32:43 +00:00
|
|
|
WindowRef w = FrontWindow();
|
|
|
|
|
2018-05-16 19:44:37 +00:00
|
|
|
m = GetMenuHandle(kMenuFile);
|
|
|
|
if(w && (w == aboutWindow || GetWindowKind(w) < 0))
|
|
|
|
EnableItem(m,kItemClose);
|
|
|
|
else
|
|
|
|
DisableItem(m,kItemClose);
|
2018-04-23 19:32:43 +00:00
|
|
|
|
2018-05-14 21:58:11 +00:00
|
|
|
m = GetMenuHandle(kMenuEdit);
|
2018-05-10 05:59:35 +00:00
|
|
|
|
|
|
|
bool enableEditMenu = (w && GetWindowKind(w) < 0);
|
2018-04-23 19:32:43 +00:00
|
|
|
// Desk accessory in front: Enable edit menu items
|
|
|
|
// Application window or nothing in front, disable edit menu
|
2018-05-10 05:59:35 +00:00
|
|
|
|
|
|
|
for(short i : {1,3,4,5,6})
|
|
|
|
SetItemEnabled(m,i,enableEditMenu);
|
2018-04-24 20:06:32 +00:00
|
|
|
|
2018-05-14 21:58:11 +00:00
|
|
|
m = GetMenuHandle(kMenuConnection);
|
2018-05-10 05:59:35 +00:00
|
|
|
SetItemEnabled(m, 1, portsAvailable[(int)Port::macTCP]);
|
|
|
|
CheckMenuItem(m, 1, gPrefs.port == Port::macTCP);
|
2018-05-16 00:04:18 +00:00
|
|
|
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);
|
2019-01-23 18:41:12 +00:00
|
|
|
SetItemEnabled(m, 5, portsAvailable[(int)Port::sharedFiles]);
|
|
|
|
CheckMenuItem(m, 5, gPrefs.port == Port::sharedFiles);
|
|
|
|
for(int i = 7; i < kItemChooseFolder; i++)
|
2018-04-24 20:06:32 +00:00
|
|
|
{
|
|
|
|
Str255 str;
|
|
|
|
long baud;
|
|
|
|
GetMenuItemText(m, i, str);
|
|
|
|
StringToNum(str, &baud);
|
2018-04-27 07:00:33 +00:00
|
|
|
CheckMenuItem(m, i, baud == gPrefs.baud);
|
2018-05-16 19:44:37 +00:00
|
|
|
SetItemEnabled(m, i, gPrefs.port == Port::modemPort || gPrefs.port == Port::printerPort);
|
2018-04-24 20:06:32 +00:00
|
|
|
}
|
2018-04-23 19:32:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DoMenuCommand(long menuCommand)
|
|
|
|
{
|
|
|
|
Str255 str;
|
|
|
|
WindowRef w;
|
|
|
|
short menuID = menuCommand >> 16;
|
|
|
|
short menuItem = menuCommand & 0xFFFF;
|
|
|
|
if(menuID == kMenuApple)
|
|
|
|
{
|
|
|
|
if(menuItem == kItemAbout)
|
2018-05-19 12:55:27 +00:00
|
|
|
AboutBox::ShowAboutBox();
|
2018-04-23 19:32:43 +00:00
|
|
|
#if !TARGET_API_MAC_CARBON
|
|
|
|
else
|
|
|
|
{
|
|
|
|
GetMenuItemText(GetMenu(128), menuItem, str);
|
|
|
|
OpenDeskAcc(str);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else if(menuID == kMenuFile)
|
|
|
|
{
|
|
|
|
switch(menuItem)
|
|
|
|
{
|
2018-05-16 19:44:37 +00:00
|
|
|
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;
|
|
|
|
|
2018-04-23 19:32:43 +00:00
|
|
|
case kItemQuit:
|
2018-04-27 07:00:33 +00:00
|
|
|
gQuitting = true;
|
2018-04-23 19:32:43 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(menuID == kMenuEdit)
|
|
|
|
{
|
|
|
|
#if !TARGET_API_MAC_CARBON
|
|
|
|
if(!SystemEdit(menuItem - 1))
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
// edit command not handled by desk accessory
|
|
|
|
}
|
|
|
|
}
|
2018-05-07 21:51:47 +00:00
|
|
|
else if(menuID == kMenuConnection)
|
2018-04-24 20:06:32 +00:00
|
|
|
{
|
2018-05-10 05:59:35 +00:00
|
|
|
switch(menuItem)
|
2018-05-07 21:51:47 +00:00
|
|
|
{
|
2018-05-10 05:59:35 +00:00
|
|
|
case 1:
|
|
|
|
gPrefs.port = Port::macTCP;
|
|
|
|
break;
|
|
|
|
case 2:
|
2018-05-16 00:04:18 +00:00
|
|
|
gPrefs.port = Port::openTptTCP;
|
2018-05-10 05:59:35 +00:00
|
|
|
break;
|
|
|
|
case 3:
|
2018-05-16 00:04:18 +00:00
|
|
|
gPrefs.port = Port::modemPort;
|
|
|
|
break;
|
|
|
|
case 4:
|
2018-05-10 05:59:35 +00:00
|
|
|
gPrefs.port = Port::printerPort;
|
|
|
|
break;
|
2019-01-23 18:41:12 +00:00
|
|
|
case 5:
|
|
|
|
gPrefs.port = Port::sharedFiles;
|
|
|
|
break;
|
|
|
|
case kItemChooseFolder:
|
|
|
|
ChooseSharedDirectory();
|
|
|
|
UnloadSeg((void*) &ChooseSharedDirectory);
|
|
|
|
break;
|
2018-05-10 05:59:35 +00:00
|
|
|
default:
|
2018-05-14 21:58:11 +00:00
|
|
|
GetMenuItemText(GetMenuHandle(menuID), menuItem, str);
|
2018-05-10 05:59:35 +00:00
|
|
|
StringToNum(str, &gPrefs.baud);
|
2018-05-07 21:51:47 +00:00
|
|
|
}
|
2018-05-08 00:15:05 +00:00
|
|
|
ConnectionChanged();
|
2018-04-24 20:06:32 +00:00
|
|
|
}
|
2018-04-23 19:32:43 +00:00
|
|
|
HiliteMenu(0);
|
|
|
|
}
|
|
|
|
|
2018-05-06 16:49:43 +00:00
|
|
|
std::unique_ptr<StatusDisplay> statusDisplay;
|
2018-04-23 19:32:43 +00:00
|
|
|
|
2018-05-08 00:15:05 +00:00
|
|
|
std::unique_ptr<ConnectionProvider> connection;
|
|
|
|
|
|
|
|
class LaunchServer : public StreamListener
|
|
|
|
{
|
|
|
|
uint32_t dataSize, rsrcSize;
|
|
|
|
uint32_t remainingSize;
|
|
|
|
short refNum;
|
|
|
|
short outRefNum;
|
|
|
|
long outSize, outSizeRemaining;
|
|
|
|
std::unique_ptr<AppLauncher> appLauncher;
|
|
|
|
int nullEventCounter = 0;
|
2018-04-23 23:42:36 +00:00
|
|
|
|
2018-04-23 19:32:43 +00:00
|
|
|
enum class State
|
|
|
|
{
|
2018-05-05 16:53:27 +00:00
|
|
|
command,
|
|
|
|
header,
|
2018-04-23 19:32:43 +00:00
|
|
|
data,
|
|
|
|
rsrc,
|
|
|
|
launch,
|
2018-04-23 23:42:36 +00:00
|
|
|
wait,
|
|
|
|
respond
|
2018-04-23 19:32:43 +00:00
|
|
|
};
|
2018-05-05 16:53:27 +00:00
|
|
|
State state = State::command;
|
|
|
|
RemoteCommand command;
|
2018-05-16 00:44:23 +00:00
|
|
|
bool upgrade = false;
|
2018-04-23 19:32:43 +00:00
|
|
|
|
2018-05-05 22:56:48 +00:00
|
|
|
OSType type, creator;
|
|
|
|
|
2018-05-08 00:15:05 +00:00
|
|
|
public:
|
|
|
|
|
2018-04-23 23:42:36 +00:00
|
|
|
void onReset()
|
|
|
|
{
|
2019-01-08 23:15:44 +00:00
|
|
|
AppStatus readyState{ (int)AppStatus::readyModem + (int)gPrefs.port - (int)Port::modemPort };
|
|
|
|
statusDisplay->SetStatus(readyState, 0, 0);
|
2018-05-05 16:53:27 +00:00
|
|
|
state = State::command;
|
2018-04-23 23:42:36 +00:00
|
|
|
}
|
2018-04-23 19:32:43 +00:00
|
|
|
|
|
|
|
size_t onReceive(const uint8_t* p, size_t n)
|
|
|
|
{
|
2019-01-08 23:31:32 +00:00
|
|
|
#ifdef DEBUG_CONSOLE
|
2019-01-23 18:41:12 +00:00
|
|
|
printf("Received %d bytes in state %d.\n", (int)n, (int)state);
|
2019-01-08 23:31:32 +00:00
|
|
|
#endif
|
2018-04-23 19:32:43 +00:00
|
|
|
switch(state)
|
|
|
|
{
|
2018-05-05 16:53:27 +00:00
|
|
|
case State::command:
|
2018-04-23 19:32:43 +00:00
|
|
|
{
|
2018-05-05 16:53:27 +00:00
|
|
|
if(n < 1)
|
2018-04-23 19:32:43 +00:00
|
|
|
return 0;
|
2018-05-05 16:53:27 +00:00
|
|
|
command = (RemoteCommand)p[0];
|
2018-05-05 21:45:59 +00:00
|
|
|
if(command == RemoteCommand::launchApp || command == RemoteCommand::upgradeLauncher)
|
2018-05-05 16:53:27 +00:00
|
|
|
state = State::header;
|
2019-01-08 23:31:32 +00:00
|
|
|
|
2018-05-05 16:53:27 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
case State::header:
|
|
|
|
{
|
|
|
|
if(n < 16)
|
|
|
|
return 0;
|
2018-05-05 22:56:48 +00:00
|
|
|
type = *(const OSType*)(p+0);
|
|
|
|
creator = *(const OSType*)(p+4);
|
2018-05-05 16:53:27 +00:00
|
|
|
dataSize = *(const uint32_t*)(p+8);
|
|
|
|
rsrcSize = *(const uint32_t*)(p+12);
|
2018-04-23 19:32:43 +00:00
|
|
|
|
2018-05-08 00:37:25 +00:00
|
|
|
statusDisplay->SetStatus(command == RemoteCommand::upgradeLauncher ?
|
|
|
|
AppStatus::upgrading : AppStatus::downloading,
|
|
|
|
0, dataSize + rsrcSize);
|
2018-04-23 19:32:43 +00:00
|
|
|
|
|
|
|
FSDelete("\pRetro68App", 0);
|
2018-05-05 16:53:27 +00:00
|
|
|
Create("\pRetro68App", 0, creator, type);
|
2018-04-23 19:32:43 +00:00
|
|
|
OpenDF("\pRetro68App", 0, &refNum);
|
2018-04-24 05:57:43 +00:00
|
|
|
FSDelete("\pout", 0);
|
|
|
|
Create("\pout", 0, 'ttxt', 'TEXT');
|
|
|
|
|
2019-01-08 23:18:44 +00:00
|
|
|
if(dataSize)
|
|
|
|
{
|
2019-01-08 23:31:32 +00:00
|
|
|
state = State::data;
|
|
|
|
remainingSize = dataSize;
|
2019-01-08 23:18:44 +00:00
|
|
|
}
|
|
|
|
else if(rsrcSize)
|
|
|
|
{
|
|
|
|
FSClose(refNum);
|
|
|
|
OpenRF("\pRetro68App", 0, &refNum);
|
|
|
|
state = State::rsrc;
|
|
|
|
remainingSize = rsrcSize;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
state = State::launch;
|
|
|
|
|
2018-05-05 16:53:27 +00:00
|
|
|
return 16;
|
2018-04-23 19:32:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case State::data:
|
|
|
|
{
|
|
|
|
long count = n < remainingSize ? n : remainingSize;
|
|
|
|
|
|
|
|
FSWrite(refNum, &count, p);
|
|
|
|
remainingSize -= count;
|
|
|
|
|
2018-05-08 00:37:25 +00:00
|
|
|
statusDisplay->SetProgress(dataSize - remainingSize, dataSize + rsrcSize);
|
2018-04-23 19:32:43 +00:00
|
|
|
|
|
|
|
if(remainingSize)
|
|
|
|
return count;
|
|
|
|
|
|
|
|
FSClose(refNum);
|
|
|
|
OpenRF("\pRetro68App", 0, &refNum);
|
2019-01-08 23:18:44 +00:00
|
|
|
if(rsrcSize)
|
|
|
|
{
|
2019-01-08 23:31:32 +00:00
|
|
|
state = State::rsrc;
|
|
|
|
remainingSize = rsrcSize;
|
2019-01-08 23:18:44 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
state = State::launch;
|
2018-04-23 19:32:43 +00:00
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
case State::rsrc:
|
|
|
|
{
|
|
|
|
long count = n < remainingSize ? n : remainingSize;
|
|
|
|
|
|
|
|
FSWrite(refNum, &count, p);
|
|
|
|
remainingSize -= count;
|
|
|
|
|
2018-05-08 00:37:25 +00:00
|
|
|
statusDisplay->SetProgress(dataSize + rsrcSize - remainingSize, dataSize + rsrcSize);
|
2018-04-23 19:32:43 +00:00
|
|
|
|
|
|
|
if(remainingSize)
|
|
|
|
return count;
|
|
|
|
|
|
|
|
FSClose(refNum);
|
|
|
|
|
2018-05-06 16:49:43 +00:00
|
|
|
statusDisplay->SetStatus(AppStatus::running);
|
2018-04-23 19:32:43 +00:00
|
|
|
|
|
|
|
state = State::launch;
|
|
|
|
return count;
|
|
|
|
}
|
2019-01-04 02:35:32 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
return 0;
|
2018-04-23 19:32:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-08 00:15:05 +00:00
|
|
|
void idle()
|
|
|
|
{
|
|
|
|
++nullEventCounter;
|
2018-05-14 21:58:11 +00:00
|
|
|
if(connection)
|
|
|
|
connection->idle();
|
2018-04-24 20:06:32 +00:00
|
|
|
|
2018-05-08 00:15:05 +00:00
|
|
|
if(state == State::launch)
|
|
|
|
{
|
2018-04-24 05:57:43 +00:00
|
|
|
|
2018-05-08 00:15:05 +00:00
|
|
|
if(command == RemoteCommand::upgradeLauncher)
|
|
|
|
{
|
2018-05-16 00:44:23 +00:00
|
|
|
Stream *stream = connection->getStream();
|
2018-05-08 00:15:05 +00:00
|
|
|
if(creator == 'R68L' && type == 'APPL')
|
|
|
|
{
|
2018-05-16 00:44:23 +00:00
|
|
|
uint32_t zero = 0;
|
|
|
|
stream->write(&zero, 4);
|
|
|
|
stream->write(&zero, 4);
|
2018-04-26 23:36:33 +00:00
|
|
|
|
2018-05-16 00:44:23 +00:00
|
|
|
upgrade = true;
|
2018-05-08 00:15:05 +00:00
|
|
|
}
|
2018-05-16 00:44:23 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
uint32_t error = 1;
|
|
|
|
stream->write(&error, 4);
|
|
|
|
}
|
|
|
|
stream->flushWrite();
|
|
|
|
outRefNum = 0;
|
|
|
|
outSizeRemaining = 0;
|
|
|
|
outSize = 0;
|
|
|
|
state = State::respond;
|
2018-05-08 00:15:05 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-05-16 00:44:23 +00:00
|
|
|
connection->suspend();
|
2019-01-04 02:35:32 +00:00
|
|
|
#if TARGET_CPU_68K
|
2018-05-16 00:44:23 +00:00
|
|
|
if(void *seg = connection->segmentToUnload())
|
|
|
|
UnloadSeg(seg);
|
2019-01-04 02:35:32 +00:00
|
|
|
#endif
|
2018-05-16 00:44:23 +00:00
|
|
|
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;
|
2018-05-08 00:15:05 +00:00
|
|
|
|
2018-05-16 00:44:23 +00:00
|
|
|
statusDisplay->SetStatus(AppStatus::running, 0, 0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-01-08 23:31:32 +00:00
|
|
|
#ifdef DEBUG_CONSOLE
|
|
|
|
printf("Failed to Launch.\n");
|
|
|
|
#endif
|
2018-05-16 00:44:23 +00:00
|
|
|
connection->resume();
|
|
|
|
onReset();
|
|
|
|
}
|
2018-05-08 00:15:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
|
2018-05-16 00:44:23 +00:00
|
|
|
if(outSizeRemaining == 0 && outRefNum)
|
2018-05-08 00:15:05 +00:00
|
|
|
{
|
|
|
|
FSClose(outRefNum);
|
|
|
|
}
|
|
|
|
if(outSizeRemaining == 0 && stream->allDataArrived())
|
|
|
|
{
|
2018-05-16 00:44:23 +00:00
|
|
|
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;
|
2019-01-04 02:35:32 +00:00
|
|
|
LaunchApplication(&lpb);
|
2018-05-16 00:44:23 +00:00
|
|
|
ExitToShell();
|
|
|
|
}
|
2018-05-08 00:37:25 +00:00
|
|
|
onReset();
|
2018-05-08 00:15:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void StartResponding()
|
2018-04-27 07:00:33 +00:00
|
|
|
{
|
2018-05-08 00:15:05 +00:00
|
|
|
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();
|
2018-04-27 07:00:33 +00:00
|
|
|
}
|
2018-05-08 00:15:05 +00:00
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
LaunchServer server;
|
|
|
|
|
2018-05-10 05:59:35 +00:00
|
|
|
|
|
|
|
|
2018-05-08 00:15:05 +00:00
|
|
|
void ConnectionChanged()
|
|
|
|
{
|
2019-01-23 18:41:12 +00:00
|
|
|
void *connectionSeg = connection ? connection->segmentToUnload() : nullptr;
|
|
|
|
|
2018-05-16 00:04:18 +00:00
|
|
|
connection.reset(); // deallocate before we create the new provider
|
2018-05-16 19:44:37 +00:00
|
|
|
|
2019-01-23 18:41:12 +00:00
|
|
|
if(connectionSeg)
|
|
|
|
UnloadSeg(connectionSeg);
|
|
|
|
|
2018-05-16 19:44:37 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2018-05-10 05:59:35 +00:00
|
|
|
switch(gPrefs.port)
|
|
|
|
{
|
2018-05-14 21:58:11 +00:00
|
|
|
#if !TARGET_API_MAC_CARBON
|
2019-09-09 20:13:28 +00:00
|
|
|
#ifdef HAVE_MACTCP
|
2018-05-10 05:59:35 +00:00
|
|
|
case Port::macTCP:
|
2018-05-16 19:44:37 +00:00
|
|
|
connection = std::make_unique<TCPConnectionProvider>(statusDisplay.get());
|
2018-05-10 05:59:35 +00:00
|
|
|
break;
|
2019-09-09 20:13:28 +00:00
|
|
|
#endif
|
2018-05-10 05:59:35 +00:00
|
|
|
case Port::modemPort:
|
|
|
|
connection = std::make_unique<SerialConnectionProvider>(0, gPrefs.baud, statusDisplay.get());
|
|
|
|
break;
|
|
|
|
case Port::printerPort:
|
2019-10-02 22:26:35 +00:00
|
|
|
connection = std::make_unique<SerialConnectionProvider>(1, gPrefs.baud, statusDisplay.get());
|
2018-05-10 05:59:35 +00:00
|
|
|
break;
|
2018-05-16 00:04:18 +00:00
|
|
|
#endif
|
2019-08-17 21:49:17 +00:00
|
|
|
#ifdef HAVE_OPENTRANSPORT
|
2018-05-16 00:04:18 +00:00
|
|
|
case Port::openTptTCP:
|
|
|
|
connection = std::make_unique<OpenTptConnectionProvider>(statusDisplay.get());;
|
|
|
|
break;
|
2019-08-17 21:49:17 +00:00
|
|
|
#endif
|
2019-01-23 18:41:12 +00:00
|
|
|
case Port::sharedFiles:
|
|
|
|
if(gPrefs.sharedDirectoryPath[0] == 0)
|
|
|
|
{
|
|
|
|
ChooseSharedDirectory();
|
|
|
|
UnloadSeg((void*) &ChooseSharedDirectory);
|
|
|
|
}
|
|
|
|
if(gPrefs.sharedDirectoryPath[0] != 0)
|
|
|
|
connection = std::make_unique<SharedFileProvider>(statusDisplay.get());;
|
|
|
|
break;
|
2019-01-04 02:35:32 +00:00
|
|
|
default:
|
|
|
|
;
|
2018-05-10 05:59:35 +00:00
|
|
|
}
|
2019-01-24 17:15:47 +00:00
|
|
|
WritePrefs();
|
2018-05-14 21:58:11 +00:00
|
|
|
if(connection)
|
|
|
|
{
|
|
|
|
connection->setListener(&server);
|
|
|
|
server.onReset();
|
|
|
|
}
|
2018-04-27 07:00:33 +00:00
|
|
|
}
|
2018-04-26 23:36:33 +00:00
|
|
|
|
2018-05-05 21:59:38 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-04-23 19:32:43 +00:00
|
|
|
int main()
|
|
|
|
{
|
2018-05-14 21:58:11 +00:00
|
|
|
#if !TARGET_API_MAC_CARBON
|
2018-05-01 19:17:39 +00:00
|
|
|
// default stack size is 8KB on B&W macs
|
|
|
|
// and 24 KB on Color macs.
|
2018-05-08 00:15:05 +00:00
|
|
|
// 8KB is too little as soon as we allocate a buffer on the stack.
|
|
|
|
// To allow that, increae stack size: SetApplLimit(GetApplLimit() - 8192);
|
2018-05-01 19:17:39 +00:00
|
|
|
MaxApplZone();
|
2018-05-14 21:58:11 +00:00
|
|
|
|
2018-04-23 19:32:43 +00:00
|
|
|
InitGraf(&qd.thePort);
|
|
|
|
InitFonts();
|
|
|
|
InitWindows();
|
|
|
|
InitMenus();
|
|
|
|
TEInit();
|
|
|
|
InitDialogs(NULL);
|
2018-05-05 21:59:38 +00:00
|
|
|
#endif
|
2018-05-05 22:56:48 +00:00
|
|
|
|
2018-05-05 21:59:38 +00:00
|
|
|
#if TARGET_CPU_68K && !TARGET_RT_MAC_CFM
|
2018-05-01 23:47:33 +00:00
|
|
|
short& ROM85 = *(short*) 0x028E;
|
2019-08-18 11:21:00 +00:00
|
|
|
Boolean is128KROM = (ROM85 > 0);
|
|
|
|
Boolean hasWaitNextEvent = false;
|
2018-05-06 10:40:26 +00:00
|
|
|
Boolean hasGestalt = false;
|
2018-05-05 21:59:38 +00:00
|
|
|
Boolean hasAppleEvents = false;
|
2018-05-16 22:38:55 +00:00
|
|
|
hasIconUtils = false;
|
|
|
|
hasColorQD = false;
|
|
|
|
|
2019-08-18 11:21:00 +00:00
|
|
|
if (is128KROM)
|
|
|
|
{
|
|
|
|
UniversalProcPtr trapUnimpl = GetToolTrapAddress(_Unimplemented);
|
2018-05-01 23:47:33 +00:00
|
|
|
UniversalProcPtr trapWaitNextEvent = GetToolTrapAddress(_WaitNextEvent);
|
2018-05-06 10:40:26 +00:00
|
|
|
UniversalProcPtr trapGestalt = GetOSTrapAddress(_Gestalt);
|
2018-05-01 23:47:33 +00:00
|
|
|
|
2019-08-18 11:21:00 +00:00
|
|
|
hasWaitNextEvent = (trapWaitNextEvent != trapUnimpl);
|
2018-05-06 10:40:26 +00:00
|
|
|
hasGestalt = (trapGestalt != trapUnimpl);
|
|
|
|
|
|
|
|
if(hasGestalt)
|
|
|
|
{
|
|
|
|
long response = 0;
|
|
|
|
OSErr err = Gestalt(gestaltAppleEventsAttr, &response);
|
|
|
|
hasAppleEvents = err == noErr && response != 0;
|
2018-05-16 22:38:55 +00:00
|
|
|
|
|
|
|
err = Gestalt(gestaltIconUtilitiesAttr, &response);
|
|
|
|
hasIconUtils = err == noErr && response != 0;
|
|
|
|
|
|
|
|
err = Gestalt(gestaltQuickdrawVersion, &response);
|
|
|
|
hasColorQD = err == noErr && response != 0;
|
2019-01-23 18:41:12 +00:00
|
|
|
|
|
|
|
err = Gestalt(gestaltStandardFileAttr, &response);
|
|
|
|
hasSys7StdFile = err == noErr && (response & (1 << gestaltStandardFile58)) != 0;
|
2018-05-06 10:40:26 +00:00
|
|
|
}
|
2019-08-18 11:21:00 +00:00
|
|
|
}
|
2018-05-05 21:59:38 +00:00
|
|
|
#else
|
2019-01-04 02:35:32 +00:00
|
|
|
#if !TARGET_API_MAC_CARBON
|
2019-08-18 11:21:00 +00:00
|
|
|
const Boolean hasWaitNextEvent = true;
|
2019-01-04 02:35:32 +00:00
|
|
|
#endif
|
2018-05-06 10:40:26 +00:00
|
|
|
const Boolean hasGestalt = true;
|
2018-05-05 21:59:38 +00:00
|
|
|
const Boolean hasAppleEvents = true;
|
2018-05-01 23:47:33 +00:00
|
|
|
#endif
|
2018-05-14 21:58:11 +00:00
|
|
|
|
|
|
|
#if !TARGET_API_MAC_CARBON
|
2018-05-10 05:59:35 +00:00
|
|
|
if(hasGestalt)
|
|
|
|
{
|
|
|
|
long resp;
|
|
|
|
if(Gestalt('mtcp', &resp) == noErr)
|
|
|
|
portsAvailable[(int)Port::macTCP] = true;
|
|
|
|
if(Gestalt(gestaltSerialAttr, &resp) == noErr)
|
|
|
|
{
|
2019-01-25 19:56:29 +00:00
|
|
|
// gestaltPortADisabled and gestaltPortBDisabled are "recent" additions
|
|
|
|
// (Universal Interfaces 3.4 only). Use the literal constants for compatibility
|
|
|
|
// in case people are using older interfaces.
|
2018-05-10 05:59:35 +00:00
|
|
|
portsAvailable[(int)Port::modemPort] =
|
2019-01-25 19:56:29 +00:00
|
|
|
(resp & ((1 << gestaltHidePortA) | (1<< 5/*gestaltPortADisabled*/))) == 0;
|
2018-05-10 05:59:35 +00:00
|
|
|
portsAvailable[(int)Port::printerPort] =
|
2019-01-25 19:56:29 +00:00
|
|
|
(resp & ((1 << gestaltHidePortB) | (1<< 6/*gestaltPortBDisabled*/))) == 0;
|
2018-05-10 05:59:35 +00:00
|
|
|
}
|
|
|
|
}
|
2018-05-14 21:58:11 +00:00
|
|
|
#endif
|
2018-05-16 00:43:59 +00:00
|
|
|
if(hasGestalt)
|
|
|
|
{
|
|
|
|
long resp;
|
|
|
|
if(Gestalt(gestaltOpenTpt, &resp) == noErr && resp)
|
|
|
|
portsAvailable[(int)Port::openTptTCP] = true;
|
|
|
|
}
|
2018-05-01 23:47:33 +00:00
|
|
|
|
2018-04-23 19:32:43 +00:00
|
|
|
SetMenuBar(GetNewMBar(128));
|
|
|
|
AppendResMenu(GetMenu(128), 'DRVR');
|
|
|
|
DrawMenuBar();
|
|
|
|
|
|
|
|
InitCursor();
|
|
|
|
|
2018-05-05 21:59:38 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2018-05-06 16:49:43 +00:00
|
|
|
statusDisplay = std::make_unique<StatusDisplay>();
|
|
|
|
|
2018-05-08 00:15:05 +00:00
|
|
|
ReadPrefs();
|
|
|
|
ConnectionChanged();
|
2018-04-23 19:32:43 +00:00
|
|
|
|
2018-04-27 07:00:33 +00:00
|
|
|
if(gPrefs.inSubLaunch)
|
|
|
|
{
|
|
|
|
gPrefs.inSubLaunch = false;
|
2018-05-08 00:15:05 +00:00
|
|
|
server.StartResponding();
|
2018-04-27 07:00:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
while(!gQuitting)
|
2018-04-23 19:32:43 +00:00
|
|
|
{
|
|
|
|
EventRecord e;
|
|
|
|
WindowRef win;
|
2018-05-01 23:47:33 +00:00
|
|
|
Boolean hadEvent;
|
|
|
|
|
|
|
|
#if !TARGET_API_MAC_CARBON
|
|
|
|
if(!hasWaitNextEvent)
|
|
|
|
{
|
|
|
|
SystemTask();
|
|
|
|
hadEvent = GetNextEvent(everyEvent, &e);
|
|
|
|
}
|
|
|
|
else
|
2018-04-23 19:32:43 +00:00
|
|
|
#endif
|
2018-05-01 23:47:33 +00:00
|
|
|
{
|
2018-05-07 21:51:47 +00:00
|
|
|
hadEvent = WaitNextEvent(everyEvent, &e, 1, NULL);
|
2018-05-01 23:47:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if(hadEvent)
|
2018-04-23 19:32:43 +00:00
|
|
|
{
|
|
|
|
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))
|
2018-05-16 19:44:37 +00:00
|
|
|
{
|
2018-05-19 12:55:27 +00:00
|
|
|
if(Window *winObject = reinterpret_cast<Window*>(GetWRefCon(win)))
|
2018-05-16 19:44:37 +00:00
|
|
|
{
|
2018-05-19 12:55:27 +00:00
|
|
|
delete winObject;
|
2018-05-16 19:44:37 +00:00
|
|
|
}
|
|
|
|
}
|
2018-04-23 19:32:43 +00:00
|
|
|
break;
|
|
|
|
case inDrag:
|
2018-05-14 21:58:11 +00:00
|
|
|
#if !TARGET_API_MAC_CARBON
|
2018-04-23 19:32:43 +00:00
|
|
|
DragWindow(win, e.where, &qd.screenBits.bounds);
|
2018-05-14 21:58:11 +00:00
|
|
|
#else
|
|
|
|
DragWindow(win, e.where, nullptr);
|
|
|
|
#endif
|
2018-04-23 19:32:43 +00:00
|
|
|
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:
|
2018-05-19 12:55:27 +00:00
|
|
|
win = reinterpret_cast<WindowRef>(e.message);
|
|
|
|
if(Window *winObject = reinterpret_cast<Window*>(GetWRefCon(win)))
|
|
|
|
{
|
|
|
|
winObject->Update();
|
|
|
|
}
|
2018-04-23 19:32:43 +00:00
|
|
|
break;
|
2018-05-05 21:59:38 +00:00
|
|
|
case kHighLevelEvent:
|
|
|
|
if(hasAppleEvents)
|
|
|
|
AEProcessAppleEvent(&e);
|
|
|
|
break;
|
2018-04-23 19:32:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-08 00:15:05 +00:00
|
|
|
server.idle();
|
2018-05-06 16:49:43 +00:00
|
|
|
statusDisplay->Idle();
|
2018-04-23 19:32:43 +00:00
|
|
|
}
|
2018-04-27 07:00:33 +00:00
|
|
|
|
|
|
|
WritePrefs();
|
2019-08-18 11:21:00 +00:00
|
|
|
return 0;
|
2018-04-23 19:32:43 +00:00
|
|
|
}
|