# (c) Copyright 2010, 2015. CodeWeavers, Inc.
import os
import subprocess
import distversion
import bottlewrapperbase
import bottlequery
import bottlemanagement
import c4profiles
import cxutils
# for localization
from cxutils import cxgettext as _
class BottleWrapper(bottlewrapperbase.BottleWrapperBase):
STATUS_INIT = _(u"Scanning\u2026")
STATUS_UPGRADE = _(u"Needs upgrade")
STATUS_READY = _(u"Ready")
STATUS_ARCHIVING = _(u"Archiving\u2026")
STATUS_DEFAULTING = _(u"Making default\u2026")
STATUS_RENAMING = _(u"Renaming\u2026")
STATUS_DELETING = _(u"Deleting\u2026")
STATUS_DOWNING = _(u"Shutting down\u2026")
STATUS_FORCE_DOWNING = _(u"Forcing shutdown\u2026")
STATUS_DOWN = _(u"Shut down")
STATUS_UPGRADING = _(u"Upgrading\u2026")
STATUS_X11_UNKNOWN = "X11Uknown"
STATUS_X11_YES = "X11Yes"
STATUS_X11_NO = "X11No"
STATUS_CSMT_UNKNOWN = "CSMTUnknown"
STATUS_CSMT_ENABLED = "CSMTEnabled"
STATUS_CSMT_DISABLED = "CSMTDisabled"
STATUS_DXVK_UNKNOWN = "DXVKUnknown"
STATUS_DXVK_ENABLED = "DXVKEnabled"
STATUS_DXVK_DISABLED = "DXVKDisabled"
def __init__(self, inName):
cxutils.expect_unicode(inName)
bottlewrapperbase.BottleWrapperBase.__init__(self, inName)
self._change_delegates = []
self._bottle_info_ready = False
self._property_dict = {}
self.current_description = None
self.bottletemplate = ""
self.arch = ""
self._needs_refresh_up_to_date = True
self._last_quit_failed = False
self.control_panel_loading = False
self._control_panel_ready = False
self._control_panel_table = []
self._control_panel_off_thread_table = []
self.is_x11_driver_ready = False
self.is_x11_driver_state = BottleWrapper.STATUS_X11_UNKNOWN
self._graphicsDriverKey = "HKEY_CURRENT_USER\\Software\\Wine\\Drivers"
self.is_csmt_disabled_ready = False
self.is_csmt_disabled_loading = False
self.is_csmt_disabled_state = BottleWrapper.STATUS_CSMT_UNKNOWN
self._direct3DKey = "HKEY_CURRENT_USER\\Software\\Wine\\Direct3D"
self.is_dxvk_enabled_ready = False
self.is_dxvk_enabled_loading = False
self._dlloverridesKey = "HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides"
self.is_dxvk_enabled_state = BottleWrapper.STATUS_DXVK_UNKNOWN
self.dxvk_dlls = ("dxgi", "d3d11", "d3d10", "d3d10_1", "d3d10core")
self.is_high_resolution_ready = False
self.is_high_resolution_state = False
self._macDriverKey = "HKEY_CURRENT_USER\\Software\\Wine\\Mac Driver"
self._logPixelsKey = "HKEY_CURRENT_USER\\Control Panel\\Desktop"
self._oldLogPixelsKey = "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Hardware Profiles\\Current\\Software\\Fonts"
self._installed_packages = {}
self.installedPackageValues = []
self._installed_packages_off_thread = {}
self._installed_packages_ready = False
self.installed_packages_loading = False
self.status = BottleWrapper.STATUS_INIT
self.status_overrides = []
# mtime values:
# 0 = not yet updated
# -1 = no config file
# None = deleted
# anything else = mtime of config file
self.mtime = 0
# This is something we need to know immediately for
# Mac initialization purposes. Hopefully it's not too
# expensive to do here.
self.is_managed = bottlequery.is_managed(self.name)
self.refresh_up_to_date()
self.marked_for_death = False
# Let's learn a bit about ourselves. Everything here
# should be very quick.
self.wine_prefix = bottlequery.get_prefix_for_bottle(self.name)
self.system_drive = os.path.join(self.wine_prefix, 'drive_c')
self.is_default = (self.name == bottlequery.get_default_bottle())
self.pre_load_basic_info()
self.load_basic_info()
self.post_load_basic_info()
# This will be called, below, only on ObjC. It sets up
# some miscellaneous notifications that the GUI needs.
@classmethod
def prepare_kvo_notification(cls):
# The setKeys_... functions are implemented in pyobjc and thus
# invisible to pylint on non-Mac platforms.
# pylint: disable=E1101
cls.setKeys_triggerChangeNotificationsForDependentKey_(["up_to_date", "is_managed", "is_busy"], "can_run_commands")
cls.setKeys_triggerChangeNotificationsForDependentKey_(["name", "is_default"], "display_name")
cls.setKeys_triggerChangeNotificationsForDependentKey_(["name"], "changeablename")
cls.setKeys_triggerChangeNotificationsForDependentKey_(["status", "up_to_date", "bottle_info_ready"], "is_active")
cls.setKeys_triggerChangeNotificationsForDependentKey_(["up_to_date", "is_managed", "marked_for_death"], "can_edit")
cls.setKeys_triggerChangeNotificationsForDependentKey_(["is_managed", "marked_for_death", "status"], "canInstall")
cls.setKeys_triggerChangeNotificationsForDependentKey_(["status", "_last_quit_failed"], "can_force_quit")
cls.setKeys_triggerChangeNotificationsForDependentKey_(["status", "marked_for_death"], "is_busy")
cls.setKeys_triggerChangeNotificationsForDependentKey_(["status", "marked_for_death", "is_managed"], "can_rename")
cls.setKeys_triggerChangeNotificationsForDependentKey_(["up_to_date", "is_managed"], "needs_upgrade")
#####
#
# load basic info
#
#####
def pre_load_basic_info(self):
"""This function is pretty fast and MUST be called in the main thread,
BEFORE calling load_basic_info().
"""
self.add_status_override(BottleWrapper.STATUS_INIT)
self.bottle_info_ready = False
def load_basic_info(self):
"""This function loads the bottle's basic properties.
It takes quite a bit of time to run and thus must be run in the
background. Thus, for purposes of thread management, it is divided into
three parts, with pre_ and post_ functions that are cheap and need to
be called on the main thread, in order to properly arrange the state of
our properties.
"""
self.set_property_dict(bottlequery.get_bottle_properties(self.name))
def post_load_basic_info(self):
"""This function is pretty fast and MUST be called in the main thread,
AFTER load_basic_info() has returned.
"""
if self._property_dict:
# copy everything we've learned out of the
# property dict and into actual properties.
self.bottletemplate = self._property_dict["template"]
if "managed" in self._property_dict:
self.is_managed = self._property_dict["managed"]
else:
self.is_managed = False
self.arch = self._property_dict["arch"]
self.current_description = self._property_dict["description"]
# Notify everyone that we're ready to go.
self.bottle_info_ready = True
else:
self.bottle_info_ready = False
self.remove_status_override(BottleWrapper.STATUS_INIT)
# Aliases for objective C:
preLoadBasicInfo = pre_load_basic_info
loadBasicInfo = load_basic_info
postLoadBasicInfo = post_load_basic_info
def _maybe_upgrade(self):
# If we get the control panel or installed application info for a
# bottle that needs to be upgraded, this upgrades the bottle, which
# in turn updates the mtime of cxbottle.conf. This would cause us to
# incorrectly assume our information is out of date, so if we need
# an upgrade we trigger it explicitly and ignore the mtime change.
stub_needs_upgrade = False
if self.is_managed:
stub_needs_upgrade = not bottlemanagement.get_up_to_date(self.name, "private")
if not self.up_to_date or stub_needs_upgrade:
args = [os.path.join(cxutils.CX_ROOT, "bin", "wine"),
"--bottle", self.name, '--no-gui',
"--ux-app", "true"]
cxutils.system(args)
self.mtime = self._real_mtime()
self._needs_refresh_up_to_date = True
#####
#
# load x11 driver preferences
#
# Three sections, same as load_basic_info, above.
#
# Loads the setting which determines whether
# the x11 driver is enabled or not.
#
#####
def pre_load_is_x11_driver(self):
self.is_x11_driver_ready = False
def load_is_x11_driver(self):
self.is_x11_driver_state = BottleWrapper.STATUS_X11_NO
try:
_subkeys, values = bottlequery.get_registry_key(self.name,
self._graphicsDriverKey)
if 'graphics' in values:
if values['graphics'].upper().find("X11") == 0:
self.is_x11_driver_state = BottleWrapper.STATUS_X11_YES
except bottlequery.NotFoundError:
pass
def post_load_is_x11_driver(self):
self.is_x11_driver_ready = True
def enable_x11_driver(self):
bottlequery.set_registry_key(self.name,
self._graphicsDriverKey,
"Graphics",
"x11,mac")
self.is_x11_driver_state = BottleWrapper.STATUS_X11_YES
def disable_x11_driver(self):
bottlequery.unset_registry_value(self.name,
self._graphicsDriverKey,
"Graphics")
self.is_x11_driver_state = BottleWrapper.STATUS_X11_NO
def is_x11_driver(self):
return self.is_x11_driver_state == BottleWrapper.STATUS_X11_YES
#####
#
# load dxvk preferences
#
# Loads the setting which determines whether
# dxvk is enabled in this bottle.
#
# dxvk is a bit unusual.
# It is installed via crosstie.
# Installing it requires replacing some wine dlls.
# with custom versions. The dxvk crosstie
# does this by downloading native PE dlls
# for the things dxvk needs and sets
# native overrides for those.
#
# Because dxvk can either help or hurt
# performance, it is useful to be able to
# turn it on and off easily from the GUI.
# For various reasons, we don't ship it,
# so the GUI option should only show up
# if it has actually been installed
# in the prefix.
#
#####
def pre_load_is_dxvk_enabled(self):
self.is_dxvk_enabled_ready = False
self.is_dxvk_enabled_loading = True
def load_is_dxvk_enabled(self):
self.is_dxvk_enabled_state = BottleWrapper.STATUS_DXVK_UNKNOWN
dxvk_present = True
try:
dlls = ("dxgi.dll", "d3d11.dll")
for dll in dlls:
winpath = bottlequery.expand_win_string(bottlequery.get_win_environ(self.name, c4profiles.ENVIRONMENT_VARIABLES), "%WinSysDir%")
winpath = os.path.join(winpath, dll)
dllfile = bottlequery.get_native_path(self.name, winpath)
if cxutils.check_fake_dll(dllfile):
dxvk_present = False
if dxvk_present != True:
return
self.is_dxvk_enabled_state = BottleWrapper.STATUS_DXVK_DISABLED
_subkeys, values = bottlequery.get_registry_key(self.name,
self._dlloverridesKey)
if 'd3d11' in values:
if values['d3d11'].startswith('native'):
self.is_dxvk_enabled_state = BottleWrapper.STATUS_DXVK_ENABLED
except bottlequery.NotFoundError:
pass
def post_load_is_dxvk_enabled(self):
self.is_dxvk_enabled_ready = True
self.is_dxvk_disabled_loading = False
def enable_dxvk(self):
for dll in self.dxvk_dlls:
bottlequery.set_registry_key(self.name,
self._dlloverridesKey,
dll,
"native,builtin")
self.is_dxvk_enabled_state = BottleWrapper.STATUS_DXVK_ENABLED
def disable_dxvk(self):
for dll in self.dxvk_dlls:
bottlequery.unset_registry_value(self.name,
self._dlloverridesKey,
dll)
self.is_dxvk_enabled_state = BottleWrapper.STATUS_DXVK_DISABLED
def is_dxvk_enabled(self):
return self.is_dxvk_enabled_state == BottleWrapper.STATUS_DXVK_ENABLED
def pre_load_is_csmt_disabled(self):
self.is_csmt_disabled_ready = False
self.is_csmt_disabled_loading = True
def load_is_csmt_disabled(self):
self.is_csmt_disabled_state = BottleWrapper.STATUS_CSMT_ENABLED
try:
_subkeys, values = bottlequery.get_registry_key(self.name,
self._direct3DKey)
if 'csmt' in values:
if isinstance(values['csmt'], int):
if values['csmt']:
self.is_csmt_disabled_state = BottleWrapper.STATUS_CSMT_ENABLED
else:
self.is_csmt_disabled_state = BottleWrapper.STATUS_CSMT_DISABLED
elif values['csmt'] == 'disabled':
self.is_csmt_disabled_state = BottleWrapper.STATUS_CSMT_DISABLED
except bottlequery.NotFoundError:
pass
def post_load_is_csmt_disabled(self):
self.is_csmt_disabled_ready = True
self.is_csmt_disabled_loading = False
def enable_csmt(self):
bottlequery.set_registry_key(self.name,
self._direct3DKey,
"CSMT",
'dword:1')
self.is_csmt_disabled_state = BottleWrapper.STATUS_CSMT_ENABLED
def disable_csmt(self):
bottlequery.set_registry_key(self.name,
self._direct3DKey,
"CSMT",
'dword:0')
self.is_csmt_disabled_state = BottleWrapper.STATUS_CSMT_DISABLED
def is_csmt_disabled(self):
return self.is_csmt_disabled_state == BottleWrapper.STATUS_CSMT_DISABLED
#####
#
# load Mac driver high resolution mode preferences
#
#####
def preLoadIsHighResolution(self):
self.is_high_resolution_ready = False
def loadIsHighResolution(self):
self.is_high_resolution_state = False
try:
_subkeys, values = bottlequery.get_registry_key(self.name,
self._macDriverKey)
if 'retinamode' in values:
if values['retinamode'][0] in set("yYtT1"):
self.is_high_resolution_state = True
except bottlequery.NotFoundError:
pass
def postLoadIsHighResolution(self):
self.is_high_resolution_ready = True
def enableHighResolution(self):
if bottlequery.set_registry_key(self.name, self._macDriverKey, "RetinaMode", "y"):
self.is_high_resolution_state = True
values = {}
try:
_subkeys, values = bottlequery.get_registry_key(self.name,
self._logPixelsKey)
except bottlequery.NotFoundError:
pass
if 'logpixels' not in values:
try:
_subkeys, values = bottlequery.get_registry_key(self.name,
self._oldLogPixelsKey)
except bottlequery.NotFoundError:
pass
logpixels = values.get('logpixels', 96)
logpixels *= 2
bottlequery.set_registry_key(self.name,
self._logPixelsKey,
"LogPixels",
"dword:" + hex(logpixels)[2:])
def disableHighResolution(self):
if bottlequery.unset_registry_value(self.name, self._macDriverKey, "RetinaMode"):
self.is_high_resolution_state = False
values = {}
try:
_subkeys, values = bottlequery.get_registry_key(self.name,
self._logPixelsKey)
except bottlequery.NotFoundError:
pass
if 'logpixels' not in values:
try:
_subkeys, values = bottlequery.get_registry_key(self.name,
self._oldLogPixelsKey)
except bottlequery.NotFoundError:
pass
if 'logpixels' in values:
logpixels = values['logpixels']
logpixels = max(logpixels // 2, 96)
bottlequery.set_registry_key(self.name,
self._logPixelsKey,
"LogPixels",
"dword:" + hex(logpixels)[2:])
def is_high_resolution(self):
return self.is_high_resolution_state
#####
#
# load control panel info
#
# Three sections, same as load_basic_info, above.
#
# Loads the list of control panels available for this bottle,
# and sets up icons and other user-readable-data for each.
#
#####
def pre_load_control_panel_info(self):
"""This function is pretty fast and MUST be called in the main thread,
BEFORE calling load_control_panel_info().
"""
self.control_panel_loading = True
self.control_panel_ready = False
self.add_status_override(BottleWrapper.STATUS_INIT)
def load_control_panel_info(self):
"""This function initializes the bottle's list of control panel applets.
It takes quite a bit of time to run and thus must be run in the
background. Thus, for purposes of thread management, it is divided into
three parts, with pre_ and post_ functions that are cheap and need to
be called on the main thread, in order to properly arrange the state of
our properties.
"""
self._maybe_upgrade()
self._control_panel_off_thread_table = bottlequery.get_control_panel_info(self.name)
if not distversion.IS_MACOSX:
self._control_panel_off_thread_table.append(["cxassoceditui", _("Edit Associations"),
_("Manage the Windows programs used to open files in the native environment"), "cxassocedit"])
self._control_panel_off_thread_table.append(["cxmenueditui", _("Edit Menus"),
_("Manage the menus and desktop icons in this bottle"), "cxmenuedit"])
if distversion.HAS_PLUGIN:
self._control_panel_off_thread_table.append(["cxplugineditui", _("Edit Plugins"),
_("Manage the browser plugins in this bottle"), "cxpluginedit"])
def post_load_control_panel_info(self):
"""This function is pretty fast and MUST be called in the main thread,
AFTER load_control_panel_info() has returned.
"""
if self._needs_refresh_up_to_date:
self.refresh_up_to_date()
cptable = []
std_root = os.path.join(cxutils.CX_ROOT, 'share', 'icons')
for panel in self._control_panel_off_thread_table:
cpdict = {}
cpdict["exe"] = panel[0]
cpdict["name"] = panel[1]
cpdict["description"] = panel[2]
base, ext = os.path.splitext(panel[3])
if ext == '' and base != '':
# This is a builtin icon
cpdict["icon"] = cxutils.get_icon_path(std_root, '', base,
('24x24', '48x48', '32x32', '64x64', ''))
elif distversion.IS_MACOSX:
# conveniently, cxcplinfo provides us with .ico files in
# addition to the .xpms which the Mac can't handle.
if ext.lower() == ".xpm":
ext = ".ico"
cpdict["icon"] = base + ext
else:
cpdict["icon"] = panel[3]
if cpdict["icon"] is None or cpdict["icon"] == '':
cpdict["icon"] = cxutils.get_icon_path(std_root, '',
'crossover', ('24x24', '48x48', '32x32', '64x64', ''))
cptable.append(cpdict)
self.control_panel_table = cptable
self.control_panel_ready = True
self.control_panel_loading = False
self.remove_status_override(BottleWrapper.STATUS_INIT)
def launch_control_panel_applet(self, applet):
wine = os.path.join(cxutils.CX_ROOT, "bin", "wine")
if applet.lower().endswith(".exe.so"):
args = [wine, "--bottle", self.name, "--wl-app", applet]
if applet.lower() == "reboot.exe.so":
args.append("--show-gui")
elif applet.lower().endswith(".cpl"):
args = [wine, "--bottle", self.name, "--wl-app", "rundll32", "shell32.dll,Control_RunDLL", applet]
else:
__import__(applet).start(self)
return
subprocess.Popen(args, close_fds=True)
# Aliases for objective C:
preLoadControlPanelInfo = pre_load_control_panel_info
loadControlPanelInfo = load_control_panel_info
postLoadControlPanelInfo = post_load_control_panel_info
preLoadIsX11Driver = pre_load_is_x11_driver
loadIsX11Driver = load_is_x11_driver
postLoadIsX11Driver = post_load_is_x11_driver
enableX11Driver = enable_x11_driver
disableX11Driver = disable_x11_driver
preLoadIsCSMTDisabled = pre_load_is_csmt_disabled
loadIsCSMTDisabled = load_is_csmt_disabled
postLoadIsCSMTDisabled = post_load_is_csmt_disabled
disableCSMT = disable_csmt
enableCSMT = enable_csmt
launchControlPanelApplet_ = launch_control_panel_applet
#####
# load installed applications
#
# Three sections, same as load_basic_info, above.
#
#
# Loads the list of applications installed in the bottle.
# This can take quite a while.
#
#####
def pre_load_installed_applications(self):
"""This function is pretty fast and MUST be called in the main thread,
BEFORE calling load_installed_applications().
"""
self.installed_packages_ready = False
self.installed_packages_loading = True
self.installed_packages_off_thread = {}
self.add_status_override(BottleWrapper.STATUS_INIT)
def load_installed_applications(self):
"""This function determines the list of applications that are installed
in the bottle.
It takes quite a bit of time to run and thus must be run in the
background. Thus, for purposes of thread management, it is divided into
three parts, with pre_ and post_ functions that are cheap and need to
be called on the main thread, in order to properly arrange the state of
our properties.
"""
import c4profilesmanager
import appdetector
self._maybe_upgrade()
profiles = c4profilesmanager.C4ProfilesSet.all_profiles()
self.installed_packages_off_thread = appdetector.fast_get_installed_applications(self.name, profiles)
def post_load_installed_applications(self):
"""This function is pretty fast and MUST be called in the main thread,
AFTER load_installed_applications() has returned.
"""
if self._needs_refresh_up_to_date:
self.refresh_up_to_date()
self.installed_packages = self.installed_packages_off_thread
self.installed_packages_ready = True
self.installed_packages_loading = False
self.remove_status_override(BottleWrapper.STATUS_INIT)
def file_is_in_bottle(self, filename):
drivepath = os.path.abspath(os.path.realpath(self.system_drive))
filepath = os.path.abspath(os.path.realpath(filename))
return filepath.startswith(drivepath)
def write_description(self):
# Note: Expensive! Should only be done within an operation.
current_description = self.current_description
if self._property_dict["description"] != current_description:
bottlemanagement.set_bottle_description(self.name, current_description)
self._property_dict["description"] = current_description
def is_description_written(self):
return self.current_description == self._property_dict["description"]
# Aliases for objective C:
preLoadInstalledApplications = pre_load_installed_applications
loadInstalledApplications = load_installed_applications
postLoadInstalledApplications = post_load_installed_applications
fileIsInBottle_ = file_is_in_bottle
writeDescription = write_description
def change_description(self, inDescription):
self.current_description = inDescription
def pre_rename(self):
if self.changeablename == self.name:
return
self.add_status_override(BottleWrapper.STATUS_RENAMING)
self.marked_for_death = True
def rename(self):
if self.changeablename == self.name:
return False, None
(success, err) = bottlemanagement.rename_bottle(self.name, self.changeablename)
if not success:
print "Rename from %s to %s failed: %s" % (self.name, self.changeablename, err)
return success, err
def post_rename(self, success):
if self.changeablename == self.name:
return
self.remove_status_override(BottleWrapper.STATUS_RENAMING)
self.marked_for_death = False
if not success:
self.changeablename = self.name
return
self.name = self.changeablename
self.wine_prefix = bottlequery.get_prefix_for_bottle(self.name)
self.system_drive = os.path.join(self.wine_prefix, 'drive_c')
preRename = pre_rename
postRename_ = post_rename
def get_control_panel_table(self):
return self._control_panel_table
def set_control_panel_table(self, in_table):
self._control_panel_table = in_table
for delegate in self._change_delegates:
delegate.bottleChanged(self)
control_panel_table = property(get_control_panel_table, set_control_panel_table)
def get_installed_packages(self):
return self._installed_packages
def set_installed_packages(self, installed_apps):
self._installed_packages = installed_apps
# installedPackageValues stores the same data but as an array.
# We need this for bindings, and having an actual member
# allows us to use KVO properly.
self.installedPackageValues = installed_apps.values()
for delegate in self._change_delegates:
delegate.bottleChanged(self)
installed_packages = property(get_installed_packages, set_installed_packages)
def set_property_dict(self, inDict):
self._property_dict = inDict
# if current_description varies from the
# dict value, we know it's dirty and needs
# to be written.
if "description" in inDict:
self.current_description = inDict["description"]
else:
self.current_description = None
for delegate in self._change_delegates:
delegate.bottleChanged(self)
def _real_mtime(self):
config_file_path = bottlequery.config_file_path(self.name)
try:
mtime = os.path.getmtime(config_file_path)
except OSError:
# distinguish between a bottle with no config file and a deleted bottle
if os.path.exists(self.wine_prefix):
mtime = -1
else:
mtime = None
return mtime
def get_needs_update(self, updating=False):
if self.status_overrides:
return False
mtime = self._real_mtime()
if self.mtime != mtime:
if updating:
self.mtime = mtime
return True
else:
return False
needs_update = property(get_needs_update)
def get_needs_import(self):
return False
needs_import = property(get_needs_import)
def add_status_override(self, status):
self.status_overrides.append(status)
self.status = status
for delegate in self._change_delegates:
delegate.bottleChanged(self)
addStatusOverride_ = add_status_override
def remove_all_status_overrides(self, status):
while status in self.status_overrides:
self.remove_status_override(status)
def remove_status_override(self, status):
if status in self.status_overrides:
self.status_overrides.remove(status)
if self.status_overrides:
self.status = self.status_overrides[len(self.status_overrides)-1]
elif not self.bottle_info_ready or not self.installed_packages_ready or \
not (distversion.IS_MACOSX or self.control_panel_ready):
self.status = BottleWrapper.STATUS_INIT
else:
self.status = BottleWrapper.STATUS_READY
for delegate in self._change_delegates:
delegate.bottleChanged(self)
removeStatusOverride_ = remove_status_override
def get_can_run_commands(self):
return (self.up_to_date or not self.is_managed) and not self.is_busy
can_run_commands = property(get_can_run_commands)
def get_can_edit(self):
return self.up_to_date and not self.marked_for_death and not self.is_managed
can_edit = property(get_can_edit)
def canInstall(self):
return not self.is_managed and not self.marked_for_death and \
BottleWrapper.STATUS_RENAMING not in self.status_overrides and \
BottleWrapper.STATUS_DELETING not in self.status_overrides and \
BottleWrapper.STATUS_DOWNING not in self.status_overrides and \
BottleWrapper.STATUS_FORCE_DOWNING not in self.status_overrides and \
BottleWrapper.STATUS_DOWN not in self.status_overrides
def get_is_active(self):
return self.bottle_info_ready and not self.status_overrides and self.up_to_date
is_active = property(get_is_active)
def get_can_force_quit(self):
"""Be careful of race conditions with this property. If all you've done
is initiate an asynchronous operation to quit the bottle, these statuses
won't necessarily be set yet."""
return (self._last_quit_failed or BottleWrapper.STATUS_DOWNING in self.status_overrides) and \
BottleWrapper.STATUS_FORCE_DOWNING not in self.status_overrides
can_force_quit = property(get_can_force_quit)
def get_is_busy(self):
return self.marked_for_death or \
BottleWrapper.STATUS_RENAMING in self.status_overrides or \
BottleWrapper.STATUS_DELETING in self.status_overrides or \
BottleWrapper.STATUS_ARCHIVING in self.status_overrides
is_busy = property(get_is_busy)
def get_can_rename(self):
return not self.marked_for_death and not self.status_overrides and not self.is_managed
can_rename = property(get_can_rename)
def get_needs_upgrade(self):
return self.is_managed and not self.up_to_date
needs_upgrade = property(get_needs_upgrade)
# For ObjC:
def setMarkedForDeath_(self, inBool):
if inBool:
self.add_status_override(BottleWrapper.STATUS_DELETING)
self.marked_for_death = inBool
def is_running(self):
return bottlemanagement.is_running(self.name)
def preQuit(self):
"""On the Mac, this function MUST be called in the main thread because
it modifies properties which are bound to the UI.
"""
self._last_quit_failed = False
self.add_status_override(BottleWrapper.STATUS_DOWN)
self.add_status_override(BottleWrapper.STATUS_DOWNING)
def doQuit(self):
return bottlemanagement.quit_bottle(self.name)
def postQuit_(self, success):
"""On the Mac, this function MUST be called in the main thread because
it modifies properties which are bound to the UI.
"""
self.remove_status_override(BottleWrapper.STATUS_DOWNING)
if not success:
self._last_quit_failed = True
self.remove_status_override(BottleWrapper.STATUS_DOWN)
def quit(self):
self.preQuit()
success = self.doQuit()
self.postQuit_(success)
return success
def refresh_up_to_date(self):
"""On the Mac, this function MUST be called in the main thread because
it modifies properties which are bound to the UI.
"""
self._needs_refresh_up_to_date = False
if self.is_managed:
self.up_to_date = bottlemanagement.get_up_to_date(self.name, "managed")
else:
self.up_to_date = bottlemanagement.get_up_to_date(self.name)
if self.is_managed:
if not self.up_to_date:
self.add_status_override(BottleWrapper.STATUS_UPGRADE)
else:
self.remove_all_status_overrides(BottleWrapper.STATUS_UPGRADE)
refreshUpToDate = refresh_up_to_date
def preForceQuit(self):
"""On the Mac, this function MUST be called in the main thread because
it modifies properties which are bound to the UI.
"""
self._last_quit_failed = False
self.add_status_override(BottleWrapper.STATUS_DOWN)
self.add_status_override(BottleWrapper.STATUS_FORCE_DOWNING)
def doForceQuit(self):
return bottlemanagement.kill_bottle(self.name)
def postForceQuit_(self, success):
"""On the Mac, this function MUST be called in the main thread because
it modifies properties which are bound to the UI.
"""
self.remove_status_override(BottleWrapper.STATUS_FORCE_DOWNING)
if not success:
self.remove_status_override(BottleWrapper.STATUS_DOWN)
def force_quit(self):
self.preForceQuit()
success = self.doForceQuit()
self.postForceQuit_(success)
return success
# Cancel shutdown is called to restore the status of a bottle
# to a non-deleting, non-dying, non-shutting-down status.
def cancelShutdown(self):
"""On the Mac, this function MUST be called in the main thread because
it modifies properties which are bound to the UI.
"""
self.remove_status_override(BottleWrapper.STATUS_DOWN)
self.remove_status_override(BottleWrapper.STATUS_FORCE_DOWNING)
self.remove_status_override(BottleWrapper.STATUS_DOWNING)
self.remove_status_override(BottleWrapper.STATUS_DELETING)
def get_display_name(self):
if self.is_default:
return _("%s (default)") % self.name
return self.name
display_name = property(get_display_name)
# A couple of crutches for reverse-compatibility with the old BottleWrapper.m
def bottleName(self):
return self.name
displayName = get_display_name
if distversion.IS_MACOSX:
BottleWrapper.prepare_kvo_notification()