File:  [Isaki's NoNo m68k/m88k emulator] / nono / wx / wxapp.cpp
Revision 1.1.1.21 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:53 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, v026, HEAD
nono 1.6.0

//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

//
// wx アプリケーションのエントリポイント
//

#include "fontmanager.h"
#include "wxlogsetting.h"
#include "wxmainframe.h"
#include "wxmainview.h"
#include "wxuimessage.h"
#include "wxversion.h"
#include "config.h"
#include "mainapp.h"
#include "memdump.h"
#include <algorithm>
#include <wx/intl.h>

class WXApp : public wxApp
{
	using inherited = wxApp;
 public:
	~WXApp() override;
	bool OnInit() override;
	int OnRun() override;
 private:
	bool ParseMonitors(const wxString& str);
	bool ParseMemdumpValue(uint id, const std::string&, const std::string&);
	void ShowMonitorsName();
	int SearchMonitorName(const char *name);
	int FindAvailable(const std::vector<uint>& candidates,
		const std::string& name, int basenum, int count) const;

	wxLocale locale;

	std::unique_ptr<FontManager> pFontManager {};
	std::unique_ptr<HostInfoMonitor> pHostInfo {};
};

static std::string utf8_to_sjis(const std::string&);

// IMPLEMENT_APP() は闇マクロすぎて clang のちからには耐えられない
PRAGMA_PUSH_WARNINGS
IMPLEMENT_APP(WXApp)
PRAGMA_POP_WARNINGS

WXApp::~WXApp()
{
	// 開発用
	WXUIMessage::AssertAllDisconnected();
}

bool
WXApp::OnInit()
{
	return true;
}

int
WXApp::OnRun()
{
	int rv;

	rv = gMainApp.Init1(argc, argv, utf8_to_sjis);
	if (rv != MainApp::PASS) {
		return rv;
	}

	// 環境変数 $NONO_BUILD_HOME が定義されていればカタログ検索パスに
	// $NONO_BUILD_HOME/po を追加する (システムパスより前に挿入される)。
	// カタログ開発時にワークツリーにあるカタログを見るため。
	locale.Init(wxLANGUAGE_DEFAULT, wxLOCALE_DONT_LOAD_DEFAULT);
	const char *build_home = getenv("NONO_BUILD_HOME");
	if (build_home) {
		wxString path(build_home);
		path += "/po";
		wxLocale::AddCatalogLookupPathPrefix(path);
	}
	locale.AddCatalog("nono");

	// テキスト系コントロールのフォントサイズ。
	// 紛らわしいが設定の monitor-fontsize は 12, 16 とかいう値。
	// FontManager::fontid は enum 値。
	const ConfigItem& item_fontsize = gConfig->Find("monitor-fontsize");
	int fontsize = item_fontsize.AsInt();
	FontId fontid;
	switch (fontsize) {
	 case 12:
		fontid = FontId::_6x12;
		break;
	 case 16:
		fontid = FontId::_8x16;
		break;
	 case 24:
		fontid = FontId::_12x24;
		break;
	 default:
		item_fontsize.Err("Must be either 12, 16 or 24");
		return EXIT_FAILURE;
	}
	try {
		pFontManager.reset(new FontManager());
	} catch (...) { }
	if ((bool)pFontManager == false) {
		warnx("Failed to initialize FontManager");
		return EXIT_FAILURE;
	}
	gFontManager = pFontManager.get();
	gFontManager->SetFont(fontid);

	// ホスト情報用のモニタ (どこでやるのがいいか)
	try {
		pHostInfo.reset(new HostInfoMonitor());
	} catch (...) { }
	if ((bool)pHostInfo == false) {
		warnx("Failed to initialize HostInfoMonitor");
		return EXIT_FAILURE;
	}
	gHostInfo = pHostInfo.get();

	// 手順 6. (vm/device.h)
	// これ以降モニタの登録をしてはいけない。
	gMonitorManager->Fix();

	// -M 起動時に表示するモニタ
	if (!gMainApp.monitor_opt.empty()) {
		if (!ParseMonitors(gMainApp.monitor_opt)) {
			return EXIT_FAILURE;
		}
	}

	// メインスクリーン倍率
	const ConfigItem& item_scale = gConfig->Find("mainview-scale");
	double scale;
	if (item_scale.TryDouble(&scale) == false) {
		item_scale.Err();
		return EXIT_FAILURE;
	}
	// 制限は適当
	if (scale <= 0 || scale >= 10) {
		item_scale.Err();
		return EXIT_FAILURE;
	}
	WXMainView::screen_scale = scale;

	// モニター更新間隔
	const ConfigItem& item_rate = gConfig->Find("monitor-rate");
	int rate = item_rate.AsInt();
	if (rate < 1 || rate > 60) {
		item_rate.Err();
		return EXIT_FAILURE;
	}
	WXMainFrame::SetMonitorRate(rate);

	// 引数処理が終わったので、スレッド作成を伴う初期化。
	if (!gMainApp.Init2()) {
		return EXIT_FAILURE;
	}

	// メインウィンドウ
	auto mainframe = new WXMainFrame(NULL);
	mainframe->Show(true);
	SetTopWindow(mainframe);

	// 設定反映などの初期化
	if (!mainframe->Init()) {
		return EXIT_FAILURE;
	}

	// イベントループ。
	rv = inherited::OnRun();

	// 戻ってきたので VM を解放。
	gMainApp.Dispose();

	return rv;
}

// -M オプションの解析。書式は
//  -M <arg>[,<arg>[,...]]
// <arg> のどれかが "help" なら有効な名前を列挙して終了。
// <arg> は通常モニタ名(サブウィンドウ名)のみだが
// memdump* の場合のみ <name>=<hexaddr> 形式を受け付ける。
// 継続する場合は true、しない場合は false を返す。
bool
WXApp::ParseMonitors(const wxString& str)
{
	std::vector<std::string> args = string_split(str, ',');

	// "help" があればヘルプを表示して終了
	for (const auto& arg : args) {
		if (arg == "help") {
			ShowMonitorsName();
			return false;
		}
	}

	// 一致するモニタ(ウィンドウ)の開始フラグを立てる
	for (const auto& arg : args) {
		std::string name;
		std::string value;

		auto pos = arg.find('=');
		if (pos != std::string::npos) {
			name = arg.substr(0, pos);
			value = arg.substr(++pos);
		} else {
			name = arg;
		}

		// この名前のウィンドウを探して start フラグを立てる
		int id = SearchMonitorName(name.c_str());
		if (id < 0) {
			return false;
		}
		WXMainFrame::subwindow_start[id] = true;

		// 値付き memdump の処理
		if ((IS_MONITOR_MEMDUMP(id) || IS_MONITOR_XPMEMDUMP(id))
				&& value.empty() == false)
		{
			if (ParseMemdumpValue(id, name, value) == false) {
				return false;
			}
		}
	}
	return true;
}

// -Mmemdump= の続きの文字列 value を解析してセットする。
// 書式は <hex-address>[.<fmt>]
// fmt は B/W/L と M(MMU)、I(dIsasm)、Z(XPdisasm)
bool
WXApp::ParseMemdumpValue(uint id,
	const std::string& name, const std::string& value)
{
	auto mon = gMonitorManager->Get(id);
	auto memdump = dynamic_cast<Memdump *>(mon->obj);
	assert(memdump);

	errno = 0;
	char *end;
	unsigned long u = strtoul(value.c_str(), &end, 16);
	// これが呼ばれた時点で value は空ではないはず
	if (errno == ERANGE || u > 0xffffffff) {
		errno = ERANGE;
		warn("-M %s=%s", name.c_str(), value.c_str());
		return false;
	}
	// アドレスを設定
	memdump->SetAddr((uint32)u);

	// さらに後ろがあれば表示形式指定。
	if (*end != '\0') {
		if (*end++ != '.') {
			goto usage;
		}

		if (end[1] != '\0') {
			goto usage;
		}

		Memdump::Format fmt;
		switch (std::toupper((int)*end)) {
		 case 'B':	fmt = Memdump::Byte;	break;
		 case '\0':
		 case 'W':	fmt = Memdump::Word;	break;
		 case 'L':	fmt = Memdump::Long;	break;
		 case 'M':
			if (gMainApp.Has(VMCap::M88K)) {
				fmt = Memdump::M88200Page;
			} else {
				// m68k はちょっと複雑すぎて放置。
				goto usage;
			}
			break;
		 case 'I':
			if (gMainApp.Has(VMCap::M88K)) {
				fmt = Memdump::M88100Disasm;
			} else {
				fmt = Memdump::M680x0Disasm;
			}
			break;
		 case 'Z':
			fmt = Memdump::HD64180Disasm;
			break;
		 default:
		 usage:
			warnx("-M %s=%s: syntax error; syntax is \"<hex-addr>[.<BWLMIZ>]\"",
				name.c_str(), value.c_str());
			return false;
		}

		// 表示形式を設定
		memdump->SetFormat(fmt);
	}

	return true;
}

// モニタ名の一覧を表示する
void
WXApp::ShowMonitorsName()
{
	std::vector<uint> list;

	// モニターは登録されていれば列挙
	// (登録されているものを全部列挙ではないことに注意)
	for (uint id = ID_MONITOR_START; id <= ID_MONITOR_END; id++) {
		if (gMonitorManager->Find(id)) {
			list.push_back(id);
		}
	}
	// サブウィンドウは機種情報から判断
	for (uint id = ID_SUBWIN_START; id <= ID_SUBWIN_END; id++) {
		VMCap vmcap = gMonitorManager->GetVMCap(id);
		if (gMainApp.Has(vmcap)) {
			list.push_back(id);
		}
	}

	// 一覧を表示
	std::vector<std::string> names = gMonitorManager->MakeListString(list);
	MainApp::ShowHelpList(stdout, names);
}

// モニタ名 name を検索し、一つに確定すればその id を返す。
// 確定しない、あるいは一致しない場合はエラーメッセージを表示し -1 を返す。
//
// "memdump" (数字なし) は空いてる memdump<N> を順に使用する。
// ただし今の所この処理は1パスで前から順に処理しているだけなので、
// "memdump0,memdump" は指定可能だが(2つ目が memdump1 になる)、
// "memdump,memdump0" は memdump0 を2回指定したことになる。
int
WXApp::SearchMonitorName(const char *name)
{
	std::vector<uint> candidates;
	std::vector<std::string> cand_names;

	for (uint id = ID_MONITOR_START; id <= ID_SUBWIN_END; id++) {
		if (id < ID_SUBWIN_START) {
			// モニターは登録されていなければ除外する。
			Monitor *mon = gMonitorManager->Find(id);
			if (mon == NULL) {
				continue;
			}
			// SLIRP モニタだけ、登録はされてて obj が NULL という状態がある。
			if (mon->obj == NULL) {
				continue;
			}
		} else {
			// サブウィンドウは該当機種でなければ除外する
			VMCap vmcap = gMonitorManager->GetVMCap(id);
			if (gMainApp.Has(vmcap) == false) {
				continue;
			}
		}

		const auto& aliases = gMonitorManager->GetAliases(id);

		bool matched = false;
		for (const auto& alias : aliases) {
			// 全文一致したら確定
			// ("lunafb" と "lunafb0" みたいなのがあるため)
			if (alias == name) {
				return id;
			}

			// 部分一致したら名前はすべて覚えておく
			if (starts_with_ignorecase(alias, name)) {
				cand_names.push_back(alias);
				matched = true;
			}
		}
		// 同じオブジェクトで別名が複数回部分一致しても、1回として数える
		if (matched) {
			candidates.push_back(id);
		}
	}

	if (candidates.empty()) {
		// 一致しない
		warnx("Unknown window name \"%s\"", name);
		return -1;
	} else if (candidates.size() == 1) {
		// 部分一致したのが1つだけなら確定
		return candidates[0];
	} else {
		// 候補が複数あった
		uint id;

		// "memdump" を空いてる "memdump*" に割り当てる
		id = FindAvailable(candidates, "memdump",
			ID_MONITOR_MEMDUMP0, MAX_MEMDUMP_MONITOR);
		if (id != 0) {
			return id;
		}

		// "xpmemdump" を空いてる "xpmemdump*" に割り当てる
		id = FindAvailable(candidates, "xpmemdump",
			ID_MONITOR_XPMEMDUMP0, MAX_XPMEMDUMP_MONITOR);
		if (id != 0) {
			return id;
		}

		// そうでなければ、候補を表示
		std::string candstr;
		for (const auto& cname : cand_names) {
			candstr += ' ';
			candstr += cname;
		}
		warnx("Ambiguous window name \"%s\": candidates are%s",
			name, candstr.c_str());
		return -1;
	}
}

// candidates (候補の一覧) がすべて basenum .. count 内に入っていれば、
// その中の空きウィンドウ ID を返す。
// 空きがなければエラーメッセージを表示して - 1 を返す。
// candidates の中に basenum .. count 内に入っていないものがあれば 0 を返す。
// 今の所ここでターゲットにしている人たちに ID=0 はいないので。
// start は ID_MONITOR_*0、count は MAX_*_MONITOR のほうで、
// 最初と最後の ID ではないので注意。
int
WXApp::FindAvailable(const std::vector<uint>& candidates,
	const std::string& name, int start, int count) const
{
	// candidates がすべてこの start..end に入っているか
	bool all = std::all_of(candidates.begin(), candidates.end(),
		[&](int id) { return (start <= id && id < start + count); }
	);

	if (all) {
		// この時点で空いてるものを返す。
		for (int i = 0; i < count; i++) {
			int id = start + i;
			if (WXMainFrame::subwindow_start[id] == false) {
				return id;
			}
		}
		// 空きがなければエラー
		warnx("No more %s windows available", name.c_str());
		return -1;
	}

	// 候補が他にもあった
	return 0;
}

// UTF-8 文字列を Shift_JIS 文字列に変換して返すグローバル関数。
// GUI の場合に host/logger.cpp で必要なのだが、このために iconv を
// 追加で要求するのはビルド的に手間なので、wxWidgets の機構を使う。
// Shift_JIS への変換が発生するのは GUI 起動の場合だけなので問題ない。
static std::string
utf8_to_sjis(const std::string& utf8str)
{
	static wxCSConv conv("CP932");

	// UTF-8 -> wxString
	wxString wstr(utf8str.c_str(), wxConvUTF8);

	// wxString -> Shift_JIS
	wxCharBuffer sjisbuf = wstr.mb_str(conv);

	return std::string(sjisbuf.data());
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.