def __init__(self): Gtk.Window.__init__(self, title=cn.App.application_name) self.hbar = hb.Headerbar(self) self.set_titlebar(self.hbar) HGtk = hl.HGtk() HGtk.set_dark_mode(1) self.stack = sk.Stack(self) self.add(self.stack) self.screen = Gdk.Screen.get_default() self.css_provider = Gtk.CssProvider() try: # development css self.css_provider.load_from_path('../data/style.css') except GLib.Error: # production css self.css_provider.load_from_path( '/usr/share/com.github.mirkobrombin.bottles/bottles/style.css') except GLib.Error: # different python version css self.css_provider.load_from_path('/usr/bin/bottles/style.css') except GLib.Error: print('Couldn\'t load style.css') exit(1) self.context = Gtk.StyleContext() self.context.add_provider_for_screen(self.screen, self.css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
def __init__(self, parent): Gtk.HeaderBar.__init__(self) self.parent = parent self.wine = w.Wine(self) self.HGtk = hl.HGtk() locale_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locale') try: current_locale, encoding = locale.getdefaultlocale() translate = gettext.translation ('bottles', locale_path, [current_locale] ) _ = translate.gettext except FileNotFoundError: _ = str self.set_show_close_button(True) self.props.title = cn.App.application_name # help button # self.help = Gtk.Button.new_from_icon_name("help-contents", Gtk.IconSize.LARGE_TOOLBAR) # self.help.connect("clicked", self.on_help_clicked) # self.pack_end(self.help) # trash button self.trash = Gtk.Button() self.trash = Gtk.Button.new_from_icon_name("edit-delete", Gtk.IconSize.LARGE_TOOLBAR) self.trash.connect("clicked", self.on_trash_clicked) self.pack_end(self.trash) # properties button self.properties = Gtk.Button() self.properties = Gtk.Button.new_from_icon_name("document-properties", Gtk.IconSize.LARGE_TOOLBAR) self.properties.connect("clicked", self.on_properties_clicked) self.pack_end(self.properties) # save button self.save = Gtk.Button(_('Save')) self.save.connect("clicked", self.on_save_clicked) self.HGtk.add_class(self.save, "suggested-action") self.pack_end(self.save) # spinner button self.spinner = Gtk.Spinner() self.pack_end(self.spinner) # back button self.back = Gtk.Button(_('Return')) self.back.connect("clicked", self.on_back_clicked) Gtk.StyleContext.add_class(self.back.get_style_context(), "back-button") self.pack_start(self.back)
def __init__(self): Gtk.Window.__init__(self, title=cn.App.application_name) self.hbar = hb.Headerbar(self) self.set_titlebar(self.hbar) HGtk = hl.HGtk() HGtk.set_dark_mode(1) self.stack = sk.Stack(self) self.add(self.stack) self.screen = Gdk.Screen.get_default() self.css_provider = Gtk.CssProvider() try: self.css_provider.load_from_path('../data/style.css') except GLib.Error: self.css_provider.load_from_path( '/usr/local/bin/bottles/style.css') self.context = Gtk.StyleContext() self.context.add_provider_for_screen(self.screen, self.css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
class Wine: HGtk = hl.HGtk() working_dir = str(Path.home()) + "/.Bottles/" working_dir_link = str(Path.home()) + "/My Bottles" POL_working_dir = str(Path.home()) + "/.PlayOnLinux/wineprefix/" def __init__(self, parent): self.parent = parent def get_wine_version(): version = subprocess.check_output(["wine", "--version"]) return str(version).replace("b'wine-", "").replace("\\n'", "") def check_work_dir(self): HLog.info( _('Checking for .Bottles directory on: %s' % self.working_dir)) if not os.path.exists(self.working_dir): HLog.warning( '[NO_BOTTLES_DIR] ' + _('I did not find the Bottles directory, creating in: %s' % self.working_dir)) os.mkdir(self.working_dir) HLog.info( _('Directory created, now creating the symlink: %s' % self.working_dir_link)) os.symlink(self.working_dir, self.working_dir_link) else: HLog.success(_('Bottles directory founded!')) def run_winecfg(self, working_dir): T_Winecfg(working_dir).start() def run_winetricks(self, working_dir): T_Wintricks(working_dir).start() def run_console(self, working_dir): T_Console(working_dir).start() def run_monitor(self, working_dir): T_Monitor(working_dir).start() def run_control(self, working_dir): T_Control(working_dir).start() def run_regedit(self, working_dir): T_Regedit(working_dir).start() def run_uninstaller(self, working_dir): T_Uninstaller(working_dir).start() def run_wineboot(self, working_dir): T_Wineboot(working_dir).start() def run_debug(self, working_dir): T_Debug(working_dir).start() def run_clone(self, working_dir): self.parent.parent.parent.hbar.spinner.set_tooltip_text( _('Cloning the bottle..')) self.parent.parent.parent.hbar.spinner.start() T_Clone(working_dir, self.parent).start() def run_software(self, working_dir, file_src): T_Software(working_dir, file_src).start() def check_special_chars(self, string): if not re.match(r'^\w+$', string): HLog.error('[BOTTLE_NAME_ERROR] ' + _('Bottle name can not contain special characters')) message_dialog = Gtk.MessageDialog.new_with_image_from_icon_name( "BOTTLE_NAME_ERROR", _('Bottle name can not contain special characters'), "dialog-warning", Gtk.ButtonsType.CANCEL) message_dialog.set_transient_for(self.parent.parent) message_dialog.set_flags = Gtk.DialogFlags.MODAL message_dialog.connect("response", self.md_ok) message_dialog.show_all() return False else: return True def create_bottle(self, name, arch): if self.check_special_chars(name): # create dir self.working_prefix_dir = self.working_dir + "prefix_" + name if not os.path.exists(self.working_prefix_dir): # create new bottle HLog.info(_('Creating new bottle with arch: %s' % arch)) if arch == "32 Bit": w_arch = "win32" else: w_arch = "win64" subprocess.call("WINEPREFIX=" + self.working_prefix_dir + " WINEARCH=" + w_arch + " wine wineboot", shell=True) # create version file version_bottle = self.working_prefix_dir + "/version.bottle" with open(version_bottle, "w") as f: f.write(arch) else: HLog.warning('[BOTTLE_ALREADY_EXISTS] ' + _( 'There is already a bottle named: %s, redirecting to it.' % name)) self.detail_bottle(name) # re-fill list lt = self.parent.parent.stack.list_all lt.generate_entries(True) def list_bottles(self): HLog.info(_('Generating the bottles list..')) bottles = [] try: walk = next(os.walk(self.working_dir))[1] for w in walk: # Arch try: with open(self.working_dir + w + "/version.bottle", 'r') as arch_f: arch = arch_f.read().replace('\n', '') except FileNotFoundError: HLog.warning('[NO_VERSION_FILE] ' + _( 'I can not find the version file for %s, I assume it is a 32 Bit.' % w)) arch = "32 Bit" # Size size = subprocess.run(['du', '-sh', self.working_dir + w], stdout=subprocess.PIPE) size = str(size.stdout).split('\\t', 1)[0].replace("b'", '') bottles.append([w, arch, size]) except StopIteration: pass return bottles def list_POLs(self): HLog.info(_('Generating the POL wineprefix list..')) POLs = [] try: walk = next(os.walk(self.POL_working_dir))[1] for w in walk: try: with open(self.POL_working_dir + w + "/playonlinux.cfg", 'r') as f: rows = f.readlines() # Arch if rows[0].startswith("ARCH="): arch = rows[0] elif rows[1].startswith("ARCH="): arch = rows[1] else: arch = rows[2] arch = arch.replace('\n', '').replace('ARCH=', '') arch = '32 Bit' if arch == 'x86' else '64 Bit' # Version if rows[0].startswith("VERSION="): version = rows[0] elif rows[1].startswith("VERSION="): version = rows[1] else: version = rows[2] version = version.replace('\n', '').replace('VERSION=', '') # Size size = subprocess.run( ['du', '-sh', self.POL_working_dir + w], stdout=subprocess.PIPE) size = str(size.stdout).split('\\t', 1)[0].replace("b'", '') POLs.append([w, arch, version, size]) except FileNotFoundError: HLog.warning('[POL_WITHOUT_CONFIG] ' + _( 'The POL wineprefix: %s does not contain the configuration file. Ignoring' % w)) pass except StopIteration: pass return POLs def remove_bottle(self, bottle_name): HLog.info(_('Removing bottle: %s' % bottle_name)) if self.check_special_chars(bottle_name): shutil.rmtree(self.working_dir + bottle_name, ignore_errors=True) # re-fill list lt = self.parent.parent.stack.list_all lt.generate_entries(True) def convert_POL(self, POL_name, arch): message_dialog = Granite.MessageDialog.new_with_image_from_icon_name( "POL_TO_BOTTLE_DISCLAIMER", _('This process converts the POL wineprefix into a bottle. \n\nNote that Bottles uses the Wine version installed on the system. After the conversion, the programs installed in the bottle may not work properly.\n\nThe original version of the POL wineprefix will remain intact.' ), "dialog-warning", Gtk.ButtonsType.CANCEL) message_dialog.set_transient_for(self.parent.parent) message_dialog.set_flags = Gtk.DialogFlags.MODAL message_dialog.connect("response", self.md_ok) message_dialog.show_all() self.parent.spinner.set_tooltip_text(_('Converting POL to bottle..')) self.parent.spinner.start() T_POL_Convert(self.working_dir, self.POL_working_dir, POL_name, arch, self.parent).start() def detail_bottle(self, name): HLog.info(_('Loading details for bottle: %s' % name)) # get detail data dt = self.parent.parent.stack.detail self.parent.properties.hide() self.parent.trash.hide() if name.find("prefix_") == -1: dt.working_dir = self.working_dir + "prefix_" + name else: dt.working_dir = self.working_dir + name with open(dt.working_dir + "/version.bottle", 'r') as arch_f: arch = arch_f.read().replace('\n', '') HLog.info(_('This is a %s bottle' % arch)) if arch == "32 Bit": version = subprocess.check_output(["wine", "--version"]) else: version = subprocess.check_output(["wine64", "--version"]) version = str(version) version = version.replace("b'", "") version = version.replace("\\n'", "") # remove prefix_ from bottle name name = name.replace("prefix_", "") # set detail title and description self.parent.props.title = name + " " + version + " (" + arch + ")" # change stack to detail self.parent.save.hide() self.parent.parent.stack.stack.set_visible_child_name("detail") ''' MessageDialog responses ''' def md_ok(self, widget, response_id): widget.destroy()
class Search: HGtk = hl.HGtk() HUser = hl.HUser() HPath = hl.HPath() HString = hl.HString() ''' Define the names of the lists we use for indexing search results ''' index_data = [ ] # contains the list of windows and applications (id, str, str) index_windows_act = [] # contains xid for windows (id, str) index_apps_act = [] # contains all commands for applications (id, str) search_entry = None i = 0 # Params item_height = 26 # This is the height size for each treeview row trim_limit = 50 # Limit characters per line # Load Translate try: current_locale, encoding = locale.getdefaultlocale() locale_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locale') translate = gettext.translation(cn.App.application_shortname, locale_path, [current_locale]) _ = translate.gettext except FileNotFoundError: _ = str def __init__(self, parent): self.parent = parent # I use this to reach the window and widgets self.search_entry = self.parent.search_entry ''' Here we create the application index (all apps from /usr/share/applications) ''' def make_index_apps(self): for appinfo in Gio.AppInfo.get_all(): # <https://developer.gnome.org/pygobject/stable/class-gioappinfo.html> if appinfo.supports_files() or appinfo.supports_uris(): appname = appinfo.get_name() appcmd = appinfo.get_commandline() self.index_data.append([ self.HString.trim(appname, self.trim_limit), self.i, "[app]" ]) self.index_apps_act.append([self.index_data[-1][1], appcmd]) self.i = self.i + 1 ''' Here we create the windows index (all open windows) ''' def make_index_windows(self): windows = Wnck.Screen.get_default().get_windows() for w in windows: self.index_data.append([ self.HString.trim(w.get_name(), self.trim_limit), self.i, "[win]" ]) self.index_windows_act.append( [self.index_data[-1][0], w.get_xid()]) self.i = self.i + 1 ''' Create index_data and index_final ''' def index(self): self.i = 0 # To avoid duplicate results, empty indexes self.index_apps_act = [] self.index_windows_act = [] self.index_data = [] # Generate indexes self.make_index_apps() self.make_index_windows() ''' Shows a window from its xid ''' def show_window(self, xid): print(Wnck.Window.get(xid).activate(time.time())) ''' Search start from here. Here I filter the indexes and get the "filter" list ''' def find(self, search_text): # Get widgets self.results = self.parent.stack.results # Check for empty if search_text == "": self.results.resize(0) else: self.index_data = [] self.index() found = [] if search_text.startswith("!calc"): clean_data = search_text.replace("!calc ", "") found.append(b_calc.run(clean_data)) else: # Get data for f, i, t in self.index_data: if search_text in f: found.append([f, i, t]) if len(found) == 0: found.append([self._('I did not find anything'), 0, "[none]"]) # Resize results box (calculate the number of results not > 250) estimated_results_size = len(found) * self.item_height if estimated_results_size > 250: self.results.resize(250) else: self.results.resize(estimated_results_size) # Return results self.results.generate_items(found, True) ''' Check result type and call action ''' def do(self, result): if result[2] == "[none]" or result[2] == "[bang]": pass elif result[2] == "[app]": # Application detected for i, a in self.index_apps_act: if result[1] == i: os.system(a) elif result[2] == "[win]": # Window detected for i, x in self.index_windows_act: if result[1] == i: self.show_window(x)
class Window(Gtk.Dialog): HGtk = hl.HGtk() def __init__(self): Gtk.Dialog.__init__(self, title=cn.App.application_name, width_request=550, resizable=False, icon_name=cn.App.application_id) # Load Translate try: current_locale, encoding = locale.getdefaultlocale() locale_path = os.path.join( os.path.abspath(os.path.dirname(__file__)), 'locale') translate = gettext.translation(cn.App.application_shortname, locale_path, [current_locale]) _ = translate.gettext except FileNotFoundError: _ = str # Dialog params self.HGtk.add_class(self, Gtk.STYLE_CLASS_FLAT) self.set_deletable(False) self.set_border_width(0) # Content params self.get_action_area().destroy() self.main_content = self.get_content_area() self.main_content.set_spacing(0) self.main_content.set_border_width(0) # Search Box self.search_box = Gtk.Frame() self.HGtk.add_class(self.search_box, "SearchBox") self.main_content.add(self.search_box) # Search Entry self.search_entry = Gtk.SearchEntry() self.HGtk.add_class(self.search_entry, "SearchEntry") self.search_entry.set_placeholder_text(_('Search..')) self.search_entry.connect("key-release-event", self.on_entry_key_release) self.search_box.add(self.search_entry) # Stack self.stack = sk.Stack(self) self.main_content.add(self.stack) # Search self.Search = sh.Search(self) # Style self.screen = Gdk.Screen.get_default() self.css_provider = Gtk.CssProvider() try: self.css_provider.load_from_path('../data/style.css') except GLib.Error: self.css_provider.load_from_path( '/usr/local/bin/kangaroo/style.css') except GLib.Error: self.css_provider.load_from_path('/usr/bin/kangaroo/style.css') except GLib.Error: print('Couldn\'t load style.css') exit(1) self.context = Gtk.StyleContext() self.context.add_provider_for_screen(self.screen, self.css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) def on_entry_key_release(self, entry, ev, data=None): # If Escape pressed, close Kangaroo if ev.keyval == Gdk.KEY_Escape: exit() search_text = entry.get_text() self.Search.find(search_text)
def __init__(self, parent): Gtk.HeaderBar.__init__(self) self.parent = parent self.wine = w.Wine(self) self.HGtk = hl.HGtk() self.set_name("WineHeaderbar") locale_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locale') try: current_locale, encoding = locale.getdefaultlocale() translate = gettext.translation (cn.App.application_shortname, locale_path, [current_locale] ) _ = translate.gettext except FileNotFoundError: _ = str self.set_show_close_button(True) self.props.title = cn.App.application_name # help button # self.help = Gtk.Button.new_from_icon_name("help-contents", Gtk.IconSize.LARGE_TOOLBAR) # self.help.connect("clicked", self.on_help_clicked) # self.pack_end(self.help) # trash button self.trash = Gtk.Button() self.trash = Gtk.Button.new_from_icon_name("user-trash-symbolic", Gtk.IconSize.SMALL_TOOLBAR) self.trash.connect("clicked", self.on_trash_clicked) self.pack_end(self.trash) # properties button self.properties = Gtk.Button() self.properties = Gtk.Button.new_from_icon_name("document-properties-symbolic", Gtk.IconSize.SMALL_TOOLBAR) self.properties.connect("clicked", self.on_properties_clicked) self.pack_end(self.properties) # save button self.save = Gtk.Button(_('Save')) self.save.connect("clicked", self.on_save_clicked) self.HGtk.add_class(self.save, "suggested-action") self.pack_end(self.save) # start button # [INFO] This button shows the applications installed in the selected bottle # self.start = Gtk.Button() # self.start = Gtk.Button.new_from_icon_name("applications-other", Gtk.IconSize.LARGE_TOOLBAR) # self.start.connect("clicked", self.on_start_clicked) # self.pack_end(self.start) # convert button self.convert = Gtk.Button() self.convert = Gtk.Button.new_from_icon_name("rotate-symbolic", Gtk.IconSize.SMALL_TOOLBAR) self.convert.connect("clicked", self.on_convert_clicked) self.pack_end(self.convert) # refresh button self.refresh = Gtk.Button() self.refresh = Gtk.Button.new_from_icon_name("view-refresh-symbolic", Gtk.IconSize.SMALL_TOOLBAR) self.refresh.connect("clicked", self.on_refresh_clicked) self.pack_end(self.refresh) # spinner button self.spinner = Gtk.Spinner() self.pack_end(self.spinner) # back button self.back = Gtk.Button.new_from_icon_name("go-previous-symbolic", Gtk.IconSize.SMALL_TOOLBAR) self.back.connect("clicked", self.on_back_clicked) Gtk.StyleContext.add_class(self.back.get_style_context(), "back-button") self.pack_start(self.back)
class Wine: HGtk = hl.HGtk() working_dir = str(Path.home()) + "/.Bottles/" working_dir_link = str(Path.home()) + "/My Bottles" try: current_locale, encoding = locale.getdefaultlocale() locale_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locale') translate = gettext.translation('bottles', locale_path, [current_locale]) _ = translate.gettext except FileNotFoundError: _ = str def __init__(self, parent): self.parent = parent def check_work_dir(self): if not os.path.exists(self.working_dir): os.mkdir(self.working_dir) os.symlink(self.working_dir, self.working_dir_link) def run_winecfg(self, working_dir): T_Winecfg(working_dir).start() def run_winetricks(self, working_dir): T_Wintricks(working_dir).start() def run_console(self, working_dir): T_Console(working_dir).start() def run_monitor(self, working_dir): T_Monitor(working_dir).start() def run_control(self, working_dir): T_Control(working_dir).start() def run_regedit(self, working_dir): T_Regedit(working_dir).start() def run_uninstaller(self, working_dir): T_Uninstaller(working_dir).start() def run_wineboot(self, working_dir): T_Wineboot(working_dir).start() def run_debug(self, working_dir): T_Debug(working_dir).start() def run_clone(self, working_dir): self.parent.parent.parent.hbar.spinner.start() T_Clone(working_dir, self.parent).start() def run_software(self, working_dir, file_src): T_Software(working_dir, file_src).start() def check_special_chars(self, string): if not re.match(r'^\w+$', string): alert = al.Alert( self.parent.parent, "BOTTLE_NAME_ERROR: " + self._('Bottle name can not contain special characters'), 600, 90) response = alert.run() if response == Gtk.ResponseType.OK: alert.destroy() return False else: return True def create_bottle(self, name, arch): if self.check_special_chars(name): print( self._('Creating a bottle with name:') + name + " and arch: " + arch) # create dir self.working_prefix_dir = self.working_dir + "prefix_" + name if not os.path.exists(self.working_prefix_dir): os.mkdir(self.working_prefix_dir) version_bottle = self.working_prefix_dir + "/version.bottle" with open(version_bottle, "w") as f: f.write(arch) # start winecfg self.run_winecfg(self.working_prefix_dir) self.detail_bottle(name) # re-fill list lt = self.parent.parent.stack.list_all lt.generate_entries(True) def list_bottles(self): bottles = [] try: walk = next(os.walk(self.working_dir))[1] for w in walk: bottles.append(w) except StopIteration: pass return bottles def remove_bottle(self, bottle_name): if self.check_special_chars(bottle_name): shutil.rmtree(self.working_dir + bottle_name, ignore_errors=True) # re-fill list lt = self.parent.parent.stack.list_all lt.generate_entries(True) def detail_bottle(self, name): # get detail data dt = self.parent.parent.stack.detail self.parent.properties.hide() self.parent.trash.hide() if name.find("prefix_") == -1: dt.working_dir = self.working_dir + "prefix_" + name else: dt.working_dir = self.working_dir + name with open(dt.working_dir + "/version.bottle", 'r') as arch_f: arch = arch_f.read().replace('\n', '') if arch == "32 Bit": version = subprocess.check_output(["wine", "--version"]) else: version = subprocess.check_output(["wine64", "--version"]) version = str(version) version = version.replace("b'", "") version = version.replace("\\n'", "") # remove prefix_ from bottle name name = name.replace("prefix_", "") # set detail title and description self.parent.props.title = name + " " + version + " (" + arch + ")" # change stack to detail self.parent.save.hide() self.parent.parent.stack.stack.set_visible_child_name("detail")