Установка в строке приложения меню Mac OSX пункта меню, отличного от "Python", в моем приложении Python Qt

Я пишу приложение с графическим интерфейсом, используя Python и Qt. Когда я запускаю свое приложение на 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? РЕДАКТИРОВАТЬ: Я бы предпочел продолжать использовать системный питон (или любой другой питон на пользовательском PATH), если это возможно.

6 ответов

Вам, кажется, нужен OSX .app для этого, так как Info.plist Файл там содержит видимое пользователем имя для приложения, которое помещается туда. По умолчанию это Python, то есть заголовок, который вы видите для меню программы. В этой записи блога описаны шаги, которые необходимо предпринять, в то время как в библиотеке разработчика OSX есть документы в списке свойств, которые необходимо заполнить.

Я нашел ядро ​​ответа на этот вопрос. Поскольку я хочу вручить награду кому-то, кроме меня (я являюсь ОП), пожалуйста, любой, возьмите это ядро ​​и доработайте его до более полного собственного ответа.

Я могу получить меню приложения "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.

Должен быть умный способ создания пакета приложения или сценария оболочки, который надежно использует этот механизм...

Если вы намерены распространять приложение, то символическая ссылка на двоичный файл python не гарантированно будет работать, учитывая, что обычно на машинах разработки системный python не является python по умолчанию, и обычным пользователям, скорее всего, не будут установлены Qt и PyQt.

Более надежный подход состоит в том, чтобы иметь собственный загрузочный двоичный файл OSX, который позаботится о запуске вашего приложения PyQt. Как py2app, так и PyInstaller могут генерировать нативные оболочки. Хотя py2app хорошо работал для создания приложения с псевдонимами, то есть обертки, которая символически ссылается на существующие файлы в вашей системе, заставить его связывать только требуемые зависимости PyQt оказалось нетривиальным. Кроме того, "псевдоним приложения", сгенерированный py2app с --aliased Флаг перестанет работать, если вы переместите его в другую папку, так как символические ссылки относятся к папке, в которой вы изначально запускали скрипт сборки.

PyInstaller работал "из коробки", и я получил пакет OSX, который включал зависимости размером около 16 МБ.

Скомпилируйте скрипт на python в отдельное приложение OSX:

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

Это создаст автономный пакет, который будет отображать все, что у вас есть в вашем .setWindowTitle() как имя приложения в строке заголовка OSX.

NB: по какой-то причине версия pyinstaller установлен через pip не работал для меня. Поэтому я удалил оригинальную версию пипа (pip uninstall pyinstaller) и установил последнюю версию develop ветка из GitHub с:

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

После этого он работал как шарм.

Еще одним вариантом создания оболочки приложения является использование Automator (введите имя в Spotlight), перейдите в меню "Файл"> "Создать"> "Приложение"> "Поиск" и перетащите сценарий "Выполнить оболочку" в редактор и в разделе " Shell" выберите bash, python, Вы также можете использовать " Запустить Applescript" и запустить приложение с помощью Apple-скрипта - это может быть полезно, например, если вам нужно запросить пароль пользователя для запуска с повышенными привилегиями.

Лучшей альтернативой Automator является Platypus - бесплатное приложение с открытым исходным кодом, которое включает различные типы сценариев в приложения OSX, а также предоставляет некоторые дополнительные функции, такие как окна вывода текста графического интерфейса пользователя, запуск с правами администратора, настройка пользовательских значков и т. Д. Код доступен на github.

И, наконец, есть возможность создать в Xcode пустое OSX-приложение, которое запустит ваш скрипт PyQt (некоторые примеры здесь) и выполнит другие пользовательские задачи, необходимые для вашего приложения.

Я знаю, что это не то, чего точно хочет ОП, но подумал, что это может добавить некоторую ценность поиску решения этой проблемы.

Вы можете сделать так, чтобы файловые меню появлялись в вашем пользовательском интерфейсе окна, а не в родной системной меню Mac OS X:

menubar.setNativeMenuBar(False)

Это приведет к тому, что строка меню будет выглядеть так же, как в Windows.

Основываясь на ответе Кристофера Брунса, а также на сценарии из раздела "Как создать пакет приложений Mac для сценария Python через Python", приведен сценарий Python, который создает пакет (приложение) для пользовательского сценария Python, в котором будет отображаться имя приложения. а не "питон" в меню. Для этого он пытается найти пакетную версию Python и ссылается на нее с именем приложения. Я протестировал его с помощью скрипта wxpython, но он должен работать и для Qt.

Пользовательский скрипт запускается из своего исходного местоположения, а не помещает его в приложение. Если вы хотите поместить свои сценарии в пакет (вместе с 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)

Я думаю, что вам нужно сделать это:

win.setWindowTitle("MyApp")

Изменить: это для PyQt. Я не знаю эквивалент в PySide.

Другие вопросы по тегам