79int main(
int argc,
char *argv[])
85 sentry_options_t *options = sentry_options_new();
86 sentry_options_set_dsn(options,
"https://719a4881adf6e678b198bf9aad6b4500@o4508704323469312.ingest.us.sentry.io/4508704326352896");
90 const auto dbPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) +
"/sentry-native";
91 sentry_options_set_database_path(options, dbPath.toStdString().c_str());
93 sentry_options_set_release(options,
"wiredpanda@" APP_VERSION);
94 sentry_options_set_debug(options, 0);
97 sentry_options_set_environment(options,
"development");
99 sentry_options_set_environment(options,
"production");
102 sentry_init(options);
106 auto userId = QSettings().value(
"sentry/userId").toString();
107 if (userId.isEmpty()) {
108 userId = QUuid::createUuid().toString(QUuid::WithoutBraces);
109 QSettings().setValue(
"sentry/userId", userId);
111 sentry_value_t user = sentry_value_new_object();
112 sentry_value_set_by_key(user,
"id", sentry_value_new_string(userId.toStdString().c_str()));
113 sentry_set_user(user);
116 sentry_set_tag(
"qt.version", QT_VERSION_STR);
119 auto sentryClose = qScopeGuard([] { sentry_close(); });
126#ifdef ENABLE_MCP_SERVER
130 bool mcpMode =
false;
131 bool mcpGuiMode =
false;
132 for (
int i = 1; i < argc; i++) {
133 if (QString(argv[i]) ==
"--mcp") {
136 if (QString(argv[i]) ==
"--mcp-gui") {
141 bool mcpMode =
false;
142 bool mcpGuiMode =
false;
149 bool isCliMode =
false;
150 for (
int i = 1; i < argc; ++i) {
151 QString arg = QString::fromLocal8Bit(argv[i]);
152 if (arg ==
"--arduino-file" || arg ==
"-a" ||
153 arg ==
"--waveform" || arg ==
"-w" ||
154 arg ==
"--terminal" || arg ==
"-c" ||
155 arg ==
"--help" || arg ==
"-h" ||
156 arg ==
"--version" || arg ==
"-v") {
163 if (isCliMode || !qEnvironmentVariableIsSet(
"DISPLAY")) {
164 qputenv(
"QT_QPA_PLATFORM",
"offscreen");
169 if (!mcpMode && !mcpGuiMode) {
171 FILE *fpstdout = stdout, *fpstderr = stderr;
172 if (AttachConsole(ATTACH_PARENT_PROCESS)) {
173 freopen_s(&fpstdout,
"CONOUT$",
"w", stdout);
174 freopen_s(&fpstderr,
"CONOUT$",
"w", stderr);
181 if (mcpMode && !mcpGuiMode) {
182 qputenv(
"QT_QPA_PLATFORM",
"offscreen");
187 g_previousMessageHandler = qInstallMessageHandler(portalQuietMessageHandler);
192 if (qEnvironmentVariableIsSet(
"APPIMAGE")) {
193 QDir::setCurrent(qEnvironmentVariable(
"OWD"));
198 app.setOrganizationName(
"GIBIS-UNIFESP");
199 app.setApplicationName(
"wiRedPanda");
200 app.setApplicationVersion(APP_VERSION);
201 app.setDesktopFileName(
"wiredpanda");
204 app.setStyle(
"Fusion");
209 appIcon.addPixmap(QPixmap(
":/Assets/Icons/64x64/wpanda.png"));
210 appIcon.addPixmap(QPixmap(
":/Assets/Icons/128x128/wpanda.png"));
211 app.setWindowIcon(appIcon);
227 const QString appImagePath = qEnvironmentVariable(
"APPIMAGE");
228 const QString appDir = qEnvironmentVariable(
"APPDIR");
229 const QString localShare = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
230 const QString appsDir = localShare +
"/applications";
231 const QString desktopDst = appsDir +
"/wiredpanda.desktop";
233 if (!appImagePath.isEmpty() && !appDir.isEmpty()) {
238 QString desiredDesktop;
240 QFile src(appDir +
"/usr/share/applications/wiredpanda.desktop");
241 if (src.open(QIODevice::ReadOnly)) {
242 desiredDesktop = QString::fromUtf8(src.readAll());
243 desiredDesktop.replace(QRegularExpression(
"^Exec=.*$", QRegularExpression::MultilineOption),
244 "Exec=\"" + appImagePath +
"\" %F");
248 if (!desiredDesktop.isEmpty()) {
249 bool needsUpdate =
true;
251 QFile installed(desktopDst);
252 if (installed.open(QIODevice::ReadOnly))
253 needsUpdate = (QString::fromUtf8(installed.readAll()) != desiredDesktop);
256 QDir().mkpath(appsDir);
257 QFile dst(desktopDst);
258 if (dst.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
259 const QByteArray bytes = desiredDesktop.toUtf8();
260 if (dst.write(bytes) != bytes.size()) {
261 qCWarning(zero) <<
"Failed to write desktop entry:" << dst.errorString();
271 const QString iconsDir = localShare +
"/icons/hicolor/scalable/apps";
272 QDir().mkpath(iconsDir);
273 const QString iconDst = iconsDir +
"/wpanda.svg";
274 if (!QFile::exists(iconDst))
275 QFile::copy(appDir +
"/usr/share/icons/hicolor/scalable/apps/wpanda.svg", iconDst);
277 const QString icons256Dir = localShare +
"/icons/hicolor/256x256/apps";
278 QDir().mkpath(icons256Dir);
279 const QString icon256Dst = icons256Dir +
"/wpanda.png";
280 if (!QFile::exists(icon256Dst))
281 QFile::copy(appDir +
"/usr/share/icons/hicolor/256x256/apps/wpanda.png", icon256Dst);
282 }
else if (QFile::exists(desktopDst)) {
286 QFile::remove(desktopDst);
292 QCommandLineParser parser;
293 parser.setApplicationDescription(app.applicationName());
294 parser.addHelpOption();
295 parser.addVersionOption();
296 parser.addPositionalArgument(
"file", QCoreApplication::translate(
"main",
"Circuit file to open."));
298 QCommandLineOption verbosityOption(
300 QCoreApplication::translate(
"main",
"Verbosity level 0 to 5, disabled by default."),
301 QCoreApplication::translate(
"main",
"verbosity level"));
302 parser.addOption(verbosityOption);
304 QCommandLineOption arduinoFileOption(
305 {
"a",
"arduino-file"},
306 QCoreApplication::translate(
"main",
"Exports circuit to <arduino-file>."),
307 QCoreApplication::translate(
"main",
"arduino file"));
308 parser.addOption(arduinoFileOption);
310 QCommandLineOption waveformFileOption(
312 QCoreApplication::translate(
"main",
"Exports circuit to waveform text file."),
313 QCoreApplication::translate(
"main",
"waveform input text file"));
314 parser.addOption(waveformFileOption);
316 QCommandLineOption terminalFileOption(
318 QCoreApplication::translate(
"main",
"Exports circuit to waveform text file, reading input from terminal."));
319 parser.addOption(terminalFileOption);
321 QCommandLineOption blockTruthTableOption(
322 {
"btt",
"blockTruthTable"},
323 QCoreApplication::translate(
"main",
"When used with -c/--terminal, block execution if the circuit contains Truth Tables."));
324 parser.addOption(blockTruthTableOption);
326#ifdef ENABLE_MCP_SERVER
327 QCommandLineOption mcpModeOption(
329 QCoreApplication::translate(
"main",
"Run in MCP (Model Context Protocol) mode for programmatic control."));
330 parser.addOption(mcpModeOption);
332 QCommandLineOption mcpGuiOption(
334 QCoreApplication::translate(
"main",
"Run MCP mode with a visible GUI window."));
335 parser.addOption(mcpGuiOption);
340 if (
const QString verbosity = parser.value(verbosityOption); !verbosity.isEmpty()) {
344 QStringList args = parser.positionalArguments();
356 const QString inputFile = args.empty() ? QString() : QDir::current().absoluteFilePath(args.at(0));
358 if (
const QString arduFile = parser.value(arduinoFileOption); !arduFile.isEmpty()) {
359 if (inputFile.isEmpty()) {
360 QTextStream(stderr) << QCoreApplication::translate(
"main",
"Error: no input circuit file given.") <<
'\n';
370 if (
const QString wfFile = parser.value(waveformFileOption); !wfFile.isEmpty()) {
371 if (inputFile.isEmpty()) {
372 QTextStream(stderr) << QCoreApplication::translate(
"main",
"Error: no input circuit file given.") <<
'\n';
382 if (
const bool isTerminal = parser.isSet(terminalFileOption); isTerminal) {
383 if (inputFile.isEmpty()) {
384 QTextStream(stderr) << QCoreApplication::translate(
"main",
"Error: no input circuit file given.") <<
'\n';
395 if (parser.isSet(blockTruthTableOption)) {
396 bool containsTruthTable =
false;
399 for (
const auto *element : elements) {
400 if (element->elementType() == ElementType::TruthTable) {
401 containsTruthTable =
true;
406 if (containsTruthTable) {
407 QTextStream(stderr) << QCoreApplication::translate(
"main",
"Error: Circuit contains Truth Table elements.") <<
'\n';
417#ifdef ENABLE_MCP_SERVER
419 if (parser.isSet(mcpModeOption) || parser.isSet(mcpGuiOption)) {
425 if (parser.isSet(mcpGuiOption)) {
440 if (!inputFile.isEmpty()) {
441 window->loadPandaFile(inputFile);
443 }
catch (
const std::exception &e) {
445 QMessageBox::critical(
nullptr, QObject::tr(
"Error!"), e.what());
450 QTextStream(stderr) << QCoreApplication::translate(
"main",
"Error: ") << e.what() <<
'\n';
Main application window providing menus, toolbars, and tab management.
int main(int argc, char *argv[])
The top-level application window hosting the tab bar, menus, element palette, and editor.
void loadPandaFile(const QString &fileName)
Loads a .panda circuit file into the current or a new tab.
WorkSpace * currentTab() const override
Returns the currently visible WorkSpace tab, or nullptr.