|
|
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());
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.