diff --git a/appshell/cef_main_window.cpp b/appshell/cef_main_window.cpp index 1909b55c8..4fc0293f9 100644 --- a/appshell/cef_main_window.cpp +++ b/appshell/cef_main_window.cpp @@ -26,11 +26,21 @@ #include "resource.h" #include "native_menu_model.h" #include "config.h" +#include // external extern HINSTANCE hInst; extern CefRefPtr g_handler; +// portable build file structure +typedef struct { + char szSignature[18]; // MAKEPORTABLE_BRACKETS_SIGNATURE_STRING + unsigned short uVersion; // current file version + RECT rcWindow; + RECT rcRestored; + UINT showCmd; +} sCefMainWindowData; + // constants static const wchar_t kWindowClassname[] = L"CEFCLIENT"; static const wchar_t kWindowPostionFolder[] = L"Window Position"; @@ -104,21 +114,17 @@ BOOL cef_main_window::Create() { RegisterWndClass(); - int left = CW_USEDEFAULT; - int top = CW_USEDEFAULT; - int width = CW_USEDEFAULT; - int height = CW_USEDEFAULT; int showCmd = SW_SHOW; - LoadWindowRestoreRect(left, top, width, height, showCmd); + LoadWindowRestoreRect(showCmd); DWORD styles = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_EX_COMPOSITED; if (showCmd == SW_MAXIMIZE) styles |= WS_MAXIMIZE; - if (!cef_host_window::Create(::kWindowClassname, GetBracketsWindowTitleText(), - styles, left, top, width, height)) + if (!cef_host_window::Create(::kWindowClassname, GetBracketsWindowTitleText(), styles, + mWindowRect.left, mWindowRect.top, mWindowRect.right, mWindowRect.bottom)) { return FALSE; } @@ -293,36 +299,117 @@ void cef_main_window::SaveWindowRestoreRect() RECT rect; if (GetWindowRect(&rect)) { - ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefLeft, rect.left); - ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefTop, rect.top); - ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefWidth, ::RectWidth(rect)); - ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefHeight, ::RectHeight(rect)); + mWindowRect.left = rect.left; + mWindowRect.top = rect.top; + mWindowRect.right = ::RectWidth(rect); + mWindowRect.bottom = ::RectHeight(rect); } if (wp.showCmd == SW_MAXIMIZE) { // When window is maximized, we also store the "restore" size - ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefRestoreLeft, wp.rcNormalPosition.left); - ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefRestoreTop, wp.rcNormalPosition.top); - ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefRestoreRight, wp.rcNormalPosition.right); - ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefRestoreBottom, wp.rcNormalPosition.bottom); + mRestoredRect.left = wp.rcNormalPosition.left; + mRestoredRect.top = wp.rcNormalPosition.top; + mRestoredRect.right = wp.rcNormalPosition.right; + mRestoredRect.bottom = wp.rcNormalPosition.bottom; } - // Maximize is the only special case we handle - ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefShowState, (wp.showCmd == SW_MAXIMIZE) ? SW_MAXIMIZE : SW_SHOW); + if (!ClientApp::IsPortableInstall()) + { + // for normal installations, serialize to the Registry + ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefLeft, mWindowRect.left); + ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefTop, mWindowRect.top); + ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefWidth, mWindowRect.right); + ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefHeight, mWindowRect.bottom); + + ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefRestoreLeft, mRestoredRect.left); + ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefRestoreTop, mRestoredRect.top); + ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefRestoreRight, mRestoredRect.right); + ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefRestoreBottom, mRestoredRect.bottom); + + ::WriteRegistryInt(::kWindowPostionFolder, ::kPrefShowState, (wp.showCmd == SW_MAXIMIZE) ? SW_MAXIMIZE : SW_SHOW); + } + else + { + // for portable installations, serialize to a file in the app folder + sCefMainWindowData data; + memset(&data, 0, sizeof(sCefMainWindowData)); + strcpy(data.szSignature, MAKEPORTABLE_BRACKETS_SIGNATURE_STRING); + data.uVersion = MAKEPORTABLE_BRACKETS_VERSION_CURRENT; + data.rcWindow = mWindowRect; + data.rcRestored = mRestoredRect; + data.showCmd = wp.showCmd; + + std::wstring filename; + ClientApp::GetPortableInstallFilename(filename); + HANDLE hFile = ::CreateFile(filename.c_str(), GENERIC_WRITE, + FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (INVALID_HANDLE_VALUE != hFile) + { + DWORD dwBytesWritten = 0L; + ::WriteFile(hFile, (LPVOID)&data, sizeof(data), &dwBytesWritten, NULL); + ::CloseHandle(hFile); + } + } } } } // Initialization helper -- // Loads the restore window placement data from the registry -void cef_main_window::LoadWindowRestoreRect(int& left, int& top, int& width, int& height, int& showCmd) +void cef_main_window::LoadWindowRestoreRect(int& showCmd) { - ::GetRegistryInt(::kWindowPostionFolder, ::kPrefLeft, NULL, left); - ::GetRegistryInt(::kWindowPostionFolder, ::kPrefTop, NULL, top); - ::GetRegistryInt(::kWindowPostionFolder, ::kPrefWidth, NULL, width); - ::GetRegistryInt(::kWindowPostionFolder, ::kPrefHeight, NULL, height); - ::GetRegistryInt(::kWindowPostionFolder, ::kPrefShowState, NULL, showCmd); + // set defaults + ::SetRect(&mWindowRect, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT); + showCmd = SW_SHOW; + ::SetRect(&mRestoredRect, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT); + + if (!ClientApp::IsPortableInstall()) + { + // for normal installations, serialize to the Registry + ::GetRegistryInt(::kWindowPostionFolder, ::kPrefLeft, NULL, (int&)mWindowRect.left); + ::GetRegistryInt(::kWindowPostionFolder, ::kPrefTop, NULL, (int&)mWindowRect.top); + // stuff the width and height into the right and bottom fields + ::GetRegistryInt(::kWindowPostionFolder, ::kPrefWidth, NULL, (int&)mWindowRect.right); + ::GetRegistryInt(::kWindowPostionFolder, ::kPrefHeight, NULL, (int&)mWindowRect.bottom); + + ::GetRegistryInt(::kWindowPostionFolder, ::kPrefShowState, NULL, showCmd); + + ::GetRegistryInt(::kWindowPostionFolder, ::kPrefRestoreLeft, NULL, (int&)mRestoredRect.left); + ::GetRegistryInt(::kWindowPostionFolder, ::kPrefRestoreTop, NULL, (int&)mRestoredRect.top); + ::GetRegistryInt(::kWindowPostionFolder, ::kPrefRestoreRight, NULL, (int&)mRestoredRect.right); + ::GetRegistryInt(::kWindowPostionFolder, ::kPrefRestoreBottom, NULL, (int&)mRestoredRect.bottom); + } + else + { + // for portable installations, serialize to a file in the app folder + std::wstring filename; + ClientApp::GetPortableInstallFilename(filename); + HANDLE hFile = ::CreateFile(filename.c_str(), GENERIC_READ, + FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (INVALID_HANDLE_VALUE != hFile) + { + DWORD dwFileSize = ::GetFileSize(hFile, NULL); + DWORD dwBytesRead; + sCefMainWindowData data; + if ((sizeof(sCefMainWindowData) == dwFileSize) + && ::ReadFile(hFile, (LPVOID)&data, sizeof(sCefMainWindowData), &dwBytesRead, NULL) + && (dwBytesRead == sizeof(sCefMainWindowData)) + && (strcmp(data.szSignature, MAKEPORTABLE_BRACKETS_SIGNATURE_STRING) == 0)) + { + if (data.uVersion <= MAKEPORTABLE_BRACKETS_VERSION_1) + { + mWindowRect = data.rcWindow; + mRestoredRect = data.rcRestored; + showCmd = data.showCmd; + } + + // future version struct additions go here... + // if (data.uVersion <= MAKEPORTABLE_BRACKETS_VERSION_xx) { } + } + ::CloseHandle(hFile); + } + } } // Initialization helper -- @@ -343,15 +430,7 @@ void cef_main_window::RestoreWindowPlacement(int showCmd) wp.ptMaxPosition.x = -1; wp.ptMaxPosition.y = -1; - wp.rcNormalPosition.left = CW_USEDEFAULT; - wp.rcNormalPosition.top = CW_USEDEFAULT; - wp.rcNormalPosition.right = CW_USEDEFAULT; - wp.rcNormalPosition.bottom = CW_USEDEFAULT; - - GetRegistryInt(::kWindowPostionFolder, ::kPrefRestoreLeft, NULL, (int&)wp.rcNormalPosition.left); - GetRegistryInt(::kWindowPostionFolder, ::kPrefRestoreTop, NULL, (int&)wp.rcNormalPosition.top); - GetRegistryInt(::kWindowPostionFolder, ::kPrefRestoreRight, NULL, (int&)wp.rcNormalPosition.right); - GetRegistryInt(::kWindowPostionFolder, ::kPrefRestoreBottom, NULL, (int&)wp.rcNormalPosition.bottom); + wp.rcNormalPosition = mRestoredRect; // This returns FALSE on failure but not sure what we could do in that case SetWindowPlacement(&wp); diff --git a/appshell/cef_main_window.h b/appshell/cef_main_window.h index a23f798c0..c883f56ab 100644 --- a/appshell/cef_main_window.h +++ b/appshell/cef_main_window.h @@ -45,7 +45,7 @@ class cef_main_window : public cef_host_window protected: // Initalization - Protected Members void SaveWindowRestoreRect(); - void LoadWindowRestoreRect(int& left, int& top, int& width, int& height, int& showCmd); + void LoadWindowRestoreRect(int& showCmd); void RestoreWindowPlacement(int showCmd); // Message Handlers @@ -72,5 +72,8 @@ class cef_main_window : public cef_host_window private: // Initialization - Private Members static ATOM RegisterWndClass(); + + RECT mWindowRect; // persisted size of normal window + RECT mRestoredRect; // persisted restored size of maximized window }; diff --git a/appshell/client_app.h b/appshell/client_app.h index 3388211fc..d1ea56e01 100644 --- a/appshell/client_app.h +++ b/appshell/client_app.h @@ -91,6 +91,15 @@ class ClientApp : public CefApp, double GetElapsedMilliseconds(); CefString GetCurrentLanguage(); std::string GetExtensionJSSource(); + static bool IsPortableInstall(); + static void GetPortableInstallFilename( +#ifdef OS_WIN + std::wstring& filename +#else + std::string& filename +#endif + ); + static CefString AppGetAppDirectory(); static CefString AppGetSupportDirectory(); static CefString AppGetDocumentsDirectory(); diff --git a/appshell/client_app_mac.mm b/appshell/client_app_mac.mm index 7894b6703..90e189b99 100644 --- a/appshell/client_app_mac.mm +++ b/appshell/client_app_mac.mm @@ -83,12 +83,60 @@ return result; } +// check if this is a portable installation +// which is marked by the existence of a file named "makePortable" in the same folder as .app. +bool ClientApp::IsPortableInstall() +{ + std::string filename; + GetPortableInstallFilename(filename); + NSString * nsFilename = [NSString stringWithUTF8String:filename.c_str()]; + return [[NSFileManager defaultManager] fileExistsAtPath:nsFilename]; +} -CefString ClientApp::AppGetSupportDirectory() { - NSString *libraryDirectory = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - NSString *supportDirectory = [NSString stringWithFormat:@"%@/%@%@", libraryDirectory, GROUP_NAME, APP_NAME]; - - return CefString([supportDirectory UTF8String]); +// return the name of the portable install data file +void ClientApp::GetPortableInstallFilename(std::string& filename) +{ + // the existence of this file in the same folder as the Brackets application will + // cause the application to be run in a "portable" state + filename = ClientApp::AppGetAppDirectory(); + filename += "/"; + filename += MAKEPORTABLE_BRACKETS_FILENAME; +} + +// returns the directory to which the app has been installed +CefString ClientApp::AppGetAppDirectory() +{ + // find the path-only of Brackets.app. Need to iterate in case we're called from the helper.app. + NSString * applicationPath = [[NSBundle mainBundle] bundlePath]; + NSString * appFilename = [applicationPath lastPathComponent]; + while ([appFilename length] && ![appFilename isEqualToString:APP_NAME @".app"]) + { + applicationPath = [applicationPath stringByDeletingLastPathComponent]; + appFilename = [applicationPath lastPathComponent]; + } + applicationPath = [applicationPath stringByDeletingLastPathComponent]; + + return CefString([applicationPath UTF8String]); +} + +CefString ClientApp::AppGetSupportDirectory() +{ + if (!IsPortableInstall()) + { + // for normal installations, use the Library folder + NSString *libraryDirectory = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString *supportDirectory = [NSString stringWithFormat:@"%@/%@%@", libraryDirectory, GROUP_NAME, APP_NAME]; + return CefString([supportDirectory UTF8String]); + } + else + { + // for portable installations, use the app's installed folder + std::string strAppDir = ClientApp::AppGetAppDirectory(); + strAppDir += "/"; + NSString * appDir = [NSString stringWithUTF8String:strAppDir.c_str()]; + appDir = [appDir stringByAppendingString:APP_NAME]; + return CefString([appDir UTF8String]); + } } CefString ClientApp::AppGetDocumentsDirectory() { diff --git a/appshell/client_app_win.cpp b/appshell/client_app_win.cpp index f55a1fa33..1cecce497 100644 --- a/appshell/client_app_win.cpp +++ b/appshell/client_app_win.cpp @@ -90,12 +90,46 @@ double ClientApp::GetElapsedMilliseconds() return (timeGetTime() - g_appStartupTime); } +// returns the directory to which the app has been installed +CefString ClientApp::AppGetAppDirectory() +{ + // find the full pathname of the app .exe + std::wstring appPath; + HMODULE hModule = ::GetModuleHandle(NULL); + if (hModule) + { + WCHAR filename[MAX_PATH+1] = {0}; + ::GetModuleFileName(hModule, filename, MAX_PATH); + appPath = filename; + + // strip off the filename and extension + int idx = appPath.rfind('\\'); + if (idx >= 0) + appPath = appPath.substr(0, idx); + } + + // Convert '\\' to '/' + replace(appPath.begin(), appPath.end(), '\\', '/'); + + return CefString(appPath); +} + CefString ClientApp::AppGetSupportDirectory() { - wchar_t dataPath[MAX_UNC_PATH]; - SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, dataPath); - - std::wstring appSupportPath = dataPath; + std::wstring appSupportPath; + if (!IsPortableInstall()) + { + // for normal installations, use the user's APPDATA folder + wchar_t dataPath[MAX_UNC_PATH]; + SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, dataPath); + appSupportPath = dataPath; + } + else + { + // for portable installations, use the app's installed folder + appSupportPath = ClientApp::AppGetAppDirectory(); + } + appSupportPath += L"\\" GROUP_NAME APP_NAME; // Convert '\\' to '/' @@ -116,3 +150,33 @@ CefString ClientApp::AppGetDocumentsDirectory() return CefString(appUserDocuments); } + + +// check if this is a portable installation +// to be a portable installation, the installer should write the empty file to the app folder +bool ClientApp::IsPortableInstall() +{ + typedef enum { UNINITIALIZED = -1, ISNOTPORTABLE, ISPORTABLE } ePortableFlag; + static ePortableFlag isPortableInstall = UNINITIALIZED; + + if (isPortableInstall == UNINITIALIZED) + { + std::wstring filename; + GetPortableInstallFilename(filename); + HANDLE hFile = ::CreateFile(filename.c_str(), GENERIC_READ, + FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + isPortableInstall = (INVALID_HANDLE_VALUE != hFile) ? ISPORTABLE : ISNOTPORTABLE; + ::CloseHandle(hFile); + } + return (isPortableInstall == ISPORTABLE); +} + +// return the name of the portable install data file +void ClientApp::GetPortableInstallFilename(std::wstring& filename) +{ + // the existence of this file in the same folder as the Brackets application will + // cause the application to be run in a "portable" state + filename = ClientApp::AppGetAppDirectory(); + filename += L"/"; + filename += MAKEPORTABLE_BRACKETS_FILENAME; +} \ No newline at end of file diff --git a/appshell/config.h b/appshell/config.h index 645b5fe9e..b9da9fe5e 100644 --- a/appshell/config.h +++ b/appshell/config.h @@ -72,3 +72,14 @@ #define CUSTOM_TRAFFIC_LIGHTS #define LIGHT_CAPTION_TEXT +// filename of Brackets' make portable switch +// the existence of this file in the same folder as the Brackets application will +// cause the application to be run in a "portable" state +#ifdef OS_WIN +#define MAKEPORTABLE_BRACKETS_FILENAME L"portable" +#else +#define MAKEPORTABLE_BRACKETS_FILENAME "portable" +#endif //OS_WIN +#define MAKEPORTABLE_BRACKETS_SIGNATURE_STRING "BracketsPortable" +#define MAKEPORTABLE_BRACKETS_VERSION_1 1 +#define MAKEPORTABLE_BRACKETS_VERSION_CURRENT MAKEPORTABLE_BRACKETS_VERSION_1