Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

beebox / crossover   deb

Repository URL to install this package:

Version: 18.5.0-1 

/ opt / cxoffice / lib / python / bottlewrapper.py

# (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()