# (c) Copyright 2009-2015. CodeWeavers, Inc.
import os
import cxproduct
import shutil
import distversion
import cxutils
import cxobjc
import cxlog
import cxmenu
import bottlequery
class BottleManagement(cxobjc.Proxy):
pass
def _bottle_tool():
return os.path.join(cxutils.CX_ROOT, "bin", "cxbottle")
@cxobjc.method(BottleManagement, 'createBottle_fromTemplate_')
def create_bottle(bottlename, template="winxp", env=None, appid=None):
"""Creates the specified bottle and returns a (success, err_msg) tuple.
"""
args = [_bottle_tool(), "--bottle", bottlename,
"--create", "--template", template, "--install"]
if appid is not None:
s = "EnvironmentVariables:CX_BOTTLE_CREATOR_APPID=%s" % appid
args.append("--param")
args.append(s)
retcode, _out, err = cxutils.run(args, env=env, stderr=cxutils.GRAB)
return (retcode == 0), err
@cxobjc.method(BottleManagement, 'duplicateBottle_fromBottle_')
def copy_bottle(bottlename, bottleToCopy):
"""Copies the specified bottle and returns a (success, err_msg) tuple.
"""
args = [_bottle_tool(), "--bottle", bottlename,
"--copy", bottleToCopy, "--install"]
retcode, _out, err = cxutils.run(args, stderr=cxutils.GRAB)
return (retcode == 0), err
def publish_bottle(bottlename, bottleToCopy):
"""Publishes the specified bottle and returns a (success, err_msg) tuple.
"""
if cxproduct.is_root_install():
cxsu = os.path.join(cxutils.CX_ROOT, "bin", "cxsu")
cxsu_args = [cxsu, '--ignore-home']
else:
cxsu_args = []
prefix = bottlequery.get_prefix_for_bottle(bottleToCopy)
args = cxsu_args + [_bottle_tool(), "--bottle", bottlename,
"--copy", prefix, "--install", "--scope", "managed"]
retcode, _out, err = cxutils.run(args, stderr=cxutils.GRAB)
return (retcode == 0), err
@cxobjc.method(BottleManagement, 'restoreBottle_fromArchive_')
def restore_bottle(bottlename, archiveFile):
"""Restores the specified archive to the specified bottle name and
returns a (success, err_msg) tuple.
"""
args = [_bottle_tool(), "--bottle", bottlename,
"--restore", archiveFile, "--install"]
env = None
if distversion.IS_MACOSX and 'LANG' not in os.environ and \
'LC_CTYPE' not in os.environ and 'LC_ALL' not in os.environ:
env = os.environ.copy()
env['LC_CTYPE'] = 'UTF-8'
retcode, _out, err = cxutils.run(args, stderr=cxutils.GRAB, env=env)
return (retcode == 0), err
def migration_info_for_games_bottle(gamesbottlename):
"""Returns the information necessary for the front-end to migrate (move or
copy) a Games bottle.
"""
fullBottleLocation = os.path.join(cxproduct.get_games_bottle_path(), gamesbottlename)
newBottleName = bottlequery.unique_bottle_name(gamesbottlename)
fullNewBottleLocation = os.path.join(cxproduct.get_bottle_path(), newBottleName)
return fullBottleLocation, newBottleName, fullNewBottleLocation
def post_migrate_games_bottle(newBottleName):
"""Finishes the migration of a Games bottle into this product's bottle
directory.
It will clean up and reinstall the new bottle.
"""
fullNewBottleLocation = os.path.join(cxproduct.get_bottle_path(), newBottleName)
if distversion.IS_MACOSX:
desktopdata = os.path.join(fullNewBottleLocation, "desktopdata")
try:
os.unlink(os.path.join(desktopdata, "cxmenu", "cxmenu_macosx.plist"))
except OSError:
pass
try:
os.unlink(os.path.join(desktopdata, "cxassoc", "cxassoc_macosx.plist"))
except OSError:
pass
cxconfig = cxproduct.get_config()
menu_root = cxconfig['BottleDefaults'].get('MenuRoot', '/Windows Applications')
# At this point we know the bottle's menus are not installed yet so there
# is no need to update them
cxmenu.set_menuroot(newBottleName, menu_root, update=False)
args = [_bottle_tool(), "--bottle", newBottleName, "--restored", "--install"]
retcode, _out, err = cxutils.run(args, stderr=cxutils.GRAB)
if retcode != 0:
cxlog.log("Failed to install menus for migrated bottle: %s." % cxlog.to_str(err))
if retcode != 0:
return err
return None
def migrate_games_bottle(gamesbottlename):
"""Pulls a bottle out of the old cxgames bottle prefix and
moves it into the modern bottle directory.
It will move, and reinstall. Uninstall, if necessary, must be done
before calling.
"""
fullBottleLocation, newBottleName, fullNewBottleLocation = migration_info_for_games_bottle(gamesbottlename)
shutil.move(fullBottleLocation, fullNewBottleLocation)
err = post_migrate_games_bottle(newBottleName)
if err is None:
return True, newBottleName
return False, err
def copy_games_bottle(gamesbottlename):
"""Copy a bottle out of the old cxgames bottle prefix and
into the modern bottle directory.
It will copy and reinstall.
"""
fullBottleLocation, newBottleName, fullNewBottleLocation = migration_info_for_games_bottle(gamesbottlename)
shutil.copytree(fullBottleLocation, fullNewBottleLocation, symlinks=True)
err = post_migrate_games_bottle(newBottleName)
if err is None:
return True, newBottleName
return False, err
@cxobjc.method(BottleManagement, 'renameBottle_to_')
def rename_bottle(bottlename, newBottleName):
"""Renames the specified bottle and returns a (success, err_msg) tuple."""
oldPrefix = bottlequery.get_prefix_for_bottle(bottlename)
newPrefix = os.path.join(os.path.dirname(oldPrefix), newBottleName)
wasDefault = (bottlequery.get_default_bottle() == bottlename)
if os.path.exists(newPrefix):
err = "Cannot rename; that bottle already exists."
return (False, err)
args = [_bottle_tool(), "--bottle", bottlename,
"--removeall"]
retcode, _out, err = cxutils.run(args, stderr=cxutils.GRAB)
if retcode != 0:
errstr = "Failed to uninstall the old bottle name: %s" % err
return (False, errstr)
os.rename(oldPrefix, newPrefix)
args = [_bottle_tool(), "--bottle", newBottleName,
"--new-uuid", "--install"]
if wasDefault:
args.append("--default")
retcode, _out, err = cxutils.run(args, stderr=cxutils.GRAB)
if retcode != 0:
errstr = "Failed to install the new bottle name: %s" % err
return (False, errstr)
return (True, "")
def delete_bottle(bottlename, isManaged=False):
"""Deletes the specified bottle and returns a (success, err_msg) tuple.
"""
if isManaged:
if cxproduct.is_root_install():
cxsu = os.path.join(cxutils.CX_ROOT, "bin", "cxsu")
cxsu_args = [cxsu, '--ignore-home']
else:
cxsu_args = []
args = cxsu_args + [_bottle_tool(), "--bottle", bottlename,
"--removeall", "--delete", "--force"]
retcode, _out, err = cxutils.run(args, stderr=cxutils.GRAB)
if retcode != 0:
cxlog.log("Failed to delete %s." % cxlog.to_str(bottlename))
# Delete the stub, if it's there.
args = [_bottle_tool(), "--bottle", bottlename,
"--removeall", "--delete", "--force", "--scope", "private"]
retcode, _out, err = cxutils.run(args, stderr=cxutils.GRAB)
if retcode != 0:
cxlog.log("Failed to delete the stub for %s." % cxlog.to_str(bottlename))
return (retcode == 0), err
else:
args = [_bottle_tool(), "--bottle", bottlename,
"--removeall", "--delete", "--force", "--scope", "private"]
retcode, _out, err = cxutils.run(args, stderr=cxutils.GRAB)
return (retcode == 0), err
@cxobjc.method(BottleManagement, 'deleteBottle_')
def _deleteBottle_(bottlename):
return delete_bottle(bottlename, False)
@cxobjc.method(BottleManagement, 'setBottle_isDefault_')
def set_default_bottle(bottlename, inState):
"""Makes or unmakes the bottle the default bottle and returns a
(success, err_msg) tuple.
"""
if inState:
cmd = "--default"
else:
cmd = "--undefault"
args = [_bottle_tool(), "--bottle", bottlename, cmd]
retcode, _out, err = cxutils.run(args, stderr=cxutils.GRAB)
return (retcode == 0), err
def package_bottle(bottlename, packagetype, release, productid, packager, outdir):
args = [_bottle_tool(), "--%s" % packagetype, "--release", release, "--bottle", bottlename, "--productid", productid, "--packager", packager]
retcode, _out, err = cxutils.run(args, cwd=outdir, stderr=cxutils.GRAB)
return (retcode == 0), err
@cxobjc.method(BottleManagement, 'setBottle_description_')
def set_bottle_description(bottlename, inDescription):
"""Sets the bottle's description and returns a (success, err_msg)
tuple.
"""
args = [_bottle_tool(), "--bottle", bottlename,
"--description", inDescription]
retcode, _out, err = cxutils.run(args, stderr=cxutils.GRAB)
return (retcode == 0), err
@cxobjc.method(BottleManagement, 'archiveBottle_toPath_')
def archive_bottle(bottlename, inArchivePath):
"""Archives the bottle to the specified file and returns a
(success, err_msg) tuple.
"""
args = [_bottle_tool(), "--bottle", bottlename,
"--tar", inArchivePath]
env = None
if distversion.IS_MACOSX and 'LANG' not in os.environ and \
'LC_CTYPE' not in os.environ and 'LC_ALL' not in os.environ:
env = os.environ.copy()
env['LC_CTYPE'] = 'UTF-8'
retcode, _out, err = cxutils.run(args, stderr=cxutils.GRAB, env=env)
return (retcode == 0), err
@cxobjc.method(BottleManagement, 'templates')
def template_list():
templateList = []
templateDir = os.path.join(cxutils.CX_ROOT, "share", "crossover", "bottle_templates")
is_64bit = cxproduct.is_64bit_install()
for dentry in os.listdir(templateDir):
if not is_64bit and dentry.endswith('_64'):
continue
if os.path.exists(os.path.join(templateDir, dentry, "setup")):
templateList.append(dentry)
return templateList
_TEMPLATE_PROPERTIES = {
# pylint: disable=C0326
'win98': (0, 'Windows 98', None),
'win2000': (1, 'Windows 2000', None),
'winxp': (2, 'Windows XP', None),
'winxp_64': (3, 'Windows XP 64-bit', None),
'winvista': (4, 'Windows Vista', None),
'winvista_64': (5, 'Windows Vista 64-bit', None),
'win7': (6, 'Windows 7', None),
'win7_64': (7, 'Windows 7 64-bit', None),
'win8': (8, 'Windows 8', None),
'win8_64': (9, 'Windows 8 64-bit', None),
'win10': (10, 'Windows 10', None),
'win10_64': (11, 'Windows 10 64-bit', None),
}
@cxobjc.method(BottleManagement, 'sortKeyForTemplate_')
def get_template_key(template):
prop = _TEMPLATE_PROPERTIES.get(template)
if prop is None:
return (99, 'zzz', template)
return (prop[0], prop[1], template)
@cxobjc.method(BottleManagement, 'displayNameForTemplate_')
def get_template_name(template, recommend=False):
if template not in _TEMPLATE_PROPERTIES:
return template
prop = _TEMPLATE_PROPERTIES[template]
if not recommend or prop[2] is None:
return prop[1]
return '%s (%s)' % (prop[1], prop[2])
@cxobjc.method(BottleManagement, 'isBottleRunning_')
def is_running(bottlename):
args = [os.path.join(cxutils.CX_ROOT, "bin", "wine"),
"--bottle", bottlename, "--no-update",
"--ux-app", "wineserver", "-k0"]
retcode = cxutils.system(args)
return retcode == 0
@cxobjc.method(BottleManagement, 'quitBottle_')
def quit_bottle(bottlename):
"""Return True if shutdown succeeded, False if failed or canceled."""
if not is_running(bottlename):
return True
args = [os.path.join(cxutils.CX_ROOT, "bin", "wine"),
"--bottle", bottlename, "--no-update",
"--wl-app", "wineboot.exe", "--", "--end-session",
"--shutdown", "--force", "--kill"]
retcode, _out, _err = cxutils.run(args, stdout=cxutils.GRAB)
return retcode == 0
@cxobjc.method(BottleManagement, 'bottleIsUpToDate_')
def get_up_to_date(bottlename, scope="private"):
args = [_bottle_tool(), "--bottle", bottlename, "--status", "--scope", scope]
retcode, out, _err = cxutils.run(args, stdout=cxutils.GRAB)
if retcode == 0 and "Status=uptodate" in out:
return True
return False
@cxobjc.method(BottleManagement, 'killBottle_')
def kill_bottle(bottlename):
if not is_running(bottlename):
return True
args = [os.path.join(cxutils.CX_ROOT, "bin", "wine"),
"--bottle", bottlename, "--no-update",
"--ux-app", "wineserver", "-k"]
retcode = cxutils.system(args)
return retcode == 0
@cxobjc.method(BottleManagement, 'wineEjectForBottle_')
def wine_eject(bottlename):
args = [os.path.join(cxutils.CX_ROOT, "bin", "wine"),
"--bottle", bottlename,
"--wl-app", "eject.exe", "-a"]
retcode = cxutils.system(args)
return retcode == 0