Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Qt/QtMain.cpp
5656 views
1
/*
2
* Copyright (c) 2012 Sacha Refshauge
3
*
4
*/
5
// Qt 4.7+ / 5.0+ implementation of the framework.
6
// Currently supports: Android, Linux, Windows, Mac OSX
7
8
#include "ppsspp_config.h"
9
#include <QApplication>
10
#include <QClipboard>
11
#include <QDesktopWidget>
12
#include <QDesktopServices>
13
#include <QDir>
14
#include <QFile>
15
#include <QFileDialog>
16
#include <QLocale>
17
#include <QScreen>
18
#include <QThread>
19
#include <QUrl>
20
21
#include "ext/glslang/glslang/Public/ShaderLang.h"
22
23
#if QT_VERSION > QT_VERSION_CHECK(5, 0, 0)
24
#include <QStandardPaths>
25
#ifdef QT_HAS_SYSTEMINFO
26
#include <QScreenSaver>
27
#endif
28
#endif
29
30
#ifdef SDL
31
#include "SDL/SDLJoystick.h"
32
#include "SDL_audio.h"
33
#include "SDL_keyboard.h"
34
#endif
35
36
#include "Common/Audio/AudioBackend.h"
37
#include "Common/System/NativeApp.h"
38
#include "Common/System/Request.h"
39
#include "Common/GPU/OpenGL/GLFeatures.h"
40
#include "Common/Math/math_util.h"
41
#include "Common/Profiler/Profiler.h"
42
43
#include "QtMain.h"
44
#include "Qt/mainwindow.h"
45
#include "Common/Data/Text/I18n.h"
46
#include "Common/Thread/ThreadUtil.h"
47
#include "Common/Data/Encoding/Utf8.h"
48
#include "Common/StringUtils.h"
49
#include "Common/TimeUtil.h"
50
#include "Common/Log/LogManager.h"
51
52
#include "Core/Config.h"
53
#include "Core/ConfigValues.h"
54
#include "Core/HW/Camera.h"
55
#include "Core/Debugger/SymbolMap.h"
56
57
#include <signal.h>
58
#include <string.h>
59
60
// AUDIO
61
#define AUDIO_FREQ 44100
62
#define AUDIO_CHANNELS 2
63
#define AUDIO_SAMPLES 2048
64
#define AUDIO_SAMPLESIZE 16
65
#define AUDIO_BUFFERS 5
66
67
MainUI *emugl = nullptr;
68
static float refreshRate = 60.f;
69
static int browseFileEvent = -1;
70
static int browseFolderEvent = -1;
71
static int inputBoxEvent = -1;
72
73
QTCamera *qtcamera = nullptr;
74
MainWindow *g_mainWindow;
75
76
#ifdef SDL
77
SDL_AudioSpec g_retFmt;
78
79
static SDL_AudioDeviceID audioDev = 0;
80
81
extern void mixaudio(void *userdata, Uint8 *stream, int len) {
82
NativeMix((short *)stream, len / 4, AUDIO_FREQ, userdata);
83
}
84
85
static void InitSDLAudioDevice() {
86
SDL_AudioSpec fmt;
87
memset(&fmt, 0, sizeof(fmt));
88
fmt.freq = 44100;
89
fmt.format = AUDIO_S16;
90
fmt.channels = 2;
91
fmt.samples = std::max(g_Config.iSDLAudioBufferSize, 128);
92
fmt.callback = &mixaudio;
93
fmt.userdata = nullptr;
94
95
audioDev = 0;
96
if (!g_Config.sAudioDevice.empty()) {
97
audioDev = SDL_OpenAudioDevice(g_Config.sAudioDevice.c_str(), 0, &fmt, &g_retFmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
98
if (audioDev <= 0) {
99
WARN_LOG(Log::Audio, "Failed to open preferred audio device %s", g_Config.sAudioDevice.c_str());
100
}
101
}
102
if (audioDev <= 0) {
103
audioDev = SDL_OpenAudioDevice(nullptr, 0, &fmt, &g_retFmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
104
}
105
if (audioDev <= 0) {
106
ERROR_LOG(Log::Audio, "Failed to open audio: %s", SDL_GetError());
107
} else {
108
if (g_retFmt.samples != fmt.samples) // Notify, but still use it
109
ERROR_LOG(Log::Audio, "Output audio samples: %d (requested: %d)", g_retFmt.samples, fmt.samples);
110
if (g_retFmt.format != fmt.format || g_retFmt.channels != fmt.channels) {
111
ERROR_LOG(Log::Audio, "Sound buffer format does not match requested format.");
112
ERROR_LOG(Log::Audio, "Output audio freq: %d (requested: %d)", g_retFmt.freq, fmt.freq);
113
ERROR_LOG(Log::Audio, "Output audio format: %d (requested: %d)", g_retFmt.format, fmt.format);
114
ERROR_LOG(Log::Audio, "Output audio channels: %d (requested: %d)", g_retFmt.channels, fmt.channels);
115
ERROR_LOG(Log::Audio, "Provided output format does not match requirement, turning audio off");
116
SDL_CloseAudioDevice(audioDev);
117
}
118
SDL_PauseAudioDevice(audioDev, 0);
119
}
120
}
121
122
static void StopSDLAudioDevice() {
123
if (audioDev > 0) {
124
SDL_PauseAudioDevice(audioDev, 1);
125
SDL_CloseAudioDevice(audioDev);
126
}
127
}
128
#endif
129
130
std::string System_GetProperty(SystemProperty prop) {
131
switch (prop) {
132
case SYSPROP_NAME:
133
#if defined(__ANDROID__)
134
return "Qt:Android";
135
#elif defined(Q_OS_LINUX)
136
return "Qt:Linux";
137
#elif defined(_WIN32)
138
return "Qt:Windows";
139
#elif defined(Q_OS_MAC)
140
return "Qt:macOS";
141
#else
142
return "Qt";
143
#endif
144
case SYSPROP_LANGREGION:
145
return QLocale::system().name().toStdString();
146
case SYSPROP_CLIPBOARD_TEXT:
147
return QApplication::clipboard()->text().toStdString();
148
#if defined(SDL)
149
case SYSPROP_AUDIO_DEVICE_LIST:
150
{
151
std::string result;
152
for (int i = 0; i < SDL_GetNumAudioDevices(0); ++i) {
153
const char *name = SDL_GetAudioDeviceName(i, 0);
154
if (!name) {
155
continue;
156
}
157
158
if (i == 0) {
159
result = name;
160
} else {
161
result.append(1, '\0');
162
result.append(name);
163
}
164
}
165
return result;
166
}
167
#endif
168
case SYSPROP_BUILD_VERSION:
169
return PPSSPP_GIT_VERSION;
170
default:
171
return "";
172
}
173
}
174
175
std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
176
std::vector<std::string> result;
177
switch (prop) {
178
case SYSPROP_TEMP_DIRS:
179
if (getenv("TMPDIR") && strlen(getenv("TMPDIR")) != 0)
180
result.push_back(getenv("TMPDIR"));
181
if (getenv("TMP") && strlen(getenv("TMP")) != 0)
182
result.push_back(getenv("TMP"));
183
if (getenv("TEMP") && strlen(getenv("TEMP")) != 0)
184
result.push_back(getenv("TEMP"));
185
return result;
186
187
default:
188
return result;
189
}
190
}
191
192
int64_t System_GetPropertyInt(SystemProperty prop) {
193
switch (prop) {
194
#if defined(SDL)
195
case SYSPROP_AUDIO_SAMPLE_RATE:
196
return g_retFmt.freq;
197
case SYSPROP_AUDIO_FRAMES_PER_BUFFER:
198
return g_retFmt.samples;
199
case SYSPROP_KEYBOARD_LAYOUT:
200
{
201
// TODO: Use Qt APIs for detecting this
202
char q, w, y;
203
q = SDL_GetKeyFromScancode(SDL_SCANCODE_Q);
204
w = SDL_GetKeyFromScancode(SDL_SCANCODE_W);
205
y = SDL_GetKeyFromScancode(SDL_SCANCODE_Y);
206
if (q == 'a' && w == 'z' && y == 'y')
207
return KEYBOARD_LAYOUT_AZERTY;
208
else if (q == 'q' && w == 'w' && y == 'z')
209
return KEYBOARD_LAYOUT_QWERTZ;
210
return KEYBOARD_LAYOUT_QWERTY;
211
}
212
#endif
213
case SYSPROP_DEVICE_TYPE:
214
#if defined(__ANDROID__)
215
return DEVICE_TYPE_MOBILE;
216
#elif defined(Q_OS_LINUX)
217
return DEVICE_TYPE_DESKTOP;
218
#elif defined(_WIN32)
219
return DEVICE_TYPE_DESKTOP;
220
#elif defined(Q_OS_MAC)
221
return DEVICE_TYPE_DESKTOP;
222
#else
223
return DEVICE_TYPE_DESKTOP;
224
#endif
225
case SYSPROP_DISPLAY_COUNT:
226
return QApplication::screens().size();
227
default:
228
return -1;
229
}
230
}
231
232
float System_GetPropertyFloat(SystemProperty prop) {
233
switch (prop) {
234
case SYSPROP_DISPLAY_REFRESH_RATE:
235
return refreshRate;
236
case SYSPROP_DISPLAY_LOGICAL_DPI:
237
return QApplication::primaryScreen()->logicalDotsPerInch();
238
case SYSPROP_DISPLAY_DPI:
239
return QApplication::primaryScreen()->physicalDotsPerInch();
240
case SYSPROP_DISPLAY_SAFE_INSET_LEFT:
241
case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:
242
case SYSPROP_DISPLAY_SAFE_INSET_TOP:
243
case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM:
244
return 0.0f;
245
default:
246
return -1;
247
}
248
}
249
250
bool System_GetPropertyBool(SystemProperty prop) {
251
switch (prop) {
252
case SYSPROP_HAS_TEXT_CLIPBOARD:
253
return true;
254
case SYSPROP_HAS_BACK_BUTTON:
255
return true;
256
case SYSPROP_HAS_IMAGE_BROWSER:
257
case SYSPROP_HAS_FILE_BROWSER:
258
case SYSPROP_HAS_FOLDER_BROWSER:
259
case SYSPROP_HAS_OPEN_DIRECTORY:
260
case SYSPROP_HAS_TEXT_INPUT_DIALOG:
261
case SYSPROP_CAN_SHOW_FILE:
262
return true;
263
case SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR:
264
return true; // FileUtil.cpp: OpenFileInEditor
265
case SYSPROP_APP_GOLD:
266
#ifdef GOLD
267
return true;
268
#else
269
return false;
270
#endif
271
case SYSPROP_CAN_JIT:
272
return true;
273
case SYSPROP_HAS_KEYBOARD:
274
return true;
275
default:
276
return false;
277
}
278
}
279
280
void System_Notify(SystemNotification notification) {
281
switch (notification) {
282
case SystemNotification::BOOT_DONE:
283
g_symbolMap->SortSymbols();
284
g_mainWindow->Notify(MainWindowMsg::BOOT_DONE);
285
break;
286
case SystemNotification::SYMBOL_MAP_UPDATED:
287
if (g_symbolMap)
288
g_symbolMap->SortSymbols();
289
break;
290
case SystemNotification::AUDIO_RESET_DEVICE:
291
#ifdef SDL
292
StopSDLAudioDevice();
293
InitSDLAudioDevice();
294
#endif
295
break;
296
default:
297
break;
298
}
299
}
300
301
// TODO: Find a better version to pass parameters to HandleCustomEvent.
302
static std::string g_param1;
303
static std::string g_param2;
304
static int g_param3;
305
static int g_requestId;
306
307
bool MainUI::HandleCustomEvent(QEvent *e) {
308
if (e->type() == browseFileEvent) {
309
BrowseFileType fileType = (BrowseFileType)g_param3;
310
const char *filter = "All files (*.*)";
311
switch (fileType) {
312
case BrowseFileType::BOOTABLE:
313
filter = "PSP ROMs (*.iso *.cso *.chd *.pbp *.elf *.zip *.ppdmp)";
314
break;
315
case BrowseFileType::IMAGE:
316
filter = "Pictures (*.jpg *.png)";
317
break;
318
case BrowseFileType::INI:
319
filter = "INI files (*.ini)";
320
break;
321
case BrowseFileType::DB:
322
filter = "DB files (*.db)";
323
break;
324
case BrowseFileType::SOUND_EFFECT:
325
filter = "WAVE files (*.wav *.mp3)";
326
break;
327
case BrowseFileType::ZIP:
328
filter = "ZIP files (*.zip)";
329
break;
330
case BrowseFileType::ATRAC3:
331
filter = "AT3 files (*.at3)";
332
break;
333
case BrowseFileType::ANY:
334
break;
335
case BrowseFileType::SYMBOL_MAP:
336
filter = "PPSSPP symbol map files (*.ppmap)";
337
break;
338
case BrowseFileType::SYMBOL_MAP_NOCASH:
339
filter = "NoCash symbol map files (*.sym)";
340
break;
341
}
342
343
QString fileName = QFileDialog::getOpenFileName(nullptr, g_param1.c_str(), g_Config.currentDirectory.c_str(), filter);
344
if (QFile::exists(fileName)) {
345
g_requestManager.PostSystemSuccess(g_requestId, fileName.toStdString().c_str());
346
} else {
347
g_requestManager.PostSystemFailure(g_requestId);
348
}
349
} else if (e->type() == browseFolderEvent) {
350
QString title = QString::fromStdString(g_param1);
351
QString fileName = QFileDialog::getExistingDirectory(nullptr, title, g_Config.currentDirectory.c_str());
352
if (QDir(fileName).exists()) {
353
g_requestManager.PostSystemSuccess(g_requestId, fileName.toStdString().c_str());
354
} else {
355
g_requestManager.PostSystemFailure(g_requestId);
356
}
357
} else if (e->type() == inputBoxEvent) {
358
QString title = QString::fromStdString(g_param1);
359
QString defaultValue = QString::fromStdString(g_param2);
360
QString text = emugl->InputBoxGetQString(title, defaultValue);
361
if (text.isEmpty()) {
362
g_requestManager.PostSystemFailure(g_requestId);
363
} else {
364
g_requestManager.PostSystemSuccess(g_requestId, text.toStdString().c_str());
365
}
366
} else {
367
return false;
368
}
369
return true;
370
}
371
372
bool System_MakeRequest(SystemRequestType type, int requestId, const std::string &param1, const std::string &param2, int64_t param3, int64_t param4) {
373
switch (type) {
374
case SystemRequestType::EXIT_APP:
375
qApp->exit(0);
376
return true;
377
case SystemRequestType::RESTART_APP:
378
// Should find a way to properly restart the app.
379
qApp->exit(0);
380
return true;
381
case SystemRequestType::COPY_TO_CLIPBOARD:
382
QApplication::clipboard()->setText(param1.c_str());
383
return true;
384
case SystemRequestType::SET_WINDOW_TITLE:
385
{
386
std::string title = std::string("PPSSPP ") + PPSSPP_GIT_VERSION;
387
if (!param1.empty())
388
title += std::string(" - ") + param1;
389
#ifdef _DEBUG
390
title += " (debug)";
391
#endif
392
g_mainWindow->SetWindowTitleAsync(title);
393
return true;
394
}
395
case SystemRequestType::INPUT_TEXT_MODAL:
396
{
397
g_requestId = requestId;
398
g_param1 = param1;
399
g_param2 = param2;
400
g_param3 = param3;
401
QCoreApplication::postEvent(emugl, new QEvent((QEvent::Type)inputBoxEvent));
402
return true;
403
}
404
case SystemRequestType::BROWSE_FOR_IMAGE:
405
// Fall back to file browser.
406
return System_MakeRequest(SystemRequestType::BROWSE_FOR_FILE, requestId, param1, param2, (int)BrowseFileType::IMAGE, 0);
407
case SystemRequestType::BROWSE_FOR_FILE:
408
g_requestId = requestId;
409
g_param1 = param1;
410
g_param2 = param2;
411
g_param3 = param3;
412
QCoreApplication::postEvent(emugl, new QEvent((QEvent::Type)browseFileEvent));
413
return true;
414
case SystemRequestType::BROWSE_FOR_FOLDER:
415
g_requestId = requestId;
416
g_param1 = param1;
417
g_param2 = param2;
418
QCoreApplication::postEvent(emugl, new QEvent((QEvent::Type)browseFolderEvent));
419
return true;
420
case SystemRequestType::CAMERA_COMMAND:
421
if (!strncmp(param1.c_str(), "startVideo", 10)) {
422
int width = 0, height = 0;
423
sscanf(param1.c_str(), "startVideo_%dx%d", &width, &height);
424
emit(qtcamera->onStartCamera(width, height));
425
} else if (param1 == "stopVideo") {
426
emit(qtcamera->onStopCamera());
427
}
428
return true;
429
case SystemRequestType::SHOW_FILE_IN_FOLDER:
430
QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromUtf8(param1.c_str())));
431
return true;
432
default:
433
return false;
434
}
435
}
436
437
void System_Toast(std::string_view text) {}
438
439
void System_AskForPermission(SystemPermission permission) {}
440
PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; }
441
442
void System_Vibrate(int length_ms) {
443
if (length_ms == -1 || length_ms == -3)
444
length_ms = 50;
445
else if (length_ms == -2)
446
length_ms = 25;
447
}
448
449
void System_LaunchUrl(LaunchUrlType urlType, std::string_view url) {
450
QDesktopServices::openUrl(QUrl(std::string(url).c_str()));
451
}
452
453
AudioBackend *System_CreateAudioBackend() {
454
// Use legacy mechanisms.
455
return nullptr;
456
}
457
458
static int mainInternal(QApplication &a) {
459
#ifdef MOBILE_DEVICE
460
emugl = new MainUI();
461
emugl->resize(g_display.pixel_xres, g_display.pixel_yres);
462
emugl->showFullScreen();
463
#endif
464
EnableFZ();
465
// Disable screensaver
466
#if defined(QT_HAS_SYSTEMINFO)
467
QScreenSaver ssObject(emugl);
468
ssObject.setScreenSaverEnabled(false);
469
#endif
470
471
#ifdef SDL
472
SDLJoystick joy(true);
473
joy.registerEventHandler();
474
SDL_Init(SDL_INIT_AUDIO);
475
InitSDLAudioDevice();
476
#else
477
QScopedPointer<MainAudio> audio(new MainAudio());
478
audio->run();
479
#endif
480
481
browseFileEvent = QEvent::registerEventType();
482
browseFolderEvent = QEvent::registerEventType();
483
inputBoxEvent = QEvent::registerEventType();
484
485
int retval = a.exec();
486
delete emugl;
487
return retval;
488
}
489
490
void MainUI::EmuThreadFunc() {
491
SetCurrentThreadName("EmuThread");
492
493
// There's no real requirement that NativeInit happen on this thread, though it can't hurt...
494
// We just call the update/render loop here. NativeInitGraphics should be here though.
495
NativeInitGraphics(graphicsContext);
496
497
emuThreadState = (int)EmuThreadState::RUNNING;
498
while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) {
499
updateAccelerometer();
500
NativeFrame(graphicsContext);
501
}
502
emuThreadState = (int)EmuThreadState::STOPPED;
503
504
NativeShutdownGraphics();
505
}
506
507
void MainUI::EmuThreadStart() {
508
emuThreadState = (int)EmuThreadState::START_REQUESTED;
509
emuThread = std::thread([&]() { this->EmuThreadFunc(); } );
510
}
511
512
void MainUI::EmuThreadStop() {
513
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
514
}
515
516
void MainUI::EmuThreadJoin() {
517
emuThread.join();
518
emuThread = std::thread();
519
}
520
521
MainUI::MainUI(QWidget *parent)
522
: QGLWidget(parent) {
523
emuThreadState = (int)EmuThreadState::DISABLED;
524
setAttribute(Qt::WA_AcceptTouchEvents);
525
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
526
setAttribute(Qt::WA_LockLandscapeOrientation);
527
#endif
528
#if defined(MOBILE_DEVICE)
529
acc = new QAccelerometer(this);
530
acc->start();
531
#endif
532
setFocus();
533
setFocusPolicy(Qt::StrongFocus);
534
startTimer(16);
535
}
536
537
MainUI::~MainUI() {
538
INFO_LOG(Log::System, "MainUI::Destructor");
539
if (emuThreadState != (int)EmuThreadState::DISABLED) {
540
INFO_LOG(Log::System, "EmuThreadStop");
541
EmuThreadStop();
542
graphicsContext->ThreadFrameUntilCondition([this]() -> bool {
543
return emuThreadState == (int)EmuThreadState::STOPPED;
544
});
545
EmuThreadJoin();
546
}
547
#if defined(MOBILE_DEVICE)
548
delete acc;
549
#endif
550
graphicsContext->Shutdown();
551
delete graphicsContext;
552
graphicsContext = nullptr;
553
}
554
555
QString MainUI::InputBoxGetQString(QString title, QString defaultValue) {
556
bool ok;
557
QString text = QInputDialog::getText(this, title, title, QLineEdit::Normal, defaultValue, &ok);
558
if (!ok)
559
text = QString();
560
return text;
561
}
562
563
void MainUI::resizeGL(int w, int h) {
564
if (Native_UpdateScreenScale(w, h, UIScaleFactorToMultiplier(g_Config.iUIScaleFactor))) {
565
System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);
566
}
567
xscale = w / this->width();
568
yscale = h / this->height();
569
570
PSP_CoreParameter().pixelWidth = g_display.pixel_xres;
571
PSP_CoreParameter().pixelHeight = g_display.pixel_yres;
572
}
573
574
void MainUI::timerEvent(QTimerEvent *) {
575
updateGL();
576
emit newFrame();
577
}
578
579
void MainUI::changeEvent(QEvent *e) {
580
QGLWidget::changeEvent(e);
581
if (e->type() == QEvent::WindowStateChange)
582
Native_NotifyWindowHidden(isMinimized());
583
}
584
585
bool MainUI::event(QEvent *e) {
586
TouchInput input;
587
QList<QTouchEvent::TouchPoint> touchPoints;
588
589
switch (e->type()) {
590
case QEvent::TouchBegin:
591
case QEvent::TouchUpdate:
592
case QEvent::TouchEnd:
593
touchPoints = static_cast<QTouchEvent *>(e)->touchPoints();
594
foreach (const QTouchEvent::TouchPoint &touchPoint, touchPoints) {
595
switch (touchPoint.state()) {
596
case Qt::TouchPointStationary:
597
break;
598
case Qt::TouchPointPressed:
599
case Qt::TouchPointReleased:
600
input.x = touchPoint.pos().x() * g_display.dpi_scale_x * xscale;
601
input.y = touchPoint.pos().y() * g_display.dpi_scale_y * yscale;
602
input.flags = (touchPoint.state() == Qt::TouchPointPressed) ? TouchInputFlags::DOWN : TouchInputFlags::UP;
603
input.id = touchPoint.id();
604
NativeTouch(input);
605
break;
606
case Qt::TouchPointMoved:
607
input.x = touchPoint.pos().x() * g_display.dpi_scale_x * xscale;
608
input.y = touchPoint.pos().y() * g_display.dpi_scale_y * yscale;
609
input.flags = TouchInputFlags::MOVE;
610
input.id = touchPoint.id();
611
NativeTouch(input);
612
break;
613
default:
614
break;
615
}
616
}
617
break;
618
case QEvent::MouseButtonDblClick:
619
if (!g_Config.bShowTouchControls || GetUIState() != UISTATE_INGAME)
620
emit doubleClick();
621
break;
622
case QEvent::MouseButtonPress:
623
case QEvent::MouseButtonRelease:
624
switch(((QMouseEvent*)e)->button()) {
625
case Qt::LeftButton:
626
input.x = ((QMouseEvent*)e)->pos().x() * g_display.dpi_scale_x * xscale;
627
input.y = ((QMouseEvent*)e)->pos().y() * g_display.dpi_scale_y * yscale;
628
input.flags = ((e->type() == QEvent::MouseButtonPress) ? TouchInputFlags::DOWN : TouchInputFlags::UP) | TouchInputFlags::MOUSE;
629
input.id = 0;
630
NativeTouch(input);
631
break;
632
case Qt::RightButton:
633
NativeKey(KeyInput(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_2, (e->type() == QEvent::MouseButtonPress) ? KeyInputFlags::DOWN : KeyInputFlags::UP));
634
break;
635
case Qt::MiddleButton:
636
NativeKey(KeyInput(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_3, (e->type() == QEvent::MouseButtonPress) ? KeyInputFlags::DOWN : KeyInputFlags::UP));
637
break;
638
case Qt::ExtraButton1:
639
NativeKey(KeyInput(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_4, (e->type() == QEvent::MouseButtonPress) ? KeyInputFlags::DOWN : KeyInputFlags::UP));
640
break;
641
case Qt::ExtraButton2:
642
NativeKey(KeyInput(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_5, (e->type() == QEvent::MouseButtonPress) ? KeyInputFlags::DOWN : KeyInputFlags::UP));
643
break;
644
default:
645
break;
646
}
647
break;
648
case QEvent::MouseMove:
649
input.x = ((QMouseEvent*)e)->pos().x() * g_display.dpi_scale_x * xscale;
650
input.y = ((QMouseEvent*)e)->pos().y() * g_display.dpi_scale_y * yscale;
651
input.flags = TouchInputFlags::MOVE | TouchInputFlags::MOUSE;
652
input.id = 0;
653
NativeTouch(input);
654
break;
655
case QEvent::Wheel:
656
NativeKey(KeyInput(DEVICE_ID_MOUSE, ((QWheelEvent*)e)->delta()<0 ? NKCODE_EXT_MOUSEWHEEL_DOWN : NKCODE_EXT_MOUSEWHEEL_UP, KeyInputFlags::DOWN));
657
break;
658
case QEvent::KeyPress:
659
{
660
auto qtKeycode = ((QKeyEvent*)e)->key();
661
auto iter = KeyMapRawQttoNative.find(qtKeycode);
662
InputKeyCode nativeKeycode = NKCODE_UNKNOWN;
663
if (iter != KeyMapRawQttoNative.end()) {
664
nativeKeycode = iter->second;
665
NativeKey(KeyInput(DEVICE_ID_KEYBOARD, nativeKeycode, KeyInputFlags::DOWN));
666
}
667
668
// Also get the unicode value.
669
QString text = ((QKeyEvent*)e)->text();
670
std::string str = text.toStdString();
671
// Now, we don't want CHAR events for non-printable characters. Not quite sure how we'll best
672
// do that, but here's one attempt....
673
switch (nativeKeycode) {
674
case NKCODE_DEL:
675
case NKCODE_FORWARD_DEL:
676
case NKCODE_TAB:
677
break;
678
default:
679
if (str.size()) {
680
int pos = 0;
681
int unicode = u8_nextchar(str.c_str(), &pos, str.size());
682
NativeKey(KeyInput(DEVICE_ID_KEYBOARD, unicode));
683
}
684
break;
685
}
686
}
687
break;
688
case QEvent::KeyRelease:
689
NativeKey(KeyInput(DEVICE_ID_KEYBOARD, KeyMapRawQttoNative.find(((QKeyEvent*)e)->key())->second, KeyInputFlags::UP));
690
break;
691
692
default:
693
// Can't switch on dynamic event types.
694
if (!HandleCustomEvent(e)) {
695
return QWidget::event(e);
696
}
697
}
698
e->accept();
699
return true;
700
}
701
702
void MainUI::initializeGL() {
703
if (g_Config.iGPUBackend != (int)GPUBackend::OPENGL) {
704
INFO_LOG(Log::System, "Only GL supported under Qt - switching.");
705
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
706
}
707
708
bool useCoreContext = format().profile() == QGLFormat::CoreProfile;
709
710
SetGLCoreContext(useCoreContext);
711
712
#ifndef USING_GLES2
713
// Some core profile drivers elide certain extensions from GL_EXTENSIONS/etc.
714
// glewExperimental allows us to force GLEW to search for the pointers anyway.
715
if (useCoreContext) {
716
glewExperimental = true;
717
}
718
glewInit();
719
// Unfortunately, glew will generate an invalid enum error, ignore.
720
if (useCoreContext) {
721
glGetError();
722
}
723
#endif
724
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
725
// OpenGL uses a background thread to do the main processing and only renders on the gl thread.
726
INFO_LOG(Log::System, "Initializing GL graphics context");
727
graphicsContext = new QtGLGraphicsContext();
728
INFO_LOG(Log::System, "Using thread, starting emu thread");
729
EmuThreadStart();
730
} else {
731
INFO_LOG(Log::System, "Not using thread, backend=%d", (int)g_Config.iGPUBackend);
732
}
733
graphicsContext->ThreadStart();
734
}
735
736
void MainUI::paintGL() {
737
#ifdef SDL
738
SDL_PumpEvents();
739
#endif
740
updateAccelerometer();
741
if (emuThreadState == (int)EmuThreadState::DISABLED) {
742
NativeFrame(graphicsContext);
743
} else {
744
graphicsContext->ThreadFrame(true);
745
// Do the rest in EmuThreadFunc
746
}
747
}
748
749
void MainUI::updateAccelerometer() {
750
#if defined(MOBILE_DEVICE)
751
// TODO: Toggle it depending on whether it is enabled
752
QAccelerometerReading *reading = acc->reading();
753
if (reading) {
754
NativeAccelerometer(reading->x(), reading->y(), reading->z());
755
}
756
#endif
757
}
758
759
#ifndef SDL
760
761
MainAudio::~MainAudio() {
762
if (feed != nullptr) {
763
killTimer(timer);
764
feed->close();
765
}
766
if (output) {
767
output->stop();
768
delete output;
769
}
770
if (mixbuf)
771
free(mixbuf);
772
}
773
774
void MainAudio::run() {
775
QAudioFormat fmt;
776
fmt.setSampleRate(AUDIO_FREQ);
777
fmt.setCodec("audio/pcm");
778
fmt.setChannelCount(AUDIO_CHANNELS);
779
fmt.setSampleSize(AUDIO_SAMPLESIZE);
780
fmt.setByteOrder(QAudioFormat::LittleEndian);
781
fmt.setSampleType(QAudioFormat::SignedInt);
782
mixlen = sizeof(short)*AUDIO_BUFFERS*AUDIO_CHANNELS*AUDIO_SAMPLES;
783
mixbuf = (char*)malloc(mixlen);
784
output = new QAudioOutput(fmt);
785
output->setBufferSize(mixlen);
786
feed = output->start();
787
if (feed != nullptr) {
788
// buffering has already done in the internal mixed buffer
789
// use a small interval to copy mixed audio stream from
790
// internal buffer to audio output buffer as soon as possible
791
// use 1 instead of 0 to prevent CPU exhausting
792
timer = startTimer(1);
793
}
794
}
795
796
void MainAudio::timerEvent(QTimerEvent *) {
797
memset(mixbuf, 0, mixlen);
798
NativeMix((short *)mixbuf, AUDIO_BUFFERS * AUDIO_SAMPLES, AUDIO_FREQ);
799
feed->write(mixbuf, sizeof(short) * AUDIO_CHANNELS * frames);
800
}
801
802
#endif
803
804
805
void QTCamera::startCamera(int width, int height) {
806
__qt_startCapture(width, height);
807
}
808
809
void QTCamera::stopCamera() {
810
__qt_stopCapture();
811
}
812
813
#ifndef SDL
814
Q_DECL_EXPORT
815
#endif
816
int main(int argc, char *argv[])
817
{
818
TimeInit();
819
820
g_logManager.EnableOutput(LogOutput::Stdio);
821
822
for (int i = 1; i < argc; i++) {
823
if (!strcmp(argv[i], "--version")) {
824
printf("%s\n", PPSSPP_GIT_VERSION);
825
return 0;
826
}
827
}
828
829
// Ignore sigpipe.
830
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
831
perror("Unable to ignore SIGPIPE");
832
}
833
834
PROFILE_INIT();
835
glslang::InitializeProcess();
836
#if defined(Q_OS_LINUX)
837
QApplication::setAttribute(Qt::AA_X11InitThreads, true);
838
#endif
839
840
// Qt would otherwise default to a 3.0 compatibility profile
841
// except on Nvidia, where Nvidia gives us the highest supported anyway
842
QGLFormat format;
843
format.setVersion(4, 6);
844
format.setProfile(QGLFormat::CoreProfile);
845
QGLFormat::setDefaultFormat(format);
846
847
QApplication a(argc, argv);
848
QScreen* screen = a.primaryScreen();
849
QSizeF res = screen->physicalSize();
850
851
if (res.width() < res.height())
852
res.transpose();
853
854
// We assume physicalDotsPerInchY is the same as PerInchX.
855
float dpi_scale_x = screen->logicalDotsPerInchX() / screen->physicalDotsPerInchX();
856
float dpi_scale_y = screen->logicalDotsPerInchY() / screen->physicalDotsPerInchY();
857
g_display.Recalculate(res.width(), res.height(), dpi_scale_x, dpi_scale_y, UIScaleFactorToMultiplier(g_Config.iUIScaleFactor));
858
859
refreshRate = screen->refreshRate();
860
861
qtcamera = new QTCamera;
862
QObject::connect(qtcamera, SIGNAL(onStartCamera(int, int)), qtcamera, SLOT(startCamera(int, int)));
863
QObject::connect(qtcamera, SIGNAL(onStopCamera()), qtcamera, SLOT(stopCamera()));
864
865
std::string savegame_dir = ".";
866
std::string external_dir = ".";
867
#if QT_VERSION > QT_VERSION_CHECK(5, 0, 0)
868
savegame_dir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation).toStdString();
869
external_dir = QStandardPaths::writableLocation(QStandardPaths::DataLocation).toStdString();
870
#endif
871
savegame_dir += "/";
872
external_dir += "/";
873
874
NativeInit(argc, (const char **)argv, savegame_dir.c_str(), external_dir.c_str(), nullptr);
875
876
g_mainWindow = new MainWindow(nullptr, g_Config.bFullScreen);
877
g_mainWindow->show();
878
879
// TODO: Support other backends than GL, like Vulkan, in the Qt backend.
880
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
881
882
int ret = mainInternal(a);
883
INFO_LOG(Log::System, "Left mainInternal here.");
884
885
#ifdef SDL
886
if (audioDev > 0) {
887
SDL_PauseAudioDevice(audioDev, 1);
888
SDL_CloseAudioDevice(audioDev);
889
}
890
#endif
891
NativeShutdown();
892
glslang::FinalizeProcess();
893
return ret;
894
}
895
896