21

我正在使用 python 和 Qt 编写一个 GUI 应用程序。当我在 Mac 上启动我的应用程序时,屏幕顶部 Mac 菜单栏中的第一个菜单项是“Python”。我希望那里的应用程序名称是我的应用程序的名称。我怎样才能在那里获得我的程序名称?

下面的演示程序创建一个带有两个菜单的窗口:“Python”和“Foo”。我不喜欢这样,因为无论我是用 python 还是 COBOL 编写应用程序,对我的用户来说都没有区别。相反,我想要菜单“MyApp”和“Foo”。

#!/usr/bin/python

# This example demonstrates unwanted "Python"
# application menu name on Mac.

# Makes no difference whether we use PySide or PyQt4
from PySide.QtGui import *
# from PyQt4.QtGui import *

import sys

app = QApplication(sys.argv)
# Mac menubar application menu is always "Python".
# I want "DesiredAppTitle" instead.
# setApplicationName() does not affect Mac menu bar.
app.setApplicationName("DesiredAppTitle")
win = QMainWindow()
# need None parent for menubar on Mac to get custom menus at all
mbar = QMenuBar()
# Add a custom menu to menubar.
fooMenu = QMenu(mbar)
fooMenu.setTitle("Foo")
mbar.addAction(fooMenu.menuAction())
win.setMenuBar(mbar)
win.show()
sys.exit(app.exec_())

如何在 Mac 上更改该应用程序菜单名称?编辑:如果可能的话,我宁愿继续使用系统 python(或用户 PATH 上的任何 python)。

4

6 回答 6

9

您似乎需要一个 OSX .app 才能工作,因为其中的Info.plist文件包含放置在那里的应用程序的用户可见名称。这默认为 Python,这是您在程序菜单中看到的标题。这篇博文概述了您需要采取的步骤,而OSX 开发人员库的属性列表中有您需要填写的文档。

于 2011-10-24T12:43:52.003 回答
5

我找到了这个问题的答案的核心。因为我想将赏金奖励给我自己以外的人(我是 OP),所以请任何人都使用这个内核并将其详细说明为您自己的更完整的答案。

我可以将应用程序菜单设置为“MyApp”,如下所示:

ln -s /System/Library/Frameworks/Python.framework/Versions/2.6/Resources/Python.app/Contents/MacOS/Python MyApp

./MyApp MyApp.py

要使其正常工作,需要两个要素:

  1. 符号链接必须命名为“MyApp”(或您希望出现在应用程序菜单中的任何名称)
  2. 符号链接必须指向系统 python 应用程序包中的 Python 可执行文件。例如,如果您链​​接到 /usr/bin/python,它就不起作用。

必须有一种聪明的方法来创建一个应用程序包或 shell 脚本,以一种健壮的方式利用这种机制......

于 2011-10-24T18:23:50.707 回答
4

如果您打算分发应用程序,则不能保证对 python 二进制文件进行符号链接,因为通常在开发机器上系统 python 不是默认 python,并且普通用户很可能不会安装 Qt 和 PyQt。

一种更可靠的方法是使用本机 OSX 引导二进制文件来负责启动您的 PyQt 应用程序。py2app和PyInstaller可以生成本机包装器。虽然py2app在创建别名应用程序方面效果很好,即符号链接到系统上现有文件的包装器,但让它仅捆绑所需的 PyQt 依赖项被证明是不平凡的。此外,如果您将py2app生成的带有标志的“别名应用程序”--aliased移动到另一个文件夹,它将停止工作,因为符号链接与您最初运行构建脚本的文件夹相关。

PyInstaller

PyInstaller开箱即用,我最终得到了一个 OSX 包,其中包含大约 16MB 的依赖项。

将 python 脚本捆绑到独立的 OSX 应用程序:

pyinstaller -w --noconfirm -i=myappicon.icns --clean -F myscript.py

.setWindowTitle()这会生成一个独立的包,它将在 OSX 标题栏中显示您在应用程序名称中的任何内容。该-w开关很重要,因为它将创建一个带有适当.plist文件的 MacOS 应用程序包。

注意:由于某种原因,pyinstaller安装 via的版本pip对我不起作用。所以我删除了原来的 pip 版本( ),并从 githubpip uninstall pyinstaller安装了最新的分支:develop

pip install -v -e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=pyinstaller-github

在那之后,它就像一个魅力。

Mac 自动机

