CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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