mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 06:50:29 +02:00
Merge remote-tracking branch 'origin/master' into loadmodule-deferred
This commit is contained in:
commit
4e2d3ab8d0
14 files changed
+267
-57
No files matched your search
@@ -156,6 +156,49 @@ void RequestManager::Clear() {
|
||||
callbackMap_.clear();
|
||||
}
|
||||
|
||||
static std::mutex g_textInputLock;
|
||||
static bool g_textInputWanted[(size_t)TextInputUser::COUNT];
|
||||
|
||||
static void SendUIEvent(UIEventNotification notification) {
|
||||
g_requestManager.MakeSystemRequest(SystemRequestType::NOTIFY_UI_EVENT, NO_REQUESTER_TOKEN, nullptr, nullptr, "", "", (int64_t)notification, 0);
|
||||
}
|
||||
|
||||
void System_SetTextInputWanted(TextInputUser user, bool wanted) {
|
||||
bool anyWanted = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(g_textInputLock);
|
||||
g_textInputWanted[(size_t)user] = wanted;
|
||||
for (bool w : g_textInputWanted) {
|
||||
anyWanted = anyWanted || w;
|
||||
}
|
||||
}
|
||||
// GOTFOCUS always goes through, as platforms may need it to show a keyboard again.
|
||||
if (wanted) {
|
||||
SendUIEvent(UIEventNotification::TEXT_GOTFOCUS);
|
||||
} else if (!anyWanted) {
|
||||
SendUIEvent(UIEventNotification::TEXT_LOSTFOCUS);
|
||||
}
|
||||
}
|
||||
|
||||
void System_NotifyUIEvent(UIEventNotification notification) {
|
||||
switch (notification) {
|
||||
case UIEventNotification::TEXT_GOTFOCUS:
|
||||
System_SetTextInputWanted(TextInputUser::UI, true);
|
||||
break;
|
||||
case UIEventNotification::TEXT_LOSTFOCUS:
|
||||
System_SetTextInputWanted(TextInputUser::UI, false);
|
||||
break;
|
||||
case UIEventNotification::POPUP_CLOSED:
|
||||
// Platforms may hide an on-screen keyboard on this, but text input itself goes through the above.
|
||||
System_SetTextInputWanted(TextInputUser::UI, false);
|
||||
SendUIEvent(notification);
|
||||
break;
|
||||
default:
|
||||
SendUIEvent(notification);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void System_CreateGameShortcut(const Path &path, std::string_view title) {
|
||||
g_requestManager.MakeSystemRequest(SystemRequestType::CREATE_GAME_SHORTCUT, NO_REQUESTER_TOKEN, nullptr, nullptr, path.ToString(), title, 0);
|
||||
}
|
||||
|
||||
+11
-3
@@ -182,9 +182,17 @@ inline void System_ShareFile(const Path &path, std::string_view mimeType = "") {
|
||||
g_requestManager.MakeSystemRequest(SystemRequestType::SHARE_FILE, NO_REQUESTER_TOKEN, nullptr, nullptr, path.ToString(), mimeType, 0);
|
||||
}
|
||||
|
||||
inline void System_NotifyUIEvent(UIEventNotification notification) {
|
||||
g_requestManager.MakeSystemRequest(SystemRequestType::NOTIFY_UI_EVENT, NO_REQUESTER_TOKEN, nullptr, nullptr, "", "", (int64_t)notification, 0);
|
||||
}
|
||||
// TEXT_GOTFOCUS/TEXT_LOSTFOCUS (and POPUP_CLOSED) sent through here count as the regular UI's use of text input.
|
||||
void System_NotifyUIEvent(UIEventNotification notification);
|
||||
|
||||
// Several users can want text input at the same time (for example type-to-search under the imgui debugger).
|
||||
// The platform only gets TEXT_LOSTFOCUS once none of them does.
|
||||
enum class TextInputUser {
|
||||
UI,
|
||||
ImDebugger,
|
||||
COUNT,
|
||||
};
|
||||
void System_SetTextInputWanted(TextInputUser user, bool wanted);
|
||||
|
||||
inline void System_SetKeepScreenBright(bool keepScreenBright) {
|
||||
g_requestManager.MakeSystemRequest(SystemRequestType::SET_KEEP_SCREEN_BRIGHT, NO_REQUESTER_TOKEN, nullptr, nullptr, "", "", (int64_t)keepScreenBright);
|
||||
|
||||
+7
-5
@@ -151,14 +151,11 @@ static ParseParamResult ParseParameterStr(int argc, const char *argv[], size_t &
|
||||
}
|
||||
|
||||
// Check for boolean/inverse-boolean parameter - these don't take a separate param value.
|
||||
if (param.type == CmdParamType::Bool && equals(arg, std::string("--") + param.longName)) {
|
||||
// SetValue() does the inverting, so both get "true".
|
||||
if ((param.type == CmdParamType::Bool || param.type == CmdParamType::BoolInverse) && equals(arg, std::string("--") + param.longName)) {
|
||||
ParseParamResult result = SetValue(options, param, "true");
|
||||
i++; // Skip --param
|
||||
return result;
|
||||
} else if (param.type == CmdParamType::BoolInverse && equals(arg, std::string("--") + param.longName)) {
|
||||
ParseParamResult result = SetValue(options, param, "false");
|
||||
i++; // Skip --param
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check for long form with space: --param value
|
||||
@@ -202,6 +199,7 @@ static const CommandLineParam g_autoParams[] = {
|
||||
{POFF(compare), CmdParamType::Bool, "compare", 'c', "Enable comparison mode", CmdLineMode::Headless},
|
||||
{POFF(bench), CmdParamType::Bool, "bench", 'b', "Enable benchmark mode", CmdLineMode::Headless},
|
||||
{POFF(oldAtrac), CmdParamType::Bool, "old-atrac", '\0', "Use old ATRAC decoder"},
|
||||
{POFF(memStickInserted), CmdParamType::BoolInverse, "no-memstick", '\0', "Run with the memory stick removed"},
|
||||
{POFF(log), CmdParamType::String, "log", '\0', "Output log to FILE", CmdLineMode::Application},
|
||||
{POFF(enableLogging), CmdParamType::Bool, "log", '\0', "Full log output, not just emulated printfs", CmdLineMode::Headless},
|
||||
{POFF(screenshotFilename), CmdParamType::String, "screenshot", '\0', "Compare rendered output against a reference screenshot FILE", CmdLineMode::Headless},
|
||||
@@ -554,6 +552,10 @@ void CommandLineOptions::ApplyToConfig() const {
|
||||
g_Config.bAutoRun = false;
|
||||
g_Config.bSaveSettings = false;
|
||||
}
|
||||
if (memStickInserted.has_value()) {
|
||||
g_Config.bMemStickInserted = memStickInserted.value();
|
||||
g_Config.DoNotSaveSetting(&g_Config.bMemStickInserted);
|
||||
}
|
||||
if (escapeExit.has_value()) {
|
||||
g_Config.bPauseExitsEmulator = escapeExit.value();
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ struct CommandLineOptions {
|
||||
std::optional<std::string> startScreen;
|
||||
|
||||
std::optional<bool> escapeExit;
|
||||
// Run as if no memory stick were inserted.
|
||||
std::optional<bool> memStickInserted;
|
||||
std::optional<bool> pauseMenuExit;
|
||||
|
||||
// Enables the WebSocket debugger on startup, on this port (0 = pick automatically).
|
||||
|
||||
@@ -1114,10 +1114,48 @@ void PSPSaveDialog::ExecuteIOAction() {
|
||||
ioThreadStatus = SAVEIO_READY;
|
||||
}
|
||||
|
||||
// What a mode that shows no dialog returns without a memory stick, or 0 for one not tested on
|
||||
// hardware. They all start fine and fail when they run (pspautotests utility/savedata/nomemstick).
|
||||
static u32 NoMemstickResult(SceUtilitySavedataType mode) {
|
||||
switch (mode) {
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_AUTOLOAD:
|
||||
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_MS;
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_AUTOSAVE:
|
||||
return SCE_UTILITY_SAVEDATA_ERROR_SAVE_NO_MS;
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_AUTODELETE:
|
||||
return SCE_UTILITY_SAVEDATA_ERROR_DELETE_NO_MS;
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_SIZES:
|
||||
return SCE_UTILITY_SAVEDATA_ERROR_SIZES_NO_MS;
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_LIST:
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_FILES:
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE:
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATA:
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE:
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_READDATA:
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE:
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATA:
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_ERASESECURE:
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_ERASE:
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_DELETEDATA:
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_GETSIZE:
|
||||
return SCE_UTILITY_SAVEDATA_ERROR_RW_NO_MEMSTICK;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void PSPSaveDialog::ExecuteNotVisibleIOAction() {
|
||||
auto &result = ioRequest_.common.result;
|
||||
|
||||
SceUtilitySavedataType utilityMode = (SceUtilitySavedataType)(u32)ioRequest_.mode;
|
||||
if (MemoryStick_State() != PSP_MEMORYSTICK_STATE_INSERTED) {
|
||||
const u32 noMemstickResult = NoMemstickResult(utilityMode);
|
||||
if (noMemstickResult != 0) {
|
||||
result = noMemstickResult;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (utilityMode) {
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_LOAD: // Only load and exit
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_AUTOLOAD:
|
||||
@@ -1138,16 +1176,10 @@ void PSPSaveDialog::ExecuteNotVisibleIOAction() {
|
||||
result = ioParam_.GetFilesList(&ioRequest_, requestAddr, ioListSaveDirName_);
|
||||
break;
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_GETSIZE:
|
||||
{
|
||||
bool sizeResult = ioParam_.GetSize(&ioRequest_);
|
||||
// TODO: According to JPCSP, should test/verify this part but seems edge casey.
|
||||
if (MemoryStick_State() != PSP_MEMORYSTICK_STATE_INSERTED) {
|
||||
result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_MEMSTICK;
|
||||
} else if (sizeResult) {
|
||||
result = 0;
|
||||
} else {
|
||||
result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;
|
||||
}
|
||||
if (ioParam_.GetSize(&ioRequest_)) {
|
||||
result = 0;
|
||||
} else {
|
||||
result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;
|
||||
}
|
||||
break;
|
||||
case SCE_UTILITY_SAVEDATA_TYPE_DELETEDATA:
|
||||
|
||||
@@ -946,10 +946,22 @@ static void __IoSchedSync(FileNode *f, int fd, int usec) {
|
||||
f->hasAsyncResult = false;
|
||||
}
|
||||
|
||||
// With the memory stick out, there's no device behind ms0: (pspautotests utility/savedata/nomemstick).
|
||||
static bool IsRemovedMemstickPath(const char *path) {
|
||||
if (MemoryStick_State() == PSP_MEMORYSTICK_STATE_INSERTED) {
|
||||
return false;
|
||||
}
|
||||
return startsWithNoCase(path, "ms0:") || startsWithNoCase(path, "fatms0:");
|
||||
}
|
||||
|
||||
static u32 sceIoGetstat(const char *filename, u32 addr) {
|
||||
// TODO: Improve timing (although this seems normally slow..)
|
||||
int usec = 1000;
|
||||
|
||||
if (IsRemovedMemstickPath(filename)) {
|
||||
return hleDelayResult(hleLogWarning(Log::sceIo, SCE_KERNEL_ERROR_NODEV, "memory stick removed"), "io getstat", usec);
|
||||
}
|
||||
|
||||
// A real PSP refuses to stat the root of a volume - io/stat records sceIoGetstat("ms0:/")
|
||||
// coming back as an invalid argument rather than describing the directory.
|
||||
const char *colon = strchr(filename, ':');
|
||||
@@ -1573,6 +1585,10 @@ static FileNode *__IoOpen(int &error, const char *filename, int flags, int mode)
|
||||
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
|
||||
return nullptr;
|
||||
}
|
||||
if (IsRemovedMemstickPath(filename)) {
|
||||
error = SCE_KERNEL_ERROR_NODEV;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int access = FILEACCESS_NONE;
|
||||
if (flags & PSP_O_RDONLY)
|
||||
@@ -2549,6 +2565,10 @@ static u32 sceIoDopen(const char *path) {
|
||||
return hleLogError(Log::sceIo, SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "nullptr file not found");
|
||||
}
|
||||
|
||||
if (IsRemovedMemstickPath(path)) {
|
||||
return hleLogWarning(Log::sceIo, SCE_KERNEL_ERROR_NODEV, "memory stick removed");
|
||||
}
|
||||
|
||||
double startTime = time_now_d();
|
||||
|
||||
bool listingExists = false;
|
||||
|
||||
@@ -721,10 +721,14 @@ bool GameManager::ExtractZipContents(struct zip *z, const Path &dest, const ZipF
|
||||
Path outFilename = dest / zippedName.substr(info.stripChars);
|
||||
|
||||
bool isDir = zippedName.empty() || zippedName.back() == '/';
|
||||
if (!isDir && zippedName.find('/') != std::string::npos) {
|
||||
outFilename = dest / zippedName.substr(info.stripChars, zippedName.rfind('/') - info.stripChars);
|
||||
} else if (!isDir) {
|
||||
outFilename = dest;
|
||||
if (!isDir) {
|
||||
// stripChars can reach past the last slash (save states strip their whole directory).
|
||||
size_t lastSlash = zippedName.rfind('/');
|
||||
if (lastSlash != std::string::npos && lastSlash > (size_t)info.stripChars) {
|
||||
outFilename = dest / zippedName.substr(info.stripChars, lastSlash - info.stripChars);
|
||||
} else {
|
||||
outFilename = dest;
|
||||
}
|
||||
}
|
||||
|
||||
Path outPath(outFilename);
|
||||
|
||||
+18
-2
@@ -108,6 +108,9 @@ static int g_audioFramesPerBuffer = 0;
|
||||
|
||||
static bool g_textFocusChanged;
|
||||
static bool g_textFocus;
|
||||
// Set when SDL_StartTextInput fails, so we generate KeyInputFlags::CHAR from key events ourselves.
|
||||
// SDL 3.4.0 to 3.4.4 refuse text input on Wayland compositors without zwp_text_input_v3 (like WSLg's Weston).
|
||||
static bool g_textInputFallback;
|
||||
double g_audioStartTime = 0.0;
|
||||
|
||||
// Window state to be transferred to the main SDL thread.
|
||||
@@ -1016,7 +1019,6 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
|
||||
g_textFocus = true;
|
||||
g_textFocusChanged = true;
|
||||
break;
|
||||
case UIEventNotification::POPUP_CLOSED:
|
||||
case UIEventNotification::TEXT_LOSTFOCUS:
|
||||
g_textFocus = false;
|
||||
g_textFocusChanged = true;
|
||||
@@ -1523,6 +1525,17 @@ static void ProcessSDLEvent(SDL_Window *window, const SDL_Event &event, InputSta
|
||||
#endif
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
{
|
||||
if (g_textInputFallback && g_textFocus && !(event.key.mod & (SDL_KMOD_CTRL | SDL_KMOD_ALT | SDL_KMOD_GUI))) {
|
||||
// Layout-aware, with shift applied. Repeats are wanted here, like real text input.
|
||||
SDL_Keycode c = SDL_GetKeyFromScancode(event.key.scancode, event.key.mod, false);
|
||||
if (c >= 0x20 && c != 0x7F && !(c & SDLK_SCANCODE_MASK)) {
|
||||
KeyInput key;
|
||||
key.flags = KeyInputFlags::CHAR;
|
||||
key.unicodeChar = c;
|
||||
key.deviceId = DEVICE_ID_KEYBOARD;
|
||||
NativeKey(key);
|
||||
}
|
||||
}
|
||||
if (event.key.repeat > 0) {
|
||||
break;
|
||||
}
|
||||
@@ -1848,7 +1861,10 @@ void UpdateTextFocus(SDL_Window *window) {
|
||||
if (g_textFocusChanged) {
|
||||
DEBUG_LOG(Log::System, "Updating text focus: %d", g_textFocus);
|
||||
if (g_textFocus) {
|
||||
SDL_StartTextInput(window);
|
||||
if (!SDL_StartTextInput(window) && !g_textInputFallback) {
|
||||
WARN_LOG(Log::System, "SDL_StartTextInput failed (%s), generating text from key events instead. Update SDL, or set SDL_VIDEO_DRIVER=x11, for full text input.", SDL_GetError());
|
||||
g_textInputFallback = true;
|
||||
}
|
||||
} else {
|
||||
SDL_StopTextInput(window);
|
||||
}
|
||||
|
||||
+25
-26
@@ -212,7 +212,7 @@ static std::vector<QueuedEvent> g_inputEventQueue;
|
||||
static std::unique_ptr<ImDebugger> imDebugger_;
|
||||
static ImCommand imCmd_{}; // needed to buffer commands in case imgui wasn't created yet.
|
||||
static bool imguiInited_ = false;
|
||||
static bool lastImguiEnabled_ = false;
|
||||
static bool imguiWantTextInput_ = false;
|
||||
static ImGuiContext *ctx_ = nullptr;
|
||||
|
||||
class GlobalListener : public ControlListener {
|
||||
@@ -297,20 +297,7 @@ static void RunAchievementsOverrideUpdate(std::function<void()> func) {
|
||||
}
|
||||
|
||||
void runImDebugger(Draw::DrawContext *draw) {
|
||||
bool lastImguiEnabled_ = false; // temp
|
||||
if (lastImguiEnabled_ && g_Config.bShowImDebugger) {
|
||||
#if !defined(MOBILE_DEVICE)
|
||||
// On mobile devices (specifically iOS) we don't want to pop the keyboard
|
||||
// on activating imgui. Instead, we should do it when a text edit field in imgui gets focus,
|
||||
// although we'll still have ugly overlap problems.
|
||||
System_NotifyUIEvent(UIEventNotification::TEXT_GOTFOCUS);
|
||||
#endif
|
||||
VERBOSE_LOG(Log::System, "activating keyboard");
|
||||
} else if (lastImguiEnabled_ && !g_Config.bShowImDebugger) {
|
||||
System_NotifyUIEvent(UIEventNotification::TEXT_LOSTFOCUS);
|
||||
VERBOSE_LOG(Log::System, "deactivating keyboard");
|
||||
}
|
||||
lastImguiEnabled_ = g_Config.bShowImDebugger;
|
||||
bool wantTextInput = false;
|
||||
if (g_Config.bShowImDebugger) {
|
||||
if (!imguiInited_) {
|
||||
// TODO: Do this only on demand.
|
||||
@@ -383,10 +370,20 @@ void runImDebugger(Draw::DrawContext *draw) {
|
||||
|
||||
// Convert to drawlists.
|
||||
ImGui::Render();
|
||||
wantTextInput = io.WantTextInput;
|
||||
} else {
|
||||
uiContext->SetOverrideScreenFrame(nullptr);
|
||||
SetOverrideScreenFrame(nullptr);
|
||||
}
|
||||
|
||||
// Enable text input (needed for KeyInputFlags::CHAR events) only while an imgui text field is active.
|
||||
// On mobile devices (specifically iOS) this would pop the keyboard, with ugly overlap problems.
|
||||
#if !defined(MOBILE_DEVICE)
|
||||
if (wantTextInput != imguiWantTextInput_) {
|
||||
System_SetTextInputWanted(TextInputUser::ImDebugger, wantTextInput);
|
||||
imguiWantTextInput_ = wantTextInput;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void renderImDebugger(Draw::DrawContext *draw) {
|
||||
@@ -1569,9 +1566,11 @@ bool NativeKey(const KeyInput &key) {
|
||||
// Filtering, detailed rules needed for good imgui behavior without having to ask the screen about what to do.
|
||||
InputMode inputMode = g_screenManager->PassInputToMapper();
|
||||
|
||||
bool imguiHasKeyboard = false;
|
||||
if (g_Config.bShowImDebugger && imguiInited_) {
|
||||
if (ImGui::GetIO().WantCaptureKeyboard) {
|
||||
inputMode &= ~InputMode::Keyboard;
|
||||
imguiHasKeyboard = true;
|
||||
}
|
||||
if (ImGui::GetIO().WantCaptureMouse) {
|
||||
inputMode &= ~InputMode::Mouse;
|
||||
@@ -1579,21 +1578,21 @@ bool NativeKey(const KeyInput &key) {
|
||||
}
|
||||
|
||||
bool passKeyThrough = false;
|
||||
if (inputMode != InputMode::None) {
|
||||
if ((inputMode & InputMode::ImDebuggerToggle) && (key.flags & (KeyInputFlags::UP | KeyInputFlags::DOWN))) {
|
||||
InputMapping mapping(key.deviceId, key.keyCode);
|
||||
std::vector<int> pspButtons;
|
||||
bool mappingFound = KeyMap::InputMappingToPspButton(mapping, &pspButtons);
|
||||
if (mappingFound) {
|
||||
for (auto b : pspButtons) {
|
||||
if (b == VIRTKEY_TOGGLE_DEBUGGER || b == VIRTKEY_PAUSE) {
|
||||
// TRUE
|
||||
passKeyThrough = true;
|
||||
}
|
||||
// The debugger toggle also goes through while imgui has the keyboard, so it can always be closed again.
|
||||
if (((inputMode & InputMode::ImDebuggerToggle) || imguiHasKeyboard) && (key.flags & (KeyInputFlags::UP | KeyInputFlags::DOWN))) {
|
||||
InputMapping mapping(key.deviceId, key.keyCode);
|
||||
std::vector<int> pspButtons;
|
||||
bool mappingFound = KeyMap::InputMappingToPspButton(mapping, &pspButtons);
|
||||
if (mappingFound) {
|
||||
for (auto b : pspButtons) {
|
||||
if (b == VIRTKEY_TOGGLE_DEBUGGER || (b == VIRTKEY_PAUSE && (inputMode & InputMode::ImDebuggerToggle))) {
|
||||
passKeyThrough = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inputMode != InputMode::None) {
|
||||
if (key.deviceId == DEVICE_ID_MOUSE) {
|
||||
if (inputMode & InputMode::Mouse) {
|
||||
passKeyThrough = true;
|
||||
|
||||
+17
-5
@@ -165,6 +165,13 @@ namespace MainWindow {
|
||||
static bool hasFocus = true;
|
||||
static bool g_keepScreenBright = false;
|
||||
|
||||
// The simulated double-click that toggles fullscreen. Only two clicks on the running game count, so
|
||||
// focus loss, a click outside the client area, or a UI state change in between resets it.
|
||||
static double lastMouseDownTime = 0.0;
|
||||
static float lastMouseDownX = -1.0f;
|
||||
static float lastMouseDownY = -1.0f;
|
||||
static GlobalUIState lastMouseDownUIState = UISTATE_MENU;
|
||||
|
||||
static bool disasmMapLoadPending = false;
|
||||
static bool memoryMapLoadPending = false;
|
||||
static bool g_wasMinimized = false;
|
||||
@@ -825,6 +832,7 @@ namespace MainWindow {
|
||||
trapMouse = true;
|
||||
}
|
||||
if (wParam == WA_INACTIVE) {
|
||||
lastMouseDownTime = 0.0;
|
||||
System_PostUIMessage(UIMessage::LOST_FOCUS);
|
||||
WindowsRawInput::LoseFocus();
|
||||
g_InputManager.LoseFocus();
|
||||
@@ -1208,27 +1216,31 @@ namespace MainWindow {
|
||||
SetCapture(hWnd);
|
||||
|
||||
// Simulate doubleclick, doesn't work with RawInput enabled
|
||||
static double lastMouseDownTime;
|
||||
static float lastMouseDownX = -1.0f;
|
||||
static float lastMouseDownY = -1.0f;
|
||||
const double now = time_now_d();
|
||||
if ((now - lastMouseDownTime) < 0.001 * GetDoubleClickTime()) {
|
||||
const GlobalUIState uiState = GetUIState();
|
||||
if ((now - lastMouseDownTime) < 0.001 * GetDoubleClickTime() && uiState == lastMouseDownUIState) {
|
||||
const float dx = lastMouseDownX - x;
|
||||
const float dy = lastMouseDownY - y;
|
||||
const float distSq = dx * dx + dy * dy;
|
||||
if (distSq < 3.0f*3.0f && !g_Config.bShowTouchControls && !g_Config.bShowImDebugger && !g_Config.bMouseControl && GetUIState() == UISTATE_INGAME && g_Config.bFullscreenOnDoubleclick) {
|
||||
if (distSq < 3.0f*3.0f && !g_Config.bShowTouchControls && !g_Config.bShowImDebugger && !g_Config.bMouseControl && uiState == UISTATE_INGAME && g_Config.bFullscreenOnDoubleclick) {
|
||||
g_Config.bFullScreen = !g_Config.bFullScreen;
|
||||
SendApplyFullscreenState();
|
||||
}
|
||||
lastMouseDownTime = 0.0;
|
||||
} else {
|
||||
lastMouseDownTime = now;
|
||||
lastMouseDownUIState = uiState;
|
||||
}
|
||||
lastMouseDownX = x;
|
||||
lastMouseDownY = y;
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_NCLBUTTONDOWN:
|
||||
// The title bar or the window's buttons, so not part of a double-click on the game.
|
||||
lastMouseDownTime = 0.0;
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
|
||||
case WM_MOUSEMOVE:
|
||||
if (!touchHandler.hasTouch() ||
|
||||
(GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN)
|
||||
|
||||
+1
-1
Submodule pspautotests updated: e7bc66349a...3066d2f076.
@@ -512,6 +512,7 @@ tests_good = [
|
||||
"utility/savedata/filelist",
|
||||
"utility/savedata/getsize",
|
||||
"utility/savedata/makedata",
|
||||
"utility/savedata/nomemstick",
|
||||
"utility/systemparam/systemparam",
|
||||
"umd/api/api",
|
||||
"umd/callbacks/umd",
|
||||
@@ -656,6 +657,13 @@ tests_next = [
|
||||
]
|
||||
|
||||
|
||||
# Tests that need extra headless flags, so they run in a headless process of their own. The flags
|
||||
# have to match how the .expected was recorded.
|
||||
test_flags = {
|
||||
"utility/savedata/nomemstick": ["--no-memstick"],
|
||||
}
|
||||
|
||||
|
||||
# These are the tests we ignore (not important, or impossible to run)
|
||||
tests_ignored = [
|
||||
"kirk/kirk",
|
||||
@@ -773,7 +781,11 @@ def main():
|
||||
if skipped:
|
||||
print("Skipping %d known failures on %s: %s" % (len(skipped), skip_arch, ", ".join(skipped)))
|
||||
|
||||
returncode = run_tests(tests, args)
|
||||
plain = [t for t in tests if t not in test_flags]
|
||||
returncode = run_tests(plain, args)
|
||||
for test in tests:
|
||||
if test in test_flags:
|
||||
returncode = run_tests([test], args + test_flags[test]) or returncode
|
||||
if teamcity:
|
||||
return 0
|
||||
return returncode
|
||||
|
||||
@@ -80,6 +80,49 @@ static bool TestZipSlipExtraction() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Save state zips are detected with stripChars past the last slash, since the whole
|
||||
// directory is stripped. The files must land directly in the destination.
|
||||
static bool TestSaveStateZipExtraction() {
|
||||
// Absolute, since CreateFullPath does nothing with relative paths.
|
||||
Path tempRoot = File::GetCurDirectory() / "unittest_zip_savestate_test";
|
||||
File::DeleteDirRecursively(tempRoot);
|
||||
EXPECT_TRUE(File::CreateDir(tempRoot));
|
||||
|
||||
Path destDir = tempRoot / "dest";
|
||||
EXPECT_TRUE(File::CreateDir(destDir));
|
||||
|
||||
Path zipPath = tempRoot / "states.zip";
|
||||
int errorp = 0;
|
||||
zip_t *z = zip_open(zipPath.c_str(), ZIP_CREATE | ZIP_TRUNCATE, &errorp);
|
||||
EXPECT_TRUE(z != nullptr);
|
||||
static const char *const contents = "data";
|
||||
EXPECT_TRUE(zip_dir_add(z, "SAVE/", ZIP_FL_ENC_UTF_8) >= 0);
|
||||
EXPECT_TRUE(zip_dir_add(z, "SAVE/PPSSPP_STATE/", ZIP_FL_ENC_UTF_8) >= 0);
|
||||
for (const char *name : { "SAVE/PPSSPP_STATE/UCES01401_1.00_1.jpg", "SAVE/PPSSPP_STATE/UCES01401_1.00_1.ppst" }) {
|
||||
zip_source_t *source = zip_source_buffer(z, contents, strlen(contents), 0);
|
||||
EXPECT_TRUE(source != nullptr);
|
||||
EXPECT_TRUE(zip_file_add(z, name, source, ZIP_FL_ENC_UTF_8) >= 0);
|
||||
}
|
||||
EXPECT_TRUE(zip_close(z) == 0);
|
||||
|
||||
z = zip_open(zipPath.c_str(), 0, &errorp);
|
||||
EXPECT_TRUE(z != nullptr);
|
||||
|
||||
ZipFileInfo info;
|
||||
DetectZipFileContents(z, &info);
|
||||
EXPECT_EQ_INT((int)info.contents, (int)ZipFileContents::SAVE_STATES);
|
||||
|
||||
GameManager manager;
|
||||
EXPECT_TRUE(manager.ExtractZipContents(z, destDir, info, true));
|
||||
zip_close(z);
|
||||
|
||||
EXPECT_TRUE(File::Exists(destDir / "UCES01401_1.00_1.jpg") && !File::IsDirectory(destDir / "UCES01401_1.00_1.jpg"));
|
||||
EXPECT_TRUE(File::Exists(destDir / "UCES01401_1.00_1.ppst") && !File::IsDirectory(destDir / "UCES01401_1.00_1.ppst"));
|
||||
|
||||
File::DeleteDirRecursively(tempRoot);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TestIsSafePathComponent() {
|
||||
EXPECT_TRUE(IsSafePathComponent("ULUS10041"));
|
||||
EXPECT_TRUE(IsSafePathComponent("NPJH-50465"));
|
||||
@@ -106,5 +149,7 @@ bool TestZipSlip() {
|
||||
return false;
|
||||
if (!TestZipSlipExtraction())
|
||||
return false;
|
||||
if (!TestSaveStateZipExtraction())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@@ -3029,6 +3029,21 @@ bool TestCmdLine() {
|
||||
EXPECT_EQ_INT(options.timeoutWall.value_or(0), 4);
|
||||
EXPECT_EQ_INT(options.timeoutEmulated.value_or(0), 5);
|
||||
}
|
||||
// Inverse booleans: --windowed turns fullscreen off, and --no-memstick removes the stick.
|
||||
{
|
||||
const char *argv[] = {
|
||||
"ppsspp",
|
||||
"--windowed",
|
||||
"--no-memstick",
|
||||
};
|
||||
int argc = ARRAY_SIZE(argv);
|
||||
CommandLineOptions options;
|
||||
options.Parse(argc, argv, CmdLineMode::Application);
|
||||
EXPECT_TRUE(options.fullscreen.has_value());
|
||||
EXPECT_FALSE(options.fullscreen.value_or(true));
|
||||
EXPECT_TRUE(options.memStickInserted.has_value());
|
||||
EXPECT_FALSE(options.memStickInserted.value_or(true));
|
||||
}
|
||||
// Test GL version override
|
||||
{
|
||||
const char *argv[] = {
|
||||
|
||||
Reference in new issue
Block a user