#include "oneshot.h" /****************** * HERE BE DRAGONS ******************/ #include "eventthread.h" #include "debugwriter.h" #include "bitmap.h" #include "font.h" #include "config.h" #include "define.h" #include #include #include #include #include // OS-Specific code #if defined(_WIN32) #define SECURITY_WIN32 #include #include #include #include #include #elif defined(__APPLE__) #include #include #include #include #include #elif unix_like #include #include #include #include #include #include #include "xdg-user-dir-lookup.h" #else #error "Operating system not detected or unsupported." #endif const Config conf; #define DEF_SCREEN_W conf.defScreenW #define DEF_SCREEN_H conf.defScreenH struct OneshotPrivate { // Main SDL window SDL_Window *window; // String data std::string os; std::string lang; std::string userName; std::string savePath; std::string docsPath; std::string gamePath; std::string journal; // Dialog text std::string txtYes; std::string txtNo; bool exiting; bool allowExit; // Alpha texture data for portions of window obscured by screen edges int winX, winY, winW, winH; SDL_Mutex *winMutex; std::vector obscuredMap; bool obscuredNeedToUpdate; bool obscuredCleared; OneshotPrivate() : window(0), winMutex(SDL_CreateMutex()) { } ~OneshotPrivate() { SDL_DestroyMutex(winMutex); } }; // OS-SPECIFIC FUNCTIONS #if unix_like struct linux_DialogData { // Input int type; const char *body; const char *title; // Output bool result; }; static int linux_dialog(void *rawData) { linux_DialogData *data = reinterpret_cast(rawData); // Determine correct flags GtkMessageType gtktype; GtkButtonsType gtkbuttons = GTK_BUTTONS_OK; switch (data->type) { case Oneshot::MSG_INFO: gtktype = GTK_MESSAGE_INFO; break; case Oneshot::MSG_YESNO: gtktype = GTK_MESSAGE_QUESTION; gtkbuttons = GTK_BUTTONS_YES_NO; break; case Oneshot::MSG_WARN: gtktype = GTK_MESSAGE_WARNING; break; case Oneshot::MSG_ERR: gtktype = GTK_MESSAGE_ERROR; break; default: gtk_main_quit(); return 0; } // Display dialog and get result GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, gtktype, gtkbuttons, "%s", data->body); gtk_window_set_title(GTK_WINDOW(dialog), data->title); int result = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); // Interpret result and return data->result = (result == GTK_RESPONSE_OK || result == GTK_RESPONSE_YES); gtk_main_quit(); return 0; } #elif defined _WIN32 /* Convert WCHAR pointer to std::string */ static std::string w32_fromWide(const WCHAR *ustr) { std::string result; int size = WideCharToMultiByte(CP_UTF8, 0, ustr, -1, 0, 0, 0, 0); if (size > 0) { CHAR *str = new CHAR[size]; if (WideCharToMultiByte(CP_UTF8, 0, ustr, -1, str, size, 0, 0) == size) result = str; delete[] str; } return result; } /* Convert WCHAR pointer from const char* */ static WCHAR *w32_toWide(const char *str) { if (str) { int size = MultiByteToWideChar(CP_UTF8, 0, str, -1, 0, 0); if (size > 0) { WCHAR *ustr = new WCHAR[size]; if (MultiByteToWideChar(CP_UTF8, 0, str, -1, ustr, size) == size) return ustr; delete[] ustr; } } // Return empty string WCHAR *ustr = new WCHAR[1]; *ustr = 0; return ustr; } #endif Oneshot::Oneshot(RGSSThreadData &threadData) : threadData(threadData) { p = new OneshotPrivate(); p->window = threadData.window; p->savePath = threadData.config.commonDataPath.substr(0, threadData.config.commonDataPath.size() - 1); p->winX = 0; p->winY = 0; SDL_GetWindowSize(p->window, &p->winW, &p->winH); p->obscuredMap.resize(p->winW * p->winH, 255); obscuredDirty = true; p->obscuredNeedToUpdate = false; p->allowExit = true; p->exiting = false; #ifdef _WIN32 p->os = "windows"; #elif __APPLE__ p->os = "macos"; #elif unix_like p->os = "linux"; #endif /******************** * USERNAME/DOCS PATH ********************/ #if defined _WIN32 // Get language code WCHAR wlang[9]; GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, wlang, sizeof(wlang) / sizeof(WCHAR)); p->lang = w32_fromWide(wlang) + "_"; GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, wlang, sizeof(wlang) / sizeof(WCHAR)); p->lang += w32_fromWide(wlang); // Get user's name ULONG size = 0; GetUserNameEx(NameDisplay, 0, &size); if (GetLastError() == ERROR_MORE_DATA) { // Get their full (display) name WCHAR *name = new WCHAR[size]; GetUserNameEx(NameDisplay, name, &size); p->userName = w32_fromWide(name); delete[] name; } else { // Get their login name DWORD size2 = 0; GetUserName(0, &size2); if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { WCHAR *name = new WCHAR[size2]; GetUserName(name, &size2); p->userName = w32_fromWide(name); delete[] name; } } // Get documents path WCHAR path[MAX_PATH]; SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, path); p->docsPath = w32_fromWide(path); p->gamePath = p->docsPath + "\\My Games"; p->journal = "_______.exe"; #else // Get language code const char *lc_all = SDL_getenv("LC_ALL"); const char *lang = SDL_getenv("LANG"); const char *code = (lc_all ? lc_all : lang); if (code) { // find first dot, copy language code int end = 0; for (; code[end] && code[end] != '.'; ++end) { } p->lang = std::string(code, end); } else p->lang = "en"; // Get user's name #ifdef OS_OSX struct passwd *pwd = getpwuid(geteuid()); #elif unix_like struct passwd *pwd = getpwuid(getuid()); #endif if (pwd) { if (pwd->pw_gecos && pwd->pw_gecos[0] && pwd->pw_gecos[0] != ',') { // Get the user's full name int comma = 0; for (; pwd->pw_gecos[comma] && pwd->pw_gecos[comma] != ','; ++comma) { } p->userName = std::string(pwd->pw_gecos, comma); } else p->userName = pwd->pw_name; } // Get documents path #ifdef __APPLE__ std::string path = std::string(SDL_getenv("HOME")) + "/Documents"; p->docsPath = path.c_str(); p->gamePath = path.c_str(); p->journal = "_______.app"; #elif unix_like char *path = xdg_user_dir_lookup("DOCUMENTS"); p->docsPath = path; p->gamePath = path; p->journal = "_______"; #endif #endif Debug() << "[oneshot] Game path :" << p->gamePath; Debug() << "[oneshot] Docs path :" << p->docsPath; #ifdef unix_like char const *xdg_current_desktop = SDL_getenv("XDG_CURRENT_DESKTOP"); gtk_init(0, 0); if (xdg_current_desktop == NULL) { desktopEnv = "nope"; } else { std::string desktop(xdg_current_desktop); std::transform(desktop.begin(), desktop.end(), desktop.begin(), ::SDL_tolower); if (desktop.find("cinnamon") != std::string::npos) { desktopEnv = "cinnamon"; } else if ( desktop.find("gnome") != std::string::npos || desktop.find("unity") != std::string::npos) { desktopEnv = "gnome"; } else if (desktop.find("mate") != std::string::npos) { desktopEnv = "mate"; } else if (desktop.find("xfce") != std::string::npos) { desktopEnv = "xfce"; } else if (desktop.find("kde") != std::string::npos) { desktopEnv = "kde"; } else if (desktop.find("lxde") != std::string::npos) { desktopEnv = "lxde"; } else if (desktop.find("lxqt") != std::string::npos) { desktopEnv = "lxqt"; } else if (desktop.find("deepin") != std::string::npos) { desktopEnv = "deepin"; } } Debug() << "[oneshot] Desktop env :" << desktopEnv; #endif /******** * MISC ********/ #if defined _WIN32 // Get windows version OSVERSIONINFOW version; ZeroMemory(&version, sizeof(version)); version.dwOSVersionInfoSize = sizeof(version); GetVersionEx(&version); #endif } Oneshot::~Oneshot() { delete p; } const std::string &Oneshot::os() const { return p->os; } const std::string &Oneshot::lang() const { return p->lang; } const std::string &Oneshot::userName() const { return p->userName; } const std::string &Oneshot::savePath() const { return p->savePath; } const std::string &Oneshot::docsPath() const { return p->docsPath; } const std::string &Oneshot::gamePath() const { return p->gamePath; } const std::string &Oneshot::journal() const { return p->journal; } const std::vector &Oneshot::obscuredMap() const { return p->obscuredMap; } bool Oneshot::obscuredCleared() const { return p->obscuredCleared; } bool Oneshot::exiting() const { return p->exiting; } bool Oneshot::allowExit() const { return p->allowExit; } void Oneshot::setYesNo(const char *yes, const char *no) { p->txtYes = yes; p->txtNo = no; } void Oneshot::setExiting(bool exiting) { if (p->exiting != exiting) { p->exiting = exiting; if (exiting) { threadData.exiting.set(); } else { threadData.exiting.clear(); } } } void Oneshot::setAllowExit(bool allowExit) { if (p->allowExit != allowExit) { p->allowExit = allowExit; if (allowExit) { threadData.allowExit.set(); } else { threadData.allowExit.clear(); } } } bool Oneshot::msgbox(int type, const char *body, const char *title) { if (!title) title = ""; #ifdef unix_like linux_DialogData data = {type, body, title, 0}; gdk_threads_add_idle(linux_dialog, &data); gtk_main(); return data.result; #else // SDL message box // Button data static const SDL_MessageBoxButtonData buttonOk = {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "OK"}; static const SDL_MessageBoxButtonData buttonsOk[] = {buttonOk}; SDL_MessageBoxButtonData buttonYes = {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, p->txtYes.c_str()}; SDL_MessageBoxButtonData buttonNo = {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 0, p->txtNo.c_str()}; SDL_MessageBoxButtonData buttonsYesNo[] = {buttonNo, buttonYes}; // Messagebox data SDL_MessageBoxData data; data.window = p->window; data.colorScheme = 0; data.title = title; data.message = body; #ifdef _WIN32 DWORD sound; #endif // Set type switch (type) { case MSG_INFO: case MSG_YESNO: default: data.flags = SDL_MESSAGEBOX_INFORMATION; #ifdef _WIN32 sound = SND_ALIAS_SYSTEMQUESTION; #endif break; case MSG_WARN: data.flags = SDL_MESSAGEBOX_WARNING; #ifdef _WIN32 sound = SND_ALIAS_SYSTEMEXCLAMATION; #endif break; case MSG_ERR: data.flags = SDL_MESSAGEBOX_WARNING; #ifdef _WIN32 sound = SND_ALIAS_SYSTEMASTERISK; #endif break; } // Set buttons switch (type) { case MSG_INFO: case MSG_WARN: case MSG_ERR: default: data.numbuttons = 1; data.buttons = buttonsOk; break; case MSG_YESNO: data.numbuttons = 2; data.buttons = buttonsYesNo; break; } // Show messagebox #ifdef _WIN32 PlaySoundW(MAKEINTRESOURCEW(sound), NULL, SND_ALIAS_ID | SND_ASYNC); #endif int button; #ifdef __APPLE__ int *btn = &button; // Message boxes and UI changes must be performed from the main thread on macOS Mojave and above. // This block ensures the message box will show from the main thread. dispatch_sync(dispatch_get_main_queue(), ^{ SDL_ShowMessageBox(&data, btn); }); #else SDL_ShowMessageBox(&data, &button); #endif return button ? true : false; #endif // #ifdef OS_LINUX } std::string Oneshot::textinput(const char *prompt, int char_limit, const char *fontName) { std::vector *fontNames = new std::vector(); fontNames->push_back(fontName); fontNames->push_back("VL Gothic"); Font *font = new Font(fontNames, 18); Bitmap *promptBmp = new Bitmap(DEF_SCREEN_W, DEF_SCREEN_H); promptBmp->setInitFont(font); promptBmp->drawText(0, 0, DEF_SCREEN_W, DEF_SCREEN_H, prompt, 1); Bitmap *inputBmp = new Bitmap(DEF_SCREEN_W, DEF_SCREEN_H); inputBmp->setInitFont(font); inputBmp->drawText(0, 0, DEF_SCREEN_W, DEF_SCREEN_H, "", 1); std::string inputTextPrev = std::string(""); threadData.acceptingTextInput.set(); threadData.inputTextLimit = char_limit; threadData.inputText.clear(); SDL_StartTextInput(this->p->window); // Main loop while (threadData.acceptingTextInput) { if (inputTextPrev != threadData.inputText) { inputBmp->clear(); inputBmp->drawText(DEF_SCREEN_W / 2, DEF_SCREEN_H / 2, DEF_SCREEN_W, DEF_SCREEN_H, threadData.inputText.c_str(), 1); inputTextPrev = threadData.inputText; } } // Disable text input SDL_StopTextInput(this->p->window); return threadData.inputText; } void Oneshot::setObscuredUpdating(bool enabled){ p->obscuredNeedToUpdate = enabled; } void Oneshot::updateObscuredSize(int winW, int winH){ p->winW = winW; p->winH = winH; p->obscuredMap.resize(p->winW * p->winH, 255); } void Oneshot::updateObscured(int winX, int winY) { SDL_LockMutex(p->winMutex); p->winX = winX; p->winY = winY; SDL_UnlockMutex(p->winMutex); if (!p->obscuredNeedToUpdate) return; // Map of unobscured pixels in this frame static std::vector obscuredFrame; obscuredFrame.resize(p->obscuredMap.size()); std::fill(obscuredFrame.begin(), obscuredFrame.end(), true); SDL_Rect screenRect; SDL_LockMutex(p->winMutex); screenRect.x = p->winX; screenRect.y = p->winY; SDL_UnlockMutex(p->winMutex); screenRect.w = p->winW; screenRect.h = p->winH; // костыль ебучий int num_displays; SDL_DisplayID *displays = SDL_GetDisplays(&num_displays); if (!displays) printf("SDL_GetDisplays failed (oneshot.cpp)\n"); // Update obscured map and texture for window portion offscreen for (int i = 0, max = num_displays; i < max; ++i) { SDL_Rect bounds; if (!SDL_GetDisplayBounds(displays[i], &bounds)) printf("SDL_GetDisplayBounds failed (oneshot.cpp)\n"); // Get intersection of window and the screen SDL_Rect intersect; if (!SDL_GetRectIntersection(&screenRect, &bounds, &intersect)) continue; intersect.x -= screenRect.x; intersect.y -= screenRect.y; // If it's entirely within the bounds of the screen, we don't need to check out // any other monitors if (intersect.x == 0 && intersect.y == 0 && intersect.w == conf.defScreenW && intersect.h == conf.defScreenH) return; for (int y = intersect.y; y < intersect.y + intersect.h; ++y) { int start = y * p->winW + intersect.x; std::fill(obscuredFrame.begin() + start, obscuredFrame.begin() + (start + intersect.w), false); } } SDL_free(displays); // Update the obscured map, and return prematurely if we don't have any changes // to make to the texture bool needsUpdate = false; bool cleared = true; for (size_t i = 0; i < obscuredFrame.size(); ++i) { if (obscuredFrame[i]) { p->obscuredMap[i] = 0; needsUpdate = true; } if (p->obscuredMap[i] == 255) cleared = false; } p->obscuredCleared = cleared; if (!needsUpdate) return; // Flag as dirty obscuredDirty = true; } void Oneshot::resetObscured() { std::fill(p->obscuredMap.begin(), p->obscuredMap.end(), 255); obscuredDirty = true; p->obscuredCleared = false; }