Установка пункта меню в строке меню приложения 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), если это возможно.


person Christopher Bruns    schedule 19.10.2011    source источник
comment
Как вы начинаете свою программу? Я очень мало знаю о Mac, но похоже, что здесь просто помещается значение sys.argv [0].   -  person Falmarri    schedule 20.10.2011
comment
Я запускаю программу, набирая python MyApp.py или ./MyApp.py. В обоих случаях sys.argv [0] - это MyApp.py или ./MyApp.py. Python не является частью argv.   -  person Christopher Bruns    schedule 20.10.2011
comment
@Christpher Bruns: Хм, ты прав. Я не знаю, почему я думал, что argv python предоставил python в качестве argv[0]. Я почти ничего не знаю о Mac, так что у меня нет идей.   -  person Falmarri    schedule 20.10.2011


Ответы (6)


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

person jro    schedule 24.10.2011
comment
При необходимости я готов создать пакет приложений. Но я очень недоволен тем, что вы не можете использовать вывод этого сообщения в блоге о системном питоне. Неужели нет возможности использовать системный питон и не иметь пункта меню Python? - person Christopher Bruns; 24.10.2011
comment
Видимо ... процитировать другой источник: Совершенно необходимо настроить среду для использования правильной версии Python перед построением зависимостей на основе Python. Из того, что я могу вспомнить из задания некоторое время назад, этот способ наверняка работает, и после того, как мы автоматизировали развертывание, это больше не было проблемой. Я не знаю, как это обойтись, так что ... Думаю, другого выхода нет, нет. - person jro; 24.10.2011
comment
Я нашел путь. Смотрите мой ответ. Я рекомендую вам начать второй ответ, если вы думаете, что сможете использовать его в более полное решение. - person Christopher Bruns; 24.10.2011

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

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

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

person Christopher Bruns    schedule 24.10.2011
comment
Если я правильно понимаю, вы просто меняете имя исполняемого файла, так сказать (я понимаю символические ссылки, но для ясности)? Хотя я по-прежнему выступаю за создание пакета приложений и включение в него усилий по автоматизации, вы можете просто создать средство запуска для своего приложения, которое сначала создает символическую ссылку, запускает ваше приложение и после его закрытия удаляет ссылку. Но, как уже было сказано, я больше за создание правильного пакета приложений ... чтобы кто-то мог свободно скомпилировать это в свой собственный ответ. - person jro; 25.10.2011
comment
@jro Я согласен с тем, что для элегантной реализации тактики символических ссылок, вероятно, потребуется набор приложений. Я ищу решение, которое показывает, как создавать и использовать символическую ссылку в пакете приложений. Если не появится более конкретного ответа, возможно, лучше всего подойдет вариант использования набора приложений. - person Christopher Bruns; 25.10.2011

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

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

PyInstaller

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

Свяжите скрипт Python с автономным приложением OSX:

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

Это создает автономный пакет, который будет отображать все, что у вас есть в .setWindowTitle(), как имя приложения в строке заголовка OSX. Переключатель -w важен, поскольку он создает пакет приложений MacOS с правильным .plist файлом.

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

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

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

Mac Automator

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

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

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

ПРИМЕЧАНИЕ. Имейте в виду, что пакет приложений, который вызывает ваш код Python с помощью предопределенного интерпретатора, является «неглубоким» пакетом приложений и будет зависеть от тех зависимостей Python, которые вы устанавливаете локально. Скорее всего не будет работать OOTB на чужом Mac.

Нуйтка

Вы также можете попробовать 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, например, с помощью Platypus или Automator.

person ccpizza    schedule 08.12.2015

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

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

menubar.setNativeMenuBar(False)

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

person fredrik    schedule 28.09.2016

Я бы предпочел, чтобы конечные пользователи имели доступ к моему коду Python, чтобы они могли видеть и, возможно, улучшать код, поэтому меня не интересуют варианты, скрывающие код. Я обнаружил, что могу заставить приложения wx (не пробовал использовать Qt) для запуска с указанным именем приложения с использованием различных стандартных интерпретаторов Python, упаковывая их в пакет по мере их установки (например, как часть процесса установки пакета conda ). Вот короткий сценарий, который создает пакет .app с указанным именем с помощью Python, выполняющего сценарий. Запуск скрипта с использованием созданной программной ссылки (см. 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 (названную в соответствии с моим приложением), как указано выше. Ответ Кристофера Брунса, а также сценарий из статьи «Как создать пакет приложений Mac для сценария Python через 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)
person bht    schedule 02.07.2013

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

win.setWindowTitle("MyApp")

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

person Di Zou    schedule 24.10.2011
comment
setWindowTitle () помещает текст в строку заголовка приложения, а не в строку меню. Мне нужно изменить пункт меню приложения в строке меню Mac. setWindowTitle () этого не делает. - person Christopher Bruns; 24.10.2011