using bundles (and LSUIElement=1) to better manage OSX dock icons

This commit is contained in:
David Rose 2009-10-24 23:24:34 +00:00
parent 270d79658e
commit 884c389bad
11 changed files with 194 additions and 44 deletions

View File

@ -39,7 +39,8 @@ class Packager:
newName = None, deleteTemp = False,
explicit = False, compress = None, extract = None,
text = None, unprocessed = None,
executable = None, platformSpecific = None):
executable = None, dependencyDir = None,
platformSpecific = None):
assert isinstance(filename, Filename)
self.filename = Filename(filename)
self.newName = newName
@ -50,6 +51,7 @@ class Packager:
self.text = text
self.unprocessed = unprocessed
self.executable = executable
self.dependencyDir = dependencyDir
self.platformSpecific = platformSpecific
if not self.newName:
@ -73,6 +75,11 @@ class Packager:
if self.executable is None:
self.executable = (ext in packager.executableExtensions)
if self.executable and self.dependencyDir is None:
# By default, install executable dependencies in the
# same directory with the executable itself.
self.dependencyDir = Filename(self.newName).getDirname()
if self.extract is None:
self.extract = self.executable or (ext in packager.extractExtensions)
if self.platformSpecific is None:
@ -779,7 +786,9 @@ class Packager:
for filename in filenames:
filename = Filename.fromOsSpecific(filename)
filename.resolveFilename(path)
self.addFile(filename, newName = filename.getBasename(),
newName = Filename(file.dependencyDir, filename.getBasename())
self.addFile(filename, newName = newName.cStr(),
explicit = False, executable = True)
def __parseDependenciesWindows(self, tempFile):
@ -851,13 +860,19 @@ class Packager:
""" Copies the given library file to a temporary directory,
and alters the dependencies so that it doesn't contain absolute
framework dependencies. """
# Copy the file to a temporary location because we don't want
# to modify the original (there's a big chance that we break it)
assert file.filename.exists(), "File doesn't exist: %s" % ffilename
tmpfile = Filename.fromOsSpecific("/tmp/p3d_" + hashlib.md5(file.filename.toOsSpecific()).hexdigest())
if not tmpfile.exists():
if not file.deleteTemp:
# Copy the file to a temporary location because we
# don't want to modify the original (there's a big
# chance that we break it).
# Copy it every time, because the source file might
# have changed since last time we ran.
assert file.filename.exists(), "File doesn't exist: %s" % ffilename
tmpfile = Filename.temporary('', "p3d_" + file.filename.getBasename())
file.filename.copyTo(tmpfile)
file.filename = tmpfile
file.deleteTemp = True
# Alter the dependencies to have a relative path rather than absolute
for filename in framework_deps:
@ -865,7 +880,6 @@ class Packager:
os.system('install_name_tool -id "%s" "%s"' % (os.path.basename(filename), tmpfile.toOsSpecific()))
else:
os.system('install_name_tool -change "%s" "%s" "%s"' % (filename, os.path.basename(filename), tmpfile.toOsSpecific()))
self.sourceFilenames[file.filename].filename = tmpfile
def __addImplicitDependenciesOSX(self):
""" Walks through the list of files, looking for dylib's
@ -915,7 +929,7 @@ class Packager:
if len(framework_deps) > 0:
# Fixes dependencies like @executable_path/../Library/Frameworks/Cg.framework/Cg
self.__alterFrameworkDependencies(file, framework_deps)
for filename in filenames:
if '.framework/' in filename:
# It references a framework, and besides the fact
@ -926,7 +940,9 @@ class Packager:
# It's just a normal library - find it on the path.
filename = Filename.fromOsSpecific(filename)
filename.resolveFilename(path)
self.addFile(filename, newName = filename.getBasename(),
newName = Filename(file.dependencyDir, filename.getBasename())
self.addFile(filename, newName = newName.cStr(),
explicit = False, executable = True)
def __parseDependenciesOSX(self, tempFile):
@ -998,7 +1014,9 @@ class Packager:
for filename in filenames:
filename = Filename.fromOsSpecific(filename)
filename.resolveFilename(path)
self.addFile(filename, newName = filename.getBasename(),
newName = Filename(file.dependencyDir, filename.getBasename())
self.addFile(filename, newName = newName.cStr(),
explicit = False, executable = True)
def __parseDependenciesPosix(self, tempFile):
@ -2517,6 +2535,22 @@ class Packager:
# an associated dynamic library. Note that the .exe and .dll
# extensions are automatically replaced with the appropriate
# platform-specific extensions.
if self.platform.startswith('osx'):
# On Mac, we package up a P3DPython.app bundle. This
# includes specifications in the plist file to avoid
# creating a dock icon and stuff.
# Find p3dpython.plist in the direct source tree.
import direct
plist = Filename(direct.__path__[0], 'plugin/p3dpython.plist')
self.do_makeBundle('P3DPython.app', plist, executable = 'p3dpython',
dependencyDir = '')
else:
# Anywhere else, we just ship the executable file p3dcert.exe.
self.do_file('p3dcert.exe')
self.do_file('p3dpython.exe')
if PandaSystem.getPlatform().startswith('win'):
self.do_file('p3dpythonw.exe')
@ -2574,6 +2608,27 @@ class Packager:
freezer.reset()
package.mainModule = None
def do_makeBundle(self, bundleName, plist, executable = None,
resources = None, dependencyDir = None):
""" Constructs a minimal OSX "bundle" consisting of an
executable and a plist file, with optional resource files
(such as icons), and adds it to the package under the given
name. """
contents = bundleName + '/Contents'
self.addFiles([plist], newName = contents + '/Info.plist',
extract = True)
if executable:
basename = Filename(executable).getBasename()
self.addFiles([executable], newName = contents + '/MacOS/' + basename,
extract = True, executable = True, dependencyDir = dependencyDir)
if resources:
self.addFiles(resources, newDir = contents + '/Resources',
extract = True, dependencyDir = dependencyDir)
def do_file(self, *args, **kw):
""" Adds the indicated file or files to the current package.
See addFiles(). """
@ -2582,7 +2637,7 @@ class Packager:
def addFiles(self, filenames, text = None, newName = None,
newDir = None, extract = None, executable = None,
deleteTemp = False, literal = False):
deleteTemp = False, literal = False, dependencyDir = None):
""" Adds the indicated arbitrary files to the current package.
@ -2705,7 +2760,8 @@ class Packager:
self.currentPackage.addFile(
filename, newName = name, extract = extract,
explicit = explicit, executable = executable,
text = text, deleteTemp = deleteTemp)
text = text, deleteTemp = deleteTemp,
dependencyDir = dependencyDir)
def do_exclude(self, filename):
""" Marks the indicated filename as not to be included. The

View File

@ -69,4 +69,16 @@ class p3dcert(package):
# user to accept or deny unknown applications, is its own package.
config(display_name = "Authorization Dialog")
file('p3dcert.exe')
if platform.startswith('osx'):
# On Mac, we package up a P3DCert.app bundle. This includes
# specifications in the plist file to avoid creating a dock
# icon and stuff.
# Find p3dcert.plist in the direct source tree.
import direct
plist = Filename(direct.__path__[0], 'plugin/p3dcert.plist')
makeBundle('P3DCert.app', plist, executable = 'p3dcert')
else:
# Anywhere else, we just ship the executable file p3dcert.exe.
file('p3dcert.exe')

View File

@ -155,6 +155,10 @@ start_p3dcert() {
#ifdef _WIN32
_p3dcert_exe += ".exe";
#endif
#ifdef __APPLE__
// On OSX, run from the packaged bundle.
_p3dcert_exe = root_dir + "/P3DCert.app/Contents/MacOS/p3dcert";
#endif
// Populate the new process' environment.
_env = string();

View File

@ -17,11 +17,6 @@
#include "wx/filename.h"
#include "ca_bundle_data_src.c"
#ifdef __WXMAC__
#include <Carbon/Carbon.h>
extern "C" { void CPSEnableForegroundOperation(ProcessSerialNumber* psn); }
#endif
static const wxString
self_signed_cert_text =
@ -96,19 +91,11 @@ OnInit() {
OpenSSL_add_all_algorithms();
#ifdef __WXMAC__
// Enable the dialog to go to the foreground on Mac, even without
// having to wrap it up in a bundle.
ProcessSerialNumber psn;
GetCurrentProcess(&psn);
CPSEnableForegroundOperation(&psn);
SetFrontProcess(&psn);
#endif
AuthDialog *dialog = new AuthDialog(_cert_filename, _cert_dir);
SetTopWindow(dialog);
dialog->Show(true);
dialog->SetFocus();
dialog->Raise();
// Return true to enter the main loop and wait for user input.
return true;
@ -155,7 +142,11 @@ END_EVENT_TABLE()
////////////////////////////////////////////////////////////////////
AuthDialog::
AuthDialog(const wxString &cert_filename, const wxString &cert_dir) :
wxDialog(NULL, wxID_ANY, _T("New Panda3D Application"), wxDefaultPosition),
// I hate stay-on-top dialogs, but if we don't set this flag, it
// doesn't come to the foreground on OSX, and might be lost behind
// the browser window.
wxDialog(NULL, wxID_ANY, _T("New Panda3D Application"), wxDefaultPosition,
wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxSTAY_ON_TOP),
_cert_dir(cert_dir)
{
_view_cert_dialog = NULL;

View File

@ -786,6 +786,10 @@ start_p3dpython(P3DInstance *inst) {
_p3dpython_exe = P3D_PLUGIN_P3DPYTHON;
if (_p3dpython_exe.empty()) {
_p3dpython_exe = _python_root_dir + "/p3dpython";
#ifdef __APPLE__
// On OSX, run from the packaged bundle.
_p3dpython_exe = _python_root_dir + "/P3DPython.app/Contents/MacOS/p3dpython";
#endif
}
#ifdef _WIN32
if (!inst_mgr->get_console_environment()) {

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>P3DCert</string>
<key>CFBundleExecutable</key>
<string>p3dcert</string>
<key>CFBundleIdentifier</key>
<string>org.panda3d.runtime.p3dcert</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>P3DCert</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.9.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.9.3</string>
<key>LSUIElement</key>
<string>1</string>
<key>LSHasLocalizedDisplayName</key>
<false/>
<key>NSAppleScriptEnabled</key>
<false/>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>P3DPython</string>
<key>CFBundleExecutable</key>
<string>p3dpython</string>
<key>CFBundleIdentifier</key>
<string>org.panda3d.runtime.p3dpython</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>P3DPython</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.9.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.9.3</string>
<key>LSUIElement</key>
<string>1</string>
<key>LSHasLocalizedDisplayName</key>
<false/>
<key>NSAppleScriptEnabled</key>
<false/>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@ -32,7 +32,7 @@
<key>CFBundleIconFile</key>
<string>panda3d.icns</string>
<key>CFBundleIdentifier</key>
<string>org.panda3d.runtime</string>
<string>org.panda3d.runtime.panda3d_mac</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>

View File

@ -11,15 +11,6 @@
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////
#ifdef __APPLE__
// We have to include this before we include any Panda libraries,
// because one of the things we pick up in Panda defines a macro for
// TCP_NODELAY and friends, causing heartaches for the header files
// picked up here.
#include <Carbon/Carbon.h>
extern "C" { void CPSEnableForegroundOperation(ProcessSerialNumber* psn); }
#endif
#include "showBase.h"
@ -68,6 +59,9 @@ get_config_showbase() {
// At the moment, this is a no-op except on Mac.
void
init_app_for_gui() {
// Actually, this may not be necessary after all. Let's assume the
// user will always be running from a bundle or from pythonw.
/*
static bool initted_for_gui = false;
if (!initted_for_gui) {
initted_for_gui = true;
@ -79,6 +73,7 @@ init_app_for_gui() {
SetFrontProcess(&psn);
#endif // IS_OSX
}
*/
}
// klunky interface since we cant pass array from python->C++ to use verify_window_sizes directly

View File

@ -1077,7 +1077,17 @@ os_open_window(WindowProperties &req_properties) {
GlobalInits = true;
ProcessSerialNumber psn = { 0, kCurrentProcess };
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
// Determine if we're running from a bundle.
CFDictionaryRef dref =
ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask);
// If the dictionary doesn't have "BundlePath", then we're not
// running from a bundle, and we need to call TransformProcessType
// to make the process a "foreground" application, with its own
// icon in the dock and such.
if (!CFDictionaryContainsKey(dref, CFSTR("BundlePath"))) {
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
}
SetFrontProcess(&psn);
}

View File

@ -880,8 +880,18 @@ bool TinyOsxGraphicsWindow::OSOpenWindow(WindowProperties &req_properties)
GlobalInits = true;
ProcessSerialNumber psn = { 0, kCurrentProcess };
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
SetFrontProcess(&psn);
// Determine if we're running from a bundle.
CFDictionaryRef dref =
ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask);
// If the dictionary doesn't have "BundlePath", then we're not
// running from a bundle, and we need to call TransformProcessType
// to make the process a "foreground" application, with its own
// icon in the dock and such.
if (!CFDictionaryContainsKey(dref, CFSTR("BundlePath"))) {
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
}
SetFrontProcess(&psn);
}
if (req_properties.has_fullscreen() && req_properties.get_fullscreen())