# (c) Copyright 2009-2015. CodeWeavers, Inc.
import ctypes
import os
import sys
import traceback
import urllib
import gobject
import gtk
import cxlog
import cxutils
import distversion
# for localization
from cxutils import cxgettext as _
cxutils.setup_textdomain()
def toplevel_quit():
"""If there are no visible toplevel windows left, call gtk.main_quit().
This should be called when a toplevel is closed on a process that might have
multiple toplevels and needs to live as long as at least one is still open."""
for toplevel in gtk.window_list_toplevels():
if toplevel.get_property('visible') and toplevel.window and \
toplevel.window.get_window_type() != gtk.gdk.WINDOW_TEMP:
break
else:
gtk.main_quit()
def get_ui_path(basename):
try:
ui_dir = os.environ["CX_GLADEPATH"]
except KeyError:
ui_dir = os.path.join(cxutils.CX_ROOT, "lib", "python", "glade")
return os.path.join(ui_dir, basename + ".ui")
ICONS = dict()
def get_std_icon(basename, sizes=cxutils.S_MEDIUM):
# Assume scanning the cache is faster than disk accesses
for size in sizes:
key = basename + "." + size
if key in ICONS:
return ICONS[key]
root = os.path.join(cxutils.CX_ROOT, 'share', 'icons')
filename = cxutils.get_icon_path(root, '', basename, sizes)
if filename:
try:
ICONS[key] = gtk.gdk.pixbuf_new_from_file(filename)
return ICONS[key]
except gobject.GError:
cxlog.warn("could not load icon file %s:\n%s" % (cxlog.debug_str(filename), traceback.format_exc()))
return None
def get_std_icon_list(basename='crossover'):
icons = []
root = os.path.join(cxutils.CX_ROOT, 'share', 'icons')
for filename in cxutils.get_icon_paths(root, '', basename):
try:
icons.append(gtk.gdk.pixbuf_new_from_file(filename))
except gobject.GError:
cxlog.warn("could not load icon file %s:\n%s" % (cxlog.debug_str(filename), traceback.format_exc()))
icons.sort(key=lambda x: x.get_property('width'))
return icons
def set_default_icon(basename='crossover'):
gtk.window_set_default_icon_list(*get_std_icon_list(basename))
def draw_widget_background(_widget, event, pixbuf):
event.window.draw_pixbuf(None, pixbuf, 0, 0, 0, 0)
return False
def set_widget_background(widget, filename):
filename = os.path.join(cxutils.CX_ROOT, 'share', 'images', filename)
try:
pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
except gobject.GError:
cxlog.warn("couldn't load icon file %s:\n%s" % (cxlog.debug_str(filename), traceback.format_exc()))
return
widget.connect('expose-event', draw_widget_background, pixbuf)
# pylint: disable=C0103
def blend_colors(a, b, a_weight, b_weight):
total_weight = a_weight + b_weight
return gtk.gdk.Color(
(a.red * a_weight + b.red * b_weight) // total_weight,
(a.green * a_weight + b.green * b_weight) // total_weight,
(a.blue * a_weight + b.blue * b_weight) // total_weight)
def recolor_pixbuf(pixbuf, color, symbolic=False):
width = pixbuf.get_property('width')
height = pixbuf.get_property('height')
result = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, width, height)
pixbuf.copy_area(0, 0, width, height, result, 0, 0)
pixels = ctypes.cast(ctypes.c_void_p(hash(result.get_property('pixels'))), ctypes.POINTER(ctypes.c_ubyte))
r = color.red // 256
g = color.green // 256
b = color.blue // 256
stride = result.get_property('rowstride')
for y in range(height):
i = y * stride
for _x in range(width):
if not symbolic or (0xbe == pixels[i] == pixels[i+1] == pixels[i+2]):
pixels[i] = r
pixels[i+1] = g
pixels[i+2] = b
i += 4
return result
#####
#
# CXMessageDlg
#
#####
def _messagedlg_handle_response(dialog, response, response_function, close_on_response, user_data):
if close_on_response:
dialog.destroy()
if response_function:
if user_data is not None:
result = response_function(response, user_data)
else:
result = response_function(response)
if not close_on_response and result:
dialog.destroy()
def CXMessageDlg(message=None, primary=None, secondary=None, markup=None, buttons=gtk.BUTTONS_OK, response_function=None, close_on_response=True, parent=None, user_data=None, button_array=None, message_type=None):
if not markup:
if message is None:
text = []
if primary is not None:
text.append(u'<span weight="bold" size="larger">%s</span>' % cxutils.html_escape(primary))
if secondary is not None:
text.append(cxutils.html_escape(secondary))
markup = '\n\n'.join(text)
else:
markup = cxutils.html_escape(message)
else:
# is an important HTML typographic entity (e.g. in French) but
# it is not supported by GTK+. So manually convert it to the
# corresponding Unicode character
markup = markup.replace(u" ", u"\u00a0")
if message_type is None:
cxlog.warn("Callers to cxguitools.CXMessageDlg should always set an alert type.")
message_type = gtk.MESSAGE_INFO
if button_array:
dialog = gtk.MessageDialog(buttons=gtk.BUTTONS_NONE, flags=gtk.DIALOG_MODAL, parent=parent, type=message_type)
for button in button_array:
dialog.add_button(button[0], button[1])
else:
dialog = gtk.MessageDialog(buttons=buttons, flags=gtk.DIALOG_MODAL, parent=parent, type=message_type)
dialog.set_markup(markup)
dialog.connect('response', _messagedlg_handle_response, response_function, close_on_response, user_data)
dialog.show()
def _malware_response(response, user_data):
c4pfile, stop_loop = user_data
if response == 0:
# More Info
url = 'http://www.codeweavers.com/support/wiki/malware/' + urllib.quote_plus(c4pfile.malware_appid)
cxutils.launch_url(url)
return False
else:
# Close
if stop_loop:
gtk.main_quit()
return True
def show_malware_dialog(c4pfile, parent=None, stop_loop=False):
primary = _("The file '%(filename)s' contains malware") % {'filename': c4pfile.filename}
secondary = _("This file cannot be used because it would install malicious software or otherwise harm your computer.")
CXMessageDlg(secondary=secondary, primary=primary,
button_array=[[_(u"More Info\u2026"), 0],
[gtk.STOCK_CLOSE, 1]],
user_data=(c4pfile, stop_loop),
response_function=_malware_response,
close_on_response=False,
parent=parent,
message_type=gtk.MESSAGE_ERROR)
#####
#
# Standard warning dialog if running as root
#
#####
def return_from_root_check(response):
if response:
sys.exit()
# dismiss this dialog
gtk.main_quit()
# This should be called before loading the main dialog to make sure the code
# this protects is not run at all
def warn_if_root():
if os.getuid() == 0:
# We probably shouldn't be running as root.
warning = _("Running this tool as root is very dangerous, and will only allow you to configure CrossOver for the root user.")
if distversion.HAS_MULTI_USER:
warning = _("%s\n\nIf you wish to create a bottle that all the users of this computer can access, log in as a user and use the 'Publish Bottle' feature.") % warning
CXMessageDlg(warning, buttons=None, button_array=[[_("Exit"), 1], [_("Configure CrossOver for Root"), 0]], response_function=return_from_root_check, message_type=gtk.MESSAGE_WARNING)
gtk.main()
#####
#
# Standard File Picker selectors
#
#####
FILTERS_RUNNABLE = set(('exe', 'lnk'))
FILTERS_INSTALLABLE = set(('install',))
FILTERS_CXARCHIVES = set(('archives', 'cxarchives'))
FILTERS_ALLFILES = set(('all',))
def add_ipattern(file_picker, pattern):
"""Adds a case-insensitive pattern based on the given case-sensitive one.
"""
insensitive = []
for char in pattern:
if char.isalpha():
insensitive.append("[%s%s]" % (char.lower(), char.upper()))
else:
insensitive.append(char)
file_picker.add_pattern("".join(insensitive))
def add_filters(file_picker, filters):
"""Adds the specified set of standard file filters to the file picker."""
file_filters = {}
default = ''
if 'archives' in filters:
file_filter = gtk.FileFilter()
# These are Unix files so keep them case-sensitive
file_filter.add_pattern("*.cpio.7z")
file_filter.add_pattern("*.cpio.gz")
file_filter.add_pattern("*.cpio.bz2")
file_filter.add_pattern("*.cpio.Z")
file_filter.add_pattern("*.tar.7z")
file_filter.add_pattern("*.tar.gz")
file_filter.add_pattern("*.tar.bz2")
file_filter.add_pattern("*.tar.Z")
file_filter.add_pattern("*.tgz")
file_filters[_("Unix Archives")] = file_filter
if 'cxarchives' in filters:
file_filter = gtk.FileFilter()
# These are Unix files so keep them case-sensitive
file_filter.add_pattern("*.cxarchive")
default = _("CrossOver Bottle Archives")
file_filters[default] = file_filter
if 'exe' in filters:
file_filter = gtk.FileFilter()
add_ipattern(file_filter, "*.bat")
add_ipattern(file_filter, "*.cmd")
add_ipattern(file_filter, "*.com")
add_ipattern(file_filter, "*.exe")
add_ipattern(file_filter, "autorun.inf")
default = _("Windows Programs")
file_filters[default] = file_filter
if 'install' in filters:
file_filter = gtk.FileFilter()
# executables
add_ipattern(file_filter, "*.bat")
add_ipattern(file_filter, "*.cmd")
add_ipattern(file_filter, "*.com")
add_ipattern(file_filter, "*.exe")
add_ipattern(file_filter, "autorun.inf")
# fonts
add_ipattern(file_filter, "*.otf")
add_ipattern(file_filter, "*.ttc")
add_ipattern(file_filter, "*.ttf")
# msi
add_ipattern(file_filter, "*.msi")
add_ipattern(file_filter, "*.msp")
# archives
add_ipattern(file_filter, "*.zip")
add_ipattern(file_filter, "*.cab")
add_ipattern(file_filter, "*.tgz")
add_ipattern(file_filter, "*.tar.gz")
add_ipattern(file_filter, "*.tar.bz2")
add_ipattern(file_filter, "*.tar")
add_ipattern(file_filter, "*.tbz")
add_ipattern(file_filter, "*.tb2")
add_ipattern(file_filter, "*.rar")
add_ipattern(file_filter, "*.7z")
default = _("Installer Files")
file_filters[default] = file_filter
if 'lnk' in filters:
file_filter = gtk.FileFilter()
add_ipattern(file_filter, "*.lnk")
file_filters[_("Windows Shortcuts")] = file_filter
if 'all' in filters:
file_filter = gtk.FileFilter()
add_ipattern(file_filter, "*")
file_filters[_("All Files")] = file_filter
names = file_filters.keys()
names.sort()
for name in names:
file_filter = file_filters[name]
file_filter.set_name(name)
file_picker.add_filter(file_filter)
if name == default:
# Make this filter the default
file_picker.set_filter(file_filter)
def _skip_confirm_extensionless(file_picker):
"""If we're going to append an extension to a filename,
skip the overwrite confirm dialog.
"""
# pylint: disable=R0201
filename = os.path.basename(file_picker.get_filename())
extension = os.path.splitext(filename)[1]
if extension == "":
return gtk.FILE_CHOOSER_CONFIRMATION_ACCEPT_FILENAME
return gtk.FILE_CHOOSER_CONFIRMATION_CONFIRM
def _append_extension(file_picker, response, default_extension):
"""Append extension to a filename if it lacks one."""
# pylint: disable=R0201
if response == gtk.RESPONSE_OK:
filename = os.path.basename(file_picker.get_filename())
basename = os.path.splitext(filename)[0]
extension = os.path.splitext(filename)[1]
if extension == "":
# we have to restart the 'response' event because the overwrite check already happened
file_picker.stop_emission('response')
filename = basename + "." + default_extension
file_picker.set_current_name(filename)
file_picker.response(gtk.RESPONSE_OK)
return True
def set_default_extension(file_picker, extension):
file_picker.connect("response", _append_extension, extension)
file_picker.connect("confirm-overwrite", _skip_confirm_extensionless)
def show_help(page):
for lang in cxutils.get_preferred_languages():
# We must convert the language id to match the naming of the
# $CX_ROOT/doc folders. Note that on Debian this also relies on the
# doc symbolic link.
if lang == "":
lang = "en"
else:
lang = lang.replace('-', '_')
filename = os.path.join(cxutils.CX_ROOT, 'doc', lang, "html", page)
if os.path.exists(filename):
cxutils.launch_url(filename)
return
message = _("Sorry, the '%s' documentation page appears to be missing!") % page
CXMessageDlg(message, buttons=None, button_array=[[_("Close"), 1]], message_type=gtk.MESSAGE_WARNING)