创建应用程序包装器的另一个选项是使用Automator(在 Spotlight 中键入名称),转到 File > New > Application > 搜索并将Run shell script拖到编辑器中,然后在Shell下选择bash, python。您还可以使用Run Applescript并使用 Apple 脚本引导您的应用程序 - 例如,如果您需要询问用户密码以便以提升的权限运行,这将非常有用。

一个更好的 Automator 替代品是Platypus - 一个免费的开源应用程序,它将各种类型的脚本包装到 OSX 应用程序中,并且还提供了一些额外的功能,如 gui 文本输出窗口、以管理员权限运行、设置自定义图标等。可在github上找到。

还可以选择在 Xcode 中创建准系统 OSX 应用程序,该应用程序将启动您的 PyQt 脚本(这里有一些示例),并执行您的应用程序所需的其他自定义任务。

注意:请记住,使用预定义解释器调用您的 python 代码的应用程序包是一个“浅层”应用程序包,它将取决于您在本地安装的任何 python 依赖项。很可能无法在其他人的 Mac 上运行 OOTB。

努伊特卡

您可能还想尝试Nuitka ( pip3 install nuitka),它旨在成为一个通过将您的 python 代码转换为 C++ 然后编译它来构建真正本机可执行文件的项目。

使用以下方式构建本机应用程序的示例nuitka

nuitka3 --follow-imports --python-flag=no_site --verbose --standalone --show-progress --show-modules --output-dir=my_build_dir myscript.py

这将在文件夹中创建带有库的 mac 可执行my_build_dir文件。您需要自己将它们包装在 MacOS 应用程序包中,例如使用PlatypusAutomator

于 2015-12-08T16:48:58.293 回答
2

我知道这不是 OP 想要的,但认为它可以为寻找解决这个问题的方法增加一些价值。

您可以使文件菜单出现在您的窗口 UI 中,而不是在本机 Mac OS X 系统菜单栏中:

menubar.setNativeMenuBar(False)

这将导致菜单栏出现在例如 Windows 中。

于 2016-09-28T12:51:43.337 回答
1

我宁愿最终用户可以访问我的 Python 代码,以便他们查看并改进代码,所以我对隐藏代码的选项不感兴趣。我发现我可以让 wx 应用程序(尚未尝试使用 Qt)使用不同的标准 Python 解释器以指定的应用程序名称运行,方法是在我安装它们时将它们包装成一个包(例如,作为 conda 包安装过程的一部分) )。这是一个简短的脚本,它使用执行脚本的 Python 创建具有指定名称的 .app 包。使用创建的软链接(参见下面的 pyAlias)运行脚本,如下所示

./MyApplication.app/Contents/MacOS/MyApplication /path/MyApplication.py 

做这项工作。

from __future__ import division, print_function
import os,sys
appName = 'MyApplication'    # name of app
scriptdir = '.'
iconfile = os.path.join(scriptdir,'MyApplication.icns') # optional icon file

if __name__ == '__main__':
    wrapApp = os.path.join(scriptdir,appName+'.app','Contents')
    os.makedirs(wrapApp)
    wrapPy = os.path.join(wrapApp,'MacOS')
    os.makedirs(wrapPy)
    fp = open(os.path.join(wrapApp,'PkgInfo'),'w')
    fp.write('APPL????\n')
    fp.close()
    fp = open(os.path.join(wrapApp,'Info.plist'),'w')
    fp.write('''<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">\n<plist version="0.9">\n<dict>\n        <key>CFBundleIconFile</key>\n        <string></string>\n        <key>CFBundlePackageType</key>\n        <string>APPL</string>\n        <key>CFBundleGetInfoString</key>\n        <string>Created in makeApp.py (Brian Toby/QMake</string>\n        <key>CFBundleSignature</key>\n        <string>????</string>\n        <key>CFBundleExecutable</key>\n        <string>{:}</string>\n        <key>CFBundleIdentifier</key>\n        <string>com.continuum.python</string>\n        <key>NSPrincipalClass</key>\n        <string>NSApplication</string>\n</dict>\n</plist>\n'''.format(appName))
    fp.close()
    pyAlias = os.path.join(wrapPy,appName)
    pythonpath = os.path.realpath(sys.executable)
    os.symlink(pythonpath,pyAlias)
    if os.path.exists(iconfile):
        shutil.copyfile(iconfile,oldicon)
    print('Use',pyAlias,'to run wxPython scripts')

