Skip to content
This repository has been archived by the owner on Sep 2, 2021. It is now read-only.

Implement Brackets Portable Build on Mac and Windows #333

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 111 additions & 32 deletions appshell/cef_main_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,21 @@
#include "resource.h"
#include "native_menu_model.h"
#include "config.h"
#include <sstream>

// external
extern HINSTANCE hInst;
extern CefRefPtr<ClientHandler> 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";
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 --
Expand All @@ -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);
Expand Down
5 changes: 4 additions & 1 deletion appshell/cef_main_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
};

9 changes: 9 additions & 0 deletions appshell/client_app.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
58 changes: 53 additions & 5 deletions appshell/client_app_mac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
72 changes: 68 additions & 4 deletions appshell/client_app_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 '/'
Expand All @@ -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;
}
11 changes: 11 additions & 0 deletions appshell/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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