以下是一个较旧的答案,我将保留任何历史价值。现在在我的应用程序中,我使用了它们之间的混合,在其中我创建了一个拖放式 AppleScript 包(如下所示),我在其中放置了一个指向 Python 的软链接(命名为与我的应用程序匹配),如上所示。 基于 Christopher Bruns 的回答,以及“如何通过 Python 为 Python 脚本创建 Mac 应用程序包”中的脚本,这里是一个 Python 脚本,它为用户 Python 脚本创建一个包(应用程序),它将显示应用程序名称而不是菜单中的“Python”。为此,它会尝试定位 Python 的捆绑版本,并使用应用程序名称符号链接到该版本。我用 wxpython 脚本对其进行了测试,但它也应该适用于 Qt。

用户脚本从其原始位置运行,而不是放置在应用程序中。如果您想将您的脚本(s)放入一个包(与 python 一起)以进行重新分发,请参阅py2app

#!/usr/bin/env python
'''This creates an app to launch a python script. The app is
created in the directory where python is called. A version of Python
is created via a softlink, named to match the app, which means that
the name of the app rather than Python shows up as the name in the
menu bar, etc, but this requires locating an app version of Python
(expected name .../Resources/Python.app/Contents/MacOS/Python in
directory tree of calling python interpreter).

Run this script with one or two arguments:
    <python script>
    <project name>
The script path may be specified relative to the current path or given
an absolute path, but will be accessed via an absolute path. If the
project name is not specified, it will be taken from the root name of
the script.
'''
import sys, os, os.path, stat
def Usage():
    print("\n\tUsage: python "+sys.argv[0]+" <python script> [<project name>]\n")
    sys.exit()

version = "1.0.0"
bundleIdentifier = "org.test.test"

if not 2 <= len(sys.argv) <= 3:
    Usage()

script = os.path.abspath(sys.argv[1])
if not os.path.exists(script):
    print("\nFile "+script+" not found")
    Usage()
if os.path.splitext(script)[1].lower() != '.py':
    print("\nScript "+script+" does not have extension .py")
    Usage()

if len(sys.argv) == 3:
    project = sys.argv[2]
else:
    project = os.path.splitext(os.path.split(script)[1])[0]

# find the python application; must be an OS X app
pythonpath,top = os.path.split(os.path.realpath(sys.executable))
while top:
    if 'Resources' in pythonpath:
        pass
    elif os.path.exists(os.path.join(pythonpath,'Resources')):
        break
    pythonpath,top = os.path.split(pythonpath)
else:
    print("\nSorry, failed to find a Resources directory associated with "+str(sys.executable))
    sys.exit()
pythonapp = os.path.join(pythonpath,'Resources','Python.app','Contents','MacOS','Python')
if not os.path.exists(pythonapp): 
    print("\nSorry, failed to find a Python app in "+str(pythonapp))
    sys.exit()

apppath = os.path.abspath(os.path.join('.',project+".app"))
newpython =  os.path.join(apppath,"Contents","MacOS",project)
projectversion = project + " " + version
if os.path.exists(apppath):
    print("\nSorry, an app named "+project+" exists in this location ("+str(apppath)+")")
    sys.exit()

os.makedirs(os.path.join(apppath,"Contents","MacOS"))

f = open(os.path.join(apppath,"Contents","Info.plist"), "w")
f.write('''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//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>CFBundleExecutable</key>
    <string>main.sh</string>
    <key>CFBundleGetInfoString</key>
    <string>{:}</string>
    <key>CFBundleIconFile</key>
    <string>app.icns</string>
    <key>CFBundleIdentifier</key>
    <string>{:}</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>{:}</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>{:}</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>{:}</string>
    <key>NSAppleScriptEnabled</key>
    <string>YES</string>
    <key>NSMainNibFile</key>
    <string>MainMenu</string>
    <key>NSPrincipalClass</key>
    <string>NSApplication</string>
</dict>
</plist>
'''.format(projectversion, bundleIdentifier, project, projectversion, version)
    )
f.close()

# not sure what this file does
f = open(os.path.join(apppath,'Contents','PkgInfo'), "w")
f.write("APPL????")
f.close()
# create a link to the python app, but named to match the project
os.symlink(pythonapp,newpython)
# create a script that launches python with the requested app
shell = os.path.join(apppath,"Contents","MacOS","main.sh")
# create a short shell script
f = open(shell, "w")
f.write('#!/bin/sh\nexec "'+newpython+'" "'+script+'"\n')
f.close()
os.chmod(shell, os.stat(shell).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
于 2013-07-02T19:40:12.960 回答
-3

我认为你需要这样做:

win.setWindowTitle("MyApp")

编辑:这是为 PyQt 准备的。我不知道 PySide 中的等价物。

于 2011-10-24T13:58:48.790 回答